From 416a331399452521f3e9cf7e1394d020373a95c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulo=20Silva=20de=20Souza?= <77391175+joao-paulo-parity@users.noreply.github.com> Date: Tue, 6 Dec 2022 08:42:38 -0300 Subject: [PATCH 001/127] Rename some crates for publishing to crates.io (#12837) * rename some crates for publishing to crates.io * s/remote-ext/frame-remote-externalities --- Cargo.lock | 94 ++++++++++----------- client/beefy/Cargo.toml | 2 +- client/beefy/rpc/Cargo.toml | 2 +- client/merkle-mountain-range/Cargo.toml | 2 +- frame/bags-list/remote-tests/Cargo.toml | 2 +- frame/beefy-mmr/Cargo.toml | 2 +- frame/beefy-mmr/primitives/Cargo.toml | 2 +- frame/beefy/Cargo.toml | 2 +- frame/state-trie-migration/Cargo.toml | 2 +- primitives/beefy/Cargo.toml | 2 +- test-utils/runtime/Cargo.toml | 2 +- utils/frame/remote-externalities/Cargo.toml | 2 +- utils/frame/try-runtime/cli/Cargo.toml | 2 +- 13 files changed, 59 insertions(+), 59 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5118edb9e3..736501ecef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -374,7 +374,6 @@ version = "4.0.0-dev" dependencies = [ "array-bytes", "async-trait", - "beefy-primitives", "fnv", "futures", "futures-timer", @@ -396,6 +395,7 @@ dependencies = [ "sp-api", "sp-application-crypto", "sp-arithmetic", + "sp-beefy", "sp-blockchain", "sp-consensus", "sp-core", @@ -419,7 +419,6 @@ name = "beefy-gadget-rpc" version = "4.0.0-dev" dependencies = [ "beefy-gadget", - "beefy-primitives", "futures", "jsonrpsee", "log", @@ -429,6 +428,7 @@ dependencies = [ "sc-utils", "serde", "serde_json", + "sp-beefy", "sp-core", "sp-runtime", "substrate-test-runtime-client", @@ -441,31 +441,13 @@ name = "beefy-merkle-tree" version = "4.0.0-dev" dependencies = [ "array-bytes", - "beefy-primitives", "env_logger", "log", "sp-api", + "sp-beefy", "sp-runtime", ] -[[package]] -name = "beefy-primitives" -version = "4.0.0-dev" -dependencies = [ - "array-bytes", - "parity-scale-codec", - "scale-info", - "serde", - "sp-api", - "sp-application-crypto", - "sp-core", - "sp-io", - "sp-keystore", - "sp-mmr-primitives", - "sp-runtime", - "sp-std", -] - [[package]] name = "bincode" version = "1.3.3" @@ -2226,6 +2208,25 @@ dependencies = [ "serde", ] +[[package]] +name = "frame-remote-externalities" +version = "0.10.0-dev" +dependencies = [ + "env_logger", + "frame-support", + "log", + "pallet-elections-phragmen", + "parity-scale-codec", + "serde", + "serde_json", + "sp-core", + "sp-io", + "sp-runtime", + "sp-version", + "substrate-rpc-client", + "tokio", +] + [[package]] name = "frame-support" version = "4.0.0-dev" @@ -4139,7 +4140,6 @@ dependencies = [ name = "mmr-gadget" version = "4.0.0-dev" dependencies = [ - "beefy-primitives", "futures", "log", "parity-scale-codec", @@ -4147,6 +4147,7 @@ dependencies = [ "sc-client-api", "sc-offchain", "sp-api", + "sp-beefy", "sp-blockchain", "sp-consensus", "sp-core", @@ -5115,12 +5116,12 @@ name = "pallet-bags-list-remote-tests" version = "4.0.0-dev" dependencies = [ "frame-election-provider-support", + "frame-remote-externalities", "frame-support", "frame-system", "log", "pallet-bags-list", "pallet-staking", - "remote-externalities", "sp-core", "sp-runtime", "sp-std", @@ -5149,13 +5150,13 @@ dependencies = [ name = "pallet-beefy" version = "4.0.0-dev" dependencies = [ - "beefy-primitives", "frame-support", "frame-system", "pallet-session", "parity-scale-codec", "scale-info", "serde", + "sp-beefy", "sp-core", "sp-io", "sp-runtime", @@ -5169,7 +5170,6 @@ version = "4.0.0-dev" dependencies = [ "array-bytes", "beefy-merkle-tree", - "beefy-primitives", "frame-support", "frame-system", "log", @@ -5179,6 +5179,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", + "sp-beefy", "sp-core", "sp-io", "sp-runtime", @@ -6087,13 +6088,13 @@ name = "pallet-state-trie-migration" version = "4.0.0-dev" dependencies = [ "frame-benchmarking", + "frame-remote-externalities", "frame-support", "frame-system", "log", "pallet-balances", "parity-scale-codec", "parking_lot 0.12.1", - "remote-externalities", "scale-info", "serde", "sp-core", @@ -7189,25 +7190,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "remote-externalities" -version = "0.10.0-dev" -dependencies = [ - "env_logger", - "frame-support", - "log", - "pallet-elections-phragmen", - "parity-scale-codec", - "serde", - "serde_json", - "sp-core", - "sp-io", - "sp-runtime", - "sp-version", - "substrate-rpc-client", - "tokio", -] - [[package]] name = "remove_dir_all" version = "0.5.3" @@ -9327,6 +9309,24 @@ dependencies = [ "sp-std", ] +[[package]] +name = "sp-beefy" +version = "4.0.0-dev" +dependencies = [ + "array-bytes", + "parity-scale-codec", + "scale-info", + "serde", + "sp-api", + "sp-application-crypto", + "sp-core", + "sp-io", + "sp-keystore", + "sp-mmr-primitives", + "sp-runtime", + "sp-std", +] + [[package]] name = "sp-block-builder" version = "4.0.0-dev" @@ -10323,7 +10323,6 @@ name = "substrate-test-runtime" version = "2.0.0" dependencies = [ "beefy-merkle-tree", - "beefy-primitives", "cfg-if", "frame-support", "frame-system", @@ -10342,6 +10341,7 @@ dependencies = [ "serde", "sp-api", "sp-application-crypto", + "sp-beefy", "sp-block-builder", "sp-consensus", "sp-consensus-aura", @@ -10952,10 +10952,10 @@ name = "try-runtime-cli" version = "0.10.0-dev" dependencies = [ "clap 4.0.11", + "frame-remote-externalities", "frame-try-runtime", "log", "parity-scale-codec", - "remote-externalities", "sc-chain-spec", "sc-cli", "sc-executor", diff --git a/client/beefy/Cargo.toml b/client/beefy/Cargo.toml index b6a77f00e7..d0b36a9255 100644 --- a/client/beefy/Cargo.toml +++ b/client/beefy/Cargo.toml @@ -19,7 +19,7 @@ log = "0.4" parking_lot = "0.12.1" thiserror = "1.0" wasm-timer = "0.2.5" -beefy-primitives = { version = "4.0.0-dev", path = "../../primitives/beefy" } +beefy-primitives = { version = "4.0.0-dev", path = "../../primitives/beefy", package = "sp-beefy" } prometheus = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } sc-chain-spec = { version = "4.0.0-dev", path = "../../client/chain-spec" } sc-client-api = { version = "4.0.0-dev", path = "../api" } diff --git a/client/beefy/rpc/Cargo.toml b/client/beefy/rpc/Cargo.toml index 11ad15af19..d272258245 100644 --- a/client/beefy/rpc/Cargo.toml +++ b/client/beefy/rpc/Cargo.toml @@ -17,7 +17,7 @@ parking_lot = "0.12.1" serde = { version = "1.0.136", features = ["derive"] } thiserror = "1.0" beefy-gadget = { version = "4.0.0-dev", path = "../." } -beefy-primitives = { version = "4.0.0-dev", path = "../../../primitives/beefy" } +beefy-primitives = { version = "4.0.0-dev", path = "../../../primitives/beefy", package = "sp-beefy" } sc-rpc = { version = "4.0.0-dev", path = "../../rpc" } sc-utils = { version = "4.0.0-dev", path = "../../utils" } sp-core = { version = "7.0.0", path = "../../../primitives/core" } diff --git a/client/merkle-mountain-range/Cargo.toml b/client/merkle-mountain-range/Cargo.toml index 6e8cb8194e..e32764eff1 100644 --- a/client/merkle-mountain-range/Cargo.toml +++ b/client/merkle-mountain-range/Cargo.toml @@ -14,7 +14,7 @@ homepage = "https://substrate.io" codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3" log = "0.4" -beefy-primitives = { version = "4.0.0-dev", path = "../../primitives/beefy" } +beefy-primitives = { version = "4.0.0-dev", path = "../../primitives/beefy", package = "sp-beefy" } sc-client-api = { version = "4.0.0-dev", path = "../api" } sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } diff --git a/frame/bags-list/remote-tests/Cargo.toml b/frame/bags-list/remote-tests/Cargo.toml index de97ebd0e6..941076753b 100644 --- a/frame/bags-list/remote-tests/Cargo.toml +++ b/frame/bags-list/remote-tests/Cargo.toml @@ -28,7 +28,7 @@ sp-runtime = { path = "../../../primitives/runtime", version = "7.0.0" } sp-std = { path = "../../../primitives/std", version = "5.0.0" } # utils -remote-externalities = { path = "../../../utils/frame/remote-externalities", version = "0.10.0-dev" } +remote-externalities = { path = "../../../utils/frame/remote-externalities", version = "0.10.0-dev", package = "frame-remote-externalities" } # others log = "0.4.17" diff --git a/frame/beefy-mmr/Cargo.toml b/frame/beefy-mmr/Cargo.toml index 33b9334310..f65270acdc 100644 --- a/frame/beefy-mmr/Cargo.toml +++ b/frame/beefy-mmr/Cargo.toml @@ -15,7 +15,7 @@ log = { version = "0.4.17", default-features = false } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", optional = true } beefy-merkle-tree = { version = "4.0.0-dev", default-features = false, path = "./primitives" } -beefy-primitives = { version = "4.0.0-dev", default-features = false, path = "../../primitives/beefy" } +beefy-primitives = { version = "4.0.0-dev", default-features = false, path = "../../primitives/beefy", package = "sp-beefy" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } pallet-beefy = { version = "4.0.0-dev", default-features = false, path = "../beefy" } diff --git a/frame/beefy-mmr/primitives/Cargo.toml b/frame/beefy-mmr/primitives/Cargo.toml index edd0daa5aa..c087bc2f37 100644 --- a/frame/beefy-mmr/primitives/Cargo.toml +++ b/frame/beefy-mmr/primitives/Cargo.toml @@ -12,7 +12,7 @@ homepage = "https://substrate.io" array-bytes = { version = "4.1", optional = true } log = { version = "0.4", default-features = false, optional = true } -beefy-primitives = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/beefy" } +beefy-primitives = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/beefy", package = "sp-beefy" } sp-api = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/api" } sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } diff --git a/frame/beefy/Cargo.toml b/frame/beefy/Cargo.toml index 5cb180750a..707e8e2571 100644 --- a/frame/beefy/Cargo.toml +++ b/frame/beefy/Cargo.toml @@ -12,7 +12,7 @@ homepage = "https://substrate.io" codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", optional = true } -beefy-primitives = { version = "4.0.0-dev", default-features = false, path = "../../primitives/beefy" } +beefy-primitives = { version = "4.0.0-dev", default-features = false, path = "../../primitives/beefy", package = "sp-beefy" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } pallet-session = { version = "4.0.0-dev", default-features = false, path = "../session" } diff --git a/frame/state-trie-migration/Cargo.toml b/frame/state-trie-migration/Cargo.toml index 30d90e8b86..8d10a34077 100644 --- a/frame/state-trie-migration/Cargo.toml +++ b/frame/state-trie-migration/Cargo.toml @@ -22,7 +22,7 @@ zstd = { version = "0.11.2", default-features = false, optional = true } frame-benchmarking = { default-features = false, optional = true, path = "../benchmarking" } frame-support = { default-features = false, path = "../support" } frame-system = { default-features = false, path = "../system" } -remote-externalities = { optional = true, path = "../../utils/frame/remote-externalities" } +remote-externalities = { optional = true, path = "../../utils/frame/remote-externalities", package = "frame-remote-externalities" } sp-core = { default-features = false, path = "../../primitives/core" } sp-io = { default-features = false, path = "../../primitives/io" } sp-runtime = { default-features = false, path = "../../primitives/runtime" } diff --git a/primitives/beefy/Cargo.toml b/primitives/beefy/Cargo.toml index 586f2e4b30..a7ff212a09 100644 --- a/primitives/beefy/Cargo.toml +++ b/primitives/beefy/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "beefy-primitives" +name = "sp-beefy" version = "4.0.0-dev" authors = ["Parity Technologies "] edition = "2021" diff --git a/test-utils/runtime/Cargo.toml b/test-utils/runtime/Cargo.toml index b64a847b15..a201906f1c 100644 --- a/test-utils/runtime/Cargo.toml +++ b/test-utils/runtime/Cargo.toml @@ -13,7 +13,7 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -beefy-primitives = { version = "4.0.0-dev", default-features = false, path = "../../primitives/beefy" } +beefy-primitives = { version = "4.0.0-dev", default-features = false, path = "../../primitives/beefy", package = "sp-beefy" } beefy-merkle-tree = { version = "4.0.0-dev", default-features = false, path = "../../frame/beefy-mmr/primitives" } sp-application-crypto = { version = "7.0.0", default-features = false, path = "../../primitives/application-crypto" } sp-consensus-aura = { version = "0.10.0-dev", default-features = false, path = "../../primitives/consensus/aura" } diff --git a/utils/frame/remote-externalities/Cargo.toml b/utils/frame/remote-externalities/Cargo.toml index f24ab346c4..a39dc9d386 100644 --- a/utils/frame/remote-externalities/Cargo.toml +++ b/utils/frame/remote-externalities/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "remote-externalities" +name = "frame-remote-externalities" version = "0.10.0-dev" authors = ["Parity Technologies "] edition = "2021" diff --git a/utils/frame/try-runtime/cli/Cargo.toml b/utils/frame/try-runtime/cli/Cargo.toml index e7bbba1311..acac966aa1 100644 --- a/utils/frame/try-runtime/cli/Cargo.toml +++ b/utils/frame/try-runtime/cli/Cargo.toml @@ -18,7 +18,7 @@ log = "0.4.17" parity-scale-codec = "3.0.0" serde = "1.0.136" zstd = { version = "0.11.2", default-features = false } -remote-externalities = { version = "0.10.0-dev", path = "../../remote-externalities" } +remote-externalities = { version = "0.10.0-dev", path = "../../remote-externalities", package = "frame-remote-externalities" } sc-chain-spec = { version = "4.0.0-dev", path = "../../../../client/chain-spec" } sc-cli = { version = "0.10.0-dev", path = "../../../../client/cli" } sc-executor = { version = "0.10.0-dev", path = "../../../../client/executor" } From 44fbbd92c3a03ef541fe0880b15bba33b943272f Mon Sep 17 00:00:00 2001 From: Muharem Ismailov Date: Tue, 6 Dec 2022 15:51:03 +0100 Subject: [PATCH 002/127] Whitelist pallet preimage provider upgrade (#12834) * whitelist preimage provider upgrade * rustdocs unresolved link error fix * ".git/.scripts/bench-bot.sh" pallet dev pallet_whitelist * PreimageHash alias, remove type annotation Co-authored-by: command-bot <> --- bin/node/runtime/src/lib.rs | 2 +- frame/whitelist/src/benchmarking.rs | 29 ++++---- frame/whitelist/src/lib.rs | 54 +++++++++------ frame/whitelist/src/mock.rs | 2 +- frame/whitelist/src/tests.rs | 69 ++++++++++++++----- frame/whitelist/src/weights.rs | 101 +++++++++++++++------------- 6 files changed, 153 insertions(+), 104 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 633106e10b..8c4e70c37d 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1544,7 +1544,7 @@ impl pallet_whitelist::Config for Runtime { type RuntimeCall = RuntimeCall; type WhitelistOrigin = EnsureRoot; type DispatchWhitelistedOrigin = EnsureRoot; - type PreimageProvider = Preimage; + type Preimages = Preimage; type WeightInfo = pallet_whitelist::weights::SubstrateWeight; } diff --git a/frame/whitelist/src/benchmarking.rs b/frame/whitelist/src/benchmarking.rs index e0a758b2dd..923adc6ccf 100644 --- a/frame/whitelist/src/benchmarking.rs +++ b/frame/whitelist/src/benchmarking.rs @@ -21,10 +21,7 @@ use super::*; use frame_benchmarking::benchmarks; -use frame_support::{ - ensure, - traits::{EnsureOrigin, Get, PreimageRecipient}, -}; +use frame_support::{ensure, traits::EnsureOrigin}; #[cfg(test)] use crate::Pallet as Whitelist; @@ -40,7 +37,7 @@ benchmarks! { "call not whitelisted" ); ensure!( - T::PreimageProvider::preimage_requested(&call_hash), + T::Preimages::is_requested(&call_hash), "preimage not requested" ); } @@ -57,7 +54,7 @@ benchmarks! { "whitelist not removed" ); ensure!( - !T::PreimageProvider::preimage_requested(&call_hash), + !T::Preimages::is_requested(&call_hash), "preimage still requested" ); } @@ -66,30 +63,30 @@ benchmarks! { // If the resulting weight is too big, maybe it worth having a weight which depends // on the size of the call, with a new witness in parameter. dispatch_whitelisted_call { - let origin = T::DispatchWhitelistedOrigin::successful_origin(); // NOTE: we remove `10` because we need some bytes to encode the variants and vec length - let remark_len = >::MaxSize::get() - 10; - let remark = sp_std::vec![1u8; remark_len as usize]; + let n in 1 .. T::Preimages::MAX_LENGTH as u32 - 10; + let origin = T::DispatchWhitelistedOrigin::successful_origin(); + let remark = sp_std::vec![1u8; n as usize]; let call: ::RuntimeCall = frame_system::Call::remark { remark }.into(); let call_weight = call.get_dispatch_info().weight; let encoded_call = call.encode(); - let call_hash = T::Hashing::hash(&encoded_call[..]); + let call_encoded_len = encoded_call.len() as u32; + let call_hash = call.blake2_256().into(); Pallet::::whitelist_call(origin.clone(), call_hash) .expect("whitelisting call must be successful"); - let encoded_call = encoded_call.try_into().expect("encoded_call must be small enough"); - T::PreimageProvider::note_preimage(encoded_call); + T::Preimages::note(encoded_call.into()).unwrap(); - }: _(origin, call_hash, call_weight) + }: _(origin, call_hash, call_encoded_len, call_weight) verify { ensure!( !WhitelistedCall::::contains_key(call_hash), "whitelist not removed" ); ensure!( - !T::PreimageProvider::preimage_requested(&call_hash), + !T::Preimages::is_requested(&call_hash), "preimage still requested" ); } @@ -101,7 +98,7 @@ benchmarks! { let remark = sp_std::vec![1u8; n as usize]; let call: ::RuntimeCall = frame_system::Call::remark { remark }.into(); - let call_hash = T::Hashing::hash_of(&call); + let call_hash = call.blake2_256().into(); Pallet::::whitelist_call(origin.clone(), call_hash) .expect("whitelisting call must be successful"); @@ -112,7 +109,7 @@ benchmarks! { "whitelist not removed" ); ensure!( - !T::PreimageProvider::preimage_requested(&call_hash), + !T::Preimages::is_requested(&call_hash), "preimage still requested" ); } diff --git a/frame/whitelist/src/lib.rs b/frame/whitelist/src/lib.rs index be5fdf9e47..1b2dc94156 100644 --- a/frame/whitelist/src/lib.rs +++ b/frame/whitelist/src/lib.rs @@ -27,7 +27,7 @@ //! with the root origin. //! //! In the meantime the call corresponding to the hash must have been submitted to the pre-image -//! handler [`PreimageProvider`]. +//! handler [`pallet::Config::Preimages`]. #![cfg_attr(not(feature = "std"), no_std)] @@ -44,11 +44,12 @@ use codec::{DecodeLimit, Encode, FullCodec}; use frame_support::{ dispatch::{GetDispatchInfo, PostDispatchInfo}, ensure, - traits::{PreimageProvider, PreimageRecipient}, + traits::{Hash as PreimageHash, QueryPreimage, StorePreimage}, weights::Weight, + Hashable, }; use scale_info::TypeInfo; -use sp_runtime::traits::{Dispatchable, Hash}; +use sp_runtime::traits::Dispatchable; use sp_std::prelude::*; pub use pallet::*; @@ -80,8 +81,7 @@ pub mod pallet { type DispatchWhitelistedOrigin: EnsureOrigin; /// The handler of pre-images. - // NOTE: recipient is only needed for benchmarks. - type PreimageProvider: PreimageProvider + PreimageRecipient; + type Preimages: QueryPreimage + StorePreimage; /// The weight information for this pallet. type WeightInfo: WeightInfo; @@ -94,9 +94,9 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - CallWhitelisted { call_hash: T::Hash }, - WhitelistedCallRemoved { call_hash: T::Hash }, - WhitelistedCallDispatched { call_hash: T::Hash, result: DispatchResultWithPostInfo }, + CallWhitelisted { call_hash: PreimageHash }, + WhitelistedCallRemoved { call_hash: PreimageHash }, + WhitelistedCallDispatched { call_hash: PreimageHash, result: DispatchResultWithPostInfo }, } #[pallet::error] @@ -114,12 +114,13 @@ pub mod pallet { } #[pallet::storage] - pub type WhitelistedCall = StorageMap<_, Twox64Concat, T::Hash, (), OptionQuery>; + pub type WhitelistedCall = + StorageMap<_, Twox64Concat, PreimageHash, (), OptionQuery>; #[pallet::call] impl Pallet { #[pallet::weight(T::WeightInfo::whitelist_call())] - pub fn whitelist_call(origin: OriginFor, call_hash: T::Hash) -> DispatchResult { + pub fn whitelist_call(origin: OriginFor, call_hash: PreimageHash) -> DispatchResult { T::WhitelistOrigin::ensure_origin(origin)?; ensure!( @@ -128,7 +129,7 @@ pub mod pallet { ); WhitelistedCall::::insert(call_hash, ()); - T::PreimageProvider::request_preimage(&call_hash); + T::Preimages::request(&call_hash); Self::deposit_event(Event::::CallWhitelisted { call_hash }); @@ -136,12 +137,15 @@ pub mod pallet { } #[pallet::weight(T::WeightInfo::remove_whitelisted_call())] - pub fn remove_whitelisted_call(origin: OriginFor, call_hash: T::Hash) -> DispatchResult { + pub fn remove_whitelisted_call( + origin: OriginFor, + call_hash: PreimageHash, + ) -> DispatchResult { T::WhitelistOrigin::ensure_origin(origin)?; WhitelistedCall::::take(call_hash).ok_or(Error::::CallIsNotWhitelisted)?; - T::PreimageProvider::unrequest_preimage(&call_hash); + T::Preimages::unrequest(&call_hash); Self::deposit_event(Event::::WhitelistedCallRemoved { call_hash }); @@ -149,11 +153,13 @@ pub mod pallet { } #[pallet::weight( - T::WeightInfo::dispatch_whitelisted_call().saturating_add(*call_weight_witness) + T::WeightInfo::dispatch_whitelisted_call(*call_encoded_len) + .saturating_add(*call_weight_witness) )] pub fn dispatch_whitelisted_call( origin: OriginFor, - call_hash: T::Hash, + call_hash: PreimageHash, + call_encoded_len: u32, call_weight_witness: Weight, ) -> DispatchResultWithPostInfo { T::DispatchWhitelistedOrigin::ensure_origin(origin)?; @@ -163,8 +169,8 @@ pub mod pallet { Error::::CallIsNotWhitelisted, ); - let call = T::PreimageProvider::get_preimage(&call_hash) - .ok_or(Error::::UnavailablePreImage)?; + let call = T::Preimages::fetch(&call_hash, Some(call_encoded_len)) + .map_err(|_| Error::::UnavailablePreImage)?; let call = ::RuntimeCall::decode_all_with_depth_limit( sp_api::MAX_EXTRINSIC_DEPTH, @@ -177,8 +183,9 @@ pub mod pallet { Error::::InvalidCallWeightWitness ); - let actual_weight = Self::clean_and_dispatch(call_hash, call) - .map(|w| w.saturating_add(T::WeightInfo::dispatch_whitelisted_call())); + let actual_weight = Self::clean_and_dispatch(call_hash, call).map(|w| { + w.saturating_add(T::WeightInfo::dispatch_whitelisted_call(call_encoded_len)) + }); Ok(actual_weight.into()) } @@ -196,7 +203,7 @@ pub mod pallet { ) -> DispatchResultWithPostInfo { T::DispatchWhitelistedOrigin::ensure_origin(origin)?; - let call_hash = ::Hashing::hash_of(&call); + let call_hash = call.blake2_256().into(); ensure!( WhitelistedCall::::contains_key(call_hash), @@ -217,10 +224,13 @@ impl Pallet { /// Clean whitelisting/preimage and dispatch call. /// /// Return the call actual weight of the dispatched call if there is some. - fn clean_and_dispatch(call_hash: T::Hash, call: ::RuntimeCall) -> Option { + fn clean_and_dispatch( + call_hash: PreimageHash, + call: ::RuntimeCall, + ) -> Option { WhitelistedCall::::remove(call_hash); - T::PreimageProvider::unrequest_preimage(&call_hash); + T::Preimages::unrequest(&call_hash); let result = call.dispatch(frame_system::Origin::::Root.into()); diff --git a/frame/whitelist/src/mock.rs b/frame/whitelist/src/mock.rs index d4446cb803..ef5d3d4fb4 100644 --- a/frame/whitelist/src/mock.rs +++ b/frame/whitelist/src/mock.rs @@ -106,7 +106,7 @@ impl pallet_whitelist::Config for Test { type RuntimeCall = RuntimeCall; type WhitelistOrigin = EnsureRoot; type DispatchWhitelistedOrigin = EnsureRoot; - type PreimageProvider = Preimage; + type Preimages = Preimage; type WeightInfo = (); } diff --git a/frame/whitelist/src/tests.rs b/frame/whitelist/src/tests.rs index fd6558e83f..d04bb48c1e 100644 --- a/frame/whitelist/src/tests.rs +++ b/frame/whitelist/src/tests.rs @@ -20,7 +20,10 @@ use crate::mock::*; use codec::Encode; use frame_support::{ - assert_noop, assert_ok, dispatch::GetDispatchInfo, traits::PreimageProvider, weights::Weight, + assert_noop, assert_ok, + dispatch::GetDispatchInfo, + traits::{QueryPreimage, StorePreimage}, + weights::Weight, }; use sp_runtime::{traits::Hash, DispatchError}; @@ -43,7 +46,7 @@ fn test_whitelist_call_and_remove() { assert_ok!(Whitelist::whitelist_call(RuntimeOrigin::root(), call_hash)); - assert!(Preimage::preimage_requested(&call_hash)); + assert!(Preimage::is_requested(&call_hash)); assert_noop!( Whitelist::whitelist_call(RuntimeOrigin::root(), call_hash), @@ -57,7 +60,7 @@ fn test_whitelist_call_and_remove() { assert_ok!(Whitelist::remove_whitelisted_call(RuntimeOrigin::root(), call_hash)); - assert!(!Preimage::preimage_requested(&call_hash)); + assert!(!Preimage::is_requested(&call_hash)); assert_noop!( Whitelist::remove_whitelisted_call(RuntimeOrigin::root(), call_hash), @@ -72,33 +75,50 @@ fn test_whitelist_call_and_execute() { let call = RuntimeCall::System(frame_system::Call::remark_with_event { remark: vec![1] }); let call_weight = call.get_dispatch_info().weight; let encoded_call = call.encode(); + let call_encoded_len = encoded_call.len() as u32; let call_hash = ::Hashing::hash(&encoded_call[..]); assert_noop!( - Whitelist::dispatch_whitelisted_call(RuntimeOrigin::root(), call_hash, call_weight), + Whitelist::dispatch_whitelisted_call( + RuntimeOrigin::root(), + call_hash, + call_encoded_len, + call_weight + ), crate::Error::::CallIsNotWhitelisted, ); assert_ok!(Whitelist::whitelist_call(RuntimeOrigin::root(), call_hash)); assert_noop!( - Whitelist::dispatch_whitelisted_call(RuntimeOrigin::signed(1), call_hash, call_weight), + Whitelist::dispatch_whitelisted_call( + RuntimeOrigin::signed(1), + call_hash, + call_encoded_len, + call_weight + ), DispatchError::BadOrigin, ); assert_noop!( - Whitelist::dispatch_whitelisted_call(RuntimeOrigin::root(), call_hash, call_weight), + Whitelist::dispatch_whitelisted_call( + RuntimeOrigin::root(), + call_hash, + call_encoded_len, + call_weight + ), crate::Error::::UnavailablePreImage, ); - assert_ok!(Preimage::note_preimage(RuntimeOrigin::root(), encoded_call)); + assert_ok!(Preimage::note(encoded_call.into())); - assert!(Preimage::preimage_requested(&call_hash)); + assert!(Preimage::is_requested(&call_hash)); assert_noop!( Whitelist::dispatch_whitelisted_call( RuntimeOrigin::root(), call_hash, + call_encoded_len, call_weight - Weight::from_ref_time(1) ), crate::Error::::InvalidCallWeightWitness, @@ -107,13 +127,19 @@ fn test_whitelist_call_and_execute() { assert_ok!(Whitelist::dispatch_whitelisted_call( RuntimeOrigin::root(), call_hash, + call_encoded_len, call_weight )); - assert!(!Preimage::preimage_requested(&call_hash)); + assert!(!Preimage::is_requested(&call_hash)); assert_noop!( - Whitelist::dispatch_whitelisted_call(RuntimeOrigin::root(), call_hash, call_weight), + Whitelist::dispatch_whitelisted_call( + RuntimeOrigin::root(), + call_hash, + call_encoded_len, + call_weight + ), crate::Error::::CallIsNotWhitelisted, ); }); @@ -124,21 +150,24 @@ fn test_whitelist_call_and_execute_failing_call() { new_test_ext().execute_with(|| { let call = RuntimeCall::Whitelist(crate::Call::dispatch_whitelisted_call { call_hash: Default::default(), + call_encoded_len: Default::default(), call_weight_witness: Weight::zero(), }); let call_weight = call.get_dispatch_info().weight; let encoded_call = call.encode(); + let call_encoded_len = encoded_call.len() as u32; let call_hash = ::Hashing::hash(&encoded_call[..]); assert_ok!(Whitelist::whitelist_call(RuntimeOrigin::root(), call_hash)); - assert_ok!(Preimage::note_preimage(RuntimeOrigin::root(), encoded_call)); - assert!(Preimage::preimage_requested(&call_hash)); + assert_ok!(Preimage::note(encoded_call.into())); + assert!(Preimage::is_requested(&call_hash)); assert_ok!(Whitelist::dispatch_whitelisted_call( RuntimeOrigin::root(), call_hash, + call_encoded_len, call_weight )); - assert!(!Preimage::preimage_requested(&call_hash)); + assert!(!Preimage::is_requested(&call_hash)); }); } @@ -151,14 +180,14 @@ fn test_whitelist_call_and_execute_without_note_preimage() { let call_hash = ::Hashing::hash_of(&call); assert_ok!(Whitelist::whitelist_call(RuntimeOrigin::root(), call_hash)); - assert!(Preimage::preimage_requested(&call_hash)); + assert!(Preimage::is_requested(&call_hash)); assert_ok!(Whitelist::dispatch_whitelisted_call_with_preimage( RuntimeOrigin::root(), call.clone() )); - assert!(!Preimage::preimage_requested(&call_hash)); + assert!(!Preimage::is_requested(&call_hash)); assert_noop!( Whitelist::dispatch_whitelisted_call_with_preimage(RuntimeOrigin::root(), call), @@ -176,14 +205,20 @@ fn test_whitelist_call_and_execute_decode_consumes_all() { // Appending something does not make the encoded call invalid. // This tests that the decode function consumes all data. call.extend(call.clone()); + let call_encoded_len = call.len() as u32; let call_hash = ::Hashing::hash(&call[..]); - assert_ok!(Preimage::note_preimage(RuntimeOrigin::root(), call)); + assert_ok!(Preimage::note(call.into())); assert_ok!(Whitelist::whitelist_call(RuntimeOrigin::root(), call_hash)); assert_noop!( - Whitelist::dispatch_whitelisted_call(RuntimeOrigin::root(), call_hash, call_weight), + Whitelist::dispatch_whitelisted_call( + RuntimeOrigin::root(), + call_hash, + call_encoded_len, + call_weight + ), crate::Error::::UndecodableCall, ); }); diff --git a/frame/whitelist/src/weights.rs b/frame/whitelist/src/weights.rs index 22d238d0fd..efd48d6578 100644 --- a/frame/whitelist/src/weights.rs +++ b/frame/whitelist/src/weights.rs @@ -18,24 +18,25 @@ //! Autogenerated weights for pallet_whitelist //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2022-12-05, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// ./target/production/substrate +// /home/benchbot/cargo_target_dir/production/substrate // benchmark // pallet -// --chain=dev // --steps=50 // --repeat=20 -// --pallet=pallet_whitelist // --extrinsic=* // --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 -// --output=./frame/whitelist/src/weights.rs +// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/substrate/.git/.artifacts/bench.json +// --pallet=pallet_whitelist +// --chain=dev // --header=./HEADER-APACHE2 +// --output=./frame/whitelist/src/weights.rs // --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] @@ -49,7 +50,7 @@ use sp_std::marker::PhantomData; pub trait WeightInfo { fn whitelist_call() -> Weight; fn remove_whitelisted_call() -> Weight; - fn dispatch_whitelisted_call() -> Weight; + fn dispatch_whitelisted_call(n: u32, ) -> Weight; fn dispatch_whitelisted_call_with_preimage(n: u32, ) -> Weight; } @@ -59,38 +60,41 @@ impl WeightInfo for SubstrateWeight { // Storage: Whitelist WhitelistedCall (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) fn whitelist_call() -> Weight { - // Minimum execution time: 26_352 nanoseconds. - Weight::from_ref_time(26_727_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Minimum execution time: 26_261 nanoseconds. + Weight::from_ref_time(26_842_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) } // Storage: Whitelist WhitelistedCall (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) fn remove_whitelisted_call() -> Weight { - // Minimum execution time: 25_536 nanoseconds. - Weight::from_ref_time(25_969_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Minimum execution time: 25_092 nanoseconds. + Weight::from_ref_time(25_903_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) } // Storage: Whitelist WhitelistedCall (r:1 w:1) - // Storage: Preimage StatusFor (r:1 w:1) // Storage: Preimage PreimageFor (r:1 w:1) - fn dispatch_whitelisted_call() -> Weight { - // Minimum execution time: 4_802_466 nanoseconds. - Weight::from_ref_time(4_820_197_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Storage: Preimage StatusFor (r:1 w:1) + /// The range of component `n` is `[1, 4194294]`. + fn dispatch_whitelisted_call(n: u32, ) -> Weight { + // Minimum execution time: 36_685 nanoseconds. + Weight::from_ref_time(37_167_000) + // Standard Error: 0 + .saturating_add(Weight::from_ref_time(1_144).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) } // Storage: Whitelist WhitelistedCall (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) /// The range of component `n` is `[1, 10000]`. fn dispatch_whitelisted_call_with_preimage(n: u32, ) -> Weight { - // Minimum execution time: 29_184 nanoseconds. - Weight::from_ref_time(30_530_970 as u64) - // Standard Error: 7 - .saturating_add(Weight::from_ref_time(1_496 as u64).saturating_mul(n as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Minimum execution time: 29_187 nanoseconds. + Weight::from_ref_time(29_896_714) + // Standard Error: 6 + .saturating_add(Weight::from_ref_time(1_505).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) } } @@ -99,37 +103,40 @@ impl WeightInfo for () { // Storage: Whitelist WhitelistedCall (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) fn whitelist_call() -> Weight { - // Minimum execution time: 26_352 nanoseconds. - Weight::from_ref_time(26_727_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Minimum execution time: 26_261 nanoseconds. + Weight::from_ref_time(26_842_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) } // Storage: Whitelist WhitelistedCall (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) fn remove_whitelisted_call() -> Weight { - // Minimum execution time: 25_536 nanoseconds. - Weight::from_ref_time(25_969_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Minimum execution time: 25_092 nanoseconds. + Weight::from_ref_time(25_903_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) } // Storage: Whitelist WhitelistedCall (r:1 w:1) - // Storage: Preimage StatusFor (r:1 w:1) // Storage: Preimage PreimageFor (r:1 w:1) - fn dispatch_whitelisted_call() -> Weight { - // Minimum execution time: 4_802_466 nanoseconds. - Weight::from_ref_time(4_820_197_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Storage: Preimage StatusFor (r:1 w:1) + /// The range of component `n` is `[1, 4194294]`. + fn dispatch_whitelisted_call(n: u32, ) -> Weight { + // Minimum execution time: 36_685 nanoseconds. + Weight::from_ref_time(37_167_000) + // Standard Error: 0 + .saturating_add(Weight::from_ref_time(1_144).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(3)) } // Storage: Whitelist WhitelistedCall (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) /// The range of component `n` is `[1, 10000]`. fn dispatch_whitelisted_call_with_preimage(n: u32, ) -> Weight { - // Minimum execution time: 29_184 nanoseconds. - Weight::from_ref_time(30_530_970 as u64) - // Standard Error: 7 - .saturating_add(Weight::from_ref_time(1_496 as u64).saturating_mul(n as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Minimum execution time: 29_187 nanoseconds. + Weight::from_ref_time(29_896_714) + // Standard Error: 6 + .saturating_add(Weight::from_ref_time(1_505).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) } } From fafc8e0ba8c98bd22b47913ded414e74a0fcb67f Mon Sep 17 00:00:00 2001 From: Marcin S Date: Tue, 6 Dec 2022 09:55:10 -0500 Subject: [PATCH 003/127] Remove `mem_info` and references to `parity-util-mem` (#12795) * Remove mem_info and some references to parity-util-mem * [Draft] Finish removing references to `parity-util-mem` * Upgrade dependencies * Update scripts/ci/deny.toml Co-authored-by: ordian * Fix Cargo.lock (remove unwanted dependency changes) * Removed unused argument * Run cargo fmt (didn't have pre-commit set up) * Fix some CI errors * Fix another CI error * Remove unused dependency Co-authored-by: ordian --- Cargo.lock | 63 +++---------------- bin/node/bench/Cargo.toml | 5 +- bin/node/bench/src/tempdb.rs | 1 - bin/node/cli/Cargo.toml | 5 -- client/api/src/client.rs | 17 ----- client/db/Cargo.toml | 8 +-- client/db/src/lib.rs | 3 +- client/db/src/storage_cache.rs | 10 +-- client/informant/Cargo.toml | 1 - client/informant/src/lib.rs | 16 +---- client/service/Cargo.toml | 3 - client/service/src/builder.rs | 11 +--- client/service/src/metrics.rs | 20 ------ client/state-db/Cargo.toml | 2 - client/state-db/src/lib.rs | 59 ++++------------- client/state-db/src/noncanonical.rs | 3 - client/state-db/src/pruning.rs | 9 +-- client/transaction-pool/Cargo.toml | 1 - .../transaction-pool/src/graph/base_pool.rs | 25 +------- client/transaction-pool/src/graph/future.rs | 33 +--------- client/transaction-pool/src/graph/pool.rs | 9 --- client/transaction-pool/src/graph/ready.rs | 25 +------- .../transaction-pool/src/graph/tracked_map.rs | 2 +- .../src/graph/validated_pool.rs | 10 --- client/transaction-pool/src/lib.rs | 11 ---- client/transaction-pool/tests/pool.rs | 11 ---- frame/support/Cargo.toml | 1 - frame/system/src/lib.rs | 4 +- primitives/database/Cargo.toml | 2 +- primitives/runtime/Cargo.toml | 2 - primitives/runtime/src/generic/block.rs | 9 +-- primitives/runtime/src/generic/digest.rs | 3 +- primitives/runtime/src/generic/header.rs | 24 +------ .../src/generic/unchecked_extrinsic.rs | 12 ---- primitives/runtime/src/lib.rs | 7 --- primitives/runtime/src/testing.rs | 12 +--- primitives/runtime/src/traits.rs | 22 ++----- .../runtime/src/transaction_validity.rs | 1 - primitives/test-primitives/Cargo.toml | 2 - primitives/test-primitives/src/lib.rs | 1 - primitives/trie/Cargo.toml | 5 +- primitives/trie/src/lib.rs | 15 ++--- scripts/ci/deny.toml | 1 - test-utils/runtime/Cargo.toml | 4 +- test-utils/runtime/src/lib.rs | 2 - utils/frame/benchmarking-cli/Cargo.toml | 4 +- 46 files changed, 70 insertions(+), 426 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 736501ecef..1ba2462a24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2241,7 +2241,6 @@ dependencies = [ "log", "once_cell", "parity-scale-codec", - "parity-util-mem", "paste", "pretty_assertions", "scale-info", @@ -3378,35 +3377,31 @@ dependencies = [ [[package]] name = "kvdb" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585089ceadba0197ffe9af6740ab350b325e3c1f5fccfbc3522e0250c750409b" +checksum = "e7d770dcb02bf6835887c3a979b5107a04ff4bbde97a5f0928d27404a155add9" dependencies = [ - "parity-util-mem", "smallvec", ] [[package]] name = "kvdb-memorydb" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40d109c87bfb7759edd2a49b2649c1afe25af785d930ad6a38479b4dc70dd873" +checksum = "bf7a85fe66f9ff9cd74e169fdd2c94c6e1e74c412c99a73b4df3200b5d3760b2" dependencies = [ "kvdb", - "parity-util-mem", "parking_lot 0.12.1", ] [[package]] name = "kvdb-rocksdb" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c076cc2cdbac89b9910c853a36c957d3862a779f31c2661174222cefb49ee597" +checksum = "2182b8219fee6bd83aacaab7344e840179ae079d5216aa4e249b4d704646a844" dependencies = [ "kvdb", - "log", "num_cpus", - "parity-util-mem", "parking_lot 0.12.1", "regex", "rocksdb", @@ -4081,13 +4076,12 @@ dependencies = [ [[package]] name = "memory-db" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ac11bb793c28fa095b7554466f53b3a60a2cd002afdac01bcf135cbd73a269" +checksum = "5e0c7cba9ce19ac7ffd2053ac9f49843bbd3f4318feedfd74e85c19d5fb0ba66" dependencies = [ "hash-db", "hashbrown 0.12.3", - "parity-util-mem", ] [[package]] @@ -4433,7 +4427,6 @@ dependencies = [ "node-primitives", "node-testing", "parity-db", - "parity-util-mem", "rand 0.7.3", "sc-basic-authorship", "sc-client-api", @@ -4527,7 +4520,6 @@ dependencies = [ "sp-tracing", "sp-transaction-pool", "sp-transaction-storage-proof", - "sp-trie", "substrate-build-script-utils", "substrate-frame-cli", "substrate-rpc-client", @@ -6379,33 +6371,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9777aa91b8ad9dd5aaa04a9b6bcb02c7f1deb952fca5a66034d5e63afc5c6f" -[[package]] -name = "parity-util-mem" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d32c34f4f5ca7f9196001c0aba5a1f9a5a12382c8944b8b0f90233282d1e8f8" -dependencies = [ - "cfg-if", - "hashbrown 0.12.3", - "impl-trait-for-tuples", - "parity-util-mem-derive", - "parking_lot 0.12.1", - "primitive-types", - "smallvec", - "winapi", -] - -[[package]] -name = "parity-util-mem-derive" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f557c32c6d268a07c921471619c0295f5efad3a0e76d4f97a05c091a51d110b2" -dependencies = [ - "proc-macro2", - "syn", - "synstructure", -] - [[package]] name = "parity-wasm" version = "0.45.0" @@ -8066,7 +8031,6 @@ dependencies = [ "futures", "futures-timer", "log", - "parity-util-mem", "sc-client-api", "sc-network-common", "sc-transaction-pool-api", @@ -8498,7 +8462,6 @@ dependencies = [ "jsonrpsee", "log", "parity-scale-codec", - "parity-util-mem", "parking_lot 0.12.1", "pin-project", "rand 0.7.3", @@ -8600,8 +8563,6 @@ version = "0.10.0-dev" dependencies = [ "log", "parity-scale-codec", - "parity-util-mem", - "parity-util-mem-derive", "parking_lot 0.12.1", "sc-client-api", "sp-core", @@ -8715,7 +8676,6 @@ dependencies = [ "linked-hash-map", "log", "parity-scale-codec", - "parity-util-mem", "parking_lot 0.12.1", "sc-block-builder", "sc-client-api", @@ -9723,7 +9683,6 @@ dependencies = [ "impl-trait-for-tuples", "log", "parity-scale-codec", - "parity-util-mem", "paste", "rand 0.7.3", "scale-info", @@ -9908,7 +9867,6 @@ name = "sp-test-primitives" version = "2.0.0" dependencies = [ "parity-scale-codec", - "parity-util-mem", "serde", "sp-application-crypto", "sp-core", @@ -10333,7 +10291,6 @@ dependencies = [ "pallet-babe", "pallet-timestamp", "parity-scale-codec", - "parity-util-mem", "sc-block-builder", "sc-executor", "sc-service", @@ -10850,9 +10807,9 @@ checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41" [[package]] name = "trie-bench" -version = "0.32.0" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dae77b1daad50cd3ed94c506d2dab27e2e47f7b5153a6d4b1992bb3f6028cb" +checksum = "c5b26bd2cdd7641c5beb476b314c0cb1f629832bf21a6235f545e2d47bc9d05a" dependencies = [ "criterion", "hash-db", diff --git a/bin/node/bench/Cargo.toml b/bin/node/bench/Cargo.toml index 33c9e7c892..63087215ae 100644 --- a/bin/node/bench/Cargo.toml +++ b/bin/node/bench/Cargo.toml @@ -23,8 +23,8 @@ sp-state-machine = { version = "0.13.0", path = "../../../primitives/state-machi serde = "1.0.136" serde_json = "1.0.85" derive_more = { version = "0.99.17", default-features = false, features = ["display"] } -kvdb = "0.12.0" -kvdb-rocksdb = "0.16.0" +kvdb = "0.13.0" +kvdb-rocksdb = "0.17.0" sp-trie = { version = "7.0.0", path = "../../../primitives/trie" } sp-core = { version = "7.0.0", path = "../../../primitives/core" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } @@ -37,7 +37,6 @@ tempfile = "3.1.0" fs_extra = "1" rand = { version = "0.7.2", features = ["small_rng"] } lazy_static = "1.4.0" -parity-util-mem = { version = "0.12.0", default-features = false, features = ["primitive-types"] } parity-db = "0.4.2" sc-transaction-pool = { version = "4.0.0-dev", path = "../../../client/transaction-pool" } sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../../client/transaction-pool/api" } diff --git a/bin/node/bench/src/tempdb.rs b/bin/node/bench/src/tempdb.rs index eb3bb1d3fc..82895ddfab 100644 --- a/bin/node/bench/src/tempdb.rs +++ b/bin/node/bench/src/tempdb.rs @@ -29,7 +29,6 @@ pub enum DatabaseType { pub struct TempDatabase(tempfile::TempDir); struct ParityDbWrapper(parity_db::Db); -parity_util_mem::malloc_size_of_is_0!(ParityDbWrapper); impl KeyValueDB for ParityDbWrapper { /// Get a value by key. diff --git a/bin/node/cli/Cargo.toml b/bin/node/cli/Cargo.toml index 108923265f..cb2baf9e0d 100644 --- a/bin/node/cli/Cargo.toml +++ b/bin/node/cli/Cargo.toml @@ -101,11 +101,6 @@ node-inspect = { version = "0.9.0-dev", optional = true, path = "../inspect" } try-runtime-cli = { version = "0.10.0-dev", optional = true, path = "../../../utils/frame/try-runtime/cli" } serde_json = "1.0.85" -[target.'cfg(any(target_arch="x86_64", target_arch="aarch64"))'.dependencies] -sp-trie = { version = "7.0.0", default-features = false, path = "../../../primitives/trie", features = [ - "memory-tracker", -] } - [dev-dependencies] sc-keystore = { version = "4.0.0-dev", path = "../../../client/keystore" } sc-client-db = { version = "0.10.0-dev", path = "../../../client/db" } diff --git a/client/api/src/client.rs b/client/api/src/client.rs index bb88853d23..05e3163dcc 100644 --- a/client/api/src/client.rs +++ b/client/api/src/client.rs @@ -197,17 +197,6 @@ impl fmt::Display for MemorySize { } } -/// Memory statistics for state db. -#[derive(Default, Clone, Debug)] -pub struct StateDbMemoryInfo { - /// Memory usage of the non-canonical overlay - pub non_canonical: MemorySize, - /// Memory usage of the pruning window. - pub pruning: Option, - /// Memory usage of the pinned blocks. - pub pinned: MemorySize, -} - /// Memory statistics for client instance. #[derive(Default, Clone, Debug)] pub struct MemoryInfo { @@ -215,8 +204,6 @@ pub struct MemoryInfo { pub state_cache: MemorySize, /// Size of backend database cache. pub database_cache: MemorySize, - /// Size of the state db. - pub state_db: StateDbMemoryInfo, } /// I/O statistics for client instance. @@ -264,13 +251,9 @@ impl fmt::Display for UsageInfo { write!( f, "caches: ({} state, {} db overlay), \ - state db: ({} non-canonical, {} pruning, {} pinned), \ i/o: ({} tx, {} write, {} read, {} avg tx, {}/{} key cache reads/total, {} trie nodes writes)", self.memory.state_cache, self.memory.database_cache, - self.memory.state_db.non_canonical, - self.memory.state_db.pruning.unwrap_or_default(), - self.memory.state_db.pinned, self.io.transactions, self.io.bytes_written, self.io.bytes_read, diff --git a/client/db/Cargo.toml b/client/db/Cargo.toml index dda1a640d8..ee879a161e 100644 --- a/client/db/Cargo.toml +++ b/client/db/Cargo.toml @@ -17,9 +17,9 @@ codec = { package = "parity-scale-codec", version = "3.0.0", features = [ "derive", ] } hash-db = "0.15.2" -kvdb = "0.12.0" -kvdb-memorydb = "0.12.0" -kvdb-rocksdb = { version = "0.16.0", optional = true } +kvdb = "0.13.0" +kvdb-memorydb = "0.13.0" +kvdb-rocksdb = { version = "0.17.0", optional = true } linked-hash-map = "0.5.4" log = "0.4.17" parity-db = "0.4.2" @@ -36,7 +36,7 @@ sp-trie = { version = "7.0.0", path = "../../primitives/trie" } [dev-dependencies] criterion = "0.3.3" -kvdb-rocksdb = "0.16.0" +kvdb-rocksdb = "0.17.0" rand = "0.8.4" tempfile = "3.1.0" quickcheck = { version = "1.0.3", default-features = false } diff --git a/client/db/src/lib.rs b/client/db/src/lib.rs index 305db2284b..426876f5cb 100644 --- a/client/db/src/lib.rs +++ b/client/db/src/lib.rs @@ -2086,10 +2086,9 @@ impl sc_client_api::backend::Backend for Backend { let state_cache = MemorySize::from_bytes( self.shared_trie_cache.as_ref().map_or(0, |c| c.used_memory_size()), ); - let state_db = self.storage.state_db.memory_info(); Some(UsageInfo { - memory: MemoryInfo { state_cache, database_cache, state_db }, + memory: MemoryInfo { state_cache, database_cache }, io: IoInfo { transactions: io_stats.transactions, bytes_read: io_stats.bytes_read, diff --git a/client/db/src/storage_cache.rs b/client/db/src/storage_cache.rs index d9253fe09e..474599e5d7 100644 --- a/client/db/src/storage_cache.rs +++ b/client/db/src/storage_cache.rs @@ -59,15 +59,11 @@ pub struct Cache { struct LRUMap(LinkedHashMap, usize, usize); -/// Internal trait similar to `heapsize` but using -/// a simply estimation. +/// Internal trait similar to `heapsize` but using a simple estimation. /// -/// This should not be made public, it is implementation -/// detail trait. If it need to become public please -/// consider using `malloc_size_of`. +/// This should not be made public, it is an implementation detail trait. trait EstimateSize { - /// Return a size estimation of additional size needed - /// to cache this struct (in bytes). + /// Return a size estimation of the additional size needed to cache this struct (in bytes). fn estimate_size(&self) -> usize; } diff --git a/client/informant/Cargo.toml b/client/informant/Cargo.toml index 682a754ba1..a432c2ada5 100644 --- a/client/informant/Cargo.toml +++ b/client/informant/Cargo.toml @@ -17,7 +17,6 @@ ansi_term = "0.12.1" futures = "0.3.21" futures-timer = "3.0.1" log = "0.4.17" -parity-util-mem = { version = "0.12.0", default-features = false, features = ["primitive-types"] } sc-client-api = { version = "4.0.0-dev", path = "../api" } sc-network-common = { version = "0.10.0-dev", path = "../network/common" } sc-transaction-pool-api = { version = "4.0.0-dev", path = "../transaction-pool/api" } diff --git a/client/informant/src/lib.rs b/client/informant/src/lib.rs index 52f1c95fe0..b03b92686e 100644 --- a/client/informant/src/lib.rs +++ b/client/informant/src/lib.rs @@ -22,10 +22,8 @@ use ansi_term::Colour; use futures::prelude::*; use futures_timer::Delay; use log::{debug, info, trace}; -use parity_util_mem::MallocSizeOf; use sc_client_api::{BlockchainEvents, UsageProvider}; use sc_network_common::service::NetworkStatusProvider; -use sc_transaction_pool_api::TransactionPool; use sp_blockchain::HeaderMetadata; use sp_runtime::traits::{Block as BlockT, Header}; use std::{collections::VecDeque, fmt::Display, sync::Arc, time::Duration}; @@ -53,16 +51,11 @@ impl Default for OutputFormat { } /// Builds the informant and returns a `Future` that drives the informant. -pub async fn build( - client: Arc, - network: N, - pool: Arc

, - format: OutputFormat, -) where +pub async fn build(client: Arc, network: N, format: OutputFormat) +where N: NetworkStatusProvider, C: UsageProvider + HeaderMetadata + BlockchainEvents, >::Error: Display, - P: TransactionPool + MallocSizeOf, { let mut display = display::InformantDisplay::new(format.clone()); @@ -83,11 +76,6 @@ pub async fn build( "Usage statistics not displayed as backend does not provide it", ) } - trace!( - target: "usage", - "Subsystems memory [txpool: {} kB]", - parity_util_mem::malloc_size(&*pool) / 1024, - ); display.display(&info, net_status); future::ready(()) }); diff --git a/client/service/Cargo.toml b/client/service/Cargo.toml index 8b17a8287c..87949ef12d 100644 --- a/client/service/Cargo.toml +++ b/client/service/Cargo.toml @@ -80,9 +80,6 @@ sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } sc-sysinfo = { version = "6.0.0-dev", path = "../sysinfo" } tracing = "0.1.29" tracing-futures = { version = "0.2.4" } -parity-util-mem = { version = "0.12.0", default-features = false, features = [ - "primitive-types", -] } async-trait = "0.1.57" tokio = { version = "1.22.0", features = ["time", "rt-multi-thread", "parking_lot"] } tempfile = "3.1.0" diff --git a/client/service/src/builder.rs b/client/service/src/builder.rs index df20f2009e..dd89ce6dff 100644 --- a/client/service/src/builder.rs +++ b/client/service/src/builder.rs @@ -436,9 +436,7 @@ where TBl::Hash: Unpin, TBl::Header: Unpin, TBackend: 'static + sc_client_api::backend::Backend + Send, - TExPool: MaintainedTransactionPool::Hash> - + parity_util_mem::MallocSizeOf - + 'static, + TExPool: MaintainedTransactionPool::Hash> + 'static, { let SpawnTasksParams { mut config, @@ -540,12 +538,7 @@ where spawn_handle.spawn( "informant", None, - sc_informant::build( - client.clone(), - network, - transaction_pool.clone(), - config.informant_output_format, - ), + sc_informant::build(client.clone(), network, config.informant_output_format), ); task_manager.keep_alive((config.base_path, rpc)); diff --git a/client/service/src/metrics.rs b/client/service/src/metrics.rs index 13b249a7b9..c83b3988f9 100644 --- a/client/service/src/metrics.rs +++ b/client/service/src/metrics.rs @@ -43,7 +43,6 @@ struct PrometheusMetrics { // I/O database_cache: Gauge, state_cache: Gauge, - state_db: GaugeVec, } impl PrometheusMetrics { @@ -117,13 +116,6 @@ impl PrometheusMetrics { Gauge::new("substrate_state_cache_bytes", "State cache size in bytes")?, registry, )?, - state_db: register( - GaugeVec::new( - Opts::new("substrate_state_db_cache_bytes", "State DB cache in bytes"), - &["subtype"], - )?, - registry, - )?, }) } } @@ -254,18 +246,6 @@ impl MetricsService { if let Some(info) = info.usage.as_ref() { metrics.database_cache.set(info.memory.database_cache.as_bytes() as u64); metrics.state_cache.set(info.memory.state_cache.as_bytes() as u64); - - metrics - .state_db - .with_label_values(&["non_canonical"]) - .set(info.memory.state_db.non_canonical.as_bytes() as u64); - if let Some(pruning) = info.memory.state_db.pruning { - metrics.state_db.with_label_values(&["pruning"]).set(pruning.as_bytes() as u64); - } - metrics - .state_db - .with_label_values(&["pinned"]) - .set(info.memory.state_db.pinned.as_bytes() as u64); } } diff --git a/client/state-db/Cargo.toml b/client/state-db/Cargo.toml index 07c0836328..b6d40a8106 100644 --- a/client/state-db/Cargo.toml +++ b/client/state-db/Cargo.toml @@ -15,8 +15,6 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } log = "0.4.17" -parity-util-mem = { version = "0.12.0", default-features = false, features = ["primitive-types"] } -parity-util-mem-derive = "0.1.0" parking_lot = "0.12.1" sc-client-api = { version = "4.0.0-dev", path = "../api" } sp-core = { version = "7.0.0", path = "../../primitives/core" } diff --git a/client/state-db/src/lib.rs b/client/state-db/src/lib.rs index 01a198a1b3..94d4178770 100644 --- a/client/state-db/src/lib.rs +++ b/client/state-db/src/lib.rs @@ -49,10 +49,8 @@ mod test; use codec::Codec; use log::trace; use noncanonical::NonCanonicalOverlay; -use parity_util_mem::{malloc_size, MallocSizeOf}; use parking_lot::RwLock; use pruning::{HaveBlock, RefWindow}; -use sc_client_api::{MemorySize, StateDbMemoryInfo}; use std::{ collections::{hash_map::Entry, HashMap}, fmt, @@ -220,8 +218,6 @@ pub struct Constraints { /// Maximum blocks. Defaults to 0 when unspecified, effectively keeping only non-canonical /// states. pub max_blocks: Option, - /// Maximum memory in the pruning overlay. - pub max_mem: Option, } /// Pruning mode. @@ -238,7 +234,7 @@ pub enum PruningMode { impl PruningMode { /// Create a mode that keeps given number of blocks. pub fn blocks_pruning(n: u32) -> PruningMode { - PruningMode::Constrained(Constraints { max_blocks: Some(n), max_mem: None }) + PruningMode::Constrained(Constraints { max_blocks: Some(n) }) } /// Is this an archive (either ArchiveAll or ArchiveCanonical) pruning mode? @@ -276,7 +272,7 @@ impl Default for PruningMode { impl Default for Constraints { fn default() -> Self { - Self { max_blocks: Some(DEFAULT_MAX_BLOCK_CONSTRAINT), max_mem: None } + Self { max_blocks: Some(DEFAULT_MAX_BLOCK_CONSTRAINT) } } } @@ -294,9 +290,7 @@ pub struct StateDbSync { ref_counting: bool, } -impl - StateDbSync -{ +impl StateDbSync { fn new( mode: PruningMode, ref_counting: bool, @@ -306,8 +300,7 @@ impl let non_canonical: NonCanonicalOverlay = NonCanonicalOverlay::new(&db)?; let pruning: Option> = match mode { - PruningMode::Constrained(Constraints { max_mem: Some(_), .. }) => unimplemented!(), - PruningMode::Constrained(Constraints { max_blocks, .. }) => + PruningMode::Constrained(Constraints { max_blocks }) => Some(RefWindow::new(db, max_blocks.unwrap_or(0), ref_counting)?), PruningMode::ArchiveAll | PruningMode::ArchiveCanonical => None, }; @@ -392,10 +385,6 @@ impl break } - if constraints.max_mem.map_or(false, |m| pruning.mem_used() > m) { - break - } - let pinned = &self.pinned; match pruning.next_hash() { // the block record is temporary unavailable, break and try next time @@ -496,14 +485,6 @@ impl } db.get(key.as_ref()).map_err(Error::Db) } - - fn memory_info(&self) -> StateDbMemoryInfo { - StateDbMemoryInfo { - non_canonical: MemorySize::from_bytes(malloc_size(&self.non_canonical)), - pruning: self.pruning.as_ref().map(|p| MemorySize::from_bytes(malloc_size(&p))), - pinned: MemorySize::from_bytes(malloc_size(&self.pinned)), - } - } } /// State DB maintenance. See module description. @@ -512,9 +493,7 @@ pub struct StateDb { db: RwLock>, } -impl - StateDb -{ +impl StateDb { /// Create an instance of [`StateDb`]. pub fn open( db: D, @@ -637,11 +616,6 @@ impl *state_db = StateDbSync::new(state_db.mode.clone(), state_db.ref_counting, db)?; Ok(()) } - - /// Returns the current memory statistics of this instance. - pub fn memory_info(&self) -> StateDbMemoryInfo { - self.db.read().memory_info() - } } /// The result return by `StateDb::is_pruned` @@ -772,10 +746,8 @@ mod tests { #[test] fn block_record_unavailable() { - let (mut db, state_db) = make_test_db(PruningMode::Constrained(Constraints { - max_blocks: Some(1), - max_mem: None, - })); + let (mut db, state_db) = + make_test_db(PruningMode::Constrained(Constraints { max_blocks: Some(1) })); // import 2 blocks for i in &[5, 6] { db.commit( @@ -809,19 +781,13 @@ mod tests { #[test] fn prune_window_0() { - let (db, _) = make_test_db(PruningMode::Constrained(Constraints { - max_blocks: Some(0), - max_mem: None, - })); + let (db, _) = make_test_db(PruningMode::Constrained(Constraints { max_blocks: Some(0) })); assert!(db.data_eq(&make_db(&[21, 3, 922, 94]))); } #[test] fn prune_window_1() { - let (db, sdb) = make_test_db(PruningMode::Constrained(Constraints { - max_blocks: Some(1), - max_mem: None, - })); + let (db, sdb) = make_test_db(PruningMode::Constrained(Constraints { max_blocks: Some(1) })); assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(0), 0), IsPruned::Pruned); assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(1), 1), IsPruned::Pruned); assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(21), 2), IsPruned::Pruned); @@ -831,10 +797,7 @@ mod tests { #[test] fn prune_window_2() { - let (db, sdb) = make_test_db(PruningMode::Constrained(Constraints { - max_blocks: Some(2), - max_mem: None, - })); + let (db, sdb) = make_test_db(PruningMode::Constrained(Constraints { max_blocks: Some(2) })); assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(0), 0), IsPruned::Pruned); assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(1), 1), IsPruned::Pruned); assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(21), 2), IsPruned::NotPruned); @@ -858,7 +821,7 @@ mod tests { ) .unwrap(), ); - let new_mode = PruningMode::Constrained(Constraints { max_blocks: Some(2), max_mem: None }); + let new_mode = PruningMode::Constrained(Constraints { max_blocks: Some(2) }); let state_db_open_result: Result<(_, StateDb), _> = StateDb::open(db.clone(), Some(new_mode), false, false); assert!(state_db_open_result.is_err()); diff --git a/client/state-db/src/noncanonical.rs b/client/state-db/src/noncanonical.rs index 3711cf7a42..df09a9c017 100644 --- a/client/state-db/src/noncanonical.rs +++ b/client/state-db/src/noncanonical.rs @@ -30,7 +30,6 @@ pub(crate) const LAST_CANONICAL: &[u8] = b"last_canonical"; const MAX_BLOCKS_PER_LEVEL: u64 = 32; /// See module documentation. -#[derive(parity_util_mem_derive::MallocSizeOf)] pub struct NonCanonicalOverlay { last_canonicalized: Option<(BlockHash, u64)>, levels: VecDeque>, @@ -41,7 +40,6 @@ pub struct NonCanonicalOverlay { pinned_insertions: HashMap, u32)>, } -#[derive(parity_util_mem_derive::MallocSizeOf)] #[cfg_attr(test, derive(PartialEq, Debug))] struct OverlayLevel { blocks: Vec>, @@ -81,7 +79,6 @@ fn to_journal_key(block: u64, index: u64) -> Vec { } #[cfg_attr(test, derive(PartialEq, Debug))] -#[derive(parity_util_mem_derive::MallocSizeOf)] struct BlockOverlay { hash: BlockHash, journal_index: u64, diff --git a/client/state-db/src/pruning.rs b/client/state-db/src/pruning.rs index 458522b811..d942fb2428 100644 --- a/client/state-db/src/pruning.rs +++ b/client/state-db/src/pruning.rs @@ -36,7 +36,6 @@ pub(crate) const LAST_PRUNED: &[u8] = b"last_pruned"; const PRUNING_JOURNAL: &[u8] = b"pruning_journal"; /// See module documentation. -#[derive(parity_util_mem_derive::MallocSizeOf)] pub struct RefWindow { /// A queue of blocks keep tracking keys that should be deleted for each block in the /// pruning window. @@ -50,7 +49,6 @@ pub struct RefWindow { /// blocks in memory, and keep track of re-inserted keys to not delete them when pruning /// - `DbBacked`, used when the backend database supports reference counting, only keep /// a few number of blocks in memory and load more blocks on demand -#[derive(parity_util_mem_derive::MallocSizeOf)] enum DeathRowQueue { Mem { /// A queue of keys that should be deleted for each block in the pruning window. @@ -60,7 +58,6 @@ enum DeathRowQueue { }, DbBacked { // The backend database - #[ignore_malloc_size_of = "Shared data"] db: D, /// A queue of keys that should be deleted for each block in the pruning window. /// Only caching the first few blocks of the pruning window, blocks inside are @@ -251,7 +248,7 @@ fn load_death_row_from_db( } } -#[derive(Clone, Debug, PartialEq, Eq, parity_util_mem_derive::MallocSizeOf)] +#[derive(Clone, Debug, PartialEq, Eq)] struct DeathRow { hash: BlockHash, deleted: HashSet, @@ -345,10 +342,6 @@ impl RefWindow { Ok(res) } - pub fn mem_used(&self) -> usize { - 0 - } - fn is_empty(&self) -> bool { self.window_size() == 0 } diff --git a/client/transaction-pool/Cargo.toml b/client/transaction-pool/Cargo.toml index f7f644a6b0..7a3ab042d5 100644 --- a/client/transaction-pool/Cargo.toml +++ b/client/transaction-pool/Cargo.toml @@ -19,7 +19,6 @@ futures = "0.3.21" futures-timer = "3.0.2" linked-hash-map = "0.5.4" log = "0.4.17" -parity-util-mem = { version = "0.12.0", default-features = false, features = ["primitive-types"] } parking_lot = "0.12.1" serde = { version = "1.0.136", features = ["derive"] } thiserror = "1.0.30" diff --git a/client/transaction-pool/src/graph/base_pool.rs b/client/transaction-pool/src/graph/base_pool.rs index 8e0422739c..67580d698b 100644 --- a/client/transaction-pool/src/graph/base_pool.rs +++ b/client/transaction-pool/src/graph/base_pool.rs @@ -84,7 +84,7 @@ pub struct PruneStatus { /// Immutable transaction #[cfg_attr(test, derive(Clone))] -#[derive(PartialEq, Eq, parity_util_mem::MallocSizeOf)] +#[derive(PartialEq, Eq)] pub struct Transaction { /// Raw extrinsic representing that transaction. pub data: Extrinsic, @@ -207,7 +207,7 @@ const RECENTLY_PRUNED_TAGS: usize = 2; /// as-is for the second time will fail or produce unwanted results. /// Most likely it is required to revalidate them and recompute set of /// required tags. -#[derive(Debug, parity_util_mem::MallocSizeOf)] +#[derive(Debug)] pub struct BasePool { reject_future_transactions: bool, future: FutureTransactions, @@ -796,27 +796,6 @@ mod tests { } } - #[test] - fn can_track_heap_size() { - let mut pool = pool(); - pool.import(Transaction { - data: vec![5u8; 1024], - hash: 5, - provides: vec![vec![0], vec![4]], - ..DEFAULT_TX.clone() - }) - .expect("import 1 should be ok"); - pool.import(Transaction { - data: vec![3u8; 1024], - hash: 7, - provides: vec![vec![2], vec![7]], - ..DEFAULT_TX.clone() - }) - .expect("import 2 should be ok"); - - assert!(parity_util_mem::malloc_size(&pool) > 5000); - } - #[test] fn should_remove_invalid_transactions() { // given diff --git a/client/transaction-pool/src/graph/future.rs b/client/transaction-pool/src/graph/future.rs index ae49e3f2d3..d23b5f2b6e 100644 --- a/client/transaction-pool/src/graph/future.rs +++ b/client/transaction-pool/src/graph/future.rs @@ -28,7 +28,6 @@ use std::time::Instant; use super::base_pool::Transaction; -#[derive(parity_util_mem::MallocSizeOf)] /// Transaction with partially satisfied dependencies. pub struct WaitingTransaction { /// Transaction details. @@ -108,7 +107,7 @@ impl WaitingTransaction { /// /// Contains transactions that are still awaiting for some other transactions that /// could provide a tag that they require. -#[derive(Debug, parity_util_mem::MallocSizeOf)] +#[derive(Debug)] pub struct FutureTransactions { /// tags that are not yet provided by any transaction and we await for them wanted_tags: HashMap>, @@ -251,33 +250,3 @@ impl FutureTransactions { self.waiting.values().fold(0, |acc, tx| acc + tx.transaction.bytes) } } - -#[cfg(test)] -mod tests { - use super::*; - use sp_runtime::transaction_validity::TransactionSource; - - #[test] - fn can_track_heap_size() { - let mut future = FutureTransactions::default(); - future.import(WaitingTransaction { - transaction: Transaction { - data: vec![0u8; 1024], - bytes: 1, - hash: 1, - priority: 1, - valid_till: 2, - requires: vec![vec![1], vec![2]], - provides: vec![vec![3], vec![4]], - propagate: true, - source: TransactionSource::External, - } - .into(), - missing_tags: vec![vec![1u8], vec![2u8]].into_iter().collect(), - imported_at: std::time::Instant::now(), - }); - - // data is at least 1024! - assert!(parity_util_mem::malloc_size(&future) > 1024); - } -} diff --git a/client/transaction-pool/src/graph/pool.rs b/client/transaction-pool/src/graph/pool.rs index 7b3a8db159..9c747ade12 100644 --- a/client/transaction-pool/src/graph/pool.rs +++ b/client/transaction-pool/src/graph/pool.rs @@ -144,15 +144,6 @@ pub struct Pool { validated_pool: Arc>, } -impl parity_util_mem::MallocSizeOf for Pool -where - ExtrinsicFor: parity_util_mem::MallocSizeOf, -{ - fn size_of(&self, ops: &mut parity_util_mem::MallocSizeOfOps) -> usize { - self.validated_pool.size_of(ops) - } -} - impl Pool { /// Create a new transaction pool. pub fn new(options: Options, is_validator: IsValidator, api: Arc) -> Self { diff --git a/client/transaction-pool/src/graph/ready.rs b/client/transaction-pool/src/graph/ready.rs index 220e69b13e..b52372a3c4 100644 --- a/client/transaction-pool/src/graph/ready.rs +++ b/client/transaction-pool/src/graph/ready.rs @@ -37,7 +37,7 @@ use super::{ /// An in-pool transaction reference. /// /// Should be cheap to clone. -#[derive(Debug, parity_util_mem::MallocSizeOf)] +#[derive(Debug)] pub struct TransactionRef { /// The actual transaction data. pub transaction: Arc>, @@ -74,7 +74,7 @@ impl PartialEq for TransactionRef { } impl Eq for TransactionRef {} -#[derive(Debug, parity_util_mem::MallocSizeOf)] +#[derive(Debug)] pub struct ReadyTx { /// A reference to a transaction pub transaction: TransactionRef, @@ -105,7 +105,7 @@ qed "#; /// Validated transactions that are block ready with all their dependencies met. -#[derive(Debug, parity_util_mem::MallocSizeOf)] +#[derive(Debug)] pub struct ReadyTransactions { /// Next free insertion id (used to indicate when a transaction was inserted into the pool). insertion_id: u64, @@ -742,25 +742,6 @@ mod tests { assert_eq!(it.next(), None); } - #[test] - fn can_report_heap_size() { - let mut ready = ReadyTransactions::default(); - let tx = Transaction { - data: vec![5], - bytes: 1, - hash: 5, - priority: 1, - valid_till: u64::MAX, // use the max here for testing. - requires: vec![], - provides: vec![], - propagate: true, - source: Source::External, - }; - import(&mut ready, tx).unwrap(); - - assert!(parity_util_mem::malloc_size(&ready) > 200); - } - #[test] fn should_order_refs() { let mut id = 1; diff --git a/client/transaction-pool/src/graph/tracked_map.rs b/client/transaction-pool/src/graph/tracked_map.rs index 32d04b0068..7292293258 100644 --- a/client/transaction-pool/src/graph/tracked_map.rs +++ b/client/transaction-pool/src/graph/tracked_map.rs @@ -33,7 +33,7 @@ pub trait Size { /// Map with size tracking. /// /// Size reported might be slightly off and only approximately true. -#[derive(Debug, parity_util_mem::MallocSizeOf)] +#[derive(Debug)] pub struct TrackedMap { index: Arc>>, bytes: AtomicIsize, diff --git a/client/transaction-pool/src/graph/validated_pool.rs b/client/transaction-pool/src/graph/validated_pool.rs index dcb8195073..ab99a090e5 100644 --- a/client/transaction-pool/src/graph/validated_pool.rs +++ b/client/transaction-pool/src/graph/validated_pool.rs @@ -110,16 +110,6 @@ pub struct ValidatedPool { rotator: PoolRotator>, } -impl parity_util_mem::MallocSizeOf for ValidatedPool -where - ExtrinsicFor: parity_util_mem::MallocSizeOf, -{ - fn size_of(&self, ops: &mut parity_util_mem::MallocSizeOfOps) -> usize { - // other entries insignificant or non-primary references - self.pool.size_of(ops) - } -} - impl ValidatedPool { /// Create a new transaction pool. pub fn new(options: Options, is_validator: IsValidator, api: Arc) -> Self { diff --git a/client/transaction-pool/src/lib.rs b/client/transaction-pool/src/lib.rs index a441bf9b2a..0124038b75 100644 --- a/client/transaction-pool/src/lib.rs +++ b/client/transaction-pool/src/lib.rs @@ -135,17 +135,6 @@ impl ReadyPoll { } } -impl parity_util_mem::MallocSizeOf for BasicPool -where - PoolApi: graph::ChainApi, - Block: BlockT, -{ - fn size_of(&self, ops: &mut parity_util_mem::MallocSizeOfOps) -> usize { - // other entries insignificant or non-primary references - self.pool.size_of(ops) - } -} - /// Type of revalidation. pub enum RevalidationType { /// Light revalidation type. diff --git a/client/transaction-pool/tests/pool.rs b/client/transaction-pool/tests/pool.rs index 2789143275..7ba61e58b4 100644 --- a/client/transaction-pool/tests/pool.rs +++ b/client/transaction-pool/tests/pool.rs @@ -440,17 +440,6 @@ fn should_push_watchers_during_maintenance() { ); } -#[test] -fn can_track_heap_size() { - let (pool, _api, _guard) = maintained_pool(); - block_on(pool.submit_one(&BlockId::number(0), SOURCE, uxt(Alice, 209))).expect("1. Imported"); - block_on(pool.submit_one(&BlockId::number(0), SOURCE, uxt(Alice, 210))).expect("1. Imported"); - block_on(pool.submit_one(&BlockId::number(0), SOURCE, uxt(Alice, 211))).expect("1. Imported"); - block_on(pool.submit_one(&BlockId::number(0), SOURCE, uxt(Alice, 212))).expect("1. Imported"); - - assert!(parity_util_mem::malloc_size(&pool) > 3000); -} - #[test] fn finalization() { let xt = uxt(Alice, 209); diff --git a/frame/support/Cargo.toml b/frame/support/Cargo.toml index b199c014d3..4945b5ab91 100644 --- a/frame/support/Cargo.toml +++ b/frame/support/Cargo.toml @@ -44,7 +44,6 @@ serde_json = "1.0.85" assert_matches = "1.3.0" pretty_assertions = "1.2.1" frame-system = { version = "4.0.0-dev", path = "../system" } -parity-util-mem = { version = "0.12.0", default-features = false, features = ["primitive-types"] } [features] default = ["std"] diff --git a/frame/system/src/lib.rs b/frame/system/src/lib.rs index 7c4c468395..f7e3849bee 100644 --- a/frame/system/src/lib.rs +++ b/frame/system/src/lib.rs @@ -72,7 +72,7 @@ use sp_runtime::{ generic, traits::{ self, AtLeast32Bit, AtLeast32BitUnsigned, BadOrigin, BlockNumberProvider, Bounded, - CheckEqual, Dispatchable, Hash, Lookup, LookupError, MaybeDisplay, MaybeMallocSizeOf, + CheckEqual, Dispatchable, Hash, Lookup, LookupError, MaybeDisplay, MaybeSerializeDeserialize, Member, One, Saturating, SimpleBitOps, StaticLookup, Zero, }, DispatchError, RuntimeDebug, @@ -250,7 +250,6 @@ pub mod pallet { + Copy + sp_std::hash::Hash + sp_std::str::FromStr - + MaybeMallocSizeOf + MaxEncodedLen + TypeInfo; @@ -268,7 +267,6 @@ pub mod pallet { + sp_std::hash::Hash + AsRef<[u8]> + AsMut<[u8]> - + MaybeMallocSizeOf + MaxEncodedLen; /// The hashing system (algorithm) being used in the runtime (e.g. Blake2). diff --git a/primitives/database/Cargo.toml b/primitives/database/Cargo.toml index f19a647fed..b1105f88ba 100644 --- a/primitives/database/Cargo.toml +++ b/primitives/database/Cargo.toml @@ -11,5 +11,5 @@ documentation = "https://docs.rs/sp-database" readme = "README.md" [dependencies] -kvdb = "0.12.0" +kvdb = "0.13.0" parking_lot = "0.12.1" diff --git a/primitives/runtime/Cargo.toml b/primitives/runtime/Cargo.toml index 578c01583f..8ea6ed3eb3 100644 --- a/primitives/runtime/Cargo.toml +++ b/primitives/runtime/Cargo.toml @@ -19,7 +19,6 @@ either = { version = "1.5", default-features = false } hash256-std-hasher = { version = "0.15.2", default-features = false } impl-trait-for-tuples = "0.2.2" log = { version = "0.4.17", default-features = false } -parity-util-mem = { version = "0.12.0", default-features = false, features = ["primitive-types"], optional = true } paste = "1.0" rand = { version = "0.7.2", optional = true } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } @@ -48,7 +47,6 @@ std = [ "either/use_std", "hash256-std-hasher/std", "log/std", - "parity-util-mem/std", "rand", "scale-info/std", "serde", diff --git a/primitives/runtime/src/generic/block.rs b/primitives/runtime/src/generic/block.rs index 2cd350b2c5..3b01633635 100644 --- a/primitives/runtime/src/generic/block.rs +++ b/primitives/runtime/src/generic/block.rs @@ -25,10 +25,7 @@ use serde::{Deserialize, Serialize}; use crate::{ codec::{Codec, Decode, Encode}, - traits::{ - self, Block as BlockT, Header as HeaderT, MaybeMallocSizeOf, MaybeSerialize, Member, - NumberFor, - }, + traits::{self, Block as BlockT, Header as HeaderT, MaybeSerialize, Member, NumberFor}, Justifications, }; use sp_core::RuntimeDebug; @@ -82,7 +79,7 @@ impl fmt::Display for BlockId { /// Abstraction over a substrate block. #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize, parity_util_mem::MallocSizeOf))] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] #[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] #[cfg_attr(feature = "std", serde(deny_unknown_fields))] pub struct Block { @@ -95,7 +92,7 @@ pub struct Block { impl traits::Block for Block where Header: HeaderT, - Extrinsic: Member + Codec + traits::Extrinsic + MaybeMallocSizeOf, + Extrinsic: Member + Codec + traits::Extrinsic, { type Extrinsic = Extrinsic; type Header = Header; diff --git a/primitives/runtime/src/generic/digest.rs b/primitives/runtime/src/generic/digest.rs index ec74ebb0d4..1d1173057e 100644 --- a/primitives/runtime/src/generic/digest.rs +++ b/primitives/runtime/src/generic/digest.rs @@ -34,7 +34,7 @@ use sp_core::RuntimeDebug; /// Generic header digest. #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, Default)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize, parity_util_mem::MallocSizeOf))] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct Digest { /// A list of logs in the digest. pub logs: Vec, @@ -70,7 +70,6 @@ impl Digest { /// Digest item that is able to encode/decode 'system' digest items and /// provide opaque access to other items. #[derive(PartialEq, Eq, Clone, RuntimeDebug)] -#[cfg_attr(feature = "std", derive(parity_util_mem::MallocSizeOf))] pub enum DigestItem { /// A pre-runtime digest. /// diff --git a/primitives/runtime/src/generic/header.rs b/primitives/runtime/src/generic/header.rs index a7b43608f2..04d09f6b15 100644 --- a/primitives/runtime/src/generic/header.rs +++ b/primitives/runtime/src/generic/header.rs @@ -22,7 +22,7 @@ use crate::{ generic::Digest, scale_info::TypeInfo, traits::{ - self, AtLeast32BitUnsigned, Hash as HashT, MaybeDisplay, MaybeMallocSizeOf, MaybeSerialize, + self, AtLeast32BitUnsigned, Hash as HashT, MaybeDisplay, MaybeSerialize, MaybeSerializeDeserialize, Member, SimpleBitOps, }, }; @@ -54,22 +54,6 @@ pub struct Header + TryFrom, Hash: HashT> { pub digest: Digest, } -#[cfg(feature = "std")] -impl parity_util_mem::MallocSizeOf for Header -where - Number: Copy + Into + TryFrom + parity_util_mem::MallocSizeOf, - Hash: HashT, - Hash::Output: parity_util_mem::MallocSizeOf, -{ - fn size_of(&self, ops: &mut parity_util_mem::MallocSizeOfOps) -> usize { - self.parent_hash.size_of(ops) + - self.number.size_of(ops) + - self.state_root.size_of(ops) + - self.extrinsics_root.size_of(ops) + - self.digest.size_of(ops) - } -} - #[cfg(feature = "std")] pub fn serialize_number + TryFrom>( val: &T, @@ -103,8 +87,7 @@ where + Copy + Into + TryFrom - + sp_std::str::FromStr - + MaybeMallocSizeOf, + + sp_std::str::FromStr, Hash: HashT, Hash::Output: Default + sp_std::hash::Hash @@ -115,8 +98,7 @@ where + Debug + MaybeDisplay + SimpleBitOps - + Codec - + MaybeMallocSizeOf, + + Codec, { type Number = Number; type Hash = ::Output; diff --git a/primitives/runtime/src/generic/unchecked_extrinsic.rs b/primitives/runtime/src/generic/unchecked_extrinsic.rs index fb333abd6a..5d378410e4 100644 --- a/primitives/runtime/src/generic/unchecked_extrinsic.rs +++ b/primitives/runtime/src/generic/unchecked_extrinsic.rs @@ -87,18 +87,6 @@ where } } -#[cfg(feature = "std")] -impl parity_util_mem::MallocSizeOf - for UncheckedExtrinsic -where - Extra: SignedExtension, -{ - fn size_of(&self, _ops: &mut parity_util_mem::MallocSizeOfOps) -> usize { - // Instantiated only in runtime. - 0 - } -} - impl UncheckedExtrinsic { diff --git a/primitives/runtime/src/lib.rs b/primitives/runtime/src/lib.rs index 3752e31cbe..e94efda86a 100644 --- a/primitives/runtime/src/lib.rs +++ b/primitives/runtime/src/lib.rs @@ -874,13 +874,6 @@ impl OpaqueExtrinsic { } } -#[cfg(feature = "std")] -impl parity_util_mem::MallocSizeOf for OpaqueExtrinsic { - fn size_of(&self, ops: &mut parity_util_mem::MallocSizeOfOps) -> usize { - self.0.size_of(ops) - } -} - impl sp_std::fmt::Debug for OpaqueExtrinsic { #[cfg(feature = "std")] fn fmt(&self, fmt: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { diff --git a/primitives/runtime/src/testing.rs b/primitives/runtime/src/testing.rs index d16a37e6a2..0cd78ba626 100644 --- a/primitives/runtime/src/testing.rs +++ b/primitives/runtime/src/testing.rs @@ -204,13 +204,10 @@ impl Header { } /// An opaque extrinsic wrapper type. -#[derive(PartialEq, Eq, Clone, Debug, Encode, Decode, parity_util_mem::MallocSizeOf)] +#[derive(PartialEq, Eq, Clone, Debug, Encode, Decode)] pub struct ExtrinsicWrapper(Xt); -impl traits::Extrinsic for ExtrinsicWrapper -where - Xt: parity_util_mem::MallocSizeOf, -{ +impl traits::Extrinsic for ExtrinsicWrapper { type Call = (); type SignaturePayload = (); @@ -243,7 +240,7 @@ impl Deref for ExtrinsicWrapper { } /// Testing block -#[derive(PartialEq, Eq, Clone, Serialize, Debug, Encode, Decode, parity_util_mem::MallocSizeOf)] +#[derive(PartialEq, Eq, Clone, Serialize, Debug, Encode, Decode)] pub struct Block { /// Block header pub header: Header, @@ -306,9 +303,6 @@ impl TestXt { } } -// Non-opaque extrinsics always 0. -parity_util_mem::malloc_size_of_is_0!(any: TestXt); - impl Serialize for TestXt where TestXt: Encode, diff --git a/primitives/runtime/src/traits.rs b/primitives/runtime/src/traits.rs index 276a62349a..6af711cba8 100644 --- a/primitives/runtime/src/traits.rs +++ b/primitives/runtime/src/traits.rs @@ -808,9 +808,6 @@ sp_core::impl_maybe_marker!( /// A type that implements Serialize, DeserializeOwned and Debug when in std environment. trait MaybeSerializeDeserialize: DeserializeOwned, Serialize; - - /// A type that implements MallocSizeOf. - trait MaybeMallocSizeOf: parity_util_mem::MallocSizeOf; ); /// A type that can be used in runtime structures. @@ -828,9 +825,7 @@ pub trait IsMember { /// `parent_hash`, as well as a `digest` and a block `number`. /// /// You can also create a `new` one from those fields. -pub trait Header: - Clone + Send + Sync + Codec + Eq + MaybeSerialize + Debug + MaybeMallocSizeOf + 'static -{ +pub trait Header: Clone + Send + Sync + Codec + Eq + MaybeSerialize + Debug + 'static { /// Header number. type Number: Member + MaybeSerializeDeserialize @@ -840,8 +835,7 @@ pub trait Header: + MaybeDisplay + AtLeast32BitUnsigned + Codec - + sp_std::str::FromStr - + MaybeMallocSizeOf; + + sp_std::str::FromStr; /// Header hash type type Hash: Member + MaybeSerializeDeserialize @@ -855,7 +849,6 @@ pub trait Header: + Codec + AsRef<[u8]> + AsMut<[u8]> - + MaybeMallocSizeOf + TypeInfo; /// Hashing algorithm type Hashing: Hash; @@ -904,13 +897,11 @@ pub trait Header: /// `Extrinsic` pieces of information as well as a `Header`. /// /// You can get an iterator over each of the `extrinsics` and retrieve the `header`. -pub trait Block: - Clone + Send + Sync + Codec + Eq + MaybeSerialize + Debug + MaybeMallocSizeOf + 'static -{ +pub trait Block: Clone + Send + Sync + Codec + Eq + MaybeSerialize + Debug + 'static { /// Type for extrinsics. - type Extrinsic: Member + Codec + Extrinsic + MaybeSerialize + MaybeMallocSizeOf; + type Extrinsic: Member + Codec + Extrinsic + MaybeSerialize; /// Header type. - type Header: Header + MaybeMallocSizeOf; + type Header: Header; /// Block hash type. type Hash: Member + MaybeSerializeDeserialize @@ -924,7 +915,6 @@ pub trait Block: + Codec + AsRef<[u8]> + AsMut<[u8]> - + MaybeMallocSizeOf + TypeInfo; /// Returns a reference to the header. @@ -945,7 +935,7 @@ pub trait Block: } /// Something that acts like an `Extrinsic`. -pub trait Extrinsic: Sized + MaybeMallocSizeOf { +pub trait Extrinsic: Sized { /// The function call. type Call; diff --git a/primitives/runtime/src/transaction_validity.rs b/primitives/runtime/src/transaction_validity.rs index d8e71cc276..3b89644c28 100644 --- a/primitives/runtime/src/transaction_validity.rs +++ b/primitives/runtime/src/transaction_validity.rs @@ -227,7 +227,6 @@ impl From for TransactionValidity { /// For instance we can disallow specific kinds of transactions if they were not produced /// by our local node (for instance off-chain workers). #[derive(Copy, Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)] -#[cfg_attr(feature = "std", derive(parity_util_mem::MallocSizeOf))] pub enum TransactionSource { /// Transaction is already included in block. /// diff --git a/primitives/test-primitives/Cargo.toml b/primitives/test-primitives/Cargo.toml index 6cfd17afcc..1d12ed74ee 100644 --- a/primitives/test-primitives/Cargo.toml +++ b/primitives/test-primitives/Cargo.toml @@ -13,7 +13,6 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -parity-util-mem = { version = "0.12.0", default-features = false, features = ["primitive-types"] } serde = { version = "1.0.136", features = ["derive"], optional = true } sp-application-crypto = { version = "7.0.0", default-features = false, path = "../application-crypto" } sp-core = { version = "7.0.0", default-features = false, path = "../core" } @@ -25,7 +24,6 @@ default = [ ] std = [ "codec/std", - "parity-util-mem/std", "serde", "sp-application-crypto/std", "sp-core/std", diff --git a/primitives/test-primitives/src/lib.rs b/primitives/test-primitives/src/lib.rs index 976bb9ddd9..9779fe2393 100644 --- a/primitives/test-primitives/src/lib.rs +++ b/primitives/test-primitives/src/lib.rs @@ -29,7 +29,6 @@ use sp_runtime::traits::{BlakeTwo256, Extrinsic as ExtrinsicT, Verify}; /// Extrinsic for test-runtime. #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)] -#[cfg_attr(feature = "std", derive(parity_util_mem::MallocSizeOf))] pub enum Extrinsic { IncludeData(Vec), StorageChange(Vec, Option>), diff --git a/primitives/trie/Cargo.toml b/primitives/trie/Cargo.toml index 67839a157a..68a5fb17c3 100644 --- a/primitives/trie/Cargo.toml +++ b/primitives/trie/Cargo.toml @@ -24,7 +24,7 @@ hashbrown = { version = "0.12.3", optional = true } hash-db = { version = "0.15.2", default-features = false } lazy_static = { version = "1.4.0", optional = true } lru = { version = "0.8.1", optional = true } -memory-db = { version = "0.30.0", default-features = false } +memory-db = { version = "0.31.0", default-features = false } nohash-hasher = { version = "0.2.0", optional = true } parking_lot = { version = "0.12.1", optional = true } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } @@ -38,7 +38,7 @@ sp-std = { version = "5.0.0", default-features = false, path = "../std" } [dev-dependencies] array-bytes = "4.1" criterion = "0.3.3" -trie-bench = "0.32.0" +trie-bench = "0.33.0" trie-standardmap = "0.15.2" sp-runtime = { version = "7.0.0", path = "../runtime" } @@ -62,4 +62,3 @@ std = [ "trie-db/std", "trie-root/std", ] -memory-tracker = [] diff --git a/primitives/trie/src/lib.rs b/primitives/trie/src/lib.rs index d036db7b1f..01650e9a37 100644 --- a/primitives/trie/src/lib.rs +++ b/primitives/trie/src/lib.rs @@ -146,11 +146,6 @@ where } } -#[cfg(not(feature = "memory-tracker"))] -type MemTracker = memory_db::NoopTracker; -#[cfg(feature = "memory-tracker")] -type MemTracker = memory_db::MemCounter; - /// TrieDB error over `TrieConfiguration` trait. pub type TrieError = trie_db::TrieError, CError>; /// Reexport from `hash_db`, with genericity set for `Hasher` trait. @@ -161,14 +156,13 @@ pub type HashDB<'a, H> = dyn hash_db::HashDB + 'a; /// Reexport from `hash_db`, with genericity set for `Hasher` trait. /// This uses a `KeyFunction` for prefixing keys internally (avoiding /// key conflict for non random keys). -pub type PrefixedMemoryDB = - memory_db::MemoryDB, trie_db::DBValue, MemTracker>; +pub type PrefixedMemoryDB = memory_db::MemoryDB, trie_db::DBValue>; /// Reexport from `hash_db`, with genericity set for `Hasher` trait. /// This uses a noops `KeyFunction` (key addressing must be hashed or using /// an encoding scheme that avoid key conflict). -pub type MemoryDB = memory_db::MemoryDB, trie_db::DBValue, MemTracker>; +pub type MemoryDB = memory_db::MemoryDB, trie_db::DBValue>; /// Reexport from `hash_db`, with genericity set for `Hasher` trait. -pub type GenericMemoryDB = memory_db::MemoryDB; +pub type GenericMemoryDB = memory_db::MemoryDB; /// Persistent trie database read-access interface for the a given hasher. pub type TrieDB<'a, 'cache, L> = trie_db::TrieDB<'a, 'cache, L>; @@ -548,8 +542,7 @@ mod tests { type LayoutV0 = super::LayoutV0; type LayoutV1 = super::LayoutV1; - type MemoryDBMeta = - memory_db::MemoryDB, trie_db::DBValue, MemTracker>; + type MemoryDBMeta = memory_db::MemoryDB, trie_db::DBValue>; fn hashed_null_node() -> TrieHash { ::hashed_null_node() diff --git a/scripts/ci/deny.toml b/scripts/ci/deny.toml index 8cc7635d50..bc41f746cb 100644 --- a/scripts/ci/deny.toml +++ b/scripts/ci/deny.toml @@ -160,7 +160,6 @@ allow = [ ] # List of crates to deny deny = [ - { name = "parity-util-mem", version = "<0.6" } # Each entry the name of a crate and a version range. If version is # not specified, all versions will be matched. ] diff --git a/test-utils/runtime/Cargo.toml b/test-utils/runtime/Cargo.toml index a201906f1c..7576a63ac3 100644 --- a/test-utils/runtime/Cargo.toml +++ b/test-utils/runtime/Cargo.toml @@ -23,7 +23,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../../primitives/inherents" } sp-keyring = { version = "7.0.0", optional = true, path = "../../primitives/keyring" } -memory-db = { version = "0.30.0", default-features = false } +memory-db = { version = "0.31.0", default-features = false } sp-offchain = { version = "4.0.0-dev", default-features = false, path = "../../primitives/offchain" } sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } @@ -42,7 +42,6 @@ sp-finality-grandpa = { version = "4.0.0-dev", default-features = false, path = sp-trie = { version = "7.0.0", default-features = false, path = "../../primitives/trie" } sp-transaction-pool = { version = "4.0.0-dev", default-features = false, path = "../../primitives/transaction-pool" } trie-db = { version = "0.24.0", default-features = false } -parity-util-mem = { version = "0.12.0", default-features = false, features = ["primitive-types"] } sc-service = { version = "0.10.0-dev", default-features = false, optional = true, features = ["test-helpers"], path = "../../client/service" } sp-state-machine = { version = "0.13.0", default-features = false, path = "../../primitives/state-machine" } sp-externalities = { version = "0.13.0", default-features = false, path = "../../primitives/externalities" } @@ -67,7 +66,6 @@ default = [ "std", ] std = [ - "parity-util-mem/std", "beefy-primitives/std", "beefy-merkle-tree/std", "sp-application-crypto/std", diff --git a/test-utils/runtime/src/lib.rs b/test-utils/runtime/src/lib.rs index 51f057e3de..8b64528e24 100644 --- a/test-utils/runtime/src/lib.rs +++ b/test-utils/runtime/src/lib.rs @@ -166,8 +166,6 @@ pub enum Extrinsic { Store(Vec), } -parity_util_mem::malloc_size_of_is_0!(Extrinsic); // non-opaque extrinsic does not need this - #[cfg(feature = "std")] impl serde::Serialize for Extrinsic { fn serialize(&self, seq: S) -> Result diff --git a/utils/frame/benchmarking-cli/Cargo.toml b/utils/frame/benchmarking-cli/Cargo.toml index 1b38f0295f..5173340f7d 100644 --- a/utils/frame/benchmarking-cli/Cargo.toml +++ b/utils/frame/benchmarking-cli/Cargo.toml @@ -22,11 +22,11 @@ handlebars = "4.2.2" hash-db = "0.15.2" Inflector = "0.11.4" itertools = "0.10.3" -kvdb = "0.12.0" +kvdb = "0.13.0" lazy_static = "1.4.0" linked-hash-map = "0.5.4" log = "0.4.17" -memory-db = "0.30.0" +memory-db = "0.31.0" rand = { version = "0.8.4", features = ["small_rng"] } rand_pcg = "0.3.1" serde = "1.0.136" From 11c50578549969979121577cde987ad3f9d95bd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Tue, 6 Dec 2022 15:58:52 +0100 Subject: [PATCH 004/127] frame-support: Introduce `EnsureOriginOrHigherPrivilege` (#12844) * frame-support: Introduce `EnsureOriginOrHigherPrivilege` This adds a new `EnsureOrigin` implementation that checks if a given origin matches or if the origin is has a higher or equal origin matches or if the origin is has a higher or equal privilege. * FMT --- frame/support/src/traits.rs | 4 +- frame/support/src/traits/dispatch.rs | 92 +++++++++++++++++++++++++++- 2 files changed, 91 insertions(+), 5 deletions(-) diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index 1b1d5d68cb..e5ba98fe0c 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -99,8 +99,8 @@ mod dispatch; pub use dispatch::EnsureOneOf; pub use dispatch::{ AsEnsureOriginWithArg, CallerTrait, EitherOf, EitherOfDiverse, EnsureOrigin, - EnsureOriginWithArg, MapSuccess, NeverEnsureOrigin, OriginTrait, TryMapSuccess, - UnfilteredDispatchable, + EnsureOriginEqualOrHigherPrivilege, EnsureOriginWithArg, MapSuccess, NeverEnsureOrigin, + OriginTrait, TryMapSuccess, UnfilteredDispatchable, }; mod voting; diff --git a/frame/support/src/traits/dispatch.rs b/frame/support/src/traits/dispatch.rs index b96cfae450..36ddf5b507 100644 --- a/frame/support/src/traits/dispatch.rs +++ b/frame/support/src/traits/dispatch.rs @@ -20,10 +20,12 @@ use crate::dispatch::{DispatchResultWithPostInfo, Parameter, RawOrigin}; use codec::MaxEncodedLen; use sp_runtime::{ - traits::{BadOrigin, Member, Morph, TryMorph}, + traits::{BadOrigin, Get, Member, Morph, TryMorph}, Either, }; -use sp_std::marker::PhantomData; +use sp_std::{cmp::Ordering, marker::PhantomData}; + +use super::misc; /// Some sort of check on the origin is performed by this object. pub trait EnsureOrigin { @@ -59,7 +61,7 @@ pub trait EnsureOrigin { } } -/// `EnsureOrigin` implementation that always fails. +/// [`EnsureOrigin`] implementation that always fails. pub struct NeverEnsureOrigin(sp_std::marker::PhantomData); impl EnsureOrigin for NeverEnsureOrigin { type Success = Success; @@ -72,6 +74,90 @@ impl EnsureOrigin for NeverEnsureOrigin { } } +/// [`EnsureOrigin`] implementation that checks that an origin has equal or higher privilege +/// compared to the expected `Origin`. +/// +/// It will take the shortcut of comparing the incoming origin with the expected `Origin` and if +/// both are the same the origin is accepted. +/// +/// # Example +/// +/// ```rust +/// # use frame_support::traits::{EnsureOriginEqualOrHigherPrivilege, PrivilegeCmp, EnsureOrigin as _}; +/// # use sp_runtime::traits::{parameter_types, Get}; +/// # use sp_std::cmp::Ordering; +/// +/// #[derive(Eq, PartialEq, Debug)] +/// pub enum Origin { +/// Root, +/// SomethingBelowRoot, +/// NormalUser, +/// } +/// +/// struct OriginPrivilegeCmp; +/// +/// impl PrivilegeCmp for OriginPrivilegeCmp { +/// fn cmp_privilege(left: &Origin, right: &Origin) -> Option { +/// match (left, right) { +/// (Origin::Root, Origin::Root) => Some(Ordering::Equal), +/// (Origin::Root, _) => Some(Ordering::Greater), +/// (Origin::SomethingBelowRoot, Origin::SomethingBelowRoot) => Some(Ordering::Equal), +/// (Origin::SomethingBelowRoot, Origin::Root) => Some(Ordering::Less), +/// (Origin::SomethingBelowRoot, Origin::NormalUser) => Some(Ordering::Greater), +/// (Origin::NormalUser, Origin::NormalUser) => Some(Ordering::Equal), +/// (Origin::NormalUser, _) => Some(Ordering::Less), +/// } +/// } +/// } +/// +/// parameter_types! { +/// pub const ExpectedOrigin: Origin = Origin::SomethingBelowRoot; +/// } +/// +/// type EnsureOrigin = EnsureOriginEqualOrHigherPrivilege; +/// +/// // `Root` has an higher privilege as our expected origin. +/// assert!(EnsureOrigin::ensure_origin(Origin::Root).is_ok()); +/// // `SomethingBelowRoot` is exactly the expected origin. +/// assert!(EnsureOrigin::ensure_origin(Origin::SomethingBelowRoot).is_ok()); +/// // The `NormalUser` origin is not allowed. +/// assert!(EnsureOrigin::ensure_origin(Origin::NormalUser).is_err()); +/// ``` +pub struct EnsureOriginEqualOrHigherPrivilege( + sp_std::marker::PhantomData<(Origin, PrivilegeCmp)>, +); + +impl EnsureOrigin + for EnsureOriginEqualOrHigherPrivilege +where + Origin: Get, + OuterOrigin: Eq, + PrivilegeCmp: misc::PrivilegeCmp, +{ + type Success = (); + + fn try_origin(o: OuterOrigin) -> Result { + let expected_origin = Origin::get(); + + // If this is the expected origin, it has the same privilege. + if o == expected_origin { + return Ok(()) + } + + let cmp = PrivilegeCmp::cmp_privilege(&o, &expected_origin); + + match cmp { + Some(Ordering::Equal) | Some(Ordering::Greater) => Ok(()), + None | Some(Ordering::Less) => Err(o), + } + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + Ok(Origin::get()) + } +} + /// Some sort of check on the origin is performed by this object. pub trait EnsureOriginWithArg { /// A return type. From 0d153c9c4003261d97bde4adbdd4bc151472f9b9 Mon Sep 17 00:00:00 2001 From: Adrian Catangiu Date: Wed, 7 Dec 2022 12:19:46 +0200 Subject: [PATCH 005/127] Mmr persist state (#12822) client/mmr: persisting gadget state across runs Fixes #12780 * client/mmr: on init do canonicalization catch-up * client/mmr: add more tests * client/mmr: persist gadget progress in aux db * client/mmr: add more tests * client/mmr: replace async_std with tokio * remove leftover comment * address review comments Signed-off-by: acatangiu --- Cargo.lock | 2 + client/merkle-mountain-range/Cargo.toml | 4 +- .../merkle-mountain-range/src/aux_schema.rs | 228 ++++++++++++++++++ client/merkle-mountain-range/src/lib.rs | 64 +++-- .../merkle-mountain-range/src/offchain_mmr.rs | 163 +++++++++++-- .../merkle-mountain-range/src/test_utils.rs | 98 +++++--- 6 files changed, 477 insertions(+), 82 deletions(-) create mode 100644 client/merkle-mountain-range/src/aux_schema.rs diff --git a/Cargo.lock b/Cargo.lock index 1ba2462a24..c4f06732a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4137,6 +4137,7 @@ dependencies = [ "futures", "log", "parity-scale-codec", + "parking_lot 0.12.1", "sc-block-builder", "sc-client-api", "sc-offchain", @@ -4148,6 +4149,7 @@ dependencies = [ "sp-io", "sp-mmr-primitives", "sp-runtime", + "sp-tracing", "substrate-test-runtime-client", "tokio", ] diff --git a/client/merkle-mountain-range/Cargo.toml b/client/merkle-mountain-range/Cargo.toml index e32764eff1..4fb423cee8 100644 --- a/client/merkle-mountain-range/Cargo.toml +++ b/client/merkle-mountain-range/Cargo.toml @@ -26,6 +26,8 @@ sc-offchain = { version = "4.0.0-dev", path = "../offchain" } sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } [dev-dependencies] -tokio = "1.17.0" +parking_lot = "0.12.1" sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } +sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } +tokio = "1.17.0" diff --git a/client/merkle-mountain-range/src/aux_schema.rs b/client/merkle-mountain-range/src/aux_schema.rs new file mode 100644 index 0000000000..907deb0bde --- /dev/null +++ b/client/merkle-mountain-range/src/aux_schema.rs @@ -0,0 +1,228 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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 . + +//! Schema for MMR-gadget state persisted in the aux-db. + +use crate::LOG_TARGET; +use codec::{Decode, Encode}; +use log::{info, trace}; +use sc_client_api::backend::AuxStore; +use sp_blockchain::{Error as ClientError, Result as ClientResult}; +use sp_runtime::traits::{Block, NumberFor}; + +const VERSION_KEY: &[u8] = b"mmr_auxschema_version"; +const GADGET_STATE: &[u8] = b"mmr_gadget_state"; + +const CURRENT_VERSION: u32 = 1; +pub(crate) type PersistedState = NumberFor; + +pub(crate) fn write_current_version(backend: &B) -> ClientResult<()> { + info!(target: LOG_TARGET, "write aux schema version {:?}", CURRENT_VERSION); + AuxStore::insert_aux(backend, &[(VERSION_KEY, CURRENT_VERSION.encode().as_slice())], &[]) +} + +/// Write gadget state. +pub(crate) fn write_gadget_state( + backend: &BE, + state: &PersistedState, +) -> ClientResult<()> { + trace!(target: LOG_TARGET, "persisting {:?}", state); + backend.insert_aux(&[(GADGET_STATE, state.encode().as_slice())], &[]) +} + +fn load_decode(backend: &B, key: &[u8]) -> ClientResult> { + match backend.get_aux(key)? { + None => Ok(None), + Some(t) => T::decode(&mut &t[..]) + .map_err(|e| ClientError::Backend(format!("MMR aux DB is corrupted: {}", e))) + .map(Some), + } +} + +/// Load or initialize persistent data from backend. +pub(crate) fn load_persistent(backend: &BE) -> ClientResult>> +where + B: Block, + BE: AuxStore, +{ + let version: Option = load_decode(backend, VERSION_KEY)?; + + match version { + None => (), + Some(1) => return load_decode::<_, PersistedState>(backend, GADGET_STATE), + other => + return Err(ClientError::Backend(format!("Unsupported MMR aux DB version: {:?}", other))), + } + + // No persistent state found in DB. + Ok(None) +} + +#[cfg(test)] +pub(crate) mod tests { + use super::*; + use crate::test_utils::{ + run_test_with_mmr_gadget_pre_post_using_client, MmrBlock, MockClient, OffchainKeyType, + }; + use parking_lot::Mutex; + use sp_core::offchain::{DbExternalities, StorageKind}; + use sp_mmr_primitives::utils::NodesUtils; + use sp_runtime::generic::BlockId; + use std::{sync::Arc, time::Duration}; + use substrate_test_runtime_client::{runtime::Block, Backend}; + + #[test] + fn should_load_persistent_sanity_checks() { + let client = MockClient::new(); + let backend = &*client.backend; + + // version not available in db -> None + assert_eq!(load_persistent::(backend).unwrap(), None); + + // populate version in db + write_current_version(backend).unwrap(); + // verify correct version is retrieved + assert_eq!(load_decode(backend, VERSION_KEY).unwrap(), Some(CURRENT_VERSION)); + + // version is available in db but state isn't -> None + assert_eq!(load_persistent::(backend).unwrap(), None); + } + + #[test] + fn should_persist_progress_across_runs() { + sp_tracing::try_init_simple(); + + let client = Arc::new(MockClient::new()); + let backend = client.backend.clone(); + + // version not available in db -> None + assert_eq!(load_decode::>(&*backend, VERSION_KEY).unwrap(), None); + // state not available in db -> None + assert_eq!(load_persistent::(&*backend).unwrap(), None); + // run the gadget while importing and finalizing 3 blocks + run_test_with_mmr_gadget_pre_post_using_client( + client.clone(), + |_| async {}, + |client| async move { + let a1 = client.import_block(&BlockId::Number(0), b"a1", Some(0)).await; + let a2 = client.import_block(&BlockId::Number(1), b"a2", Some(1)).await; + let a3 = client.import_block(&BlockId::Number(2), b"a3", Some(2)).await; + client.finalize_block(a3.hash(), Some(3)); + tokio::time::sleep(Duration::from_millis(200)).await; + // a1, a2, a3 were canonicalized + client.assert_canonicalized(&[&a1, &a2, &a3]); + }, + ); + + // verify previous progress was persisted and run the gadget again + run_test_with_mmr_gadget_pre_post_using_client( + client.clone(), + |client| async move { + let backend = &*client.backend; + // check there is both version and best canon available in db before running gadget + assert_eq!(load_decode(backend, VERSION_KEY).unwrap(), Some(CURRENT_VERSION)); + assert_eq!(load_persistent::(backend).unwrap(), Some(3)); + }, + |client| async move { + let a4 = client.import_block(&BlockId::Number(3), b"a4", Some(3)).await; + let a5 = client.import_block(&BlockId::Number(4), b"a5", Some(4)).await; + let a6 = client.import_block(&BlockId::Number(5), b"a6", Some(5)).await; + client.finalize_block(a6.hash(), Some(6)); + tokio::time::sleep(Duration::from_millis(200)).await; + + // a4, a5, a6 were canonicalized + client.assert_canonicalized(&[&a4, &a5, &a6]); + // check persisted best canon was updated + assert_eq!(load_persistent::(&*client.backend).unwrap(), Some(6)); + }, + ); + } + + #[test] + fn should_resume_from_persisted_state() { + sp_tracing::try_init_simple(); + + let client = Arc::new(MockClient::new()); + let blocks = Arc::new(Mutex::new(Vec::::new())); + let blocks_clone = blocks.clone(); + + // run the gadget while importing and finalizing 3 blocks + run_test_with_mmr_gadget_pre_post_using_client( + client.clone(), + |_| async {}, + |client| async move { + let mut blocks = blocks_clone.lock(); + blocks.push(client.import_block(&BlockId::Number(0), b"a1", Some(0)).await); + blocks.push(client.import_block(&BlockId::Number(1), b"a2", Some(1)).await); + blocks.push(client.import_block(&BlockId::Number(2), b"a3", Some(2)).await); + client.finalize_block(blocks.last().unwrap().hash(), Some(3)); + tokio::time::sleep(Duration::from_millis(200)).await; + // a1, a2, a3 were canonicalized + let slice: Vec<&MmrBlock> = blocks.iter().collect(); + client.assert_canonicalized(&slice); + + // now manually move them back to non-canon/temp location + let mut offchain_db = client.offchain_db(); + for mmr_block in slice { + for node in NodesUtils::right_branch_ending_in_leaf(mmr_block.leaf_idx.unwrap()) + { + let canon_key = mmr_block.get_offchain_key(node, OffchainKeyType::Canon); + let val = offchain_db + .local_storage_get(StorageKind::PERSISTENT, &canon_key) + .unwrap(); + offchain_db.local_storage_clear(StorageKind::PERSISTENT, &canon_key); + + let temp_key = mmr_block.get_offchain_key(node, OffchainKeyType::Temp); + offchain_db.local_storage_set(StorageKind::PERSISTENT, &temp_key, &val); + } + } + }, + ); + + let blocks_clone = blocks.clone(); + // verify new gadget continues from block 4 and ignores 1, 2, 3 based on persisted state + run_test_with_mmr_gadget_pre_post_using_client( + client.clone(), + |client| async move { + let blocks = blocks_clone.lock(); + let slice: Vec<&MmrBlock> = blocks.iter().collect(); + + // verify persisted state says a1, a2, a3 were canonicalized, + assert_eq!(load_persistent::(&*client.backend).unwrap(), Some(3)); + // but actually they are NOT canon (we manually reverted them earlier). + client.assert_not_canonicalized(&slice); + }, + |client| async move { + let a4 = client.import_block(&BlockId::Number(3), b"a4", Some(3)).await; + let a5 = client.import_block(&BlockId::Number(4), b"a5", Some(4)).await; + let a6 = client.import_block(&BlockId::Number(5), b"a6", Some(5)).await; + client.finalize_block(a6.hash(), Some(6)); + tokio::time::sleep(Duration::from_millis(200)).await; + + let block_1_to_3 = blocks.lock(); + let slice: Vec<&MmrBlock> = block_1_to_3.iter().collect(); + // verify a1, a2, a3 are still NOT canon (skipped by gadget based on data in aux db) + client.assert_not_canonicalized(&slice); + // but a4, a5, a6 were canonicalized + client.assert_canonicalized(&[&a4, &a5, &a6]); + // check persisted best canon was updated + assert_eq!(load_persistent::(&*client.backend).unwrap(), Some(6)); + }, + ); + } +} diff --git a/client/merkle-mountain-range/src/lib.rs b/client/merkle-mountain-range/src/lib.rs index 59f26b4265..401a5d5d4d 100644 --- a/client/merkle-mountain-range/src/lib.rs +++ b/client/merkle-mountain-range/src/lib.rs @@ -37,15 +37,15 @@ #![warn(missing_docs)] +mod aux_schema; mod offchain_mmr; #[cfg(test)] pub mod test_utils; -use std::{marker::PhantomData, sync::Arc}; - +use crate::offchain_mmr::OffchainMmr; +use beefy_primitives::MmrRootHash; use futures::StreamExt; -use log::{error, trace, warn}; - +use log::{debug, error, trace, warn}; use sc_client_api::{Backend, BlockchainEvents, FinalityNotifications}; use sc_offchain::OffchainDb; use sp_api::ProvideRuntimeApi; @@ -55,50 +55,75 @@ use sp_runtime::{ generic::BlockId, traits::{Block, Header, NumberFor}, }; - -use crate::offchain_mmr::OffchainMMR; -use beefy_primitives::MmrRootHash; -use sp_core::offchain::OffchainStorage; +use std::{marker::PhantomData, sync::Arc}; /// Logging target for the mmr gadget. pub const LOG_TARGET: &str = "mmr"; -struct OffchainMmrBuilder { +struct OffchainMmrBuilder, C> { + backend: Arc, client: Arc, - offchain_db: OffchainDb, + offchain_db: OffchainDb, indexing_prefix: Vec, _phantom: PhantomData, } -impl OffchainMmrBuilder +impl OffchainMmrBuilder where B: Block, + BE: Backend, C: ProvideRuntimeApi + HeaderBackend + HeaderMetadata, C::Api: MmrApi>, - S: OffchainStorage, { async fn try_build( self, finality_notifications: &mut FinalityNotifications, - ) -> Option> { + ) -> Option> { while let Some(notification) = finality_notifications.next().await { let best_block = *notification.header.number(); match self.client.runtime_api().mmr_leaf_count(&BlockId::number(best_block)) { Ok(Ok(mmr_leaf_count)) => { + debug!( + target: LOG_TARGET, + "pallet-mmr detected at block {:?} with mmr size {:?}", + best_block, + mmr_leaf_count + ); match utils::first_mmr_block_num::(best_block, mmr_leaf_count) { Ok(first_mmr_block) => { - let mut offchain_mmr = OffchainMMR { + debug!( + target: LOG_TARGET, + "pallet-mmr genesis computed at block {:?}", first_mmr_block, + ); + let best_canonicalized = + match offchain_mmr::load_or_init_best_canonicalized::( + &*self.backend, + first_mmr_block, + ) { + Ok(best) => best, + Err(e) => { + error!( + target: LOG_TARGET, + "Error loading state from aux db: {:?}", e + ); + return None + }, + }; + let mut offchain_mmr = OffchainMmr { + backend: self.backend, client: self.client, offchain_db: self.offchain_db, indexing_prefix: self.indexing_prefix, first_mmr_block, - - _phantom: Default::default(), + best_canonicalized, }; + // We need to make sure all blocks leading up to current notification + // have also been canonicalized. + offchain_mmr.canonicalize_catch_up(¬ification); // We have to canonicalize and prune the blocks in the finality // notification that lead to building the offchain-mmr as well. - offchain_mmr.canonicalize_and_prune(¬ification); + offchain_mmr.canonicalize_and_prune(notification); return Some(offchain_mmr) }, Err(e) => { @@ -143,14 +168,14 @@ where C: BlockchainEvents + HeaderBackend + HeaderMetadata + ProvideRuntimeApi, C::Api: MmrApi>, { - async fn run(mut self, builder: OffchainMmrBuilder) { + async fn run(mut self, builder: OffchainMmrBuilder) { let mut offchain_mmr = match builder.try_build(&mut self.finality_notifications).await { Some(offchain_mmr) => offchain_mmr, None => return, }; while let Some(notification) = self.finality_notifications.next().await { - offchain_mmr.canonicalize_and_prune(¬ification); + offchain_mmr.canonicalize_and_prune(notification); } } @@ -174,6 +199,7 @@ where }; mmr_gadget .run(OffchainMmrBuilder { + backend, client, offchain_db, indexing_prefix, diff --git a/client/merkle-mountain-range/src/offchain_mmr.rs b/client/merkle-mountain-range/src/offchain_mmr.rs index 1cdd3810b4..988b3ffef8 100644 --- a/client/merkle-mountain-range/src/offchain_mmr.rs +++ b/client/merkle-mountain-range/src/offchain_mmr.rs @@ -21,33 +21,57 @@ #![warn(missing_docs)] -use std::{marker::PhantomData, sync::Arc}; - -use log::{debug, error, warn}; - -use sc_client_api::FinalityNotification; +use crate::{aux_schema, LOG_TARGET}; +use log::{debug, error, info, warn}; +use sc_client_api::{AuxStore, Backend, FinalityNotification}; use sc_offchain::OffchainDb; use sp_blockchain::{CachedHeaderMetadata, ForkBackend, HeaderBackend, HeaderMetadata}; -use sp_core::offchain::{DbExternalities, OffchainStorage, StorageKind}; +use sp_core::offchain::{DbExternalities, StorageKind}; use sp_mmr_primitives::{utils, utils::NodesUtils, NodeIndex}; -use sp_runtime::traits::{Block, Header}; - -use crate::LOG_TARGET; +use sp_runtime::{ + traits::{Block, NumberFor, One}, + Saturating, +}; +use std::{collections::VecDeque, sync::Arc}; + +pub(crate) fn load_or_init_best_canonicalized( + backend: &BE, + first_mmr_block: NumberFor, +) -> sp_blockchain::Result> +where + BE: AuxStore, + B: Block, +{ + // Initialize gadget best_canon from AUX DB or from pallet genesis. + if let Some(best) = aux_schema::load_persistent::(backend)? { + info!(target: LOG_TARGET, "Loading MMR best canonicalized state from db: {:?}.", best); + Ok(best) + } else { + let best = first_mmr_block.saturating_sub(One::one()); + info!( + target: LOG_TARGET, + "Loading MMR from pallet genesis on what appears to be the first startup: {:?}.", best + ); + aux_schema::write_current_version(backend)?; + aux_schema::write_gadget_state::(backend, &best)?; + Ok(best) + } +} /// `OffchainMMR` exposes MMR offchain canonicalization and pruning logic. -pub struct OffchainMMR { +pub struct OffchainMmr, C> { + pub backend: Arc, pub client: Arc, - pub offchain_db: OffchainDb, + pub offchain_db: OffchainDb, pub indexing_prefix: Vec, - pub first_mmr_block: ::Number, - - pub _phantom: PhantomData, + pub first_mmr_block: NumberFor, + pub best_canonicalized: NumberFor, } -impl OffchainMMR +impl OffchainMmr where C: HeaderBackend + HeaderMetadata, - S: OffchainStorage, + BE: Backend, B: Block, { fn node_temp_offchain_key(&self, pos: NodeIndex, parent_hash: B::Hash) -> Vec { @@ -77,7 +101,7 @@ where fn right_branch_ending_in_block_or_log( &self, - block_num: ::Number, + block_num: NumberFor, action: &str, ) -> Option> { match utils::block_num_to_leaf_index::(block_num, self.first_mmr_block) { @@ -128,9 +152,9 @@ where } } - fn canonicalize_branch(&mut self, block_hash: &B::Hash) { + fn canonicalize_branch(&mut self, block_hash: B::Hash) { let action = "canonicalize"; - let header = match self.header_metadata_or_log(*block_hash, action) { + let header = match self.header_metadata_or_log(block_hash, action) { Some(header) => header, _ => return, }; @@ -148,6 +172,7 @@ where None => { // If we can't convert the block number to a leaf index, the chain state is probably // corrupted. We only log the error, hoping that the chain state will be fixed. + self.best_canonicalized = header.number; return }, }; @@ -174,16 +199,58 @@ where ); } } + if self.best_canonicalized != header.number.saturating_sub(One::one()) { + warn!( + target: LOG_TARGET, + "Detected canonicalization skip: best {:?} current {:?}.", + self.best_canonicalized, + header.number, + ); + } + self.best_canonicalized = header.number; + } + + /// In case of missed finality notifications (node restarts for example), + /// make sure to also canon everything leading up to `notification.tree_route`. + pub fn canonicalize_catch_up(&mut self, notification: &FinalityNotification) { + let first = notification.tree_route.first().unwrap_or(¬ification.hash); + if let Some(mut header) = self.header_metadata_or_log(*first, "canonicalize") { + let mut to_canon = VecDeque::<::Hash>::new(); + // Walk up the chain adding all blocks newer than `self.best_canonicalized`. + loop { + header = match self.header_metadata_or_log(header.parent, "canonicalize") { + Some(header) => header, + _ => break, + }; + if header.number <= self.best_canonicalized { + break + } + to_canon.push_front(header.hash); + } + // Canonicalize all blocks leading up to current finality notification. + for hash in to_canon.drain(..) { + self.canonicalize_branch(hash); + } + if let Err(e) = + aux_schema::write_gadget_state::(&*self.backend, &self.best_canonicalized) + { + debug!(target: LOG_TARGET, "error saving state: {:?}", e); + } + } } /// Move leafs and nodes added by finalized blocks in offchain db from _fork-aware key_ to /// _canonical key_. /// Prune leafs and nodes added by stale blocks in offchain db from _fork-aware key_. - pub fn canonicalize_and_prune(&mut self, notification: &FinalityNotification) { + pub fn canonicalize_and_prune(&mut self, notification: FinalityNotification) { // Move offchain MMR nodes for finalized blocks to canonical keys. - for block_hash in notification.tree_route.iter().chain(std::iter::once(¬ification.hash)) + for hash in notification.tree_route.iter().chain(std::iter::once(¬ification.hash)) { + self.canonicalize_branch(*hash); + } + if let Err(e) = + aux_schema::write_gadget_state::(&*self.backend, &self.best_canonicalized) { - self.canonicalize_branch(block_hash); + debug!(target: LOG_TARGET, "error saving state: {:?}", e); } // Remove offchain MMR nodes for stale forks. @@ -201,9 +268,10 @@ where #[cfg(test)] mod tests { - use crate::test_utils::run_test_with_mmr_gadget; + use crate::test_utils::{run_test_with_mmr_gadget, run_test_with_mmr_gadget_pre_post}; + use parking_lot::Mutex; use sp_runtime::generic::BlockId; - use std::time::Duration; + use std::{sync::Arc, time::Duration}; #[test] fn canonicalize_and_prune_works_correctly() { @@ -243,4 +311,51 @@ mod tests { client.assert_pruned(&[&b1, &b2, &b3, &a4]); }) } + + #[test] + fn canonicalize_catchup_works_correctly() { + let mmr_blocks = Arc::new(Mutex::new(vec![])); + let mmr_blocks_ref = mmr_blocks.clone(); + run_test_with_mmr_gadget_pre_post( + |client| async move { + // G -> A1 -> A2 + // | | + // | | -> finalized without gadget (missed notification) + // | + // | -> first mmr block + + let a1 = client.import_block(&BlockId::Number(0), b"a1", Some(0)).await; + let a2 = client.import_block(&BlockId::Hash(a1.hash()), b"a2", Some(1)).await; + + client.finalize_block(a2.hash(), Some(2)); + + { + let mut mmr_blocks = mmr_blocks_ref.lock(); + mmr_blocks.push(a1); + mmr_blocks.push(a2); + } + }, + |client| async move { + // G -> A1 -> A2 -> A3 -> A4 + // | | | | + // | | | | -> finalized after starting gadget + // | | | + // | | | -> gadget start + // | | + // | | -> finalized before starting gadget (missed notification) + // | + // | -> first mmr block + let blocks = mmr_blocks.lock(); + let a1 = blocks[0].clone(); + let a2 = blocks[1].clone(); + let a3 = client.import_block(&BlockId::Hash(a2.hash()), b"a3", Some(2)).await; + let a4 = client.import_block(&BlockId::Hash(a3.hash()), b"a4", Some(3)).await; + + client.finalize_block(a4.hash(), Some(4)); + tokio::time::sleep(Duration::from_millis(200)).await; + // expected finalized heads: a1, a2 _and_ a3, a4. + client.assert_canonicalized(&[&a1, &a2, &a3, &a4]); + }, + ) + } } diff --git a/client/merkle-mountain-range/src/test_utils.rs b/client/merkle-mountain-range/src/test_utils.rs index b854686b2d..f345fb5257 100644 --- a/client/merkle-mountain-range/src/test_utils.rs +++ b/client/merkle-mountain-range/src/test_utils.rs @@ -16,12 +16,8 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use std::{ - future::Future, - sync::{Arc, Mutex}, - time::Duration, -}; - +use crate::MmrGadget; +use parking_lot::Mutex; use sc_block_builder::BlockBuilderProvider; use sc_client_api::{ Backend as BackendT, BlockchainEvents, FinalityNotifications, ImportNotifications, @@ -41,33 +37,34 @@ use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, Header as HeaderT}, }; +use std::{future::Future, sync::Arc, time::Duration}; use substrate_test_runtime_client::{ runtime::{Block, BlockNumber, Hash, Header}, Backend, BlockBuilderExt, Client, ClientBlockImportExt, ClientExt, DefaultTestClientBuilderExt, TestClientBuilder, TestClientBuilderExt, }; - -use crate::MmrGadget; +use tokio::runtime::Runtime; type MmrHash = H256; -struct MockRuntimeApiData { - num_blocks: BlockNumber, +pub(crate) struct MockRuntimeApiData { + pub(crate) num_blocks: BlockNumber, } #[derive(Clone)] -pub struct MockRuntimeApi { - data: Arc>, +pub(crate) struct MockRuntimeApi { + pub(crate) data: Arc>, } impl MockRuntimeApi { - pub const INDEXING_PREFIX: &'static [u8] = b"mmr_test"; + pub(crate) const INDEXING_PREFIX: &'static [u8] = b"mmr_test"; } -pub struct MmrBlock { - block: Block, - leaf_idx: Option, - leaf_data: Vec, +#[derive(Clone, Debug)] +pub(crate) struct MmrBlock { + pub(crate) block: Block, + pub(crate) leaf_idx: Option, + pub(crate) leaf_data: Vec, } #[derive(Clone, Copy)] @@ -90,7 +87,7 @@ impl MmrBlock { OffchainKeyType::Temp => NodesUtils::node_temp_offchain_key::

( MockRuntimeApi::INDEXING_PREFIX, node, - *self.block.header.parent_hash(), + self.parent_hash(), ), OffchainKeyType::Canon => NodesUtils::node_canon_offchain_key(MockRuntimeApi::INDEXING_PREFIX, node), @@ -98,14 +95,14 @@ impl MmrBlock { } } -pub struct MockClient { - client: Mutex>, - backend: Arc, - runtime_api_params: Arc>, +pub(crate) struct MockClient { + pub(crate) client: Mutex>, + pub(crate) backend: Arc, + pub(crate) runtime_api_params: Arc>, } impl MockClient { - fn new() -> Self { + pub(crate) fn new() -> Self { let client_builder = TestClientBuilder::new().enable_offchain_indexing_api(); let (client, backend) = client_builder.build_with_backend(); MockClient { @@ -115,7 +112,7 @@ impl MockClient { } } - fn offchain_db(&self) -> OffchainDb<>::OffchainStorage> { + pub(crate) fn offchain_db(&self) -> OffchainDb<>::OffchainStorage> { OffchainDb::new(self.backend.offchain_storage().unwrap()) } @@ -125,7 +122,7 @@ impl MockClient { name: &[u8], maybe_leaf_idx: Option, ) -> MmrBlock { - let mut client = self.client.lock().unwrap(); + let mut client = self.client.lock(); let mut block_builder = client.new_block_at(at, Default::default(), false).unwrap(); // Make sure the block has a different hash than its siblings @@ -157,9 +154,9 @@ impl MockClient { } pub fn finalize_block(&self, hash: Hash, maybe_num_mmr_blocks: Option) { - let client = self.client.lock().unwrap(); + let client = self.client.lock(); if let Some(num_mmr_blocks) = maybe_num_mmr_blocks { - self.runtime_api_params.lock().unwrap().num_blocks = num_mmr_blocks; + self.runtime_api_params.lock().num_blocks = num_mmr_blocks; } client.finalize_block(hash, None).unwrap(); @@ -216,7 +213,7 @@ impl HeaderMetadata for MockClient { type Error = as HeaderMetadata>::Error; fn header_metadata(&self, hash: Hash) -> Result, Self::Error> { - self.client.lock().unwrap().header_metadata(hash) + self.client.lock().header_metadata(hash) } fn insert_header_metadata(&self, _hash: Hash, _header_metadata: CachedHeaderMetadata) { @@ -230,23 +227,23 @@ impl HeaderMetadata for MockClient { impl HeaderBackend for MockClient { fn header(&self, id: BlockId) -> sc_client_api::blockchain::Result> { - self.client.lock().unwrap().header(&id) + self.client.lock().header(&id) } fn info(&self) -> Info { - self.client.lock().unwrap().info() + self.client.lock().info() } fn status(&self, id: BlockId) -> sc_client_api::blockchain::Result { - self.client.lock().unwrap().status(id) + self.client.lock().status(id) } fn number(&self, hash: Hash) -> sc_client_api::blockchain::Result> { - self.client.lock().unwrap().number(hash) + self.client.lock().number(hash) } fn hash(&self, number: BlockNumber) -> sc_client_api::blockchain::Result> { - self.client.lock().unwrap().hash(number) + self.client.lock().hash(number) } } @@ -256,7 +253,7 @@ impl BlockchainEvents for MockClient { } fn finality_notification_stream(&self) -> FinalityNotifications { - self.client.lock().unwrap().finality_notification_stream() + self.client.lock().finality_notification_stream() } fn storage_changes_notification_stream( @@ -283,7 +280,7 @@ sp_api::mock_impl_runtime_apis! { } fn mmr_leaf_count(&self) -> Result { - Ok(self.data.lock().unwrap().num_blocks) + Ok(self.data.lock().num_blocks) } fn generate_proof( @@ -310,13 +307,38 @@ sp_api::mock_impl_runtime_apis! { } } -pub fn run_test_with_mmr_gadget(f: F) +pub(crate) fn run_test_with_mmr_gadget(post_gadget: F) where F: FnOnce(Arc) -> Fut + 'static, Fut: Future, { - let runtime = tokio::runtime::Runtime::new().unwrap(); + run_test_with_mmr_gadget_pre_post(|_| async {}, post_gadget); +} + +pub(crate) fn run_test_with_mmr_gadget_pre_post(pre_gadget: F, post_gadget: G) +where + F: FnOnce(Arc) -> RetF + 'static, + G: FnOnce(Arc) -> RetG + 'static, + RetF: Future, + RetG: Future, +{ let client = Arc::new(MockClient::new()); + run_test_with_mmr_gadget_pre_post_using_client(client, pre_gadget, post_gadget) +} + +pub(crate) fn run_test_with_mmr_gadget_pre_post_using_client( + client: Arc, + pre_gadget: F, + post_gadget: G, +) where + F: FnOnce(Arc) -> RetF + 'static, + G: FnOnce(Arc) -> RetG + 'static, + RetF: Future, + RetG: Future, +{ + let client_clone = client.clone(); + let runtime = Runtime::new().unwrap(); + runtime.block_on(async move { pre_gadget(client_clone).await }); let client_clone = client.clone(); runtime.spawn(async move { @@ -327,6 +349,6 @@ where runtime.block_on(async move { tokio::time::sleep(Duration::from_millis(200)).await; - f(client).await + post_gadget(client).await }); } From 200088e980a72978ef8011819c7f236c32542f81 Mon Sep 17 00:00:00 2001 From: Muharem Ismailov Date: Wed, 7 Dec 2022 13:20:48 +0100 Subject: [PATCH 006/127] Refund referendum submission deposit (#12788) * optinal submission deposit and migration * refund submission deposit call, test, bench * try runtime fixes * assert for bench * Only refund cancelled/approved referenda deposits * update storage version Co-authored-by: Gav --- Cargo.lock | 1 + frame/referenda/Cargo.toml | 1 + frame/referenda/src/benchmarking.rs | 13 + frame/referenda/src/lib.rs | 58 +++- frame/referenda/src/migration.rs | 232 ++++++++++++++ frame/referenda/src/tests.rs | 38 +++ frame/referenda/src/types.rs | 21 +- frame/referenda/src/weights.rs | 470 ++++++++++++++-------------- 8 files changed, 593 insertions(+), 241 deletions(-) create mode 100644 frame/referenda/src/migration.rs diff --git a/Cargo.lock b/Cargo.lock index c4f06732a9..17b66c920c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5871,6 +5871,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", + "log", "pallet-balances", "pallet-preimage", "pallet-scheduler", diff --git a/frame/referenda/Cargo.toml b/frame/referenda/Cargo.toml index a9428a408d..02894e1499 100644 --- a/frame/referenda/Cargo.toml +++ b/frame/referenda/Cargo.toml @@ -26,6 +26,7 @@ frame-system = { version = "4.0.0-dev", default-features = false, path = "../sys sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +log = { version = "0.4.17", default-features = false } [dev-dependencies] assert_matches = { version = "1.5" } diff --git a/frame/referenda/src/benchmarking.rs b/frame/referenda/src/benchmarking.rs index bc6fb31bf1..b82ac5b3bd 100644 --- a/frame/referenda/src/benchmarking.rs +++ b/frame/referenda/src/benchmarking.rs @@ -264,6 +264,19 @@ benchmarks_instance_pallet! { assert_matches!(ReferendumInfoFor::::get(index), Some(ReferendumInfo::Cancelled(_, _, None))); } + refund_submission_deposit { + let (origin, index) = create_referendum::(); + let caller = frame_system::ensure_signed(origin.clone()).unwrap(); + let balance = T::Currency::free_balance(&caller); + assert_ok!(Referenda::::cancel(T::CancelOrigin::successful_origin(), index)); + assert_matches!(ReferendumInfoFor::::get(index), Some(ReferendumInfo::Cancelled(_, Some(_), _))); + }: _(origin, index) + verify { + assert_matches!(ReferendumInfoFor::::get(index), Some(ReferendumInfo::Cancelled(_, None, _))); + let new_balance = T::Currency::free_balance(&caller); + assert!(new_balance > balance); + } + cancel { let (_origin, index) = create_referendum::(); place_deposit::(index); diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index 742ad48963..2bb01baa0c 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -85,6 +85,7 @@ use sp_runtime::{ use sp_std::{fmt::Debug, prelude::*}; mod branch; +pub mod migration; mod types; pub mod weights; @@ -140,8 +141,12 @@ pub mod pallet { use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] + #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); #[pallet::config] @@ -342,6 +347,15 @@ pub mod pallet { /// The final tally of votes in this referendum. tally: T::Tally, }, + /// The submission deposit has been refunded. + SubmissionDepositRefunded { + /// Index of the referendum. + index: ReferendumIndex, + /// The account who placed the deposit. + who: T::AccountId, + /// The amount placed by the account. + amount: BalanceOf, + }, } #[pallet::error] @@ -368,6 +382,8 @@ pub mod pallet { NoPermission, /// The deposit cannot be refunded since none was made. NoDeposit, + /// The referendum status is invalid for this operation. + BadStatus, } #[pallet::call] @@ -495,7 +511,7 @@ pub mod pallet { Self::deposit_event(Event::::Cancelled { index, tally: status.tally }); let info = ReferendumInfo::Cancelled( frame_system::Pallet::::block_number(), - status.submission_deposit, + Some(status.submission_deposit), status.decision_deposit, ); ReferendumInfoFor::::insert(index, info); @@ -579,6 +595,36 @@ pub mod pallet { }; Ok(Some(branch.weight::()).into()) } + + /// Refund the Submission Deposit for a closed referendum back to the depositor. + /// + /// - `origin`: must be `Signed` or `Root`. + /// - `index`: The index of a closed referendum whose Submission Deposit has not yet been + /// refunded. + /// + /// Emits `SubmissionDepositRefunded`. + #[pallet::weight(T::WeightInfo::refund_submission_deposit())] + pub fn refund_submission_deposit( + origin: OriginFor, + index: ReferendumIndex, + ) -> DispatchResult { + ensure_signed_or_root(origin)?; + let mut info = + ReferendumInfoFor::::get(index).ok_or(Error::::BadReferendum)?; + let deposit = info + .take_submission_deposit() + .map_err(|_| Error::::BadStatus)? + .ok_or(Error::::NoDeposit)?; + Self::refund_deposit(Some(deposit.clone())); + ReferendumInfoFor::::insert(index, info); + let e = Event::::SubmissionDepositRefunded { + index, + who: deposit.who, + amount: deposit.amount, + }; + Self::deposit_event(e); + Ok(()) + } } } @@ -671,9 +717,9 @@ impl, I: 'static> Polling for Pallet { Self::note_one_fewer_deciding(status.track); let now = frame_system::Pallet::::block_number(); let info = if approved { - ReferendumInfo::Approved(now, status.submission_deposit, status.decision_deposit) + ReferendumInfo::Approved(now, Some(status.submission_deposit), status.decision_deposit) } else { - ReferendumInfo::Rejected(now, status.submission_deposit, status.decision_deposit) + ReferendumInfo::Rejected(now, Some(status.submission_deposit), status.decision_deposit) }; ReferendumInfoFor::::insert(index, info); Ok(()) @@ -995,7 +1041,7 @@ impl, I: 'static> Pallet { return ( ReferendumInfo::TimedOut( now, - status.submission_deposit, + Some(status.submission_deposit), status.decision_deposit, ), true, @@ -1027,7 +1073,7 @@ impl, I: 'static> Pallet { return ( ReferendumInfo::Approved( now, - status.submission_deposit, + Some(status.submission_deposit), status.decision_deposit, ), true, @@ -1052,7 +1098,7 @@ impl, I: 'static> Pallet { return ( ReferendumInfo::Rejected( now, - status.submission_deposit, + Some(status.submission_deposit), status.decision_deposit, ), true, diff --git a/frame/referenda/src/migration.rs b/frame/referenda/src/migration.rs new file mode 100644 index 0000000000..e495090c13 --- /dev/null +++ b/frame/referenda/src/migration.rs @@ -0,0 +1,232 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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. + +//! Storage migrations for the referenda pallet. + +use super::*; +use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; +use frame_support::{pallet_prelude::*, storage_alias, traits::OnRuntimeUpgrade}; +use log; + +/// Initial version of storage types. +pub mod v0 { + use super::*; + // ReferendumStatus and its dependency types referenced from the latest version while staying + // unchanged. [`super::test::referendum_status_v0()`] checks its immutability between v0 and + // latest version. + #[cfg(test)] + pub(super) use super::{ReferendumStatus, ReferendumStatusOf}; + + pub type ReferendumInfoOf = ReferendumInfo< + TrackIdOf, + PalletsOriginOf, + ::BlockNumber, + BoundedCallOf, + BalanceOf, + TallyOf, + ::AccountId, + ScheduleAddressOf, + >; + + /// Info regarding a referendum, present or past. + #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] + pub enum ReferendumInfo< + TrackId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + RuntimeOrigin: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + Moment: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone + EncodeLike, + Call: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + Balance: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + Tally: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + AccountId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + ScheduleAddress: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + > { + /// Referendum has been submitted and is being voted on. + Ongoing( + ReferendumStatus< + TrackId, + RuntimeOrigin, + Moment, + Call, + Balance, + Tally, + AccountId, + ScheduleAddress, + >, + ), + /// Referendum finished with approval. Submission deposit is held. + Approved(Moment, Deposit, Option>), + /// Referendum finished with rejection. Submission deposit is held. + Rejected(Moment, Deposit, Option>), + /// Referendum finished with cancellation. Submission deposit is held. + Cancelled(Moment, Deposit, Option>), + /// Referendum finished and was never decided. Submission deposit is held. + TimedOut(Moment, Deposit, Option>), + /// Referendum finished with a kill. + Killed(Moment), + } + + #[storage_alias] + pub type ReferendumInfoFor, I: 'static> = + StorageMap, Blake2_128Concat, ReferendumIndex, ReferendumInfoOf>; +} + +pub mod v1 { + use super::*; + + /// The log target. + const TARGET: &'static str = "runtime::democracy::migration::v1"; + + /// Transforms a submission deposit of ReferendumInfo(Approved|Rejected|Cancelled|TimedOut) to + /// optional value, making it refundable. + pub struct MigrateV0ToV1(PhantomData<(T, I)>); + impl, I: 'static> OnRuntimeUpgrade for MigrateV0ToV1 { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, &'static str> { + let onchain_version = Pallet::::on_chain_storage_version(); + assert_eq!(onchain_version, 0, "migration from version 0 to 1."); + let referendum_count = v0::ReferendumInfoFor::::iter().count(); + log::info!( + target: TARGET, + "pre-upgrade state contains '{}' referendums.", + referendum_count + ); + Ok((referendum_count as u32).encode()) + } + + fn on_runtime_upgrade() -> Weight { + let current_version = Pallet::::current_storage_version(); + let onchain_version = Pallet::::on_chain_storage_version(); + let mut weight = T::DbWeight::get().reads(1); + log::info!( + target: TARGET, + "running migration with current storage version {:?} / onchain {:?}.", + current_version, + onchain_version + ); + if onchain_version != 0 { + log::warn!(target: TARGET, "skipping migration from v0 to v1."); + return weight + } + v0::ReferendumInfoFor::::iter().for_each(|(key, value)| { + let maybe_new_value = match value { + v0::ReferendumInfo::Ongoing(_) | v0::ReferendumInfo::Killed(_) => None, + v0::ReferendumInfo::Approved(e, s, d) => + Some(ReferendumInfo::Approved(e, Some(s), d)), + v0::ReferendumInfo::Rejected(e, s, d) => + Some(ReferendumInfo::Rejected(e, Some(s), d)), + v0::ReferendumInfo::Cancelled(e, s, d) => + Some(ReferendumInfo::Cancelled(e, Some(s), d)), + v0::ReferendumInfo::TimedOut(e, s, d) => + Some(ReferendumInfo::TimedOut(e, Some(s), d)), + }; + if let Some(new_value) = maybe_new_value { + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + log::info!(target: TARGET, "migrating referendum #{:?}", &key); + ReferendumInfoFor::::insert(key, new_value); + } else { + weight.saturating_accrue(T::DbWeight::get().reads(1)); + } + }); + StorageVersion::new(1).put::>(); + weight.saturating_accrue(T::DbWeight::get().writes(1)); + weight + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(state: Vec) -> Result<(), &'static str> { + let onchain_version = Pallet::::on_chain_storage_version(); + assert_eq!(onchain_version, 1, "must upgrade from version 0 to 1."); + let pre_referendum_count: u32 = Decode::decode(&mut &state[..]) + .expect("failed to decode the state from pre-upgrade."); + let post_referendum_count = ReferendumInfoFor::::iter().count() as u32; + assert_eq!( + post_referendum_count, pre_referendum_count, + "must migrate all referendums." + ); + log::info!(target: TARGET, "migrated all referendums."); + Ok(()) + } + } +} + +#[cfg(test)] +pub mod test { + use super::*; + use crate::mock::{Test as T, *}; + use core::str::FromStr; + + // create referendum status v0. + fn create_status_v0() -> v0::ReferendumStatusOf { + let origin: OriginCaller = frame_system::RawOrigin::Root.into(); + let track = >::Tracks::track_for(&origin).unwrap(); + v0::ReferendumStatusOf:: { + track, + in_queue: true, + origin, + proposal: set_balance_proposal_bounded(1), + enactment: DispatchTime::At(1), + tally: TallyOf::::new(track), + submission_deposit: Deposit { who: 1, amount: 10 }, + submitted: 1, + decision_deposit: None, + alarm: None, + deciding: None, + } + } + + #[test] + pub fn referendum_status_v0() { + // make sure the bytes of the encoded referendum v0 is decodable. + let ongoing_encoded = sp_core::Bytes::from_str("0x00000000013001012a000000000000000400000100000000000000010000000000000001000000000000000a00000000000000000000000000000000000100").unwrap(); + let ongoing_dec = v0::ReferendumInfoOf::::decode(&mut &*ongoing_encoded).unwrap(); + let ongoing = v0::ReferendumInfoOf::::Ongoing(create_status_v0()); + assert_eq!(ongoing, ongoing_dec); + } + + #[test] + fn migration_v0_to_v1_works() { + new_test_ext().execute_with(|| { + // create and insert into the storage an ongoing referendum v0. + let status_v0 = create_status_v0(); + let ongoing_v0 = v0::ReferendumInfoOf::::Ongoing(status_v0.clone()); + v0::ReferendumInfoFor::::insert(2, ongoing_v0); + // create and insert into the storage an approved referendum v0. + let approved_v0 = v0::ReferendumInfoOf::::Approved( + 123, + Deposit { who: 1, amount: 10 }, + Some(Deposit { who: 2, amount: 20 }), + ); + v0::ReferendumInfoFor::::insert(5, approved_v0); + // run migration from v0 to v1. + v1::MigrateV0ToV1::::on_runtime_upgrade(); + // fetch and assert migrated into v1 the ongoing referendum. + let ongoing_v1 = ReferendumInfoFor::::get(2).unwrap(); + // referendum status schema is the same for v0 and v1. + assert_eq!(ReferendumInfoOf::::Ongoing(status_v0), ongoing_v1); + // fetch and assert migrated into v1 the approved referendum. + let approved_v1 = ReferendumInfoFor::::get(5).unwrap(); + assert_eq!( + approved_v1, + ReferendumInfoOf::::Approved( + 123, + Some(Deposit { who: 1, amount: 10 }), + Some(Deposit { who: 2, amount: 20 }) + ) + ); + }); + } +} diff --git a/frame/referenda/src/tests.rs b/frame/referenda/src/tests.rs index db67825210..c109fafe33 100644 --- a/frame/referenda/src/tests.rs +++ b/frame/referenda/src/tests.rs @@ -443,6 +443,44 @@ fn refund_deposit_works() { }); } +#[test] +fn refund_submission_deposit_works() { + new_test_ext().execute_with(|| { + // refund of non existing referendum fails. + let e = Error::::BadReferendum; + assert_noop!(Referenda::refund_submission_deposit(RuntimeOrigin::signed(1), 0), e); + // create a referendum. + let h = set_balance_proposal_bounded(1); + assert_ok!(Referenda::submit( + RuntimeOrigin::signed(1), + Box::new(RawOrigin::Root.into()), + h.clone(), + DispatchTime::At(10), + )); + // refund of an ongoing referendum fails. + let e = Error::::BadStatus; + assert_noop!(Referenda::refund_submission_deposit(RuntimeOrigin::signed(3), 0), e); + // cancel referendum. + assert_ok!(Referenda::cancel(RuntimeOrigin::signed(4), 0)); + // refund of canceled referendum works. + assert_ok!(Referenda::refund_submission_deposit(RuntimeOrigin::signed(3), 0)); + // fails if already refunded. + let e = Error::::NoDeposit; + assert_noop!(Referenda::refund_submission_deposit(RuntimeOrigin::signed(2), 0), e); + // create second referendum. + assert_ok!(Referenda::submit( + RuntimeOrigin::signed(1), + Box::new(RawOrigin::Root.into()), + h, + DispatchTime::At(10), + )); + // refund of a killed referendum fails. + assert_ok!(Referenda::kill(RuntimeOrigin::root(), 1)); + let e = Error::::NoDeposit; + assert_noop!(Referenda::refund_submission_deposit(RuntimeOrigin::signed(2), 0), e); + }); +} + #[test] fn cancel_works() { new_test_ext().execute_with(|| { diff --git a/frame/referenda/src/types.rs b/frame/referenda/src/types.rs index 3d5fa753f5..0763a71a95 100644 --- a/frame/referenda/src/types.rs +++ b/frame/referenda/src/types.rs @@ -221,13 +221,13 @@ pub enum ReferendumInfo< >, ), /// Referendum finished with approval. Submission deposit is held. - Approved(Moment, Deposit, Option>), + Approved(Moment, Option>, Option>), /// Referendum finished with rejection. Submission deposit is held. - Rejected(Moment, Deposit, Option>), + Rejected(Moment, Option>, Option>), /// Referendum finished with cancellation. Submission deposit is held. - Cancelled(Moment, Deposit, Option>), + Cancelled(Moment, Option>, Option>), /// Referendum finished and was never decided. Submission deposit is held. - TimedOut(Moment, Deposit, Option>), + TimedOut(Moment, Option>, Option>), /// Referendum finished with a kill. Killed(Moment), } @@ -256,6 +256,19 @@ impl< Killed(_) => Ok(None), } } + + /// Take the Submission Deposit from `self`, if there is one and it's in a valid state to be + /// taken. Returns an `Err` if `self` is not in a valid state for the Submission Deposit to be + /// refunded. + pub fn take_submission_deposit(&mut self) -> Result>, ()> { + use ReferendumInfo::*; + match self { + // Can only refund deposit if it's appoved or cancelled. + Approved(_, s, _) | Cancelled(_, s, _) => Ok(s.take()), + // Cannot refund deposit if Ongoing as this breaks assumptions. + Ongoing(..) | Rejected(..) | TimedOut(..) | Killed(..) => Err(()), + } + } } /// Type for describing a curve over the 2-dimensional space of axes between 0-1, as represented diff --git a/frame/referenda/src/weights.rs b/frame/referenda/src/weights.rs index d8609abb9f..f0eae517af 100644 --- a/frame/referenda/src/weights.rs +++ b/frame/referenda/src/weights.rs @@ -1,8 +1,3 @@ -// This file is part of Substrate. - -// Copyright (C) 2022 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 @@ -18,24 +13,22 @@ //! Autogenerated weights for pallet_referenda //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 +//! DATE: 2022-11-27, STEPS: `20`, REPEAT: 1, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `cob`, CPU: `` +//! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// ./target/production/substrate +// ./target/release/substrate // benchmark // pallet // --chain=dev -// --steps=50 -// --repeat=20 -// --pallet=pallet_referenda +// --steps=20 +// --repeat=1 +// --pallet=pallet-referenda // --extrinsic=* -// --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 -// --output=./frame/referenda/src/weights.rs -// --header=./HEADER-APACHE2 +// --output=./frame/referenda/src/._weights.rs // --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] @@ -54,6 +47,7 @@ pub trait WeightInfo { fn place_decision_deposit_passing() -> Weight; fn place_decision_deposit_failing() -> Weight; fn refund_decision_deposit() -> Weight; + fn refund_submission_deposit() -> Weight; fn cancel() -> Weight; fn kill() -> Weight; fn one_fewer_deciding_queue_empty() -> Weight; @@ -83,231 +77,238 @@ impl WeightInfo for SubstrateWeight { // Storage: Scheduler Agenda (r:1 w:1) // Storage: Referenda ReferendumInfoFor (r:0 w:1) fn submit() -> Weight { - // Minimum execution time: 41_475 nanoseconds. - Weight::from_ref_time(42_153_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 29_000 nanoseconds. + Weight::from_ref_time(29_000_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(3)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn place_decision_deposit_preparing() -> Weight { - // Minimum execution time: 52_291 nanoseconds. - Weight::from_ref_time(53_147_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 35_000 nanoseconds. + Weight::from_ref_time(35_000_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Referenda DecidingCount (r:1 w:0) // Storage: Referenda TrackQueue (r:1 w:1) fn place_decision_deposit_queued() -> Weight { - // Minimum execution time: 57_322 nanoseconds. - Weight::from_ref_time(58_145_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Minimum execution time: 40_000 nanoseconds. + Weight::from_ref_time(40_000_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Referenda DecidingCount (r:1 w:0) // Storage: Referenda TrackQueue (r:1 w:1) fn place_decision_deposit_not_queued() -> Weight { - // Minimum execution time: 57_170 nanoseconds. - Weight::from_ref_time(58_012_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Minimum execution time: 39_000 nanoseconds. + Weight::from_ref_time(39_000_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Referenda DecidingCount (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn place_decision_deposit_passing() -> Weight { - // Minimum execution time: 67_805 nanoseconds. - Weight::from_ref_time(68_844_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Minimum execution time: 43_000 nanoseconds. + Weight::from_ref_time(43_000_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(4)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Referenda DecidingCount (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn place_decision_deposit_failing() -> Weight { - // Minimum execution time: 63_408 nanoseconds. - Weight::from_ref_time(64_049_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Minimum execution time: 84_000 nanoseconds. + Weight::from_ref_time(84_000_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(4)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) fn refund_decision_deposit() -> Weight { - // Minimum execution time: 36_639 nanoseconds. - Weight::from_ref_time(37_329_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 25_000 nanoseconds. + Weight::from_ref_time(25_000_000) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + fn refund_submission_deposit() -> Weight { + // Minimum execution time: 25_000 nanoseconds. + Weight::from_ref_time(25_000_000) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn cancel() -> Weight { - // Minimum execution time: 42_442 nanoseconds. - Weight::from_ref_time(43_006_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 26_000 nanoseconds. + Weight::from_ref_time(26_000_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn kill() -> Weight { - // Minimum execution time: 74_681 nanoseconds. - Weight::from_ref_time(75_567_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 47_000 nanoseconds. + Weight::from_ref_time(47_000_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) } // Storage: Referenda TrackQueue (r:1 w:0) // Storage: Referenda DecidingCount (r:1 w:1) fn one_fewer_deciding_queue_empty() -> Weight { - // Minimum execution time: 14_262 nanoseconds. - Weight::from_ref_time(14_504_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 8_000 nanoseconds. + Weight::from_ref_time(8_000_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Referenda TrackQueue (r:1 w:1) // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn one_fewer_deciding_failing() -> Weight { - // Minimum execution time: 88_618 nanoseconds. - Weight::from_ref_time(89_443_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Minimum execution time: 88_000 nanoseconds. + Weight::from_ref_time(88_000_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(4)) } // Storage: Referenda TrackQueue (r:1 w:1) // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn one_fewer_deciding_passing() -> Weight { - // Minimum execution time: 89_784 nanoseconds. - Weight::from_ref_time(90_619_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Minimum execution time: 75_000 nanoseconds. + Weight::from_ref_time(75_000_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(4)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Referenda TrackQueue (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_requeued_insertion() -> Weight { - // Minimum execution time: 73_179 nanoseconds. - Weight::from_ref_time(74_025_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 72_000 nanoseconds. + Weight::from_ref_time(72_000_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Referenda TrackQueue (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_requeued_slide() -> Weight { - // Minimum execution time: 73_168 nanoseconds. - Weight::from_ref_time(73_769_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 56_000 nanoseconds. + Weight::from_ref_time(56_000_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Referenda DecidingCount (r:1 w:0) // Storage: Referenda TrackQueue (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_queued() -> Weight { - // Minimum execution time: 75_027 nanoseconds. - Weight::from_ref_time(76_220_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 55_000 nanoseconds. + Weight::from_ref_time(55_000_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(3)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Referenda DecidingCount (r:1 w:0) // Storage: Referenda TrackQueue (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_not_queued() -> Weight { - // Minimum execution time: 74_815 nanoseconds. - Weight::from_ref_time(75_803_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 60_000 nanoseconds. + Weight::from_ref_time(60_000_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(3)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_no_deposit() -> Weight { - // Minimum execution time: 31_877 nanoseconds. - Weight::from_ref_time(32_236_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Minimum execution time: 22_000 nanoseconds. + Weight::from_ref_time(22_000_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_preparing() -> Weight { - // Minimum execution time: 33_322 nanoseconds. - Weight::from_ref_time(33_762_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Minimum execution time: 21_000 nanoseconds. + Weight::from_ref_time(21_000_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) fn nudge_referendum_timed_out() -> Weight { - // Minimum execution time: 25_393 nanoseconds. - Weight::from_ref_time(25_913_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 17_000 nanoseconds. + Weight::from_ref_time(17_000_000) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Referenda DecidingCount (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_begin_deciding_failing() -> Weight { - // Minimum execution time: 47_114 nanoseconds. - Weight::from_ref_time(47_586_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 29_000 nanoseconds. + Weight::from_ref_time(29_000_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Referenda DecidingCount (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_begin_deciding_passing() -> Weight { - // Minimum execution time: 48_443 nanoseconds. - Weight::from_ref_time(50_003_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 39_000 nanoseconds. + Weight::from_ref_time(39_000_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_begin_confirming() -> Weight { - // Minimum execution time: 44_556 nanoseconds. - Weight::from_ref_time(45_167_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Minimum execution time: 31_000 nanoseconds. + Weight::from_ref_time(31_000_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_end_confirming() -> Weight { - // Minimum execution time: 45_474 nanoseconds. - Weight::from_ref_time(46_105_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Minimum execution time: 30_000 nanoseconds. + Weight::from_ref_time(30_000_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_continue_not_confirming() -> Weight { - // Minimum execution time: 42_795 nanoseconds. - Weight::from_ref_time(43_123_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Minimum execution time: 28_000 nanoseconds. + Weight::from_ref_time(28_000_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_continue_confirming() -> Weight { - // Minimum execution time: 41_928 nanoseconds. - Weight::from_ref_time(42_272_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Minimum execution time: 30_000 nanoseconds. + Weight::from_ref_time(30_000_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) // Storage: Scheduler Lookup (r:1 w:1) fn nudge_referendum_approved() -> Weight { - // Minimum execution time: 55_186 nanoseconds. - Weight::from_ref_time(55_714_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Minimum execution time: 45_000 nanoseconds. + Weight::from_ref_time(45_000_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(4)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_rejected() -> Weight { - // Minimum execution time: 44_892 nanoseconds. - Weight::from_ref_time(45_353_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Minimum execution time: 30_000 nanoseconds. + Weight::from_ref_time(30_000_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) } } @@ -317,230 +318,237 @@ impl WeightInfo for () { // Storage: Scheduler Agenda (r:1 w:1) // Storage: Referenda ReferendumInfoFor (r:0 w:1) fn submit() -> Weight { - // Minimum execution time: 41_475 nanoseconds. - Weight::from_ref_time(42_153_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 29_000 nanoseconds. + Weight::from_ref_time(29_000_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(3)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn place_decision_deposit_preparing() -> Weight { - // Minimum execution time: 52_291 nanoseconds. - Weight::from_ref_time(53_147_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 35_000 nanoseconds. + Weight::from_ref_time(35_000_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(3)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Referenda DecidingCount (r:1 w:0) // Storage: Referenda TrackQueue (r:1 w:1) fn place_decision_deposit_queued() -> Weight { - // Minimum execution time: 57_322 nanoseconds. - Weight::from_ref_time(58_145_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Minimum execution time: 40_000 nanoseconds. + Weight::from_ref_time(40_000_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(2)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Referenda DecidingCount (r:1 w:0) // Storage: Referenda TrackQueue (r:1 w:1) fn place_decision_deposit_not_queued() -> Weight { - // Minimum execution time: 57_170 nanoseconds. - Weight::from_ref_time(58_012_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Minimum execution time: 39_000 nanoseconds. + Weight::from_ref_time(39_000_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(2)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Referenda DecidingCount (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn place_decision_deposit_passing() -> Weight { - // Minimum execution time: 67_805 nanoseconds. - Weight::from_ref_time(68_844_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // Minimum execution time: 43_000 nanoseconds. + Weight::from_ref_time(43_000_000) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().writes(4)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Referenda DecidingCount (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn place_decision_deposit_failing() -> Weight { - // Minimum execution time: 63_408 nanoseconds. - Weight::from_ref_time(64_049_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // Minimum execution time: 84_000 nanoseconds. + Weight::from_ref_time(84_000_000) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().writes(4)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) fn refund_decision_deposit() -> Weight { - // Minimum execution time: 36_639 nanoseconds. - Weight::from_ref_time(37_329_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 25_000 nanoseconds. + Weight::from_ref_time(25_000_000) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: Referenda ReferendumInfoFor (r:1 w:1) + fn refund_submission_deposit() -> Weight { + // Minimum execution time: 25_000 nanoseconds. + Weight::from_ref_time(25_000_000) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn cancel() -> Weight { - // Minimum execution time: 42_442 nanoseconds. - Weight::from_ref_time(43_006_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 26_000 nanoseconds. + Weight::from_ref_time(26_000_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(3)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn kill() -> Weight { - // Minimum execution time: 74_681 nanoseconds. - Weight::from_ref_time(75_567_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 47_000 nanoseconds. + Weight::from_ref_time(47_000_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(3)) } // Storage: Referenda TrackQueue (r:1 w:0) // Storage: Referenda DecidingCount (r:1 w:1) fn one_fewer_deciding_queue_empty() -> Weight { - // Minimum execution time: 14_262 nanoseconds. - Weight::from_ref_time(14_504_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 8_000 nanoseconds. + Weight::from_ref_time(8_000_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Referenda TrackQueue (r:1 w:1) // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn one_fewer_deciding_failing() -> Weight { - // Minimum execution time: 88_618 nanoseconds. - Weight::from_ref_time(89_443_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // Minimum execution time: 88_000 nanoseconds. + Weight::from_ref_time(88_000_000) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().writes(4)) } // Storage: Referenda TrackQueue (r:1 w:1) // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn one_fewer_deciding_passing() -> Weight { - // Minimum execution time: 89_784 nanoseconds. - Weight::from_ref_time(90_619_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // Minimum execution time: 75_000 nanoseconds. + Weight::from_ref_time(75_000_000) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().writes(4)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Referenda TrackQueue (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_requeued_insertion() -> Weight { - // Minimum execution time: 73_179 nanoseconds. - Weight::from_ref_time(74_025_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 72_000 nanoseconds. + Weight::from_ref_time(72_000_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(3)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Referenda TrackQueue (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_requeued_slide() -> Weight { - // Minimum execution time: 73_168 nanoseconds. - Weight::from_ref_time(73_769_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 56_000 nanoseconds. + Weight::from_ref_time(56_000_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(3)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Referenda DecidingCount (r:1 w:0) // Storage: Referenda TrackQueue (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_queued() -> Weight { - // Minimum execution time: 75_027 nanoseconds. - Weight::from_ref_time(76_220_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 55_000 nanoseconds. + Weight::from_ref_time(55_000_000) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().writes(3)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Referenda DecidingCount (r:1 w:0) // Storage: Referenda TrackQueue (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_not_queued() -> Weight { - // Minimum execution time: 74_815 nanoseconds. - Weight::from_ref_time(75_803_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 60_000 nanoseconds. + Weight::from_ref_time(60_000_000) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().writes(3)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_no_deposit() -> Weight { - // Minimum execution time: 31_877 nanoseconds. - Weight::from_ref_time(32_236_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Minimum execution time: 22_000 nanoseconds. + Weight::from_ref_time(22_000_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_preparing() -> Weight { - // Minimum execution time: 33_322 nanoseconds. - Weight::from_ref_time(33_762_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Minimum execution time: 21_000 nanoseconds. + Weight::from_ref_time(21_000_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) fn nudge_referendum_timed_out() -> Weight { - // Minimum execution time: 25_393 nanoseconds. - Weight::from_ref_time(25_913_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 17_000 nanoseconds. + Weight::from_ref_time(17_000_000) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Referenda DecidingCount (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_begin_deciding_failing() -> Weight { - // Minimum execution time: 47_114 nanoseconds. - Weight::from_ref_time(47_586_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 29_000 nanoseconds. + Weight::from_ref_time(29_000_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(3)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Referenda DecidingCount (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_begin_deciding_passing() -> Weight { - // Minimum execution time: 48_443 nanoseconds. - Weight::from_ref_time(50_003_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 39_000 nanoseconds. + Weight::from_ref_time(39_000_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(3)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_begin_confirming() -> Weight { - // Minimum execution time: 44_556 nanoseconds. - Weight::from_ref_time(45_167_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Minimum execution time: 31_000 nanoseconds. + Weight::from_ref_time(31_000_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_end_confirming() -> Weight { - // Minimum execution time: 45_474 nanoseconds. - Weight::from_ref_time(46_105_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Minimum execution time: 30_000 nanoseconds. + Weight::from_ref_time(30_000_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_continue_not_confirming() -> Weight { - // Minimum execution time: 42_795 nanoseconds. - Weight::from_ref_time(43_123_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Minimum execution time: 28_000 nanoseconds. + Weight::from_ref_time(28_000_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_continue_confirming() -> Weight { - // Minimum execution time: 41_928 nanoseconds. - Weight::from_ref_time(42_272_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Minimum execution time: 30_000 nanoseconds. + Weight::from_ref_time(30_000_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) // Storage: Scheduler Lookup (r:1 w:1) fn nudge_referendum_approved() -> Weight { - // Minimum execution time: 55_186 nanoseconds. - Weight::from_ref_time(55_714_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // Minimum execution time: 45_000 nanoseconds. + Weight::from_ref_time(45_000_000) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().writes(4)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_rejected() -> Weight { - // Minimum execution time: 44_892 nanoseconds. - Weight::from_ref_time(45_353_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Minimum execution time: 30_000 nanoseconds. + Weight::from_ref_time(30_000_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) } } From 5722ece5efb61cc82307f3e919a60bd4e807f1df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Wed, 7 Dec 2022 13:48:30 +0100 Subject: [PATCH 007/127] Remove sandboxing host function interface (#12852) * Remove sandboxing interface * Remove unused struct --- .gitlab-ci.yml | 20 - Cargo.lock | 634 +----------------- Cargo.toml | 1 - bin/node/runtime/Cargo.toml | 7 - client/executor/Cargo.toml | 3 - client/executor/common/Cargo.toml | 7 - client/executor/common/src/error.rs | 3 - client/executor/common/src/lib.rs | 1 - client/executor/common/src/sandbox.rs | 585 ---------------- .../common/src/sandbox/wasmer_backend.rs | 449 ------------- .../common/src/sandbox/wasmi_backend.rs | 339 ---------- client/executor/runtime-test/Cargo.toml | 3 - client/executor/runtime-test/src/lib.rs | 159 ----- client/executor/src/integration_tests/mod.rs | 106 --- .../executor/src/integration_tests/sandbox.rs | 339 ---------- client/executor/src/lib.rs | 2 +- client/executor/wasmi/Cargo.toml | 2 - client/executor/wasmi/src/lib.rs | 232 +------ client/executor/wasmtime/Cargo.toml | 4 +- client/executor/wasmtime/src/host.rs | 277 +------- client/executor/wasmtime/src/runtime.rs | 10 - client/executor/wasmtime/src/util.rs | 18 - primitives/io/src/lib.rs | 94 --- primitives/sandbox/Cargo.toml | 40 -- primitives/sandbox/README.md | 21 - primitives/sandbox/src/embedded_executor.rs | 478 ------------- primitives/sandbox/src/env.rs | 120 ---- primitives/sandbox/src/host_executor.rs | 274 -------- primitives/sandbox/src/lib.rs | 190 ------ primitives/wasm-interface/src/lib.rs | 57 -- scripts/ci/gitlab/pipeline/test.yml | 37 +- 31 files changed, 34 insertions(+), 4478 deletions(-) delete mode 100644 client/executor/common/src/sandbox.rs delete mode 100644 client/executor/common/src/sandbox/wasmer_backend.rs delete mode 100644 client/executor/common/src/sandbox/wasmi_backend.rs delete mode 100644 client/executor/src/integration_tests/sandbox.rs delete mode 100644 primitives/sandbox/Cargo.toml delete mode 100644 primitives/sandbox/README.md delete mode 100644 primitives/sandbox/src/embedded_executor.rs delete mode 100644 primitives/sandbox/src/env.rs delete mode 100644 primitives/sandbox/src/host_executor.rs delete mode 100644 primitives/sandbox/src/lib.rs diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 992a2d491a..5b67481554 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -166,26 +166,6 @@ default: - if: $CI_PIPELINE_SOURCE == "schedule" - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs -.test-refs-wasmer-sandbox: - rules: - - if: $CI_PIPELINE_SOURCE == "web" - - if: $CI_PIPELINE_SOURCE == "schedule" - - if: $CI_COMMIT_REF_NAME == "master" - changes: - - client/executor/**/* - - frame/contracts/**/* - - primitives/sandbox/**/* - - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs - changes: - - client/executor/**/* - - frame/contracts/**/* - - primitives/sandbox/**/* - - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 - changes: - - client/executor/**/* - - frame/contracts/**/* - - primitives/sandbox/**/* - .build-refs: rules: - if: $CI_PIPELINE_SOURCE == "pipeline" diff --git a/Cargo.lock b/Cargo.lock index 17b66c920c..f2b394e099 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,7 +18,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" dependencies = [ - "gimli 0.26.1", + "gimli", ] [[package]] @@ -648,27 +648,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" -[[package]] -name = "bytecheck" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "314889ea31cda264cb7c3d6e6e5c9415a987ecb0e72c17c00d36fbb881d34abe" -dependencies = [ - "bytecheck_derive", - "ptr_meta", -] - -[[package]] -name = "bytecheck_derive" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a2b3b92c135dae665a6f760205b89187638e83bed17ef3e44e83c712cf30600" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "byteorder" version = "1.4.3" @@ -1020,39 +999,13 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" -[[package]] -name = "cranelift-bforest" -version = "0.76.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e6bea67967505247f54fa2c85cf4f6e0e31c4e5692c9b70e4ae58e339067333" -dependencies = [ - "cranelift-entity 0.76.0", -] - [[package]] name = "cranelift-bforest" version = "0.88.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b27bbd3e6c422cf6282b047bcdd51ecd9ca9f3497a3be0132ffa08e509b824b0" dependencies = [ - "cranelift-entity 0.88.0", -] - -[[package]] -name = "cranelift-codegen" -version = "0.76.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48194035d2752bdd5bdae429e3ab88676e95f52a2b1355a5d4e809f9e39b1d74" -dependencies = [ - "cranelift-bforest 0.76.0", - "cranelift-codegen-meta 0.76.0", - "cranelift-codegen-shared 0.76.0", - "cranelift-entity 0.76.0", - "gimli 0.25.0", - "log", - "regalloc", - "smallvec", - "target-lexicon", + "cranelift-entity", ] [[package]] @@ -1063,55 +1016,33 @@ checksum = "872f5d4557a411b087bd731df6347c142ae1004e6467a144a7e33662e5715a01" dependencies = [ "arrayvec 0.7.2", "bumpalo", - "cranelift-bforest 0.88.0", - "cranelift-codegen-meta 0.88.0", - "cranelift-codegen-shared 0.88.0", - "cranelift-entity 0.88.0", + "cranelift-bforest", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-entity", "cranelift-isle", - "gimli 0.26.1", + "gimli", "log", "regalloc2", "smallvec", "target-lexicon", ] -[[package]] -name = "cranelift-codegen-meta" -version = "0.76.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976efb22fcab4f2cd6bd4e9913764616a54d895c1a23530128d04e03633c555f" -dependencies = [ - "cranelift-codegen-shared 0.76.0", - "cranelift-entity 0.76.0", -] - [[package]] name = "cranelift-codegen-meta" version = "0.88.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21b49fdebb29c62c1fc4da1eeebd609e9d530ecde24a9876def546275f73a244" dependencies = [ - "cranelift-codegen-shared 0.88.0", + "cranelift-codegen-shared", ] -[[package]] -name = "cranelift-codegen-shared" -version = "0.76.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dabb5fe66e04d4652e434195b45ae65b5c8172d520247b8f66d8df42b2b45dc" - [[package]] name = "cranelift-codegen-shared" version = "0.88.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fc0c091e2db055d4d7f6b7cec2d2ead286bcfaea3357c6a52c2a2613a8cb5ac" -[[package]] -name = "cranelift-entity" -version = "0.76.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3329733e4d4b8e91c809efcaa4faee80bf66f20164e3dd16d707346bd3494799" - [[package]] name = "cranelift-entity" version = "0.88.0" @@ -1121,25 +1052,13 @@ dependencies = [ "serde", ] -[[package]] -name = "cranelift-frontend" -version = "0.76.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279afcc0d3e651b773f94837c3d581177b348c8d69e928104b2e9fccb226f921" -dependencies = [ - "cranelift-codegen 0.76.0", - "log", - "smallvec", - "target-lexicon", -] - [[package]] name = "cranelift-frontend" version = "0.88.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cd8dd3fb8b82c772f4172e87ae1677b971676fffa7c4e3398e3047e650a266b" dependencies = [ - "cranelift-codegen 0.88.0", + "cranelift-codegen", "log", "smallvec", "target-lexicon", @@ -1157,7 +1076,7 @@ version = "0.88.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c30ba8b910f1be023af0c39109cb28a8809734942a6b3eecbf2de8993052ea5e" dependencies = [ - "cranelift-codegen 0.88.0", + "cranelift-codegen", "libc", "target-lexicon", ] @@ -1168,13 +1087,13 @@ version = "0.88.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "776a8916d201894aca9637a20814f1e11abc62acd5cfbe0b4eb2e63922756971" dependencies = [ - "cranelift-codegen 0.88.0", - "cranelift-entity 0.88.0", - "cranelift-frontend 0.88.0", + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", "itertools", "log", "smallvec", - "wasmparser 0.89.1", + "wasmparser", "wasmtime-types", ] @@ -1441,41 +1360,6 @@ dependencies = [ "syn", ] -[[package]] -name = "darling" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "757c0ded2af11d8e739c4daea1ac623dd1624b06c844cf3f5a39f1bdbd99bb12" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c34d8efb62d0c2d7f60ece80f75e5c63c1588ba68032740494b0b9a996466e3" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn", -] - -[[package]] -name = "darling_macro" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade7bff147130fe5e6d39f089c6bd49ec0250f35d70b2eebf72afdfc919f15cc" -dependencies = [ - "darling_core", - "quote", - "syn", -] - [[package]] name = "data-encoding" version = "2.3.2" @@ -1672,32 +1556,6 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21e50f3adc76d6a43f5ed73b698a87d0760ca74617f60f7c3b879003536fdd28" -[[package]] -name = "dynasm" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add9a102807b524ec050363f09e06f1504214b0e1c7797f64261c891022dce8b" -dependencies = [ - "bitflags", - "byteorder", - "lazy_static", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "dynasmrt" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64fba5a42bd76a17cad4bfa00de168ee1cbfa06a5e8ce992ae880218c05641a9" -dependencies = [ - "byteorder", - "dynasm", - "memmap2", -] - [[package]] name = "ecdsa" version = "0.14.7" @@ -1784,26 +1642,6 @@ dependencies = [ "syn", ] -[[package]] -name = "enum-iterator" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eeac5c5edb79e4e39fe8439ef35207780a11f69c52cbe424ce3dfad4cb78de6" -dependencies = [ - "enum-iterator-derive", -] - -[[package]] -name = "enum-iterator-derive" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "enumflags2" version = "0.7.4" @@ -1824,27 +1662,6 @@ dependencies = [ "syn", ] -[[package]] -name = "enumset" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e76129da36102af021b8e5000dab2c1c30dbef85c1e482beeff8da5dde0e0b0" -dependencies = [ - "enumset_derive", -] - -[[package]] -name = "enumset_derive" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6451128aa6655d880755345d085494cf7561a6bee7c8dc821e5d77e6d267ecd4" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "env_logger" version = "0.9.0" @@ -2630,17 +2447,6 @@ dependencies = [ "polyval", ] -[[package]] -name = "gimli" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7" -dependencies = [ - "fallible-iterator", - "indexmap", - "stable_deref_trait", -] - [[package]] name = "gimli" version = "0.26.1" @@ -2754,9 +2560,6 @@ name = "hashbrown" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" -dependencies = [ - "ahash", -] [[package]] name = "hashbrown" @@ -2930,12 +2733,6 @@ dependencies = [ "tokio-rustls", ] -[[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" @@ -3365,7 +3162,6 @@ dependencies = [ "sp-io", "sp-offchain", "sp-runtime", - "sp-sandbox", "sp-session", "sp-staking", "sp-std", @@ -3943,27 +3739,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "loupe" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6a72dfa44fe15b5e76b94307eeb2ff995a8c5b283b55008940c02e0c5b634d" -dependencies = [ - "indexmap", - "loupe-derive", - "rustversion", -] - -[[package]] -name = "loupe-derive" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fbfc88337168279f2e9ae06e157cfed4efd3316e14dc96ed074d4f2e6c5952" -dependencies = [ - "quote", - "syn", -] - [[package]] name = "lru" version = "0.8.1" @@ -4197,12 +3972,6 @@ dependencies = [ "syn", ] -[[package]] -name = "more-asserts" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0debeb9fcf88823ea64d64e4a815ab1643f33127d995978e099942ce38f25238" - [[package]] name = "multiaddr" version = "0.14.0" @@ -4854,18 +4623,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "object" -version = "0.28.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40bec70ba014595f99f7aa110b84331ffe1ee9aece7fe6f387cc7e3ecda4d456" -dependencies = [ - "crc32fast", - "hashbrown 0.11.2", - "indexmap", - "memchr", -] - [[package]] name = "object" version = "0.29.0" @@ -6865,26 +6622,6 @@ dependencies = [ "cc", ] -[[package]] -name = "ptr_meta" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" -dependencies = [ - "ptr_meta_derive", -] - -[[package]] -name = "ptr_meta_derive" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "quick-error" version = "1.2.3" @@ -7096,17 +6833,6 @@ dependencies = [ "syn", ] -[[package]] -name = "regalloc" -version = "0.0.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "571f7f397d61c4755285cd37853fe8e03271c243424a907415909379659381c5" -dependencies = [ - "log", - "rustc-hash", - "smallvec", -] - [[package]] name = "regalloc2" version = "0.3.2" @@ -7146,18 +6872,6 @@ version = "0.6.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" -[[package]] -name = "region" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76e189c2369884dce920945e2ddf79b3dff49e071a167dd1817fa9c4c00d512e" -dependencies = [ - "bitflags", - "libc", - "mach", - "winapi", -] - [[package]] name = "remove_dir_all" version = "0.5.3" @@ -7167,15 +6881,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "rend" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79af64b4b6362ffba04eef3a4e10829718a4896dac19daa741851c86781edf95" -dependencies = [ - "bytecheck", -] - [[package]] name = "resolv-conf" version = "0.7.0" @@ -7212,31 +6917,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "rkyv" -version = "0.7.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f08c8062c1fe1253064043b8fc07bfea1b9702b71b4a86c11ea3588183b12e1" -dependencies = [ - "bytecheck", - "hashbrown 0.12.3", - "ptr_meta", - "rend", - "rkyv_derive", - "seahash", -] - -[[package]] -name = "rkyv_derive" -version = "0.7.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e289706df51226e84814bf6ba1a9e1013112ae29bc7a9878f73fce360520c403" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "rocksdb" version = "0.19.0" @@ -7867,7 +7547,6 @@ dependencies = [ "array-bytes", "criterion", "env_logger", - "lazy_static", "lru", "num_cpus", "parity-scale-codec", @@ -7881,7 +7560,6 @@ dependencies = [ "sc-tracing", "sp-api", "sp-core", - "sp-core-hashing-proc-macro", "sp-externalities", "sp-io", "sp-maybe-compressed-blob", @@ -7904,15 +7582,11 @@ dependencies = [ name = "sc-executor-common" version = "0.10.0-dev" dependencies = [ - "environmental", - "parity-scale-codec", "sc-allocator", "sp-maybe-compressed-blob", - "sp-sandbox", "sp-wasm-interface", "thiserror", "wasm-instrument 0.3.0", - "wasmer", "wasmi 0.13.0", ] @@ -7921,11 +7595,9 @@ name = "sc-executor-wasmi" version = "0.10.0-dev" dependencies = [ "log", - "parity-scale-codec", "sc-allocator", "sc-executor-common", "sp-runtime-interface", - "sp-sandbox", "sp-wasm-interface", "wasmi 0.13.0", ] @@ -7939,7 +7611,6 @@ dependencies = [ "log", "once_cell", "parity-scale-codec", - "parity-wasm", "paste", "rustix", "sc-allocator", @@ -7947,7 +7618,6 @@ dependencies = [ "sc-runtime-test", "sp-io", "sp-runtime-interface", - "sp-sandbox", "sp-wasm-interface", "tempfile", "wasmtime", @@ -8443,11 +8113,9 @@ dependencies = [ name = "sc-runtime-test" version = "2.0.0" dependencies = [ - "paste", "sp-core", "sp-io", "sp-runtime", - "sp-sandbox", "sp-std", "substrate-wasm-builder", ] @@ -8802,12 +8470,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "seahash" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" - [[package]] name = "sec1" version = "0.3.0" @@ -8914,15 +8576,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde_bytes" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9" -dependencies = [ - "serde", -] - [[package]] name = "serde_cbor" version = "0.11.1" @@ -9777,21 +9430,6 @@ dependencies = [ "substrate-wasm-builder", ] -[[package]] -name = "sp-sandbox" -version = "0.10.0-dev" -dependencies = [ - "assert_matches", - "log", - "parity-scale-codec", - "sp-core", - "sp-io", - "sp-std", - "sp-wasm-interface", - "wasmi 0.13.0", - "wat", -] - [[package]] name = "sp-serializer" version = "4.0.0-dev" @@ -10721,7 +10359,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" dependencies = [ "cfg-if", - "log", "pin-project-lite 0.2.6", "tracing-attributes", "tracing-core", @@ -11301,219 +10938,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "wasmer" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f727a39e7161f7438ddb8eafe571b67c576a8c2fb459f666d9053b5bba4afdea" -dependencies = [ - "cfg-if", - "indexmap", - "js-sys", - "loupe", - "more-asserts", - "target-lexicon", - "thiserror", - "wasm-bindgen", - "wasmer-compiler", - "wasmer-compiler-cranelift", - "wasmer-compiler-singlepass", - "wasmer-derive", - "wasmer-engine", - "wasmer-engine-dylib", - "wasmer-engine-universal", - "wasmer-types", - "wasmer-vm", - "wat", - "winapi", -] - -[[package]] -name = "wasmer-compiler" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e9951599222eb12bd13d4d91bcded0a880e4c22c2dfdabdf5dc7e5e803b7bf3" -dependencies = [ - "enumset", - "loupe", - "rkyv", - "serde", - "serde_bytes", - "smallvec", - "target-lexicon", - "thiserror", - "wasmer-types", - "wasmer-vm", - "wasmparser 0.78.2", -] - -[[package]] -name = "wasmer-compiler-cranelift" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c83273bce44e668f3a2b9ccb7f1193db918b1d6806f64acc5ff71f6ece5f20" -dependencies = [ - "cranelift-codegen 0.76.0", - "cranelift-entity 0.76.0", - "cranelift-frontend 0.76.0", - "gimli 0.25.0", - "loupe", - "more-asserts", - "rayon", - "smallvec", - "target-lexicon", - "tracing", - "wasmer-compiler", - "wasmer-types", - "wasmer-vm", -] - -[[package]] -name = "wasmer-compiler-singlepass" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5432e993840cdb8e6875ddc8c9eea64e7a129579b4706bd91b8eb474d9c4a860" -dependencies = [ - "byteorder", - "dynasm", - "dynasmrt", - "lazy_static", - "loupe", - "more-asserts", - "rayon", - "smallvec", - "wasmer-compiler", - "wasmer-types", - "wasmer-vm", -] - -[[package]] -name = "wasmer-derive" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458dbd9718a837e6dbc52003aef84487d79eedef5fa28c7d28b6784be98ac08e" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "wasmer-engine" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed603a6d037ebbb14014d7f739ae996a78455a4b86c41cfa4e81c590a1253b9" -dependencies = [ - "backtrace", - "enumset", - "lazy_static", - "loupe", - "memmap2", - "more-asserts", - "rustc-demangle", - "serde", - "serde_bytes", - "target-lexicon", - "thiserror", - "wasmer-compiler", - "wasmer-types", - "wasmer-vm", -] - -[[package]] -name = "wasmer-engine-dylib" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccd7fdc60e252a795c849b3f78a81a134783051407e7e279c10b7019139ef8dc" -dependencies = [ - "cfg-if", - "enum-iterator", - "enumset", - "leb128", - "libloading", - "loupe", - "object 0.28.3", - "rkyv", - "serde", - "tempfile", - "tracing", - "wasmer-compiler", - "wasmer-engine", - "wasmer-object", - "wasmer-types", - "wasmer-vm", - "which", -] - -[[package]] -name = "wasmer-engine-universal" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcff0cd2c01a8de6009fd863b14ea883132a468a24f2d2ee59dc34453d3a31b5" -dependencies = [ - "cfg-if", - "enum-iterator", - "enumset", - "leb128", - "loupe", - "region", - "rkyv", - "wasmer-compiler", - "wasmer-engine", - "wasmer-types", - "wasmer-vm", - "winapi", -] - -[[package]] -name = "wasmer-object" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ce18ac2877050e59580d27ee1a88f3192d7a31e77fbba0852abc7888d6e0b5" -dependencies = [ - "object 0.28.3", - "thiserror", - "wasmer-compiler", - "wasmer-types", -] - -[[package]] -name = "wasmer-types" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "659fa3dd6c76f62630deff4ac8c7657b07f0b1e4d7e0f8243a552b9d9b448e24" -dependencies = [ - "indexmap", - "loupe", - "rkyv", - "serde", - "thiserror", -] - -[[package]] -name = "wasmer-vm" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afdc46158517c2769f9938bc222a7d41b3bb330824196279d8aa2d667cd40641" -dependencies = [ - "backtrace", - "cc", - "cfg-if", - "enum-iterator", - "indexmap", - "libc", - "loupe", - "memoffset", - "more-asserts", - "region", - "rkyv", - "serde", - "thiserror", - "wasmer-types", - "winapi", -] - [[package]] name = "wasmi" version = "0.13.0" @@ -11576,12 +11000,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "wasmparser" -version = "0.78.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52144d4c78e5cf8b055ceab8e5fa22814ce4315d6002ad32cfd914f37c12fd65" - [[package]] name = "wasmparser" version = "0.89.1" @@ -11619,7 +11037,7 @@ dependencies = [ "rayon", "serde", "target-lexicon", - "wasmparser 0.89.1", + "wasmparser", "wasmtime-cache", "wasmtime-cranelift", "wasmtime-environ", @@ -11664,17 +11082,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f03cf79d982fc68e94ba0bea6a300a3b94621c4eb9705eece0a4f06b235a3b5" dependencies = [ "anyhow", - "cranelift-codegen 0.88.0", - "cranelift-entity 0.88.0", - "cranelift-frontend 0.88.0", + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", "cranelift-native", "cranelift-wasm", - "gimli 0.26.1", + "gimli", "log", "object 0.29.0", "target-lexicon", "thiserror", - "wasmparser 0.89.1", + "wasmparser", "wasmtime-environ", ] @@ -11685,15 +11103,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c587c62e91c5499df62012b87b88890d0eb470b2ffecc5964e9da967b70c77c" dependencies = [ "anyhow", - "cranelift-entity 0.88.0", - "gimli 0.26.1", + "cranelift-entity", + "gimli", "indexmap", "log", "object 0.29.0", "serde", "target-lexicon", "thiserror", - "wasmparser 0.89.1", + "wasmparser", "wasmtime-types", ] @@ -11708,7 +11126,7 @@ dependencies = [ "bincode", "cfg-if", "cpp_demangle", - "gimli 0.26.1", + "gimli", "log", "object 0.29.0", "rustc-demangle", @@ -11764,10 +11182,10 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "790cf43ee8e2d5dad1780af30f00d7a972b74725fb1e4f90c28d62733819b185" dependencies = [ - "cranelift-entity 0.88.0", + "cranelift-entity", "serde", "thiserror", - "wasmparser 0.89.1", + "wasmparser", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index e885f0916c..64ceb104c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -199,7 +199,6 @@ members = [ "primitives/runtime-interface/test", "primitives/runtime-interface/test-wasm", "primitives/runtime-interface/test-wasm-deprecated", - "primitives/sandbox", "primitives/serializer", "primitives/session", "primitives/staking", diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index dfddf6a894..706d2c7720 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -39,7 +39,6 @@ sp-session = { version = "4.0.0-dev", default-features = false, path = "../../.. sp-transaction-pool = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/transaction-pool" } sp-version = { version = "5.0.0", default-features = false, path = "../../../primitives/version" } sp-io = { version = "7.0.0", default-features = false, path = "../../../primitives/io" } -sp-sandbox = { version = "0.10.0-dev", default-features = false, path = "../../../primitives/sandbox" } # frame dependencies frame-executive = { version = "4.0.0-dev", default-features = false, path = "../../../frame/executive" } @@ -117,7 +116,6 @@ substrate-wasm-builder = { version = "5.0.0-dev", path = "../../../utils/wasm-bu default = ["std"] with-tracing = ["frame-executive/with-tracing"] std = [ - "sp-sandbox/std", "pallet-whitelist/std", "pallet-offences-benchmarking?/std", "pallet-election-provider-support-benchmarking?/std", @@ -312,8 +310,3 @@ try-runtime = [ "pallet-vesting/try-runtime", "pallet-whitelist/try-runtime", ] -# Force `sp-sandbox` to call into the host resident executor. One still need to make sure -# that `sc-executor` gets the `wasmer-sandbox` feature which happens automatically when -# specified on the command line. -# Don't use that on a production chain. -wasmer-sandbox = ["sp-sandbox/wasmer-sandbox"] diff --git a/client/executor/Cargo.toml b/client/executor/Cargo.toml index 7cba725a9e..83d4801d1c 100644 --- a/client/executor/Cargo.toml +++ b/client/executor/Cargo.toml @@ -14,7 +14,6 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -lazy_static = "1.4.0" lru = "0.8.1" parking_lot = "0.12.1" tracing = "0.1.29" @@ -26,7 +25,6 @@ sc-executor-wasmi = { version = "0.10.0-dev", path = "wasmi" } sc-executor-wasmtime = { version = "0.10.0-dev", path = "wasmtime" } sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } sp-core = { version = "7.0.0", path = "../../primitives/core" } -sp-core-hashing-proc-macro = { version = "5.0.0", path = "../../primitives/core/hashing/proc-macro" } sp-externalities = { version = "0.13.0", path = "../../primitives/externalities" } sp-io = { version = "7.0.0", path = "../../primitives/io" } sp-panic-handler = { version = "5.0.0", path = "../../primitives/panic-handler" } @@ -61,4 +59,3 @@ default = ["std"] # This crate does not have `no_std` support, we just require this for tests std = [] wasm-extern-trace = [] -wasmer-sandbox = ["sc-executor-common/wasmer-sandbox"] diff --git a/client/executor/common/Cargo.toml b/client/executor/common/Cargo.toml index 4b83e9fcc9..648e937d37 100644 --- a/client/executor/common/Cargo.toml +++ b/client/executor/common/Cargo.toml @@ -14,19 +14,12 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.0.0" } -environmental = "1.1.3" thiserror = "1.0.30" wasm-instrument = "0.3" -wasmer = { version = "2.2", features = ["singlepass"], optional = true } wasmi = "0.13" sc-allocator = { version = "4.1.0-dev", path = "../../allocator" } sp-maybe-compressed-blob = { version = "4.1.0-dev", path = "../../../primitives/maybe-compressed-blob" } -sp-sandbox = { version = "0.10.0-dev", path = "../../../primitives/sandbox" } sp-wasm-interface = { version = "7.0.0", path = "../../../primitives/wasm-interface" } [features] default = [] -wasmer-sandbox = [ - "wasmer", -] diff --git a/client/executor/common/src/error.rs b/client/executor/common/src/error.rs index 376ac190bd..c35a874b77 100644 --- a/client/executor/common/src/error.rs +++ b/client/executor/common/src/error.rs @@ -30,9 +30,6 @@ pub enum Error { #[error(transparent)] Wasmi(#[from] wasmi::Error), - #[error("Sandbox error: {0}")] - Sandbox(String), - #[error("Error calling api function: {0}")] ApiError(Box), diff --git a/client/executor/common/src/lib.rs b/client/executor/common/src/lib.rs index b69883afba..79bb74b62a 100644 --- a/client/executor/common/src/lib.rs +++ b/client/executor/common/src/lib.rs @@ -23,6 +23,5 @@ pub mod error; pub mod runtime_blob; -pub mod sandbox; pub mod util; pub mod wasm_runtime; diff --git a/client/executor/common/src/sandbox.rs b/client/executor/common/src/sandbox.rs deleted file mode 100644 index 1e925bd5a7..0000000000 --- a/client/executor/common/src/sandbox.rs +++ /dev/null @@ -1,585 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2018-2022 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 . - -//! This module implements sandboxing support in the runtime. -//! -//! Sandboxing is backed by wasmi and wasmer, depending on the configuration. - -#[cfg(feature = "wasmer-sandbox")] -mod wasmer_backend; -mod wasmi_backend; - -use std::{collections::HashMap, rc::Rc}; - -use codec::Decode; -use sp_sandbox::env as sandbox_env; -use sp_wasm_interface::{FunctionContext, Pointer, WordSize}; - -use crate::{ - error::{self, Result}, - util, -}; - -#[cfg(feature = "wasmer-sandbox")] -use self::wasmer_backend::{ - get_global as wasmer_get_global, instantiate as wasmer_instantiate, invoke as wasmer_invoke, - new_memory as wasmer_new_memory, Backend as WasmerBackend, - MemoryWrapper as WasmerMemoryWrapper, -}; -use self::wasmi_backend::{ - get_global as wasmi_get_global, instantiate as wasmi_instantiate, invoke as wasmi_invoke, - new_memory as wasmi_new_memory, MemoryWrapper as WasmiMemoryWrapper, -}; - -/// Index of a function inside the supervisor. -/// -/// This is a typically an index in the default table of the supervisor, however -/// the exact meaning of this index is depends on the implementation of dispatch function. -#[derive(Copy, Clone, Debug, PartialEq)] -pub struct SupervisorFuncIndex(usize); - -impl From for usize { - fn from(index: SupervisorFuncIndex) -> Self { - index.0 - } -} - -/// Index of a function within guest index space. -/// -/// This index is supposed to be used as index for `Externals`. -#[derive(Copy, Clone, Debug, PartialEq)] -struct GuestFuncIndex(usize); - -/// This struct holds a mapping from guest index space to supervisor. -struct GuestToSupervisorFunctionMapping { - /// Position of elements in this vector are interpreted - /// as indices of guest functions and are mapped to - /// corresponding supervisor function indices. - funcs: Vec, -} - -impl GuestToSupervisorFunctionMapping { - /// Create an empty function mapping - fn new() -> GuestToSupervisorFunctionMapping { - GuestToSupervisorFunctionMapping { funcs: Vec::new() } - } - - /// Add a new supervisor function to the mapping. - /// Returns a newly assigned guest function index. - fn define(&mut self, supervisor_func: SupervisorFuncIndex) -> GuestFuncIndex { - let idx = self.funcs.len(); - self.funcs.push(supervisor_func); - GuestFuncIndex(idx) - } - - /// Find supervisor function index by its corresponding guest function index - fn func_by_guest_index(&self, guest_func_idx: GuestFuncIndex) -> Option { - self.funcs.get(guest_func_idx.0).cloned() - } -} - -/// Holds sandbox function and memory imports and performs name resolution -struct Imports { - /// Maps qualified function name to its guest function index - func_map: HashMap<(Vec, Vec), GuestFuncIndex>, - - /// Maps qualified field name to its memory reference - memories_map: HashMap<(Vec, Vec), Memory>, -} - -impl Imports { - fn func_by_name(&self, module_name: &str, func_name: &str) -> Option { - self.func_map - .get(&(module_name.as_bytes().to_owned(), func_name.as_bytes().to_owned())) - .cloned() - } - - fn memory_by_name(&self, module_name: &str, memory_name: &str) -> Option { - self.memories_map - .get(&(module_name.as_bytes().to_owned(), memory_name.as_bytes().to_owned())) - .cloned() - } -} - -/// The sandbox context used to execute sandboxed functions. -pub trait SandboxContext { - /// Invoke a function in the supervisor environment. - /// - /// This first invokes the dispatch thunk function, passing in the function index of the - /// desired function to call and serialized arguments. The thunk calls the desired function - /// with the deserialized arguments, then serializes the result into memory and returns - /// reference. The pointer to and length of the result in linear memory is encoded into an - /// `i64`, with the upper 32 bits representing the pointer and the lower 32 bits representing - /// the length. - /// - /// # Errors - /// - /// Returns `Err` if the dispatch_thunk function has an incorrect signature or traps during - /// execution. - fn invoke( - &mut self, - invoke_args_ptr: Pointer, - invoke_args_len: WordSize, - state: u32, - func_idx: SupervisorFuncIndex, - ) -> Result; - - /// Returns the supervisor context. - fn supervisor_context(&mut self) -> &mut dyn FunctionContext; -} - -/// Implementation of [`Externals`] that allows execution of guest module with -/// [externals][`Externals`] that might refer functions defined by supervisor. -/// -/// [`Externals`]: ../wasmi/trait.Externals.html -pub struct GuestExternals<'a> { - /// Instance of sandboxed module to be dispatched - sandbox_instance: &'a SandboxInstance, - - /// External state passed to guest environment, see the `instantiate` function - state: u32, -} - -/// Module instance in terms of selected backend -enum BackendInstance { - /// Wasmi module instance - Wasmi(wasmi::ModuleRef), - - /// Wasmer module instance - #[cfg(feature = "wasmer-sandbox")] - Wasmer(wasmer::Instance), -} - -/// Sandboxed instance of a wasm module. -/// -/// It's primary purpose is to [`invoke`] exported functions on it. -/// -/// All imports of this instance are specified at the creation time and -/// imports are implemented by the supervisor. -/// -/// Hence, in order to invoke an exported function on a sandboxed module instance, -/// it's required to provide supervisor externals: it will be used to execute -/// code in the supervisor context. -/// -/// This is generic over a supervisor function reference type. -/// -/// [`invoke`]: #method.invoke -pub struct SandboxInstance { - backend_instance: BackendInstance, - guest_to_supervisor_mapping: GuestToSupervisorFunctionMapping, -} - -impl SandboxInstance { - /// Invoke an exported function by a name. - /// - /// `supervisor_externals` is required to execute the implementations - /// of the syscalls that published to a sandboxed module instance. - /// - /// The `state` parameter can be used to provide custom data for - /// these syscall implementations. - pub fn invoke( - &self, - export_name: &str, - args: &[sp_wasm_interface::Value], - state: u32, - sandbox_context: &mut dyn SandboxContext, - ) -> std::result::Result, error::Error> { - match &self.backend_instance { - BackendInstance::Wasmi(wasmi_instance) => - wasmi_invoke(self, wasmi_instance, export_name, args, state, sandbox_context), - - #[cfg(feature = "wasmer-sandbox")] - BackendInstance::Wasmer(wasmer_instance) => - wasmer_invoke(wasmer_instance, export_name, args, state, sandbox_context), - } - } - - /// Get the value from a global with the given `name`. - /// - /// Returns `Some(_)` if the global could be found. - pub fn get_global_val(&self, name: &str) -> Option { - match &self.backend_instance { - BackendInstance::Wasmi(wasmi_instance) => wasmi_get_global(wasmi_instance, name), - - #[cfg(feature = "wasmer-sandbox")] - BackendInstance::Wasmer(wasmer_instance) => wasmer_get_global(wasmer_instance, name), - } - } -} - -/// Error occurred during instantiation of a sandboxed module. -pub enum InstantiationError { - /// Something wrong with the environment definition. It either can't - /// be decoded, have a reference to a non-existent or torn down memory instance. - EnvironmentDefinitionCorrupted, - /// Provided module isn't recognized as a valid webassembly binary. - ModuleDecoding, - /// Module is a well-formed webassembly binary but could not be instantiated. This could - /// happen because, e.g. the module imports entries not provided by the environment. - Instantiation, - /// Module is well-formed, instantiated and linked, but while executing the start function - /// a trap was generated. - StartTrapped, - /// The code was compiled with a CPU feature not available on the host. - CpuFeature, -} - -fn decode_environment_definition( - mut raw_env_def: &[u8], - memories: &[Option], -) -> std::result::Result<(Imports, GuestToSupervisorFunctionMapping), InstantiationError> { - let env_def = sandbox_env::EnvironmentDefinition::decode(&mut raw_env_def) - .map_err(|_| InstantiationError::EnvironmentDefinitionCorrupted)?; - - let mut func_map = HashMap::new(); - let mut memories_map = HashMap::new(); - let mut guest_to_supervisor_mapping = GuestToSupervisorFunctionMapping::new(); - - for entry in &env_def.entries { - let module = entry.module_name.clone(); - let field = entry.field_name.clone(); - - match entry.entity { - sandbox_env::ExternEntity::Function(func_idx) => { - let externals_idx = - guest_to_supervisor_mapping.define(SupervisorFuncIndex(func_idx as usize)); - func_map.insert((module, field), externals_idx); - }, - sandbox_env::ExternEntity::Memory(memory_idx) => { - let memory_ref = memories - .get(memory_idx as usize) - .cloned() - .ok_or(InstantiationError::EnvironmentDefinitionCorrupted)? - .ok_or(InstantiationError::EnvironmentDefinitionCorrupted)?; - memories_map.insert((module, field), memory_ref); - }, - } - } - - Ok((Imports { func_map, memories_map }, guest_to_supervisor_mapping)) -} - -/// An environment in which the guest module is instantiated. -pub struct GuestEnvironment { - /// Function and memory imports of the guest module - imports: Imports, - - /// Supervisor functinons mapped to guest index space - guest_to_supervisor_mapping: GuestToSupervisorFunctionMapping, -} - -impl GuestEnvironment { - /// Decodes an environment definition from the given raw bytes. - /// - /// Returns `Err` if the definition cannot be decoded. - pub fn decode
( - store: &Store
, - raw_env_def: &[u8], - ) -> std::result::Result { - let (imports, guest_to_supervisor_mapping) = - decode_environment_definition(raw_env_def, &store.memories)?; - Ok(Self { imports, guest_to_supervisor_mapping }) - } -} - -/// An unregistered sandboxed instance. -/// -/// To finish off the instantiation the user must call `register`. -#[must_use] -pub struct UnregisteredInstance { - sandbox_instance: Rc, -} - -impl UnregisteredInstance { - /// Finalizes instantiation of this module. - pub fn register
(self, store: &mut Store
, dispatch_thunk: DT) -> u32 { - // At last, register the instance. - store.register_sandbox_instance(self.sandbox_instance, dispatch_thunk) - } -} - -/// Sandbox backend to use -pub enum SandboxBackend { - /// Wasm interpreter - Wasmi, - - /// Wasmer environment - #[cfg(feature = "wasmer-sandbox")] - Wasmer, - - /// Use wasmer backend if available. Fall back to wasmi otherwise. - TryWasmer, -} - -/// Memory reference in terms of a selected backend -#[derive(Clone, Debug)] -pub enum Memory { - /// Wasmi memory reference - Wasmi(WasmiMemoryWrapper), - - /// Wasmer memory refernce - #[cfg(feature = "wasmer-sandbox")] - Wasmer(WasmerMemoryWrapper), -} - -impl Memory { - /// View as wasmi memory - pub fn as_wasmi(&self) -> Option { - match self { - Memory::Wasmi(memory) => Some(memory.clone()), - - #[cfg(feature = "wasmer-sandbox")] - Memory::Wasmer(_) => None, - } - } - - /// View as wasmer memory - #[cfg(feature = "wasmer-sandbox")] - pub fn as_wasmer(&self) -> Option { - match self { - Memory::Wasmer(memory) => Some(memory.clone()), - Memory::Wasmi(_) => None, - } - } -} - -impl util::MemoryTransfer for Memory { - fn read(&self, source_addr: Pointer, size: usize) -> Result> { - match self { - Memory::Wasmi(sandboxed_memory) => sandboxed_memory.read(source_addr, size), - - #[cfg(feature = "wasmer-sandbox")] - Memory::Wasmer(sandboxed_memory) => sandboxed_memory.read(source_addr, size), - } - } - - fn read_into(&self, source_addr: Pointer, destination: &mut [u8]) -> Result<()> { - match self { - Memory::Wasmi(sandboxed_memory) => sandboxed_memory.read_into(source_addr, destination), - - #[cfg(feature = "wasmer-sandbox")] - Memory::Wasmer(sandboxed_memory) => sandboxed_memory.read_into(source_addr, destination), - } - } - - fn write_from(&self, dest_addr: Pointer, source: &[u8]) -> Result<()> { - match self { - Memory::Wasmi(sandboxed_memory) => sandboxed_memory.write_from(dest_addr, source), - - #[cfg(feature = "wasmer-sandbox")] - Memory::Wasmer(sandboxed_memory) => sandboxed_memory.write_from(dest_addr, source), - } - } -} - -/// Information specific to a particular execution backend -enum BackendContext { - /// Wasmi specific context - Wasmi, - - /// Wasmer specific context - #[cfg(feature = "wasmer-sandbox")] - Wasmer(WasmerBackend), -} - -impl BackendContext { - pub fn new(backend: SandboxBackend) -> BackendContext { - match backend { - SandboxBackend::Wasmi => BackendContext::Wasmi, - - #[cfg(not(feature = "wasmer-sandbox"))] - SandboxBackend::TryWasmer => BackendContext::Wasmi, - - #[cfg(feature = "wasmer-sandbox")] - SandboxBackend::Wasmer | SandboxBackend::TryWasmer => - BackendContext::Wasmer(WasmerBackend::new()), - } - } -} - -/// This struct keeps track of all sandboxed components. -/// -/// This is generic over a supervisor function reference type. -pub struct Store
{ - /// Stores the instance and the dispatch thunk associated to per instance. - /// - /// Instances are `Some` until torn down. - instances: Vec, DT)>>, - /// Memories are `Some` until torn down. - memories: Vec>, - backend_context: BackendContext, -} - -impl Store
{ - /// Create a new empty sandbox store. - pub fn new(backend: SandboxBackend) -> Self { - Store { - instances: Vec::new(), - memories: Vec::new(), - backend_context: BackendContext::new(backend), - } - } - - /// Create a new memory instance and return it's index. - /// - /// # Errors - /// - /// Returns `Err` if the memory couldn't be created. - /// Typically happens if `initial` is more than `maximum`. - pub fn new_memory(&mut self, initial: u32, maximum: u32) -> Result { - let memories = &mut self.memories; - let backend_context = &self.backend_context; - - let maximum = match maximum { - sandbox_env::MEM_UNLIMITED => None, - specified_limit => Some(specified_limit), - }; - - let memory = match &backend_context { - BackendContext::Wasmi => wasmi_new_memory(initial, maximum)?, - - #[cfg(feature = "wasmer-sandbox")] - BackendContext::Wasmer(context) => wasmer_new_memory(context, initial, maximum)?, - }; - - let mem_idx = memories.len(); - memories.push(Some(memory)); - - Ok(mem_idx as u32) - } - - /// Returns `SandboxInstance` by `instance_idx`. - /// - /// # Errors - /// - /// Returns `Err` If `instance_idx` isn't a valid index of an instance or - /// instance is already torndown. - pub fn instance(&self, instance_idx: u32) -> Result> { - self.instances - .get(instance_idx as usize) - .ok_or("Trying to access a non-existent instance")? - .as_ref() - .map(|v| v.0.clone()) - .ok_or_else(|| "Trying to access a torndown instance".into()) - } - - /// Returns dispatch thunk by `instance_idx`. - /// - /// # Errors - /// - /// Returns `Err` If `instance_idx` isn't a valid index of an instance or - /// instance is already torndown. - pub fn dispatch_thunk(&self, instance_idx: u32) -> Result
{ - self.instances - .get(instance_idx as usize) - .as_ref() - .ok_or("Trying to access a non-existent instance")? - .as_ref() - .map(|v| v.1.clone()) - .ok_or_else(|| "Trying to access a torndown instance".into()) - } - - /// Returns reference to a memory instance by `memory_idx`. - /// - /// # Errors - /// - /// Returns `Err` If `memory_idx` isn't a valid index of an memory or - /// if memory has been torn down. - pub fn memory(&self, memory_idx: u32) -> Result { - self.memories - .get(memory_idx as usize) - .cloned() - .ok_or("Trying to access a non-existent sandboxed memory")? - .ok_or_else(|| "Trying to access a torndown sandboxed memory".into()) - } - - /// Tear down the memory at the specified index. - /// - /// # Errors - /// - /// Returns `Err` if `memory_idx` isn't a valid index of an memory or - /// if it has been torn down. - pub fn memory_teardown(&mut self, memory_idx: u32) -> Result<()> { - match self.memories.get_mut(memory_idx as usize) { - None => Err("Trying to teardown a non-existent sandboxed memory".into()), - Some(None) => Err("Double teardown of a sandboxed memory".into()), - Some(memory) => { - *memory = None; - Ok(()) - }, - } - } - - /// Tear down the instance at the specified index. - /// - /// # Errors - /// - /// Returns `Err` if `instance_idx` isn't a valid index of an instance or - /// if it has been torn down. - pub fn instance_teardown(&mut self, instance_idx: u32) -> Result<()> { - match self.instances.get_mut(instance_idx as usize) { - None => Err("Trying to teardown a non-existent instance".into()), - Some(None) => Err("Double teardown of an instance".into()), - Some(instance) => { - *instance = None; - Ok(()) - }, - } - } - - /// Instantiate a guest module and return it's index in the store. - /// - /// The guest module's code is specified in `wasm`. Environment that will be available to - /// guest module is specified in `guest_env`. A dispatch thunk is used as function that - /// handle calls from guests. `state` is an opaque pointer to caller's arbitrary context - /// normally created by `sp_sandbox::Instance` primitive. - /// - /// Note: Due to borrowing constraints dispatch thunk is now propagated using DTH - /// - /// Returns uninitialized sandboxed module instance or an instantiation error. - pub fn instantiate( - &mut self, - wasm: &[u8], - guest_env: GuestEnvironment, - state: u32, - sandbox_context: &mut dyn SandboxContext, - ) -> std::result::Result { - let sandbox_instance = match self.backend_context { - BackendContext::Wasmi => wasmi_instantiate(wasm, guest_env, state, sandbox_context)?, - - #[cfg(feature = "wasmer-sandbox")] - BackendContext::Wasmer(ref context) => - wasmer_instantiate(context, wasm, guest_env, state, sandbox_context)?, - }; - - Ok(UnregisteredInstance { sandbox_instance }) - } -} - -// Private routines -impl
Store
{ - fn register_sandbox_instance( - &mut self, - sandbox_instance: Rc, - dispatch_thunk: DT, - ) -> u32 { - let instance_idx = self.instances.len(); - self.instances.push(Some((sandbox_instance, dispatch_thunk))); - instance_idx as u32 - } -} diff --git a/client/executor/common/src/sandbox/wasmer_backend.rs b/client/executor/common/src/sandbox/wasmer_backend.rs deleted file mode 100644 index 29926141ed..0000000000 --- a/client/executor/common/src/sandbox/wasmer_backend.rs +++ /dev/null @@ -1,449 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2019-2021 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 . - -//! Wasmer specific impls for sandbox - -use std::{cell::RefCell, collections::HashMap, rc::Rc}; - -use wasmer::RuntimeError; - -use codec::{Decode, Encode}; -use sp_sandbox::HostError; -use sp_wasm_interface::{FunctionContext, Pointer, ReturnValue, Value, WordSize}; - -use crate::{ - error::{Error, Result}, - sandbox::{ - BackendInstance, GuestEnvironment, InstantiationError, Memory, SandboxContext, - SandboxInstance, SupervisorFuncIndex, - }, - util::{checked_range, MemoryTransfer}, -}; - -environmental::environmental!(SandboxContextStore: trait SandboxContext); - -/// Wasmer specific context -pub struct Backend { - store: wasmer::Store, -} - -impl Backend { - pub fn new() -> Self { - let compiler = wasmer::Singlepass::default(); - Backend { store: wasmer::Store::new(&wasmer::Universal::new(compiler).engine()) } - } -} - -/// Invoke a function within a sandboxed module -pub fn invoke( - instance: &wasmer::Instance, - export_name: &str, - args: &[Value], - _state: u32, - sandbox_context: &mut dyn SandboxContext, -) -> std::result::Result, Error> { - let function = instance - .exports - .get_function(export_name) - .map_err(|error| Error::Sandbox(error.to_string()))?; - - let args: Vec = args - .iter() - .map(|v| match *v { - Value::I32(val) => wasmer::Val::I32(val), - Value::I64(val) => wasmer::Val::I64(val), - Value::F32(val) => wasmer::Val::F32(f32::from_bits(val)), - Value::F64(val) => wasmer::Val::F64(f64::from_bits(val)), - }) - .collect(); - - let wasmer_result = SandboxContextStore::using(sandbox_context, || { - function.call(&args).map_err(|error| Error::Sandbox(error.to_string())) - })?; - - match wasmer_result.as_ref() { - [] => Ok(None), - - [wasm_value] => { - let wasmer_value = match *wasm_value { - wasmer::Val::I32(val) => Value::I32(val), - wasmer::Val::I64(val) => Value::I64(val), - wasmer::Val::F32(val) => Value::F32(f32::to_bits(val)), - wasmer::Val::F64(val) => Value::F64(f64::to_bits(val)), - _ => - return Err(Error::Sandbox(format!( - "Unsupported return value: {:?}", - wasm_value, - ))), - }; - - Ok(Some(wasmer_value)) - }, - - _ => Err(Error::Sandbox("multiple return types are not supported yet".into())), - } -} - -/// Instantiate a module within a sandbox context -pub fn instantiate( - context: &Backend, - wasm: &[u8], - guest_env: GuestEnvironment, - state: u32, - sandbox_context: &mut dyn SandboxContext, -) -> std::result::Result, InstantiationError> { - let module = wasmer::Module::new(&context.store, wasm) - .map_err(|_| InstantiationError::ModuleDecoding)?; - - type Exports = HashMap; - let mut exports_map = Exports::new(); - - for import in module.imports() { - match import.ty() { - // Nothing to do here - wasmer::ExternType::Global(_) | wasmer::ExternType::Table(_) => (), - - wasmer::ExternType::Memory(_) => { - let exports = exports_map - .entry(import.module().to_string()) - .or_insert_with(wasmer::Exports::new); - - let memory = guest_env - .imports - .memory_by_name(import.module(), import.name()) - .ok_or(InstantiationError::ModuleDecoding)?; - - let wasmer_memory_ref = memory.as_wasmer().expect( - "memory is created by wasmer; \ - exported by the same module and backend; \ - thus the operation can't fail; \ - qed", - ); - - // This is safe since we're only instantiating the module and populating - // the export table, so no memory access can happen at this time. - // All subsequent memory accesses should happen through the wrapper, - // that enforces the memory access protocol. - // - // We take exclusive lock to ensure that we're the only one here, - // since during instantiation phase the memory should only be created - // and not yet accessed. - let wasmer_memory = wasmer_memory_ref - .buffer - .try_borrow_mut() - .map_err(|_| InstantiationError::EnvironmentDefinitionCorrupted)? - .clone(); - - exports.insert(import.name(), wasmer::Extern::Memory(wasmer_memory)); - }, - - wasmer::ExternType::Function(func_ty) => { - let guest_func_index = - guest_env.imports.func_by_name(import.module(), import.name()); - - let guest_func_index = if let Some(index) = guest_func_index { - index - } else { - // Missing import (should we abort here?) - continue - }; - - let supervisor_func_index = guest_env - .guest_to_supervisor_mapping - .func_by_guest_index(guest_func_index) - .ok_or(InstantiationError::ModuleDecoding)?; - - let function = - dispatch_function(supervisor_func_index, &context.store, func_ty, state); - - let exports = exports_map - .entry(import.module().to_string()) - .or_insert_with(wasmer::Exports::new); - - exports.insert(import.name(), wasmer::Extern::Function(function)); - }, - } - } - - let mut import_object = wasmer::ImportObject::new(); - for (module_name, exports) in exports_map.into_iter() { - import_object.register(module_name, exports); - } - - let instance = SandboxContextStore::using(sandbox_context, || { - wasmer::Instance::new(&module, &import_object).map_err(|error| match error { - wasmer::InstantiationError::Link(_) => InstantiationError::Instantiation, - wasmer::InstantiationError::Start(_) => InstantiationError::StartTrapped, - wasmer::InstantiationError::HostEnvInitialization(_) => - InstantiationError::EnvironmentDefinitionCorrupted, - wasmer::InstantiationError::CpuFeature(_) => InstantiationError::CpuFeature, - }) - })?; - - Ok(Rc::new(SandboxInstance { - backend_instance: BackendInstance::Wasmer(instance), - guest_to_supervisor_mapping: guest_env.guest_to_supervisor_mapping, - })) -} - -fn dispatch_function( - supervisor_func_index: SupervisorFuncIndex, - store: &wasmer::Store, - func_ty: &wasmer::FunctionType, - state: u32, -) -> wasmer::Function { - wasmer::Function::new(store, func_ty, move |params| { - SandboxContextStore::with(|sandbox_context| { - // Serialize arguments into a byte vector. - let invoke_args_data = params - .iter() - .map(|val| match val { - wasmer::Val::I32(val) => Ok(Value::I32(*val)), - wasmer::Val::I64(val) => Ok(Value::I64(*val)), - wasmer::Val::F32(val) => Ok(Value::F32(f32::to_bits(*val))), - wasmer::Val::F64(val) => Ok(Value::F64(f64::to_bits(*val))), - _ => - Err(RuntimeError::new(format!("Unsupported function argument: {:?}", val))), - }) - .collect::, _>>()? - .encode(); - - // Move serialized arguments inside the memory, invoke dispatch thunk and - // then free allocated memory. - let invoke_args_len = invoke_args_data.len() as WordSize; - let invoke_args_ptr = - sandbox_context.supervisor_context().allocate_memory(invoke_args_len).map_err( - |_| RuntimeError::new("Can't allocate memory in supervisor for the arguments"), - )?; - - let deallocate = |fe: &mut dyn FunctionContext, ptr, fail_msg| { - fe.deallocate_memory(ptr).map_err(|_| RuntimeError::new(fail_msg)) - }; - - if sandbox_context - .supervisor_context() - .write_memory(invoke_args_ptr, &invoke_args_data) - .is_err() - { - deallocate( - sandbox_context.supervisor_context(), - invoke_args_ptr, - "Failed dealloction after failed write of invoke arguments", - )?; - - return Err(RuntimeError::new("Can't write invoke args into memory")) - } - - // Perform the actuall call - let serialized_result = sandbox_context - .invoke(invoke_args_ptr, invoke_args_len, state, supervisor_func_index) - .map_err(|e| RuntimeError::new(e.to_string())); - - deallocate( - sandbox_context.supervisor_context(), - invoke_args_ptr, - "Failed dealloction after invoke", - )?; - - let serialized_result = serialized_result?; - - // dispatch_thunk returns pointer to serialized arguments. - // Unpack pointer and len of the serialized result data. - let (serialized_result_val_ptr, serialized_result_val_len) = { - // Cast to u64 to use zero-extension. - let v = serialized_result as u64; - let ptr = (v as u64 >> 32) as u32; - let len = (v & 0xFFFFFFFF) as u32; - (Pointer::new(ptr), len) - }; - - let serialized_result_val = sandbox_context - .supervisor_context() - .read_memory(serialized_result_val_ptr, serialized_result_val_len) - .map_err(|_| { - RuntimeError::new("Can't read the serialized result from dispatch thunk") - }); - - deallocate( - sandbox_context.supervisor_context(), - serialized_result_val_ptr, - "Can't deallocate memory for dispatch thunk's result", - )?; - - let serialized_result_val = serialized_result_val?; - - let deserialized_result = std::result::Result::::decode( - &mut serialized_result_val.as_slice(), - ) - .map_err(|_| RuntimeError::new("Decoding Result failed!"))? - .map_err(|_| RuntimeError::new("Supervisor function returned sandbox::HostError"))?; - - let result = match deserialized_result { - ReturnValue::Value(Value::I32(val)) => vec![wasmer::Val::I32(val)], - ReturnValue::Value(Value::I64(val)) => vec![wasmer::Val::I64(val)], - ReturnValue::Value(Value::F32(val)) => vec![wasmer::Val::F32(f32::from_bits(val))], - ReturnValue::Value(Value::F64(val)) => vec![wasmer::Val::F64(f64::from_bits(val))], - - ReturnValue::Unit => vec![], - }; - - Ok(result) - }) - .expect("SandboxContextStore is set when invoking sandboxed functions; qed") - }) -} - -/// Allocate new memory region -pub fn new_memory( - context: &Backend, - initial: u32, - maximum: Option, -) -> crate::error::Result { - let ty = wasmer::MemoryType::new(initial, maximum, false); - let memory = Memory::Wasmer(MemoryWrapper::new( - wasmer::Memory::new(&context.store, ty).map_err(|_| Error::InvalidMemoryReference)?, - )); - - Ok(memory) -} - -/// In order to enforce memory access protocol to the backend memory -/// we wrap it with `RefCell` and encapsulate all memory operations. -#[derive(Debug, Clone)] -pub struct MemoryWrapper { - buffer: Rc>, -} - -impl MemoryWrapper { - /// Take ownership of the memory region and return a wrapper object - pub fn new(memory: wasmer::Memory) -> Self { - Self { buffer: Rc::new(RefCell::new(memory)) } - } - - /// Returns linear memory of the wasm instance as a slice. - /// - /// # Safety - /// - /// Wasmer doesn't provide comprehensive documentation about the exact behavior of the data - /// pointer. If a dynamic style heap is used the base pointer of the heap can change. Since - /// growing, we cannot guarantee the lifetime of the returned slice reference. - unsafe fn memory_as_slice(memory: &wasmer::Memory) -> &[u8] { - let ptr = memory.data_ptr() as *const _; - - let len: usize = memory.data_size().try_into().expect( - "maximum memory object size never exceeds pointer size on any architecture; \ - usize by design and definition is enough to store any memory object size \ - possible on current achitecture; thus the conversion can not fail; qed", - ); - - if len == 0 { - &[] - } else { - core::slice::from_raw_parts(ptr, len) - } - } - - /// Returns linear memory of the wasm instance as a slice. - /// - /// # Safety - /// - /// See `[memory_as_slice]`. In addition to those requirements, since a mutable reference is - /// returned it must be ensured that only one mutable and no shared references to memory - /// exists at the same time. - unsafe fn memory_as_slice_mut(memory: &mut wasmer::Memory) -> &mut [u8] { - let ptr = memory.data_ptr(); - - let len: usize = memory.data_size().try_into().expect( - "maximum memory object size never exceeds pointer size on any architecture; \ - usize by design and definition is enough to store any memory object size \ - possible on current achitecture; thus the conversion can not fail; qed", - ); - - if len == 0 { - &mut [] - } else { - core::slice::from_raw_parts_mut(ptr, len) - } - } -} - -impl MemoryTransfer for MemoryWrapper { - fn read(&self, source_addr: Pointer, size: usize) -> Result> { - let memory = self.buffer.borrow(); - - let data_size: usize = memory.data_size().try_into().expect( - "maximum memory object size never exceeds pointer size on any architecture; \ - usize by design and definition is enough to store any memory object size \ - possible on current achitecture; thus the conversion can not fail; qed", - ); - - let range = checked_range(source_addr.into(), size, data_size) - .ok_or_else(|| Error::Other("memory read is out of bounds".into()))?; - - let mut buffer = vec![0; range.len()]; - self.read_into(source_addr, &mut buffer)?; - - Ok(buffer) - } - - fn read_into(&self, source_addr: Pointer, destination: &mut [u8]) -> Result<()> { - unsafe { - let memory = self.buffer.borrow(); - - // This should be safe since we don't grow up memory while caching this reference - // and we give up the reference before returning from this function. - let source = Self::memory_as_slice(&memory); - - let range = checked_range(source_addr.into(), destination.len(), source.len()) - .ok_or_else(|| Error::Other("memory read is out of bounds".into()))?; - - destination.copy_from_slice(&source[range]); - Ok(()) - } - } - - fn write_from(&self, dest_addr: Pointer, source: &[u8]) -> Result<()> { - unsafe { - let memory = &mut self.buffer.borrow_mut(); - - // This should be safe since we don't grow up memory while caching this reference - // and we give up the reference before returning from this function. - let destination = Self::memory_as_slice_mut(memory); - - let range = checked_range(dest_addr.into(), source.len(), destination.len()) - .ok_or_else(|| Error::Other("memory write is out of bounds".into()))?; - - destination[range].copy_from_slice(source); - Ok(()) - } - } -} - -/// Get global value by name -pub fn get_global(instance: &wasmer::Instance, name: &str) -> Option { - let global = instance.exports.get_global(name).ok()?; - let wasmtime_value = match global.get() { - wasmer::Val::I32(val) => Value::I32(val), - wasmer::Val::I64(val) => Value::I64(val), - wasmer::Val::F32(val) => Value::F32(f32::to_bits(val)), - wasmer::Val::F64(val) => Value::F64(f64::to_bits(val)), - _ => None?, - }; - - Some(wasmtime_value) -} diff --git a/client/executor/common/src/sandbox/wasmi_backend.rs b/client/executor/common/src/sandbox/wasmi_backend.rs deleted file mode 100644 index 2ba133f5f1..0000000000 --- a/client/executor/common/src/sandbox/wasmi_backend.rs +++ /dev/null @@ -1,339 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2019-2021 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 . - -//! Wasmi specific impls for sandbox - -use std::{fmt, rc::Rc}; - -use codec::{Decode, Encode}; -use sp_sandbox::HostError; -use sp_wasm_interface::{FunctionContext, Pointer, ReturnValue, Value, WordSize}; -use wasmi::{ - memory_units::Pages, ImportResolver, MemoryInstance, Module, ModuleInstance, RuntimeArgs, - RuntimeValue, Trap, -}; - -use crate::{ - error::{self, Error}, - sandbox::{ - BackendInstance, GuestEnvironment, GuestExternals, GuestFuncIndex, Imports, - InstantiationError, Memory, SandboxContext, SandboxInstance, - }, - util::{checked_range, MemoryTransfer}, -}; - -environmental::environmental!(SandboxContextStore: trait SandboxContext); - -#[derive(Debug)] -struct CustomHostError(String); - -impl fmt::Display for CustomHostError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "HostError: {}", self.0) - } -} - -impl wasmi::HostError for CustomHostError {} - -/// Construct trap error from specified message -fn trap(msg: &'static str) -> Trap { - Trap::host(CustomHostError(msg.into())) -} - -impl ImportResolver for Imports { - fn resolve_func( - &self, - module_name: &str, - field_name: &str, - signature: &wasmi::Signature, - ) -> std::result::Result { - let idx = self.func_by_name(module_name, field_name).ok_or_else(|| { - wasmi::Error::Instantiation(format!("Export {}:{} not found", module_name, field_name)) - })?; - - Ok(wasmi::FuncInstance::alloc_host(signature.clone(), idx.0)) - } - - fn resolve_memory( - &self, - module_name: &str, - field_name: &str, - _memory_type: &wasmi::MemoryDescriptor, - ) -> std::result::Result { - let mem = self.memory_by_name(module_name, field_name).ok_or_else(|| { - wasmi::Error::Instantiation(format!("Export {}:{} not found", module_name, field_name)) - })?; - - let wrapper = mem.as_wasmi().ok_or_else(|| { - wasmi::Error::Instantiation(format!( - "Unsupported non-wasmi export {}:{}", - module_name, field_name - )) - })?; - - // Here we use inner memory reference only to resolve the imports - // without accessing the memory contents. All subsequent memory accesses - // should happen through the wrapper, that enforces the memory access protocol. - let mem = wrapper.0; - - Ok(mem) - } - - fn resolve_global( - &self, - module_name: &str, - field_name: &str, - _global_type: &wasmi::GlobalDescriptor, - ) -> std::result::Result { - Err(wasmi::Error::Instantiation(format!("Export {}:{} not found", module_name, field_name))) - } - - fn resolve_table( - &self, - module_name: &str, - field_name: &str, - _table_type: &wasmi::TableDescriptor, - ) -> std::result::Result { - Err(wasmi::Error::Instantiation(format!("Export {}:{} not found", module_name, field_name))) - } -} - -/// Allocate new memory region -pub fn new_memory(initial: u32, maximum: Option) -> crate::error::Result { - let memory = Memory::Wasmi(MemoryWrapper::new( - MemoryInstance::alloc(Pages(initial as usize), maximum.map(|m| Pages(m as usize))) - .map_err(|error| Error::Sandbox(error.to_string()))?, - )); - - Ok(memory) -} - -/// Wasmi provides direct access to its memory using slices. -/// -/// This wrapper limits the scope where the slice can be taken to -#[derive(Debug, Clone)] -pub struct MemoryWrapper(wasmi::MemoryRef); - -impl MemoryWrapper { - /// Take ownership of the memory region and return a wrapper object - fn new(memory: wasmi::MemoryRef) -> Self { - Self(memory) - } -} - -impl MemoryTransfer for MemoryWrapper { - fn read(&self, source_addr: Pointer, size: usize) -> error::Result> { - self.0.with_direct_access(|source| { - let range = checked_range(source_addr.into(), size, source.len()) - .ok_or_else(|| error::Error::Other("memory read is out of bounds".into()))?; - - Ok(Vec::from(&source[range])) - }) - } - - fn read_into(&self, source_addr: Pointer, destination: &mut [u8]) -> error::Result<()> { - self.0.with_direct_access(|source| { - let range = checked_range(source_addr.into(), destination.len(), source.len()) - .ok_or_else(|| error::Error::Other("memory read is out of bounds".into()))?; - - destination.copy_from_slice(&source[range]); - Ok(()) - }) - } - - fn write_from(&self, dest_addr: Pointer, source: &[u8]) -> error::Result<()> { - self.0.with_direct_access_mut(|destination| { - let range = checked_range(dest_addr.into(), source.len(), destination.len()) - .ok_or_else(|| error::Error::Other("memory write is out of bounds".into()))?; - - destination[range].copy_from_slice(source); - Ok(()) - }) - } -} - -impl<'a> wasmi::Externals for GuestExternals<'a> { - fn invoke_index( - &mut self, - index: usize, - args: RuntimeArgs, - ) -> std::result::Result, Trap> { - SandboxContextStore::with(|sandbox_context| { - // Make `index` typesafe again. - let index = GuestFuncIndex(index); - - // Convert function index from guest to supervisor space - let func_idx = self.sandbox_instance - .guest_to_supervisor_mapping - .func_by_guest_index(index) - .expect( - "`invoke_index` is called with indexes registered via `FuncInstance::alloc_host`; - `FuncInstance::alloc_host` is called with indexes that were obtained from `guest_to_supervisor_mapping`; - `func_by_guest_index` called with `index` can't return `None`; - qed" - ); - - // Serialize arguments into a byte vector. - let invoke_args_data: Vec = args - .as_ref() - .iter() - .cloned() - .map(sp_wasm_interface::Value::from) - .collect::>() - .encode(); - - let state = self.state; - - // Move serialized arguments inside the memory, invoke dispatch thunk and - // then free allocated memory. - let invoke_args_len = invoke_args_data.len() as WordSize; - let invoke_args_ptr = sandbox_context - .supervisor_context() - .allocate_memory(invoke_args_len) - .map_err(|_| trap("Can't allocate memory in supervisor for the arguments"))?; - - let deallocate = |supervisor_context: &mut dyn FunctionContext, ptr, fail_msg| { - supervisor_context.deallocate_memory(ptr).map_err(|_| trap(fail_msg)) - }; - - if sandbox_context - .supervisor_context() - .write_memory(invoke_args_ptr, &invoke_args_data) - .is_err() - { - deallocate( - sandbox_context.supervisor_context(), - invoke_args_ptr, - "Failed dealloction after failed write of invoke arguments", - )?; - return Err(trap("Can't write invoke args into memory")) - } - - let result = sandbox_context.invoke( - invoke_args_ptr, - invoke_args_len, - state, - func_idx, - ); - - deallocate( - sandbox_context.supervisor_context(), - invoke_args_ptr, - "Can't deallocate memory for dispatch thunk's invoke arguments", - )?; - let result = result?; - - // dispatch_thunk returns pointer to serialized arguments. - // Unpack pointer and len of the serialized result data. - let (serialized_result_val_ptr, serialized_result_val_len) = { - // Cast to u64 to use zero-extension. - let v = result as u64; - let ptr = (v as u64 >> 32) as u32; - let len = (v & 0xFFFFFFFF) as u32; - (Pointer::new(ptr), len) - }; - - let serialized_result_val = sandbox_context - .supervisor_context() - .read_memory(serialized_result_val_ptr, serialized_result_val_len) - .map_err(|_| trap("Can't read the serialized result from dispatch thunk")); - - deallocate( - sandbox_context.supervisor_context(), - serialized_result_val_ptr, - "Can't deallocate memory for dispatch thunk's result", - ) - .and(serialized_result_val) - .and_then(|serialized_result_val| { - let result_val = std::result::Result::::decode(&mut serialized_result_val.as_slice()) - .map_err(|_| trap("Decoding Result failed!"))?; - - match result_val { - Ok(return_value) => Ok(match return_value { - ReturnValue::Unit => None, - ReturnValue::Value(typed_value) => Some(RuntimeValue::from(typed_value)), - }), - Err(HostError) => Err(trap("Supervisor function returned sandbox::HostError")), - } - }) - }).expect("SandboxContextStore is set when invoking sandboxed functions; qed") - } -} - -fn with_guest_externals(sandbox_instance: &SandboxInstance, state: u32, f: F) -> R -where - F: FnOnce(&mut GuestExternals) -> R, -{ - f(&mut GuestExternals { sandbox_instance, state }) -} - -/// Instantiate a module within a sandbox context -pub fn instantiate( - wasm: &[u8], - guest_env: GuestEnvironment, - state: u32, - sandbox_context: &mut dyn SandboxContext, -) -> std::result::Result, InstantiationError> { - let wasmi_module = Module::from_buffer(wasm).map_err(|_| InstantiationError::ModuleDecoding)?; - let wasmi_instance = ModuleInstance::new(&wasmi_module, &guest_env.imports) - .map_err(|_| InstantiationError::Instantiation)?; - - let sandbox_instance = Rc::new(SandboxInstance { - // In general, it's not a very good idea to use `.not_started_instance()` for - // anything but for extracting memory and tables. But in this particular case, we - // are extracting for the purpose of running `start` function which should be ok. - backend_instance: BackendInstance::Wasmi(wasmi_instance.not_started_instance().clone()), - guest_to_supervisor_mapping: guest_env.guest_to_supervisor_mapping, - }); - - with_guest_externals(&sandbox_instance, state, |guest_externals| { - SandboxContextStore::using(sandbox_context, || { - wasmi_instance - .run_start(guest_externals) - .map_err(|_| InstantiationError::StartTrapped) - }) - })?; - - Ok(sandbox_instance) -} - -/// Invoke a function within a sandboxed module -pub fn invoke( - instance: &SandboxInstance, - module: &wasmi::ModuleRef, - export_name: &str, - args: &[Value], - state: u32, - sandbox_context: &mut dyn SandboxContext, -) -> std::result::Result, error::Error> { - with_guest_externals(instance, state, |guest_externals| { - SandboxContextStore::using(sandbox_context, || { - let args = args.iter().cloned().map(Into::into).collect::>(); - - module - .invoke_export(export_name, &args, guest_externals) - .map(|result| result.map(Into::into)) - .map_err(|error| error::Error::Sandbox(error.to_string())) - }) - }) -} - -/// Get global value by name -pub fn get_global(instance: &wasmi::ModuleRef, name: &str) -> Option { - Some(instance.export_by_name(name)?.as_global()?.get().into()) -} diff --git a/client/executor/runtime-test/Cargo.toml b/client/executor/runtime-test/Cargo.toml index c8b173de16..eadf547823 100644 --- a/client/executor/runtime-test/Cargo.toml +++ b/client/executor/runtime-test/Cargo.toml @@ -13,11 +13,9 @@ repository = "https://github.com/paritytech/substrate/" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -paste = "1.0.6" sp-core = { version = "7.0.0", default-features = false, path = "../../../primitives/core" } sp-io = { version = "7.0.0", default-features = false, features = ["improved_panic_error_reporting"], path = "../../../primitives/io" } sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } -sp-sandbox = { version = "0.10.0-dev", default-features = false, path = "../../../primitives/sandbox" } sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } [build-dependencies] @@ -29,6 +27,5 @@ std = [ "sp-core/std", "sp-io/std", "sp-runtime/std", - "sp-sandbox/std", "sp-std/std", ] diff --git a/client/executor/runtime-test/src/lib.rs b/client/executor/runtime-test/src/lib.rs index 0424ad4186..fc98d1909d 100644 --- a/client/executor/runtime-test/src/lib.rs +++ b/client/executor/runtime-test/src/lib.rs @@ -29,8 +29,6 @@ use sp_runtime::{ print, traits::{BlakeTwo256, Hash}, }; -#[cfg(not(feature = "std"))] -use sp_sandbox::{SandboxEnvironmentBuilder, SandboxInstance, SandboxMemory, Value}; extern "C" { #[allow(dead_code)] @@ -339,160 +337,3 @@ sp_core::wasm_export_functions! { return 1234; } } - -/// A macro to define a test entrypoint for each available sandbox executor. -macro_rules! wasm_export_sandbox_test_functions { - ( - $( - fn $name:ident( - $( $arg_name:ident: $arg_ty:ty ),* $(,)? - ) $( -> $ret_ty:ty )? where T: SandboxInstance<$state:ty> $(,)? - { $( $fn_impl:tt )* } - )* - ) => { - $( - #[cfg(not(feature = "std"))] - fn $name( $($arg_name: $arg_ty),* ) $( -> $ret_ty )? where T: SandboxInstance<$state> { - $( $fn_impl )* - } - - paste::paste! { - sp_core::wasm_export_functions! { - fn [<$name _host>]( $($arg_name: $arg_ty),* ) $( -> $ret_ty )? { - $name::>( $( $arg_name ),* ) - } - - fn [<$name _embedded>]( $($arg_name: $arg_ty),* ) $( -> $ret_ty )? { - $name::>( $( $arg_name ),* ) - } - } - } - )* - }; -} - -wasm_export_sandbox_test_functions! { - fn test_sandbox(code: Vec) -> bool - where - T: SandboxInstance, - { - execute_sandboxed::(&code, &[]).is_ok() - } - - fn test_sandbox_args(code: Vec) -> bool - where - T: SandboxInstance, - { - execute_sandboxed::(&code, &[Value::I32(0x12345678), Value::I64(0x1234567887654321)]) - .is_ok() - } - - fn test_sandbox_return_val(code: Vec) -> bool - where - T: SandboxInstance, - { - let ok = match execute_sandboxed::(&code, &[Value::I32(0x1336)]) { - Ok(sp_sandbox::ReturnValue::Value(Value::I32(0x1337))) => true, - _ => false, - }; - - ok - } - - fn test_sandbox_instantiate(code: Vec) -> u8 - where - T: SandboxInstance<()>, - { - let env_builder = T::EnvironmentBuilder::new(); - let code = match T::new(&code, &env_builder, &mut ()) { - Ok(_) => 0, - Err(sp_sandbox::Error::Module) => 1, - Err(sp_sandbox::Error::Execution) => 2, - Err(sp_sandbox::Error::OutOfBounds) => 3, - }; - - code - } - - fn test_sandbox_get_global_val(code: Vec) -> i64 - where - T: SandboxInstance<()>, - { - let env_builder = T::EnvironmentBuilder::new(); - let instance = if let Ok(i) = T::new(&code, &env_builder, &mut ()) { - i - } else { - return 20 - }; - - match instance.get_global_val("test_global") { - Some(sp_sandbox::Value::I64(val)) => val, - None => 30, - _ => 40, - } - } -} - -#[cfg(not(feature = "std"))] -struct State { - counter: u32, -} - -#[cfg(not(feature = "std"))] -fn execute_sandboxed( - code: &[u8], - args: &[Value], -) -> Result -where - T: sp_sandbox::SandboxInstance, -{ - fn env_assert( - _e: &mut State, - args: &[Value], - ) -> Result { - if args.len() != 1 { - return Err(sp_sandbox::HostError) - } - let condition = args[0].as_i32().ok_or_else(|| sp_sandbox::HostError)?; - if condition != 0 { - Ok(sp_sandbox::ReturnValue::Unit) - } else { - Err(sp_sandbox::HostError) - } - } - fn env_inc_counter( - e: &mut State, - args: &[Value], - ) -> Result { - if args.len() != 1 { - return Err(sp_sandbox::HostError) - } - let inc_by = args[0].as_i32().ok_or_else(|| sp_sandbox::HostError)?; - e.counter += inc_by as u32; - Ok(sp_sandbox::ReturnValue::Value(Value::I32(e.counter as i32))) - } - - let mut state = State { counter: 0 }; - - let env_builder = { - let mut env_builder = T::EnvironmentBuilder::new(); - env_builder.add_host_func("env", "assert", env_assert); - env_builder.add_host_func("env", "inc_counter", env_inc_counter); - let memory = match T::Memory::new(1, Some(16)) { - Ok(m) => m, - Err(_) => unreachable!( - " - Memory::new() can return Err only if parameters are borked; \ - We passing params here explicitly and they're correct; \ - Memory::new() can't return a Error qed" - ), - }; - env_builder.add_memory("env", "memory", memory); - env_builder - }; - - let mut instance = T::new(code, &env_builder, &mut state)?; - let result = instance.invoke("call", args, &mut state); - - result.map_err(|_| sp_sandbox::HostError) -} diff --git a/client/executor/src/integration_tests/mod.rs b/client/executor/src/integration_tests/mod.rs index 9b5c4b12fc..25b999f115 100644 --- a/client/executor/src/integration_tests/mod.rs +++ b/client/executor/src/integration_tests/mod.rs @@ -18,7 +18,6 @@ #[cfg(target_os = "linux")] mod linux; -mod sandbox; use codec::{Decode, Encode}; use sc_executor_common::{error::Error, runtime_blob::RuntimeBlob, wasm_runtime::WasmModule}; @@ -98,111 +97,6 @@ macro_rules! test_wasm_execution { }; } -/// A macro to run a given test for each available WASM execution method *and* for each -/// sandbox execution method. -#[macro_export] -macro_rules! test_wasm_execution_sandbox { - ($method_name:ident) => { - paste::item! { - #[test] - fn [<$method_name _interpreted_host_executor>]() { - $method_name(WasmExecutionMethod::Interpreted, "_host"); - } - - #[test] - fn [<$method_name _interpreted_embedded_executor>]() { - $method_name(WasmExecutionMethod::Interpreted, "_embedded"); - } - - #[test] - fn [<$method_name _compiled_pooling_cow_host_executor>]() { - $method_name(WasmExecutionMethod::Compiled { - instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::PoolingCopyOnWrite - }, "_host"); - } - - #[test] - fn [<$method_name _compiled_pooling_cow_embedded_executor>]() { - $method_name(WasmExecutionMethod::Compiled { - instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::PoolingCopyOnWrite - }, "_embedded"); - } - - #[test] - fn [<$method_name _compiled_pooling_vanilla_host_executor>]() { - $method_name(WasmExecutionMethod::Compiled { - instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::Pooling - }, "_host"); - } - - #[test] - fn [<$method_name _compiled_pooling_vanilla_embedded_executor>]() { - $method_name(WasmExecutionMethod::Compiled { - instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::Pooling - }, "_embedded"); - } - - #[test] - fn [<$method_name _compiled_recreate_instance_cow_host_executor>]() { - $method_name(WasmExecutionMethod::Compiled { - instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::RecreateInstanceCopyOnWrite - }, "_host"); - } - - #[test] - fn [<$method_name _compiled_recreate_instance_cow_embedded_executor>]() { - $method_name(WasmExecutionMethod::Compiled { - instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::RecreateInstanceCopyOnWrite - }, "_embedded"); - } - - #[test] - fn [<$method_name _compiled_recreate_instance_vanilla_host_executor>]() { - $method_name(WasmExecutionMethod::Compiled { - instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::RecreateInstance - }, "_host"); - } - - #[test] - fn [<$method_name _compiled_recreate_instance_vanilla_embedded_executor>]() { - $method_name(WasmExecutionMethod::Compiled { - instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::RecreateInstance - }, "_embedded"); - } - - #[test] - fn [<$method_name _compiled_legacy_instance_reuse_host_executor>]() { - $method_name(WasmExecutionMethod::Compiled { - instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::LegacyInstanceReuse - }, "_host"); - } - - #[test] - fn [<$method_name _compiled_legacy_instance_reuse_embedded_executor>]() { - $method_name(WasmExecutionMethod::Compiled { - instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::LegacyInstanceReuse - }, "_embedded"); - } - } - }; - - (interpreted_only $method_name:ident) => { - paste::item! { - #[test] - fn [<$method_name _interpreted_host_executor>]() { - $method_name(WasmExecutionMethod::Interpreted, "_host"); - } - } - - paste::item! { - #[test] - fn [<$method_name _interpreted_embedded_executor>]() { - $method_name(WasmExecutionMethod::Interpreted, "_embedded"); - } - } - }; -} - fn call_in_wasm( function: &str, call_data: &[u8], diff --git a/client/executor/src/integration_tests/sandbox.rs b/client/executor/src/integration_tests/sandbox.rs deleted file mode 100644 index 643db5097c..0000000000 --- a/client/executor/src/integration_tests/sandbox.rs +++ /dev/null @@ -1,339 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2018-2022 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 . - -use super::{call_in_wasm, TestExternalities}; -use crate::{test_wasm_execution_sandbox, WasmExecutionMethod}; - -use codec::Encode; - -test_wasm_execution_sandbox!(sandbox_should_work); -fn sandbox_should_work(wasm_method: WasmExecutionMethod, fn_suffix: &str) { - let mut ext = TestExternalities::default(); - let mut ext = ext.ext(); - - let code = wat::parse_str( - r#" - (module - (import "env" "assert" (func $assert (param i32))) - (import "env" "inc_counter" (func $inc_counter (param i32) (result i32))) - (func (export "call") - (drop - (call $inc_counter (i32.const 5)) - ) - - (call $inc_counter (i32.const 3)) - ;; current counter value is on the stack - - ;; check whether current == 8 - i32.const 8 - i32.eq - - call $assert - ) - ) - "#, - ) - .unwrap() - .encode(); - - assert_eq!( - call_in_wasm(&format!("test_sandbox{}", fn_suffix), &code, wasm_method, &mut ext).unwrap(), - true.encode() - ); -} - -test_wasm_execution_sandbox!(sandbox_trap); -fn sandbox_trap(wasm_method: WasmExecutionMethod, fn_suffix: &str) { - let mut ext = TestExternalities::default(); - let mut ext = ext.ext(); - - let code = wat::parse_str( - r#" - (module - (import "env" "assert" (func $assert (param i32))) - (func (export "call") - i32.const 0 - call $assert - ) - ) - "#, - ) - .unwrap(); - - assert_eq!( - call_in_wasm(&format!("test_sandbox{}", fn_suffix), &code, wasm_method, &mut ext).unwrap(), - vec![0] - ); -} - -test_wasm_execution_sandbox!(start_called); -fn start_called(wasm_method: WasmExecutionMethod, fn_suffix: &str) { - let mut ext = TestExternalities::default(); - let mut ext = ext.ext(); - - let code = wat::parse_str( - r#" - (module - (import "env" "assert" (func $assert (param i32))) - (import "env" "inc_counter" (func $inc_counter (param i32) (result i32))) - - ;; Start function - (start $start) - (func $start - ;; Increment counter by 1 - (drop - (call $inc_counter (i32.const 1)) - ) - ) - - (func (export "call") - ;; Increment counter by 1. The current value is placed on the stack. - (call $inc_counter (i32.const 1)) - - ;; Counter is incremented twice by 1, once there and once in `start` func. - ;; So check the returned value is equal to 2. - i32.const 2 - i32.eq - call $assert - ) - ) - "#, - ) - .unwrap() - .encode(); - - assert_eq!( - call_in_wasm(&format!("test_sandbox{}", fn_suffix), &code, wasm_method, &mut ext).unwrap(), - true.encode() - ); -} - -test_wasm_execution_sandbox!(invoke_args); -fn invoke_args(wasm_method: WasmExecutionMethod, fn_suffix: &str) { - let mut ext = TestExternalities::default(); - let mut ext = ext.ext(); - - let code = wat::parse_str( - r#" - (module - (import "env" "assert" (func $assert (param i32))) - - (func (export "call") (param $x i32) (param $y i64) - ;; assert that $x = 0x12345678 - (call $assert - (i32.eq - (get_local $x) - (i32.const 0x12345678) - ) - ) - - (call $assert - (i64.eq - (get_local $y) - (i64.const 0x1234567887654321) - ) - ) - ) - ) - "#, - ) - .unwrap() - .encode(); - - assert_eq!( - call_in_wasm(&format!("test_sandbox_args{}", fn_suffix), &code, wasm_method, &mut ext,) - .unwrap(), - true.encode(), - ); -} - -test_wasm_execution_sandbox!(return_val); -fn return_val(wasm_method: WasmExecutionMethod, fn_suffix: &str) { - let mut ext = TestExternalities::default(); - let mut ext = ext.ext(); - - let code = wat::parse_str( - r#" - (module - (func (export "call") (param $x i32) (result i32) - (i32.add - (get_local $x) - (i32.const 1) - ) - ) - ) - "#, - ) - .unwrap() - .encode(); - - assert_eq!( - call_in_wasm( - &format!("test_sandbox_return_val{}", fn_suffix), - &code, - wasm_method, - &mut ext, - ) - .unwrap(), - true.encode(), - ); -} - -test_wasm_execution_sandbox!(unlinkable_module); -fn unlinkable_module(wasm_method: WasmExecutionMethod, fn_suffix: &str) { - let mut ext = TestExternalities::default(); - let mut ext = ext.ext(); - - let code = wat::parse_str( - r#" - (module - (import "env" "non-existent" (func)) - - (func (export "call") - ) - ) - "#, - ) - .unwrap() - .encode(); - - assert_eq!( - call_in_wasm( - &format!("test_sandbox_instantiate{}", fn_suffix), - &code, - wasm_method, - &mut ext, - ) - .unwrap(), - 1u8.encode(), - ); -} - -test_wasm_execution_sandbox!(corrupted_module); -fn corrupted_module(wasm_method: WasmExecutionMethod, fn_suffix: &str) { - let mut ext = TestExternalities::default(); - let mut ext = ext.ext(); - - // Corrupted wasm file - let code = vec![0u8, 0, 0, 0, 1, 0, 0, 0].encode(); - - assert_eq!( - call_in_wasm( - &format!("test_sandbox_instantiate{}", fn_suffix), - &code, - wasm_method, - &mut ext, - ) - .unwrap(), - 1u8.encode(), - ); -} - -test_wasm_execution_sandbox!(start_fn_ok); -fn start_fn_ok(wasm_method: WasmExecutionMethod, fn_suffix: &str) { - let mut ext = TestExternalities::default(); - let mut ext = ext.ext(); - - let code = wat::parse_str( - r#" - (module - (func (export "call") - ) - - (func $start - ) - - (start $start) - ) - "#, - ) - .unwrap() - .encode(); - - assert_eq!( - call_in_wasm( - &format!("test_sandbox_instantiate{}", fn_suffix), - &code, - wasm_method, - &mut ext, - ) - .unwrap(), - 0u8.encode(), - ); -} - -test_wasm_execution_sandbox!(start_fn_traps); -fn start_fn_traps(wasm_method: WasmExecutionMethod, fn_suffix: &str) { - let mut ext = TestExternalities::default(); - let mut ext = ext.ext(); - - let code = wat::parse_str( - r#" - (module - (func (export "call") - ) - - (func $start - unreachable - ) - - (start $start) - ) - "#, - ) - .unwrap() - .encode(); - - assert_eq!( - call_in_wasm( - &format!("test_sandbox_instantiate{}", fn_suffix), - &code, - wasm_method, - &mut ext, - ) - .unwrap(), - 2u8.encode(), - ); -} - -test_wasm_execution_sandbox!(get_global_val_works); -fn get_global_val_works(wasm_method: WasmExecutionMethod, fn_suffix: &str) { - let mut ext = TestExternalities::default(); - let mut ext = ext.ext(); - - let code = wat::parse_str( - r#" - (module - (global (export "test_global") i64 (i64.const 500)) - ) - "#, - ) - .unwrap() - .encode(); - - assert_eq!( - call_in_wasm( - &format!("test_sandbox_get_global_val{}", fn_suffix), - &code, - wasm_method, - &mut ext, - ) - .unwrap(), - 500i64.encode(), - ); -} diff --git a/client/executor/src/lib.rs b/client/executor/src/lib.rs index 1fb041c358..0670b84094 100644 --- a/client/executor/src/lib.rs +++ b/client/executor/src/lib.rs @@ -49,7 +49,7 @@ pub use sp_wasm_interface; pub use wasm_runtime::{read_embedded_version, WasmExecutionMethod}; pub use wasmi; -pub use sc_executor_common::{error, sandbox}; +pub use sc_executor_common::error; pub use sc_executor_wasmtime::InstantiationStrategy as WasmtimeInstantiationStrategy; /// Extracts the runtime version of a given runtime code. diff --git a/client/executor/wasmi/Cargo.toml b/client/executor/wasmi/Cargo.toml index ef01f37841..4235440023 100644 --- a/client/executor/wasmi/Cargo.toml +++ b/client/executor/wasmi/Cargo.toml @@ -14,11 +14,9 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.0.0" } log = "0.4.17" wasmi = "0.13" sc-allocator = { version = "4.1.0-dev", path = "../../allocator" } sc-executor-common = { version = "0.10.0-dev", path = "../common" } sp-runtime-interface = { version = "7.0.0", path = "../../../primitives/runtime-interface" } -sp-sandbox = { version = "0.10.0-dev", path = "../../../primitives/sandbox" } sp-wasm-interface = { version = "7.0.0", path = "../../../primitives/wasm-interface" } diff --git a/client/executor/wasmi/src/lib.rs b/client/executor/wasmi/src/lib.rs index 1284cc23e4..6eb3814694 100644 --- a/client/executor/wasmi/src/lib.rs +++ b/client/executor/wasmi/src/lib.rs @@ -18,7 +18,7 @@ //! This crate provides an implementation of `WasmModule` that is baked by wasmi. -use std::{cell::RefCell, rc::Rc, str, sync::Arc}; +use std::{cell::RefCell, str, sync::Arc}; use log::{debug, error, trace}; use wasmi::{ @@ -28,26 +28,18 @@ use wasmi::{ TableRef, }; -use codec::{Decode, Encode}; use sc_allocator::AllocationStats; use sc_executor_common::{ error::{Error, MessageWithBacktrace, WasmError}, runtime_blob::{DataSegmentsSnapshot, RuntimeBlob}, - sandbox, - util::MemoryTransfer, wasm_runtime::{InvokeMethod, WasmInstance, WasmModule}, }; use sp_runtime_interface::unpack_ptr_and_len; -use sp_sandbox::env as sandbox_env; -use sp_wasm_interface::{ - Function, FunctionContext, MemoryId, Pointer, Result as WResult, Sandbox, WordSize, -}; +use sp_wasm_interface::{Function, FunctionContext, Pointer, Result as WResult, WordSize}; struct FunctionExecutor { - sandbox_store: Rc>>, heap: RefCell, memory: MemoryRef, - table: Option, host_functions: Arc>, allow_missing_func_imports: bool, missing_functions: Arc>, @@ -58,18 +50,13 @@ impl FunctionExecutor { fn new( m: MemoryRef, heap_base: u32, - t: Option, host_functions: Arc>, allow_missing_func_imports: bool, missing_functions: Arc>, ) -> Result { Ok(FunctionExecutor { - sandbox_store: Rc::new(RefCell::new(sandbox::Store::new( - sandbox::SandboxBackend::Wasmi, - ))), heap: RefCell::new(sc_allocator::FreeingBumpHeapAllocator::new(heap_base)), memory: m, - table: t, host_functions, allow_missing_func_imports, missing_functions, @@ -78,42 +65,6 @@ impl FunctionExecutor { } } -struct SandboxContext<'a> { - executor: &'a mut FunctionExecutor, - dispatch_thunk: wasmi::FuncRef, -} - -impl<'a> sandbox::SandboxContext for SandboxContext<'a> { - fn invoke( - &mut self, - invoke_args_ptr: Pointer, - invoke_args_len: WordSize, - state: u32, - func_idx: sandbox::SupervisorFuncIndex, - ) -> Result { - let result = wasmi::FuncInstance::invoke( - &self.dispatch_thunk, - &[ - RuntimeValue::I32(u32::from(invoke_args_ptr) as i32), - RuntimeValue::I32(invoke_args_len as i32), - RuntimeValue::I32(state as i32), - RuntimeValue::I32(usize::from(func_idx) as i32), - ], - self.executor, - ); - - match result { - Ok(Some(RuntimeValue::I64(val))) => Ok(val), - Ok(_) => Err("Supervisor function returned unexpected result!".into()), - Err(err) => Err(Error::Sandbox(err.to_string())), - } - } - - fn supervisor_context(&mut self) -> &mut dyn FunctionContext { - self.executor - } -} - impl FunctionContext for FunctionExecutor { fn read_memory_into(&self, address: Pointer, dest: &mut [u8]) -> WResult<()> { self.memory.get_into(address.into(), dest).map_err(|e| e.to_string()) @@ -135,189 +86,11 @@ impl FunctionContext for FunctionExecutor { .with_direct_access_mut(|mem| heap.deallocate(mem, ptr).map_err(|e| e.to_string())) } - fn sandbox(&mut self) -> &mut dyn Sandbox { - self - } - fn register_panic_error_message(&mut self, message: &str) { self.panic_message = Some(message.to_owned()); } } -impl Sandbox for FunctionExecutor { - fn memory_get( - &mut self, - memory_id: MemoryId, - offset: WordSize, - buf_ptr: Pointer, - buf_len: WordSize, - ) -> WResult { - let sandboxed_memory = - self.sandbox_store.borrow().memory(memory_id).map_err(|e| e.to_string())?; - - let len = buf_len as usize; - - let buffer = match sandboxed_memory.read(Pointer::new(offset as u32), len) { - Err(_) => return Ok(sandbox_env::ERR_OUT_OF_BOUNDS), - Ok(buffer) => buffer, - }; - - if self.memory.set(buf_ptr.into(), &buffer).is_err() { - return Ok(sandbox_env::ERR_OUT_OF_BOUNDS) - } - - Ok(sandbox_env::ERR_OK) - } - - fn memory_set( - &mut self, - memory_id: MemoryId, - offset: WordSize, - val_ptr: Pointer, - val_len: WordSize, - ) -> WResult { - let sandboxed_memory = - self.sandbox_store.borrow().memory(memory_id).map_err(|e| e.to_string())?; - - let len = val_len as usize; - - #[allow(deprecated)] - let buffer = match self.memory.get(val_ptr.into(), len) { - Err(_) => return Ok(sandbox_env::ERR_OUT_OF_BOUNDS), - Ok(buffer) => buffer, - }; - - if sandboxed_memory.write_from(Pointer::new(offset as u32), &buffer).is_err() { - return Ok(sandbox_env::ERR_OUT_OF_BOUNDS) - } - - Ok(sandbox_env::ERR_OK) - } - - fn memory_teardown(&mut self, memory_id: MemoryId) -> WResult<()> { - self.sandbox_store - .borrow_mut() - .memory_teardown(memory_id) - .map_err(|e| e.to_string()) - } - - fn memory_new(&mut self, initial: u32, maximum: u32) -> WResult { - self.sandbox_store - .borrow_mut() - .new_memory(initial, maximum) - .map_err(|e| e.to_string()) - } - - fn invoke( - &mut self, - instance_id: u32, - export_name: &str, - mut args: &[u8], - return_val: Pointer, - return_val_len: WordSize, - state: u32, - ) -> WResult { - trace!(target: "sp-sandbox", "invoke, instance_idx={}", instance_id); - - // Deserialize arguments and convert them into wasmi types. - let args = Vec::::decode(&mut args) - .map_err(|_| "Can't decode serialized arguments for the invocation")? - .into_iter() - .collect::>(); - - let instance = - self.sandbox_store.borrow().instance(instance_id).map_err(|e| e.to_string())?; - - let dispatch_thunk = self - .sandbox_store - .borrow() - .dispatch_thunk(instance_id) - .map_err(|e| e.to_string())?; - - match instance.invoke( - export_name, - &args, - state, - &mut SandboxContext { dispatch_thunk, executor: self }, - ) { - Ok(None) => Ok(sandbox_env::ERR_OK), - Ok(Some(val)) => { - // Serialize return value and write it back into the memory. - sp_wasm_interface::ReturnValue::Value(val).using_encoded(|val| { - if val.len() > return_val_len as usize { - return Err("Return value buffer is too small".into()) - } - self.write_memory(return_val, val).map_err(|_| "Return value buffer is OOB")?; - Ok(sandbox_env::ERR_OK) - }) - }, - Err(_) => Ok(sandbox_env::ERR_EXECUTION), - } - } - - fn instance_teardown(&mut self, instance_id: u32) -> WResult<()> { - self.sandbox_store - .borrow_mut() - .instance_teardown(instance_id) - .map_err(|e| e.to_string()) - } - - fn instance_new( - &mut self, - dispatch_thunk_id: u32, - wasm: &[u8], - raw_env_def: &[u8], - state: u32, - ) -> WResult { - // Extract a dispatch thunk from instance's table by the specified index. - let dispatch_thunk = { - let table = self - .table - .as_ref() - .ok_or("Runtime doesn't have a table; sandbox is unavailable")?; - table - .get(dispatch_thunk_id) - .map_err(|_| "dispatch_thunk_idx is out of the table bounds")? - .ok_or("dispatch_thunk_idx points on an empty table entry")? - }; - - let guest_env = - match sandbox::GuestEnvironment::decode(&*self.sandbox_store.borrow(), raw_env_def) { - Ok(guest_env) => guest_env, - Err(_) => return Ok(sandbox_env::ERR_MODULE as u32), - }; - - let store = self.sandbox_store.clone(); - let result = store.borrow_mut().instantiate( - wasm, - guest_env, - state, - &mut SandboxContext { executor: self, dispatch_thunk: dispatch_thunk.clone() }, - ); - - let instance_idx_or_err_code = - match result.map(|i| i.register(&mut store.borrow_mut(), dispatch_thunk)) { - Ok(instance_idx) => instance_idx, - Err(sandbox::InstantiationError::StartTrapped) => sandbox_env::ERR_EXECUTION, - Err(_) => sandbox_env::ERR_MODULE, - }; - - Ok(instance_idx_or_err_code) - } - - fn get_global_val( - &self, - instance_idx: u32, - name: &str, - ) -> WResult> { - self.sandbox_store - .borrow() - .instance(instance_idx) - .map(|i| i.get_global_val(name)) - .map_err(|e| e.to_string()) - } -} - /// Will be used on initialization of a module to resolve function and memory imports. struct Resolver<'a> { /// All the hot functions that we export for the WASM blob. @@ -502,7 +275,6 @@ fn call_in_wasm_module( let mut function_executor = FunctionExecutor::new( memory.clone(), heap_base, - table.clone(), host_functions, allow_missing_func_imports, missing_functions, diff --git a/client/executor/wasmtime/Cargo.toml b/client/executor/wasmtime/Cargo.toml index b12ca0779e..7e38929f05 100644 --- a/client/executor/wasmtime/Cargo.toml +++ b/client/executor/wasmtime/Cargo.toml @@ -14,10 +14,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] cfg-if = "1.0" -codec = { package = "parity-scale-codec", version = "3.0.0" } libc = "0.2.121" log = "0.4.17" -parity-wasm = "0.45" # When bumping wasmtime do not forget to also bump rustix # to exactly the same version as used by wasmtime! @@ -32,7 +30,6 @@ wasmtime = { version = "1.0.0", default-features = false, features = [ sc-allocator = { version = "4.1.0-dev", path = "../../allocator" } sc-executor-common = { version = "0.10.0-dev", path = "../common" } sp-runtime-interface = { version = "7.0.0", path = "../../../primitives/runtime-interface" } -sp-sandbox = { version = "0.10.0-dev", path = "../../../primitives/sandbox" } sp-wasm-interface = { version = "7.0.0", path = "../../../primitives/wasm-interface" } # Here we include the rustix crate in the exactly same semver-compatible version as used by @@ -50,3 +47,4 @@ sc-runtime-test = { version = "2.0.0", path = "../runtime-test" } sp-io = { version = "7.0.0", path = "../../../primitives/io" } tempfile = "3.3.0" paste = "1.0" +codec = { package = "parity-scale-codec", version = "3.0.0" } diff --git a/client/executor/wasmtime/src/host.rs b/client/executor/wasmtime/src/host.rs index 768a6e36e2..0d9eac8751 100644 --- a/client/executor/wasmtime/src/host.rs +++ b/client/executor/wasmtime/src/host.rs @@ -19,33 +19,17 @@ //! This module defines `HostState` and `HostContext` structs which provide logic and state //! required for execution of host. -use log::trace; -use wasmtime::{Caller, Func, Val}; +use wasmtime::Caller; -use codec::{Decode, Encode}; use sc_allocator::{AllocationStats, FreeingBumpHeapAllocator}; -use sc_executor_common::{ - error::Result, - sandbox::{self, SupervisorFuncIndex}, - util::MemoryTransfer, -}; -use sp_sandbox::env as sandbox_env; -use sp_wasm_interface::{FunctionContext, MemoryId, Pointer, Sandbox, WordSize}; +use sp_wasm_interface::{Pointer, WordSize}; use crate::{runtime::StoreData, util}; -// The sandbox store is inside of a Option>> so that we can temporarily borrow it. -struct SandboxStore(Option>>); - -// There are a bunch of `Rc`s within the sandbox store, however we only manipulate -// those within one thread so this should be safe. -unsafe impl Send for SandboxStore {} - /// The state required to construct a HostContext context. The context only lasts for one host /// call, whereas the state is maintained for the duration of a Wasm runtime call, which may make /// many different host calls that must share state. pub struct HostState { - sandbox_store: SandboxStore, allocator: FreeingBumpHeapAllocator, panic_message: Option, } @@ -53,13 +37,7 @@ pub struct HostState { impl HostState { /// Constructs a new `HostState`. pub fn new(allocator: FreeingBumpHeapAllocator) -> Self { - HostState { - sandbox_store: SandboxStore(Some(Box::new(sandbox::Store::new( - sandbox::SandboxBackend::TryWasmer, - )))), - allocator, - panic_message: None, - } + HostState { allocator, panic_message: None } } /// Takes the error message out of the host state, leaving a `None` in its place. @@ -80,35 +58,12 @@ pub(crate) struct HostContext<'a> { } impl<'a> HostContext<'a> { - fn host_state(&self) -> &HostState { - self.caller - .data() - .host_state() - .expect("host state is not empty when calling a function in wasm; qed") - } - fn host_state_mut(&mut self) -> &mut HostState { self.caller .data_mut() .host_state_mut() .expect("host state is not empty when calling a function in wasm; qed") } - - fn sandbox_store(&self) -> &sandbox::Store { - self.host_state() - .sandbox_store - .0 - .as_ref() - .expect("sandbox store is only empty when temporarily borrowed") - } - - fn sandbox_store_mut(&mut self) -> &mut sandbox::Store { - self.host_state_mut() - .sandbox_store - .0 - .as_mut() - .expect("sandbox store is only empty when temporarily borrowed") - } } impl<'a> sp_wasm_interface::FunctionContext for HostContext<'a> { @@ -144,233 +99,7 @@ impl<'a> sp_wasm_interface::FunctionContext for HostContext<'a> { .map_err(|e| e.to_string()) } - fn sandbox(&mut self) -> &mut dyn Sandbox { - self - } - fn register_panic_error_message(&mut self, message: &str) { self.host_state_mut().panic_message = Some(message.to_owned()); } } - -impl<'a> Sandbox for HostContext<'a> { - fn memory_get( - &mut self, - memory_id: MemoryId, - offset: WordSize, - buf_ptr: Pointer, - buf_len: WordSize, - ) -> sp_wasm_interface::Result { - let sandboxed_memory = self.sandbox_store().memory(memory_id).map_err(|e| e.to_string())?; - - let len = buf_len as usize; - - let buffer = match sandboxed_memory.read(Pointer::new(offset as u32), len) { - Err(_) => return Ok(sandbox_env::ERR_OUT_OF_BOUNDS), - Ok(buffer) => buffer, - }; - - if util::write_memory_from(&mut self.caller, buf_ptr, &buffer).is_err() { - return Ok(sandbox_env::ERR_OUT_OF_BOUNDS) - } - - Ok(sandbox_env::ERR_OK) - } - - fn memory_set( - &mut self, - memory_id: MemoryId, - offset: WordSize, - val_ptr: Pointer, - val_len: WordSize, - ) -> sp_wasm_interface::Result { - let sandboxed_memory = self.sandbox_store().memory(memory_id).map_err(|e| e.to_string())?; - - let len = val_len as usize; - - let buffer = match util::read_memory(&self.caller, val_ptr, len) { - Err(_) => return Ok(sandbox_env::ERR_OUT_OF_BOUNDS), - Ok(buffer) => buffer, - }; - - if sandboxed_memory.write_from(Pointer::new(offset as u32), &buffer).is_err() { - return Ok(sandbox_env::ERR_OUT_OF_BOUNDS) - } - - Ok(sandbox_env::ERR_OK) - } - - fn memory_teardown(&mut self, memory_id: MemoryId) -> sp_wasm_interface::Result<()> { - self.sandbox_store_mut().memory_teardown(memory_id).map_err(|e| e.to_string()) - } - - fn memory_new(&mut self, initial: u32, maximum: u32) -> sp_wasm_interface::Result { - self.sandbox_store_mut().new_memory(initial, maximum).map_err(|e| e.to_string()) - } - - fn invoke( - &mut self, - instance_id: u32, - export_name: &str, - mut args: &[u8], - return_val: Pointer, - return_val_len: u32, - state: u32, - ) -> sp_wasm_interface::Result { - trace!(target: "sp-sandbox", "invoke, instance_idx={}", instance_id); - - // Deserialize arguments and convert them into wasmi types. - let args = Vec::::decode(&mut args) - .map_err(|_| "Can't decode serialized arguments for the invocation")? - .into_iter() - .collect::>(); - - let instance = self.sandbox_store().instance(instance_id).map_err(|e| e.to_string())?; - - let dispatch_thunk = - self.sandbox_store().dispatch_thunk(instance_id).map_err(|e| e.to_string())?; - - let result = instance.invoke( - export_name, - &args, - state, - &mut SandboxContext { host_context: self, dispatch_thunk }, - ); - - match result { - Ok(None) => Ok(sandbox_env::ERR_OK), - Ok(Some(val)) => { - // Serialize return value and write it back into the memory. - sp_wasm_interface::ReturnValue::Value(val.into()).using_encoded(|val| { - if val.len() > return_val_len as usize { - return Err("Return value buffer is too small".into()) - } - ::write_memory(self, return_val, val) - .map_err(|_| "can't write return value")?; - Ok(sandbox_env::ERR_OK) - }) - }, - Err(_) => Ok(sandbox_env::ERR_EXECUTION), - } - } - - fn instance_teardown(&mut self, instance_id: u32) -> sp_wasm_interface::Result<()> { - self.sandbox_store_mut() - .instance_teardown(instance_id) - .map_err(|e| e.to_string()) - } - - fn instance_new( - &mut self, - dispatch_thunk_id: u32, - wasm: &[u8], - raw_env_def: &[u8], - state: u32, - ) -> sp_wasm_interface::Result { - // Extract a dispatch thunk from the instance's table by the specified index. - let dispatch_thunk = { - let table = self - .caller - .data() - .table() - .ok_or("Runtime doesn't have a table; sandbox is unavailable")?; - let table_item = table.get(&mut self.caller, dispatch_thunk_id); - - *table_item - .ok_or("dispatch_thunk_id is out of bounds")? - .funcref() - .ok_or("dispatch_thunk_idx should be a funcref")? - .ok_or("dispatch_thunk_idx should point to actual func")? - }; - - let guest_env = match sandbox::GuestEnvironment::decode(self.sandbox_store(), raw_env_def) { - Ok(guest_env) => guest_env, - Err(_) => return Ok(sandbox_env::ERR_MODULE as u32), - }; - - let mut store = self - .host_state_mut() - .sandbox_store - .0 - .take() - .expect("sandbox store is only empty when borrowed"); - - // Catch any potential panics so that we can properly restore the sandbox store - // which we've destructively borrowed. - let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - store.instantiate( - wasm, - guest_env, - state, - &mut SandboxContext { host_context: self, dispatch_thunk }, - ) - })); - - self.host_state_mut().sandbox_store.0 = Some(store); - - let result = match result { - Ok(result) => result, - Err(error) => std::panic::resume_unwind(error), - }; - - let instance_idx_or_err_code = match result { - Ok(instance) => instance.register(self.sandbox_store_mut(), dispatch_thunk), - Err(sandbox::InstantiationError::StartTrapped) => sandbox_env::ERR_EXECUTION, - Err(_) => sandbox_env::ERR_MODULE, - }; - - Ok(instance_idx_or_err_code as u32) - } - - fn get_global_val( - &self, - instance_idx: u32, - name: &str, - ) -> sp_wasm_interface::Result> { - self.sandbox_store() - .instance(instance_idx) - .map(|i| i.get_global_val(name)) - .map_err(|e| e.to_string()) - } -} - -struct SandboxContext<'a, 'b> { - host_context: &'a mut HostContext<'b>, - dispatch_thunk: Func, -} - -impl<'a, 'b> sandbox::SandboxContext for SandboxContext<'a, 'b> { - fn invoke( - &mut self, - invoke_args_ptr: Pointer, - invoke_args_len: WordSize, - state: u32, - func_idx: SupervisorFuncIndex, - ) -> Result { - let mut ret_vals = [Val::null()]; - let result = self.dispatch_thunk.call( - &mut self.host_context.caller, - &[ - Val::I32(u32::from(invoke_args_ptr) as i32), - Val::I32(invoke_args_len as i32), - Val::I32(state as i32), - Val::I32(usize::from(func_idx) as i32), - ], - &mut ret_vals, - ); - - match result { - Ok(()) => - if let Some(ret_val) = ret_vals[0].i64() { - Ok(ret_val) - } else { - Err("Supervisor function returned unexpected result!".into()) - }, - Err(err) => Err(err.to_string().into()), - } - } - - fn supervisor_context(&mut self) -> &mut dyn FunctionContext { - self.host_context - } -} diff --git a/client/executor/wasmtime/src/runtime.rs b/client/executor/wasmtime/src/runtime.rs index 5bca899648..b124fd627d 100644 --- a/client/executor/wasmtime/src/runtime.rs +++ b/client/executor/wasmtime/src/runtime.rs @@ -56,11 +56,6 @@ pub(crate) struct StoreData { } impl StoreData { - /// Returns a reference to the host state. - pub fn host_state(&self) -> Option<&HostState> { - self.host_state.as_ref() - } - /// Returns a mutable reference to the host state. pub fn host_state_mut(&mut self) -> Option<&mut HostState> { self.host_state.as_mut() @@ -70,11 +65,6 @@ impl StoreData { pub fn memory(&self) -> Memory { self.memory.expect("memory is always set; qed") } - - /// Returns the host table. - pub fn table(&self) -> Option { - self.table - } } pub(crate) type Store = wasmtime::Store; diff --git a/client/executor/wasmtime/src/util.rs b/client/executor/wasmtime/src/util.rs index 83745e21e8..15f62e4750 100644 --- a/client/executor/wasmtime/src/util.rs +++ b/client/executor/wasmtime/src/util.rs @@ -48,24 +48,6 @@ pub fn into_wasmtime_val(value: Value) -> wasmtime::Val { } } -/// Read data from a slice of memory into a newly allocated buffer. -/// -/// Returns an error if the read would go out of the memory bounds. -pub(crate) fn read_memory( - ctx: impl AsContext, - source_addr: Pointer, - size: usize, -) -> Result> { - let range = - checked_range(source_addr.into(), size, ctx.as_context().data().memory().data_size(&ctx)) - .ok_or_else(|| Error::Other("memory read is out of bounds".into()))?; - - let mut buffer = vec![0; range.len()]; - read_memory_into(ctx, source_addr, &mut buffer)?; - - Ok(buffer) -} - /// Read data from the instance memory into a slice. /// /// Returns an error if the read would go out of the memory bounds. diff --git a/primitives/io/src/lib.rs b/primitives/io/src/lib.rs index ead3ada1d1..600d76b3b4 100644 --- a/primitives/io/src/lib.rs +++ b/primitives/io/src/lib.rs @@ -1611,99 +1611,6 @@ mod tracing_setup { pub use tracing_setup::init_tracing; -/// Wasm-only interface that provides functions for interacting with the sandbox. -#[runtime_interface(wasm_only)] -pub trait Sandbox { - /// Instantiate a new sandbox instance with the given `wasm_code`. - fn instantiate( - &mut self, - dispatch_thunk: u32, - wasm_code: &[u8], - env_def: &[u8], - state_ptr: Pointer, - ) -> u32 { - self.sandbox() - .instance_new(dispatch_thunk, wasm_code, env_def, state_ptr.into()) - .expect("Failed to instantiate a new sandbox") - } - - /// Invoke `function` in the sandbox with `sandbox_idx`. - fn invoke( - &mut self, - instance_idx: u32, - function: &str, - args: &[u8], - return_val_ptr: Pointer, - return_val_len: u32, - state_ptr: Pointer, - ) -> u32 { - self.sandbox() - .invoke(instance_idx, function, args, return_val_ptr, return_val_len, state_ptr.into()) - .expect("Failed to invoke function with sandbox") - } - - /// Create a new memory instance with the given `initial` and `maximum` size. - fn memory_new(&mut self, initial: u32, maximum: u32) -> u32 { - self.sandbox() - .memory_new(initial, maximum) - .expect("Failed to create new memory with sandbox") - } - - /// Get the memory starting at `offset` from the instance with `memory_idx` into the buffer. - fn memory_get( - &mut self, - memory_idx: u32, - offset: u32, - buf_ptr: Pointer, - buf_len: u32, - ) -> u32 { - self.sandbox() - .memory_get(memory_idx, offset, buf_ptr, buf_len) - .expect("Failed to get memory with sandbox") - } - - /// Set the memory in the given `memory_idx` to the given value at `offset`. - fn memory_set( - &mut self, - memory_idx: u32, - offset: u32, - val_ptr: Pointer, - val_len: u32, - ) -> u32 { - self.sandbox() - .memory_set(memory_idx, offset, val_ptr, val_len) - .expect("Failed to set memory with sandbox") - } - - /// Teardown the memory instance with the given `memory_idx`. - fn memory_teardown(&mut self, memory_idx: u32) { - self.sandbox() - .memory_teardown(memory_idx) - .expect("Failed to teardown memory with sandbox") - } - - /// Teardown the sandbox instance with the given `instance_idx`. - fn instance_teardown(&mut self, instance_idx: u32) { - self.sandbox() - .instance_teardown(instance_idx) - .expect("Failed to teardown sandbox instance") - } - - /// Get the value from a global with the given `name`. The sandbox is determined by the given - /// `instance_idx`. - /// - /// Returns `Some(_)` when the requested global variable could be found. - fn get_global_val( - &mut self, - instance_idx: u32, - name: &str, - ) -> Option { - self.sandbox() - .get_global_val(instance_idx, name) - .expect("Failed to get global from sandbox") - } -} - /// Allocator used by Substrate when executing the Wasm runtime. #[cfg(all(target_arch = "wasm32", not(feature = "std")))] struct WasmAllocator; @@ -1779,7 +1686,6 @@ pub type SubstrateHostFunctions = ( allocator::HostFunctions, panic_handler::HostFunctions, logging::HostFunctions, - sandbox::HostFunctions, crate::trie::HostFunctions, offchain_index::HostFunctions, transaction_index::HostFunctions, diff --git a/primitives/sandbox/Cargo.toml b/primitives/sandbox/Cargo.toml deleted file mode 100644 index 024fe72093..0000000000 --- a/primitives/sandbox/Cargo.toml +++ /dev/null @@ -1,40 +0,0 @@ -[package] -name = "sp-sandbox" -version = "0.10.0-dev" -authors = ["Parity Technologies "] -edition = "2021" -license = "Apache-2.0" -homepage = "https://substrate.io" -repository = "https://github.com/paritytech/substrate/" -description = "This crate provides means to instantiate and execute wasm modules." -readme = "README.md" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } -log = { version = "0.4", default-features = false } -wasmi = { version = "0.13", default-features = false } -sp-core = { version = "7.0.0", default-features = false, path = "../core" } -sp-io = { version = "7.0.0", default-features = false, path = "../io" } -sp-std = { version = "5.0.0", default-features = false, path = "../std" } -sp-wasm-interface = { version = "7.0.0", default-features = false, path = "../wasm-interface" } - -[dev-dependencies] -assert_matches = "1.3.0" -wat = "1.0" - -[features] -default = ["std"] -std = [ - "codec/std", - "log/std", - "sp-core/std", - "sp-io/std", - "sp-std/std", - "sp-wasm-interface/std", - "wasmi/std", -] -strict = [] -wasmer-sandbox = [] diff --git a/primitives/sandbox/README.md b/primitives/sandbox/README.md deleted file mode 100644 index 9335b53ae1..0000000000 --- a/primitives/sandbox/README.md +++ /dev/null @@ -1,21 +0,0 @@ -This crate provides means to instantiate and execute wasm modules. - -It works even when the user of this library executes from -inside the wasm VM. In this case the same VM is used for execution -of both the sandbox owner and the sandboxed module, without compromising security -and without the performance penalty of full wasm emulation inside wasm. - -This is achieved by using bindings to the wasm VM, which are published by the host API. -This API is thin and consists of only a handful functions. It contains functions for instantiating -modules and executing them, but doesn't contain functions for inspecting the module -structure. The user of this library is supposed to read the wasm module. - -When this crate is used in the `std` environment all these functions are implemented by directly -calling the wasm VM. - -Examples of possible use-cases for this library are not limited to the following: - -- implementing smart-contract runtimes that use wasm for contract code -- executing a wasm substrate runtime inside of a wasm parachain - -License: Apache-2.0 \ No newline at end of file diff --git a/primitives/sandbox/src/embedded_executor.rs b/primitives/sandbox/src/embedded_executor.rs deleted file mode 100644 index 115c3192f3..0000000000 --- a/primitives/sandbox/src/embedded_executor.rs +++ /dev/null @@ -1,478 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2018-2022 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. - -//! An embedded WASM executor utilizing `wasmi`. - -use alloc::string::String; - -use wasmi::{ - memory_units::Pages, Externals, FuncInstance, FuncRef, GlobalDescriptor, GlobalRef, - ImportResolver, MemoryDescriptor, MemoryInstance, MemoryRef, Module, ModuleInstance, ModuleRef, - RuntimeArgs, RuntimeValue, Signature, TableDescriptor, TableRef, Trap, -}; - -use sp_std::{ - borrow::ToOwned, collections::btree_map::BTreeMap, fmt, marker::PhantomData, prelude::*, -}; - -use crate::{Error, HostError, HostFuncType, ReturnValue, Value, TARGET}; - -/// The linear memory used by the sandbox. -#[derive(Clone)] -pub struct Memory { - memref: MemoryRef, -} - -impl super::SandboxMemory for Memory { - fn new(initial: u32, maximum: Option) -> Result { - Ok(Memory { - memref: MemoryInstance::alloc( - Pages(initial as usize), - maximum.map(|m| Pages(m as usize)), - ) - .map_err(|_| Error::Module)?, - }) - } - - fn get(&self, ptr: u32, buf: &mut [u8]) -> Result<(), Error> { - self.memref.get_into(ptr, buf).map_err(|_| Error::OutOfBounds)?; - Ok(()) - } - - fn set(&self, ptr: u32, value: &[u8]) -> Result<(), Error> { - self.memref.set(ptr, value).map_err(|_| Error::OutOfBounds)?; - Ok(()) - } -} - -struct HostFuncIndex(usize); - -struct DefinedHostFunctions { - funcs: Vec>, -} - -impl Clone for DefinedHostFunctions { - fn clone(&self) -> DefinedHostFunctions { - DefinedHostFunctions { funcs: self.funcs.clone() } - } -} - -impl DefinedHostFunctions { - fn new() -> DefinedHostFunctions { - DefinedHostFunctions { funcs: Vec::new() } - } - - fn define(&mut self, f: HostFuncType) -> HostFuncIndex { - let idx = self.funcs.len(); - self.funcs.push(f); - HostFuncIndex(idx) - } -} - -#[derive(Debug)] -struct DummyHostError; - -impl fmt::Display for DummyHostError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "DummyHostError") - } -} - -impl wasmi::HostError for DummyHostError {} - -struct GuestExternals<'a, T: 'a> { - state: &'a mut T, - defined_host_functions: &'a DefinedHostFunctions, -} - -impl<'a, T> Externals for GuestExternals<'a, T> { - fn invoke_index( - &mut self, - index: usize, - args: RuntimeArgs, - ) -> Result, Trap> { - let args = args.as_ref().iter().cloned().map(to_interface).collect::>(); - - let result = (self.defined_host_functions.funcs[index])(self.state, &args); - match result { - Ok(value) => Ok(match value { - ReturnValue::Value(v) => Some(to_wasmi(v)), - ReturnValue::Unit => None, - }), - Err(HostError) => Err(Trap::host(DummyHostError)), - } - } -} - -enum ExternVal { - HostFunc(HostFuncIndex), - Memory(Memory), -} - -/// A builder for the environment of the sandboxed WASM module. -pub struct EnvironmentDefinitionBuilder { - map: BTreeMap<(Vec, Vec), ExternVal>, - defined_host_functions: DefinedHostFunctions, -} - -impl super::SandboxEnvironmentBuilder for EnvironmentDefinitionBuilder { - fn new() -> EnvironmentDefinitionBuilder { - EnvironmentDefinitionBuilder { - map: BTreeMap::new(), - defined_host_functions: DefinedHostFunctions::new(), - } - } - - fn add_host_func(&mut self, module: N1, field: N2, f: HostFuncType) - where - N1: Into>, - N2: Into>, - { - let idx = self.defined_host_functions.define(f); - self.map.insert((module.into(), field.into()), ExternVal::HostFunc(idx)); - } - - fn add_memory(&mut self, module: N1, field: N2, mem: Memory) - where - N1: Into>, - N2: Into>, - { - self.map.insert((module.into(), field.into()), ExternVal::Memory(mem)); - } -} - -impl ImportResolver for EnvironmentDefinitionBuilder { - fn resolve_func( - &self, - module_name: &str, - field_name: &str, - signature: &Signature, - ) -> Result { - let key = (module_name.as_bytes().to_owned(), field_name.as_bytes().to_owned()); - let externval = self.map.get(&key).ok_or_else(|| { - log::debug!(target: TARGET, "Export {}:{} not found", module_name, field_name); - wasmi::Error::Instantiation(String::new()) - })?; - let host_func_idx = match *externval { - ExternVal::HostFunc(ref idx) => idx, - _ => { - log::debug!( - target: TARGET, - "Export {}:{} is not a host func", - module_name, - field_name, - ); - return Err(wasmi::Error::Instantiation(String::new())) - }, - }; - Ok(FuncInstance::alloc_host(signature.clone(), host_func_idx.0)) - } - - fn resolve_global( - &self, - _module_name: &str, - _field_name: &str, - _global_type: &GlobalDescriptor, - ) -> Result { - log::debug!(target: TARGET, "Importing globals is not supported yet"); - Err(wasmi::Error::Instantiation(String::new())) - } - - fn resolve_memory( - &self, - module_name: &str, - field_name: &str, - _memory_type: &MemoryDescriptor, - ) -> Result { - let key = (module_name.as_bytes().to_owned(), field_name.as_bytes().to_owned()); - let externval = self.map.get(&key).ok_or_else(|| { - log::debug!(target: TARGET, "Export {}:{} not found", module_name, field_name); - wasmi::Error::Instantiation(String::new()) - })?; - let memory = match *externval { - ExternVal::Memory(ref m) => m, - _ => { - log::debug!( - target: TARGET, - "Export {}:{} is not a memory", - module_name, - field_name, - ); - return Err(wasmi::Error::Instantiation(String::new())) - }, - }; - Ok(memory.memref.clone()) - } - - fn resolve_table( - &self, - _module_name: &str, - _field_name: &str, - _table_type: &TableDescriptor, - ) -> Result { - log::debug!("Importing tables is not supported yet"); - Err(wasmi::Error::Instantiation(String::new())) - } -} - -/// Sandboxed instance of a WASM module. -pub struct Instance { - instance: ModuleRef, - defined_host_functions: DefinedHostFunctions, - _marker: PhantomData, -} - -impl super::SandboxInstance for Instance { - type Memory = Memory; - type EnvironmentBuilder = EnvironmentDefinitionBuilder; - - fn new( - code: &[u8], - env_def_builder: &EnvironmentDefinitionBuilder, - state: &mut T, - ) -> Result, Error> { - let module = Module::from_buffer(code).map_err(|_| Error::Module)?; - let not_started_instance = - ModuleInstance::new(&module, env_def_builder).map_err(|_| Error::Module)?; - - let defined_host_functions = env_def_builder.defined_host_functions.clone(); - let instance = { - let mut externals = - GuestExternals { state, defined_host_functions: &defined_host_functions }; - let instance = - not_started_instance.run_start(&mut externals).map_err(|_| Error::Execution)?; - instance - }; - - Ok(Instance { instance, defined_host_functions, _marker: PhantomData:: }) - } - - fn invoke(&mut self, name: &str, args: &[Value], state: &mut T) -> Result { - let args = args.iter().cloned().map(to_wasmi).collect::>(); - - let mut externals = - GuestExternals { state, defined_host_functions: &self.defined_host_functions }; - let result = self.instance.invoke_export(name, &args, &mut externals); - - match result { - Ok(None) => Ok(ReturnValue::Unit), - Ok(Some(val)) => Ok(ReturnValue::Value(to_interface(val))), - Err(_err) => Err(Error::Execution), - } - } - - fn get_global_val(&self, name: &str) -> Option { - let global = self.instance.export_by_name(name)?.as_global()?.get(); - - Some(to_interface(global)) - } -} - -/// Convert the substrate value type to the wasmi value type. -fn to_wasmi(value: Value) -> RuntimeValue { - match value { - Value::I32(val) => RuntimeValue::I32(val), - Value::I64(val) => RuntimeValue::I64(val), - Value::F32(val) => RuntimeValue::F32(val.into()), - Value::F64(val) => RuntimeValue::F64(val.into()), - } -} - -/// Convert the wasmi value type to the substrate value type. -fn to_interface(value: RuntimeValue) -> Value { - match value { - RuntimeValue::I32(val) => Value::I32(val), - RuntimeValue::I64(val) => Value::I64(val), - RuntimeValue::F32(val) => Value::F32(val.into()), - RuntimeValue::F64(val) => Value::F64(val.into()), - } -} - -#[cfg(test)] -mod tests { - use super::{EnvironmentDefinitionBuilder, Instance}; - use crate::{Error, HostError, ReturnValue, SandboxEnvironmentBuilder, SandboxInstance, Value}; - use assert_matches::assert_matches; - - fn execute_sandboxed(code: &[u8], args: &[Value]) -> Result { - struct State { - counter: u32, - } - - fn env_assert(_e: &mut State, args: &[Value]) -> Result { - if args.len() != 1 { - return Err(HostError) - } - let condition = args[0].as_i32().ok_or_else(|| HostError)?; - if condition != 0 { - Ok(ReturnValue::Unit) - } else { - Err(HostError) - } - } - fn env_inc_counter(e: &mut State, args: &[Value]) -> Result { - if args.len() != 1 { - return Err(HostError) - } - let inc_by = args[0].as_i32().ok_or_else(|| HostError)?; - e.counter += inc_by as u32; - Ok(ReturnValue::Value(Value::I32(e.counter as i32))) - } - /// Function that takes one argument of any type and returns that value. - fn env_polymorphic_id(_e: &mut State, args: &[Value]) -> Result { - if args.len() != 1 { - return Err(HostError) - } - Ok(ReturnValue::Value(args[0])) - } - - let mut state = State { counter: 0 }; - - let mut env_builder = EnvironmentDefinitionBuilder::new(); - env_builder.add_host_func("env", "assert", env_assert); - env_builder.add_host_func("env", "inc_counter", env_inc_counter); - env_builder.add_host_func("env", "polymorphic_id", env_polymorphic_id); - - let mut instance = Instance::new(code, &env_builder, &mut state)?; - let result = instance.invoke("call", args, &mut state); - - result.map_err(|_| HostError) - } - - #[test] - fn invoke_args() { - let code = wat::parse_str( - r#" - (module - (import "env" "assert" (func $assert (param i32))) - - (func (export "call") (param $x i32) (param $y i64) - ;; assert that $x = 0x12345678 - (call $assert - (i32.eq - (get_local $x) - (i32.const 0x12345678) - ) - ) - - (call $assert - (i64.eq - (get_local $y) - (i64.const 0x1234567887654321) - ) - ) - ) - ) - "#, - ) - .unwrap(); - - let result = - execute_sandboxed(&code, &[Value::I32(0x12345678), Value::I64(0x1234567887654321)]); - assert!(result.is_ok()); - } - - #[test] - fn return_value() { - let code = wat::parse_str( - r#" - (module - (func (export "call") (param $x i32) (result i32) - (i32.add - (get_local $x) - (i32.const 1) - ) - ) - ) - "#, - ) - .unwrap(); - - let return_val = execute_sandboxed(&code, &[Value::I32(0x1336)]).unwrap(); - assert_eq!(return_val, ReturnValue::Value(Value::I32(0x1337))); - } - - #[test] - fn signatures_dont_matter() { - let code = wat::parse_str( - r#" - (module - (import "env" "polymorphic_id" (func $id_i32 (param i32) (result i32))) - (import "env" "polymorphic_id" (func $id_i64 (param i64) (result i64))) - (import "env" "assert" (func $assert (param i32))) - - (func (export "call") - ;; assert that we can actually call the "same" function with different - ;; signatures. - (call $assert - (i32.eq - (call $id_i32 - (i32.const 0x012345678) - ) - (i32.const 0x012345678) - ) - ) - (call $assert - (i64.eq - (call $id_i64 - (i64.const 0x0123456789abcdef) - ) - (i64.const 0x0123456789abcdef) - ) - ) - ) - ) - "#, - ) - .unwrap(); - - let return_val = execute_sandboxed(&code, &[]).unwrap(); - assert_eq!(return_val, ReturnValue::Unit); - } - - #[test] - fn cant_return_unmatching_type() { - fn env_returns_i32(_e: &mut (), _args: &[Value]) -> Result { - Ok(ReturnValue::Value(Value::I32(42))) - } - - let mut env_builder = EnvironmentDefinitionBuilder::new(); - env_builder.add_host_func("env", "returns_i32", env_returns_i32); - - let code = wat::parse_str( - r#" - (module - ;; It's actually returns i32, but imported as if it returned i64 - (import "env" "returns_i32" (func $returns_i32 (result i64))) - - (func (export "call") - (drop - (call $returns_i32) - ) - ) - ) - "#, - ) - .unwrap(); - - // It succeeds since we are able to import functions with types we want. - let mut instance = Instance::new(&code, &env_builder, &mut ()).unwrap(); - - // But this fails since we imported a function that returns i32 as if it returned i64. - assert_matches!(instance.invoke("call", &[], &mut ()), Err(Error::Execution)); - } -} diff --git a/primitives/sandbox/src/env.rs b/primitives/sandbox/src/env.rs deleted file mode 100644 index 94b1c5e467..0000000000 --- a/primitives/sandbox/src/env.rs +++ /dev/null @@ -1,120 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2018-2022 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. - -//! Definition of a sandbox environment. - -use codec::{Decode, Encode}; - -use sp_core::RuntimeDebug; -use sp_std::vec::Vec; - -/// Error error that can be returned from host function. -#[derive(Encode, Decode, RuntimeDebug)] -pub struct HostError; - -/// Describes an entity to define or import into the environment. -#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)] -pub enum ExternEntity { - /// Function that is specified by an index in a default table of - /// a module that creates the sandbox. - #[codec(index = 1)] - Function(u32), - - /// Linear memory that is specified by some identifier returned by sandbox - /// module upon creation new sandboxed memory. - #[codec(index = 2)] - Memory(u32), -} - -/// An entry in a environment definition table. -/// -/// Each entry has a two-level name and description of an entity -/// being defined. -#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)] -pub struct Entry { - /// Module name of which corresponding entity being defined. - pub module_name: Vec, - /// Field name in which corresponding entity being defined. - pub field_name: Vec, - /// External entity being defined. - pub entity: ExternEntity, -} - -/// Definition of runtime that could be used by sandboxed code. -#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)] -pub struct EnvironmentDefinition { - /// Vector of all entries in the environment definition. - pub entries: Vec, -} - -/// Constant for specifying no limit when creating a sandboxed -/// memory instance. For FFI purposes. -pub const MEM_UNLIMITED: u32 = -1i32 as u32; - -/// No error happened. -/// -/// For FFI purposes. -pub const ERR_OK: u32 = 0; - -/// Validation or instantiation error occurred when creating new -/// sandboxed module instance. -/// -/// For FFI purposes. -pub const ERR_MODULE: u32 = -1i32 as u32; - -/// Out-of-bounds access attempted with memory or table. -/// -/// For FFI purposes. -pub const ERR_OUT_OF_BOUNDS: u32 = -2i32 as u32; - -/// Execution error occurred (typically trap). -/// -/// For FFI purposes. -pub const ERR_EXECUTION: u32 = -3i32 as u32; - -#[cfg(test)] -mod tests { - use super::*; - use codec::Codec; - use std::fmt; - - fn roundtrip(s: S) { - let encoded = s.encode(); - assert_eq!(S::decode(&mut &encoded[..]).unwrap(), s); - } - - #[test] - fn env_def_roundtrip() { - roundtrip(EnvironmentDefinition { entries: vec![] }); - - roundtrip(EnvironmentDefinition { - entries: vec![Entry { - module_name: b"kernel"[..].into(), - field_name: b"memory"[..].into(), - entity: ExternEntity::Memory(1337), - }], - }); - - roundtrip(EnvironmentDefinition { - entries: vec![Entry { - module_name: b"env"[..].into(), - field_name: b"abort"[..].into(), - entity: ExternEntity::Function(228), - }], - }); - } -} diff --git a/primitives/sandbox/src/host_executor.rs b/primitives/sandbox/src/host_executor.rs deleted file mode 100644 index e62c051262..0000000000 --- a/primitives/sandbox/src/host_executor.rs +++ /dev/null @@ -1,274 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2018-2022 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. - -//! A WASM executor utilizing the sandbox runtime interface of the host. - -use codec::{Decode, Encode}; - -use sp_io::sandbox; -use sp_std::{marker, mem, prelude::*, rc::Rc, slice, vec}; - -use crate::{env, Error, HostFuncType, ReturnValue, Value}; - -mod ffi { - use super::HostFuncType; - use sp_std::mem; - - /// Index into the default table that points to a `HostFuncType`. - pub type HostFuncIndex = usize; - - /// Coerce `HostFuncIndex` to a callable host function pointer. - /// - /// # Safety - /// - /// This function should be only called with a `HostFuncIndex` that was previously registered - /// in the environment definition. Typically this should only - /// be called with an argument received in `dispatch_thunk`. - pub unsafe fn coerce_host_index_to_func(idx: HostFuncIndex) -> HostFuncType { - // We need to ensure that sizes of a callable function pointer and host function index is - // indeed equal. - // We can't use `static_assertions` create because it makes compiler panic, fallback to - // runtime assert. const_assert!(mem::size_of::() == - // mem::size_of::>()); - assert!(mem::size_of::() == mem::size_of::>()); - mem::transmute::>(idx) - } -} - -struct MemoryHandle { - memory_idx: u32, -} - -impl Drop for MemoryHandle { - fn drop(&mut self) { - sandbox::memory_teardown(self.memory_idx); - } -} - -/// The linear memory used by the sandbox. -#[derive(Clone)] -pub struct Memory { - // Handle to memory instance is wrapped to add reference-counting semantics - // to `Memory`. - handle: Rc, -} - -impl super::SandboxMemory for Memory { - fn new(initial: u32, maximum: Option) -> Result { - let maximum = if let Some(maximum) = maximum { maximum } else { env::MEM_UNLIMITED }; - - match sandbox::memory_new(initial, maximum) { - env::ERR_MODULE => Err(Error::Module), - memory_idx => Ok(Memory { handle: Rc::new(MemoryHandle { memory_idx }) }), - } - } - - fn get(&self, offset: u32, buf: &mut [u8]) -> Result<(), Error> { - let result = - sandbox::memory_get(self.handle.memory_idx, offset, buf.as_mut_ptr(), buf.len() as u32); - match result { - env::ERR_OK => Ok(()), - env::ERR_OUT_OF_BOUNDS => Err(Error::OutOfBounds), - _ => unreachable!(), - } - } - - fn set(&self, offset: u32, val: &[u8]) -> Result<(), Error> { - let result = sandbox::memory_set( - self.handle.memory_idx, - offset, - val.as_ptr() as _, - val.len() as u32, - ); - match result { - env::ERR_OK => Ok(()), - env::ERR_OUT_OF_BOUNDS => Err(Error::OutOfBounds), - _ => unreachable!(), - } - } -} - -/// A builder for the environment of the sandboxed WASM module. -pub struct EnvironmentDefinitionBuilder { - env_def: env::EnvironmentDefinition, - retained_memories: Vec, - _marker: marker::PhantomData, -} - -impl EnvironmentDefinitionBuilder { - fn add_entry(&mut self, module: N1, field: N2, extern_entity: env::ExternEntity) - where - N1: Into>, - N2: Into>, - { - let entry = env::Entry { - module_name: module.into(), - field_name: field.into(), - entity: extern_entity, - }; - self.env_def.entries.push(entry); - } -} - -impl super::SandboxEnvironmentBuilder for EnvironmentDefinitionBuilder { - fn new() -> EnvironmentDefinitionBuilder { - EnvironmentDefinitionBuilder { - env_def: env::EnvironmentDefinition { entries: Vec::new() }, - retained_memories: Vec::new(), - _marker: marker::PhantomData::, - } - } - - fn add_host_func(&mut self, module: N1, field: N2, f: HostFuncType) - where - N1: Into>, - N2: Into>, - { - let f = env::ExternEntity::Function(f as u32); - self.add_entry(module, field, f); - } - - fn add_memory(&mut self, module: N1, field: N2, mem: Memory) - where - N1: Into>, - N2: Into>, - { - // We need to retain memory to keep it alive while the EnvironmentDefinitionBuilder alive. - self.retained_memories.push(mem.clone()); - - let mem = env::ExternEntity::Memory(mem.handle.memory_idx as u32); - self.add_entry(module, field, mem); - } -} - -/// Sandboxed instance of a WASM module. -pub struct Instance { - instance_idx: u32, - _retained_memories: Vec, - _marker: marker::PhantomData, -} - -/// The primary responsibility of this thunk is to deserialize arguments and -/// call the original function, specified by the index. -extern "C" fn dispatch_thunk( - serialized_args_ptr: *const u8, - serialized_args_len: usize, - state: usize, - f: ffi::HostFuncIndex, -) -> u64 { - let serialized_args = unsafe { - if serialized_args_len == 0 { - &[] - } else { - slice::from_raw_parts(serialized_args_ptr, serialized_args_len) - } - }; - let args = Vec::::decode(&mut &serialized_args[..]).expect( - "serialized args should be provided by the runtime; - correctly serialized data should be deserializable; - qed", - ); - - unsafe { - // This should be safe since `coerce_host_index_to_func` is called with an argument - // received in an `dispatch_thunk` implementation, so `f` should point - // on a valid host function. - let f = ffi::coerce_host_index_to_func(f); - - // This should be safe since mutable reference to T is passed upon the invocation. - let state = &mut *(state as *mut T); - - // Pass control flow to the designated function. - let result = f(state, &args).encode(); - - // Leak the result vector and return the pointer to return data. - let result_ptr = result.as_ptr() as u64; - let result_len = result.len() as u64; - mem::forget(result); - - (result_ptr << 32) | result_len - } -} - -impl super::SandboxInstance for Instance { - type Memory = Memory; - type EnvironmentBuilder = EnvironmentDefinitionBuilder; - - fn new( - code: &[u8], - env_def_builder: &EnvironmentDefinitionBuilder, - state: &mut T, - ) -> Result, Error> { - let serialized_env_def: Vec = env_def_builder.env_def.encode(); - // It's very important to instantiate thunk with the right type. - let dispatch_thunk = dispatch_thunk::; - let result = sandbox::instantiate( - dispatch_thunk as u32, - code, - &serialized_env_def, - state as *const T as _, - ); - - let instance_idx = match result { - env::ERR_MODULE => return Err(Error::Module), - env::ERR_EXECUTION => return Err(Error::Execution), - instance_idx => instance_idx, - }; - - // We need to retain memories to keep them alive while the Instance is alive. - let retained_memories = env_def_builder.retained_memories.clone(); - Ok(Instance { - instance_idx, - _retained_memories: retained_memories, - _marker: marker::PhantomData::, - }) - } - - fn invoke(&mut self, name: &str, args: &[Value], state: &mut T) -> Result { - let serialized_args = args.to_vec().encode(); - let mut return_val = vec![0u8; ReturnValue::ENCODED_MAX_SIZE]; - - let result = sandbox::invoke( - self.instance_idx, - name, - &serialized_args, - return_val.as_mut_ptr() as _, - return_val.len() as u32, - state as *const T as _, - ); - - match result { - env::ERR_OK => { - let return_val = - ReturnValue::decode(&mut &return_val[..]).map_err(|_| Error::Execution)?; - Ok(return_val) - }, - env::ERR_EXECUTION => Err(Error::Execution), - _ => unreachable!(), - } - } - - fn get_global_val(&self, name: &str) -> Option { - sandbox::get_global_val(self.instance_idx, name) - } -} - -impl Drop for Instance { - fn drop(&mut self) { - sandbox::instance_teardown(self.instance_idx); - } -} diff --git a/primitives/sandbox/src/lib.rs b/primitives/sandbox/src/lib.rs deleted file mode 100644 index b6b4a5a97d..0000000000 --- a/primitives/sandbox/src/lib.rs +++ /dev/null @@ -1,190 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2018-2022 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 crate provides means to instantiate and execute wasm modules. -//! -//! It works even when the user of this library executes from -//! inside the wasm VM. In this case the same VM is used for execution -//! of both the sandbox owner and the sandboxed module, without compromising security -//! and without the performance penalty of full wasm emulation inside wasm. -//! -//! This is achieved by using bindings to the wasm VM, which are published by the host API. -//! This API is thin and consists of only a handful functions. It contains functions for -//! instantiating modules and executing them, but doesn't contain functions for inspecting the -//! module structure. The user of this library is supposed to read the wasm module. -//! -//! When this crate is used in the `std` environment all these functions are implemented by directly -//! calling the wasm VM. -//! -//! Examples of possible use-cases for this library are not limited to the following: -//! -//! - implementing smart-contract runtimes that use wasm for contract code -//! - executing a wasm substrate runtime inside of a wasm parachain - -#![warn(missing_docs)] -#![cfg_attr(not(feature = "std"), no_std)] - -extern crate alloc; - -pub mod embedded_executor; -pub mod env; -#[cfg(not(feature = "std"))] -pub mod host_executor; - -use sp_core::RuntimeDebug; -use sp_std::prelude::*; - -pub use sp_wasm_interface::{ReturnValue, Value}; - -#[cfg(not(all(feature = "wasmer-sandbox", not(feature = "std"))))] -pub use self::embedded_executor as default_executor; -pub use self::env::HostError; -#[cfg(all(feature = "wasmer-sandbox", not(feature = "std")))] -pub use self::host_executor as default_executor; - -/// The target used for logging. -const TARGET: &str = "runtime::sandbox"; - -/// Error that can occur while using this crate. -#[derive(RuntimeDebug)] -pub enum Error { - /// Module is not valid, couldn't be instantiated. - Module, - - /// Access to a memory or table was made with an address or an index which is out of bounds. - /// - /// Note that if wasm module makes an out-of-bounds access then trap will occur. - OutOfBounds, - - /// Failed to invoke the start function or an exported function for some reason. - Execution, -} - -impl From for HostError { - fn from(_e: Error) -> HostError { - HostError - } -} - -/// Function pointer for specifying functions by the -/// supervisor in [`EnvironmentDefinitionBuilder`]. -/// -/// [`EnvironmentDefinitionBuilder`]: struct.EnvironmentDefinitionBuilder.html -pub type HostFuncType = fn(&mut T, &[Value]) -> Result; - -/// Reference to a sandboxed linear memory, that -/// will be used by the guest module. -/// -/// The memory can't be directly accessed by supervisor, but only -/// through designated functions [`get`](SandboxMemory::get) and [`set`](SandboxMemory::set). -pub trait SandboxMemory: Sized + Clone { - /// Construct a new linear memory instance. - /// - /// The memory allocated with initial number of pages specified by `initial`. - /// Minimal possible value for `initial` is 0 and maximum possible is `65536`. - /// (Since maximum addressable memory is 232 = 4GiB = 65536 * 64KiB). - /// - /// It is possible to limit maximum number of pages this memory instance can have by specifying - /// `maximum`. If not specified, this memory instance would be able to allocate up to 4GiB. - /// - /// Allocated memory is always zeroed. - fn new(initial: u32, maximum: Option) -> Result; - - /// Read a memory area at the address `ptr` with the size of the provided slice `buf`. - /// - /// Returns `Err` if the range is out-of-bounds. - fn get(&self, ptr: u32, buf: &mut [u8]) -> Result<(), Error>; - - /// Write a memory area at the address `ptr` with contents of the provided slice `buf`. - /// - /// Returns `Err` if the range is out-of-bounds. - fn set(&self, ptr: u32, value: &[u8]) -> Result<(), Error>; -} - -/// Struct that can be used for defining an environment for a sandboxed module. -/// -/// The sandboxed module can access only the entities which were defined and passed -/// to the module at the instantiation time. -pub trait SandboxEnvironmentBuilder: Sized { - /// Construct a new `EnvironmentDefinitionBuilder`. - fn new() -> Self; - - /// Register a host function in this environment definition. - /// - /// NOTE that there is no constraints on type of this function. An instance - /// can import function passed here with any signature it wants. It can even import - /// the same function (i.e. with same `module` and `field`) several times. It's up to - /// the user code to check or constrain the types of signatures. - fn add_host_func(&mut self, module: N1, field: N2, f: HostFuncType) - where - N1: Into>, - N2: Into>; - - /// Register a memory in this environment definition. - fn add_memory(&mut self, module: N1, field: N2, mem: Memory) - where - N1: Into>, - N2: Into>; -} - -/// Sandboxed instance of a wasm module. -/// -/// This instance can be used for invoking exported functions. -pub trait SandboxInstance: Sized { - /// The memory type used for this sandbox. - type Memory: SandboxMemory; - - /// The environment builder used to construct this sandbox. - type EnvironmentBuilder: SandboxEnvironmentBuilder; - - /// Instantiate a module with the given [`EnvironmentDefinitionBuilder`]. It will - /// run the `start` function (if it is present in the module) with the given `state`. - /// - /// Returns `Err(Error::Module)` if this module can't be instantiated with the given - /// environment. If execution of `start` function generated a trap, then `Err(Error::Execution)` - /// will be returned. - /// - /// [`EnvironmentDefinitionBuilder`]: struct.EnvironmentDefinitionBuilder.html - fn new( - code: &[u8], - env_def_builder: &Self::EnvironmentBuilder, - state: &mut State, - ) -> Result; - - /// Invoke an exported function with the given name. - /// - /// # Errors - /// - /// Returns `Err(Error::Execution)` if: - /// - /// - An export function name isn't a proper utf8 byte sequence, - /// - This module doesn't have an exported function with the given name, - /// - If types of the arguments passed to the function doesn't match function signature then - /// trap occurs (as if the exported function was called via call_indirect), - /// - Trap occurred at the execution time. - fn invoke( - &mut self, - name: &str, - args: &[Value], - state: &mut State, - ) -> Result; - - /// Get the value from a global with the given `name`. - /// - /// Returns `Some(_)` if the global could be found. - fn get_global_val(&self, name: &str) -> Option; -} diff --git a/primitives/wasm-interface/src/lib.rs b/primitives/wasm-interface/src/lib.rs index 173e324117..1ecff5a0ce 100644 --- a/primitives/wasm-interface/src/lib.rs +++ b/primitives/wasm-interface/src/lib.rs @@ -303,9 +303,6 @@ pub trait FunctionContext { fn allocate_memory(&mut self, size: WordSize) -> Result>; /// Deallocate a given memory instance. fn deallocate_memory(&mut self, ptr: Pointer) -> Result<()>; - /// Provides access to the sandbox. - fn sandbox(&mut self) -> &mut dyn Sandbox; - /// Registers a panic error message within the executor. /// /// This is meant to be used in situations where the runtime @@ -330,60 +327,6 @@ pub trait FunctionContext { fn register_panic_error_message(&mut self, message: &str); } -/// Sandbox memory identifier. -pub type MemoryId = u32; - -/// Something that provides access to the sandbox. -pub trait Sandbox { - /// Get sandbox memory from the `memory_id` instance at `offset` into the given buffer. - fn memory_get( - &mut self, - memory_id: MemoryId, - offset: WordSize, - buf_ptr: Pointer, - buf_len: WordSize, - ) -> Result; - /// Set sandbox memory from the given value. - fn memory_set( - &mut self, - memory_id: MemoryId, - offset: WordSize, - val_ptr: Pointer, - val_len: WordSize, - ) -> Result; - /// Delete a memory instance. - fn memory_teardown(&mut self, memory_id: MemoryId) -> Result<()>; - /// Create a new memory instance with the given `initial` size and the `maximum` size. - /// The size is given in wasm pages. - fn memory_new(&mut self, initial: u32, maximum: u32) -> Result; - /// Invoke an exported function by a name. - fn invoke( - &mut self, - instance_id: u32, - export_name: &str, - args: &[u8], - return_val: Pointer, - return_val_len: WordSize, - state: u32, - ) -> Result; - /// Delete a sandbox instance. - fn instance_teardown(&mut self, instance_id: u32) -> Result<()>; - /// Create a new sandbox instance. - fn instance_new( - &mut self, - dispatch_thunk_id: u32, - wasm: &[u8], - raw_env_def: &[u8], - state: u32, - ) -> Result; - - /// Get the value from a global with the given `name`. The sandbox is determined by the - /// given `instance_idx` instance. - /// - /// Returns `Some(_)` when the requested global variable could be found. - fn get_global_val(&self, instance_idx: u32, name: &str) -> Result>; -} - if_wasmtime_is_enabled! { /// A trait used to statically register host callbacks with the WASM executor, /// so that they call be called from within the runtime with minimal overhead. diff --git a/scripts/ci/gitlab/pipeline/test.yml b/scripts/ci/gitlab/pipeline/test.yml index c38eac45d7..90b43100bc 100644 --- a/scripts/ci/gitlab/pipeline/test.yml +++ b/scripts/ci/gitlab/pipeline/test.yml @@ -143,25 +143,11 @@ cargo-check-try-runtime: - time cargo check --locked --features try-runtime - rusty-cachier cache upload -cargo-check-wasmer-sandbox: - stage: test - # this is an artificial job dependency, for pipeline optimization using GitLab's DAGs - needs: - - job: cargo-check-try-runtime - artifacts: false - extends: - - .docker-env - - .test-refs - script: - - rusty-cachier snapshot create - - time cargo check --locked --features wasmer-sandbox - - rusty-cachier cache upload - test-deterministic-wasm: stage: test # this is an artificial job dependency, for pipeline optimization using GitLab's DAGs needs: - - job: cargo-check-wasmer-sandbox + - job: cargo-check-try-runtime artifacts: false extends: - .docker-env @@ -375,27 +361,6 @@ test-full-crypto-feature: - time cargo +nightly build --locked --verbose --no-default-features --features full_crypto - rusty-cachier cache upload -test-wasmer-sandbox: - stage: test - needs: - - job: cargo-check-wasmer-sandbox - artifacts: false - extends: - - .docker-env - - .test-refs-wasmer-sandbox - variables: - RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" - RUST_BACKTRACE: 1 - WASM_BUILD_NO_COLOR: 1 - WASM_BUILD_RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" - CI_JOB_NAME: "test-wasmer-sandbox" - parallel: 3 - script: - - rusty-cachier snapshot create - - echo "Node index - ${CI_NODE_INDEX}. Total amount - ${CI_NODE_TOTAL}" - - time cargo nextest run --locked --release --features runtime-benchmarks,wasmer-sandbox,disable-ui-tests --partition count:${CI_NODE_INDEX}/${CI_NODE_TOTAL} - - if [ ${CI_NODE_INDEX} == 1 ]; then rusty-cachier cache upload; fi - check-rustdoc: stage: test variables: From 0a27e54514338b71204e7321192fbd32dae3b950 Mon Sep 17 00:00:00 2001 From: Muharem Ismailov Date: Wed, 7 Dec 2022 14:57:41 +0100 Subject: [PATCH 008/127] bench assert update (#12866) --- frame/referenda/src/benchmarking.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frame/referenda/src/benchmarking.rs b/frame/referenda/src/benchmarking.rs index b82ac5b3bd..e57b5f9859 100644 --- a/frame/referenda/src/benchmarking.rs +++ b/frame/referenda/src/benchmarking.rs @@ -274,7 +274,8 @@ benchmarks_instance_pallet! { verify { assert_matches!(ReferendumInfoFor::::get(index), Some(ReferendumInfo::Cancelled(_, None, _))); let new_balance = T::Currency::free_balance(&caller); - assert!(new_balance > balance); + // the deposit is zero or make sure it was unreserved. + assert!(T::SubmissionDeposit::get().is_zero() || new_balance > balance); } cancel { From b99f2bc9c912261c64133ac5a3cb698a678beaa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulo=20Silva=20de=20Souza?= <77391175+joao-paulo-parity@users.noreply.github.com> Date: Wed, 7 Dec 2022 15:08:48 -0300 Subject: [PATCH 009/127] Implement crate publishing on CI (#12768) * implement crate publishing from CI * fix indentation * use resource_group for job exclusivity ensure that at most one instance of the publish-crates job is running at any given time to prevent race conditions * correct publish = false * Remove YAML anchors as GitLab's `extends:` doesn't need it * Temporarily force cache upload for the new jobs * Revert `RUSTY_CACHIER_FORCE_UPLOAD` * pin libp2p-tcp=0.37.0 for sc-telemetry * Revert "pin libp2p-tcp=0.37.0 for sc-telemetry" This reverts commit 29146bfad6c31e8cf0e2f17ad92a71bb81a373af. * always collect generated crates * increase timeout for publish-crates-template * Force upload the new job cache again * Revert "Force upload the new job cache again" This reverts commit 5a5feee1b2c51fdef768b25a76be4c3949ec1c99. * reformat * improve timeout explanation * s/usual/average Co-authored-by: Vladimir Istyufeev --- .gitlab-ci.yml | 19 +++++++++ Cargo.lock | 1 + Cargo.toml | 9 +++++ bin/node/bench/Cargo.toml | 1 + bin/node/cli/Cargo.toml | 1 + bin/node/executor/Cargo.toml | 1 + bin/node/inspect/Cargo.toml | 1 + bin/node/primitives/Cargo.toml | 1 + bin/node/rpc/Cargo.toml | 1 + bin/node/runtime/Cargo.toml | 1 + bin/node/testing/Cargo.toml | 2 +- client/merkle-mountain-range/rpc/Cargo.toml | 1 - client/network/bitswap/Cargo.toml | 1 - client/network/common/Cargo.toml | 1 - client/network/light/Cargo.toml | 1 - client/network/sync/Cargo.toml | 1 - client/network/transactions/Cargo.toml | 1 - client/sync-state-rpc/Cargo.toml | 1 - frame/bags-list/Cargo.toml | 1 - frame/bags-list/fuzzer/Cargo.toml | 1 - frame/bags-list/remote-tests/Cargo.toml | 2 +- .../election-provider-multi-phase/Cargo.toml | 1 - frame/election-provider-support/Cargo.toml | 1 - frame/fast-unstake/Cargo.toml | 5 ++- frame/lottery/Cargo.toml | 1 - .../nomination-pools/benchmarking/Cargo.toml | 3 +- .../nomination-pools/test-staking/Cargo.toml | 1 + frame/preimage/Cargo.toml | 1 - frame/root-testing/Cargo.toml | 1 + frame/staking/Cargo.toml | 3 +- frame/state-trie-migration/Cargo.toml | 1 - .../asset-tx-payment/Cargo.toml | 2 + frame/try-runtime/Cargo.toml | 1 - frame/whitelist/Cargo.toml | 1 - primitives/beefy/Cargo.toml | 1 - primitives/weights/Cargo.toml | 1 - scripts/ci/gitlab/pipeline/publish.yml | 38 ++++++++++++++++++ scripts/ci/gitlab/pipeline/test.yml | 13 +++++++ scripts/ci/node-template-release/Cargo.toml | 1 + test-utils/Cargo.toml | 1 + test-utils/client/Cargo.toml | 1 + test-utils/derive/Cargo.toml | 1 + utils/frame/generate-bags/Cargo.toml | 1 - .../generate-bags/node-runtime/Cargo.toml | 2 +- utils/frame/remote-externalities/Cargo.toml | 1 - utils/frame/rpc/client/Cargo.toml | 1 - utils/frame/try-runtime/cli/Cargo.toml | 1 - utils/wasm-builder/src/wasm_project.rs | 39 +++++++++++++++---- 48 files changed, 137 insertions(+), 36 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5b67481554..0a686b06e9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -180,6 +180,25 @@ default: # this job runs only on nightly pipeline with the mentioned variable, against `master` branch - if: $CI_COMMIT_REF_NAME == "master" && $CI_PIPELINE_SOURCE == "schedule" && $PIPELINE == "nightly" +.crates-publishing-template: + stage: test + extends: .docker-env + # collect artifacts even on failure so that we know how the crates were generated (they'll be + # generated to the artifacts folder according to SPUB_TMP further down) + artifacts: + name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}" + when: always + expire_in: 7 days + paths: + - artifacts/ + variables: + CRATESIO_API: https://crates.io/api + CRATESIO_CRATES_OWNER: parity-crate-owner + GH_API: https://api.github.com + REPO: substrate + REPO_OWNER: paritytech + SPUB_TMP: artifacts + #### stage: .pre skip-if-draft: diff --git a/Cargo.lock b/Cargo.lock index f2b394e099..73effefc48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5189,6 +5189,7 @@ dependencies = [ "frame-support", "frame-system", "log", + "pallet-assets", "pallet-balances", "pallet-staking", "pallet-staking-reward-curve", diff --git a/Cargo.toml b/Cargo.toml index 64ceb104c6..12f2ced0d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ members = [ "bin/node/bench", "bin/node/cli", "bin/node/executor", + "bin/node/inspect", "bin/node/primitives", "bin/node/rpc", "bin/node/runtime", @@ -39,11 +40,13 @@ members = [ "client/executor/wasmi", "client/executor/wasmtime", "client/finality-grandpa", + "client/finality-grandpa/rpc", "client/informant", "client/keystore", "client/merkle-mountain-range", "client/merkle-mountain-range/rpc", "client/network", + "client/network/transactions", "client/network-gossip", "client/network/bitswap", "client/network/common", @@ -88,11 +91,13 @@ members = [ "frame/child-bounties", "frame/collective", "frame/contracts", + "frame/contracts/proc-macro", "frame/contracts/primitives", "frame/conviction-voting", "frame/democracy", "frame/fast-unstake", "frame/try-runtime", + "frame/elections-phragmen", "frame/election-provider-multi-phase", "frame/election-provider-support", "frame/election-provider-support/benchmarking", @@ -113,6 +118,7 @@ members = [ "frame/nicks", "frame/node-authorization", "frame/offences", + "frame/offences/benchmarking", "frame/preimage", "frame/proxy", "frame/nomination-pools", @@ -143,6 +149,7 @@ members = [ "frame/support/procedural/tools/derive", "frame/support/test", "frame/support/test/compile_pass", + "frame/support/test/pallet", "frame/system", "frame/system/benchmarking", "frame/system/rpc/runtime-api", @@ -174,6 +181,7 @@ members = [ "primitives/consensus/babe", "primitives/consensus/common", "primitives/consensus/pow", + "primitives/consensus/slots", "primitives/consensus/vrf", "primitives/core", "primitives/core/hashing", @@ -215,6 +223,7 @@ members = [ "primitives/version/proc-macro", "primitives/wasm-interface", "primitives/weights", + "test-utils", "test-utils/client", "test-utils/derive", "test-utils/runtime", diff --git a/bin/node/bench/Cargo.toml b/bin/node/bench/Cargo.toml index 63087215ae..265de731de 100644 --- a/bin/node/bench/Cargo.toml +++ b/bin/node/bench/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/bin/node/cli/Cargo.toml b/bin/node/cli/Cargo.toml index cb2baf9e0d..2ca37e7feb 100644 --- a/bin/node/cli/Cargo.toml +++ b/bin/node/cli/Cargo.toml @@ -9,6 +9,7 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0" default-run = "substrate" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" +publish = false [package.metadata.wasm-pack.profile.release] # `wasm-opt` has some problems on linux, see diff --git a/bin/node/executor/Cargo.toml b/bin/node/executor/Cargo.toml index c814813d0a..8b3add78a9 100644 --- a/bin/node/executor/Cargo.toml +++ b/bin/node/executor/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" +publish = false [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/bin/node/inspect/Cargo.toml b/bin/node/inspect/Cargo.toml index 2b53805a65..a7343b3ca8 100644 --- a/bin/node/inspect/Cargo.toml +++ b/bin/node/inspect/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" +publish = false [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/bin/node/primitives/Cargo.toml b/bin/node/primitives/Cargo.toml index 9be1efd625..1aa0a8f0e2 100644 --- a/bin/node/primitives/Cargo.toml +++ b/bin/node/primitives/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" +publish = false [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/bin/node/rpc/Cargo.toml b/bin/node/rpc/Cargo.toml index a1f37e137c..9d28104136 100644 --- a/bin/node/rpc/Cargo.toml +++ b/bin/node/rpc/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" +publish = false [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 706d2c7720..02a2ae292d 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -8,6 +8,7 @@ build = "build.rs" license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" +publish = false [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/bin/node/testing/Cargo.toml b/bin/node/testing/Cargo.toml index 6944721236..0154a77845 100644 --- a/bin/node/testing/Cargo.toml +++ b/bin/node/testing/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" -publish = true +publish = false [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/merkle-mountain-range/rpc/Cargo.toml b/client/merkle-mountain-range/rpc/Cargo.toml index abbf10c1b7..ca14544000 100644 --- a/client/merkle-mountain-range/rpc/Cargo.toml +++ b/client/merkle-mountain-range/rpc/Cargo.toml @@ -7,7 +7,6 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Node-specific RPC methods for interaction with Merkle Mountain Range pallet." -publish = false [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/network/bitswap/Cargo.toml b/client/network/bitswap/Cargo.toml index 02e12e8f91..099b5cd5e8 100644 --- a/client/network/bitswap/Cargo.toml +++ b/client/network/bitswap/Cargo.toml @@ -8,7 +8,6 @@ edition = "2021" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" documentation = "https://docs.rs/sc-network-bitswap" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/network/common/Cargo.toml b/client/network/common/Cargo.toml index bf4a89c70b..545ae8a8af 100644 --- a/client/network/common/Cargo.toml +++ b/client/network/common/Cargo.toml @@ -8,7 +8,6 @@ edition = "2021" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" documentation = "https://docs.rs/sc-network-sync" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/network/light/Cargo.toml b/client/network/light/Cargo.toml index c7ec6eda7a..5b84a0adde 100644 --- a/client/network/light/Cargo.toml +++ b/client/network/light/Cargo.toml @@ -8,7 +8,6 @@ edition = "2021" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" documentation = "https://docs.rs/sc-network-light" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/network/sync/Cargo.toml b/client/network/sync/Cargo.toml index 263c2d40c2..086ab3c30c 100644 --- a/client/network/sync/Cargo.toml +++ b/client/network/sync/Cargo.toml @@ -8,7 +8,6 @@ edition = "2021" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" documentation = "https://docs.rs/sc-network-sync" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/network/transactions/Cargo.toml b/client/network/transactions/Cargo.toml index 147a86d8de..cb45abca02 100644 --- a/client/network/transactions/Cargo.toml +++ b/client/network/transactions/Cargo.toml @@ -8,7 +8,6 @@ edition = "2021" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" documentation = "https://docs.rs/sc-network-transactions" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/client/sync-state-rpc/Cargo.toml b/client/sync-state-rpc/Cargo.toml index d4e8222911..9730eb56e9 100644 --- a/client/sync-state-rpc/Cargo.toml +++ b/client/sync-state-rpc/Cargo.toml @@ -7,7 +7,6 @@ edition = "2021" license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/bags-list/Cargo.toml b/frame/bags-list/Cargo.toml index 10086635ef..52dd14b7d0 100644 --- a/frame/bags-list/Cargo.toml +++ b/frame/bags-list/Cargo.toml @@ -7,7 +7,6 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet bags list" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/bags-list/fuzzer/Cargo.toml b/frame/bags-list/fuzzer/Cargo.toml index 0f10077dcb..fc2334bea5 100644 --- a/frame/bags-list/fuzzer/Cargo.toml +++ b/frame/bags-list/fuzzer/Cargo.toml @@ -7,7 +7,6 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Fuzzer for FRAME pallet bags list" -readme = "README.md" publish = false [dependencies] diff --git a/frame/bags-list/remote-tests/Cargo.toml b/frame/bags-list/remote-tests/Cargo.toml index 941076753b..9fb6d0154b 100644 --- a/frame/bags-list/remote-tests/Cargo.toml +++ b/frame/bags-list/remote-tests/Cargo.toml @@ -7,7 +7,7 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet bags list remote test" -readme = "README.md" +publish = false [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/election-provider-multi-phase/Cargo.toml b/frame/election-provider-multi-phase/Cargo.toml index ba460055e2..42cd682a0f 100644 --- a/frame/election-provider-multi-phase/Cargo.toml +++ b/frame/election-provider-multi-phase/Cargo.toml @@ -7,7 +7,6 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "PALLET two phase election providers" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/election-provider-support/Cargo.toml b/frame/election-provider-support/Cargo.toml index 754aa8d37a..b9584c899e 100644 --- a/frame/election-provider-support/Cargo.toml +++ b/frame/election-provider-support/Cargo.toml @@ -7,7 +7,6 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "election provider supporting traits" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/fast-unstake/Cargo.toml b/frame/fast-unstake/Cargo.toml index c48ff862b7..61bc823cc1 100644 --- a/frame/fast-unstake/Cargo.toml +++ b/frame/fast-unstake/Cargo.toml @@ -7,7 +7,6 @@ license = "Unlicense" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME fast unstake pallet" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -26,7 +25,10 @@ sp-std = { version = "5.0.0", default-features = false, path = "../../primitives sp-staking = { default-features = false, path = "../../primitives/staking" } frame-election-provider-support = { default-features = false, path = "../election-provider-support" } +# optional dependencies for cargo features frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } +pallet-staking = { default-features = false, optional = true, path = "../staking" } +pallet-assets = { default-features = false, optional = true, path = "../assets" } [dev-dependencies] pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../staking/reward-curve" } @@ -36,6 +38,7 @@ sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } pallet-staking = { path = "../staking" } pallet-balances = { path = "../balances" } pallet-timestamp = { path = "../timestamp" } +pallet-assets = { path = "../assets" } [features] diff --git a/frame/lottery/Cargo.toml b/frame/lottery/Cargo.toml index 14ec21a563..9ac69d63eb 100644 --- a/frame/lottery/Cargo.toml +++ b/frame/lottery/Cargo.toml @@ -7,7 +7,6 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME Participation Lottery Pallet" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/nomination-pools/benchmarking/Cargo.toml b/frame/nomination-pools/benchmarking/Cargo.toml index ac470f04a6..be52d9777a 100644 --- a/frame/nomination-pools/benchmarking/Cargo.toml +++ b/frame/nomination-pools/benchmarking/Cargo.toml @@ -31,6 +31,7 @@ sp-runtime = { version = "7.0.0", default-features = false, path = "../../../pri sp-runtime-interface = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime-interface" } sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/staking" } sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } +sp-io = { optional = true, default-features = false, path = "../../../primitives/io" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../../balances" } @@ -54,7 +55,7 @@ std = [ "sp-runtime-interface/std", "sp-io/std", "sp-staking/std", - "sp-std/std", + "sp-std/std", ] runtime-benchmarks = [ diff --git a/frame/nomination-pools/test-staking/Cargo.toml b/frame/nomination-pools/test-staking/Cargo.toml index 8350fdd05c..a45c7852d4 100644 --- a/frame/nomination-pools/test-staking/Cargo.toml +++ b/frame/nomination-pools/test-staking/Cargo.toml @@ -7,6 +7,7 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME nomination pools pallet tests with the staking pallet" +publish = false [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/preimage/Cargo.toml b/frame/preimage/Cargo.toml index 3315405809..def39d61d5 100644 --- a/frame/preimage/Cargo.toml +++ b/frame/preimage/Cargo.toml @@ -7,7 +7,6 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet for storing preimages of hashes" -readme = "README.md" [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } diff --git a/frame/root-testing/Cargo.toml b/frame/root-testing/Cargo.toml index bc474f4f09..4d3f70c5d0 100644 --- a/frame/root-testing/Cargo.toml +++ b/frame/root-testing/Cargo.toml @@ -8,6 +8,7 @@ homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME root testing pallet" readme = "README.md" +publish = false [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/staking/Cargo.toml b/frame/staking/Cargo.toml index 466883f868..3ad63ad94a 100644 --- a/frame/staking/Cargo.toml +++ b/frame/staking/Cargo.toml @@ -32,9 +32,10 @@ sp-application-crypto = { version = "7.0.0", default-features = false, path = ". frame-election-provider-support = { version = "4.0.0-dev", default-features = false, path = "../election-provider-support" } log = { version = "0.4.17", default-features = false } -# Optional imports for benchmarking +# optional dependencies for cargo features frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } rand_chacha = { version = "0.2", default-features = false, optional = true } +pallet-bags-list = { default-features = false, optional = true, path = "../bags-list" } [dev-dependencies] sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } diff --git a/frame/state-trie-migration/Cargo.toml b/frame/state-trie-migration/Cargo.toml index 8d10a34077..90c8f426d0 100644 --- a/frame/state-trie-migration/Cargo.toml +++ b/frame/state-trie-migration/Cargo.toml @@ -7,7 +7,6 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet migration of trie" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/transaction-payment/asset-tx-payment/Cargo.toml b/frame/transaction-payment/asset-tx-payment/Cargo.toml index cfc56c91ef..b192c4e9cd 100644 --- a/frame/transaction-payment/asset-tx-payment/Cargo.toml +++ b/frame/transaction-payment/asset-tx-payment/Cargo.toml @@ -19,10 +19,12 @@ sp-io = { version = "7.0.0", default-features = false, path = "../../../primitiv sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } +# optional dependencies for cargo features frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } pallet-transaction-payment = { version = "4.0.0-dev", default-features = false, path = ".." } frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../benchmarking", optional = true } +pallet-assets = { default-features = false, optional = true, path = "../../assets" } # Other dependencies codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } diff --git a/frame/try-runtime/Cargo.toml b/frame/try-runtime/Cargo.toml index 247505e613..87aca0d1ed 100644 --- a/frame/try-runtime/Cargo.toml +++ b/frame/try-runtime/Cargo.toml @@ -7,7 +7,6 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet for democracy" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/frame/whitelist/Cargo.toml b/frame/whitelist/Cargo.toml index 94fd0db007..f3f44d3187 100644 --- a/frame/whitelist/Cargo.toml +++ b/frame/whitelist/Cargo.toml @@ -7,7 +7,6 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet for whitelisting call, and dispatch from specific origin" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/beefy/Cargo.toml b/primitives/beefy/Cargo.toml index a7ff212a09..b286d9878b 100644 --- a/primitives/beefy/Cargo.toml +++ b/primitives/beefy/Cargo.toml @@ -7,7 +7,6 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate" description = "Primitives for BEEFY protocol." -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/weights/Cargo.toml b/primitives/weights/Cargo.toml index 501a2c3b0a..0099645836 100644 --- a/primitives/weights/Cargo.toml +++ b/primitives/weights/Cargo.toml @@ -8,7 +8,6 @@ homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Types and traits for interfacing between the host and the wasm runtime." documentation = "https://docs.rs/sp-wasm-interface" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/scripts/ci/gitlab/pipeline/publish.yml b/scripts/ci/gitlab/pipeline/publish.yml index 9d242d8fb5..74d2adb3f4 100644 --- a/scripts/ci/gitlab/pipeline/publish.yml +++ b/scripts/ci/gitlab/pipeline/publish.yml @@ -161,3 +161,41 @@ update-node-template: --template-path "bin/node-template" --github-api-token "$GITHUB_TOKEN" --polkadot-branch "$CI_COMMIT_REF_NAME" + +.publish-crates-template: + stage: publish + extends: .crates-publishing-template + # We don't want multiple jobs racing to publish crates as it's redundant and they might overwrite + # the releases of one another. Use resource_group to ensure that at most one instance of this job + # is running at any given time. + resource_group: crates-publishing + variables: + # crates.io rate limits crates publishing by 1 per minute, so a delay needs to be inserted + # slightly higher than that after publishing each crate. The value is specified in seconds. + SPUB_AFTER_PUBLISH_DELAY: 64 + # We might have to publish lots of crates at a time. Given the 1 minute delay introduced above and + # taking into account the 202 (as of Dec 07, 2022) publishable Substrate crates, that would equate + # to roughly 202 minutes of delay, or 3h and 22 minutes. As such, the job needs to have a much + # higher timeout than average. + timeout: 5h + script: + - rusty-cachier snapshot create + - git clone + --depth 1 + --branch "$RELENG_SCRIPTS_BRANCH" + https://github.com/paritytech/releng-scripts.git + - CRATESIO_TARGET_INSTANCE=default ./releng-scripts/publish-crates + - rusty-cachier cache upload + +publish-crates: + extends: .publish-crates-template + needs: + - job: publish-crates-locally + artifacts: false + rules: + - if: $CI_COMMIT_REF_NAME == "master" + +publish-crates-manual: + extends: .publish-crates-template + when: manual + allow_failure: true diff --git a/scripts/ci/gitlab/pipeline/test.yml b/scripts/ci/gitlab/pipeline/test.yml index 90b43100bc..ea1240af8b 100644 --- a/scripts/ci/gitlab/pipeline/test.yml +++ b/scripts/ci/gitlab/pipeline/test.yml @@ -394,6 +394,19 @@ cargo-check-each-crate: - if [ "$CI_NODE_INDEX" == 1 ]; then rusty-cachier cache upload; fi parallel: 2 +publish-crates-locally: + extends: + - .test-refs + - .crates-publishing-template + script: + - rusty-cachier snapshot create + - git clone + --depth 1 + --branch "$RELENG_SCRIPTS_BRANCH" + https://github.com/paritytech/releng-scripts.git + - CRATESIO_TARGET_INSTANCE=local ./releng-scripts/publish-crates + - rusty-cachier cache upload + cargo-check-each-crate-macos: stage: test extends: diff --git a/scripts/ci/node-template-release/Cargo.toml b/scripts/ci/node-template-release/Cargo.toml index 0800b17536..668e0f3f62 100644 --- a/scripts/ci/node-template-release/Cargo.toml +++ b/scripts/ci/node-template-release/Cargo.toml @@ -5,6 +5,7 @@ authors = ["Parity Technologies "] edition = "2021" license = "GPL-3.0" homepage = "https://substrate.io" +publish = false [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/test-utils/Cargo.toml b/test-utils/Cargo.toml index c7201da23a..cd4839fa1c 100644 --- a/test-utils/Cargo.toml +++ b/test-utils/Cargo.toml @@ -7,6 +7,7 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Substrate test utilities" +publish = false [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/test-utils/client/Cargo.toml b/test-utils/client/Cargo.toml index 2c61f4d3ce..106ec21d79 100644 --- a/test-utils/client/Cargo.toml +++ b/test-utils/client/Cargo.toml @@ -1,5 +1,6 @@ [package] name = "substrate-test-client" +description = "Client testing utilities" version = "2.0.1" authors = ["Parity Technologies "] edition = "2021" diff --git a/test-utils/derive/Cargo.toml b/test-utils/derive/Cargo.toml index 4494b55220..b55ace822c 100644 --- a/test-utils/derive/Cargo.toml +++ b/test-utils/derive/Cargo.toml @@ -7,6 +7,7 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Substrate test utilities macros" +publish = false [dependencies] proc-macro-crate = "1.1.3" diff --git a/utils/frame/generate-bags/Cargo.toml b/utils/frame/generate-bags/Cargo.toml index 0f3ff31756..04d598c784 100644 --- a/utils/frame/generate-bags/Cargo.toml +++ b/utils/frame/generate-bags/Cargo.toml @@ -7,7 +7,6 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Bag threshold generation script for pallet-bag-list" -readme = "README.md" [dependencies] # FRAME diff --git a/utils/frame/generate-bags/node-runtime/Cargo.toml b/utils/frame/generate-bags/node-runtime/Cargo.toml index 6cc14a0595..46d999daba 100644 --- a/utils/frame/generate-bags/node-runtime/Cargo.toml +++ b/utils/frame/generate-bags/node-runtime/Cargo.toml @@ -7,7 +7,7 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Bag threshold generation script for pallet-bag-list and kitchensink-runtime." -readme = "README.md" +publish = false [dependencies] kitchensink-runtime = { version = "3.0.0-dev", path = "../../../../bin/node/runtime" } diff --git a/utils/frame/remote-externalities/Cargo.toml b/utils/frame/remote-externalities/Cargo.toml index a39dc9d386..4cb847867f 100644 --- a/utils/frame/remote-externalities/Cargo.toml +++ b/utils/frame/remote-externalities/Cargo.toml @@ -7,7 +7,6 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "An externalities provided environment that can load itself from remote nodes or cached files" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/utils/frame/rpc/client/Cargo.toml b/utils/frame/rpc/client/Cargo.toml index 371996a4ed..bbe8879818 100644 --- a/utils/frame/rpc/client/Cargo.toml +++ b/utils/frame/rpc/client/Cargo.toml @@ -7,7 +7,6 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Shared JSON-RPC client" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/utils/frame/try-runtime/cli/Cargo.toml b/utils/frame/try-runtime/cli/Cargo.toml index acac966aa1..2b095fc941 100644 --- a/utils/frame/try-runtime/cli/Cargo.toml +++ b/utils/frame/try-runtime/cli/Cargo.toml @@ -7,7 +7,6 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Cli command runtime testing and dry-running" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/utils/wasm-builder/src/wasm_project.rs b/utils/wasm-builder/src/wasm_project.rs index 0721967641..7688069dd7 100644 --- a/utils/wasm-builder/src/wasm_project.rs +++ b/utils/wasm-builder/src/wasm_project.rs @@ -365,26 +365,47 @@ fn create_project_cargo_toml( ); } -/// Find a package by the given `manifest_path` in the metadata. +/// Find a package by the given `manifest_path` in the metadata. In case it can't be found by its +/// manifest_path, fallback to finding it by name; this is necessary during publish because the +/// package's manifest path will be *generated* within a specific packaging directory, thus it won't +/// be found by its original path anymore. /// /// Panics if the package could not be found. fn find_package_by_manifest_path<'a>( + pkg_name: &str, manifest_path: &Path, crate_metadata: &'a cargo_metadata::Metadata, ) -> &'a cargo_metadata::Package { - crate_metadata + if let Some(pkg) = crate_metadata.packages.iter().find(|p| p.manifest_path == manifest_path) { + return pkg + } + let pkgs_by_name = crate_metadata .packages .iter() - .find(|p| p.manifest_path == manifest_path) - .expect("Wasm project exists in its own metadata; qed") + .filter(|p| p.name == pkg_name) + .collect::>(); + let mut pkgs = pkgs_by_name.iter(); + if let Some(pkg) = pkgs.next() { + if pkgs.next().is_some() { + panic!( + "Found multiple packages matching the name {pkg_name} ({manifest_path:?}): {:?}", + pkgs_by_name + ); + } else { + return pkg + } + } else { + panic!("Failed to find entry for package {pkg_name} ({manifest_path:?})"); + } } /// Get a list of enabled features for the project. fn project_enabled_features( + pkg_name: &str, cargo_manifest: &Path, crate_metadata: &cargo_metadata::Metadata, ) -> Vec { - let package = find_package_by_manifest_path(cargo_manifest, crate_metadata); + let package = find_package_by_manifest_path(pkg_name, cargo_manifest, crate_metadata); let std_enabled = package.features.get("std"); @@ -427,10 +448,11 @@ fn project_enabled_features( /// Returns if the project has the `runtime-wasm` feature fn has_runtime_wasm_feature_declared( + pkg_name: &str, cargo_manifest: &Path, crate_metadata: &cargo_metadata::Metadata, ) -> bool { - let package = find_package_by_manifest_path(cargo_manifest, crate_metadata); + let package = find_package_by_manifest_path(pkg_name, cargo_manifest, crate_metadata); package.features.keys().any(|k| k == "runtime-wasm") } @@ -455,9 +477,10 @@ fn create_project( fs::create_dir_all(wasm_project_folder.join("src")) .expect("Wasm project dir create can not fail; qed"); - let mut enabled_features = project_enabled_features(project_cargo_toml, crate_metadata); + let mut enabled_features = + project_enabled_features(&crate_name, project_cargo_toml, crate_metadata); - if has_runtime_wasm_feature_declared(project_cargo_toml, crate_metadata) { + if has_runtime_wasm_feature_declared(&crate_name, project_cargo_toml, crate_metadata) { enabled_features.push("runtime-wasm".into()); } From 6e73c85b7ddbc2ec2a9f6629ddc06aca2f83bcf3 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Wed, 7 Dec 2022 23:44:40 +0100 Subject: [PATCH 010/127] zombienet: warp-sync integration test added (#12675) * zombienet: warp-sync integration test added * spelling * Readme corrected * dir name updated * Check second phase of warp sync * zombienet pipeline enable + naive test network * zombienet stage added * paritypr/substrate-debug image added for zombienet testing * debugs added * debugs added * buildah problem fixed * rollback * runner tag * test name corrected * dir renamed (regex problem) * common code clean up * common code clean up * fix * warp sync test improvements * full sha used as short is too short (https://gitlab.parity.io/parity/mirrors/substrate/-/jobs/2051228#L38) * disable tracing for nodes * COMMON_USER -> DOCKERIO_USER * refs reworked * paritypr/substrate image used * DOCKERIO -> DOCKER * generate-ws-db toml cleanup * improvements * fix * raw chain spec used * zombienet v1.3.18 used * zombienet: warp sync test enabled * chain-spec path corrected * log parsing improved Checking if log does not container error or verification failed messages * warp sync test: removed validators * fix * review remarks applied * dir test name changed: 0000_block_building -> 0000-block-building * transaction finalized test added * transaction finalized test: error handling improved * trigger CI job * trigger CI job * trigger CI job * trigger CI job * Explicitly touch `version.rs` to invalidate the related cache * zombienet add logs as artifacts * Revert "Explicitly touch `version.rs` to invalidate the related cache" This reverts commit 9d00ccfe897a280581156c281961a32665dba6d5. * file naming changed Co-authored-by: parity-processbot <> Co-authored-by: Vladimir Istyufeev Co-authored-by: Javier Viola --- .gitlab-ci.yml | 18 ++ scripts/ci/docker/subkey.Dockerfile | 3 +- scripts/ci/docker/substrate.Dockerfile | 3 +- scripts/ci/gitlab/pipeline/build.yml | 2 +- scripts/ci/gitlab/pipeline/publish.yml | 51 ++++- scripts/ci/gitlab/pipeline/zombienet.yml | 53 +++++ .../0000-block-building/block-building.toml | 15 ++ .../0000-block-building/block-building.zndsl | 20 ++ .../transaction-gets-finalized.js | 59 ++++++ zombienet/0001-basic-warp-sync/README.md | 101 +++++++++ .../0001-basic-warp-sync/chain-spec.json | 192 ++++++++++++++++++ .../generate-warp-sync-database.toml | 17 ++ .../0001-basic-warp-sync/test-warp-sync.toml | 30 +++ .../0001-basic-warp-sync/test-warp-sync.zndsl | 35 ++++ 14 files changed, 587 insertions(+), 12 deletions(-) create mode 100644 scripts/ci/gitlab/pipeline/zombienet.yml create mode 100644 zombienet/0000-block-building/block-building.toml create mode 100644 zombienet/0000-block-building/block-building.zndsl create mode 100644 zombienet/0000-block-building/transaction-gets-finalized.js create mode 100644 zombienet/0001-basic-warp-sync/README.md create mode 100644 zombienet/0001-basic-warp-sync/chain-spec.json create mode 100644 zombienet/0001-basic-warp-sync/generate-warp-sync-database.toml create mode 100644 zombienet/0001-basic-warp-sync/test-warp-sync.toml create mode 100644 zombienet/0001-basic-warp-sync/test-warp-sync.zndsl diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0a686b06e9..25d61cf349 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -33,6 +33,7 @@ stages: - test - build - publish + - zombienet - deploy - notify @@ -52,6 +53,7 @@ variables: BUILDAH_IMAGE: "quay.io/buildah/stable:v1.27" RUSTY_CACHIER_SINGLE_BRANCH: master RUSTY_CACHIER_DONT_OPERATE_ON_MAIN_BRANCH: "true" + ZOMBIENET_IMAGE: "docker.io/paritytech/zombienet:v1.3.22" default: retry: @@ -166,7 +168,17 @@ default: - if: $CI_PIPELINE_SOURCE == "schedule" - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs +.publish-refs: + rules: + - if: $CI_PIPELINE_SOURCE == "pipeline" + when: never + - if: $CI_PIPELINE_SOURCE == "web" + - if: $CI_PIPELINE_SOURCE == "schedule" + - if: $CI_COMMIT_REF_NAME == "master" + - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 + .build-refs: + # publish-refs + PRs rules: - if: $CI_PIPELINE_SOURCE == "pipeline" when: never @@ -174,6 +186,10 @@ default: - if: $CI_PIPELINE_SOURCE == "schedule" - if: $CI_COMMIT_REF_NAME == "master" - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 + - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs + +.zombienet-refs: + extends: .build-refs .nightly-pipeline: rules: @@ -224,6 +240,8 @@ include: - scripts/ci/gitlab/pipeline/build.yml # publish jobs - scripts/ci/gitlab/pipeline/publish.yml + # zombienet jobs + - scripts/ci/gitlab/pipeline/zombienet.yml #### stage: deploy diff --git a/scripts/ci/docker/subkey.Dockerfile b/scripts/ci/docker/subkey.Dockerfile index 3483502845..a24595d915 100644 --- a/scripts/ci/docker/subkey.Dockerfile +++ b/scripts/ci/docker/subkey.Dockerfile @@ -3,10 +3,11 @@ FROM docker.io/library/ubuntu:20.04 # metadata ARG VCS_REF ARG BUILD_DATE +ARG IMAGE_NAME LABEL io.parity.image.authors="devops-team@parity.io" \ io.parity.image.vendor="Parity Technologies" \ - io.parity.image.title="parity/subkey" \ + io.parity.image.title="${IMAGE_NAME}" \ io.parity.image.description="Subkey: key generating utility for Substrate." \ io.parity.image.source="https://github.com/paritytech/substrate/blob/${VCS_REF}/scripts/ci/docker/subkey.Dockerfile" \ io.parity.image.revision="${VCS_REF}" \ diff --git a/scripts/ci/docker/substrate.Dockerfile b/scripts/ci/docker/substrate.Dockerfile index b4c103ed52..5c7909afc0 100644 --- a/scripts/ci/docker/substrate.Dockerfile +++ b/scripts/ci/docker/substrate.Dockerfile @@ -3,10 +3,11 @@ FROM docker.io/library/ubuntu:20.04 # metadata ARG VCS_REF ARG BUILD_DATE +ARG IMAGE_NAME LABEL io.parity.image.authors="devops-team@parity.io" \ io.parity.image.vendor="Parity Technologies" \ - io.parity.image.title="parity/substrate" \ + io.parity.image.title="${IMAGE_NAME}" \ io.parity.image.description="Substrate: The platform for blockchain innovators." \ io.parity.image.source="https://github.com/paritytech/substrate/blob/${VCS_REF}/scripts/ci/docker/Dockerfile" \ io.parity.image.revision="${VCS_REF}" \ diff --git a/scripts/ci/gitlab/pipeline/build.yml b/scripts/ci/gitlab/pipeline/build.yml index 906c1bcbe2..5bbc3fb8f7 100644 --- a/scripts/ci/gitlab/pipeline/build.yml +++ b/scripts/ci/gitlab/pipeline/build.yml @@ -86,7 +86,7 @@ build-linux-substrate: extends: - .collect-artifacts - .docker-env - - .build-refs + - .publish-refs variables: # this variable gets overriden by "rusty-cachier environment inject", use the value as default CARGO_TARGET_DIR: "$CI_PROJECT_DIR/target" diff --git a/scripts/ci/gitlab/pipeline/publish.yml b/scripts/ci/gitlab/pipeline/publish.yml index 74d2adb3f4..9053035a61 100644 --- a/scripts/ci/gitlab/pipeline/publish.yml +++ b/scripts/ci/gitlab/pipeline/publish.yml @@ -2,32 +2,33 @@ # This file is part of .gitlab-ci.yml # Here are all jobs that are executed during "publish" stage -.build-push-docker-image: +.build-push-docker-image-common: extends: - - .build-refs - .kubernetes-env + stage: publish variables: CI_IMAGE: $BUILDAH_IMAGE GIT_STRATEGY: none DOCKERFILE: $PRODUCT.Dockerfile - IMAGE_NAME: docker.io/parity/$PRODUCT + IMAGE_NAME: docker.io/$IMAGE_PATH before_script: - cd ./artifacts/$PRODUCT/ - VERSION="$(cat ./VERSION)" - echo "${PRODUCT} version = ${VERSION}" - test -z "${VERSION}" && exit 1 script: - - test "$Docker_Hub_User_Parity" -a "$Docker_Hub_Pass_Parity" || + - test "$DOCKER_USER" -a "$DOCKER_PASS" || ( echo "no docker credentials provided"; exit 1 ) - buildah bud --format=docker --build-arg VCS_REF="${CI_COMMIT_SHA}" --build-arg BUILD_DATE="$(date -u '+%Y-%m-%dT%H:%M:%SZ')" + --build-arg IMAGE_NAME="${IMAGE_PATH}" --tag "$IMAGE_NAME:$VERSION" --tag "$IMAGE_NAME:latest" --file "$DOCKERFILE" . - - echo "$Docker_Hub_Pass_Parity" | - buildah login --username "$Docker_Hub_User_Parity" --password-stdin docker.io + - echo "$DOCKER_PASS" | + buildah login --username "$DOCKER_USER" --password-stdin docker.io - buildah info - buildah push --format=v2s2 "$IMAGE_NAME:$VERSION" - buildah push --format=v2s2 "$IMAGE_NAME:latest" @@ -38,8 +39,27 @@ - echo "SUBSTRATE_IMAGE_TAG=${IMAGE_TAG}" | tee -a ./artifacts/$PRODUCT/build.env - cat ./artifacts/$PRODUCT/build.env +.build-push-docker-image: + extends: + - .publish-refs + - .build-push-docker-image-common + variables: + IMAGE_PATH: parity/$PRODUCT + DOCKER_USER: $Docker_Hub_User_Parity + DOCKER_PASS: $Docker_Hub_Pass_Parity + + +# publish image to docker.io/paritypr, (e.g. for later use in zombienet testing) +.build-push-image-temporary: + extends: + - .build-refs + - .build-push-docker-image-common + variables: + IMAGE_PATH: paritypr/$PRODUCT + DOCKER_USER: $PARITYPR_USER + DOCKER_PASS: $PARITYPR_PASS + publish-docker-substrate: - stage: publish extends: .build-push-docker-image needs: - job: build-linux-substrate @@ -47,8 +67,21 @@ publish-docker-substrate: variables: PRODUCT: substrate +publish-docker-substrate-temporary: + extends: .build-push-image-temporary + needs: + - job: build-linux-substrate + artifacts: true + variables: + PRODUCT: substrate + artifacts: + reports: + # this artifact is used in zombienet-tests job + # https://docs.gitlab.com/ee/ci/multi_project_pipelines.html#with-variable-inheritance + dotenv: ./artifacts/$PRODUCT/build.env + expire_in: 24h + publish-docker-subkey: - stage: publish extends: .build-push-docker-image needs: - job: build-subkey-linux @@ -59,7 +92,7 @@ publish-docker-subkey: publish-s3-release: stage: publish extends: - - .build-refs + - .publish-refs - .kubernetes-env needs: - job: build-linux-substrate diff --git a/scripts/ci/gitlab/pipeline/zombienet.yml b/scripts/ci/gitlab/pipeline/zombienet.yml new file mode 100644 index 0000000000..8d772ff51f --- /dev/null +++ b/scripts/ci/gitlab/pipeline/zombienet.yml @@ -0,0 +1,53 @@ +# This file is part of .gitlab-ci.yml +# Here are all jobs that are executed during "zombienet" stage + +# common settings for all zombienet jobs +.zombienet-common: + before_script: + - echo "Zombie-net Tests Config" + - echo "${ZOMBIENET_IMAGE}" + - echo "${SUBSTRATE_IMAGE_NAME} ${SUBSTRATE_IMAGE_TAG}" + - echo "${GH_DIR}" + - export DEBUG=zombie,zombie::network-node + - export ZOMBIENET_INTEGRATION_TEST_IMAGE=${SUBSTRATE_IMAGE_NAME}:${SUBSTRATE_IMAGE_TAG} + - echo "${ZOMBIENET_INTEGRATION_TEST_IMAGE}" + stage: zombienet + image: "${ZOMBIENET_IMAGE}" + needs: + - job: publish-docker-substrate-temporary + extends: + - .kubernetes-env + - .zombienet-refs + variables: + GH_DIR: "https://github.com/paritytech/substrate/tree/${CI_COMMIT_SHA}/zombienet" + FF_DISABLE_UMASK_FOR_DOCKER_EXECUTOR: 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/ + allow_failure: true + retry: 2 + tags: + - zombienet-polkadot-integration-test + +zombienet-0000-block-building: + extends: + - .zombienet-common + script: + - /home/nonroot/zombie-net/scripts/ci/run-test-env-manager.sh + --github-remote-dir="${GH_DIR}/0000-block-building" + --test="block-building.zndsl" + + +zombienet-0001-basic-warp-sync: + extends: + - .zombienet-common + script: + - /home/nonroot/zombie-net/scripts/ci/run-test-env-manager.sh + --github-remote-dir="${GH_DIR}/0001-basic-warp-sync" + --test="test-warp-sync.zndsl" diff --git a/zombienet/0000-block-building/block-building.toml b/zombienet/0000-block-building/block-building.toml new file mode 100644 index 0000000000..42ebbb58ac --- /dev/null +++ b/zombienet/0000-block-building/block-building.toml @@ -0,0 +1,15 @@ +[settings] +enable_tracing = false + +[relaychain] +default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" +default_command = "substrate" +chain = "local" + + [[relaychain.nodes]] + name = "alice" + validator = true + + [[relaychain.nodes]] + name = "bob" + validator = true diff --git a/zombienet/0000-block-building/block-building.zndsl b/zombienet/0000-block-building/block-building.zndsl new file mode 100644 index 0000000000..c53e50915c --- /dev/null +++ b/zombienet/0000-block-building/block-building.zndsl @@ -0,0 +1,20 @@ +Description: Block building +Network: ./block-building.toml +Creds: config + +alice: is up +bob: is up + +alice: reports node_roles is 4 +bob: reports node_roles is 4 + +alice: reports peers count is at least 1 +bob: reports peers count is at least 1 + +alice: reports block height is at least 5 within 20 seconds +bob: reports block height is at least 5 within 20 seconds + +alice: count of log lines containing "error" is 0 within 2 seconds +bob: count of log lines containing "error" is 0 within 2 seconds + +alice: js-script ./transaction-gets-finalized.js within 30 seconds diff --git a/zombienet/0000-block-building/transaction-gets-finalized.js b/zombienet/0000-block-building/transaction-gets-finalized.js new file mode 100644 index 0000000000..a38cf603fc --- /dev/null +++ b/zombienet/0000-block-building/transaction-gets-finalized.js @@ -0,0 +1,59 @@ +//based on: https://polkadot.js.org/docs/api/examples/promise/transfer-events + +const assert = require("assert"); + +async function run(nodeName, networkInfo, args) { + const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName]; + const api = await zombie.connect(wsUri, userDefinedTypes); + + // Construct the keyring after the API (crypto has an async init) + const keyring = new zombie.Keyring({ type: "sr25519" }); + + // Add Alice to our keyring with a hard-derivation path (empty phrase, so uses dev) + const alice = keyring.addFromUri('//Alice'); + const bob = '5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty'; + + // Create a extrinsic, transferring 10^20 units to Bob + const transfer = api.tx.balances.transfer(bob, 10n**20n); + + let transaction_success_event = false; + try { + await new Promise( async (resolve, reject) => { + const unsubscribe = await transfer + .signAndSend(alice, { nonce: -1 }, ({ events = [], status }) => { + console.log('Transaction status:', status.type); + + if (status.isInBlock) { + console.log('Included at block hash', status.asInBlock.toHex()); + console.log('Events:'); + + events.forEach(({ event: { data, method, section }, phase }) => { + console.log('\t', phase.toString(), `: ${section}.${method}`, data.toString()); + + if (section=="system" && method =="ExtrinsicSuccess") { + transaction_success_event = true; + } + }); + } else if (status.isFinalized) { + console.log('Finalized block hash', status.asFinalized.toHex()); + unsubscribe(); + if (transaction_success_event) { + resolve(); + } else { + reject("ExtrinsicSuccess has not been seen"); + } + } else if (status.isError) { + unsubscribe(); + reject("Transaction status.isError"); + } + + }); + }); + } catch (error) { + assert.fail("Transfer promise failed, error: " + error); + } + + assert.ok("test passed"); +} + +module.exports = { run } diff --git a/zombienet/0001-basic-warp-sync/README.md b/zombienet/0001-basic-warp-sync/README.md new file mode 100644 index 0000000000..7550ca8923 --- /dev/null +++ b/zombienet/0001-basic-warp-sync/README.md @@ -0,0 +1,101 @@ +# Test design +The `warp-sync` test works on predefined database which is stored in the cloud and +fetched by the test. `alice` and `bob` nodes are spun up using this database snapshot in full node mode. + +As `warp-sync` requires at least 3 peers, the test spawns the `charlie` full node which uses the same database snapshot. + +The `dave` node executed with `--sync warp` syncs database with the rest of the network. + +# How to prepare database +Database was prepared using the following zombienet file (`generate-warp-sync-database.toml`): +``` +[relaychain] +default_image = "docker.io/parity/substrate:master" +default_command = "substrate" + +chain = "gen-db" + +chain_spec_path = "chain-spec.json" + + [[relaychain.nodes]] + name = "alice" + validator = true + + [[relaychain.nodes]] + name = "bob" + validator = true +``` + +The zombienet shall be executed with the following command, and run for some period of time to allow for few grandpa eras. +``` +./zombienet-linux spawn --dir ./db-test-gen --provider native generate-warp-sync-database.toml +``` + +Once the zombienet is stopped, the database snapshot +(`{alice,bob}/data/chains/local_testnet/db/` dirs) was created using the following +commands: +```bash +mkdir -p db-snapshot/{alice,bob}/data/chains/local_testnet/db/ +cp -r db-test-gen/alice/data/chains/local_testnet/db/full db-snapshot/alice/data/chains/local_testnet/db/ +cp -r db-test-gen/bob/data/chains/local_testnet/db/full db-snapshot/bob/data/chains/local_testnet/db/ +``` + +The file format should be `tar.gz`. File shall contain `local_testnet` folder and its subfolders, e.g.: +``` +$ tar tzf chains.tgz | head +local_testnet/ +local_testnet/db/ +local_testnet/db/full/ +... +local_testnet/db/full/000469.log +``` + +Sample command to prepare archive: +``` +tar -C db-snapshot/alice/data/chains/ -czf chains.tgz local_testnet +``` + +Also refer to: [zombienet#578](https://github.com/paritytech/zombienet/issues/578) + +The `raw` chain-spec shall also be saved: `db-test-gen/gen-db-raw.json`. + +# Where to upload database +The access to this [bucket](https://console.cloud.google.com/storage/browser/zombienet-db-snaps/) is required. + +Sample public path is: `https://storage.googleapis.com/zombienet-db-snaps/substrate/0001-basic-warp-sync/chains-0bb3f0be2ce41b5615b224215bcc8363aa0416a6.tgz`. + +The database file path should be `substrate/XXXX-test-name/file-SHA1SUM.tgz`, where `SHA1SUM` is a `sha1sum` of the file. + +# Chain spec +Chain spec was simply built with: +``` +substrate build-spec --chain=local > chain-spec.json +``` + +Please note that `chain-spec.json` committed into repository is `raw` version produced by `zombienet` during database snapshot generation. Zombienet applies some modifications to plain versions of chain-spec. + +# Run the test +Test can be run with the following command: +``` +zombienet-linux test --dir db-snapshot --provider native test-warp-sync.zndsl +``` + +*NOTE*: currently blocked by: [zombienet#578](https://github.com/paritytech/zombienet/issues/578) + + +# Save some time hack +Substrate can be patched to reduce the grandpa session period. +``` +diff --git a/bin/node/runtime/src/constants.rs b/bin/node/runtime/src/constants.rs +index 23fb13cfb0..89f8646291 100644 +--- a/bin/node/runtime/src/constants.rs ++++ b/bin/node/runtime/src/constants.rs +@@ -63,7 +63,7 @@ pub mod time { + + // NOTE: Currently it is not possible to change the epoch duration after the chain has started. + // Attempting to do so will brick block production. +- pub const EPOCH_DURATION_IN_BLOCKS: BlockNumber = 10 * MINUTES; ++ pub const EPOCH_DURATION_IN_BLOCKS: BlockNumber = 1 * MINUTES / 2; + pub const EPOCH_DURATION_IN_SLOTS: u64 = { + const SLOT_FILL_RATE: f64 = MILLISECS_PER_BLOCK as f64 / SLOT_DURATION as f64 +``` diff --git a/zombienet/0001-basic-warp-sync/chain-spec.json b/zombienet/0001-basic-warp-sync/chain-spec.json new file mode 100644 index 0000000000..8c09e7c7b0 --- /dev/null +++ b/zombienet/0001-basic-warp-sync/chain-spec.json @@ -0,0 +1,192 @@ +{ + "name": "Local Testnet", + "id": "local_testnet", + "chainType": "Local", + "bootNodes": [ + "/ip4/127.0.0.1/tcp/30333/p2p/12D3KooWFvMbTsNZ8peGS8dbnRvNDBspstupzwYC9NVwbzGCLtDt" + ], + "telemetryEndpoints": null, + "protocolId": null, + "properties": null, + "forkBlocks": null, + "badBlocks": null, + "lightSyncState": null, + "codeSubstitutes": {}, + "genesis": { + "raw": { + "top": { + "0x074b65e262fcd5bd9c785caf7f42e00a4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x0e7b504e5df47062be129a8958a7a1271689c014e0a5b9a8ca8aafdff753c41c": "0xe8030000000000000000000000000000", + "0x0e7b504e5df47062be129a8958a7a1274e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x0e7b504e5df47062be129a8958a7a127ecf0c2087a354172a7b5a9a7735fe2ff": "0xc0890100", + "0x0e7b504e5df47062be129a8958a7a127fb88d072992a4a52ce055d9181748f1f": "0x0a000000000000000000000000000000", + "0x0f6738a0ee80c8e74cd2c7417c1e25564e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x1809d78346727a0ef58c0fa03bafa3234e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x1a736d37504c2e3fb73dad160c55b2914e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x1cb6f36e027abb2091cfb5110ab5087f4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x1cb6f36e027abb2091cfb5110ab5087f5e0621c4869aa60c02be9adcc98a0d1d": "0x08d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01000000000000008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a480100000000000000", + "0x1cb6f36e027abb2091cfb5110ab5087f66e8f035c8adbe7f1547b43c51e6f8a4": "0x00000000", + "0x1cb6f36e027abb2091cfb5110ab5087faacf00b9b41fda7a9268821c2a2b3e4c": "0x08d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01000000000000008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a480100000000000000", + "0x1cb6f36e027abb2091cfb5110ab5087fdc6b171b77304263c292cc3ea5ed31ef": "0x0100000000000000040000000000000001", + "0x2099d7f109d6e535fb000bba623fd4404c014e6bf8b8c2c011e7290b85696bb3": "0x08d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48", + "0x2099d7f109d6e535fb000bba623fd4404e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x2099d7f109d6e535fb000bba623fd4409f99a2ce711f3a31b2fc05604c93f179": "0x08d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48", + "0x267ada16405529c2f7ef2727d71edbde4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x26aa394eea5630e07c48ae0c9558cef734abf5cb34d6244378cddbf18e849d96": "0x00000000071c0d84db3a00", + "0x26aa394eea5630e07c48ae0c9558cef74e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x26aa394eea5630e07c48ae0c9558cef75684a022a34dd8bfa2baaf44f172b710": "0x01", + "0x26aa394eea5630e07c48ae0c9558cef78a42f33323cb5ced3b44dd825fda9fcc": "0x4545454545454545454545454545454545454545454545454545454545454545", + "0x26aa394eea5630e07c48ae0c9558cef7a44704b568d21667356a5a050c118746b4def25cfda6ef3a00000000": "0x4545454545454545454545454545454545454545454545454545454545454545", + "0x26aa394eea5630e07c48ae0c9558cef7a7fd6c28836b9a28522dc924110cf439": "0x01", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9007cbc1270b5b091758f9c42f5915b3e8ac59e11963af19174d0b94d5d78041c233f55d2e19324665bafdfb62925af2d": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da923a05cabf6d3bde7ca3ef0d11596b5611cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da932a5935f6edc617ae178fef9eb1e211fbe5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0x000000000300000001000000000000000000a0dec5adc935360000000000000000000000000000000000000000000000000064a7b3b6e00d0000000000000000000064a7b3b6e00d0000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da94f9aea1afa791265fae359272badc1cf8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da95ecffd7b6c0f78751baa9d281e0bfa3a6d6f646c70792f74727372790000000000000000000000000000000000000000": "0x0000000000000000010000000000000000407a10f35a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da96f2e33376834a63c86a195bcf685aebbfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0x000000000300000001000000000000000000a0dec5adc935360000000000000000000000000000000000000000000000000064a7b3b6e00d0000000000000000000064a7b3b6e00d0000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da98578796c363c105114787203e4d93ca6101191192fc877c24d725b337120fa3edc63d227bbc92705db1e2cb65f56981a": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9b0edae20838083f2cde1c4080db8cf8090b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9b321d16960ce1d9190b61e2421cc60131e07379407fecc4b89eb7dbd287c2c781cfb1907a96947a3eb18e4f8e7198625": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9de1e86a9a8c739864cf3cc5ec2bea59fd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9e5e802737cce3a54b0bc9e3d3e6be26e306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc20": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9edeaa42c2163f68084a988529a0e2ec5e659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9f3f619a1c2956443880db9cc9a13d058e860f1b1c7227f7c22602f53f15af80747814dffd839719731ee3bba6edc126c": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7f9cce9c888469bb1a0dceaa129672ef8": "0x3104106e6f6465", + "0x2aeddc77fe58c98d50bd37f1b90840f94e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x2b06af9719ac64d755623cda8ddd9b944e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x2b06af9719ac64d755623cda8ddd9b949f99a2ce711f3a31b2fc05604c93f179": "0x08d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48", + "0x2c5de123c468aef7f3ac2ab3a76f87ce4e7b9012096b41c4eb3aaf947f6ea429": "0x0400", + "0x2f85f1e1378cb2d7b83adbaf0b5869c24e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0x2f85f1e1378cb2d7b83adbaf0b5869c298ef7dc060436e4ed803af07632b89b65153cb1f00942ff401000000": "0x601de9615313b00e8cea3ef84e79e50b2fb70e2c8a47cff478b9fe8b3fa8f2db02000000", + "0x2f85f1e1378cb2d7b83adbaf0b5869c298ef7dc060436e4ed803af07632b89b6b4def25cfda6ef3a00000000": "0x601de9615313b00e8cea3ef84e79e50b2fb70e2c8a47cff478b9fe8b3fa8f2db02000000", + "0x2f85f1e1378cb2d7b83adbaf0b5869c2ff3ae12770bea2e48d9bde7385e7a25f": "0x0000000002000000", + "0x3a2d6c9353500637d8f8e3e0fa0bb1c54e7b9012096b41c4eb3aaf947f6ea429": "0x0400", + "0x3a2d6c9353500637d8f8e3e0fa0bb1c5ba7fb8745735dc3be2a2c61a72c39e78": "0x00", + "0x3a636f6465": "0x52bc537646db8e0528b52ffd0058dc2705bea5c66d15541040ac6c3ae971bcf5f1040221a8d1f8b7c17e61bf21cce87411480b10bd8daa5e3c2b65f8c4aa364e5a8ddf27c0313827d06a2d6334e074db9ca7d876ac8d052862b9984530f4d340bda4edddd6e4de5ba624536717e1139e14df2d875c7f7086dc3fa3d398b64f50fffc58fbf4fb73ffb0d39cc2b86761cfe6d3073b9c1fcee7fe611b7f4875f9a7d8cfe208fdf493f4d3b07d9a73ad05c8cf39e5c4dde679c3c8391b9ceec4fd78c824d218e4cfb79cb8be9cb301eace3b7f8a274c182edb72e2e83349855e7e4a2a795d7e27998b94f46d92b42eb543afcbcf5f5c9e5bf2d52dfd9b2f93dc33262710e3fc79b943e09fe2d00f8dd7edc4350b92a692a2facb7adddffddd3d89e9babb7b8e0b9d61050b2b4ddf3efdfdb3f629e9edfe9c18c4f8715bebf42d6f6e3ff74be1b9c0c8b9187cb89c83618e5bce6ebf90cb3918729745244063391ee9710cdb87bfbbbffc6047f275725e904518e7f53cec48efa788c4bd67d104a4eb81534cd2bd67cbeebb075b4c32deee59a43da30fd2d0c979410f3db2fb2972cfcad89d0f822c7ab644bade3b59d6eeddc9925eef8f34e8645980eb0fb2f8d9b2cafddec99273d7bf234bee5995292c3051e1fa97dcb31285eb5f76255cff23de917c3f72c8afaeaeac6ef74e96462e7d70864ed2ef48be2c9ef05d8f1cfa6e67cb79dd0ecdabababab5b225d7f4a72cfa6a53d9bcf739903246f521840fe916b813c65e41c0c50b7b60fe76078bafc61ff4c1bbfac7d3887b3bafc37979f73580490ef389f9f5920c8edc3f3cee7075fdc3e4cdba79f1f9c200db97df896f532c31e008355cff83a7dff7e1791b4e5097b400cb89ef19def24533b4524b567fe2c96c02292fe233d5900f2edfed96d8f34d29c2680f3270934e67054b7a4b7bfceaf95fd879b568e777e395e3aa7139fded3ff484f77779fef3ebbbfdeb6364afae9f74c32bf6dd9ef3de714c839e5df7e908f8c208b30f2fbb388847f8a2354c1e170305cb6dcb3b2de7eb0436e9ff9edc33deb3fd24ed29ef50f602cc7dbfda0ac7df8f683331cdba73fd63efefdb2f699b79f45244e492a38b708a9def9f54e6ba3643efdf9d41acdeb964524d396f3ce3fd247fa2f7380e4736a01f2e59c72bcfd606d9fb266c81ce3fc32bc0d3a198071be3f3f8b27cc0f725b7bc6cf3d2be99da45f916a7e3f38c349d21b7211a2597587be4b05e6ceef17b908d1c4dd21112e95f0ceef777111a289bb43225c2ade9ddfffe2224413778744b8f3fb594c4271b83bff48831d3217995ffa73cff86bfb94f3faf3834ed69ef11719fdf9592481f68cdf6ded19ff91063b9c64df908b10cddca5e2ddf9fd2217219ab83b24c29ddf3fc51346dc9d1fb60fdf7e16934cdc9d7fa48ff4913ed24c72cff839a76dd0789903a49e5d06c80dcee7f629f9a7e59ef1f323cd2903720ee774f91b22454f610414ad21610c79c190160c991a32c6102286c0604808865831440c2057860431c469881042da10f28221370c9961881742e6186245881c426630248b1035848421248e214d42de18b224644a081b42bc08a11212032164081943081842d6102203212e102286902e84782184cb902d845821a44ac8154248304520a449080d3d36e8a9ea99a3478e9e387ad4e8f1d233464f0c7a723d5b7aace849414f153d24e839a247aa678b1e287a9ee889ea71a207891e237a44d053821ea89e247a94e81941cf0a7a5ad0c3821d11f448d1d3448f163c3af070a046c68e1a3b69eca0b173c68e193b65ecc060478c9d17ec84b103c64e979d2f76bcd8e982878947069e251e259e241e2b3c31f0c0c083dbb9dab963c78e9d3776e0d891c1ce971d2e3b2ed8e162678b9da99d1becd86087063b74ecccb133831d3976e2d8a963c76aa7063c1be0b9b293c50e163b2cd859c18e0a76a476aed8b162a78a9da89d12ec3cb143821d26768cd82962c769e7879d1b767cd80162c7033b42ec7460070440ec0072c70e13901b00b9da69dab101481640ba00f102880b807001b20490278094000813407e000204902cb51acc20a61390a51a0d80c850fb52bba376558ba3660710a5da1c409280e06a5635388058a9cda026478d8e9a1bb5aada1b35366a30a86d51aba236454d899a096a4c0059a386a546430d57b3528361e76987089d2f74dcd0a9d25943270d1d2a1d18e88ca123860e1b3a73e880a123874e0c74e0d02943270c9d3674ccd0214387063a32d0a9814e171d2b9d3774d0d0a143470d9d17e8cc40270e1d2f3a67e808a143848e077478d0c9a263834e0d3a34e8e0a0d3019da61f6afc80e3c71a3fc2f8d1821f5dfcd8e207173fbc981ef8f1c40f277e90e047133f98f8b1c4ab04f34b8f0c604288573fbebca26a54d0c8f083aa2af9f0c2c71735197e88a1d3c33787ec8e9009f6434d1b36573a869a0bbc2f3a778473d49e422ec2168453e11630203c1bfce0522347784527869a17542d68984219603d7442c086a8f902e34167294cea425023469df26e50e3a5878c70a926d5259b1d6267c8bebc5eb0d303cc03a1d2384307467490a583a70e74a851eaa0033d8e10bfc070a8568c3db8be78687c74f408e2c545b481cb4a8f14e06c51657851856d8c3bc8bc78c5c047d40f2d6476c83421bb416581cb062f2f2f34646ed42d7c24e164f1ec78a9e15223e6c54b63d4e2c38a48c78c1d95a9068d5a11f54aad04b51d5c68b86010adc61a6ad4a0597aa541a39463031033726aa08901c9445e21ade05841c6602307a9f423f827300d1b19f4d4f1657000825718382da827e8b0886ef4808123021c23708ae809030704384ce02c81c3054709215d709cf8f1c1f900ce062a10383e8852b468d0b2c26962c686991a7048b09303ce08704a504bc306861a326ad8b0d9400d0c6ac0c8990187861a3a74385063464fe12ce1c4e003063ec6f831831e40f4f0c04b8c1f67782ce8d104a8460fa79d257afcd003043fcc905981103764a2e8a1030803d005433e007ab179b201818d10f406a01dd48ece868e0956431743dd42bff4e8402743074387ebac74576a0a2a15758aae06b1aadbc07f60c68c971daf2f558a1e5a3aa577aae9a24bea66a077b8946a9868c8a0a1010d1d3435a061636606345e66e2a091c18c1c346bd0a83143c7cc1c3469ec0411ce40860b4d0b605a68585093aa69a131a25ba2c982e605b52d343e8c1bb0c901f6011808604f3c88784551b9c8ae646dc072f820fe88991bbcc0e88ae888e88c70c9d03dc18aa8319851a3f683c8c60c1a325f646e206303992b19ab191a66b0c850f1a282460c9c20707ac00142e60a192b6049302b3255c8a4e03df03f00d181831d4419d4c6b0517a55895334493b30b41534565c4b2e1ccca99682300d980e5e0d6057604cf306e31a3655231b3e62f08241f5e245c66b0c0eb48c408c3f8c1eb08943868b07e2d5454d1aaf2b5e5b6cdc78a9604c0387089c271928402e5e55d86001af6aba8871d4d0a0070f3634d4d8d1c3071b2b21ce268b8d0d3367d4bc01bba1c68d1f2ea859e387979a18c09a6c7880d95073364b3565d0ab9a19d4b2b091e175831a2b180d3859d4d421b6f17ae2d5c4cb891f37a89101784718c3cb0697198e858f261717ae12b846e02ac2b585eb042e295c53b8927081c015e59a72b5c07584ab0617169716ae14b8b270f5e0f2800b0bd710ae255c4eb884706d7129e17ac2b5838b07d70f2e1b5c50b8a270657171c065848b05ae15b8987055e1bac275832b07d7932b042e205c1f7005e15241cf54cd183364b862a8b99a2943ac439cc3e606110e9106e21be21d40bec0711273220a8068b169c3956433c3aca3c6c4b303073cbccee891c30e0e385c841b7899a1e30510aa1a143555b5276a4e4c2d707ee080033b576021d88961a7069c295810af357464086100bfe0203141600387cc13322600bbc0f100e8458f25c02dc030769a00c1787d51b1709d51b3807140acc1cb8b9a06ce11343000c204a40c58075e65d868a9b1012ba9fe00dba1034137448ba047053369bc6cf0838d1e3df464515bd0566ab8ecf0a0d34563f1d9507bc0116205162a544da22e5195a81e783272a2462a9e8b189731052e3b64553eb8689911bee18901048d57171d2d3836e0d48033838e0e325f006143c68b8f06b5a57a458c8b2ae5aa636c72a5d1e303a21db58a1a11b5281f31e058a92800a2850f18ea0a5e50d4cc21dea0a606231a43b8801101f321b4e25b8833a82a10e5a849c2d10296c533e3470c6a5c5073064e08bc1fbc19bc2b5e0c3d33e8b1a367e81a7a072057b4161b1d76b4b8c2a01903e452bb41cf17206a0021a3cb0244059d53a743d7d4d1d075a0d3d2fdd005d1c305080f3d5d741ce87ce87ae878e880e0d1d2edd079a0cba1fb40874377031016e00c61f3464d0daf0d9d1c7ab010b700d7d0b9a1870d73861b367676e0e14167079e2606e060c101838621e3820ea39de8259a896ea29fe82e1dd55f34091a0a97aa5cfc106fc423417304cd123448e41891e38408845884e834163192603402e4400d23c6464c8e980c649e64909029a207971e60f4e822a7889c12e488409c41e4808885468a460b1a15c4ba88c120e60558450503b442d64566860c8c19ab993b66eae8b1460f1af460237644ac043124782cf168e22143ab8adaa56545cb8bda45eb8b2fe245f042f838818f2d3e50f02178103c11b12b625bc4a44627c629c6a81814b12d3113f810c207123e86a8d9e0b9e1d1e1bd018301ac0cd8182316a31763163eacf0d1021f57c8acd00981ec8a0f86efca1783a884480271095087ea0270872a06a8e585e5b5c38b86d612b5205a4c842c08c908b1a835d42cd586fa058d153eb4f808c2070fad21ea0b5a21086f107e69bd117620ecc1d3a2268697453746774617032058d870c026861aaa9a1bd8d450e3854d520d1c364d3638d4dc5113860dae260e1ba69a2f80cca16307101bec28d145f42cd136340d4056d0589aa973681e3acb9ca3710d03902ee6d5acc1b4c184a3c76a5acdaa49032052736ab66066b164a260b2604e31a5983f4ca989c584629e60e6a609261573cbfcc05cc1b46246318998219843cca46965c6309f260c1357c50215983e3c994d4ea8b820a505a1261098400424f08006a49000144e40c001941820090953082300100091282c38c07979c648c16a072a6a528125539a00b1b0640938fd4914a0254b5abf8127529c003dc1f92b5c548401202551a440292a025a00f94c1cbc0c34bf24d4c40914222946455058f0e0956c3e89895a68e1f556b8a80848a8c7c7c0310f03b7700099c7f1e05c090935712224d48400349c3bb848084a1323a0274b80802cf0018e1d2cc50251a02cc10011900b434ba04cb94651805ac04013141ee70b03ad7003067a22c5c992274da4084d71c1060c64d4048a519426464551a0c870ea6021a1264e8c9c3421328a228500301c2b067212454a94254d9c44918200a3282e4871e2e4f6c0a9019429170345500a80814be2d020041c3a7270e660189c19b01ccc82132953806c70e26022175680628128529c2c8132e50255a0894d06a78a8b9e1035b90010d19215a24079120568ca1228536e91901429284481d2821404581c38780a14168a8880a25c40ca120c00057d80f3063f018ab20294a2282e18357122850a14178680961811499902444404c70d7e026589115011910b4e6870dae0222028465088a4b060e44293252b2c8132e53699f2244a1322127ce0c8802f0065ca25020a2292e28013481c361808c80520171290c213a015566892c20f9c35b828c809100b4ebc7091511420222016a240791c35a240b1800d4e1aec044a5114284446558096604068ca94264b88809e084171c116e4448a90ed06070d6e614913a3264f9a18011d0086734606a00835816204250a14191c3338c88914a10a2c013272c109501520189c3298480a01429e404215686214c5050b2c3172024404e54914284b96d0e880367c2083918403c3ca97cb4820e770b83b919cc66355b1aae694f951efc6c6fb273d1bcff3bc27c99b1b8ffc1beadfc75f7f5d6b77adb39bf963effa7d5d2be576e68ffb03e9f4fad1a6def7791fd013fe987a7b5f777fb4995b74ff9add99b2338b226566ffd8fbbe9deea87760f7d755efcfbbfb1b476ef70aced44a99ba7762f56e96d8ac3a99bac74ce64a65cd72cf9d3f0fdbe9d7ec2f661bfe987ecceca4dde9e73895dbbb72f5cf690794fac7edb4d93f76fa71d3ef73f7bc8fb6536766ca9429534ad9fb28f5f6ef83317b53a64c993fca4e653e66a7cced9476bb575ae5cafef19021428430c8eed4e5a20e7a575abbdd0586cc937a5ead1f738fcc9f3b6598bb5377efd1dd2be0eeb0eab5d6dae3d8b4dbdde79c3e67d3f6f6a6d4bbbf6edaecd46beaf5fc2af5c09d3af5599dddbf70c645db43f7daa42ba5ee2ff6f656bb7fee1574265d57ef4a7b093b8d31b37fd3dd41b052a069482b0dc31aef1a760fc309788712a8719ed3bf5ab97b727fdc13e4eb09e21e0176770a1386ddd3b9bb99fba3f9421aeace94db2140a97baeaf7eeef4f3a8473d66af52eade5fb7fb473fef8ffbc6fb3effe6fc981bf600dab4ddbb2937b37fdd1f7bceee7d2008a34054a820303bbfda1dc0cc3e3bef98b9fb989bb9eb66c7ac6eba772c67ee58ecccdd64ef581d77ce9a5dd771d7755d73d779c71db3983b666618666f6731c73033b35831cc95997aadeedd9f7b8e7b7737a51ed0ee80f6cbddbfe6699e6e8fba7703ba76f7a49501ed7937ad1fe5e9cddd1f7f5fc7d49dba376d4f61d20528c0030e9d7f780218051ad6f41ac7a969202774cb7cddb06eeafd79b57e8e0098ce016a351d6f1a86ec734e9eacf6febeef874c7ba594c62adb7ccc95a73bedd0dd457710e6e476b06d427666a6dd4ca33edd41f77228655a29ad94b6f70ca5b45217a5d4044a5db429f5da5e2b58bfaf6777fb6cfe3e9c0efae3e993d99550e7ea6cc3ce6266af71f7cf6b6776f630c63e67c8ede2768ecd364077e8edce727767d51866f7ca732661febc69f3474343536b7dbd5e73ce5adbbd84f6f6aa57efd09d046feaeefe7277eef9f5d7947e1f87cedeee4dab778314ac2085e9eee6ea9f37280382ec3ed929ad15a4947e1ff7d7d49d7d4e0ec3574e873e42483d44e2341e76bb7bddcdeeeeecfc7ddc55669e9f659cf979e8ec79dd4eabd7dab47af5efebaed56937add4a35e53da3da7cf39270c4c7be73c3bca6af72e403b28d33dfba360d7fc79d8dd9336a594b67f4e00dbc7edec3d99bbdd3fff3e76e6767667766f6766a6a0e73ebdbda977f7f4cf999d5266fed89ddb9d32333365fe98bfef6b76ff989d7a5f6f7766767777fed8bddbbbbf23dff7cd6aedafcee97d3319113c99effbbcef930d4000edc950af7e9fabbdfdfbbeefebfebe98f77dfc7d9ff3f77decf4fb78ec9a366d9036edcfdbddbd73ea7ded1fcf39448a2153380f6e0c10496901a805cf8805a448219a0242009e0011013d2192b2c427841e20284446207c0043d323a5a60b72d2a402424252402852d3c3470f1a1e464550aa00dd742d48e981620120a22996031b98284053a634b9f10b00450122ea0e5a806214c50522cb81058c5ce8c14115201696184581423465899322282ddc0cf1e95145ca058c7c3461a1c8475013274e80827c42e8e1c14188f40459d20314d444a8024b5688028585a2262cbc102128413c00f8800807690244e4032868895115a0283a3e3e807922048505274b8c845c686201a027b4ce88a88951d1073d1cfcac00a5888c7190220c00052d710245a8022e4c99111575d0e29ea22220a2292d4871b28408888a142740444b9a143d7102b42448904f083d539c0015fdb0587062d4a4024b58288212058a05661f60c002508880a870608980a200adb00400414208a1e709d0122946533040e4a4039610101520a125559ab480035b418ad09215a240596105294234b3051f0c446401275284849adcc054017a12054acc94169a181501b500c5e87b449102c5c84993a2284d888c96184169a1022e14d1584f80808a96b420e5499426443606c8a88951d1922022291730028a92032bf2596116821150d19227519a2c01226a32a5052943c62640424e9600add084488a51d112a1264ea4088140250a90b338c8922852a014110151594224c5a8c91328424da82c2982b2240a94262c38b18005a42c31aa02c401952840fe03aba3f986d44cf805a466c28dc462c247472c9009b7c20e09e9e8e8ab48ee47484820528b09231d1dbd8e8e68bb33399a4847474794c9d13cbacddb64f2c27c611e1d1d1d1d3192787474243261a48f8f908e909c9190e6d1d16482348f909ac96dde6ecde485f9c20bcd848f90909cc9d13c3a6a2647f36832e1a3a3a323a48e1e1d21cda3598f26d264723499091f1d39133e426a267c74c448481f13a47974e4313942ea98204d24a46682349991281346426a268c8484e44c9026d264c248938905bc342633cf168a9a70ff6aba2dbecb9bc2179ba6703ceccdfb193b61ec6d8ad3fa61f6369d5af6e654377762d95b3bc9d8a6622aae72ea625e8cbd219102ec61fec6fa1bd3105beb6f48a4d0bd3775fb6f48a450bfeb5c0ec6de580fb373b4b709e5b4fe97bd4d97bd7993686fdd14da9ec24d2876c109ea68a220cbde9296425bcbde904881f5f46f93eaf6330db1517b63d949e5b47efa9ebdcd29a7f577f6d6d5766e7a36a8a2065a90800530cca9a9c58c21d4f882082b9f3dea2b9a5002872dbef460a37656517915a572a2534d533447713977a250de975e0555e5509eedb450755fba13d5955367bb266fa25ff6d477757507185faca087266a3ba6cecd37bc08c30c20a4c2b0b1cdbf6cdced8f8b1dbc7842862cdcb0b1cd6d87735a7f39ab6eff1cc138bfec70b7bba9cb1c1c59a4d933361c31c711671c31c611611cc165882e59dc0c8c9c1b4288db0f5ece0d41c5104fcc1a30bbb3b395796543826b58f0c86e3ffffc59fbcc3075fb6f74daa76fffecd86d16fe36cee5c961ce39d9c66a98c3873984d395f5cc2d371260cf8668c26947f0b30d091c10081bb60640bafc2fc840c4b644c4d6cf362bf3cad61609b64773ea809492486310fd0f6e89d41d5dfa25eb006cc6ed2c11b57b26eb7724253b57a2ca562d8eb568a25fb228f9c105278b10f8e01e4da4ae9b0b281f40e625e61c9ad77fde00f293f00738346f3f0f5e6ef8dd3759346f7f8b25f065d9a0f9ac269a5cebdea8de21bebae0b73842f8ad16d87db348ce817ed8e586cf7a10ecc26e3dab45253409ebc33fd2247c0ffe24c16791e1bba8a4fe9c96b936cbb6936bf3590f9265b3a8d43be477e08c269237db8dc9f47f5c3a9f7970bade7f28a8b003166eabf159fbd878dde7cc98b0204667f3d1a38ddbe9b44fe9fd8c3baeef25842a30699f8ea54c689f79bbe7d1c274bb1fd2519edb3117299f9a5bf219b7ab3fe57edffc46deed07694efb74443d45254beef7f5fbe6cf32ebb00559a2094852782dcffb727edf64b51fe99125d2f5823c31c967bfff6cf98267cb79bb497f70762bf16f25d3b34ae677f737e00e425c8fec260cb7accfb9197c18c158fb78df3d1549a85fbe709564c270eb83dcb8fbbd47d68f2cbde73fe21e0c777e7b55f502d6fe793dffd83ee2f3c7daa7f5cc7ff339ed539f7fd63ede23dd16297e25914010fc231d9236f559396cb9f4451dbc07b95cfa1e07ceb8f441fa208834c367b1c00727f8437ec1076b908d92ef3bfab93e259feb03bf3ec86a3d5f56cb8e509ff5ac1db25cfaaddf21e9d2a0eebf0fbf92f3b25c1ca1d56ab56c50f725fd22a4138a906ef841de9f40ffb3a5f7a165f165d52e6431b4e5ed86ef912fd27b89394839935ebfc41cc4fb693be2210976a147fabf48b6555209cb06894f5f7c16910c896314df3df03d5b8a0f5a1bf0bfef94d017ad0d6895d4f7be821ed9fa2fa8d64f49fd6a12fa2c5b7a0f3e089265f836ace70bbe47dab0ec08dd830f5abe1c98e3d2071fecd06b3d68cb794befc3073d6c91e197f386df8d002601c1f767d9a179c57a7ffaa02d5b1f92e0bc1e396f8be43b2ff3ed8618b9fcec359c0168182ebf8b87c8d1446a7568039884410f5924c16d39a75802fbcf13fafd595f562e52bf6479e86439aff760fbe5760ffb32c8a1279e40df6d59af7bef950e7e210d3bd10095eccb22e7edbc9fff89273458c5138edcd6b3c8790d8024c56d3dd80a5926208d10e50e1dd52421cefdaa880024a7db7ab04396480292d31d62725b0fd6b0251a00c986db7a100c5962121eae1259a20948b7f5a02b641da0cbd7fdb8997976aef2a8e97a0f86218b65cbda2e5779e474bd6f91cc3a65ebe787e209fd2d5b4e164356927ecfbacaa39b4588ebfdbc959c17ac618b49e8fb4f71c86f903802d2ed3eebd9f91e34df46498981dbcf17a94e2f892c583c0a0ee4c09405072f138710ecefb7e99e67f7ec9136ddcffbfd07a3ffc1f5ffbec9ee677bedb5c7df5ee59eecfd11f7c8ee821dd90fce59c3d83f3b0f3b1a765dbff7839d38028be17d3357f3dcc976ecd907d791bee95b803c3b3082b387b193b89cf3418ddb405cce7540eace2ee778b04a6264a5ea96485178b774e1062b2eff6bf6303a1597731f58e2ce2d977340bc71752ee780e0c01449686541758798dcaa1feed091eb9646658c2103e296464f408065dcd2688632ba5b168141821f504cddb2286aea561c8eaf2effe56f1dc0a179e98394ecbeebbeb8f300fe6dcb5c08dab8f4e9cfef49da28e9beb8f4bb2f6e598474e977cf7788afaeae1ae9f6776ddc79c4964b5ef2821d3689733bb2fbe24e9184a72233ad512b7927c1eeea5a9b24f3fd4998efd608e4dc105eae51dfce96fd54e6f547bae47592bc6d045e7ea46ef69739ee7cd9eb3ffbf9fd5d3401490adae4fca60d23e76e38e3d6ffde6b7d25cbd607b1be7ef7dffb834d860fba481bd7f32d91aeeb3fb2ecb643af4bc5a8afebc31769e3fa79c70769d80f36699384e270177c7afd82df645f16934c5c8b24b8fe458eff91ae6fd2c317492f48c38f643aee8bf40b76e147f20dee8bec0bd29edc7dd41b3ffc8f643bee8bbbd9d425be5f317c316e48f68fe4eb8ff88bec38e8fbfa6e029214f5b9d63fd260f308f3bf2926f9beda24f11bbedfd0964851e070b749f0c129ce1a46f05d24f8305cb04317093ee86f5c90cac1735cb00b5d64d177a9f45d81685ef0a98864d230820f52710416e3820f7247bb72e0027f0b2e86eb22792ed8627d83e233d75aa0f8618b2cfdb23e64852dd6832451288afd21eb5b248b26b00e8b9fe707ebecd8336aed752beb996b7e5ec029c3482fe76e78c19461ec2ee76e880173ad83eff3bceffb496ae0fbbe2f0d9049708add5cebbabbab5ceb7e921ae8ba67711124fddd73e5225420097c5f0f32333373cf6772dec9af906baf6f3203a3ebc50f1ffc0c8cad677dfdef33307afe7af5bf5edfaf31c05792efbf1659de777d677d7dcfbd7a2cf0abef2495796bfd6f72bbcbfdddfead36a8fef72c1ea0b26c8974599375abee5eff880fdd6ed76675edda3ff9491bfff9fe4ddaf83b59c291cbac537effbd937cbfff2a9151788744b873caadcf621226b77eb5cc3a247c64593fc8ff7bb092966888ab70b86b04735bef6e14ded6d767f1842997f547ba92444322dce69abf91776b5f7f164954dfc8bbad6751c992cb7a9c2111eee41aeb2bcb32ebb83842f7b592f3bf725efe8e2cfbe733b5b5674e8e757e5880ecc1bc7bb0f307a7c86d03bfb625d2ed79fb9899ffe33bc9a1dbfd6693437e5fd33259d25a2793ee43dfd5d5d5975b9b1cf25bed9077cfde7f64f93d95f10a511c0cb7ec1ffabec07075a9d4921544bf3edb72bcfc4d86fd54beb27f8888ca778b90eeb42c2261efdba86febbd6fd9a1f9e5521669d497beb5493273d7bf4524fe1ec9e57cef9da4d29ef7b61cba5dfa5d8974bff9555bde6efd26677777fd598434e423f0f77b437e29b5414aa892f96e83f89b59b3e2d03327768055c6166f787a2663152e32bf6fee52f1441f1f7cf1a5ede37df8fccc7359dc7a2e3243f2b9c673f9bd24f5c93abf4e92ef145b606b04d66b5e904524fcc1c69eb59ef5e04f11c9b425d29d2cb2458295a4326f392feb2749655e16eb2bc99782ae6987fcda8ce077feb4cf35fe792bc9b241f5c1a7e250bdde8321f862fd14e7d2e86f59967f82952cebb35ab644baad89a405daf276c19f248bf49eac33bf340092149759673eeb8ff8d0ed7a3fcb5be490b3fc59e4541afdc726f97e64ed7a6425c119ceff489b6987e6f52c6fc1e1ee8feb8900f426a431a8fb0f6ed9d989a5677c3937830fe0c4d23e7ce9037111229e4b043f12a7f1f399ad6793966a36ef99626c58da2776e9d70c29632c85eec4d233fa3c68a090e4fdfc23fdc2584e2c977ec9685cfa37fed16974f8e004104e4051e17036efa96d92382a821a4010431027b0252dd56cf399626cfc494b6dfb9e6900b6f91ec93400db0d8914e6df50701afd3975e97b7f432285ef3d7b3be234fa9fbd219182f7d3dede69f43dcbc3b9dcfc92a7980632a8800837c230820c2efed21f1283916d483a6071821f7e98a2cb1233b0b18dbfe4b9f4594ce17138dbc4d23e1f5cfa9c436dd06ceaa63f80cc454a1ea457d93e2dfa1da547d73b332eb55de97df43f1bc5b3a5f742ae44d5edacf7a5dbf276a92b612d6cb89e25a2765f1e5d21241fae17450049b9db7d496d59e57a14d6a2e9765f66e052cf46e1336e67a79e50b0f30515f4062df7e672aee9cb053bec48226efc47afabab2b1f6c2c227033632861650d1cfd39e4575797085c0f1a86c8e244d48153125238a3297cc62502b7f30515382a0248ca55c1b1164d36fe3203b77be60030ef949cb3a1896b342f1131f0193696c5f11936b637fea3283f7c000c18bee070b66a7149389c8dbfbcbddb720a48c34af253d2bfc506ec7c31c5958d9fc506c0c81823047758b9bab2f1c7aeb77702d02925bba7f3ab5df7fe951cbbce6b345cece2b8b29e754fc979410fe97b1d9d9776c3c0bf72112f65d77f7a7d67bd7befdefbb3bcaf9ebf7befdfbbfb77e4bca047f2ed90b41bf48f74bff7d37b1bfaf3f6b5a17078df7d5e47ce4b495ab9c6ed06c8dd532ee23d7fe791437ad6bd1997a767ddb3e865ad52df7f5adffdcc7fc0effea67d68cfea774f848b3cd7ea770f9263d3eab7c8dab4caa3fa8fac69f5bb97b50febbbe72db71c72bbe7c145ea3b55e891fcc1eafb43dd9b9ed5ef9f3fc5fa36dffbed6bf37dcfb91ad4b89cab018d2beb597d6f885e96e59ed58e6bfc2c528f9cf7a891ba7625406e9fcf9ffe47cedb91f45924a17be6da08fef4711082c503f8d3af4f6d592ffd7e8a4355926280017787ca0bdcc08eabeb7d11d2f56cd1674bfadd91423529061868951695545b56015924a1a34fc511bc67ae95dd47ceeb911d126a6d92f8d3f7a736a89fdaa1db2dfdf677b353007e0f4e1109f8e1775f49f1998bb8de7bd0458e8074cbf0c1677184ea0aaacf1f9265fdf9e014d9835dced520c6edb8e6b53842f78b64fd16392f08b278807e16eb4592f52d92e7b3888465cb79d996ac6716d90ffe9106990c1f24f9822c92c0367cb043909c9745125a97b956f283e00cc1f08f34872059490c8ce183dfba654d526f688358df2059b27e2ae967cd568be4cb22f956be5e475292f68cc3e95a409e32d22f65b77b9eaee3b9dd773fa47b5913b7fb9bfe695b67e4763ab7fbb17dfabbaeeb9e733a1bc4b923907932cf27207fb0b0673114daa77c1adab8b92573b9f4bbff6af7f7f3bee42ef77b229f4f594b689f7264a17dbe37d23e35f77b15da87733534ddef25d03fd4e6dc85b7dcefb97d6c6e19bb2591fb757fa46fc0263f21a608eef760874df6952171bf07b927bb6b685e5dfe3ed63ef4bfefbfe79ccf0671953fcdda074baee797b21bf6e176ec3e092ebba1fea37fec9200f20e19230d586eff386120bbc029c3487b369f67fc29e92414d0b9b87bcdedfff096e3cd097b46a3009e230826bad0800439dc20031bc760d4b288643e1db17ce9b17d64b73fac3c6d783987a58d4badac67937336009968ec1ab6cf1c3b00b91c1b745dcd30c70d888445871b9e7000a34b1177d85804e7734ec943ce7bc1497b36699f4049f06cf9d7fbca1f8cf6cc96467e5b2f6b1ff0fd6fdae7033f16cb96ac6f3deb0b7b36b6cfc77a7ff023e7055924873f451278dcd096e1b7fe23f986a4915fd683b379baeb23f98224df168be49e95b23bbffacf95b771d3986be5cd9db6f4409fd323f98a30bf9246dd9d21d86195eb3d93652d2b7fb0c9624ecffcbfff9e7d4c52b29cd7238dfcf24f72ec19dbced69ef578bffa0fc5e170389ccd7fc645fc7acee569cdce03eee493b1d6366e7f289bc187db7ff3649ccff34ee110c96bc42b6d5cce5d11e3967ffb0a16f7fd47a769fd8d047c79df5988b40fe7ae7cb9fd46a67882ff7c1681c672d63d11cecdc074fb75fa67dafa8ddcdcb44f3f933c3d6b9d9ef57b47922cce581c615e9d9e4d71d6b37e0a8c47b7b60fe774371d0790c9381ff48e9832e2703818669f30bb7da4670e23e736c0e57e709d72112e1a3977258acb972fd885948bf4e5d2bffbf9a00bb26b24bbfd4e96936888af6ebfd177e9839ea8e4d52473adf4af9cc4fbee5924c1ff3bd24ef2ad64d97d39afff47da28f1bebf6d50f7def3a5245f4f54f2c1f52c73cd9f8a48ba196576fb67dae6d31695700da2cc6e5b3bb9d6efe209b3eb96b9d6967b3691be498d00b9dd7f2673bb2d274e54f2baf49da4422fb5cccecc5ce3205dbc7a7693bd05387f82977332bc60a6d132ccc0bbcb3919eaa0fea0fbd0bc9acf62166f06f1724e863ba693f37e97734c42b02ee7984470653d6363d620037617b9d621d7fa99ccc0c839a62dd72fe7985070cbcadf2f32c9d4c495f1e775d535763b80dc0ee89e1af9ad1d69e437c9c4e170b83b9f92464eef7c4ae7146386ee0f3a39599c7fa4bbeefd5d44e23d73f2eb9fe2fc16bde7a40138b18cf3d9320d387f9224f0052739831657d6b2cbb9a52fae2c1eb688645eced55075653d6b71885ee61a7f8b4886e6d5a5e15a7f8b24cc4b9f4916f927392f9343f3eace771109b5b19ef54f52c6b57e273530724e862362345c846fff28e322ad20fac10e1685174e923d58d833ee597973e7bb2ee764c8e2ca7a361f64ea3db973790e20370876cf3dab5df7471a04596c40bf2d912efd239b3b8b2af8b7bd21d96ef4dbe64ede906cf48f746d9fcafed35f1af95561e4cb29cc777b3bb22d213124c2f57eda3a92da72deee3db2f4db7d93fe47bc6d2ca6e0b6f9479a927c9b641bf74fb521f9517733813c34726ea98ddb5dce2d65b90c63918479fd8fb423cda9c4a70246160d807429b521d29c0570b204e49e913091f8a74842503fdf20b64347977fb200b202e81d2ff8a29d9b8074fbdb863df3f7a79d4742bfff91e6f6b119a1dfdfbd9f4f6bfbb82dbb0ff2a7ef791fb64f5f6fbc2a00590123f8a2bf84e5f2fc72de69c39ed1a70fbe4c18fdfb878e2e1d9a97da0a80dc20b74f7bc824c8394a6d8497a77d4a23df3ee5cc48fbd0f7d7699feffa8fedc3392531ae7fac7fdae639d79fdb27259834ff120097ec88e099bf976002e096d4a1fca71301d3fc69ade328fbffe0961fb478025371fb6b869431f214976f49adae9b20e91af5a5cff405705954d25d6abb6e7e51204319f90b14e42eecf21727e052afecd2f940a0187279faf7a5ce65066282f173da87763f85726eb524bd030a19dcf9a3fffcf4ec87acc6ed9e26d9badbd95228e73e1b23dbe6b482883b3830f586153a9c61732ba048e3baec7e4ed3e603891abdcb3928a86ef9777aed5e80dc3eedd94a96f5d6ffb8c8e77111afe3229d73116f2ed2938b4cae79cfdeb441defb7b5e93b567f57bef67b565add7fbeff3bcaea3d4bdb9487def6792ef76d523eb7b5d3f3bf4dda07e4a96fe4548b7bf497b98028bcbb929a2e829a06e0bd13ede50fcd3494cebff61a6958d85e5c33c2b3bcbed2f6197610f8021978342ea1af5f5279a4d65be006e8b27787742b14e7f0f31f2852a29974b446fc95d54e8d9a463246f433581ce3c9b30da339d9e75397358ac6736582e7d1bcf8ed0d77bf642055f6ef7448c4471fb5930ea1fcff517a07d38f74413b779b84c223ee3ed248ac3a27f9240272438f7441bfd1ef9002b73900fc045910fc045d133be3ced53d621edc329788015b639447c76ec9b9e35ec0131e47ac677bcdec30fce56f25c2682f4c412414081613583191c61632a2ef3cf8ef40fb5f1330466940f4fe0da89266eb71351dc6e9e198ce53febfb6e0ab2489e32185bcfb7f5f3bfd6f741f4e9839d888465025292efa72d83be9f22fd8f0cf23e4a270f6bfa1b15ac19e286d3283c37c83fec197ffdf69e7e29c473ebb31eac64ed591be3f77c9b45d6cbfe536dfdb59e4065de69a98dc2733d1b44bfb32358aeb3677077f7f9eeb3c7985eb610caa17d93dc44c5718650cef422c4236344172210e2f9beeffbbeeffb84be28dce5234beec25d7478867011f0f96ffa67e6f530cf1f8e3d00a6b7c9c4629e5b16461221f331b171e61b1623ebd9477bf67d0c19ebd9f733e437edfb1639eb190cc917f6ef3f32b0960c599bf63d8c1c7bf6fd48f2f4ecfb1739a467dfbbc802f4ec7b912ca167df87a4093dfb1e2451e0daf7ec3f449af67d3f7f8a23f46d59eed95763ba0b0128da92874b17c6e2b22dc51785786e149e1b147eabe4b9b30c6dc973c3cf893dffb70fecf9cbd88d79181869a467fd317248d3622cf8230cf94e9bffca9189e28ffe03f3e3f7c3902c22196d19fe57fe15df4596e3156d19de72e48bc20dfabe1e39d2d8b487cf96b3fb31598e2e11c808c67f1739739aebbb63f7fbf0b97fc2a67dff3d7873bf77912212d1d69e7d7fc443922c6577da3246b2ec67bb882ee3f89e9d43e61879cb9d638cf5997462ccb83c1632def295467e8634adfbafd67194c9fe673c3debc68f2c6796ebd9982582c575e6be7624c039bbdd29ed989cff8948645f8c6cbd18d97a3306237fe7e22a76e8206d752c2fac3d63ffce3fa8b3b567ccb3d6cbcc9fabac72e9e5cbb34507a50e7ed37b1aae75f4e69486d487be77e1e4e2fb96eeee20839f0472b808e55cfade046bc8e4bbe82efd4f4442ffa667fc32aef14f19c930a21d2dbfeffba8571a399805e46286dc1b605b9670895cfa538c71be1091cbcc52e8bc80bad3d9656a4ba4282e7df79f618cf35d88b75c3acb18e74f2e287da702049fa76e39a48cf5112ea202d7c42fdff5e5ec87884effcc9ce6e1f74cbb7eb5338db11c5b3cac073b474906ba30a28ba62bc4c3c5849eb946a74d7186e4bc2209640463498488ffbcdeffc88b046718b2fc27d6347fff58f86a8c446e8b88ff2421517fe96daff72f40fb94d5a7be7f09edd3ad677d68cb1b6f81ef42442eeb5d547253010e42405d21ee7259f4b2c8b23d7b7d2491c953617b4ed417e7943377bbef723b59ff94e0b4ee8938ad7b7e06b2c548ffd6b30ec8d408648ab19c5aa696f6e96f6b342f121fdae7733aa27d3e4b0454fb44b58f4d4f2cb77bbedfe8b4eebbf17295a55c05640463f725a8c045d0184ba6badd3379d23ef5bb670137a56fed530acdeefcfeeeab98506dc95c5c44e259233a9be05af71d4219fb482023602d3dcf184baeba5df7538c4a2255e6531212f497de2644fb94d3a97de8779f0f8b48be5aad106f51a28121b8f042758598ea4e7a3d925e4a7208f363fdc3b66aebcaf176b652e7fa7733f7d9c0df97eeeeddc75dd7f1cc72bcefefc85276f9fbb2eb3aaed1779f1e59c62e7764195eae43b8c66347070d27173d3db2b6c6e8d9dab376a6773ecffe32bc73ea08d7d8081109e480219433847f96c3c577f937936a72573bebc541bbae637ecafcb2aeeb28384da0725ba270fd2759f2dc39e79ce5ec9ad0b37e27857898cbed233deb9fbc85889139bb5c29223d6b4a96425198ea064d8b44a73fa1d333de22813b9fff36c5a8bcb94239977bec596cda92c89ddffd4ce7bdf7294444c65bbce9348020c8df24ebdce6b94de476f9df7739b7ab48c25a8fb450cee5a79c33e43291cbb2d28bf5cc93fdf5a6673b7276bbfca6946b311691944244eefcb6b3fbc299bffbd2f3f83dfe208fe716fc938b7e3ab718e7cf1890e075dd7bd77d10a5f426d67d4929ed485b32b9dd53727631cea746dded3a4b72d9755de74f20b70fbd6077777b7bcb00f697eeee3d6bbfccf432fbf897dd3788a8af5f5d3107807ee9de414e49a2795b095f5d5d6effb2bb8d5a890817c9a76701e98325330d625ba70b2a7391c945f896f4b9b6c6382f7dfa5e7b367ffec806f885970dd0e6394b505582d9ed449313b8ef8201726fc0eaf60c07708740ee12702901167480f38f984c7c694288af7d027039c744976be4d7bfc8fdb84873cd595150b18aeec585f791f1b1bcd4d7e55c1371b0445ccb69006b53a8456c05e1a247bc6050239d02165601d3da2226468232623130323cf50a3855408239aedf2e0196db3fc4c80765c84528002e915bde9044fe4454c6cb3ff4978a5fce5d4af6b8200d295943ca2ee88594e471c12fa4e4cc056b48c9980bb2424ac25cb0155232c805c190923c170c434a861714434ab62ee80a29c9bae02ba4a477c131a46477415848497a419890927ec19890927dc1180c2c2626e3b24446e3ddc9ddd0b580f30aa8286e49a19a8082f280169d86c262ced9ed05ea0caa840f4a24415929d1841249283184a7441b6228a1c496fa8d97734ac8515d31b0bca6568b0360750ac1277189dc12276831e10ae7d42b146364282e505bc46496f012c25c2616de662249092c4b58dd3126b3c41bb5c28493891a6238c74416d8f8728921d8625518170c16e04cc1571954d3edc94dc059c41836146e323373126d24e18593f8726524c87c11d39fad4a9ebaced39673887333b72a798ada72e6b69c35151628d87f3f8e0ffbbcf88f0c4dd9552fc8946d35f323e3f15134b6b574ee3fd9287694d31aca69dd4f4e6ba75c12b95bce2a9eba652f4199ec74923dccccc7bccccfe75156198c2dff636c29b3e5e8d45aaa6664ac66aa50cdcce0bccc30cd3055cd34cd3459c954dd923249d9d1025bcdccd4cc8cac8b4cf6fd4de53f3c686c6c2ccf6a6a2c0d0d0d0f4b79f008656f4566ca66acf7f3a7d37492eae9659c54cf1ff3b3cbf3ebd9a614fb39f52fb2e78e71dad74f29191a1e5353aacb949215999fb132a599b7223369ec0b323b3363a79722a4db39ae75c76c59636c39c2d85206b3e56cb4e5f7bfc8ceb195950b9ef84007d0880304b6594b2db551830db87165fb5c8ba8343c80abc3043656d73147087032e08ab0816c2859e9d2050c4e6063a7b95e7ca79af1c2644714554d6ea585e25ea04e2fd0a714c0616b24a66e4999a46ee75cb3d9d8b2a54c566df9f7b3b9dced9fe5b4388d9d3c5bd6a7ce962394d3f8525bca8e2411e534be6ecbbf50311dd303c83925cddd999f89e241130545c38366522d4eebe741e79cb28ac9c46233333433333c66666a64646256c94aec9564373ff333df3236375f4dcddbd467ffe161797c0d8bc682cf36997d61c6be20f3ad076913283e65125f4f712fd8bbd5b7555b5575eef3a8ea50ac677fe2a9b5bdb853d15792b1de45e6adc4629e6d4a32d6a73e86a45ae69496a9db1ff3e59c1a81d32d1b57c563d556fed34f4eebafa3ac9f6e3f68ad1ad731b69f26ae9fc62a1be7d47c9a5652d3d64f9d1a14fff55552b7ab6e4ff194ffc8c4bac81e05a74d2a1e305e669e9d36be8cccb3d35c6fc563cf4e0bbf63969d063e28533ff6e28c65a7b15ee65f3c2c3badf5330fbbc92ccff7532dfec3c3a2e0b4f93330b6e42e773ecc965566b42553ddf92f5b8e31cb4e73d992bddcf9a22d65a12db9eace07a996dbcfe33f2d5b56962dc7caf3f178b6ac9dadf3eb6521c1348c1c20e780ec3face707adaaa68d454e1b4fd127a7e12ad3d8c44f71fee3ef24d3323631ddae2375f72e5053fec3b62e54d40b7d826a2aae7226ff513a8ab1d5af4fadf0c9296c396979a55ba8a5c97f94beb7e255eb4c4eebffac37b93375d465bafddec5f2fbf7f4bd9fdfb2b63fdb9e65a7cda6a6f2a2e5f6971d75fbbbc97fc62a1bddbbc828eef697dfdf915063e950936d2fa34fd73673d38b97cfab9a39a8692515859bca35531729a7b537a99a3edda64ffed3475831061a1da86105b67e0ae53fae830dbe1061441054d8fa6994ffcca519c0b8b2baf2c2d64f73fe13e40a1ecab85ada62eba752fe339da82307263a70c0d95a4bffd4e23fb76903bf5bdfacff89f21feffba784fd55fc07b42538adb79838ff695914fa2793ffb0ac0afdb3a95a262f789605a7f537f52e5cebf72eb7dfddbd9f8971d2aa0f89f6584515ff61b0b2be9c5a2e2f4d1beb5fb077f173abf52087e2873ffeebcbc974f92b09b333cacb6827d4d2b4bd443b9f467e1496a60df6adfa254b7dc939f04b2297c32f792effcc7f6e283c3f8c9c52e48b9c5d5ce4a422a717d6f3d72a4ef347c169e2f38f22682753cbcea6b6a5cc4e2d4e9bb6fc3bbf9c52f367b30abe3f4f0debfb13a995b258ef5f82ff80e0cf9c361fb4ffd992a5eefcf23d5b724ec6b263674b22d569d4963c7776cd8a01e49cb273255b3dfd92a7a82dd996b3cbb664abeb930463399d9e6ec9565655fed3a50b1595172fe594f29fdb1fc5d8fca793ffdcc4f7befbc207bf64a9af6b3deb4bce755ffdef4b22b79c4e2e7b7b41f4eccc697d7bc1fb723a85f641a95ccbca589c8baa76fc884079b6b2d34a9e6e2175d31480a327fb76da7ddebb9effc3faac16188ab19779feeaaed7088b81791ba6698bc9ccf0a8f9a4a56aa37999272df1d86c6c0d0d199391313c666448598ca4e120f363c8fab15a1c040c6b6c6e38480f1f381cc425be46184ccd746fba8db445df738deb9e7ef8370773b85ce35e56bcfef737f7a77f0bedadab9ce650537846960b1c5b7ff3b1a79ce6b73975e3d9e404c29acaebec6d56fd847a7d2e741c2bd7385c2ee46155415541b1fe6b2a707cb6815fb693d89a4ea3eb4b1e5ff65db64e89a1958176ec26961dab954d1dc5d83e5b9dd6cf36cfb28dfa0b69762b0b9073ca59553faf7bfef71ffafc1ed991569c9256fc236f325fc91b8f3f8ab1252d8936eafdd2b4794d947e47ff567f69daeadfba168bbb5f9ab6ce5b323154ad5f9ab6968cbd252d89b62426116c32bf346d37245288bdccdf78d8db8c5d9a3619ff987f8cbd4d2aa7f9c3d8a5696bf9c3ec14a7f9b3ec8d89d3fc47bb346d9dffcbfecd6597a6addadbe8347fd14ea71b3bcd3fb44bd3e6f9837636396daa7aef8d9dd3ccd129ef998ae7e3aad69793aa658f626cddb3ec518c8d3edbbeafcfb6fae584aae4477a6447ca9c369f92efb4396348b3bfafea8ef44b59f9b79cf5dc31127dcbf5ce62bd7d4104bf5b2dd0bec00a9f3efb4f685f68bd7e3efb8f52d873c73834ef2765530affc624c4f6b22f802efb42f879569e95ff88564914ad67e534ffd02a85e1f753eab3e583560904676eda52d6b24aadb7525fa96539ca69fed47a562cabc47a2bd5be409f6d4a2ccb4eeb9f504efb703d778c44def7cf2546ce2181eb27a927ce2181bb9e37359f9efce766c58f626cb5f5dfaa3259fdd638de723edd9884d858764cd9acf80bec3411aa6c4f3d778c7fdb9673887ecf1de308ccb5b256f6979fd44c629c498ce5e7e459959f5397f9d4b7fca43e29ff994f4ef3af751c65b25b7e52d79ffdffe23ea74fea73e2336ee94d7956de54cf2d3d9cf7e4e13aa82e1d54ec9694aa63a26add9236d128dad4ddd2735e5c8be7bacab5485955b513aea59eaed55339ad7826ee4beec253d6eb7389712a31cedb5fcea7afea32074715a9eb97179066c766c5a9288e15ff52d361ec26e78be9b159695cffd2fc6cfeb2390f9be3d8fc039bdb9bf74c426c9dc454b359712bdc92676ba6b675cbd630b6aeb1b5b53508b6b6b7ee9984786c24c9ca6763e2b1f52fb9cd3b9b8b3697b1790f9b7760737bf36712d24ebbb637fa4731b6ef6bcf7b2b2eeb3acf8e6e6f4c426cfe4c426cb718b7ecb4f94731362621366fd21cf8fec334b6d34f5a7a99edfba4a5d1e63df8fcd57f929886d89262bf44c4d6faa5d096b4f436d6d7e797f94f12d34d28be5c3ffe528e0d7cfe318969664b5a2262837dd2128f2de6976636985f92d9c64f5ae2b179335bc741e6272dbd8d7290f9cc21cce78f215d1c643e0c297290f930f2c541e68fe4c821cc7f91321c64be8b8c7190f92239c341e687240f0e321f24652c647e8bfc38c87c16197290f995ac1c020799ef91351c647e47de7090f994ecc141e63be98383cc6f128783cc3f8a99ad05e8d15f8a6123494c3c36ff256a6ba71d7dfeafb25addf3879dadaed70883f1e78fb111262ac34698c21936c214e3c146986e646c842987868d30e9d8daca9c6684a9deb011a6b1071b6192f960234c6febe7c761234c331b1ba1964536e276ac61236ddf69f3f98fe86c2a9073ca6e0a89b1ec9e3a29f1add05c14ebadb00b4e500714d3101bfd9b9527ffb137b732886013eded86f3eccd7b1944b0b1ec0de9082a64e8ec0d034a503029b18226ae6c371944b0517b3b52c38730b43c75c9c1e6f4bb4f5a0a6d5e8faf55b79f296663799de7e35bd489aae3f134f63c62ddcb4ce13ccc146da2f65dfc1b1229906fff063ef9379b07e9e76ecbbe86e2fe657fc3ddfe9c8b1c7ccecccddf904821e667fed6317ffbfec614b3f98714eab6573914cdbbfe0675fbab6ee7d89bf81cd8b7f63692f6760404dfc6de68aec6dea8ccdefcade778d81bc7622f636f74caa7702c6d82c9be656fd487a5b4875dd2b1d5677d39e658f5a6e66d605ee4f1dd7a98538beaf657279967395131958dbd895f639938ad7fc6de50705a7f8cbdf138adffb3b7251ddbf7a1bd5128af72a886a2b15df53cc5e3c9441e33cf637c2fe6f523d46d575d280e21c40b47257abe100422480d636ff0b0421925c6f9bee3f44b77770edddddd8180618e12351a86620e3a345dfc10c3d02592e0e038b95d25d205e1be42100b1a1d43590c66b090470c1fc0843de0f020268c3131cee79c58c8030b0732618b0576266cbd80e411be154f65a10f2a7068c287818f9a3096450f9b7044c10dbd0963399b1ea10f236a7c846710118387cb0c8be72d3223e390a9537322dca0508e713e75777797c1096169c4ea87231731950c7d60016343191630ca41f82d8d342714a35e1d842010aeea410802217e10be72086761cb0990d2c2708c560521fcae60fd087d00e1e9842d233a2021521312e37ce7aacb5f1391f07750e37c7777770a24a470305bba13864174e5093d2e6690b053639ceffccc3b406a3a3f40a0cd3ef0a0831c0e2cf9383e7adcd8d4d0c878ccc8c46260785c43f3eada24e1b93eaedbd202b608af5e3dfc3aea125fcf2292f18f34c82212d81f6998ef594412c3748c7d0ecf902305402105264fa618e1926653cf3a4bab95e32d5a9ae6985a9a6696f6d1021391cc2ae66c82b7cc288edc1614de64ea59e3dc0027132ea97d7c1ce793771daa00469c3052654a1b56538caabceaf0c1881025108922c2021339bc3079c2822b8e26225a4c1832c5101550f0d20585145410d7984943b0a0c043029e128ed0c152470a5042488615cfc4a5a053021d223c7610e11942045ca30d9d39549849319bbd0c669f336bd54135f322811c26847264630d1e7cb0caea773f4b4452ddb84e2f483a2994735b767eeb33c1c82dd2a8bbad56cbd69b198ce574fad6b784e8a72212f08477f0c90d15df4d8cf5e54d7973c1564bd602bf7ecb96e07f7fc46364949b5b3ef944245f3512c7752b747363be18cb822c0aef5ba4904c268bc53e161945764b16ae7ffdde1391781f8fecc278304484622808bf450ac5ca5819bb2d4a416e9fd67b6194d82d85aeff4796fede772292d0bd1cb742b10b0b613a53c613bce8077e39dedc502af47e0fb6c872bce00bfcd7d3fea14d03fff5e10c403877c07f3d8fafffc872ca108f4f45242f6b44ef28bbe3f82f925ea3f009d7b748a1f00365b2da7a6f916578c177b20caf0b7cd7d719803a77c0773d8b43e1f56c697443f09d74bd8b485ce183e477757555c70dad5078c50767e822e975d156f97018f9bdfb1669e4d73ba8919d9c2c67cb967f5bb29b1bd9188b8de16d7d19b640f0f35bff48b3482a2db2acb7f54e96357c7f3a03c073a7a4b7f5fe21c93d6bfd1107ed90b7ba67916595a1d92292eeb31e299e403b6a67cce4a00890bf2231f2cf0180ff334aa9fd2029891f1b500308aa314b07738cafcb393594408063ac76ccde185d7074808291950608368ca3173433466f8a668391750508698c2214514fb33ad200c20318a4418307b934965890460c52e377830fc8183d393ec861a46f7840c358d360638c0ed0183b30406862ac5b7800c4c892ea800553d0e2780206682cd102348ca08931d6343ad862a4657460c6c802c3832c63ddc28df15b0108478c314e80f0c4c81ac1075d8c303efcf8800e5acec0b2c6194a5db8d8f2410f638d0c3c9861ac29f062fca2e8e089b1b5841523ab08da0c660c7141b0327e977366fc70671c3043cb9d9fc3050f6a9871cbe761dd3c7f09fe439f7f8afff8b3cdf3cf9cffccd6f31f61c17f68cf38cff6f5fc44fcc7d5e3397cfe09e53f391ffb98e757c17f609e3d781e9f5ff6fc28f88f8fe7afe23f33cfa408822deb05e19bb499fd07dfc177e48d55aaf99bf748cbc1dbffff481cabe475f0b3af24cd2a75f7b407c91cab94f3cfc18b648ffeeeebc3480fac07effd072f439256c9929ff33c481f568926f3f56948259b57f2f15978b9207c4cc7d6c3c7dffc8b54f261affcb02989efe36bbe5563afb86c386f639568fef537f68a8c4de9fb9a97d97cec5b1f432ad9c4fee65d37effa98bde236251eff7a16a97463afd46c4ab0bf717deb29a9e4b25704605302df47cb5eb9b129b5bee66dec95984dc9e6e9c3904a32d55ea1b129d5aff9b657aa4dc9e6fb9d66af4c9b52ec6f5ec65ef16c4aaef7f19dbd02635392f9d7cf904a337bc5c6a6d4bdcd07f64a8e4d49f6aff7e04352c9037ba5655382f1ec95974d297c1fdf81bd42c4a6e47dcd8fa4921525245b8ebdf2814dc9e6fddf5ea1d99466fec5fa7ff227a9c481bdd236a5f16fde8a1292cdda2b03b029bdde87556259a5f19560af04f34ab18ff92bd3e6e3497be5c7a6c4fa1aab54f357a6edadd27ca57e257fa5eee9e3904a3656e9cab4d9bc0fabe4bdd2f74aac9fdf83548af91bab646395645e69e69578bc12cdcbfecab4d55825f095c257125fe9f5ae3fe2209047b94b43b2d3f865e4cc69fc3c48149cc63f4356711abf0c399d9cc61f23abd3f863481ea7f1c3902a380de734fe919c4f4e637e1749c469fc22c9c469fc2139999cc60f9213ca69fc2d52e6347e1679c469fc9564c1694d4ee3f7c819e534fe8e7ca7f153b204a7f13b39c569fc4d4e2d4ecb398d1f691ed1ba12017283fcdc73c738c284e25abf0a5cebf6c058765303317ee397b381184ba6fa619cb7ac138a8b14f10f054dfe8f6a4e8f6a2fcfa45d62087e3b8d51dd62d5afbbbb9b54e02245303f14f47abe4530b6a9b856ebe787d97236bebe9c512b98ba8d858bbcec7f2d8e9a358ddf6539b4207b5354546b4671b5519f8dba1dc5b5fe26b6d591868061b3aa4175db73652dc752e6e4d04bfc21309cf3f54114ea5f41f4881b97f841f44926065121626010751abd20ea43cffa893a5b06512d3deb328836f5accb8f36d1b3fe209aa567fded14fbf7be7bfafe9ef39f98a5514eeb87b1ecb40e46479bcbe572b95cee874659772fb7dd8b7b712d5ab468712d2d56fdaa5aacfa55454949494949b5935454545454543b39393939d1262ba7282b2b2b2bab693535353535f534f5f4f4f4f4549f260e377113377113a70277e12edc85bbf0f0f0f0f0f0f0781dad4d342a2a2a2aaafb69136da24d54ae2ef234e322e00fb59c862068cbefd97ca2962d6f7a36653d9bb19ecdb167734eff30ca25866054944b0cc1a828aa16ab7e54542d56fda8a85aacfa514545b9c4108c8a72d9b28ab61cc3a81b2515e54d2d56edffbca9d56255fb7969b1aa179d5c2e97cbb596afcb75aa5a4719ada202499d5b555555552555252525252535a5a6d3749a4efdd3a98a95959595150a3cc5533c35f33a3a6bafe7d3ea55fa51a6fe80538db2060093d314b79c5d3ab2ec926b531c8173458a3c5abba9fba0f97c51e0daa4f1cf6c62f1cfecd234c50880203272a4b9eeb41276fdc18ea47736f98f6cfc88fa0ecddb3f9b6617ff91c9c6b1cbfdbc26a70d5cf3aad9e7d1ef1ec67276995daeff6c721f6532dad44e1e0c40ce017bee1887986b1f0a5c842f5bb23d307a141929b841e1d77cbf47d13e6590f83639d7df6c797d0fa9f17d60017b9c29987f2e314f7ebf77699f92af626fbfdfc5689f3248e639285dcbd7e780742c3debb7a433f5ac9f243da967fd4f3aae67fd3864fb20dbaa67fd3dc8be21bbaa67fd3664d790eda56773d24cd9e43167a60ce9627817e7e2538e854bf916cf79141ee53476b716b0c5aadddddd44aa8f54d6a19792bdcc5cb3e03fb58ea34ce6e57e2587442691d9fd50277573924d5cebff6c39f3d23ebdb47be9597727f5ace7979ef59ca367fd234c25d336960e8ce5f40282b1e78e916f0a5c643ecf8b4596cb392c926ed95e5860753d4bfbf8f45cd3ed7ce1fc5318e2f926b2a9b8083b55d3d7677debc1ef20c6a27ef7a74ced1334ad479111059628ec68ca65d9a245ca072c9ca684e0f2d43e41dee58a1ed13e412ec6d5d597ebdf3e8c657b89baccb5efcb49e54d5eaebf37799337f984729affa4f3b3448ce564bafe93a9a96758faa7b364e99ff6c187fe692184e89f3ea29be8997f3b8d2591eb445eb6ace0d8126d2963fdadb63af27091cf76d4f765375151f97ca69d37a93a8a8b7cefad6e7a96fd6acbff9494df1677529df15f4ad99ca367d496e3d06ca367fe2328999dd481712289930505a5df7bffd510fcd68b1fbee892c17ebc8979988f199a5f24f3b197b132b11818d8f8728921d862d5cfeb6833d163bbab5dec76a7dd77d8c25f6a3d75dbf676e4bdf72fffa6dd1fdd34adfe47acef64fd47ded3efabff512b46bfffa8be077ef7fd51ebfd975a369817addf7fd4bd0763fdf74720cc78043ee85a6ad9446b0567fcdb52cbe6b256705a7fc40261fe1efda3eefb996a6cafbf3deb8fc0ff7ea9651bad151c97bdb1beeffa47fef4996a6c626841cb2dd6d3ee8fbeaf7f3beabe6b59fab72396ad7f3bf2af7ff43dad479f773beae891b7fd0e01363f2840ea03b8d93c538dcded8dfe6dc9b3f90fe11c7d5bd8dcde965c367fa6191b1d9ab4b3f2ba62aab1f9dface02cb53a6aade0dc8ec49f792baf1a5b4d0c667481b0f075139b81195d60cc0b269b81898d2e500616139b898d30957a3132638f99188ceb8fc21f6f40d6d7fd91f74e440f1b9a97752f93592b38b7a3dab2a1b156706e472d5ad9064db69b9a9918cc08fe51f8aebf2db56c36d60a0e8dccde986a6c3ceceda8f5333198d105b2beee8fc2f7bf1dd59f89c18c2e90e57f14fef7b723fa33b1bf1dc93cccdf8e627efcdb11ec5d7f3b7a3df8b723f1bf3f0a9f35637364ec18b31f636330f6066665a395bdec8dcbc6449bf347a17dd08e2dcb2c1b56fbd9da21f4df8e3c1b760821f41f515b3b84f96e6987d0cf7f6433595220fb73fdaf7cb074bdf83cfeeb19e661cfb18f29737a46c690dfb37e1812468ea4cc458a64488e2d16890433671b20b74f10578d4145b8bae38eeb4135280c1a836241b2a09ba0b7422204e5f8d5d51dd7e71060cf1d2351918f21fe21d60fd51ffa7ec8fba1ee87e80f157d53b7bf0809d16422b6e5b39e8865cb9bfa44d596b2ef893e5bc6bc27f26c39764fd4d932ec1913515b52a9b9fe44fddedc6da9ccdc69735873c600e4f629f2bca2aefba12216ab55148645afd758040313f345ee324533333c8a643f34bf48f643343688686868841186e60fc57ec87f08f643ae1fa20fbe92fa4afa8766dbf2f6a4b1a56cdaf279d872e62626634b8fd952068bb1250ccc9631d768cb97cb9623f8546a405b862d5bb2a8d45cfa546a6e2da9d4dccf96df16d7f3efec10ced5d5d5a5b6a45273dd96df16d7bf34f2ae5b2a33b7ad6cd227c09b1eb3c8b44c33f3f26a6c280f598d89c9843d9b45da8e21172132a2b7fba1d7a532c3e2224446dea53f24c2a512bb5d1122a3befe437ea9c844eff95d9e8db5b80895f07645a8d0db15a102738b506951e94bed4dcf66112ae3eda7e2ba5e848a77dde6f46c1669eb47804c9fc8e87b7eda7d5eebf96b6db15ccf1f82a14b7cfe1136fe2bf6fc31d78589bd917763de68e60ebd2e8fe7978557e689a8d0cbe38de89d79a3d81d12e1da1019c15cd91319b52ecd1351f1aecd1bf5ad792222a3f1fa7822a37a7bfccd1bc9ee905f1c2223d725a2d217c736d7e61b7977be0fd2b936bf0749b936ff86ecb836df8664716d7e0d59b9369f86fcb8365f467a5c9bcf830cb9367f8604b9365f866c716d7e8c7c716dd66a23d7605c83e1da0cd764b816e35a0cd768b826e31a0faedd70cd866b355cabd56af3917c4cb7ea26a9154859b1245585941452725f4460031ee478aafae28a10b0ae48828731368e35a717d4000c28a83881162fae08afc8218771e6724eaa885b8eef6b744edd690f358c327777291bae63b1b9ffbbbbf70da3eb724e0a861b5ece5d41c7ed2ee7aea8aa01581241dd1e21c18f449090107fdb2877fe5123cd1e01acedd34dd6f993e79c64cfb9022e5cb8542e5c64df5812b9a38f7fe3e0e978f6bb9c9e7161755688e77a9ee7d93f03acb4769de75531c6f5aa80010c150b1876f0210926a82742d0c515705cd1e50a29afbb2e2d83c7e58aa4eb793ef4522b1db9e08d29b810e38c2fbcb03117ae250fe74ca29e2d5e688163ea06343eb0851561b0ac30e3035dd7759d155bb07458baa2b5250b261e991552d430bb12b028a55356f4205af154831532b8acd0e1735070cbe7f18cc86d69aa62cb16301a872e72d862820f6879838c2b1b6dc1a53fc271691572e8d04ab1cbb92d465cf0726e0b102ab83296fd749bca7f3c2fb3ca9b534ef3ec0b1eae8b6745c5349ba6962a4f4c3cfef37d7f49a47b1b25df69de5447da34807ef79d7da13eb5b10d9ce1870485044c9bf77d47ffb347ded3ee864403e61b245801ce569ffeedc8fbefab3df2fe85a3cfe25c0c1964c1d9e8d3eefb65fe4343701cd94f651f71e73fd934a07befbb299b06d0f79ebe376d1d3992b643eafe3cc0e33f6de3075f4f77faeb5d4fa442eb59df302caa70ab475a01e20b302f3a6883bd5b2414a6c2bcdcf994eaceffaa5ce6e9cc53d807f3627d507c5004ad4d3fab71534e9b0fd3c5a6adee7c5733b55337b5963bbf2389ba17c1fa300f635f807d7db689301ff331f685ef619e6de0b745d2b18f817dec5954e1fb986f62baf3452d777e8cb4f11ec9167bb02389fa55783debfd895468bdebfd871a70033ffcd737e026fef8ad07a7a802eceb4f5185ef611e85a4fa30e1d787b136f4679eaa386d3e8cc4d9d0e974e7cb58a8182975e7c790365e54eeceef4652e634a2a27987868814a0801f2252c1a601ad7f7dab9f48059b06bc5e10fff56c83f9b648baa70ffbef41d8b3a882f81f0c09236dfa916cf5412a2a806d49ad7f3500fcd6b7a882f8af175508bff52caa30da97b5f1774d89644856929dd6226ddca2c01d42974ada34928d9d763422755d5d0072775227c5643763cc47d9181bc7366698819513d440b5851646884ed45063e3e51c1549b0018eded9efd97432a767b418dc510618531da0428a2c7ea82148a25688e7765dd7f97377871b556429430d2f6164810596ee061afe676b7ccefbcfe83f9ffdb276dd8fb8ebdd6ee9765d47ed1af53f27bc9ca30204b77c9e6e066f94c5a8e8c0951e65e5786354d8304367a9b8b2e44d41b798428c18a618c30a8fcbb929d0b8f4726e0a32b8413abf2fc3eb799e57dff33ccff3bc8fa43da39ef7a3ccfb59fb08f15c5a1b07907368bf0da57d3bb8f38914a07f2ad5cc9dc0eacea992e7ce1c0a7077feecce2a140c714b2377caeeb4bad4fb3ec08ad50c99c21e1083975bdbc7610f88c1a9677cc1c9ddaeb6338e31e672ee04572e4f39317af7a5ce0562022f2935c11b9732d9e8c7fa87da28a5b3ac945213b07129a5fea3db8e83cb39135c71c5cb39134071cbf1697b1726f0c10447c891e3e58cf096cb43faa7daf87352545dd8e59c144997c5fe233ddd18633fffa69c1d19eb99900c9c88c1972b1a8050818d5e71e98fb92a2efd9bfee11c0e8793c1463fa77fd8466de95688675c63e45b8eb7dae6fc26c79813a3783907051cb77eb9e5f828005fcc3e77a6902f403a521b92aee58ea45c73bf8d9c7ba2897291eefb3b2e22c7954d27e9144e97a7e8aaae3f38d4fd34724e055d9e6ec9530eb502dca5560ed573c7d8cf391574f1efa4fce7880aa09ac65153b92e5254d75530d534be65e7e5fa774efe43a7a895ffb49414e788102969f5c68e366e8a8bb42dfa7ea8e8b3fec435ff69c535fff9d4a58318dbce2f5c73ce498971cbee72fd5b88b1ac01e01f87fa1ff6e11f0fa54da8c4a9150cb4899a041d536a06680441400353156030482c168e08048a26d7f80114801098b2545a9fcaf42888814a19430821841802000020002030334c0002449b3da1f71f290cec0d6a2eda91cd9c18032ff0dca5a25b87109f947ed71b47ba08981d9bf2eb8274611b9bb453e3e7ac8da1b052cda092de2ca54eb20d304da0082804cda4a1f3c20b1db431f06a0187aaa837b4efb965f92729070114f4bcea59b995635e6ab964472cf4a36ab4b19709c2ca9de559662a421ffc529d1a7b238b3f2aedbd15738daf14666fd4f24795bdb76256f795d26a6f84fde51b60d7db9446a2d08e90226a9d8d122b1a6cb88ea4185e8ab8e828c0b547bbd13679d2e8eb62008f3df7c003341b2a56a1313c5bee8c133f55ef2b6be3a8f64de1d95bb8527d6f859fb221f71f6bcd8b6c8a9d7e5ae19cbd1d83b6ef5dca812c912fb71f29ccf63e3881fc2ddb5d2cbf8e2013a84318cfc5ea0ddb63bb22e7865cad61f213d625e0059b3965d0d0ba019f1b9c8f463543f237858748ee5ea3cc8ce2e9169db250f69c42c4957ef25f05bf927d337805e7a9c127a68ab304437b4fdf1cec75c4976720c5cf675cbc7c9600104ec31deab1daa6da90ea2e68b2d3fb183b47fada1739a348859ad424dfcd9b5a51a636050bdaa24005bb3e09d32a06af898c3ed46bd7ac2df71250bb69135da4b3230e9a600952a3cc8dcc1ef833daf8af458999fb360a2a0b55ccab6b747098d712665194cb54527b1c476b4e091333379dbd6d06867ef58142c14f115b2b940d25b4d25e5dc424683aff6af940c398b7e6d19b796ea7247d57f6f1b29f5a3042643962d66c46aea38ee900a581e4719715dac6a44d115c8616ed20a5c64ae32713d2162995829cb618dba14a67e654b9a7aa0ad972cd3565b969a3ac543a5ca6a717047923c352c8baaad3acab74ac18f070c0da4cd214b5d89b3bab2805208b9e7870c36ad252ece6816850c9a571009a29569705ebaac0e73f4d13750b574673759a8e009465223fd72f4de9a84d861936f12ed4982c4395d1424c35e51c48136a3bb71e1d79b70b521edddfdd699a24380f1faa9b9e956b7b3d41859a768f66a9d89e720e4ef1c4423e57ef896749b13efdcf6ce9e1356c06ce10e06fa93e1305e565f67da0669b831e088c05fa2f19bc2ebf67657f7d691c9ce87d1f5761c8bbe256fe5cb3f027af104fab7322ded5187c0c5e42ee42ce8a270b5b252e3f050235054a7b7bf28ab5bea6c6b4f4964b56e2cb65ff8d2cea2a8a4b3f71ed05c0442127b2aa9aa3d5949d533991365698308826d8568c9a443f916060611e49de48596136deddaaca8651c4e1c762906a01a1b40da9f7888ada2ba960e322b592daa3de0455ba1582813a54d77f8d896cf131506088a9bba9f2e218a5a46f5ec66a4faf1697b1753c30fa2d759189369d38b8d6059246990ab474780e435d3f1772b935304f056102dd56386b9372ef6b781125778254d44995bab3eda025a1b15072bad43cc1afd5682b16ce25195ebae8309c013ed5bbfaa9c7820349259df8681249971dfd2c01ddc3db884b424f3a0126be38a999dec88aa3d05b904b4bd83d2ee6043c1d394cfae52f77a340521e074963371f336152fe576a72774a16cc67fe0188393e5257b75f154c9409184dc7882e8e76c44c617f8bbe191a9eafe4d9243cae15e21df29d5863ca452ef3245a288ba66b90922243c5425aba5c8c845af81354dfb276445e7638df27521ac547dbc3cba9a395ba982649ee347c062f49167dbeb91b7d93f4daeadffcdf3b893749e28ea96747a2251dd12dcddf32d81258ac5e1626fd71cad511914d5d789326ef8c6c39e122d373d120c1974946253fc4bfb63eed5e467fba65f46440959dd836ea6d27bc4a21e599bf34788ad8bd92bc90f8902deffc3049c8b70c9de0629f23c561da895c73531c7471978f3e349ec1402aaeb5a6420bfec49334613ba38eb3267466eb8ce8d59adf4b3e7fa89e2274c0b2c1d5e34047f6fa091d695fe9d1064ba530cecb62fcef245c4b8f43d09f1cfb284d2456bb7aacc819ca1888b7dbe2ca0ff83c8994bef1da1806f9c27a7ebb01f4066e489df2076af97793f05fc779fded8cc8857e1fa2017d627eeefda020eaf90d931dea0bf2ef799e2b75c10b0f8c339059b86124a8db6a81dc435fb43b9890082202d636438c8a86aba073f75df8e3cb00f25ec6f0720a6cd342f71ee185cf3ecc02d4534e7487cf64be6feb6ec18d340400426452f811407ee619e2f0a440bf0d20f1252a42c6506345663530c1585405f964c212073a6ff8b86b0d61a2ae9c0511f5e57ec7bb433e6514688a56697d15b4c70ba4dff9ab8ba44f7a83dc758e4cf30c54e781d89513d83d6c9915014b882ac53f346c1c4b54bdb424b3db0688c95498cb9cd1f3912197cad54afadd7992b8f40e38baf374eb32a5e899740e46de3a43a8f39bfe7c9f77fb734e886a2212d4c175032075d54e4f44663a096901c81b5d71042a0f83841fc18dcfbda063064b2b09936097c6d75db9116b120847d2f7e01baca50e9ee5276548f69e0b460954143ed52ace21ac28e0c83f9e3d68202df581b25a558ab3dea6f7903571f32706ea6f46e7d1e53f6d15283d8ec329093f492b9eb51e5911020f4c6275593de84cc4fa54886d513dfec010bf4ac8111316299d70238b13cb64835f4849c7656d2e0a85cbaf474a1b9ef97cc84dbcad93e7bdf19c407ddfbbdfcb660f84cb303a705bea1c8ca65fb7955df5a2799b464b5499d4ffb010399475a9bf24a1a5aa03ef2c7dd2742b2336842a1b2c0ad87730a7c4ca3f80e15e3855f446d650449477629f313c4f6fb4a4217a4a3923e2f24408ec1d306fe55d3ce2f2f74536516422b51e0064cdd56b59be5baedd1e0d3f181055d0133f7cc1fe93eaa5410f74a11b148f70902a9f4e2676a6b4ac09fe8adc5f2a4f92aa5b8eda56735b937214772fda15e585d2c2a28777e3e755039e14322c5e88726dd471b446e35431e4a16d93141a72c3cc5fc1e0ce7731033d8ed114ad8b28fb8134a6a282c1fc20b6c5bd8caf1dc90d42bb6a4b01633d71b7b117faef2ff0a21c6a04c3e80736d1bbe6e843733cfd43091f91d2457f54f5355960682c21079aa9029fd89d215e307f6a4302057a57c42e62e034e52adf29c32f54f2d3d99d80053b9635ef497c564f733fd658c2b2819f189c57e9ab1ca23f1f2edf474eabbaa0371d966c29c6a30ec7cf09e866b7f88dddf0491ded82d9750976336d482c070633bb3b74b3fb1bd64564faff999dc059947cee8bc9d7815955e5e7a395c65a9988e184cfa2aba11d7a67263257d80c86466de8f6608bbe5ce27fe78cec69c143a2f2fa3064b76765d5c5d29e4f467b4d60793bfa8711d61bcd37bd612e548d115f7e3601fb39192e1fc068ec54b4eb8c9bfbc03f0d32c346d587193b8ea03f89d7c3b0388800ce66685019b0302d438b54cda15e038f79fc73f15bba5e4eefa3854b37d6ed5b9871a4c68d574525fd6e5007eac2a7ab425f0f09b43d6b8bb407bdd007d57dfec15cc52977cc66dbd6aad4cf6ebb25c7c66c7477729dbd1e4102b4911dfc436e9c29b3aad343aa41ef334246749f382e62d8d479cac350015db653088a4e93b24be39fa1beb8e1add7776c9826d28c4e8a96d554e4e62560277dd21e413f73e700c1b10e90a442a210b5c402d206d083c21b9b9a166ce94ac1f82a51040b1f3125afd009698a7b3faa49f4758298e68b352fbaf599bcece122f88bb819e604cc23088b88bd725c06333d184a2fe4285c4adb755ee942c78e0dffe0ca705cd465f6cf41406053e51ba9f5d14894bc014deaee964462db16cd2b60296041e8c4028d9db38eb6acb9a1451d6f72e963f8a96bb4ca7913b7e16a174162118e1d94dcb0593f660ee859b9736261b26643ce67ff9f7cb76152f0f222db9042a421a9d4b642938423414faca9aafca9ac52a4e42fefe1fcb3a9769c4daabe989c05ddc21873df5c3296475701c66f8e8e05e3e4626b0bb90c5c81913b61afcfa5bed9f78b5d667473e9752ece61a6b78bbdc0c0f572d765d9e2566c5112dffe7a7967743bb8568337ee2c091e2cd153c350a1ef642ca8e59a70b18be35a73b227334de8796aa5a5f908ce82566553f0758566843c57684a09a19e832946a089080e73ec42c0e15dffa5d3fc3a22ed6eee702894ef04c694441d3882bab5e3b19b77bb39b9134f94c88af0dacdf39e785c32ce9e98219758ee795b329c4a3283c89609c765679600e120e0da74e43590531452a8370f7acdc890af227ef833ed3f89dd90c8c4212c55c15753ebe2d2d9f8d70034966d14f77a869443d5e7dd4f25387ce285d9fe78cffb02122ba74e6ec3d7316672bde6f746497f635c08deb205c8fba9babffdf05937e10c0ce4b7c43971ab6ccd20005665d40581912a831504cc53d99e2078a2609b21aa271506b5435301ef04c6d6457862dfae833c81651b0f1ba9dcc52a791f2aff85393f5d68a7855ab398698775e08463158fb97bbf52eb4ee24fac01c1a5354f3d257ff4745dd952081fbdd08ea83dcd92f6ae1863a07dd78ba04d4290fed64b1c6014814cd81c90ec593fd8fc8c9e66b6c4284518ca65711cdf049454e5a59a16f34e93c1fd23c2445da0b891957c66f9b7466cbc1fc32b0a382351bc5b631b89834eddb2a8d35d2c4f2261733aaadb940cfacc87c0403e7d8ba238d3f1314c24f4941115f54a5041b2fc58816ae3864ec399039a91f8502577fc5e9918a5c0e752ab49da983b4b752de08a80bf1d76cba9f556393aa8ee96a2d9d76f3a8fec390782b9205334370d4f25d0f29510e2f471c6684044fa496ac299c91e9cf4dad132aba8437a109d4b188b6381dccd4af3d725174dbbca4ba552a85440ef9cd9a2a2ce1b0658827443de4eb17e69e3ebd75c3eacec4710b3ee4c188d391abb468db4fce44553bfd415f77672ba125a64755f54e99547dc8ca391131c80eb8b647d9f2b18d07b17d015be5927b2a40a66bbdc312e16731e49afc6efadd90b887593c8f0b8ab7c48a8837d2918e7b9b2ce4b12cc5211481b78b474713c641762d99a8aa0aa5fb258a62a393fb6320e78f168251db5e5b57aeb0d02ea9adcc80d00245fc80b9b1000f99ebffebcd30bdfd3fe403aa53a590a57f94ebb7d4511a1e9e0442465b695f028e6eba26cab08cce4bd653d01a2494db4487a3dcdc294f84dc59c341ce6f57c976863b57e1c376cd23fea5c09ae5ede3444c64404597f3f3700f12dc40fdbfcc89cb974adddc12545701f469326f207c133d0e620693202b01a031c8f102835953a465db484773104294f553ce22e39a560466b228950fe8594d689cadaff46a6b27f4e6e6b26313ac6365b1a27268a86e41bdb58d392abd83920819849a02631c279e96adda9889d39e3be786535a4ead7082aa2641d98820623ef3a77bcccebe6adc609d7d2eafda7efdb410b95844a8542b575d27ccda880f4c430f8c40f9ca179b09fff92163c85214f868e9a91fe9dcea6fdc336d507b73aaedbf3275509a8ddea008f3a04e5847b6ae6b006c7b4655231cc72021e81cb57cae946139c848cf14f3eac4f842b16b066d8e2354def71ae1498e3b6dac3375d7f1653a41de6187acfef508754220543135350ca52ba889eabb99aed267c66d7a7627acbe02a279711a81bbebaf5eae1c9133973f49e66f9c6835bea8ee90033c0dcb5a3dea5ba9d7c3a65c454448d6eda84c84ecd37120b0d8e18f3bf1491ae04dc9fb8a980d92486aaa7ff1795f166f6ce68079ac865491a2b63250b5eb7728acfc0968cf9f753ed242c871a6a4d98c43393c93d281ecf57fe38bfbe7df0bbf3f5cdd764dc7cffb471273a2c76a9810446a1c374f97bc526978649b85703a35e6a6e02429332ac0d99f08a41bbea26920312ce7a72d4ad4bcfb2682e1d1de0174d36aa0d34abd484d402463d7916b493b31c135971183a4214ae08f2ef4c8d5b16e2ff523225e1d5e2c263d71fa0b1e0e2b9739fbc53e46261cd3e6f7430c2baceab7dacfa6e80f3c78406efc56e47a642c4de674c8b5d7cd35161e98669cd281daea8be38ce44a3a790012e4e78f2da5c1c0ac58c0340db48d6e7a1e13255ead70dd2e4a27c4d3af4bb7a517290d679628884204515a079c8ae88ecb4681903a942059fb88880ddcb8d47415132e20251249bd05aa612adaabd5cc0832e680c9faef141a7ee4abcf6c5613159737bc49cd19fe629a5f1be80b35cf8c79d78d591a96cb121569ae5c61df116e892e4090bded4b1b15628c1f85e2831f5414c8d0aadd23e8950c7d3694885a57ace181a0bd4b93dd6742033b4b982c5cce4c66586c3f3b1f682a9ef939a64b197e260b6c5edfc670847719916f30c7127d7b410344119fa2c0af3f72066cae9a77af860fb5ebbce040579efc9174301ef5ced5da526b5f20689a7905da5c733fcb7f638ffe874cc977a9d16eabcfd6f82734be5bac2249c28ebcc59e54a2bad5c69e546c0080ed310755e92eaa5a74d9537f8921efbc44ac80fa2a7775c46ac323495bf245eec0a02e64b99e2e6f0acc6f7a2625bb6d81124e2f7c857d09d08c9f065f0d70d78e3aabf2c8fe93268e87915e7121768a0c5dae4a80b502002d86d462394dfb084145326330cf6ed07196d69bd5ce3dfbbfbc629e78ff792c4071317a6dd87cd79fab4f436a6ec909a98233ddb29e058b802a16b4c66d537bac5c8aad17412ae3d0c3f3a4f663dabb21d51b393a96c23e41cf7acb5d10044a7e8146bd97e9f43dba6bfb67fefc076d7826dbf2fc01672e0762c4708c5cb2088307d946e52d7cb124cf662a0cb003702adaf04c0345175b811ec78bcd83989788f4e1b4389789fe4dda5ee4d74da8d54fbde74852312ea0c790ec4ce2a98a419d4b735def0dc3f20e6c0177c1f676532c82487b1d5648541255404d0cab3c6bbb9483628e098e6e823a18406276b89152fddabe3ab79a3fb063e06277a710a31197a50d815da9ba850d8633da651e85bf2945d10f58d559a9eb2790fdfb65c841e04bd5afc866cfe55bce1dd1738d1d2526e04e7fd7e59e1a35302c3511cf560c8fbfd080e1da8b05137ebb0e1ee3afc97bf49fc18031abc78bb60536b59514be897565860f2b22db46d52daedf17e0476a962ca623042a2a79ef79b8f2815258014be8610c01b17c89c7a5111ec4bbde5e67255f7ff18b4ef6a23a5e2be22186c1256b5b985809773e3ed2de021e8354add2140e28e298e31c7b19feb0c3fe768aa8c33f9ff2c6895f2fc12c01713c7147e1ed5238b6f429e70f6dd475e5f2f26645005e23ca1bc15f2872c9f985331550a198863031a98da57103656a596bbec59350157ea33a60d0428a78d711bd6a807d49371cc728c1abfb8a6aae18c34d0b2b36fc0533f4c929d6d01572763904996ed490ff3f41c37a44fa6eb8993050330e71b5463223bd8b53788cf6f648d7d2db560290c1051e6450723fb978a0c213f0bd1af931561c271da4881e8397c02f79b1a75b67db28d6f6f20051bdda420fd7c4612b20a3cbf9778f96301c4a70981940a072d2f16feae28627b44a64981e84bd589c105d1406eacd04b583fc2d78b2162e5c2b6b32026ca2794d38464d52211b47f2101ec587dc58e470e0b8bcedc70e31f5e02e2ae12d31f99fbe766657e943ccabf125d627343edde9df62e9de6a5b5838121e3f9fcb21d66a893986b295dbb78e95dca72a153110bcbd2efca9f860203b717b06e1b2a2640dbd133a3c80d3b7f474f176898ceb2b2772e0dab1f460367f61d11795fa715b91d3f17e507b47031d7a432bc7861d1f45db58651491bd0fd197a30a51ffff9fa2ccf40e934aeb4dcfbef2fb76d4e74c7233840be7bb858202832b39cc70328959c10045cbb234e06a2c1281acac44e7a2f84c7f7fa30f4308bbd045612a72f1377d55dae2ba29ef36bc95c195437c1a2cc4313c5528f6790c9120f38157128e41cbf10406a72b85e0507b7e1228a3f2a80e6b85d29554fb1872046ed90deb2d0e170023928856dc52f3671e60d928cf63edf42886fd2d4bcb17359f59a07ce5048edf58f6fd55965d770220ce1fafa445a0c11ddc30c271540d3f8135d170bdba4ded555d8ad4b3534296f2b0a762da4bea83d60d89373d5c742bb07c4cfb76c0b823edb23ecf1871ed27300461a3d38e360ee80d2e338fca6ad682b06481e8e83888d758f9c4f37764f83d8ac023944ead7e0a5664559e8bab6451b120d42088dce7ada71cf4a1c3bed6fcb48193d39482b288abba24643bcec7442d5759e9b3094c52dd1a1390a166713d6a0a37b3c7350481b187e25d71cbfc1feafcda1eb22b5d8730dedcf9be91adb6ecb16144a25631a496c5c66ebcee41d00b4640003eb9b1d394f5f8ee79736e2929ce3791b1cc3df155e6dd80b2bc7f91ad13c1997874e15ac12488acc3f4b56d50582f0423087562b03c5e14cab16d17910cd795ed4c93e496aec1c8e42b259af81988b6fa1b51a5e488049e14d63290d30463343ae3cf120486af08f1c0f37fdceaa01ae14858b4a7b33a1b33c1a12588bd226edc4b4b9e1950f63c6c1b91ed74cdcf08498cac054231dfda990740021df3369b6b917e083f70bd4650c5f4debbf50c304c2c02920f1ff289abb20c24c23f22a7fd0d09b3913960cfa389173cb50b1f33181fd6abcfb34618c82a073353a14dae776fe127aa44a747296904b4dc0cbb7d2462e042552d0f9a7770e103a780df978a841f8cd6e4d18a91eba4e27d15e4e2bbb5fd1ce94b332fc4737a572399f759f2d2de70d92bbdbf391252b54d4f9d2a85d6aa832c5ac0f259bb6e013ba2f6d387fdefbe287834c341e91eaa162ea5470ea969e6aeacab5edc457b0bfcf526f0be7d998d3acb54876ee31fe93b885ca08e03fd1101a9f6283b664cd8b66f7ae81af8c330f168269a3a00a2c0835be103c2365db06607e1b80df50733be2f24002d1af85080efdf2891ce24b98475708d11c44a9ada84e8ec23e5b6540f76a277df5beb0078ff182e145082c0b48e70b745ad2651bbb70bc8e4a44da8bfd261341f8b1b9f0eff3f5bf8f4cecb0fe8637b6414d0fe7dede56801b0c153b3bc732fe6905419785c47e72f5c74dca782f222d82c987d84d46c3f83849577f2f1312d9f7c43231455d231806b26dfb51d8ba02fa1be8a6899e3eccb2f5296a1ce3657f20cd4173ab6d5c6642f29621cbff6f5f3ffef84151fb5da5bee1f5de807e861ef139f4b5d0c8eb07ada9136931878d4dbab9de4a8a783a8672f17e87d089e3d744b63154270cfa78a78fe1cfa704129f5fa478885f56fe8e6cd20e377c600fdf148f36798aa837e0bc17ac305960771d14666f739edab5822869e9f5131173b050a24d269c7bc12eef2af6cfe83165b10677ec60f740aa0e14e8edfa28e6412999eac68e15de1d1fff9b82492156030b289ba7836893213fde4d4ca4d6489c2133a8332edc8f8e483452c05ab127846b65fc5930b858c4f6fd0adc85e18fc675aeafc65d9b9ced088ef36a8602b422833128d4263be2cf94612d8b6809da3fc68f4611417b55ea332ec007171df88497c2d16f589402e30bb79ed859d31d9d83f6904bff7f2431e7120b7517bd94dc57fe1ccc3965094d4c8e8be8a2e5481b575cc5ec4f76122c0318b34f5ea50322def9c366a9a9956b9089d7326c330655b82edbad92b26b29f2fd87ab40b833cdcc711dfc62f9cd7bf9eb0738d4cbeeeec7ddd4b6015639ebc1f7d1c01f716642d54edbb40c3a5c5aa960a7dbe6a063966885fa62d49b28ea00681281ccdce548ebfcbf26e751f1b299a8dc3c21023195d54d3347bd3ce61a69da8605f89c6d161737efe6b5e527544cdf3827f3e62ebf33a7d7520031b79f3b8967ff40822c9467bda6d2bf3c06b0e5b42791c3b04db66ba7aa48f10424ed7cb763ab0cffab6af5e679af3183869c74b4e2f63d2ee48f26712972c2d4e1e4849dc4c75f7a3100f5a165f5e15697dc70a5a3ce1f9d3998e8ee3e110c87ef83a5fbc2a20f64abcebe87293ac719fe5b20ac2afcfa7dd558e9be395d0d972a074512b12391d7a856e1eab046b1542cf803f928923f0350a1d76efe4800c91d65eb9659b051104f7163bedae3d2157fade540eccb48e49522780ed91c88b4882f1e6d5a9e4e956859ef320223ea68a57c7bb565981bb56086007e70335f42c08ff6d1ce6a24850a91251ccf43e9b9617100fee0f149ba9b3bbc944eb403286996ff9774228e77b6f9fcec469e0a3704fee9f7077cbef9c3e93117f8cbd5d915b3c53ad34a76cc7decdc8fce33cb69fd1a3db3d11f9963ec58c302e7288290b5a3614a0be453e048ac50974dcbcf9dbae46a11a2d01ba9d9493d386007defb4108f7504d77601c4925a6daad1c5363942e014ef97b2e7dc4a6bde641f4abcd86c5c178ccae69e39aed0f09980653a7d598a67d87587c06110e005a754854af55ef79fdf2034e5efba7bcfff842e16cb8ad4f524331a30ed6ca4b7d121bc637b804f90307a5c9175644db5e0917add4308549dbd5e51a4b45a208df16be7b50dd54e58545da5ec79bb82174614fea40bba4e5433357ff87c143611e007b7ed55347838820b5364dfd7de952e6fd5bfb344420f8ecf3370f611f1b8ca86c9f1dfc5151f5c3b3de30d55ec60359d2292e926c6a7595f5dbc7300730539f47274c2ffe43306cf8ee6bb2b18693b1dc4095bda26f3360a7d247ec03d03693849c8e3f91a802bad6cc4a9a4620f93c0d3e1a4c744c6cccc1d7a9e955431c43649497d61f3d162d53379f79cee4cbe6bcdc2d61751f09612519cae44ba28c9d9c481806c777a2070465aa43dc7b50ad106e6dadf39cd189d95d647b22a8ca6d931d7cd4da0bf9fe28cc59e705ee3a7e3368e5df8e746a0540ea25c8e769f6dbe1d9b5ece225b7c796066500bf6ebc057520700e429d65a4bf05bd72cead9948242ae7131f21941a627a08c6ee5635749207777ec455eefce4c13a8e956b4509c4a88bb6d7190c73399899730719b5c97974cb960cb578e885a1e3d1e1099dd25a1565d2e3cbce28987d10a8a7d9125c41f0163c50d7bb3bc7eb4a9bec9cc4facbcc08e202dd50f820bd081f446fe007d35b7c60d01b1fa136a46e92cf79aefff226dc939b539589da8912f1a6b4481dfdccad4b23ee88ac7312ca70d9b3f2073aa1a579d8fe7573bc7fa2bef54c696c6fdf7e78d4519e4c70c0c24eb83497faf40dad191c6e70faa3e93ef1d4c0b9eb43ec8716fdaa9ffe9f2dd04c99625a72e36f9ac234b0b681f406702964653482d682f1f31fef22bf1fbfcc0abb32da30bc7887cd6e1080f2d610fdc3c070a05da2ff98797c02832842b37a31486af908247de748b1b685700e2429d718f46ca2b34d19be94d8182562464ef17dbbf66dc9a516a2ef0715879561d631f86bad67db5a330f5d3143db4864349b4e5e439e6a145353bfca7928f2feace6eb5e4f935d923728bcfb8b800028ef0487a83fbe1b680b9a91fbc49d1847f4b0bc6d1d810bd1501affba3eb024258d5b681c5257f7bcf598307101af3bb497916507da43ced9205c51360fdcfa0ec6f9c3d6907a49c3944942ed6b1403321c3f47189122d85e842c1bfea28ac0f786ea654cfca4badd50bc7521af9093e0bb0ce3af8d27af12451912a7e03f9ba0884a04a6f63c436d3e690703b53f8016f8b6c884824a342b9d4bd811eaa40f474e30e1c54b7ce52f91e860f07616aac89882820a69da2f20aa2f5a7e48b6ae869c82ad837cac55af7c9e5a685474730c7618be629379d8a54b4bf5d6354c2dde2d975fc749a699de5bcf32dfe13e06054df534d6608db699e91390cb95e39778d8f185a045c9a6ded78cbb8bebb23c9a1657a9b4865c78a40a224b6ca1b7e087d0ad7113b3ea9dcbf4e5c377949d01abf2c32bba86950ca4d43aa3e271cbfa86608830fed8589fd5f57021ecbd1e54da6438a2454ef04a642a9fd42f4fab6590527517b7dd68956e86bbe299359914199efaf4397ae386da63f6c14e4a36c81f054218576a384b5108b23cbe4cac271234053799dacbd91adcd74df345dce2345c3a9707059cba4dca3537d37ee0ea4cec7a8fe6a79446e06814c687a9c68818f439d39aaeaebcd3e2cfe75774397f32430b66bd3877911495c0b90ec1f01cb2451211e8fac316c1084aca550cac2d1cf43f788f935a21c0a17b13f88290c543e6931996c1e25e5f1cfd818d68f1d718a14012873a7568e4a9c3ace3871dc91a530c3f8b3c08a115bbc421eedf719a144536195e12bdd800777f408e6e9aaf1d3b1b0bb87ffc3c94e084a0819bd9d3abf590030552b44ce0fafb924615dfb78247e8c7fee88ab90e7e2fb0f2f3d0369979b5e7fa727d6eb82cbceccbe6c2cfd363a489149a724e02405120bf626e544ca0fc97abcdba3054a11e20cee642794a24f5032853cbea21072372b64424a011e0df224280a169000737dc72ca147dfb1449e13e8e677e24480e70a695a9813dcdc5b1e3b94335331815de9e233d6bb925f0d7d308a3643d99d31fb370823cab57677ea5ad136b1b45c389b3894ae5f5d2f3b31d2ebb1e1d08906819b867c13fbe7975824bee474b0ed6c4081ae26e36685abb04e98ba00838ccb34b2f802a02014695e898ded7a225c4104411f9f92120a301c48795bcf49412e8bdce70dbf5198df4dc5113a9d52dc7ba1289e4b06191ae36940f6b13dfc92f6f18f669e92e0c3d5136c18859daa4a2557298b148187e29d8705f06809d260a3e5f676e2316ab31df76b55d641610ff61c97b823ed61e45fb25b89ad18d96d9168c1be55674a901cd7174e0498f1a1959721c12bf32c59a6d1b0b7acc96e006f714aaf451669379fb9637e82057cb1e8ba710c43ed117efddc17d4b67ecbec195561f8d195d0b51a654142b29848324853cc777a8fc1a561382e3e232004670bb3cd56762ae8341344618d4d47208f9ae5cc52718b08f889da9a97e06a80f700af8dcf73618428cb7379e7c5b5052b59918b751a4f32224c61a66047a9d609ef5cf7252744b2127b7b0dcd7f4d8d13419201b06df235b024967dc87a922b4aac44fd1ef94ca98805203aced89ba43710323d67c2ffd03dc483c224fe43dbadfcce5aea24ad2bcede482b31e9ab7a13b324162d56635f5aebac9329954e1d1636841199d58b8b0842b667111739611d939f35196b8329c0015252758e78be56e19c3f1d6e7b73d4973a07edee1c8f31d9f1d20c23403b2a69d1afbc501a68c5d04a537f5b6f7f7d8170edf8dfa4ea72f16891514b5d8cbb41345bf16564761d5c89ee29274db5a9a08350f6a2952bd1a9f8631b85a59ce5d056e32625c6c658d19dbc973c83bf165489269f42f7c502bd3c52ebad09cd44b8ab5e1762a868c442724e615c2e6aabeaa3e46c1ab7e6104a9f8312b55e914184f51eface583add07f9da6228d3b514c31ddbaaca1e2c5399ea3000df3342ae2ce01dfebf17217421e038494c4b3f8a52cb91b36c77299fa336043a349a2a5f5d4896a244afe4726e94f2f1cd02e89a8cc400eca22979c08e8fbee7aa0ead455ad470eec33fc85211e56fca0e18f50dbb16823a543c0268c73bdc87d7a13ad4465e7599845368875b45e64e77f9ac29910108941c4df04224bee7e0af26808da05b6f30a0d558ff366302d5b9441918617386790e390d43dd8972b5025c6c003aea8906482493b7eb6987e1c89e66b4cf8d66a6432f225636563d47896aa06320b538703bd7233d2d1529cbe7c7ee2dd4423412b1455299adba9e05be87f440f5e14012d7e44c0a70f243ea516758995fb4641dad229c4b2df55f4ed2569830fb84e55ddc040e7687690240734c5bc05981b9d3ac122aa490e35b08dcf2cb5ca0f9c3affeeca04cbdbedc41302e1f76e456709531f609d1747f497efa36735819d20bb73efffac7e9eff52b0b59e4d3378f92827dfd5044f3097f927bcc27172a13e535873bc7ee971f36e2991b3e5285ca6d09c9b166b6b2383a7107018ab6b541699593207c7925a0c5050de446612391dac6b634082cad3711cadd66844153421af90ca6ec7a352020316d835c08861f77717622817ab926a6137490c2df60c003e46831b23ed3539f988fd966b029475e30ccfdedaa30e92e76fdaf4b1ae25e58bc6623d7b0929aaeac3d7481d9575a46aeda762796905a64186caeb0bd257d996e9fe03c602b168926d04f5b3968ef45f001c25311619ac1114e23fd8b1f382abdc0b49754ab15076aff23f4159bd0fe8d8546f24b812f542c1fab63a0b775563536c8fab6282a5450a59ba64521d033718dbbd107e388c2a55fd0e4711900eb7ffb18f10f7f17ec0bc10d484c1f998edbfa3218494f575857949868393238dd66bb50341c84409381a5c5858a3090a9a5e3b58533aaaedaec495607d3fc465dbb027d826ee187dc2d706a0e058902d099e6bc132daee3c5c5e197d20835cc6942e849f9fd7a20cb3c51f707a9caf9af5c11c19bb3a83385cc47d78e1191d231f8ab3d1d9e783c7a17147e14f2e8cafd98a4cf8f1f9ffe7b1de3e8fd2385fe6e16743bd53565ba155d8b4f40320b9581ece0b38edf24bdebc6ba75c1fcf395c07a846cdd367357e89fe81b277ba6c89bf63ea5ca94e4784b72e43615990f0c0d770aad51c735ee622927c9ac3672eb2a61cb3aceea79f601296b9ee528c6354265d92cf2e03311e97acfc850799408287c1e6ea83720320c2a0e42edff504fcd6828da11b30e5fcaa543794f068cbf16518f78073053f5056c69896d91d70b7f471f180938f2178d0d091153276065fb61fe168fec0a4768f2e52ec14dc5c6281026d6bca4897d4a04f074a38b2710160171b4d2dd41e0245ef7da941f11e1c2d79d6ca006d018b395bfc52b92cb4473910a29ce103749da71e5f1feb322497717a0000ecda2e68e412d0ee340f70aab402bc7fabf24c05ecfed15496fd39a6b658b6f098bce75a34662085a725f364e6fc72b38368ca87693e2ee25a06cab2f13c10633ad73b4610a0edb78bab3efc06f38b29d3540aa3f72bc90efa1e3bf52e0bcd928af40844e9cc78b7df1e7d01f822e608089c4007f1578090ec3b3c1e9b25b89c2b07d48d6c7c7fdb0dc4b3899a32c7456fd13a8082b44dd67413911bf6bc98e8502520b8b90edadeca68de6641375b4cb4535fa9283490fc18de9d4df6e03e9b66b13103cc9ba7d5f3536e4e8814b5d70c2f01c7fb69161128cd4aff928ec7a4ab49e6bab1c7fe7927dc7adda2a08f0d1c37391d73346781c2aefa346499ea2e5d2ede2108fc44a03a8c38166dd0c4a95162db39cd9b834c7d804c4d3ab9eb3635502deab599d5bfb95d72b59c90614a5b0d74eaf9cdfd00982b5aeaeba9ccee837140dac541f5d0f80381e1a6a9730c4f8f7e8b592c539f96e73b7516b998db9b6940b65d5a0f0221eda939af58f93e7d77a24b06c49ff53589f8342ac16f6fc94ff29dfb4a585afee9ea933122e8981223bf3efd4af410088ffca42ee334992d706f527a8d8f9989af067e5a9a2cb1dcf0bea18232260654431dd51726d2483c29add94c217d05bb8f8df072f3290689d49212f3cea2fbc337c2c0a9812ee72cfb1d7acb55f9c35d61e5443e6aff8c447252f4aa7a1c1a61c06ecdca76b8edf64881827726e0601e59b5fb844b99f7ada63a13627730e93ec90310a41ee5c744b0c1870a1844a91155f0f08c4e160f9e8ba0a0decef6d6ced168987d707bfbb50e03073924d44e4238f11b1b9002bcf11b746dce92af119f877e66bce32ede73a19bab999413994040d76bc561c893cec7801149659b8ef301d6256931b573ebcb4b5ce0df690b72f8c242a9586967dfecfd553b34ac214c3dae889f4ff465a916d8849cb108a4dc1bb46d2e4753dc85642762e9d4283287ba83db9657a2e62004f4b0d620a89cb1a8784b54e34aad099ebc1ad7da627aba9597158639a5bfc526e68674a278d7b0587598890192478083dc5015f28582c8c423d6b7df57e2c03e92f99a19231ca14db9243bf11a8bab0a0e52b3bd1d4669e67461f34c00dfd0c48617bd01416c725384c3ab741c7f9c3b6da7fd521d4298563ef8353d0496a901838de4541b4559cbd589b083a13a5fb626ea4c8c042f89535ce839ab143ec88a50e3db04b581c7278f551a8a0c88f2dcc588482abcca00ab84d498b8fe29b401bbbbfa42a4cc226c1723dd88090ae5b22ba8917b5a060f2238fb5182ee2d41fd51783b1ce5e08f16626f5150a104e8d63eb005c41916c3b78ff58b8c4eba0dbf7ccec9aa6d2ccc3187a3a3c3e57243b0440e80824362fcfce297ae40bf0dffc84b19e9a44a14ae41cb024b9744ab18ede9e9e2cac6836fdb89bfc895a99275fac83c1baa87f0efc48166f755a68405144199f467d9e57b559937a996ae5a5f18f8295ee7db7a98a459850aeed35e90a245a0c6575de32d8eb0a2a7de98741e77e219d80119beefd82086712390ec8a2a37e78aa88704aa86a250c2f91d85cfafa9debbb80f82eb54509b6bbbe00e6225374e4a47fb5554c833ca88ba7eec656e9a5a403f7c0843687d500d2e66659f5ac6e26a922e047d0d0c9e25d47b4e48879a0caef173b279536eeea391eaacc1c095d2bce4f5e1a700839dd29cff2c1a158d13d28883594972f14f4bc943dbf18c1486a472445615dbf93f665fd7422e5b5382f6b9d148d0608df5f9209313c53139c979a50c6ad89841cf08101b2c6a84b18fab12f08fbfc2ca794c2c51817d67f36a01b56cb36ad8911d2fe033cc1359926ab888be8b04e6456ecd63805422364c752783210f6ea0dbfbf1607c35e9d40945be8d54d4d7fc90e7571e20cc7f8bda3b6547dd170a3e917606cea48db513251ec0129194d1342637a7ba2387ed867bfe195e002436a2f8f93fd2e79ed9eec513d79d2a185daa39101982a301e09c7c1c225ac2a62d9a3fb42db993f9a3f774e321c6843008dd08f367d3ee56f0543be85e04438800b0a64513803b6ab4a312663f544386e738e6f443e3fc04d0a4f81269f1ce79121e9f9466cad5c5eb475f26834f72cf718638528c3e086107f32ae58b8e29b3c9b8583796e9c240c0faf9435b770d89b007d2397b2746a1d9851f1b9c9b59efeb06afb1620d64dd67687b5745721b673c94adad2a4404c602bddbb2d23658d969318f449527b0f128cac9931845bfc300420011f792e93535fd3ea2126caf2c6b864cbb6bab4d30214e246464078127706dea43bd1db23d6563ced164200c029ecdd9e3330ef7d76092a4c2806c348170dcfdc08625bcb20cdcc4ad32ad2d289bf6af7d119037f605b5afbf130a6092e072dce1beb864023a9f79c5a3a5f98dab3a65515609b404d5ced7bc1375aa94ca605f68846ac4ac88bb194d6f6da7aef92c63733af3c43c30810409578e3d8e22d31d0b06283d6924c9bb4901042a939610c44bd3feb72862b816866ad20a54de52c01758813ca8b6ac36be0c97708225c799a837cb3cccc967080bfaed5163cf3d8382d0a7c5f482bfbcdf0a4ea2513670bc39a5e00fdf19370dc6542f5c3226d04b4d2c0c8a65bae7c481bc49ac926aed78aac0af2cb110aece11b8f2b6fb6cad716f50441029f1ffddefdce253c4708d22604a82b2f5016a63271cd02e506c7ee1c80cb4ced4e47f6383fa6eee82aa0472af9552e176672d5592362440923fda58a8509a950cad878e023c075f585dc3552eaa4af92367ec4659bf5cfd8e6afd04c17df69629aaf19d1855479b5f1d7c722602babecbaa9186e8895c4115b8238bfab23413046e63be7970c971d902fb5ca2ff4dec4a236343bea3abf1845c1b469d73a1582d164635cfcf2a04ec1e215e89b940fca1cbafddc50fca43805600a8e086e85195800ed609459d79690f4a0514c53c0f32c1c1b08222a1950c04972abeed430e0f17eeff06544212756bf8d66c5dac489283fc60846329da5412f28a0a83d30ce860d16f201887c0af84de974d009e0018964aff08d4a9fb14785d023bc804899568df516bfa449263b931b92fb3cd3a4f2708fdd851783d1362ca45ff38a0cded50a959292da82accb2164b0ba6e0d6ae606d2109b53f666359995afa62923bb4c3254b0bef114505603218e9a56528fef28461b3483249d1e9be49f2445b7a3b878eb4cfb1036ffc01f57ad9908c34da333896b780d4f748046462c192b269501842175b0e67d9d29481ba563f82c6b56f3421c637bce9bb65f7017c4cbba2ee7d9616657655095c1319e295d1e00c640eb26f2f431e1ae4587a4343a79b870d4528c8e0f69595a624599e938458ba643a20681980ab6970b017ac0d7efb1b5e83f1b1d1b063926abd58deca36d3565288acc2d4088b8e13896da1e46350c39c5e761abfc4703271e668c1059894bdff08f0614aa4772d9f72e40ca6e04fc34193503ef1c082e75c79095e9839c18185ff4a1551f946cd70456766db1d0a4f39e59c52f62cdbce32ed8a8d3dcc41fbdf155471ab636e474c6eed245703acd50824f44e0d6295915c1412371d0dd2536287265c93a71b58b937d47b6a8671bc106c95d7aab570c244ccba305e40c4d55127dbd9c5a9ba9447fb46c313266191a701c076b04f76ec98206554d22c4669aa6c85c0c762d20bbe3f6a9f1ca611594438f7895240610c58f75aad70cd75470f9a007a30d8b5a66b6cd5bcf836809b187d1ae86248f569bc749900106cc2baf43949fc306c496b9a6a34ec5baddea773dcb7be6e2f04e0e8839f302abd884c807aa696edf973eeaf905e67d6d1ec80e4026696ed140ae4713a22c64b59ea37cbbfe1b35c3ca0e35f07a2990236b360f30ad7e84b23b55ba60505cfdf008b59d44305570b0a6c4b61ed2fb085c6be5482d0a77ff94280fec16f228d04adeeffec810cebc7be2a3943f40c67fd14b0b35c4a3764e1b639249720dd7827cdb22a04e235c9299802b7cfea5b7e9b07a554046400639ac884ba9f7ca5c0315daeae4ad29a4a780f5bd9929780f771215b229624002975ab68241f120a3611a03619244c0cf5d911a6e70449cb23930c93c99c03bb04c229418c1d2547720e3b3f5f11a0e84f0f51fba5af4768708271f0d432dcd8c1f808bb517fca85b7eb18b4ed187e5fe7dfb29277cdf1e9b9278c766e79590745e3c9648a4a94b9f5111b59ff4aad2266bd4dcbb7515195fab0ba485c4b62bd46873652e4fe998bc4fe4ac1a50d4b0eed44010b2540927b78d2ef5310655f3234c947811482fd9c0c6cf8d57d82494189b66697dc8798404ea234f787ffd54f04af4e555e138802ff0328b4c181612b4d80208dbfe6fd60659c42b0cf8af21dfba0e02b4ae205ceb8d55ba19c43d801e50abce08d4456940b0b515ad01552167dd7149183d6d7fcc918d5c22d5a993660a5d87f30da0149d4d0b147d6e525d843a242acd3368170c7568ac5c4dc2c83b5623bc4197daa2e0a13c07552705d4dc3717ea70adad8004b6c51d8766d22d61599fb693ace46d0d7edaf0a79f426834864b8fc06c7faf4e63cff3dfc316a740bde3001f18212b212881d40245ce78e0ec4fbc0ff945338c6d62800ae1449d7404da370037535090fb2dc3507c93e817a9f9b5dc5ba646915052059b0130c385531acd4a53d6ef234cad7165e4983c14920a6b1dd439844c3e49a52035ee80c3f81caf89952fc057db6bc1bff7c84b99672ba5292ae0930bb2ffacb8c8c6eaacceb0de43af7b79575dc5e36931cf83f68165942fb9de822b40f64fb78069f6d9c4665b39d6c5a356dea6c7f23c5f638119327621b2f5921f5a38475344a68af2ac4c0f4c06f1dac3127766da05c31da5ccbcf18b6cf3ecd417178c808cce370407c25219795f8fd540c9cc89977073c9fc4d8638d7f1b18a056b42ba00da1a4ec79674148924d98e4d9dfd73e5260f663507af88dbd72c9b713ed17a919c655c4fca9b71693899e4d54dd0d7fda5dee217f563d1dcec94593801d3a5284a4968d5cda17684dd1ef507c10caf1ca437aceb7d0af69e89afc4bab4a19d5a3cb79d2061cea20e49495ff6ff4f5b6da38298de4dbcf9188b581a927935ae7da7395a63d717c8ac5eb18f1f5915e6dcfb781c4092d23fa99817d889bedeb5190597d71c448f9b70346d64334d17cae44d9fd1cd6abd6a1d1d331ac63e837550d40ae851ee34b314dca48f9de4d3d458cd0c7b422494006ca41798f11c6fefca4879067a76c39bcf38af2791c92ea1e810d041f517ea35690b4c32cccfa2ab67cad629c718014d15d0e79764f1e06fbbee7388d92db5c918955692f2e4e6ce4802bbc02cbc6016b35dabd0b3d7c5e6c50973edb775a83f9f51cc05e6674d572f0bdab7dc19438ace4fcda2c8787ed7cc0094b21c5127c6ed177f6364cdd5e92076b7d9b7f9a4859ac965fd2e454bdb77ab009e205a2b8cf0ec12c3cc6800e2745c3cd5d021e34bfb6cced466231f4446b3b0843de787a01c1c04d7b7380246d95a9d82bd2cae6d967b9edd23a9f8e6a2a61f96d5e277650b2057ad66aa25138cc5e91266e3be80733d781805527e225d91b044f2b3a1cecfabb3ae8e095f8b1eafb2ad148b8c6d410701f6c4dffecb8f431a49e70f337d1dd584b9a6095069ea7d87e14402c5483ccb79c815860349a6fc9eeb639cfd52fe2fcd5808d066a66c6300322d74e199fa8c014c78b5424abc5949dcb81c7e5cae3d6dd813a45c4582bd5bfb8450b03ae76ee5ea2ff5a1a3db944a2d5c638414ed72eec1656b46e2167f77dc9761e4243021bc45f7990b953ad35eda2538395ed7870f350cef1ba503aba18f172dde2d46de2343e22de42d3ea1c2ce175a73bd67f36c108ad619e7fb06bd27262e320e95d21774dc1b8cb65355f3db34c36bbd2f596b1e4334be8265e6d27e6667a8e163336ffaacfec3473d3bbedfd5b8b964210594fd36304cd97cb09daf43e20eaeebe79f08829e0700e685a3f98078c98e302a0f5fdb7d8d3a1ab30521ad8f42dfd27c7f9974f55f2978efe8f1124b194eda34f5fc79a825b8868e3796bf411bfbea58b0afdb042941457f3f321dc53128a36b89dbb2dd85bb9f96faa21961a3448033f29b2ee181443999cc30fea01d5e24ccfd0c5463eb8f51e9a1a8983a30093ba61393e1e8aa5287c92009f1a7058b09acdddee731e7a6c4861afc55c7091f2aa5c8ef698fa39eb79e0af6ba2eabbc5df753a0b36cbdfe42d955a3143cd93271cb8f012652c43e54775a31cc3e3c9b60fe8e495c395937bdfb8422a340a3071b8245a12c4f2480edc87ea8fc93101fed600f825a4314fa0482adf0eacdb07a029c1f2f07de4c4fd3a5c0107a6631078867a3a812c272cd2b97addd7c3b4c0c7c19e13396721783c4f9272ea714669dee5c51d246dee723fecfff678deb7c37756c4538a2b1945250e67c6c117e75d0a619163be94891e7f13ea29b7fad1edb8988bb19ca2af144ac13acdabff82d07dd1a6d72f527d0c1c988f3ed1bf0fa1cdde0bdec42b838035e659a8b2c53e1231ceb843235bfb1d7eb52dac6a6af0dd4feda255bdfbe90a80d5b24ab259f358e4f1fdd42ce99778784529ce1088e9ddb178ac67345e4c80bcce2120715a1ab4e17c0790abaa4b395439b5153ec5ad3fa4018bca598f242c5200df5c3d4ca72f72f4236e70b797355843533b291a40c63ad423ed5c16838c586142cefeaf2f4073fd06fd1198db10d4e327a9454a04fdb0afbd25dbde2d15179920e06664e6986a9551e6d0e750231c91f81065aa5cc56f313373c110c7f022e05a39b2b021b6f59d28733088dc715c31a410483dc68f022e9286016b05ca4a306182b829f5fa3140aaadf5160359b14eae2daaf5d9e0e55d52a97313d46c0ab641de352f092cbacdd880d85e0ee3be096076fa84c23cf3280fe1d4406da0bf9f233e1ea908944b8bd1964c5f8f0f765ac4c0fe9a9e0dbbbe42a6f8fb8b4b9e423fbf56393256669012a4dc005c89325a5ee52f14f98402e22b5bb2ff04692007853f3f3a376c7cf9c23c987b5e443c2d488a05c8295d0f0f84c6ca7360d3f22f3632f0c30377167e8d75b10c515fc21ad07824e0ef2b53971f73edaf1653c5ccca861cc44cae8354b3e92b4d99bbf7e590ab7a982dbfe883b872b2bf4c72841209f0ca3895360cdc5e7e484738d7e4b6886e227c1e2e2f1526d583117d7c654a75688b0c0f1310b6cf6f2c7dd49dc3a84d0010f2b83839852265f1fff3c307404f18b5926493b6de12288b1e703cc5f7f5ea06ef1dd2011202c80ffcea302c6758de23336d03c5d25f3df577790a4de49efd09472ba626a60573fd3320afc9893dbb35cbeb08014003cc61bdff6bb1d5c46590164c2c7a0b82f1707b8f42789a6e9b96640fff53f71c62840ddc5eb34aa4d3f3ffd8b9cc782a993dc2fff8b636b75ee60087bfec016c72cba50c76d30f9da80d8638f029d4db221e00d40a908fb7ca99f32c671387cd3448972d84a69459dd1a17f1b40e5512b04e46cc28b5c8156ad54c13720b0faeb614a97e08bb4a78039ea027bfccff6db0637cdd6e0ec832423ca170ad3deb57ffe3343c34f069a5349fd8eb8de975a56587044472f49b370550fff00b16e9fe39ce7e0afb4b8c0e43e234a4db043c88d04284e5228cca274ff92236ffe0f68ee67be398a65dc407da34fd518f807840732e79b881a06fda39874e17964b0f4742c2714e3e70d92055827725bbc7942d9dc4828d3227c7735267230ed24fbad8e9883907a1e336963125a2366390709741dc89e9448fdd2145be4e6d34a4d1d91470dac20b4eb76b970ca456f8572fc4633fc2598247b792687b1c7b331a3b4461f0012e4e9c9ee9b04dc587dc8371141a655998648ff89e91ea9264bba62c8e825489d9c7b6913443b8b2141bea76f451f489d05cd8241023b948b112a703a173fd65c849ff96ad41837bd93a18a59dd35fee4e6f87a9e5aaf775041f5da2d8112a138c0cb8594454a45ca083886b4e310d0137d663668d80b30a9ecaadb345d561049d459bddf0ae98bcd165f3d1ec3fda3e856e095d7d0800feb329b39f95c9fe291876825792a3203f4cdc3addca09a724f687018a24e104c3dce0bf414d27c0bb2d14bfc1f5860f3bb1d1b84869e0c8949b867e390207d96b9506fdbd486deefac20a0b1fc459983bc0e0b98aee13da113a36ad2fdc654a2f9fd8128409c00ca213bd405f60f97c85f4332738288791db25ca328b4e7b3adb7c0fef638f5cb4b1b31e94e47b9ab1e2e9a13753e1ac41f849edc5777d3e79e911225d79a952d93ac11bc52b67451cd6b87cd75c58e5ec3d2c1790d1637276914bda8b547057b6f09f9056cff04dfd118043bae7b50b13af90a8cf86475d6391ca5b22b0a23d769a35ade080c36174db8df670d0b7555619623dba5f03abe959f440ef0fe46f5c3b243c71d9f7e8a50aea522d08a3a89548150983254d893e3c26c8258f62008eac8e3187c0db5942ae141e62412aec3771cd63e1fa224b42a59847c6e7484ed31b26a1fd8381cc0ac1b86e6873ad8ddff7a50b08836baee156592c2e418a7c6b3294d246683b645f046163430e170d4d989fae7861782e9e85a4a8d677f8bbe23ffc486cbe8efe00a4832bda64d452a3000311df6aaa591268dd750376ae9ae734810613de6931414f2c3d202c82bec35f761dbf0588b62177647db07a0e9f9643862cc51e6403f02914657a27f457d717f19afa00bf6084b6b34731f5b9964ea7cea07d5fc5a85c12a00210272f19b38803a94e9a412f7de54621888be887f213fc7d9c336a07c5cb0bdc4dd73508109047147332fdf6ac19d17d0afb263dea52bf6a651a5a55ca912ffb69c71e36c05dc2484dc2aac4d0f848e18ee5718edeb123600c02b8df0614883dd215e67070e83b980571522882f83a1e9ce8e828ab2bfe34f42558e6a9817745974927ae5333d244821f67d9df9c95495ea16745e861e5f70b72027b74e25f0d125871b44f3463eb9a068bcef7cc2e5516bb9f3328e897121b563e302d606248b579f57b2deaa605316584c1ee99e24c5a257a590124cbde59c00253c501317b40c2afce813c59075b831ad1e47fd01ce1b9ea012264450f8d083cb43541c6b772bb254f0ea63ece3168b9a27ae823339c97bf53bcc40755ef1ad035c9046fc2f43d0730498fc4f82c511b831d1f6f671ffbd0f60aed8d1eda8b0a6c318dd5006684b14fe4a9b0d4289fb208146c66bcfa93837cbdc17268465e11b3bc4ec000eba5b06edffda7bb5b530b8f076b6de88621c368be445efc6934f7da18263e4945fff49415072fdc2432368dc42cd6fde6a9dbc6999eb59bea5f2bc2c1a311322b57dbbc7d66815760a6245b0a8801501fc46d4c86204072ea71310dc2170ece85a1a4e93fdb5f3279ba9ef0d8b214e89733782b3073cd170ba670e47d832f8d44d8cc452cbaaeda04e4bc0c8a5243f98713b02cac0047c88d02d7be6a9e661c47367b2d984668ed2631e89d848a37ff8f8b7466fca64fc614be9c9da3cbc16fce7138d531ba6d1cbc5f82cc7fb4988c4693f6fad48d115a621fa5a1386c3a158c28c25a8ef9dc7afe78faa7a3563650455c591a0d943da8b1854a3284fb24f48f8520da6e6ce0a50ee7b2deced0701ec88adfcbced1801c4f10b605819f80522e20b15084141fbfca155e9687368b7bac253f77f83bbea5c3a0a03ac22aca32e7dfaa5bc33b3ddd928ab92af639675012a1f14c7ac8e4370cde2f184e1ede5465f7e7e567f3ba9313ea13e3b371c0ab55900b58463fa2b623f2c2080b8bd6f0f040552f2a3177953cf405d8d64f1cd6f260cd10bbb1601cf91239eef86da322d064eaba184c4ff115858062197f40b1b91bddc8db374aadd3248036e6107e12d416d5b45e4e0a87f5e7366446c84d16451d93f985e9dc5dbc64394b513bf93b560abd74eb79bac566cdfd7fb0fefe8da81f9fcb266991ede82bcc349e8bf7302175f6abc5a2416043252e160e04c232deba30f24f5b9cec79f61801c199db899468416c198e9adcb709034392d70076c5eaf585cfc1f102ca014081a18ae0a5e32bc6acca708a6c160c57acc3aa20169e2bf91ea91c10aa8a2a58b2d498c69fb5ee647cba92832fa4473bdb495e17f6bf2cdc42a82b2e2bc0f7aa8884b16f147021f23b06554a56a21f9b0d9f235bffb1a1a9edb2e3b012cc3777409541acf5b275561741ece824ac7d026f0c39ff21e284ecaadafa04c34bc72ec88c8082cc287295d9c217ef957de0e1bd90401db8dc3b7361777cd144deb06d680236367a2b055381d645d6330af6005ce078240fe72b7ca0c303286c50f68ac2a419106b6d863a9b97f5cd88fc70aad3b8cb8cad39af5f736e88bd98415ebd8574e562f1c614eacf50215e1c82a930f3e2e82df5a21da4bd7120bd320b8f2eb6cc6d542e995e34d900988f5a94954c33bbca43fb6ade09cc973c836d9230ee66a2c0b4831ad630f49691ee0a1cb1465ec1ae046407f225fa016553e46bfdc4ccf459105440ee49e7da5db84014e2ca337069a8998de3b3cc25aba6db94dac1530fd577f6846e09e4e1f5c9914d6b0c78bf7ff28e9881437cc0e00ac98277405a7e8066963a29156f6a41d2d6a9630c903da922c9d720ff8fba2adf52e117f595cc200ae9ef0e7b51966af7e78c5ca656c549b6d617b8ed4aa9127f7ae7d80630f5536ac75ad2b56300dc9e1bf14a71d6b7850c312c702bfae60d0a6d50f76e07a9e3f6347506f636b13d271468b01ac1605de2217f5f0beb2f2e85c26ab56e6a91a108275b0bf844c958b0af0222ae7ecac2b96ea4f56f9ae0aee3b7845a7dd181ee81575fc1f440eebe579d10078131b2379103566478b904b5f28680a5fc9f3684f84bd7eb99967a9f2278595f330bbc41d10f7088c540d0d19a93b65aabbee34b26f2d6002aa1dd673837adbca65866675b2a125a352fb68384298c38e825f986f052db5bfbd1216aa11855509527d20411442b376f6424aa518d081a9371b6cd032718b22a6f0cc6e9667db39525e5a671575a706e6e5e644bf8b3b158ce06bdd9593f11a0f1d641109c25c9c0ab03b9349633aa57318914c4702a53223eef1923db68cb1c5020d905c01d0dcd38433006ad5255116a3dc5aa7e084d8ab10d1f270d1c786724d6c8760784d109314249181fdeef836d59c79ae748aeb2fadf41ae298d92d0ddba53f1f1f1ff30ea125361a23578ad4b937caa11a384fafa1c4978c19b71ff2f3b609810971bc6be60c0fb31acc3ef21cde2b85fd21aff4958e738b981883b7f30dc7ce85aff73e4ba80d4759108954456b8601d3c93c4010b24ece63159ea22bb69f02b60db70af792a2fa220b1035cc745c6018a5e967edc1bcf2e9a2ead4ce3077810b6f5c2847b43b85511e3ef8dc4dcd31a6158fa8ab3ec035d8bd4f134eeab0bbe1fd52c5c141e06577af66599783ab038af045885f2aa6da28f48408f667f8ea5b59587b5c475a1b1e55262b217157993e50549d4927766119e674a733cbe8caba446bda7cef55a0364a41e1aaa69f965c139b829dc7427e62d36873d7e19081ee1ca699422c92bdf8288e340b8ca33a9a35c58a92263d3b5070c62ba0b6313ba837040f4639a22d34982149ed38fa1204117cc7af4c959e0650dbb161f97ec704463b16a1589a08c096da40464431affdb0d6c0ab84e554db7aea6cbc316ac951f79a8c9459daadaff17f543c9c4859d36334b7518c52e9e06dee6723a41f96840d2386d329eeb316709c1d95caddf882b8cfebf27ee199854b124e1adfcd81b3c1e5f4ccbfe23430cbf72b49bb05c7879ed1aeb2acb1bc7325b900938318c7104d8dd2057eb604426907a637ed7e19c6d7336d83d112b83f3aa5fc6ff0984ad06cc6b576f6ec62c38f37f7b5080ece2c62d3025205eec7ef77c2f5c618669f1e5bf20d792365e6c44d3983f83128179a89c7cce1f7f9add93c736c36eefabbd2f501d1d423199663227edc8b90efa5256ad8c8206dae862489644544aae2b5342f9a2f4b325477667b2fef471db6c1144b15fb04e1f41a87a43f14ba69515b4cab35d5ba326fe8ee6917f867b9bd4e50a8f2230b06c8bba75dfac5785815c846c533e0983e997aac1dc83ff73577f4740df9ca1fcc3bed13b611da3ed055b25b12780f32cbe708a56a79952bc6e3d23c99312246b90d38d3e3c6c2e3c55ff2f04ea1303ae45b3617988af9ecfa86c6808f1b6bdba20c4cfda71e6430c58fb6c0e9c82cc0d0a9a2e63bf8e93eece12a943eb702e748081e9b4d2e37fb920ccbfa6dde536d9f6dee7f7abf2c6c13b0effcd1dd4990a52cc3cc6687ca703675dd1cc074b308e7fa60d57076e56383d5e5f02e1bfe29592fa6ae0e8c90ffbd94e8757f89cbdb196d1502cc598368cba4989beb574a8ac3022354c30a9fcd6a560c9e53c4975fd7757d74334459df724a84d6706975a56094de3e9d39090389951b903fc16fe19e6f8c80716f7b9137f6911e40b5540f57c27a47c68f40475000231e8c613fbde599d0a08d9276c2f58fcb1df2f111428af03f2e9d7d1c942c4fa58c67eae69bd5e51400b097d544b6be51e8c28c66048315e3d5cdf79aebebdca54118d5f32f5e024144027a63c6f162cf51527d130c5a068c8a213318b7f40776de479df22dba62d764b5b57b88c7b864a62d088466307de16f7e2d36751ebc1e2c4d046d2d1b6fb46f14c592589eb6fc5ed105b73692674121b979988efaa673f93b32dfb4641ad085da16e1e3a2cd9a9abf9678d3c6db4ac74c51132a0bb1d858bd9f247c016bf870a2dcb824231408875fda4ef8fa2c52114c7ee586e213834540e622014d6acb682ffcc2cccbfd13571f1266051578669dbd9622319359d66d27f14042feec254952d3549d8b027bad35780315b0657360aa18ef18c10917a0a9937d276cee2e8a4495b1c2d7c6c3ac8a06a69f31324b026b0295fdb3474d5b3c1b606eb62d851a1e2d84fc3576f99a300016aa356b11baba1e0dfec87eb544743ba15c92ed5c3565706ca8b521e5ba8631070dc1d1d63b5307fd2e871acc1268f0da20719202ff0f99f7f0f53aff74b773676415e9b1455e749d291e126bd032798675fd4725a2cb877d0e83d9840c74cc6e93d4c0011ca01a4100e396ad83ad8ba3bf80d3e3a93b4048edd829678d3bf9060af91c49c06c5c92d5d4827b310cc21d75817f02cd8bf4515155f90812d30ee24f69743314677574515b5c0463fd3a49aadec3d61be3067e41755a76cfa96e44d4dce5990ec80cb998ace89e5c8c728af14cbbcad72ef6559e0d65100e879db9934b71c4856f53a49b490509c2984f8612793afe9f285d17338e80a47ce8392239a1c451025f70ff2390538b89a5c1fade36f54e28cb17391294919f780f945fd047b639787c588ff4e9913e77348fd09e8ffcdad96fc16f52f72967b50d2de81ef3037455bec94e6e6f36809804b7306776c639daea9a85fe120c60eb6019526d3493490f65d5c9f4d7260b6dff838b311e1240f849c44e40a8a082a86843461a033df32f2c5f10c13c642b22563f15e83696f71c875294cf5706c5fb10fc7feb9ac57a4bb033ae537d35b204c4a9b0e6569c85a1e4ddc8d48371519ad438a7623eaa621a57934518f2d867b65d6b2582daee8f78be9db3a1076d945cef90d7edc816074cd461f4a71171da3536e38517aa1a7c1bd18433e0d86f82da4eddd6defbdb79452ca5d0c400d950d3e6f5e740975cdbaf8fce0bafc74d1527e6a3d2009cdaad4805cec0431010a42e2484bebc5dc1bc82648c8c802f16f563d929511f2372dbabab4173f59e082b69c28d2dfb188b60555c1e566f563f5fb665d50fe7513f250516bee2322fa4d73376bb0ad185a31b482546188288bad5f42d1b7055b39ad9e18824b4b1f134254c2ef9ba5e2eb0df3d7c777909f766f3368eb9b762b2202cdca2ab38ab0ef25dc8bbffda6159d3d7c0ef45db384be2dc821118538dc02c2f466e122310516687ed3bcb7857a1f74a48505373f852a0e4cb418af5bb8f3ead14bcae8a814abffdc9b8a37472e4a513497545cc446bb40eaa97d282da791bedc219544bafa8f0a8b045ee1ca9aab9b46b33547ab2e54735724faebb5e63edf483aebdece68ebfb66fd75f24559ab8cfa0287c3e170385c08e6d09c035f0c94ecdae541b7d977c6212d0c43f4b34f038be70ebd04bdfcfc9d57443b507e46f78d0f511bdfb9bffd4380fb7dcf0cf913c1d26c212cf594481e99ad79953a532289efe7932c711903e641871ec2768ef7e03fb7ffa15faf52889a427a82c8311ebd3faf8e1f077f9fefc68f553cce589724974cebe1943c3ac4d9cf67b73b1e64d92b566a679633b618b63dc0284f3993415ba7953c3f7aca5717d1a77c89117f4acf522b83ac64a0d287082a673c08959a4a9e59115f10bd3ccd267b96a083e7be9f7d67ff9cc4e78be0f7dfb9af8a526646b495fa871565d00e43258d4b262ac310f2ad34a31495e411162579a445191b221a95f52af9ae9edd82f0b687ceeef80fbed6a3b21c23ba28431c2c5219b4c3952f26309e9030cca68a3fbbfd218005ff7bfc96f715c328b3123746592211934ad04ba6a0fcb47485f1028743ab63c4dde6aa458b8040de6bc3e826eef4bda86851c8b68635ac210882a07f3db0f2c4ad5fdcb4174317efed3b73e842cf81d0bfed1fa29846d31aac9ed1a334dabf3ec6f904b77b6f89b1115a6febad22997b6f0c245555adb5d65a6badb5561c36e24ad1d55e115b9ceb91bd169b35f2077ee1a7f1ecda9b74034caa3c2d74a9e88dfbefdaafe62f15fd7a908ab808a3903f10346b842888e2f88de398c7118fe3388e771ced388e63ddb99eb3d974d10f346b5cf3828bf3675e604bb3863541f333cd6c9a26bed634cdaac5518faf6bab6e6a7ad9d7eb553fd8ce318eaf175996668d7bab47d54d1396c1f0af63fd9a69dacb34807db234611606abb9b437566dbaea982607f60d017009dea233186d26c6625acbec27e1e0dc9a8c6adcb7d5235a53adc6fd593daaae5303415f746b2d3565b058f176496c9408ae180d92967034eecd4ffaa29a878746c3341a8d766d85c1768effd120968afefa6d7f8988e218414ae36a194e998473c63b7767c7eed411b64d2093e1e0e4e4cc664d3bb527043f112747cfececeaecd096684b3c97c77eb3593dd2d1d9d9e1e1b9d1683d3d311f1f5c9251ad4040f8da71c7a67b2abe9687a67b74896bad866bb55bb322cd6c227bcc1a3eb4ffb13f15f7f8d89f5bc340b9abc6fd1bf5a866f0d57b86e53d77c5caa01e55c7251989e8a8e26523cb7bae3022cb219308768b156916b2f29ebbac718d8c4a23f2351a1989a191ad3cb0d247cf4a5befcf4f2da82654ddab9baf4ef6ce1345672a542ccd48aad5665b3134444434a335dd6645452c74ac458b16b0162d5ab468f1657c5bd8da53339b6640b4594dac41b66c02f2201bbed1846c4dee33dab56fab3f43f1b5280ac27e0800fefdedc15f5488f4d3ac71ffdcfaaa5061eb07733f4fb309b5e605e58a3a7487ecd0a7c2244036c25d5bb1b862e887be2811d1ed5654c4a285db6824d228864848e08794f145b24848485506db3986cc26a2dbad286909572b8b92058b580bb3a9fc8c32bed6a88ab08de38988cc1a96bc990618c1772002dcad88450b23b3c6fd5a8f8ef0b5b51e552f329bc4ebf8a2b5bf7bf6778bb6d66a3d8b9f495cadb579cfc0d246ddba3316b4598dd455377f6b43f445599806b06f74a76817481e64fd30d1d769607bdab03a5ba4ea464797cce72e3fdf33a3d8baa88da02fba4b24dc8b180cb3a9c6b03144d8cef147f90343717c91a5098b69d9114ece4c678787d6e3f353030a12f21355615b3144742b62d1c2081f5d5b9182fea21b036f510838216bade5d72a02b25eb2d65a6beda006fe6c91acd7f879eb625d72496bd6c05fe33e07f64350ddf2e8d23d3615b0f315bd17a05ffebefce12f5f20d82ec1570ffa8a6e0c7c05edad6086596b6f40777e8ccb2aabf7265a74e7382f0dda7a07f9b768e27b4b6c7a366bd8372ff89c03fbe641149b4d1fca817d83f3f6e0e782dff96d8da4afb37b13af12ce68ae471fd8af393bd5bd4935b951301e3111b8d136b6641b94c1d1204bc4e0deec6bc6bd595ec525e5d86eb3269d7bab269947c7e78869dc11631c866088437c86a82649fbb9ce5967b9c4d556b0fcc8b004cdcf926399bd963d332c3d2f1d7fc57268268c06334d8f5d530d6e8b5b6f9af6edbe6f1bb797682ce1a01b67f418c750186aa2dfb967dc974ece399b5245659ef50a559e505010134035dfb8194035b4de22dd0d70aa8914ecace68c2ddc52122ee9b7d5cd48b28b8a9d1516b4d45bc344cd05f57a3d23c8a0a939a0ded501add15253a6f6e3fb9644d67ed07a8b2aaff2ec54af17c512d490277dd658121b9cd1cde877e525f9b03caa06c201d56a49fc18fdd45ba4857323029cb3ca8cb8fc86baed77e51971d9d8711a971c4bd1dc719db3feecac9ff3af276b8db465362f7839462f4fdb12aa9209355743894aa1d26bcd95636c24fdf3986fd14400e9a2d3b8d78afd604755c2f51642cdfda061b53acb542bd2ce327d20c7c37842af3527f3d07f646809f5196791b26b748bff64c2d09e2570389e1de7c3e39b66b348d849ecc383d65b647b18e7dcb5d7413e77199e3bf6dbeaac91cffe9df5a6b55366038c6edbe3711cc7711cc7711cc7711cc7711cc7711cc7711cc7711cc7711cc7711cb168e3e91609635ca3618c6bbfadce26264b831f1feffdfcf47a3e3ebd5e8f8bdeb6b7ad4e4bcd56199e1a0d9ce3a9b7c842ddf8aeba6050b3d2b470f4ad67b519361f1eda8f4ea3b550f125e70a638c4b1f2743d78e4d04947ebb91a10ce63c8ebd5a267be537b7dd7078ceb07e20c7c3ec558865e8be619045caf64ac7501ae65ddd5bc6bab7fbc4bb4f50ecd57dba4f3d3eeba9b7483c79f6ca066d2909c7d333e3e1e9e909122228485ba49186e65b346ef1a9b43133bad974199ccbb7a8ac5d6e64afd1b0d49beba191e4575a74973f3aad6ca1a2462dcabec31f259863cfa9b91ec75e2f2ecbb284f59c5697b33c5c9443d27765122b52e915e631c7a6024ab7569bc7ea62386e9964ae694c342c920df4deb255118d2d967bcb8e516ba54f5c6fd86967588bb065aaf8cace0fe44000d66e9178384723866627419a571a6aafb2e72b9cb33d5c943d6334db5e6d076d755769e39cf22e9fae5216af52f8f5b3e346cf5e9ef8142dd2e83430e8e1299e1de4dfb8f2756e1be563073fd06cfa5ce7b43f3bede79cf6abc539edcb4efbfab41fb36fda27edfb540bf47df4733c2e3a46cff1ac16892c1d9f1bc7934e0b63be5b6c15347e94b4efd88f5e6b8ec777ce8fa66fd88f5e5626dbb348d9471f3d8fe8b661f4a1eb30bbcf4e8719a5dd9b0ee7689edd2d137ef3e685bec763354773acb3e3aa84e3d8c39af3713c3a769f33ac45a3e39cb8de46979db51ef59ca387423f3a6d44f78def47c72602467474b4bcb78cbe3e6bbd6df2b1f39cb516edd863a791ddf62c935891b067a2c71e137aecb0d76374db3035ec34cf7db397e716c1dfc01f3a79ee325b2411c561fe7c7739d7fa3882acb67d9f1ef41ad9e2ac35fad14551747b6ea3174791c6c5dd72a45be748ab93d7e2961e74129be6a9c12d02fd737b7ebef556f7d662b3e9739cf4721ad79ed8bf93036c36c57cebaf9bf675fbd77dfb5ac5ebb1f3e7f398cf5c9f3f5976fe64f4670132375d03999b9edd4471c090510db4eba0384cd78e63e632cf91c2ce9f05c47ce63f1faa41cc673f1ac0dc74989b8e73fe7caee339d29cf3e7437f1690e338ae418ee3f8e738280e183e54031cd74171fc7cae7111e839521cc7f1f3a11d68a0e33bce809af2107111e833df4171e0d8f11d5407cd91e6788ed4ace9ec9673d614e7aca9ecaca93e6b1a3b73a4b033475a9e9bf6a0db8bde001d708623e8514435b8b70c666d34f69b331de499e8006e1148e3de40bf4ff4e719e4815e6d20ca81fb145ef108f1c918a9b3f7fa6ffd585b5d35650fd83155af32b82e8a9ad694b484f6f377edda2da0166107a32bc229cdf9bb5e6133837a84773cdd5bfd8dabbab75aedc5b0ab9b45a1abf7aca0abdf1834f62aa6f8695c5e0884d669eb4d28f5d04081ed495276eaa744516b1acd337abbb70e7a3d124dd7dfc09fef0d076c8dd52ef500e7a3862a1e47a8c0c3619db0814956ea81a52188375569b53136f8d6ad1367c04ca5624ef0822e253f304c9a22bc948c9d3ab4d15b8cb5d16f64527052d8eaa51e845ae59945ef4c3eb1a384def982fb6280fce0a578aac8d653ad328e2c09612bbde77dd29638ea1345cce8520f88f45b5bc29523572919dbc9b23fa4802295064037e429256f894f2bc5c30b4e2989afad382b398344554ac684c6e82dead03e2488524826ce2abdb310114ec24e113d85de59d64368081cb687901ca4521c1ec89670aaf1a4d27a29d8b7b7be6342ac4b6f114848ad0b171d78b5fc02480fbc948c65367a67120a718a29466ff127434d255195de1d9c46d8a564d797035010bd45924de53255a54befe39cb64c38ee94142698e8520f5e5fa052187e0c50cbbcf55ae62dd2972bba948c8950f4ce2fc6a0ed25a9ce48e19091e155e68c68268af04c96b18c11b90b9b714144e3842ce36334234378e6074e991e6740087110c93019cb44e19cf9209b717a911973660d4c099c32278468869067ce9489612433251cc28c4e2c53430e9a15341925f98c0d26192e386782d065b68466bc8848f81c09c9408166ce8867ae40abd10c980f4d8f98192be11929ca7811cd2891cfac5006cc2884c606bc4a395bf9e94ed5dedd81533d98dd39ded226c8d381bd114fe55de5e5a97a759fb62c2feb70efbbcabc7b05ea763c7c957b1f0fdcd9a99e6eeadb029f2ecf5e7d4fe0957dc2b96f07c3d75a6b73c618631fc771b4d6da529cfdadfd35fab3c7da9f81bc00202f0dfa96f8626c36651367f382ecd96cca68fdf087718da4c7582b3f59afb5166374976fad78f517e77bd1259eac38e75c7ece18573dce5586f59cdda2376c6dd7f51cd7f9b9680633c7f12eebf312b7c02f5f9f9d77ea52603de7bc5597878bacec94f9f8723287312dd3315849be463104a770ce56657dbc9d9f6497b481ac7b09976b0f43de7a046e4e5bc0ba8d9bab3ceb343078eb349d75c739a61d64e0e95062862dbad4faed955aaf5d5f4d5bbb1e0ce993c4a555a0db03afc2ed49a25eaf52be81ff7a154dd0d77116579cf5ac16c9bd5ea50c8a3324fafaa967444367bdb9c8af67507c95344f415f17aa79d1adde2c0279bd4a2dfc7a06c55709d3b3bf6e33a2d2d7836e462d5814d59bc572f47a958efc7a06c55709d33833da5fc72dbda0afd792968e8c5ab0a8378ba5f6f17a9590fc7a06c55709d338b31d9a8dda5f9f310021fafa0f8d0120b938326a516f164b3d7be1d73328be4a98c699edd07c6a37bebf8e410647f4f59e0364f00287e4e2c8a8de2c967a46e3f17a9562f8f50c8aaf12a671663b349f5a905fd7200241f4759a03221003c60b1c928ba37ab358ea19ad76865eaf12057e3d83e2ab84699cd90ecda716e4a8edf6d737d080127d7dc7021aa02029068c17382417f566b1d4335acdef39cbb93832526a41018ba4a2183718442f86702b906c36ee0ddfb837ace3811b39250a9262c0788143aa378ba59ed16a6ebb275801928ba325a35c0b2516141425dd6210c1187ab10267f37b5d04ba26fa7a8e57b3060e5d054b39250a9262c07881ab378ba59ed16a6ebbc9bc5e2526bf9e41f155c234ce6c87e6530b72d436746361e40297c4c3470cfa3a8e930f2619152ce59428488a01e345bd592cf58c5673dbcde89e00f0eb19145f254ce3cc76683eb520476d433716462e703092969ea060d0d7f5102800cc609251c1524e8982a41830eacd62a967b49adb6e4638b3bc27cceb55b2c0af67507c95308d33dba1f9d4821cb50ddd5818b9c0c148525a9a119544067d3db64b6241000030834946054b39250a9262d49bc552cf6835b7dd8c7049f77cbf9e41f155c234ce6c87e6530b72d436746361e4020723496949c68c1ba0d4d40afabad964ea6958100000cc609251c1524e8982a418305ee0905c1c19b5605174231a5a6143cb7b0ac0af67cb94e4d741cb14c3af8b9609865f7f59a6d4af9796e9855f8759a6f7ebda32e1fc3a8e65a2e1d7679609c9afef58260bfc3acd32b9f0eb3e9629007ebd66998efc7a906502805f77cb64e4d751cb34c3afdb2c530bbf3e649998fcfacd32b1f0eb2c2c930cbf6e64998afcba0bcb54815fc759a69b5f87619996fc7a926522f2eb4a9629e7d7972cd3905f97619994fcfa0ccbb4c2af07c03251e0d76958269ba5baa74f94be356c156aa708e04cc1bffeafbf4e03f6d72dc0f9eb01d8f9eb00f0f9eb3382fe3a13fad7650cfdf50a58fcf525177f3d07e3af2b29fd750ace6a91b68cbf9e74568b743dc6792dd2751867b648d75f9ca245ba8e3b4b8b741de9d416e9ba8b736691ae1f9d348b74dde8ac59a4eb2d4eb748d7599c368b74bde8bc59a4ebb7d3c8225d273a7116e9fad0996491aeaf38972cd275db39c3225d2a1aebecc53832988af346feebe8e9a7d01974029db5f3e7dc3376c5b2abd563a761e7397715f3d8774e9d7357358f7d76eecaf5d873308fc72e3bb7f5f1d8f5193bb78d7aecb073db248fdd3ccb735baab753bef268380c361001cee5d44ae15c4ef56b44f752e589e8ae57352bc420baadcee943b785da6574db295bf5d8df1a599135337ad3bee646b8ab847f30fa559ea59a5279336e8052533396664425594a5a7a824ac2253df62f3b68847bec9fd1cd480dd7cd76eb69d9dc860d4b0c58fb40daacbc38dba83df64f033411dcf0c00c475bb23113c10d6db33133f280164149bb91fd46b9717cf938679ff349578b847d8be5cdd5d42784708a2d25903085ae8717352510117eb50732388ce912c4051282209115e69081d72386ac333a22241ce1252c011128d4c0c584294af092664bf8840b574ee083102e1f2768e120d38da03ba1491256824033259c80133444818035c58820cecc109aa0cd1340bcf940c90d55cc3819ba0a13840a6d94d47082324c3c18a306092134b4095302191680382289102098b9210d162354e0208a287e2051831211ae1491c2f77ddff77df58a13595db6a420458931bd20e80b1282d8f0f402991344208284f033cd0891a5a3049e8e26d23cc4e7127a7cceeeb7abf47ddf057a14896255e487ad11c6bcc981e9f7c1bef2802893132409149128165a5882462b3bd1c2536865245a4bb4866811d1ca45b4f0105a369c3f3014c717599a3272776f37c9bd5d276153f776a9eeedfacd57e92a5da57a95ae52bd4a96eae62c8f3745eae6ecd454b5bb5d92a79bb34f4f96c7cd599dcefaf8ea22b8390fd8b8b90d947a5c5f7d833bf35a97d06abe337af59b118f57c7252dd1bcfa8cab642ac1bcfa007abc3a0607c8c0c7ab6be080085ca5254e9efe78f59f2278158bb556349b6c93aca91b60b8cbb29b013211f3b4f419b9929461c0dac2e117ae7a46d76cca41ed550afa478bdef6ca8a59e2e469cd73fcbac5ba01a85da59cab94e3d76b39388d2ec5253f3f3229f4ec5ce2e3398d0f49c6a2746d498fe7343d262642dbfcdeee121acd34c99c451b2de1e11983e824dcbddd253bbe63863df48ca57bbb4b7496febace87b7f400ee9299e734da62dd227b756fd7730b1a837bbb9e465b191220b09e82af3c20a29e8cfd344086c3c608596276a9cc6b7aa5eece5c404d655ecd05dc5486ea0ba2a065cc4e8b18aa1f231ef49002892198f86063450c1018728200c2abf22901f1c2e585228c40e31466cdd83543ce194957948f1a6648a28627217ed8906f789203c8981c80a0d2040f3acc0026081f6fc2207e8040e583820f614029524723a465e9ab5dad6e8f5bda00575a57ddaa3b874e14d19d6347e3310284fe43b13f4675d809692bc63bfb5773ae18638c6eec1f8a2d768b51119bf12763d65a6badd5daeada005b895759342ec637dc92dbbb4bb1975729fba4a0eb571a37973f2760ce19dd9f874e7edf9773fef2f77d9f8bf9dc95778a637df988e67bab34b1ee172abae39cddd1b2aa156d49e3ae886efdb2ec2097204714f1206a7bb6cc55027368367d4c501122cd6fcbcb4df9bfd3fe0b225c31e2b7dde51d50570682d9e7339b3218fa7590891e65d0fcb6f7011f9cd83f27b3d9f4e126bbf572aa392ecb91c95ce6158699e7380e1c9fe5a01e5c29a63ce1a03a1cc741af0e17d92e5c64f5059900b184807db061041f9cf8d064874f903c7ee5f9f0f47921ab6d83bcb7d5b1e8bef2aef06aef2b0f87153e3f551956ab1e5906dcf442e1a211f4ce4f6015ce7d5074ed74dabfa92a9c9351648b148f87733952eb9fd5778573301d7cd8f2a44bad7f5938b7012ecaca3d9c6bf1c313434ce952eb590be7725479f3e6cd1b2bbef207e22cabd1fdf5defa063897b770917574572dd4b4254aa2dfeeedd3db4f2a4fe5aa9ef54feb09e7729a58c2422fb59ea1b670ce3a815579f76d655e9109469e9cde7e4f34fb41bdfd2eba7317a8ed2786ac968903fb1d84808310fc1af56b3db24cf5abd71564b56dbc55ad16abef8d096d7d6b7bf1ed8729460b153d543867c99f0e4e4e6f26f9c01cb41330ac584211872c16e49088664988e37805a7e3647c895680bebcc8320baef270608ad14205f8611ef766734296592cd83433486966090096f16f997bebf162c24e007cf51ff79910b0d80c724b4c972bfca631415928da105a2643eb9d37329c0a8094c0c9917a5a3d9a4de5cc723c23cc7494b6e8ec7ca1e025c40ecf0f49578e858796f318ee413c5aed19abff70095584e0e7617a024f5a7fdd678773396a7803b5440a13da30712487df3e5c9ae8fd20c148942474e9f6c1f2dba792397383086f8cd031e44dba7d70f8ed63a5c46f9f2b1f2c2d3e44b444fddd3eaf2b9fd795cfabd7f3eaf9bc7a3e3d3c9cb33991850c0f4c7c6872c495df3d25b061099b10ac38a10211e9eeb1f2bba7891d9a10f245ca153247d2ddc3fbdda375e677cf16d70b494f941792297f774fd54baaa7c94baa87ea55d5b3fbdb23f5d7693d9cab5746b6ac382199e2cd90b4ca6f9ad65f29765052f5419a3457e9a6ddf09be6e2062468b2a42cb1cb92ee1e2abf7b9c9e86fcf5dd03d513b5040dcc123431fe9b96d545ebd245c37a39d1aa5e4e34decbc9eaafd3743857a580e255094105104a3062ca6f9a5391d490ac1c74d0ea6242ba694f527ed3a0aad090c58b0b6894880122dd341b7ed3a4fefaa64dfdfd4d1bc1f08c607878d4f070fddd3c5dbdde5f1eadbfceb3c3b96a35441026a0f8e0893354d228bf79a4feeea4b2d2a4f0c31133e9e699aafacd531535044b08266868d811916e9e1a7ef3585d61fdf5cd93c5236664c243e49944fddd3c50533c3ea67886f0ba783c3a1ea7bfbec3c3b9edc48ea9a822524cb540c3ef9d094031460910281cdc2c49f7ce0cbf77ca1c4105c994a91a76a090ee1daadf3b5a7f7def6ced70b5d989d266cadfbd5335ea769a8cba1daaf169076a7cdad98d3b527f5da787732ba24822a48d14179b23a0fcd691009728322d0041c20e27a45b4786df3a5d1948212545083abce1724af7ce93df3b4e7f7def3ced0cd1a203468b98bf5b27eb4aa7cb950e564fa7aaa7c3d3b1faeb3a3a9c2362d3c49724d448c98a4aa77eeb0899a201092c4f5c31a12add3a4e7eeb4021400d1537663e6063048574ebc4f05b47eaaf6f9d291d2a91481b91889bbf7bd625eed4883baea959d6546f4aebafcf76385787f0f1440813c61899c22385e1f74c2acbc9c9852d4e4ca124ddb326bf670228a2862a40a208614c0ee99ebdf07b66f5d7f7ec6a86956646244dd4df3d0bb766e1d64cd4e588ba99a89be5d0c0820845ac10a5072e5f5cf89df332b264ca1a2e19485022dd394c7ee7f040a50405ae3146207993ee1ca9df395a7f7de76ce57069c989a225674a4e931caa9edf39555739505739bb1c29adae9c9d43fb9d133ec1099fe0e0e0d07ee3845538210e1627281ca9bfbe71a672af74e3508541da8441dca8e1e2f92deb0aa1645921542f84d29221ca191f3e64d8c1ab926ed992dfb22aabbfbe6557322c7009191170095994cc876cc8ce6f19545757974cd7e56495a5b7d65c5674142b53feea2ad04a37b1a2cad250597a9725f5d7633d9cab65821882ca961dba9a3ca5b685df312d28944cd165b1c1865dba634a7ec77688b082113b62d0b9a9926e9de4b776faeb5b3f6910490c44128bc5b0fec6b2c0581518e38131abbf1ed3e1dc986508104ce0a16b8824290bbf634e3574545cbe484121871ee98e9de077ac2e2143171790eca6d44049770cc9ef98d45fdfb1a918d5126d9670f317d6f575c1d47c5d5ca0130c7482814e30d80ee7162085cb0f2976f84164f71b56c14441452607246d8c18936ed80abf613ca826ac4ee044854c0ee9861df90ddb3018961518112b517f619f15ecb3827d59e69705fbb260260fe7727c70240c12416cd9c1c41babc26f33688a561b116490a2254cba4d1bf5dbbcfab1e48413aeb658e121dda635f2dbdca6c985c48c8264ca5fb3c95f73eaaf592565424999bb6d4afdf5b287739509146890f232246a2b4d5ae477a95501178e58f9a1042740504977b9f596c8efb28b053655a474ecca2c916e13eab7e9f4d7b7f9640ef97494603e1da598b24b89f5b7ccfa9ccaaacfa9e4edd2eaaf973a9c3b40126b9ca0e1618db92185dfa5539625b4788152c24b0c26a4bb44e17749d543af0c9621b8146193eef284dfa5d45fdfe554ceef92ca0bd9c68b9bbf6457ce22d5e42c2e2d324baba7a5f5d7c91dcee500f2e508167638e10986ad21bfc91ac4944eca0a503790c025dda409bfc9aa024001b36608992b4c82a49b14f29bb4faeb9bbc22b19e90449e9051a40f72c85f12aaaaab8ad46d9287733c49daa059e2e4c7961382fc7e591d200c1145b4803ca52993eed7d5d3ef57d60d26f8b801ca171d2944a5fbd5d3faebfbb5f5e2ca415e517290d794579317d5df57557e41e5d72ebf7a38577b505d4a1429e28d115da92de1f7a895801d2660713a8344d491748f5b16c8ef71851b98b4b015458519a8a4fbf5e3f7cbe9afefd7d36b081ec1e0514cf97bccc263173c62e1b10a8f3c3c5afdf551877320b841480c63c490b01322f5f17b749a8185146a006303971ee91e9f9e84df23d411dd0c312c41262b041de91e7723fc1ea5fefa1ea7462a6c456c83ad886e443522d75fb1cb4accb2ea59ed706e5cd2c4901e42662823432ac26f512a07942f49a69440e5871ebea45b9c72fa2d3ae143161eae7a38d1a149ba45de87f05bb4faeb5bbc12b1701091080e2246bd7e8b50184af4210ec1525d584ad461291ece6de089335474d0d1c2134a52107e873b4c6d21c14b218a3031a43bbcfa1ebfc3ac0d58e04161450b0f880ee90e7b3c7e875a7f7d875b215711619422c229e3efb04a2b6ca215527585505de12e94faeb600fe7ea159e0e2d4360d5d0c312e98edfa09690962c3fd610c9024256bac1ad0f7e835d7fa5da8809a31b42058974873a1dbf43a7bfbec3a7bfe1102d20182d62c474835db0fe825957e0942b100be85402160d52449460f2258874834f7f75bf412850eaaf6f702a074b37487589b4b944dcfcfdbaee4ecddd714d7d5953bd292da9bf417a28618286aa89287a48f737f56f7e7f559fd55fdfdf15ceef0f2bcd47244dd4df0f6aebf3f1776bc85fcf5d9fee86a9526303902e42ac4e904ef13b5bfded42a5851f94b8e92125ddf94a8adf994b0a2c418ad4b0860990746737bfb3d65fdf794bfc9db9bce4285ea6fcddb949aecacabb9da57232fc7802ca8e24ba256914bfb156d5902436403142e40459e9c650fcc65d5d57968082c85615978f74e736bfb3d3d390bfbe3354f83b473dc1609e88d9b88badc245559867b10ee7aa559b34407a3abef850a4eb3776c25039e81842ca933527004937de3df11b4bfdf5df98eaaf8e3656879bbf4eb8488d13d743e1a2acbbc3b95a86479612c82c59410a98d489df57aaea5a31b9bf2fd6022e91ea25eaefbe500ef0f1376bc83bfdf59c0670975205f5667ddb285fc194bf396618a2ebc70a3c64f8b2246de2b7b55a00102427f43802831b2ce9b63daeff6db9feda24346e91f56da3fefa03aad2586fd62ffe3d8af96b9b84e9b643ec1313bfad53056c8c10420424b844e892b27927217e408982cc942d4b6cd9f284e50b1a273a1de9016e4e57b4c7922f639e44118509e9c5b9ba0686105ad0a1c908315c49ef9aaf00981e11212c99024c557a95f8eb38f0affb127acb9ce82dd3e9abb76e6ba2b7eebd75f3047aebdd5b3791e8ada7deba6ea2776cebadeb17f4d6bab74e1ad13b76f5d663af58158faaf382de31a7b7aee382de315b0c7ac3786fdd0683deb0abb76e96a037ece9ad9b3ff48675d1dbecbdf59e2d7a9b5b597a9b5ff436796f9dcca2b7f9f4d66961f42e7b6f9d2684dee5d65bdf71a37739f5d677a0d0bbe4bd7520357a97bab70e8484dee5d35bcf50e84d3ad19b9c7aebb318f426796f9d4704bd49dd5be771d29b7c7aeb2f2bbd5f576ffd9545ef57efadbfb2f47e49bdf557958be8abeaad874588553fbcb8e2458f994da2f3f41ebbdeba57d1fbe5f4d6c75e888ebdb7be0208cee5d4ba4cea2ad5add00a0a940f6d1f3acc6c0a5d08bdc7a9b7ee60f41e796f7db645ef51f7d6675af41e9fdebab6416ff1eaadeb287a8bbdb73ea3a2b7389ba2b7b844efb005bd45dd5b171241eff0eaad8342e81d5ab10e6ee90d5abd75708dde60d65b9711a13728f5d6750f7a7f5d6f5df3a037e8f4d683b4e8fd59bdf5202bbdbfac87f2200882a283601623511ff27ce8a6d9149679d14bb34924cd269066a57768934aef9cf5d64d287a67adb74e12d13b57bd75b288de19eaadc79eacc780e88dbbdefa8bca7ac983de7807bd7110ebe293deb7ebadf7e460bd078bde77eaad0745e96db7de7a5011bdafee15c03583171d4800697325cd497cbe62e4c7679f65bfe1ec349d4ea77b4ab3111fb6f8d05f665398035daca3d9048a6653b6593827d3c9743624f496e9defa96eddeba6c274b72a5080f9539c9745f14fd5f0ef779fd7e979f7fcf3e7b95faecf92ac5f88c6585309fb1ec867c76d06ccab52a7d0f66b3e92ba5d0af2dfd5aa34b157419a5ad1859e13176db8d56b352a489ff3c5f2516ff55a50a840b9fadd98471ff15a1bf5ed1ee65bc1144e9a4e82d9e59e449e1b42a9538691e4b5f29320196b2b09489e505c58ac345d8afc61883a8888b70d5bde1f034deb6eab1ae1be7a2a979accbdcaab13cb5bcca9205b2e441014b59cac298a5e804acdc71428c953b49e81268069aac9465d1db5e5d5d5d5d5d5de1943b5b39a598c4ac14d55ce9944e75b553024599c18856caa674f5945a47f629655bc49f12a8aa572b3f1ab4804a5993a0527fd02554025df172674ded2c3f1d325a962da8284536b652e45a51c246182a452432515926b9955f1ad16e5c510964456f7b7525b228c5234a71a66b34bfb528714cd6f78b5c6599ac0d6f947345dc339e342a7b6a4eac4538173a765c95f1557f5199b2579ede9465caa82da22255e15c88da2b32323c268fca9b9a6faf663a92aed2b6568f1dbb6851ab12682eca0f8af592442a3f5114af704e0cfa7c75f86660448ab637e79c69554697067ffb571df40e1f7c38572cd2ea598de09716c1ec9f69dc100cbf0fbdf13d08826808becf68c6e126a4675c7e58c962d046ed267e9e453dea10c5a9a0ad6f1c4ec4b966d3222449ad5e95212b0d0dbaeb99d6355ba6cf33ba6fe01b2d63dabde1330404b83a5b8fb2676c367dd93400f9e6cd6f1c3add67b483d16f885f91b2571c21b83f82f8419403fc23fadf35b5b4f52dea19cdde1bf8b3df376f42803d3f652c6af03fd75ba4812e82a7067d8b354a54d3b2e72ca2f8f6998681116d7d572b119b4d1f885ad3003a2e00e1c20f016e463b80d3ad21abb5b5625a5355aa32aad7db757d6f5729a7deae5f1ae700ead1f59c8c36d5db1dc0bdbdbea216fca0d19bf6167ccd6d94d72da8325e7fbd8a4f1863a85b84c590062ec24966b5f681f6dcb6123bbe50160a63df007f7e4f31f41c8ae199dfa2153c71a8d634dadeb8c7f5fac65d259427de0f8d756b64abaa76aa4a5dd036a1592a5e2d4700639d2a5230377d8ba55b290fe01c0c45f57d6dcf8ecfd0471bb6a610b551e69fe5b4786934f75bb652958751297b355e9007d1f6642cbbce4fe801dc9cbeb8a58d2b1f04cffab9ad36fb8c73b6fd2dd7a2ecb5cf5b97b6c74ed06c7dfd1aab36cf15004e9ec67cebbc132e8df9e629ddab87d64df00b40ccab93d6afd9047a788a2f7a782e8179ec7412d3d1699c03b8b7ebaf13832ae6e9e7cde8c49d49df396346ebd23fe2579ef884f6df3acb0d51e0d31880d72bf2662ad2b595b69fff88fee342fe23a2190079cd831cfb2549bb23f393e8a4488aa4e8a26f27495288f42092249d244992244992244992c4c9f199a9cb4c9224e943923d64a9494d239dd42167a4e79038a4cb48922449922449922449922435192361a4499664261de8ac9782ecb553c445d97f4e1b2996f072cd45d77f81fe73ddc783fce7a219d4fcc7813c3bf6d1c5fcf432dd6a6151d4bae2a0f3ea70d1cb814edb858b5eaf97bf5e4ee3be5e2ff7e979bd5eafd7ebf57ac972665eea382cc6f3bd5eaf57cfeb4543b7c8836ebdf3d241b7d5fd6bf6ca7939ce4bf6728d6e0bf5afd7ebf57abd5eafd7ebf57abd5e31747f4e30747f4f26fa2a5f24ba3fddbf72d7bfbc76d64bc1e73fa7888b3ef739352efabcda5e2807409ebe2110a4f56ab356c7c32d6ab1b5d8626bb17f19049d043ff0033ff0fbec67cfcff3c901fe2e309f568faef730a9ba53ae9276faeaf9c4dddb122eba8e4bbab71b8446df2a7cabee5548020f540f3ae8a15fbbc4741a97667b9eaaf4005586ace71c6b9175515753ac47d663e7cf75d26d0a3b7fae979ed30bf07360dffc580527bebe66b1a184c7eddb8f6118e210dda367979db11376bedc9a178c2e827ecfcfc76b36ddd0ab79033f017aba9ca17af0a2366e0fba350dd0d3e50c9407d1254e9e9a68d22db2e29b674dabf9a138f14737554afe2ee14e1af5c8fad882f6df22f9354b95247fd1adafeeadbe58af5e170c81b426a8faec3f4cf4b6bb7dadfe7abd17ad36eee9817babb5d6ef73d2def2dbb9da8b737514f475a1223014185bae9ca1d8fc7dd94eb1903f300cc12fc3903f300ca33e10fcb21afb81e0976308c2e6efcb762a7f6018825f7efa2167305ac6e49c79743927f18484fc8161087e590b981cacad923f10fcb2182e536e905a22e74ce6ea03c12f73d1d223e74c43fec03004bf1c953f300cc12f47e50f0c43f0cb4168f8c1bafd40f0cb27a86073b64ba6c879899c57e821e76c06e37b31d8f5808688b77e1b418a2b34a001b3c3e38a4081bcf8e6308761ce91befda56bae1cbdba5895aa522ed14aa215fbdf1d7ef5af32cdb048d7ab93e1f9d5b1de3e7386e863eafce775dfdf33c07afb5e21787cebedcbe07f2478ec7914abcfc6797df55bb357276b48d3222a81c704705b2d8665d4deffdc667cc1effb2c0c58ad479fe31ce22d8390a4bd49f2ae3c2da8bf713f06f2050c9cc2ccfa47adf94a5951b5028be8cb9010222e2229a2158874ecb819290a43a464851344552c3e2822f2e4c663280a16405a90c04209d18e1622ecc06237a445f401113a2f5c3d845022081e0c584154f0a10513ed8344914ec3c614d9a071b89c408b3810e1a0c32945635644515a714249eca981826c9321493497c49001c68b3cdbc2e9c8806481b25c581b9423738891d8f3a5c52c0c0b7287a24c2be2b68308686b684665137980a8105f3ba0e26bcce9563c07e1201494673d00e92a353bbbf2d3c4070cc393412674b20ccd2c6bac9c1cc4038e48e341766b7c195f70ef05f7332fb8f55e6dda10dbc132b93373ec84b22a7dd96dce8ef3bd5d8b5ed4de7b2f112f116a24a9883d59c22024a0f87ae10381b0f98dd0d6efcd366808f26b42c3a277feac944ffa5665dc6a9dc80e38b06ffefa0df1b3571bf9bda0770622a37307e1df10ffe702ac538fac59c3fc8a8af588a719503fbf0ec8e987e2f020e7f0cd14babfe8ce135a3c33ce395f8b03db1efc7dbf5d5cc415875f79686cb8d7f2b2a665dc17ed5ec67b7b86042f5b9248abdb31da46f13194e0a1c3951f8e20a24c5a2b88820b550e3bd82401c142f1d66553aa8cfc96039f4f5da56fcf40073cf67beedb5f7c4fd4c6edb17f32a90f95ed6449eecd7acf11bd653b190f99d355cabd951129222223521455e4a368c8df5d0425dbdd64bb22d9aee8c6c3b91a4c52d0b1c044162042dafcbe59d00684221f38a10b018874dfba7edf96d270e95a808147096fd27d7be2f74debafefdbd64db7b9e936b7db8deaef6ddfa0feeefeea9bd45f27eae15c8e11ca08e11df18147922652277e1369fddd41260a0626929cc9926ea2267e137101e20305365c88981242ba6f5cbf6f4e7f7ddf9e6e43b41081d122e6ef26cad257445df4151196ee1155e91e114ff788acfe3a910ee7aa9a214852c0c1c81a18aa30f19bc8490a0612945c45e124dd444fcfe6371114971f393061e20b5710e926da1149fdf54d3445fe26a2d244da68226efeee21bd1bd2bb213d35a4a786f4d4d0d00ee7f609bcbc312187373a6459e2f7d00b3a2755218871626647ba87b67e0f518006303a6489a2030d27a47b68cdef21abbfbe87ae62bf87b0d20c1149331405e3f710d4d6908fd8d690bfbea24beb86747ac8e9afafe0e15c03c20061c6092423bc014289df2bac9ae84a82ca981c88689921dd2bae92f8bd220b4b6bab0d4f47e449ba57a8f9bd62af58c1a56545142d2ba6ac68b282eaef5e51155b115b115b21f5d76d3d9cc340cc112c167ab0c10d9a1489df362d105c684184351fc80025ddb6237edb7a1f6831028a1e5a745049f70add0aa7bfbe573cc17eaf18127b6203137b621363eb62c3fabb6d595536dedf6db3faeb361dcee588caa18525588c14699225cd6f1b9a188ee872e689d319a874dbb47edbb0b4c023ca09549e5449b70dcd6f9bd414d55fdfb62adb94980e156d623adcfcdd2aba624e2ad43871fd85c2453128adbfae628773357448783494800505efcc6f1552434dfc80626a0a4c1050d2ad62cacc6f1555171082e68a8b10639a48926e15bc32bf555805f15bc595fead020b96460511581a15512a7ca818f277ab80826da16e605b2a78fc75948773475a469280b0638a2921647ea35a47a889828d151d2a40916ef4ca88df6856932294e8a1890f51a8a41bed15f11bd5bafa8d6ea51be5824541a3c0a2a0538e7ea355301eda8447054361280c95faeb670fe76a9b1d08563d429042e607dbfb7d563160a80851040952b89192eed312f1fbb422021b2082d8d1852724dde810bf51a7bfbed12774084c84130c4c04317ff7097b3a614f276c77c276276c779e3b40f1c16e2a4a93273ec6fc3e3da0c5083233f08010c142ba4f31bfcf18d63cb952c1071a76a090ee73f7617e9f527f7d9f53e6ef930a4c1b306efe6eefeaa9e9716d79d6566f4bebaffb0ee754b8e131f1810213bb35a910bf7d04a7204ea08a820c9523e97630bfbd4a032b5ba8a0b0830c4d9049b767fd76abbfbefdcab198381126517fdda1cc29f7313584d7c5731dcfe9af0bf1702ec71122d0a4e08305166ab0f2e5b7908c1dce7039c1c525cc07e9160ae2b75056069c78a932844d1125b449b71010bf85b690902982902982909010d5df2d54650a4199423b5348eaaf07f5700e035d636e6842c9921f74c0fa1d54a138320213676ec04ac2857407fdf03b288745489a10aa70c16221dd423efc1672faeb5be849680898203060c4fcdd4159bda02ebd20acada0aaad20de0eb2faeb413a9c8bc0162740b0a10ae10b0ea997df414e2190828411509cb070440de90ebafa1d046505c5ca4aea863160d21dd4c3ef20a9bfbe83a682a84a266d4a266efe6ea0ae2935535c3ca02c5e8fa7f5d781763817aa41c24409251f9c8025e5e13790140792f0c035029b2e1ba64837d00ebf81a480a1ca13504988d8ca4a375097df401b0808ab0d109136517f37105409e4a3041a52d6ba4a205d09e4f4d76b3c9c7b00970f7690f24215ac255c7ed774a60c3145550a2a84a14a776dcbef5a16053c1c4cb08107066b8a74d7b4fcae69fdf55ddbaa7191b528646d4aad09d5df5dabead5a07ab55d4f4aeba7abe6f4d777ede96f6d08c9e4070cc944ccdf1f72ea879cfa21793f24ef87e4fdfce8702ec79414ab16744873830b4c58fdfe410ae2861b868042450712e9fec9f2fba7eaf0640bcb0a63acae9449f78f0ebf7fa4fefafe99027fff50b569d3c6cddfedd345ea7cd4903a1f2ef2c9278b7cf2e99101a022a814091a0f7349a714325323c00040002317003028180c8a06a328c9b144f90114000e63b65260521e0824f220857110648c210a004000008000008001a588ca00984ddc1a451fbaf9cfb961c8ff7b3289e65cadbdd42222f66c6dd6ab7c1bdf1cf354aef12ef285df769b694d6dc1a333d0b5a3f8c7961c74a0d18c4e3dd24813a7e9f27ff25e9a1dd91c9326343a600a343ef5146b088a2bb11db8e2bd1fc26e9e0acd637d1d476173dfddbcad8b587b7267872f7993e8e7dacf52614b1bdeaafcd3e9be3aa9665dfd2e71d8f38ead7514b0b51baa7d7eab015f23ca901cc98c0413bace4278363584745700bc2f7ca205439e90da644d1af0b2c967765d8da3d3df290edd2f9cf2f48b3bdca118d5d6219b6ae8b4c33159f190f0ad12d23661796dbf0d3d46d64c6096ca0565c41bc82e53925b09e0195e730346a1c0642ac0762c782667a657bd58983e3a8369db82fc14df4c805591c5fbe8a63c420b9f468bce76a6f6e011d37547d5725005fadc4e0fd345637e8e0cb18c4ae6dd1061371328c52b1243f80e949c444a63ba5761a8afe2977ec36594747496e8fef731c433c3eeb1d0d4fcc9fdf6b11859b24e1a25930c61d914d4faba54e98d5ed4fa360a3d63e77d8aa7829e273a89c921b8aed9487bb9631e24bb547289d42e9521f2525d787d157d6f87f49345f26d501e97472c6e0d1eab884ca5d0a58fd4dbf8f3918233d097deff66b59aafda223d13c89e77d3a2ef63da73678d9de076508298328aa1c0f6c8bbaeae0dcca05c424cec1a375b4140a5c3e1a600280a39219be2449bdc19889004b31d38a7abb8d11953520f59b045ae700a7da6a498aed89686f7e93aead269de6e7c31b9abd054b762f40cb42cda90661a5b4a99290828a69def47260d649b6b29dd69b987a21275aada3f4c3616e554ad56832442805a962a68854d48274f4a9379ea14136e4c3e8b34d6377e003f991b3b73e4954f2920b0a5cbaf78b2852ccc7d6f7bc11981e052475c585592380b8d49ed07aad0b6b4f3aee88367545be5f15a2636cf5a225089fc7fc859ff7d41bfda5379385a2293bc6a1e9bc464b9b2a059484323829b897417ddd929969b47ad0a1ddf0339df0f15388d7c1e88a801cce4e69b09988e7b99428bbb2bc51bb64ddf4f022d57f4feb932ef720745e33c403818245b4f786ec3445ff9b020a71299336c04ddadb0b968f1081b6981a7b05a70276f8a3e2475ec520972156ecb28d02b08c266e1ea436434c9b06eb243fd122bf46219028429b3c013b9b6746ab970370f957df6fd2f64f4edc25d7a53f220339b5337a1c145c448f46f9d2f638b9a1288abfe40a3ca171740f87c1da80fc6349a5cb97f64094a383086c0dcae24701e8b0e17ccc5c07accae4233dad06aa3f31c706e046b7e50e7e9012e270c941886884026fa8dd0cb94b8dfd96a9bb854b39853bb78b30b30d209e810e5f256e703a6a041dee10f28cc625cc31ed8dc3520c2f8593c4632196fd7a2a8d9a0739414c4692c11b771f94e059c92aab39bd05bd24ec8189dccbd68b43e246ae26b2169b03bf141a60d200e280b6e6826703a008e9eddaebb7c5c3e5aa63cd390c8884e9e5f203755dec2f9bcaf695e54d5990806af7b6795acc3450fbb5534e322068ed64018e2f83ed9ef25705b193f2eb28fc91190b3c4685e17053dbe12eaaad9e4b683e9e35b32dfa544f1e87e301a1c4a6a5519be059303174506af9813a142da9596c9f9be4025a77ffb80d9de20e5db07286065345dd331684fa9dc4c352704695315322e616356d685f5aa05b043aefcd8882a949190870984a2a0ac1711d1f32bbe380a5169c09fdb553805cb7839203d0e961b5c3b6712d736b0178dc0c8fdc3604458418ef59a1495ec17edbdb0fd93e482a505b98e645884c80cfccaa2ebcc151a4a59593d76476620572e0288eda5954c755846875d40ac7c68114296488107a4231b80358a59fa70b383f0d36f684a5119885bea4c36415da8f7e8e4b551d995b449472234e3e14f58c2e75bbb060e558b7014abdce08fc5bc86dd8d83d18df260375ab882fcb42866eaec9ad651cf204ef9790048beef049781281b24a0b6aa0af03f183259e16a4ae993d60d471ad0220cf65220df03161ea5d63150ac17e5828c4130603d662d2b7bca17cfeb7cb85913df739cc507e3edafa0525d79d0c432ee0611f908d7bbc0a4774701c159ccad6177486e090f8e1c26c2432b74dd77c9109bf448dc2b1a4d2594d3f90b5260b1f31bda13e67ff72b008d1c7b5813333008eabc0ab8e65b7943d79827b130f38fa6b3560131e08352224285d60ad88cf75986348845da5b733b53b4f6b2a722259df684eb8a197029e92f568a2328bff318b4f6bac434abf55c42b357f0955d4e879607e48cb80849dc3cee84f722fe6daedbfd4cc20cbaa08904212285acc9f76b2f9955511db465e06dd0a82d9052fdd53da7c962ca46052153222a0d7e8f7a82fd0cba40458bd8105b97c4fd3bd2728d0b0797559fcbe18279dab5b9a541ae2423f23e971d0fe1d60ef654a3b512932aa0ea8e369d8240358f3d177e410c37ec8b918b4a7e97a243b382ba64e96924e571117f7458b358242b6795f0afb0fa388445fe239e16cf02d1be3e6df042448478e1bc457769af7e9705c560038c239c0132892f0e270e30408e3f8ff30388a864794d9f1d8511e9940a802a88ad15716c87d127116145711ef66089106924c820dc365d58b73e8832248a65ade3ea71a224bf922885053a7b8371eee4c3988b55d516e545bc6e16468f800f27808423fd5291b48db1ea191a76afe23448a6af1277c06994c93b41354182680169c2843ab568f9ce0718fe09d05f3aa1f9b51aeb41cfa0c61be9935ed6841a1da7d44b15439934f91c5c412857b17b4fa0c89dd026c15ae385ea91c9a906505e3acc355dda2e9ea456de8a793d5f3299d80a9230f0ce2c4874d849c8041603445b9e26aed63592e7b51d4bdd4c88c9c39a7cdf40ac46fd4b48daba5523e440b8a4a44147ebe6666fb573dbf1b2359bcc94fbb656bdd1871efcf0d343a5a1cb3f4c545905919fb0e9da13a75a97a7cbe996b30fe5562190c095150871da3088bdfb3b235daf2378c3f1a6df731d7fc8875257a1b79fda8859abab929b323932b3e8059187c810170466ec1a012018f5c16fff5837e1e203cb1a110f85a23d09b23acc203f0818b7cb9b082fa1bd61446f68b5e03d58a54e2bd96a32437b3e817b459c661966891ada83fc2aac6da067c268ad1b58a88c1fa290553f059d3c30638956901de9d54485f5269b10befd432eec6d1a1ab0cc8a15b2453f2562c781526acde7801927b34893f06373533b7d45e6b48c4ba67d09900efee3e23162e238ac260bae8ef40a22b255264c0c3f21ede8b771d884692692e93f0ef41656946de9c9c48df1125a902fedfe302622fdfd23eed8633a1ab3c00a2b65b20fc67d2b913327b3a62b8912564d3781ccfc83713331dd27e782955322330f1ffb9859f8806e76ddde7021ad993051d9d6bd4eec18a7b4644e566a86d4d83864c2c426920dcd9c8895879b326902b2a14d5362e25053a64c2083ffb9b8d56f80b492721ce0ca09e3b13a3efedb415726ac3181ccff810dd5bb28d7e79890af32c6245a2eb3e2976034bd4e6f084d2f5aa0fd257493ff6cedd36c84b4087cd42bdbc44ba029a51dca82990a4767203b454a90ebefba85dbba68e356900edff736e47094354ea2855b898c9edc0ecc225bbb48fd7ee7896122cca36eb0bf3d850d82dae5665cfa9314c8b435b858f38556080139d75cf5da329d6345f4f12ae567c09dfa7213c130a08a4e90d4aa3b149384fed197e3e9477129567cc28f4a3847701d85cefb443f7718209ef39de54064c203cc2e07a9e31d01a934a57080af926fee6e5d37fe1ea521f6c674e5c8b44ed060246968eec115a0bb88e0098dd38be8e0d2a7832ba10eae4b325cf893e13232ffb06dae919aa1bae8d0d1f0b221c40a6ee862a2cb5e4d0714bcfb4a90fa3f3be27e64e92fdd64562c1caf3a87e0e96db1888000949f57014c69543bc7f05861d99cfe52db502d7b1af734c23b289c6182b2422d007b8195c932001c6c3fef3b1b3864f2067bf37cf8579a0c28466851c129e684437b6a7ef03cf06843c5d3cd0fe73ce5b215f54e373ffcb424e03654e12654f6e8fc1f49d92fa264323626bcfaae27342a64a33d4f09e32ed5a62cc85f0aa4c846381b297093222f4d1220fb4f64465e0ca7dcac093f3eaed7c36a7d09d4cc832dc5c15084cae1b96644ed951f18fb6d95930056e34cf3a2ca5b6ed0f6809c9da8b29517da5f008e020bb6ccd9799773b5999cd49eb715def6ec2eac642b4adc5eb185e9adf196977edbbd1331e867a8ce6dcc46cbd39d08edf85e80a3a967fb6a701457642b3b6f5be1855987f1396e9991d913a99868f6a55c23cb36fa9191101754a98a8e72a9f9e4a4cab429fd3a2771ec26d668590e339a39c94d7c687f245174db142e813901572587117075de0f83632315b37d0bf7dd455e649e9be53cf49c7cbecd459dc72821124a79601cc008aab5fec97a88375dfb7681a5aa4fb89d8bc40096e803c76a7ed9b7662c06a3f1327fd75d9dbf0a4f79729b0362ceca7466814a4fe0a9484d16b1b6acfa8cb5ace96f5aaa7f7cca31322ead83cfec42ce1205249bb845c14f9e1b7208f9c389a323aa820a8fe11c95a6a7d5bb5d4e35a7b190a4893578512ea3bed8484ba220a610d20c59aa5dd4c2b01dc5933a08e4bd6993e19922d8c509293b355736992042b58528d3280039fc0c3069eca6881234248338b9553a350df75c7f3956501c5cc0dca0c3f662ca6d9e0cce59f58b491c1aa71572702580c50698b7895da5a3e135e2d7a423024aaf7ca9fa1490b24cddc0e507b86167f263e5cf811e1b086eee70a1da8813507b5a0842b5e38a8fce326bd784108ed6a15f8eb5454850ee1d5a255d5cefb00b5e7a72234919859216dcbf7764b37cf55a32db7f6a6231a9b4c02b7d9478d263aa4ddbec17be80ae16f8bebf5f897e9658d4d7debff96542c658de7725754c5823eb134829f9c0da5cbb9d2c2f7d5f80e06ea244ccb5a3bb789963589fa5bc947dea01e293b509473be1dfe3cd60277f28ed50a59ba77c0c297a28d9e84629d64c80e9b7cd8dac03b02ac31f44477532884c09b00be7de1b71ac0d2c33c2461bcfa848d4983c833663d383393c590b80e9c8d9a95853ab5783c55c9c8c4afafba0347a7d192498e7b38829ddf3b8af967fee0023afbed51f7a5c73008cb8185dd3351a53b62bbd62fc74b02948005829938a905225c98302e795aefd0c2160bd7d113181824a3b8bb4ca79a17108c11faf4be4bcc4963b6b280f107787126fe5ce523a487019daceda0ac19d0a6a982247e0668f07955033fd80f668381a07443e6d854dc63156722aa9d71c9a90824dad985af379e78812b7e0451f161351403e74ad4bf00067c7cb433478e438e06779f3d7a1e4a8d69c1c63c0590bf4ca3e18b9841e8ae086ba00822f9478139895d6ed92e9e2a905b7d2daad989f46def36eeb5a37e2eec5899755eed980d75a2fbf5a1ec6e20b64fffe8681d999cda81e790dd0650bbaebe074bb9e78dd9ed1d4f7ed8e078e4ea2cf3f5cb501ee9b49cce98601063ceab24b4883d670fd72f301ba80ed73332f0edfd2390ff702226192f95a702cd767276ff17a11b87a79bfc81bfca5d02bfbb893eac560d4150bd82989659a32737d06256323d8e61020adc76a3a741101b506a1d501416d1a4abc7b9108bafc12b004f93da056573d9993f7a2822cefcd1034608d60f3be8d22a0a6311121eea938d6e8eda267294070b23e8f64994aafb95edd4708936005ebf2ba4282e2060176d1be11558a6664ac90e4eb923ebd25aee87d0cc9f3a83ff73d935c7e72bb8e794e6025fd5bb33fbcb32b8934164120c8fcb28307face6c2afbdf80ac6a06f640a816ca84a31380a3b138f96debb920689701f19327ae48be359c9b27963eb0f45687fa95f7d888beb4836607bf022c9a71b9d649079a271dd7226be9762643c9730b0cbd1b6d041baa65a447f231f84432ae121000326a32e488a01ce3d051f2d4e8b23e53eb0b4d5cb81712760de61828c35058b8c9ae89eaa85f8293eed0b70104e3e9f0997e4e879232bd392902816b792bf96264f442cf5825da4a24d6cce9a453c8e5188fbf0d508c5d468874c374f7050d8e58726810c81f1ed0062d303d0bc289b5ae7986409d6c586e9331021e874e0afb0e915ab0c8403b0c9e022dd6e974b7b61a60933e2b0c859df4bd48e0839288408cd1e1276550810871c30bd053b50153ff758d83967fd7c1779b9bf0b829c30c98d5c71df838891845b1f4256bc45c2268a33e40408df62df428d7800e4dd0bb0ebe1bdb84c7471986c0549cc2c0e7580fa422decd6a2ff549fb350766b16e063da049a96209dec7134ac46add0c501a430358fbcc7ba82496ad61553a5c3639bc3560d734fb13f0eba8a6f1159dad44f5ecbc6e0501422e8c18c223d80514bdf5e29b1734313cbff05a0820bf9bd124602d479094885b834e18772341028abb2d6616a820739c1305d59fc53323b73b7b3887b2da9def9060bde6570e7f40564ff7e598516f3cd75557c0736d25924e958430a0519a643a183d598fa7c50ae46fa34989ea6fc4fd0b0e5e022ea0b7a585b93f871fe91f0181aa7b8dfdf48a7e78dad35508581b5f75b7bf79898aef7b8724b53680afdbcfa692b72daeb800667fdf778c3232151a33c7dceba9e904e33642b15026e027593b26de1663fd3134c53efd07a705a2a277b6d65845bc8df1b1b0c754631ecc3dc1f622c1f19608e17727da8ed45c7ad38259694776dd713bef6ae7fb8296455859cbd49fe76b64e62ecb46dc35b2015882b80bf432c04c5e97ec9183d0e94053ec7ec53a24c887bbf63b4a30f3ac740cca4f0c4edccf83663764d3dde5118177fdccb8fa922b500b99e0dde9e98efb900cf727b1b715094475dd1506f070f3ccbd5330c7839d0bc2b1355c6351ed1a4b80b8b4fd662f1abe4677a0d39a82504afd79087554074b98613db55a5161c4825d19e5a173b9486a4fe9722201f8ac91b2deb558a83d1560740d10cb71fe45cbb125f1536f657daab26c995ac93ecd5623d0c49c6c29417475c8b7d7da8a49de1ef82e5c50aa85e92e9fa25bd3b59490ec1f182f90220e6c9d3dda03e676f26ef384c027078964f11b3acb9edf188734ab8cdc889bf71abc4041a7fc64e733951f06b49b551777b7f481df974784aced2540054d8bc14ddd38b8a593657a1390701e14b6178848346e0d2431e3b9f0b83b51e02d49bb82aa8d0907e71362838655a8d1c0b798a4fd0858ed8623f4cc7847f758be0bd1a894b489a54176f34af19786657be77fde0a0f66e970f0ab6c323ed33b9a2bd58d8768f535aa686c97b5bd12db1acc955066e47cdbb8b0ee0ffc4b78cf4e1104824ce3ededa5e168f6f0275c41120fe2aa41c4d56795225e82e18437c9f2464bafda3a7acf8825ee5eaf8346b494ff932c0dbfa1873afcaaf0be3f2d304ae5ac84ac7fda4baf5d3cdd94c9fcec4ea83d1ad99600b89ba928719117223081699196c1f058f0e2f458a9bdc172edcc8d6d833ebc5cf929a0ed3937dab80888a2e19ad5d2c6dfac4d842a8fb5515d4e7e278ea572729b1d75d2b8cbe65bd5d20ca72c5e70116d4a919b0c42458a8d1c5c0db95c7147343cd1ce75ae1cf0ddda17484995281ef575a06558fcbcd8f8955b78cedf8f3ad053482da3ca8a8c4268c6388b257d160cad4bb8634a3be743f3403e6b797014b9327ccc02d405f9a2472dc05830a1471a264ea008c968296e2da4bc80fc303462614614dcfcbcd71ac0a1af1cfc79bc233a4ae3c15691501abdc18440c409000d811e89caaf57c4734b1cda101535ae6a08390cc2aa778a44403e75b276fba8b20949531fa481ff8a7176f0e4a8b527acebd2ebc2af8a935aeab85e3b27cbb2643d4aa8d5cf052add460e5d0d50248abd0a309f0bdf883f843ef7e7219a968e64f9d6f7b8cf3334acd044c63a5316e1d0ea89e1ea13c6c0faaefe7c03c4853750c8bf591e6448a6a3dd0734e7ea2652b0b321d516cc98fe5a042bf754a5d6ed54e28ef1de1e6588cc280b1760dfa4b92d322d025f4c6c0f7a1800b8c9a31a8dbf9dc732d59a5caedf273963ca7f5b83ed9bbc38fff45702159e04ad5a41d9980b3d6cfa40f4e6e73096b17843d47887dfdfa867cc0899b16f57b5dc173d407072095ba490be9601d19560e025affd6c3fd7c72b888ae28a0ec089ea787cf622e4038bfc30c9e3b14430b77d3f9e2e9955c9fc53bec34e6b2a7fd9b6ec69a7437cbd112e3a8c52e5115aa5dfe206b53b0407521bc8c62b86fca04e4960f144d7f2f24972c019d6a001e8f5f0884b4f3ea124aab54cbdc12d6d0e7fbe953293a6c1359f3d2593f794c3a2898ed250cacea02ec41ecf08312b999794fafba93070a0883a7b1524cc59fba3bb85a02942bd2e0d4160f1686b98815f8b96ffccd2b406f581661b70e921a0a2ac80c15ff39c9f133aa0204cec49142024cb765397103bf1f92989af2d67116e133ea8a8e86bd323b5c256584661fe1f366d40e3aa20be536254ce39ae4e65f812459779a91ea84a1dc9f0d58533d205820462ad75f484d43151de8b765bb8fb060fd183739f834c8aa4387e483075a3c89f4d8fce0e7a163d0f7bcc9b276611b57122ade7f5a03916a861be49a04068093a094e11deeb2fa00b21795218eb897cf0e59b555f738c92299d7146995ea78b274a69353f632d5c31c39e4cdbdf79a91761bdf08305938617d5efc49f755aa0e6b5c6bf3223c9f9058b33fdf2a88479e96428419e484bfd46a8a741f31cfc7d4d521c11a9a1c680b1bff22b306173095c6d3224274dd22db0e4fd467f9990fff220b758e21b4f406f9acb6eac1c993250de2d35cda3156d71ebb25ef04476e3e0cbea9882590744c3be8cca0ba5146fbe7f3dcef9d7a74ecb14884158704ed2aeff71871939e9874a5c6606af1f80cf558c1857dc4855de7460d4511126c922215c3e742340b31d1b5c3b75c848a71d71e6f411274e3b1e0bbc306801ccafa0c24e30aeeff05bbf99de67a47cb6f9dd2a471819902dbbe4e13f2b700ec818e7d5cfdfb759c0909b0ffb1105e3475cc00d382ed67a2a9321e5eda935e9960ccbc2da161f65fcc009e8efd76dda3fb759ce9ca624b9a51bc1d25470eaec57cd68bdc9e40406221871d87eefc7e074c5b064de1f97a7f874603671b74156e2e9e2b8416234bfabb8b9b1498df26dda1995549ecaa2d6b47e406daf69f5c8824bd11d2bb6581f3f7012b74ee68ea0624c3ef044597e1bbc7bfd047c087376b1bb49a575e6b4ed52531f1ef9d8671e77c8c6f500a0365ddf2ec8f8e58d0177d8d8a52832298678e6f27781d35a3c4fd4c87eb124863e8f644a01a630ffcd5c45a137d57a14fe3a1e2f87d2f10cec055a5753c351b3355520821ae8e90144e65b3d4410b5046da3d54800aed416770de131bea0df5d0e6fa1abac86944d5bcef9a9061f5dc82757e998fdefb399ec5584834dfc4ba4f47db06991bd1c7ea8452443fbd12c9c65979f66093a5ccbe708e1a5498a1722619a93cca0004c355b70edb15d1f5b3a601aa6b8d647cdb147a034da108e24448ff708a5d34b979327258edeeadead7b2edc619199863ed14db7b5b4a7a54adb74f45a1988762a2e1098d81277134d7bae71e526b5473ed7a8729ddb5d0ea3d3a20a57b13c4825aa64ca96e0909c2384d7b0f30fb3de700afb1887675accb6e401711fba90632520fb680e5f28c518b2784dda01017dc01475a245e3560d324047758ea20dadcbe1fa590b641d3051697069985b00ac1cf8a5c1ff7ac920093ab8606e52264cbf63d7e8c034e32699455cf8d1a2ba5a7f80f77c3795940d8de31759c66af7c333103b7fce6182eabd3fe7f051c0a515c513bc87a5ab8b292a6d7b350eabaae8b4584e6d1dc67d85f9bc0c6e912f69209ecf93291272b659e2069d0354c36d4b744bf9d744e05aa98d94d40ef903cf4a33e4751b66cc238a9fba76e1bf2b136819a3be28e6fc16d8293d73b75ddc691a393f7793154d550d2ef70c8c99cf9de43c1fb58b1770835e819d8c590f9c34995e818d4734319dee4a18cbc9a4372002663e275d4283733c6ad80f69361f768fbd9cd1805e454074d487ebd9e398116f14f22810f94f826c01b903e9b463ae9d050e2a6cc801c4ef9e3d7afa8d174f172e44914d8208699d85f0fd77efdf3d7b849e922f52f24d5238bfa59e9be8e411b3cd0ed770f34fa8ee9d7e318632d1761fc03287710e6f86415989ed8e6857b82f81597b2917434d1a72f0423ce20afd758085b152d02e07aba0998e7c9a75993e3701319e70ba52ee49d07e65bb3491c9eb96cd1247779441ad1284a30be8b7a162188d97628898a318ad09485f419f44a2119d7aa6bb5c63b4885cfc65dc284474847c8954964f07f8db960545d6ed93fe8aa96dec8cdbb208c8963dce210dffa58f45a63923feef227c4f48659b27a2452ca04573f369efc7cfaa39e90f7bbc926617be5839d9aec0b85b0765f527ffa39deb652860e5a4a53ab64d6cee1dfd345a0cf0f7f7a54a120d6e3278d78ee1145c456ec76d58f454525baea32bcf14952008939d338160179510ef2ed0324525a2470f499733857cbb5132c3bede02842e5b5347186e8f5b13c3aee74af83818643eb4c23e7f10ee10696b90a07df12444af27d75765c52dfe9211869af2c1dbf0ad9bd768a31c9c3fcdbcc3c39d54a0b24408969c8ba15bc31398af27180bfdd0e47009e8a1a553623bf67d2f281a428e1ee9a70fbb98cbf234b4a0814be188d57bdf432380d3a6a6b1a800cda084184ef999d3843bd8fe96f4ccd7042670597696c382cdffff90137bd36d7bf6cef744ac335c5dd75ccc5250a954a1b600200a758f655bcfca7ca6a2448c340b242dddfbcc5b02ac6c7b62280067d49712319607c255c78b4466610e790eea16ebaf9765fa21c5a198e64260112c05ba9382fcb9573bf730504a0b59cc16f914fb8c640d9009bc94dd1128b260184970ad6d20af4476c883a557250abef836ea1a5820ff10456a84b29c24ca66aecb31dabc89b2d98c4dd50cc571101795fbe9677c2eadbe349b1fd22ad5ba6e6490e96934886376c79567291ccc13c10020c25811ea15691655a0417566a5c163dc6c60336e6cb08ca53e903ee50bf82edd7d5508169b2cc2fb2d9e9e79504455bce4ffa821b3bfa8c1f31faba1f9dfc81875f7b2f2f97fee01ef3f56f6e85c3cb428310494dea4454f03a1f182da2387a597878ae59f070b0b640f124be61e2d7b6459dac9288f438ab90b7f22cd750f74209e10d773bc2495909d36a13d4d4b6a3d5f96f83d9096a87b722d757b542efdf670a1aacfbc40b7f724b207ed9b0edaece34e7d1c4d651552af72881d9803e3f30b266654b461d6259f36125db2a50da697d4b561f8259f3652a5b6db06d3efbd6d24452f97b4ff27e13243ef683da48c43c1cc9b64b5a176d97da9fb86f02816b329924411489c501812d3a8ad606d0146113ecb42bc8b919cbaddae0c66948909a629a246d0683b65dda31d4605f79413619c505c757c809ac4c98c7780d25b9b032f0e313789efa0a4d4e017b329524018d15b9be4dedc4edbd35e8e6568bd34cb99bd4dcbcabd3f9699bd302e137b732c37f56a2c7ff6722c43eea558ceecede84e60ef233ae2c732a8f6c9f92658b9894c8f9099a9b60eeeb0c336dc80f19d7d5f01ff46d5f07393d53373b7ab2b717fa933e5865227e6ce566fcaad56ffc4cdaa0e999ba49e39775b5d99fb4b9d393794c7666ece708a19db4b663659197d7765ccb0571031c520420edda58622d7dec7e859ee4324e8dd874dd0741f3a41cb3edc04cdfb3009baefc32668dd8749d07a1f2641eb3ecc04adfb30095aedc32468dd8771da6509320ea2b50a79f4e6ce337e5499f81713949d4e2aa5969b2ed1e457350cb309e37bf76a8b26acd7e08d4250436914111a5a5020d44082e252c352148e1a84a2286888124553031214c41aaea258d4e08242b0861214911a5aa240d440f2e4510d5ae2ee01b89ff6b6d23e1967470a5b710389bd8a5da7bd22d105c84b6852c3703faab5d09075c08e60a65707108e6cf751226990044e47cae72df795af1f7e3ea1c14bb32978fb2d28a18b0c6a47d24407e86db92a3f3655e1bd88594e436c355481d09f19fba94f2da6039ececa6a2ba1db369304359350a72516ec7cecded51b1f4064ba791aa75bffc0da11fee13708c8b8fe9057483cee7b22cba86e228915ebb46bd2143a590672c82bf340cb89463f793628d9af50fcb3ccf3a6f0b8bada356f807fbe0afeba3c173428774d5b014a5a52d0ba091adaf7b38b308133229f6ab544fb40af14949c3dba11da660d92e10782c84df32ecab758743b1629114645abbf4998a1d0126ffcc3a1d0a04dc094fc10e31d7d7de0fc53c855d810ac644a513cfedad39bb61fa8218009f4549a1f1920122cb3b891ded681030a6e5060c54291c8a20af0068ff9f402097710e4155177efdb30b243496212cd5878d58650fef871238488f6cbdd69070db7f4f8a6dfe1baeea6ecd48060088d15101c0f9d5c2b175389f6f2d540243d884f0d4c47e0373273a72034ec36ae693fb5837a6a3bc959b85c6f026966187a10da300f610d99c0c3e17fb340959320be4a683ed3b530f8496ba55a0a18c527dbd8f347bb0e23647156a450f3431d6b8d4bde9ad9c55d0004012cc62418eb10e39509e6f77f6afa0c86d96fa2c3c27523d82fc5c040b2cf6a34bc06d9c26513a23dd1771a42dacab4a0c038d99f61c00b575eaa0ec8dbe42a773942d82a923816423a58af41b65d0d9528b05dd75c7540561e1611825c33ea0179594483e07d108218e109a7d31460277728bfb887a89155ea5ab6e5ba8f8a83c5a70a6d376ef4bb60dbe55bd971c4df31b61d48189dfdcb5870631e3257e67b5248517ace70b1e4b42253518224f0a8558b7719c4c91b9f837b10637c4cae21c6f8807fee5fb803fa7bfffed9aff4fb7dfddd5fe8f75efcbfdff41f066e8a505cb01b44e6242e3fd2aa67ce67fb8262fc0fd4ca1be1a153cc366ad50a9a5bf58296ab5ed0ecaaf7bfbdeaf5632ad5c303e73572c07558b9880016a73859ad0daa24dc943d1d3b0730078a86e85d311e69d769b1a321ed96873ac9775ba1240f040c27744670d94e8209d3049053f4c9a2850a4137916854b56a84aca506bca0d115e44185048d4dfb3b3d445ff8ba469a36cdbea834cb51a259105e39b72c1132aa7dcc519557243af3b5427e3df49070421cc0fb86d0a65a0059a4a4bfb360f8f49aea084dd5ca39254d8d255aa69a030bf3321dc609fb0966247802d67e10b5461567ff8980bfc97c831d6aad5181183f71c0ed3381b7e2d0b461b383b07dba7e8d2bc92670f843ef8d20f3bd74163e3aebd215d8c890036988238459bfc3263b6c3b889aac2a461f0b643f3cdbb402773be3f5a879120d2393a1c06c8b5a8ff8884d14206f8bb414c7f7bb5b3cf29f8472ec94409bb3e3083f64efc0f9e6d220114a6a02770391b31b86f320afa6d3d819bc6660a35281fc9d170d061a10796ad6000a8c8963684011c2a4395464856a656eef0193c3b662709eeed769bd3e60741b0ec67a837b4feb9f8017f097510e63f42b76fb0a96e306affe1b5299b0e38f9683ff51bd6f4144f551c2889b1838dc22db9a3b1a742e75ee1500231fd565b5adb1efc7afc6600cabdf84713377be876af28f2ccdf3cc6ef971b3ad034709c6ff8b30f1ff76bfc6b81541088d6f53d1f24d25a98f339704038d51be8c4e0ec2745057192a9c444530b38ba8d57ce150d15774a8c0654cf19debe0aaa829a6a8427bbea12b4f1a25b2c63a94b5fe5c9429ec51a617e22b3c2305c05f5104ad034746582c42dccdd1ac5d6ce523a2b383d4e98a83ec19aecd590f1c6097064cf7f0a8bf08d1f78e55b0fa5250c99ea43407d706431cb42248e4122686fc1f674860fd0765139f1526f451b415653c9dc1d2cd5f9fabb4bf2a0d44aea6bf4efd13f89726d653f60e3bfde2c92a1b871196f7f997e85a93fe8ec948d494be0597f2c6a873f780c8ef3c8238380acb747b462003e6463410a1fca9ca27503af986504a7365ea451c3588a5f7fa23c4199a0b1e6661587d821320eb323998176026156a3a0dd04495acf90425724b9c67f441b9413a1b02d145921929d5054711fe604cca4fa26084ead2912aecc4c16a261fabad8747859d19451036ae8d8be5fa1f479d40051d1144f6be80826c7846648a34df0af6f61674cb0d56adce67d8eff71f9ffa18e951bbdf9dfe66d671a0aec902c08e8fe2297c8edf6b823ef1010f7f71a44f2318612539b58ac033df658c0fc3b2747cac106a417305a37baff069cafe733c1a19067707713a6d543abadbed259b5f74247702e053801dca07add35e77ed4f4491c5624ad0abdc05c4d8641e84ef293327c875ab65653d8c81443a571f8e5f74e5a2f240996c441eb6567d64349392e7bfd5e513453d76eee12ea57dda92c2bb35c99f8f44a18ffe04e5aa8be4fa10d8558738bfb1ad28bc5c357cedb7e067861f186dbbb3a245960965927901f5c23f9004a2e27005ecb7c3b9352d4cb0c0b4d2d630d8e32371a64377dbd2af7900d45ecc30950e2643c76ccdb546daec8e6223e2bc9bf8b5425b0a2d24ad75e055472234cb101a81e7180dead1278b1e09abf3c7355aa4d5c0d16e97e81a711646c6975c6c3efbe6e8687a5a20304037f3e9cebda2d2978bd3028f7bcaa1d09e3889a49472e7655bd50428c67731fd43fc2443e0fe4063a48b880b9b75015dc58455fbeb2f81ba2923730cea811425dbd4dff6cb20e76c33e8e4f528506721fffde6ba9dad16a32d3949b4ef1930f8bedc94df49b7c002e4285cc5c8124656ef9021ebe6b9018aa1a7481daef874c19e7d710e52d1640a4809b3490562c75feba6ead42d17c6d2e1fef5aaff8b7d012ca1aa424838d4280e5052d2ff93cc1a20b2394cc889305e324ce2b5c643a06051cc679cbfd0811e503197350f38ded07eadd56cbcc0f9d222a21f14be5741b8ec479bd388ccc517c4d42908d3c398cf37b2ac243a6c78ade57ea7730c51b30273e2e5e637363ec74567635c6056609a3c6b59eecbede479deecd0b2a7a4655f73e3569f2ed324eba9e1ff1520a2b4905af6c0af514daab6ac69232e5637be5091385079cdc2248c063ca1b985149239f1433b06081381f11b81b3a968d90775d6f3e55442a4b8f3f3184fd181e4e6762f0afde111dd22080814703b1e8886fffda8de78dbd5eeba706bb1ed45803c233e05121f04a613dcac7ba7ee2b34ac59a2ca9f439ff06f7fd158f84f8d508a3d4aaca7505c7bd07b54ecaee5bc708619bfaa0ed09709df31c6c4f21310f11bdb3f2bb89df2ba91ee7f7d425116290b19ef5cc3cdf56404b46951c0c8874c678b38986c88b2fb6b2973c13a57269323f5ec79f876612d4b58fb91c44586ac06cedbf7ae14b67fb009cdb3c5318fb90adfbd6c6c48765b0b774cb05656b1ec65762fb6d728b2aad69ad00bc8a0f692348357eb87530467343bffeb1284262a3fb3dff9a8b075fada0b76e05065b0307f14a765f79af62ea7ddb916e23d76c7dee007dca04eb8d8187516c2ffe7f505d3f5abefb8b68c124288937dadb9930f77d395be797c2a4a7e60ab9aced9cc3d4976c77f1682e847b27571f284d8f3a514482b3f37b000b07950c45955f900f31d95c937b99bab8ca47d1e7c83b0bc459f11a19f74fe2c695e72bc8ea395cedac4b38abd2ff977eaf906c0c43eeae22a93032a7870d01e19bfafae85ffa9a0f3e069bc0374bbbe968176173b38e4c5383a35d80e4e863500e21905f53875dd4d78a0a5167e30fe1b87d34b058bcc55e4e22c83fe4f84cac685c81efa79ecae4a310b5946793fd85ad07c983daf78b0522b20105b88e251a85dc8a7348328f4ee44775e3e901eba5928c2e4a900c02be691f12fc17f014a3020f68370e63c242f120f822210281406693e1e897e47446785755bc4c3899b5f5220fb96a21742b45ff1c207c99369b21494b365a757307b070a11cbf3123ecb1bbb242996e907319e05e397b96caa6efbb7658ba41e4135c5f9962f7e0422be3ebe65e6d6cf69234bc15fa008f0780137fab22d3b01f08091f96e154e218c8c7c25fecf1851bcaa7a87a93572ee23fd5c07eb0bee3eb4243ec8d5d3fcfa0ac38931664682b7506be24d5f7927abb13d5220ada0198536889fa4369e97f1b60dea267dc127b17238b89b29de63cf74d19bdeab661fc9728796dd58b981f0f37a98124019be0afeea881ce2a58da5780f662531b30dd0e44035eff384e77c3c53d7204712b82224b5cd96cd69c6e05b61f8a8421f006741af436c36b573e0c32d62067db26a0edbd89616698bd4a2d7a8626f15dc670aaf110701e6f24d03d90aac01b7ee0ce42f957f7fc73b9c40d290d5dc885a1e1b911b9211398e8be81a3454f1994a26a89ea276f884bba7e8290b0bdf5ceff1eb39e70b749210340053aed762ececa2763938c8e3e1624aa79e9a388a5078631b4bf7b1d71b7ff492b03879d24f7d4efe48aced9423421656f270c5dc9b4587fce4a1197487ab5b92b7eb079cdbde043c6b60ce5c3480b4168ea0b335c2e32aebd5c4adfa95ccdb8796f854e097f0abeb4cd2a85d0be6ce5463bb14de77c2eb2d30cc17ea507d6a066e8727d2133c62a286376bbfeb5be8f6d4fe8adc44371685ebf9d843edde3cadb992ad9eae20272ec18fff90f44a602c13a64218756e0affc3ec8919d70f11b86eecadd3e481b5334de453a4ed9ba318a7df66a3366e18e16ffc6386452e0f1cfbe6f61e41b5b6423fb1cd40ae5a884dd68c2e396fc5041cc957f4827e8b796268b7c97c25f80ba7c6f8eb8906c240a52162402c6669a62f6953c77525d241e13a0cfc42e8e9107cddd492b08a452e9845a16eaaa942df3428150915e8e62a0dca2a6af6b7cb3cf2fee2a267cb831de31f1357f1ca9263a3cd0c05c87264e20f192dd59412abdff08bd5bfe8dad4ab0674a81cb631a1a202309ffcee12bd58ed3ab9c01585c3b20eb9aed7ebebae70c6326ed8eb19c1b05d48de221504784f30282bfb3a52e7d0c0ca25601abf79eabe090855f26880bc8b7f1d77c6514ac1f09ab2d29321de96c570d726f6467cde180f895ec3c2fbeae36680c66c08d053d7816d484578de03d64c22457b53b8cfbfa40fab9ad1cfd93a8343d7db99529b5cece2e0853bc7f0c20f348797e8f3bb208f2949e64bd788c9626880e8de2e21d092e906cf31e2bf756d33f27d8cbbf2a0a06916ccac5118b3cf9a15dc265f61f874637a17b3f026abb91651d02e2f74be731cc86d4d32e174bf2bbd84c31a6ae5d3f7e9a129d24422e2b61611f852a505706abe7f21893fd5bfb0c0f606260a06871796246bff54b9b1fb4bf57cfc8cd6d72ff3bf9b31471c84c1a51be4774e5c0a17911e8c35a3f4d281a061e94bb5a75df2b9e1f0f21b6bcb0b38a37a3ef32e549b74181e32230c950bb842061fcd8f6ece4b4178ff27b8b8c17da1eb987ac6c1ac84bb41afa445190652c6d8e21457b8677813399d59d40c9d5e4038edd0eb97992cfd3607ec75d1b5b2ff97ae76d93a041a598b9b42ae5e2a840a72058ab80e854af4e2ecdda6387520473db9c345ba22eaa93d33d6991ffbfc996ee939d17cde47d2d4b73b4d10ad6dc0762b0b626a8a411358f362e89a942c7a8b466cc21770b5061bcf26faa3811df30592b8194850377900d4da4dba5196c6eccab2cb091bf4551eca27df5afccb0a955b5f43ae71df731ec3b02c93ab27a000c7304cb3011bdf2db43bf375f1910d58b6c44e270423b5e480160b8301e05b28c0e2404d403636e0114aef7181ca5dbf17a0e274f34609e59fb6b023a50b969862209d4a0e0d1021305ecb60a13fbb58ce2f81306dd7ab2181409975458129d875694ae7a13c1a35495fc76647f80b310acbb78c4ede3c2812947472f011606919b6ba04dec376b3e734a07b506804377c7ea4f6120c7d851690fbd561c01ae76b8b79e1935c122910b263ec61be06c75bc2c7f611ec1508971615c22641ef0e0e3ae003ddd25a4ccfed87b82815fae19da3573c813479c6997212fe2b215be94be2921a73dec66bb97f25b92510bffa04c25a85bae883704c91cbc82ab3fccda12732d94a36b29823ca4746affa834bc7be26afc809abeb9269b5493fa9e9fd27794d1711a85adc3a93871591dac2ce4cc39a8332dab7290bd7423cbb63678dcf2dd2ecc72f243ec9489834080d2c2ee06f4ea5c20d69b9afee922fe2ef3e4c99e80fa54785d4d32990bee9051f460fe02dda20af7b70bfca7a1e3c1b5c42d2e509961a102745eacb08e8b3019e79dcc019444bd2042d4cef10f1bbc665ee2164051a4adf0d4a278b97dc50d69d387b4fe0a015a2d8d3b95492bbf54b10400af950c9cc5b7910ebaad51e09a0129f5183660db0f579242e6ab4d0e70e98d0590b427ba1969f05e949c2d28061351bf832e63c134bd2945dadf3e36823faa5f19bcce6b8ddb8af81b643b892594f0a4c35ed9b77383083ee8e06ad155ff61359a939f6dc39ad6af842b56b4304c562089a1e5f2b32d21a8a6cbe96cab661da67ce0baed4719fec895af4179814f0741b893bfdc21d275a4ed8a4d0af98d641ff746396e0d669c92fa6ec5094db8a3b10ac697fe3ab8aac1d4318cfa36a0f56e91f6de0579aee033f77e9a757d7284368ce5e8b374a08540c8bfa08d55ca6fa036d8c30ff3d6d8dda98c2b4e9742c68f1ef89c5935b0b930791aa39bc756cb2139b6114d856d9ac9cfa940abbd0abf6d959b56c169cd211c2b1cc72a89f43fb52b0e728ccb64d4b6b4f42e9d600dc0f8542ec300258f2275afd6c32860ea73f82f1d77504183906a9801372d70f56fc6700295b99a83e4cbfa524045a33896df7015237377d9748a0341e5a5faecbdd0d9f902f429913836eddcbeb276ebebbb1d2223beba63e69fe30298f387d87e1f04755200ac8cbc93529bef2bd80962a25ae865e4bafa033bf29b0924957812c228984c2738812d203f65af8ce20c281e940e9c9f0c6b99c4906ff397173ea1f815790e3a55088e78d3fd82f3f7638e30cde40384373c1e02477d9579bd6de1993560dae29e0b12e0624d3639217ed873c6926b3460688d428fad0aeb687db9fcc3b36af336f75806f920060b7c3c01ed9e76b43a67d31330f578187e684b9a7cb0773178070528f9e1be1f3c36a96f3812050cf491a1e414efda1f46dd1c4da13b7dc1614a627d27366d30dfc5e01a52f3212eb208398396a4818d774364bb814be91fd7e27f36f2ed61cbb6f81bae58dfe61a7b19a29978c79f9f1fa23c3da26a9124c81bd8a6e7ee09a5e1f4e6056c1ff6bb2a9131d0e2e5edf6034a59d4dc23beb14d673b55ce77618ad1e91b88a8ce0955291f773ee8be47fca68f44c64612a62db627296d50f74445b47222151142e0235701d166872d863298bad5d9a9bba068a030bedc470fb834cedff4dcca543f36e84515722dd1cdaec754a591e2b2ffe70c334d0c4e12e10f0fe9fbae674b9e743ed0f59d674a45b17c44de1149d4a0078062924580ef266394d70d4f5d436248bd5c5c9156342b9fdf0496988349aea451d356ecc639f8f2322aa0c354933e1aace38c4602b6419d424e27e873cd401f4d74bce890a33e6b831260c899a1be34f709f43f68d58796b80e3fc8d3970d8a29ea3d70353f550561faa2a76315adf09ee7f9414669e1ac9f87261dfdaa4b5ceaef76f9d34f9264711033aaec27ac1e5998e95ba38da9be1f83736f0b9057d0f6477ffff38ab0b9daee2b907688bf35681ab33372cd4431464f335ffd6039d1582957816b91348c0a66623b9422795d797eb7a087edce1b35e0debaa8ab2d53665c8e43880bf0fdeb1a0b38273e5bdfb6c16d52ebcf28db601d146b4bbee54857bec63d10fc2bcb486e7694eeacca8d91307cfbec3935d2d762316c5616d02c1f91553520266aa56949ce2487dd2617ac020d83e9b47f5d677cd0a455a24caaa1244126eee3d6c6ab6d94608dc5d0675658c3861348aac0f46deed4d0e19d1981ecea73f28e2854c62f5f7f5078869f3e18f7e77b2be0843935d50be495e9b5635978c77aade571ee4aac066895332300435e00e79418362cd2cb03f4164a8c1a348b80deec28c1553435e97aeea1a4cd3c0662108b32aeeec32ff109f58914e20c1b742c715e1b50b8b47aee944b64b11a691454887dd9a2316e7f7cdf38d195a2e91aaf97a46fbc29efb5dcbc63c89ffb5143ab8d4b243470a158b07850bf1084295148b422e024817830aa615c8874c208a8537a24385054725d462bfddedb83ccb227cf4b34287970257f545f3d34148e8e6c26fff538458b91ecbfd59229f178a2b395d68da5cf4a30ce4452698f0135306e7d7fcc0348ccaad10b25df686bdc8d131c46309012e115894744663756279538b17891640d460680f95cb89a5ad0434e13905bcf9da7a6fafe672301afef0c3b72d67aedb8ed8f147e68a164c60c4b65906765e564714f0b0d534ee1f1f91da6dfbe05f3109670b76be344f044cc2690898f25de1183e8340e5bd5c2a27a4b585e32b428d90a675a67cf610fe5761228b8e6c029961b36b64a840960ea1c3e9529cb1d6ade5cae23f6bbd671546d28304979c51f7aaac25528ddac359a11f8cd632758f401bfc44fcc19e76ba8832bb11ac80458cd8a6433b88821cbc80374b057dbcf4607d0072504c14434cf61de7feb2515dd597999f9270227dd9d7c9c242e9d5d8436a46d2195accd86d90c28ec236f44864c1363b734f6570ad865fd5edf34f4ffd3e6493b50b84509bff453382097ccf9ada958380befe2966463f86ce83dec5886e8fed484b62352fe2754bb457f411da78891766bcd7832f9eaeac14e4dd693b8c92b34ad0d821e81573b7bbff2205901092429cb9e2d8c841b730abe05b9d32a3f4081928fdab4395960d8a26e70b81d73030ef2a1399f708cc4663780716cc78ab8e56414b0f1139611735caaf157a9f7829f4595c4bcf35fcc61d67507d08f0aca0028e4f508b4b052737bdbf46a0f70a2d9e43d2b24b07dcc6f3052a2470da80d2c50d72204dca81f9246d4034838aeaa262608096de97dfdf00d491f0e8be274f02ca624a3fa3f5fb46af1ea3aa57e5f70ac1d478a4ab7e0f84460013ea1ba3d42517f185f1c7c854681c4218ca810ce011c30d5d205054c58812d1ac946d38671a00b3ff3ed8ad2be3f4f981f8c59712f14b8dcdef20d26d8082b7f139935e9cde131c350ef97b1f3c29f459ac456f6b543bb94b1407cb50d0ae19f0ee428f79935c73999730e15164627a23e8b7fbbc123ecc78bd505090e9a20c2f485f0cb8e074e16edebb753b9c41b74366539b9617f1a0753581eeb44402e93c5cb01ee8215da3f4eabcafc2c639ba18e55f192b29a24f8011a54716beb885ab10a79caa3bb75a690bedad7b9f832fdd5f112d579296fef347fb487e506d2d835fe05988130b3c7ab8058f735360287bee1775848e5400b473d8dee5e5e49e088065727d72673f788ad2d63905209c9c90d5d7970b96494a2733abe136454a48722ed19f392a6caefbe082c109a568ec110cf146020410e6ae3584444cb4d59754ae8b4d22d52c3bf7f2ec907dcda65e4ada081231df4562a7032f2c4d964d10b8a3287c615e89151649e08b97008e464a0867e15286277cb9492b71abf136ff0497ce8808a8c8cbf1e52f8a856dab61e56c1755937951ec7c70dea12eeb5ae848384457212c317d46c942f377f0463b30043b41f0a0f67bb12f4c0807f0c304ea2005dffad0057602fa7c633656d18bf681bda8ae2f7ae73b336c2d034719f0692398353aff7d16ca0671c61d7f5adbc448a1cca51f4de6d571c4751706094f1be1602be5664f6eaa6372071c8ccea8de6a2f06ab83c2273be27727082661878944d76228ea601c1531a01807d7c2f9298f0fa4eb8a4a2f0fa78b91ca7e0a9210dcce16249532a4630746383c072e303df59aa34a019e25af6f2dac4809a77d6f90a29ccfb9f94ca4e3d39473035e61594a49bc296d3ae5b1a804a7323a972a1552b56aa68f12684519f02be794d06c5e9849dd3f5150beac987d80d2572ba1103230c1030ce349618d8519ca772d9921d12a89f2c83103af0fc8b5d189dd90841df9c911ab72d28319cad9b544edabe35886309e107ccc09e96e3284e0c87948b598caa90b022fef9b03726c59b7f23e02bd34ebcc9a185d623f599527957a4846003dda1bbdaba360687323df9d5e9f0a10c911d45c4fb8bdf0e8557fdfb45323174f49bb33036a7046e8deb57e602cf8b890657da8ae5c1cf97c763bfcebb5e62307de5ec47f0a7c307c61702dff064907854ca69a55493da7273bb08222b17eaaee7fda7c0a18644d84505de606da2be649464765d77d111159e96a8c44d66b4d59bf162a1aaecaa8c464fea76cbb117b55a71c378cbdb38edd1e8ca58e00d3c8c97bdd8f12dbf147c13c01a4c9000e26788631d62d3b736036882f57a397a5ee2ebb0a9946b6aaaed0f56182d795941216a947afe632b3848765c00cb0cab8aa3f7797978d3bb54b088a4058bbfbb660377da40625952210c0e8083d1623402060ebdf8dc697ef93118f15190f36c911d9088a78ddb18ef7b6a353d16bfb28952f3df6a520abaf632a00a5115ac909586741495e172c1862cb9af65bd2472be1ad2c5a96093ad05be1d113c4ee6ab50e50d766d3a1021a790b9d20a1b40210d2c9875b8ba95b5b8e1ec3e2bc85d7c5e11ed456d4303b49408a6ed7d5844ddfc2185ed7bba8a265c63e47992ca6fb0e597721f44e1eecc1100b11e6f0b96ea0e4d7a8c35d79e865a32de001606555fe5c25aab513fe434779afb16190168038c20cc496e114c7987343d8adc9976bfb2e689e1e3da4af10f6b6c7f726a9b9abc9339e988169831dbdb1a52af73cf6cc7a83b2f18f6306bcafacb21fa1934c92eeb94f26e9b06b128ceee241f294056a7160f6273c071ad600cf1ad829f164590cd507011264bb0741676e4008d28fece5e0a8f59448e4b35a8b735c1ea2880333f07033d8815bf022e2285b0ca525ce3acac512b4dd1353e1cd97140886b1d48d03a6ca31c2d0b83bf98008c8ddbbd66a6bb192ec0689ed92c93b35f246f69e8e946e6af5e81ce52af6a9c2590a2790e46f6ccf38456a017aca453e93560f09b9981c2e422e2c834864f3545b026987fa5a01d729ba98fe84c0316946fcc4371653dd99f2e560ae0b4bc60162412b0bf3975987ba294b59e2877ece0b71b6c448dc2d99e1f856e1cb4b84699ec1311cea4d29a2e3824bb703480da5c08efee30408bfd6b9272481a39caed7c82402eb3988a6893a3f5f8576a84946a8e2f3600f31f7f2c9fc27aa4a70b6bba8f6609764bb2ae05e43411b34f4ae1e665084258e4c0904698c2d6b9da455f2b3a68c876c6ac722e7cfb22d658f26fe74a9b438d48f51f4529e02b8178f6457c832f4e8ecd3a7c762037552a5ee8b7fca1dee72be675431a99a289668201f6dcd4b37bbce033a3c59e5e115ca00f7702a86df9b5b27915122a7357b4a16bae0ccc855103271dc4dcce6112965d42de6ada72f51909a08351900146c317ee13b27d8bcadc42a860b1b0472ddb272afcdb71e89485044019a3ec47b3ca960463d651eac263959f01380f8f919e2437872c0759b8e5c09264e0dac8e027e662c2abf75c1b1940c21e84e87a754d703713872cf1699547109aec7dcfc3238ff05873f96c1554eaefd0bbe377a29e80de5b85d83700e540b3b9d3ce0b024e41104ba6537aa5820da19a703cdaa62056cecf1c67c309c701df078e6b31026ed56e5e75da84c77bcc89632450232d54fd155fb03b19508b05002a310906a7321517d409d21fef5df3695bbdf86bd0ea43c5562c30411d9ddb6dc52a694640a3f0a930a7a0a49baab77559830c7a7c6777777ff23dbc3af9478632d47f7ec6c867d0c2141fd7d521f305e89359e34a89f2251047586342258a1c41caa2e939821478d31ce58718253da345236ad60819af6464ec125205c6567da9a6823679ae60f3346ee1760de63188691e6d2442a1f46397f985e029991b46dc70b368d944dea038aa0a0f5b943c690109eb08365825dc7d102a232658b9cf1de8a5c2959ca28658c314a12c827121dc3c14475d62d7fb617836c624448d8633f4c4c462c462931a90452d65fdab4171489667452d77b3e856ac618e35c4f6e552aa191f387b9bdb0756d240ff5fbcee380f5e2a3e20ba5336872386b2576d1054432c618a39452f20aba075ac728a594524a19638c2d85849146d05926d823c5467bea07817888c5253e020285fd3c94f1100f4d3efab9d19e192c7105f6386b0510c208218430ce28230ce6466512ccd2a13a91214bb22c215a32549d159fb92f0956fd21843470f5603761a8773295793217c166009b4c5af4d87fb0496d9e73a7a8a87987818ea8e4ba39bc3337ea10c3a28c58ef6059e0cb5f722ea58cceeeebec316e841c9296d25f3e74a34590c51e93f882b9517e652a7882c5abbe22131cfabb7b978461779e654a39290a4b02859a1b151a827232544a29e5bb9498942e4991247f3d967a786c1913844390ec52c2b9514a29254c179bf5c984978c31c618a5941247062ed7faccd15bc818638c314a29a5143782a1b880a3ec22482388e2db8821cd1831677c4c76362f6463e3332c903354df81a33acee6f3bff9f25ff3d001eae8201e1c1d280cf62f5926dd3413a9bd1c1bd4cdac0659616b6bfa4b2468ed30b922b2ca8e48890414230a7a7102f44550955c34fc7e6805917b9c3366934d36516cdc666e34c610b988d18f986a8c928a18e3a909378521a5134fc5fffa6408598b17f8dc286b21e4594c76648c31c618a39452da3cf119a44c22e7d87d85aacb44af46e832665294bde8eeee32b6df444d34708da62db0eedb24accf199dd0969312ca871042093b1a21a53db06c73229b2fe3cbc766b7af611b0efed14b3db0c6a45319d316a94829638cac448c3e027667c87d53e3016a1c4069b7fdba1a3f2e25bfe66103d4d11f9da06c803ca00ed4d16f32c1bc984c5b47eae6954a7ac9f97af17968d55c9ad86f823a7352ba03777a073939f84a7ad13a6e6be277a4eebb40f587ebc507c2258645efe0cf0b9c4570f9a432533024647a073b14935366922435b94919a3141a236f949a4cff9e874205712224b4519b1d2fc28c9874f7f7624449ac0b82094921a1fe190fe551b0852b7f4eefedbf444a229f40a1a6a99238b842f59c2b01d86555ea0cb03451013568d30038f842264d28312c4a8ce314f772b06a7bf79f476c9acc49229988b4eccbcb71c4c663cf0676d99e4b3f622a94d6afb66e07a6e92b70bf34de90e9930336afc9d2073dd18b5adc622946295b40a564e7cd64faf73c14cac66688cfd090f7cc209baf7dc47ebd38434926ca038d5ee4a6324bffa2c52998905e5ed3344eaee2ca575a6f1ad72bcd378de39526378d83ab12b7c90702137fbec3ffe88e27d013101823f1e917bb13adf33f4a2af590118b353edd9afead293881788d2fe9de2ce504b411f85eb77e303e0ebb11f8f1bd3bfdf01adf6b746e7f7dcec738b6dca394524a19638ccd33371aa57494ff6cdfba787ea64419aa47f6cabc828357ef41e95af966d81dd5d97bd97254a7a4ffb68a55ffb83c8440df1bec4a75a70175f7652132a6fb4c4b4d2fa58deb53f6481829e388b0d715eb866c4d7f944d723dc0b9d12b252a425d5ca930702ab141d5dfcbf1cdb81f99a90c650fd8f57a977ac8ff7e6d548fc9b41923b2281bb03303180fadda3ac84af5286dc1da6ca4b4b1e12a090358e9c7d7ecb509975fb46eb78644ca7ac46e8b564bedef8c3126b8628c53ba122637794e789e6f3fa1ad9452c640e73b16b7927c79c1482e47e05b943039355226a794513a9531658c5101793029860e5ec293d2cedddd1fb58355a5f7df4e229fd88169fabf4d9decda1f720e2184117b77c5103b222cbab92f0ec9b51e3b4f2261ba74e9327d90b24a9cb3e3dc4a1fa594b2b4a132cc83d08a3d4a4391509913892e6397b14fe10546fb48caa67ccd378d944d140ff451933b0c7e8f1d9d7f2b976cc881b521e421a2d8cc629c9352938905514a8f95480aa29991668c2065267f9b1f9bf5e933444248299f638c12462965472b40992d605d8c31ff79f43f948cff6c62fcb7ae17d306a580ac173f3edc940ff67a8b2af693ba806e32b8189cc906c8a634b05e1361afb499b48e2483b4aa08114efda0814d01b15e0c5797263e0ce771585420fa8bb4cae6074eed7ad35597083930a8feb04a8e0e1a86e3686afc35000ed51f66dd733562a0fac327f273a35b946d8112030e688cfdc055a4ca773f62aaf29deb266c131554ca29698fd89d9c78ebd35d069a0d8aa46da5196440d9d94da67fcf43a16c6c823c212434434ccbca1333ec68d0abf092242d470fa594d216accf1c359e307a410e9fc2f424e39c93e161d773251e3960a2417c3860abfa0f3940395472a654ef207cc97d471cd6df1a7fe89877cba457c067009aeaa68f729bad1527439aa6df145fba52f7cd2d8618ea4765cdba1f4a23a0f0872cad7187dc7b805576356e7016e1ff91e3d7bec6183fcacb3c59857decbe24ae0435354dbf0c0c5ad008bd015dba74f1ea7567fa68638c1c638cec0426d9a3a0497fe8b0d46362f2bd63a23552168954f9f1715a8b5becbcd483df25d63986b1173b9451c739bfb93941e5c7ee9b1f8397eff0096d71288c8a1f464e760b7fc71328c40176499a9b35967c6c855d2c01d92e409274a90628857579e7d1dda33bc767c618638cf993891bb1ece1aaa8fb84a911a67e94865865e76d4d3f8ce7ad4f19042c71450a32ae1d48265a3254d9c71fa7997bae431f18507f9f506afc2fa6ca0e026d4d3ff30028723dc0fa41a0f5097da40ecf0ff461a21fccd803a13fbbbb7bc78fc31c343b0a9b7e26ff221d7c715075af82ba0fc117047a85754dca01dd57fd7888833a0542106272508fcf60ae63a5ffe8f69f09e6bf5fd5cb7fa41ae767a82428c744fb207860b158ac157d0d869bf1f2428d01a840898ba51a6718a9fe839638b934f1b7971a3fe37cc02a89c657fd7f60553ef683d6ecbd044456d921d92e95d4c5ad8932ebbe10d408975f66e75b13bf582310d57f13105d8947d4e48882d7dce28c524a29638c11670a97bbe646d751805109ab9450a894914b3da27b1258e543f9fcb2f301d6d83918fbc26646f2a098935293e91f6709972b4ae95e7d0521a270c8f88c1118b61ee61553e2476670007d0105e7c0cc10ca8b012966537a703e918f5ef56e08f67ceef28744312487d60acc103cd0c2d74a4f5d00e4386c22e4cffcfb1153f57fef09f5ee6f6f9aa691cf2528930aa69ff14c0330c2144698a65a8475cc3e3e6dd5f6f2f1513b30ac09c576ac176d24875a6f879217ee4b9ea3368e6a26129771cb039511930f8fd8d4f8b00424c6132c5430eb9622076cea129923d9a3f8c8aa7964734286bd76f7ef086f9539ce87a51f70044ba4f8630f5f3efcc861ddbe7663c713a8ffd6f8b3d423da90533d7ae7f3c86684298c30c556034e38c5ad1ddae75da2d8d08bbdf9e582cdcb00d42a197e6d3a195ec0829efa6d3e6775eaee44011e2fc30b51c0db744aa4f42be0bb13c2e36578193a253c3e679504b5f99bf72c45bdcf407b23f7a6eb3e5759157c35cdfec48212e1e324d4fdadb1053627a526d3bfe7a1b00d9680406c8bc79d84c8f0de7b9d12254bf68a26785652fe952ce11f7811b492c2bd005e86cf59cd1550f8de2f8edf6f56b73875fafdd661d5007ebf9b70eac6ef374fab08f0fbddc3291bbfdf4f5895e3f73b0aa74cbfdf3fadaaf9fd06e2548ddf6f2aac2ac0efb7154ed1f8fd0e6a958edf6f214ecdf8fdc6c22ad4afccaf017ebf8b3825e3f77b0bab0ef0fb6dd447ad42c0ef3bcb99b06ac7efbb4e02dc09ab52bfef4fdca755aadf6c67347d402406ea986e0600ddcc4c07b560b07f7a00852e48423dd0228469c70748e899010097030b88cb8145657b80edd4fde778e213f6f69fe366466362c8909941a386c9c60deebbefbaeff41cf75cf7e178fe9cbaa51edfc11e1edc76c3650ae06c38f8ea8fa1fb260cdd0bdd67dac7c191ae905ceced9f38d21047d2c2debe0d8ef4626fdfc491b8705a8bbd7d1a9cd684bdfd199cd623c36932380d88d3acb0b71fc36942eced534ecba215692a4e3b6a9afd14a76d619afd04701a51d3ecefe0342c4cb38f004e0b3a00a751619a7d03701a8ad39e30cdbe0e4ee3699afd02709a0ed3ecd7701aab69f673702423a6d92700472a6a9afd0170a42c4cb34fc391849a66dfe3485698663ff35998ee9b1fddbaefb5eef32aaaf29fb2f98c85531ff6535786bd53d6d1c035a3b2cf2e4edd1862222818d57d2ee22dac62b21169e9a98b62b1154e6d152864a9fb1cd4aa5eed6793b11f4e7d994fdddfcf7c3875823e300a36855318104685539c82ba8fb9302b9c6214d49db20f547f92a01f746547381d006e5718f7f1ab6eadba1f3920ae52f705c02951b2243be2b59272f25f82802a442b2927effc55dd88bde5a95f7624031700ee60506644bf7e99bfeac2eea34b85bdec0802b1b70fbdd89badfb31f0d00fbab69fba0058522327009ce3096d673e7ed57e1e9df992874b5402e52752e12f078027d49344f835c2a036bca5f6f3164ec9f77e6d967a402b4a38cdbac9200832cc1da5a139c51fa462a5967acc1fefce9c29d3027d1b7461a67bf6fa73b0e7ede09784c22a29f377df35274171dbbd79a1eeef600fc21d2c58425e0ef6a012edb727318944ca489dc7218128f6e092c642b4da5ee3966c53ecacb4b721087bf07db0870327f8c649e9d73829d873a9c7ecb6090a3f86236c0802b3391db0454ff5ca838f205bece860c362b9a1ab0366acf37bb6f468d85ee7b373fbb0a9a424519bdc5753e7673fb98fa6cecebe01d4993177c4a662d84f200da8185714a7b01d2cc51ffe7c2deab7afec6b08d1ec49af91b89f29ba52dbf38250448aa854f83bb08ab3879f638d2a3c41fd76d41802d51a0889c33ee3fa39853d01aad82a5330aa6c434424a808a7becdc203a7aa4c014b859f837dec00dbab70cb10e8967ef0e4fab774034fee6bc66d1529bc2a97cd39272966393e9b1a2712d8e5c70ac108203a706a76275a6588a644a938326e071e046da907a8cb8328b450616976a61a50d30ae8e755f8455ab52b081fe38a703bc0af82ae1076762842048876972e15b2480c5305160c62270a3b2b25374c3464b418982f75da155eaed546822e2082cf8af44b78044106aed5e97356383e6705c3e7acfe7356dde7ac6cd4982123062d2979f9527712927de94b9d92d2679fb37ae17356317cce8afb9c55ce6abfe376098e33695028fc18388ffb2f5ee07070a82ef4e3d7eb05d71084ad96d1d18b3d7eb1e746157b21817e04b437b0f2bb0df6463e7f16a2ca4fd8e3fe99e63ed4dc7a6ea6769f2938849c88b23daed791f6f34511f5eb27cd8a3db429ec71048a3fcf13a56391cb81e2eb753464145f928b5e6c0dbf47f7865cb61dac2b5c5b7e98f04e8b75459525ead74fbae30fa7bee8457dd5299c621d13d425aef038d9b6137792008e3f3d8e0e081c7ffa3867fc79acd4235e9103290e903838de86e823e827c6ae0738030714d61ebc7eb185838b91cbbc686f606cb57e34aefc71a855710a512ce510c491177bd35de3ab5571e85539c630047d810138e24fabbce8856e9b669be6f923117b1363b0354540559401a4e1ffbf71e3e78badeee2cfd630571b54e8e745ce434d4ae06abbcf5463cb8615f4e3d76b6f56d59449bffad5a4b2084e1d10a71702ae4ea7ee147f57a7e7f71e5a63c842e3891b628397ba5700fdd41d88e080dee06c70afa5062763050adff4847ef107a855fcc5d2f03387e1aa3c82ca1f9b88ca40ec12e257a603fa404c7810d47120f843839bc1411e2450f832fc32f8618802ece1600c0e965894870d0aabc2180eba60100f7de1e050d3f0938ce0a645e8064e7ca2f84cf9f9b9a1c42d6cd2a567e54da0f09d09744b3574750224799df0b2220bc756dc012ae271c25ed429a27eb149e579e23d3d0ed4aaf8b38329b6ecac3c0bede2a7f26b6fe4de7021d2c21e3fd1162f2aa2c21ebbab7320f6f86100837eb1058de20bbef6a62bbfd6d21b4211da424bebb057a942d4abf8d2db8be051855cd8732bb1ee1536d0c2a499e5452d9d56b39af0ce4ee52dd5f0428d3f4ba05ffca9b0f4a3eb27822abfc4ed4aeb0d08fc5ec8ced5bb6fc9ae366ea32085240cad346e57923302dfdf091d25a854e9c9520f50d7950517d47e6d0d7f3cc5893dbf38152b3f1b716a5d5c742a3f1f754f4761d5328188b9706aab58b1c3af2fbaa2d87eb814a17e3d31e9409cfa62cbc5a9af5f419cfa9c4788535ffca95438c52d22eae7565af5f32b7588fae1a89951fca92cb457e800063552910cd4048d2d4e9dd8c55666165cb17b5d81414cd08f836ef07eed4dec4e34c81a3f762cb435fc469c622c5916faf5e3d756f78a185851bf6671166e827e3cc429ef4e4060779b0d481c13715116e8a6020ae58d1c8c5346da6e24712721a49c95c69d84684a66d3f86b9fb3da2d66d98bdd89867571a1739240af4e12e0df7eeb44407afe5dc1f7d7be243c597e7c589add0f9b1a5535beaac6c7691cc66123d9c7245cb3eef4237ee7035723f2d7c5854e95aa2ab97515fd548ddb1589db55e4785055f83be39299075a16148abf8683e3005665f2d7abf3f73f9b39290d92714998a6794801dd69c2284545285e90b781df6462ef701887530b21dc66d80961af83b0d75c15d42bbf0338f590fd0b66be2144bd3237f110324326eaee76ef6e6feff676ffa1113a8311c3d6ece7d4ad5b82e1051ca71b364c3568cc9091112386c2bc94368d944d4c466f2ef5e330dd6e7709cc61662d1af42c28bfc6dceefe12bb6ef729e95e62a0eda6f87d57d5fcb0dbdd87bb8421057cd7fbb77b994de0b367edc7aa91e6947feeee1b77abdd95800855a0dfd736cd78393835e4dffe93bdc781df886fecf57774c83db3ee7cf8717a924ed215be0b216ef799e0cf55650f29cc370db71d5f76303eec9c3d538c5d0f5e8f982a7c29c828a57c9798c4a4d7d825e10adfa9e0eef0c6468feefe53da9003ab57ffe07fcc70c297330ba1186394d284ea41a1e6464d419a97c327cfdea29412c3b028636904d24b3df8796e14a3a2273e0c2207b1d6374a29a59432c618db05ebf38aefdec88f9a8a42a171dc7a4bda6c6757d9fdd6c4e84fe4dd87d8b418639c73d2dea22d6fee5bd45bd1dca84de5b701428c5146982e3c89b076be3531e70a4e93f4113e3d57e201f17be80453ba54434ef59fd8b70f5cb1d7e6d6a51fde0cfb65ce0e4957ac93bda51b62e745c02eda421e0da1e47c602863e46f1fb8eb41c687ab23fe46eeea48baca6e3958614bc84c76aa9c1897b1883c077bf23df6e4a3d893b5853a28e3fa0af659d7575a8853d8cbff30ef6d4e5658eaf14215146ba12afda769a4730b63b0873fadea16c6e5c022b928afe47bd1ded474517d8b2a3f3641950febe90822dfb4e662d5b00de36690251ab0da8f7158d5b00dfbaef2fb8a8c51e5f79133d991df58a4785265073db3a4b0523fcf1b6b71ea93d9e1d487c3d4c33a9ce256932ad949953cf5c381956ee00f6c6a7bf91ed814cccb1712c406f940ac0afe348d7cf9436a7c3c173c13a623a809aafc60104172bda85236e99d22ec658dfa75508d26823c7a0ad4217f4aee2754f9f0a755dc6a9ad27f35de53f9301bc72d2c3d05f2682c56877ce923a8e93ecfa8fe162d1f05c2d6c82aaaecda4efab1871c0631d8b0bbe6614f76cb3aecc91df65ea874d0d6cc981c82f069177bf25fba06624f9686a0f2fb49fbc497dfadeca589ca2b4c2325ed1a4ba95bf6b6eeeb2c59117bab7133b0275fcb36c8e1d0c05867f30215f49ba9f2bf47710a56f95e0e4ec927717d853d99752dc49efcd975107bf284f51556f50f8bc5626225bf8338b5a9ead3c57a5bbf1a5a6896a5d95a7db02ccdf6c04ed54bd9631c576d7138e36417f49bdf030a7d28ad5fd1ee84fdae5887694ed8c38a75dc624f7e2605fd2ab520bf0eaaf26515f49b55be7c29492cc384b633d88a9df268ad2eedbaf3273d73c23d8a5b76ca9a7f383585819808bef0c354b054c873a542d65261f6ed9287531fb37a38f5b98ff4e1d4d747d2c9d17ef2898c52e1cf1406ba822ea74eeb59b765ddc47100a74a0fd9461e65599665fd5fd72c6866d9c3ecb3eca83a15fea1428ae591b1a00e9f2fc78c8f8d3ce29e8c057970142d9bd977f771fdf88759d079380ae4e14fa08eae87ab951fcc873d1e7e88e91cc9a3c95d0f70739e5651da499ea6c93a2ddb6612f4c37c6a14f6a0ff34cd13c8c3a5581df0a14f2ab606fec7f5f327153e73e73eb16e95574ffde4d12ce2d409e661253dfc8cfbdc0763b52a16613e58cf9be6cb2dc097612c0fd904eae0c2a923188eca14b54af2b46c0279482b5007fc37519e58d4a4d2d9c9a31eb3339966117b5b785a25839a069aa8d4acfbb028f06510d67d33ebac3319f452e567cfcccc2c5bf667533b6bc9035f5a813ca411d4c163a5c2ee301ff68eb6868b34823c6418ab630b5b035f4aa30a7f4615d4ffcb58af0a3356c6246bcd17a78c38d5c3fc24b36a0f7f1e712afb197984b57290fe93473d24a195df3bcf9342f2cad6609143328b0cc2ea80cf9c1cc2d640199471415f062d2adb81611b58689a0679eb66e6c85c12f443d5c9d0e173fd6085023b254b564b20f30d7c23d710429844728c2ee690c400a94020f67aa83affb7aa16ba8999b3ebc8f5bc5a2d14b95b684b0f35d1164e9dd61b4b6751a27ead056ed0d52ad872d5102bb4b235fe730594e10b06cd49a9c9e4cd23e88732d5ee866c8d7f0bb52a87a5f1f77642757f8d3179c56f5467c2aa77412c9cda2a5e04d50f6af988a840e425561550503fc8059311eef070cabf5d9c3aed7812f5834eaa0f55c7e16f62820e6995d734fede3494aae7a2f07fa65f9b09b49dd1b64077c3381a8333f22396ce561172e2a8a8bd198891a8f061aaf93b46977ac4d0b46d7ef940302b2c6931629adbd8830e85edb319323eb26464c9c822bda67dd6efdda576271f5bfd6bd8eadd8e2ffce7e7541c7e79e75e301b0f6774321d5c1a9c263dd6652f349094d841939d1da8e0ca125cb0cdd9bd5b317607444f44396a96cb0a52b297eb78f38f886d1a473f868342e349dd897f57349e76277e58e56fede7a0a02f5d13753dd459d8831f040dae7598a69535a15b321b77f2df1a3f5691385ec8c274c594202afbe7f0bc2a8a2a77a82aa8869580f0b0002654bad30f9727e8a9fe393db43139b05eec6dd53458f267ce077892c00b2780a2490f6b45e3a1d4f8053071558e46b784c6d378cd937005b20026aedabf0026aeda1d9425343a29fd52fc736870529a8b559bdb0cecd9b007677085aaf5e00ea70afd96b469dc565802224fd082dacf251c505cb06a7748d0fdaf5b9bc5e4c072c11765cfa120027af9b0a1499090201042f86a5513f58b6a41e1179b65b0772835a1f8053db65a2d085403048240104b8b080241a01a5d340d7914424643746832b443dfd0465be58ba37ac4a953bf026a8cc161f8f810b9b6e8d76b6f4e2ff4809a2a4c85df437bc3117161556fb12a08e44d2a7426b5bfa85f85232a08b151e6ddea8ceec0f8f30e4c0627c43a974fec7cfae19bbe7e5261d7722d68276169e067f5eb2157510f6d0d7c1c9e4103fa75ab5bada2f1f0db8855d5781853bf6ef5f7cede34511b310d7c58a36b2d760c7ddd1afaba15d3aeee83810f879e40bf6e56d011a8c0840aeb0c20aac1284ee2c62eb81971a9d1680b976ef08d4b409c61bf7c4aaad635769f076af3cbd51f0cfc276cc43615a029157e4701721e226a17bfac20d42e3ea27edde48b55f1503fec9d21232afc9e25885c0bfaf5d0107c085953a0c154bbe3d7d6c077223aeb56b9024f6d22f6e07f5ce3b724015fd590a93dd4aa1a73526a32fd7b5eb76874ddea1935a05a7f01f4858f0637025441ab58a155bd76516bab8071d45b6a0c81c2d20ddc8d6d5d02d23380e82cf558538cbf476e87726009a5f6b3528fd6e1428d5c8c0a4b3ff6bfb81bf483a31ab95861e986fdd8ed7a5e7b873dd82df6206b68ab7c81a5c25ac261bf3b243570edffe1b597d4f68d39fa16fd50d1e18fde2ff8e2d409b6a08e0a9af00e4c41d8f3b335f0fbd43dc4a958e1779626f2963761157ce26a2d9cda2a5db02afc36fa9ce53a15cae8dddae1d4077f2a7c58e448ebc06e52e1cf2c40e8f0676fbabfbb1fb6063e57f8700aa79430810f59ec4116e4610ffe0b3ef48bac0a3fb25a059df4ace0c756dc8952a114ab2f36893d3398013f803e82ec8dac39a0cd5718c0a0ce0c83f6861f3ebcc2a97df804d81af87c621f9cea0adf06f8413875a3c2ff01326115f41112c2a9adc245940ab554b85c2ae48fde28f8397038f511000671ea94e2d60c15ae950ab9637803e7e0d4c9042b8ebd81b50a196d3f75dfb4022a43ab0200b323e8fcd951c685c422f990a6b0caa687c464a82ea949dd8fd4438a5217be738a531ff422b238f57193d3dcdcc888fa3997ba91c984dd476ad5a5375841492d4ef1d4ec8853cc5c482c66c9bc8cd88347ecedcbb05d3c3172ad92a0ff302441bdb731711fac375ef317cddffcab5537bf34de0de7b9ab69b8de781cb6e118688f6cd8f8ccc6bb8d8fcf47a7c7f1f088536e63834279debfc9c4dd112cd530bfbbcf6117b926f4c3a2d47dd3e9e38fbfbef9d1cff4fde7d9645bd85ba34affc37e6a76d4d5a031a3fb4c2fd37defc9e83e0f15a3cb7c62bacf86494033a0de65af0c003a14fe9715d57d2c0aa7e00d6e2b89fb3ca8eed313f7ad95e73e3efad68a0d1a6607b940f11e87e1ab55cc3c5e0c4ad02f3b621ee8a2d014bbece8e326fbabf116006e573e30c110786ab4a005364db30f03125ef72686ffe6c2f01fdd176c1efbf5d77a93813b09f1f6a80a32385a799fb3dad21d11b023051d1e2c75ffe38fb7442f728483ea26e9ee7878310fc31d51b8c2cc5354c4a9d3101d38d126caf8691f3633146193cdc6c0c23b2f226e93a90f6fcae8db05fd3628898fe5d2926a320618b36930193d749bbbbb79316e3eca6b150f41a8cfce07c6f14a060a283f377bd9cb975363f8e285080aff232a0ac2362818803ad355e66e69fc913932fb47766f006b414a2e5b08b9bb7f6fe1eededdc61d68ee1ee72faf296851281b5e158a72499886872a502f6d51108c319cc561140ac53242deeef80298232d998f6188a828089c1886cd396d5c41a8aeeca93999c79cdd3d7fd99bf3677f4fd39c1deca67ceadd67a23d9f605be4a62ab05ed32c033090753f96a6bf3faebe593706d45ffeb7434578f801b2b6ca16a6d4fe21adca59f5ac3a4ca8d72fc9f3421e2fac4a92ac853c26abee7329763fe277334da8425b06d21a5fcec7e128a1b47e356798a6df25778a0feb16e1d82113185a0f4966c57ee1ea805ca3f7472cc6f81873d862bb3bb1b918ff7cf66214babfdf8500eababa7083ba6dc951d7d545a7d22c51c618e30fa2b020fe0c630140a2928ac05cdc0750d7d58527d497baae30ac34c32e30c182164840794161042618c8034b851e72aad0b245123b34d882cb166ae081ed2183c15a931ee7cb462aedd30d81173e3a7861d1d30323aaf0000d586083214461052bf0a4000c29fead28d2c41456149e8085a585a7987eae94d27fd0050c301da7262a432915b02c91456271052be850da052ba84c951c0c5552dd2a2e29ea47a1cc00084940c2105d24618515fcacf81f85c222830758698214a0d0a0084f5c8941fd4c0c797a6044142bb4c0e4f50fc42a28383822092944a1042ef8c7f46f031858e0678b1457086309415491c50b725084235eb005125842f8008b140844c10509b478a9c0083ac8224a9421a8c064052c7801152014094e4c38a009275842093818421255a860c21646208494524a29603efe1447909ad0993e9b18429e40081a26a8e28a294498011458780a200d8b2634a1e352d4efb5d0821cb55d5a5842d69a257091832368e1043d2862d5388c9deabac2e84295755d611ccd1bf8a854b0042030907022075380d2802d88a2f841124f68190285ca228a1ca86c3982082bb610644416cf62b148f099989ffedb000a24559ad0320250115c6042164f88f84901128ae0420411522c9607fc60092ac842062e2eac7041e5a75f8427b60a472062a2488a249a5471e2e708294d3401b40510b0f4e4e04089297494782541c78a19c4ced4b680051e37f104284d749c3cf1411195d5ee20082c28a83882111257acf8b7c4a88ae5ab2b8c292ada335b80bb40a89f8c8e23258e6a503d0c1254f8cd50fd7795850549e86942ca162656fe9453eca4faa338e5bf83532fd5dfc60141f6065688048450b52be99cca51631735da38827ef4a12b0b46a834aa3f34716ab759c56f9dad12418c1bc4bf83b14e8a11f8fdb25392b392bfa520b23bed6a36cd0ef933d511e4c386203f4026426c56cb16ed6bb978a71835838d0e14ff0eeeee6462e61d6c89ddc19c3333c78da61a5136ecb54692dc45d86bced1348d0477b0581a89599cfab6cb163169e7540ff293cc9ab5e64ac6afb79261b392f15f8d19646c1a89967e3f07ccc7bc36a4fecec29d1dffbc36fd98d2cb520fda49622d7d978080a04b972e433576473ccac1ca9da53217a4f4d2750f51c372f6c6bd1669950d4cd3df3ed8b381038fc4b3714170d89afe1b56c79459263bc8ca02876a7f7f6bdf0f593e3f7b8d29cbc77ce85a9adfac5a0972562188f19a73d833a77d89db3824b08b977a68a46f9246fa56890008192f045cc950c9e84e41627c8c4e095c8940c6c7f89c553faa39c80daba37f72386c8db63725ac8b9c0d5bd36d037b1d3b0f870e6850ef7c49b8fa3369776ee0514046272546b7a52031bad3aefabbd44352c8da9ae6536cd2ae2bfdd0497774f611c48686acbdd9da5880426d1c4c38d8eb2cec7cb0c7ff896098860b34fa3bb393a47d64f833c31e435485306633750c8704da4882d6a8ebc2821335d62d4152851c3fbc02856010e5e01574296dda7b85091a407168b66da651ce14d331313486b6c9b4850d7605dcc10a0ae2d407afa87c8581f415f0676e948353ba586db8a810540254c9ec4d12f686fefef73764fc500c380489d81a7e22b008085ba43eaaddb75da01c0cda1a6ef8a7de2c5b8b534df8a196161f5566ee85bc44508761e987d7207ecac54a847f074e1580e115508769b8053b4839ae366e403f1311e80787b4186159960cfbd47cc6985583f636ad8af1a55f2bf205e6b71352408506a87088a6428f63e8da80705543fbfcdbb4fba829497ce1bef764fc6743e6bf18d58cffb25a7ab82a1a5f2a3d8d199c0c27835b4e3ee44affc221698097b8e592b07fc376137bd0bd189d088088f142c0558c183f572d444a975363e4ac62fc6c1ab601fdca8d031aa3c7c6178ed5102b7bf7ecb9e7413813c01ac3e210167b081288e8e0f13b76dddd9d65bf599665998c09676bfcb353961539e2cf03a7369e04fc246152dd1d8baec7c14cd46fdb1b2ed62bdca0faebc0a902547fcd27b0375185f21d5be35e6de480fa0f69d59c1b58ff22fb45f50dc3d4aa97edfd5d1301102faf9562d7dc02125053240075863f1b185c5591a629e2382de793beaf3375b351b798ba65757bb8aa18bf6d1f83dba5217d0c4782e14893f4cd6d5fe2e6f7c675fc2dcde79ecf73766d92b942bfc8114fd235fea08453f3b90577f885dba669f662077f484c89b4dfd83372423a65339048ef836866a872a551f9718208e941e5d78155db05178bc51ac1ba8c822a3f2acb814525f56d91f30a2a2f1121b74d235292341287636b60eb57da2fabb49fbf714a3edc39c334d98e2f28c923751e7b9c7133f3833a7eb9124e4132d2d3b9fbd8cc3aec5b7345923135daccd5fc96cd2afba1f6cb55863d4e3787c4541b07ac7a00a63a56bb33e2b4e58300f34e02dda50bf74bec1b5bb9ecdc5aa0824c38bc1c54a0eb7a15d5cff41aaadf34e1f0729c82ccdf2a84592bed69abb2e79f1a6ccdd57c12c744089bd5fcd65c69118ad691709a94713cd4f8b9bbbb8c08a85b02b253a8db4139f92b59d70a04f34190a5dc9bfffdbb20d97b17815d651d4b8e34d0b65535bad082b5da1b1ac4a11f6a6dd29a33e2b4d4cfdce9477c7eee3ed819e13f75e7fd3935a7f6e9c7e7d4e6706023fdd388bf372cf1b78c43ee02ef6abbcb3c789779975b08389f5f6bcd5586ed66dd94dcf743ed561a49c2d565d4947b330fde8575ee4299b3dba6e95d02ed82aecb8ba2fa4d2f788884306b158d78d1f28275f28f8fbde44eded1b08a5dcb8651edf9afe263bf2dd32a621f39b92dba8a0f4b3460753b1ffacbea72adb98abf252002a8db4101417c1cf6eed285b74b975594400356b1dba659f6b6b9abdc605b9bbc49595e02b72ea6c60ed4fe41a31663cf6de376353921ae7570131081f0ea2708947efb21a56efb21fb3bb650247b94fd16d59c55ce8a3d761fd43826da538ec9f6dc0e3028923daa497ad70092869cdabb8ace958020a062afc94deb6f58fda7c64969d9dddd5e7ac93a1fb8627362327af747d78eb6ad9282fd6e15e209705f2190c562b1e0d27856fd03952727c5dddd7d484b0a129b6a2449d7f95ecd59fde84702bb544e7ec631c95e0829486c6a9236407ce64a35e464dcaebc6309888c2a716858fbb18c93e20363dd3de3cbec7c607f8c1d092bda72ca35081debe07a1108efef770e562d629c0f2cb1f7d681d6bdb1c7ee8ddda8f2b1caed33f6e9c7c23af906d8a57a27a10efeb81a81ff4988c9ae86af2cf706f26824904beddaa51fdeddd6f0c76f23b1ab61ab07b1da7a80eed009ac5446d25db854f88ca4bb7479e903c3ae078c75c0eef3973fbccf5f7e977050e2f18708620948a56fa8442c98ca9a5432442410000000026315000020140a068482f1804cd223dd0714000c7eac486a4a170985610e04318a216388210400001000011019a26d003f377ab5bb613e6773c7b7ad172515035068d34b4714ff6b36f4da4ef25b18a866ebec8c5ed5ae3710351334a1fa1a35d8d8b174c1cb07a3cddcf370caa0a4f18ca71e5e99be454e27a5bbd3b4d9de8b10fe01d9231f0e709c8260969bde5826edd42652b55b6554c5c67c4d7150b093502931c43656081818b8e4e561203835855b6ba3580ab49429c045171e75af299afe39e2b17855625631fe2efcfd492ec320efbd472ac454ac0d48a413ca6d17ac683863bc003292ece4136a8965134b23c12d9811a31c2595432145f489df68ed5a99f95d0190fd1c40642dfdc45792c4d0df4a89242691056950718080448f40d6cd63c0ea76833a10ac5466b185d102a1a4e7355839cb5ebea1d23bd392a94185e4cefe1e6720f9f29400cd831ec228dea517aaa5eaa7fd2751af49dde8252993126f0f9581b70a8d136fe121d0b971d95314e17ec59010fac9bb7099e2554d4da0ea396844a78753a088b156a13c35b0eccd07f5e52c5748f32479dc65943ed57b9e024b7540fa8ab3b360f07ad38f0b4be0d40889f2839aa56db96b347e05a1a45117722763bb0b7ee74d12c4ae40f590b045b8f6bde26caaf3b7f07379cd3891ed63cd382985e53184e3302c1af07ae6302ccb3d0162060a79104d50032e374709d3b2352aa940126a124d79d880190b85fad77cbe4de686e74b00e1da5deb53274074d1fa9f4eff58fe56dfe05ed1b5a4a9dc50998fa3b8bc46dca3da9ac6d4d5e4878a5ce0b424aedccf5ed6175071495bab00615c6cdcc45b2bc936a77e46aec17f6a6741608de59c18565aaddf8f9a98592c3e6b9420226b4510ed8b4a4a44a2c55822d87c10f7293f21a2a941620984b42b51d2a423d1325f18d98a3fb7ecfd3cf49b48e4b259d678e7efbc6f2b21d87ba3151f0b7b8d3b16a8fb5bd8a3de6f575b6f462d367ddb2d0d6d04dfa37731b791b020fce10a45e8bb872140ff05f1fe61eda5fdf7a7cab18fe24cfcadb5fd4971c8d79274cd9c019e3c3c79af8af682a2ea5bc6855f9267ffff6699908005ebd5250ce40f72aa81d73cf979c417a32fa676b7e582309174706aa7ba44be174219de7c7521a5fa8e07609ddd62350b69e245fcb8f7506f9c9f803b330f2be207b12576284b970715b935c7925b216a1fc0e73f6124472a400b158805e52178a5c404a1d65c916f563415684f90bf0fa7309fe49d7c9eba8e64cb5b9784778059d16543dd218e5fd94730d675b81bfe103fef107f1bf9ae58375e39248ecf4625eca9d31ecbc31dff20b894cbdf73e49911aebcb04e34fa05dd270cf385476397d9e7c91acb81c6fc04bfac104d93f05f0f053c667b47348c0523852215fd47c6913073ddc5efb8378a65e143b861c687e438d9cccb30a89f7072af4b2aa5199301ff23dba48c45f6ba662b9a912fbd49533e14de2da404e36389f052a431bf84983ee5c586e2c73345554642e1ca39a4d0220162bd7e23a0b0e6cf0b1f051b8b9c8c9c11da9a91742d3b660994fc5a78fc09fdf2354dc9570ca1f3a798d40b861bc03d4916845d32063d460b76ec4e5674535bb53b1631522eb46873e13419d20412c24cd02ea3b0be9780bb0dd7ad4a4146a9cf9bea45012b4c3d643de2613dab88ed250903698938a8acc6c28cd685da380aef9d36c0f5a0c1815936a268d3e41957e15804be307f9ab066f5d8ef7701c57a22b946c337bfc63cabd55d7e2f1fab31951d9d8aefaa2cc918f74e3519dfba80947184987f5f22e05321df6a8ba96ba6edbaed4521888cb65a410f893c97482b2ed783d89a103583856d1e6108849072e3036ef39f6e76fe4d3a4b6111fa7badec21f45caf9efc9fb8331219df48783a862be17c2466eba2d3679f8e1fa728e84812dcdeb1c4e0d023e7f32e1f0768fbabe1fc896184363d9f314006a945ff67042a23813cc6b53ffaaafeb0f26480e5458c774b0a493fc8f8ead0774708975ca0d9a4069fb21354fc8037cb3c8849c2fa42c7157b89286b2da1f412556e66475199af4e450d6169a8cd68e07a65ffdd4c2857be10f02df592be35e5275857609e02980a7cf32b77ef9dfee5e6ebc6e5c675d3edc675d3f5e6f5e675c3fdc6cd6eb8ff19b99fbff927c4c5f109072ff7473bdd16df1df5eb8de6a5ac0df4edb08ca5579d79dda5ffe12a1d1018cf8bbe172168e0a7bbed5e2abc6d54fe26a8529266ba3c2477e67a6045d4e2ab5ef98173209562a4e71023df1a619548448218499e589ed90975f1c6d5aa394337c2e32f214e547d71aeeef764a028aae6798cce850b3f839ccee4ec37a4be16eb11e22033e67587b863675e130318f1d1602a414f824075f8357b151b85f110723038cb02bd8b462c65d0621e7993f4635267c8ebc5068d3df1cbb41e9b848967f232ed17db6dec092ff3151bc2658e3dfad7841f8a1e56c5e60c4d19dc88b4054a00ece8b87ac0793ebc1000eec281cf8b27615aa01f25e3afd0b8f7ce03c7ef8dd00898ad28e62dcdb11b408e27de71f3321341d9a8deb9f2f2db0471ca006148ec19054ee8302e35d9378e072dfcfd44e840dc61fa2550b52af66a96937c8a8cc5fe737f43c1a2f6591404c2729e645b9487e70efa7ebd05be6287de044badf7666c4ed4feb51193f42bb9613f79e293a96b204aa383eb9f941bed3347eef13eb25af4434358111b3096092ed31eeb06334504a4edf4dbe4ba59e782fc56495355beb7c5d6d9440b7a6e9489855ecce444c882fbbde221ce00c275921c6f93d8ef43ae1d47815c49201edf2aa880b29edd166c1748b11354ac7b365c6a14749454893af162fa8b78e3ab25bbca0ad69dcf3c3ab34d3a48ae6b4bf76e97a6a982c4b8449ac10c405c6cf077dd54940365253560b7ee4c4b4475403c65d28c2c759c53d7097235521fe5f5a8e9429965a43a6f548f83ca70427f05c3a35bed4bf1b4a95da7265b2513beee10942f2ca95c8daa5c073e6030fcbb60f5cff4cb003d2df06405e2d2655def0b0a1a7243423c409eeeb5d22a0f4cd2714d87459b6095b510db21feb7b7b4715c9b615d43bfa65f89bf941b27c80b1f41b48611daa2b11bda36c5bf3224ed2a4ceb1a377d3129b3af4a492815c298e734fd96cd5c056a1ccdb93527c65e4d47f11ac058d550140ca90eb39517638fddaace56822f30768faae5237e1f55c3d08f3a6321bcb90f8a599caf72c97404484e985f8f28e3c813507c10d42ba427ab83cdb76e86856260ed86b23e5a13cfe74041450f6013d516cb0aea6afa82aae6d1ecd1cfaad36d8a85acf24419c50e63da3ec8ee934cc4dd000e177a4df31d369901ab7670dfbd494b27a16dc6c8e8d77f585cfbf0c45bc9c8dc86a4584bbc24c1385289310d0e0cad3efd8a1a862d8b06074838b6a9b52dab0827a2cb60646f16cb4323da13615342c9cbfc866646e513f53a5b4101d92eb63cc811e198a2b76b99d21454e32f02d8a06c56446e4f90783f0d72ad3ca1f7eeb8e6ccfc8361495f79a53e1077d316e7dd8758c5d582199002e666a8a5e5c64aec2e37384615abe9c52a8eb4e915455144c7949fa9b7a7e69d81b275aef28d32a69e4cd4bc11e196cbeec649eb0838cad87536708627419d5799538c0924276af4e10eb867dd6448fcece16ec464d78856b2b40a8b2cb5745685d2544bad13666b4230d19cb5ab9ce9bfabd2a3e7f6df82b02e744bd312434e1c26c68a79f5a8d9c56eeac28360985291f9b020d4a4f30fe1c1da0668eee333f91d4208996271b57e39e7ac239b24d92f61561ee335007e08b3ffb5be396983ea0ed3dfc4f24cf8d3cf8a8f4a662870e057ce0924d031c1ba98bdbbca5dbdb0478352e6478089d96adfb243cd5adb2af412ffa8e85a7cc60d3bf0ed0fed6ebb638efa405bb183551b35f27da1abf6d5864845270eb8c284e1510f8cd8a8212aa13780eca6db6a02a406ccb9c8b7322c2779828efa72238792930b88594e3f7b14bde0bb007708869dc115327065423b7671e35380a8476a70b0c4ac28dc9ae62469a98a29aa88658c1f1fb472ea88de83eee85c72b295165d8922a560e249541274f441cea02cb6b885664052e1022fe209759114609260e06bbe326c2e73e725dc281136b5cf0417abfb3451ae9d83dd52657041ad6e7f70efc4b97ca602623581181f991d06e3e3fa25803a78d12acef6caaf1607127900b7225dc81e81cbf566ab1d005735278e026e041f3d10613981aef008fc75bc277bf243cb94f7af030530431d817a0bf7132e252fb3b6c64c6b5ad53b29290a38096823f22a23961fa0289add9851277f335edb08e90ce950575c2178dff053d927456ddb8c2ef58e68148685b7c4a07a1e429f28a71df96ab8b68d294411dd33ff1137600ab2399b1a2b9a9901fbccaa97fe886263dd8b4ddc7115220416d52dae0c3c04f9388ecca5e76ad5c84d09f60cade4ef45443733f0bcc55e1cf9609b4ac71636e7afcd59e03c36a9e2fe72e2de3108d2f1b75e8556ccf2218a9739721968add49eade57e3ebc15328cd1332c0f0c0e3de997b853c3a6b134da6865fdc8867a7186017dd812a1cb9026f321e49c27f409b26906afb980c5d95ad2042394bb1db3a7a094b4ff2eab417998f6eb7ebbe7bae9369b7521b6c87cd9a666c302ba0e4deea8a6904a0fb8a9b8f920420871230a3a4eb5e4a9282855b3b15f7543117832439fc1e7aaa3166c2bc45db3877a3b80569c606268bf6327093146a0e035134298f03c8edd736412c2ddfea52cb484618163e0b47848662586d1f4e19a6d17da0a561a68af1cf04c62c8760cce06f41f09ea0496fc186c4512cb54d265344864f81c47d58c043438485835d31b1526da80b46a3bda075df8616580db1c5eb11d701de4f37e86a8f11eb472ce4ccedb8884f038e617ccc486b6b4b34d7f39670a0ec2a013ec0b99aee1c2d473e2b1922d5b8deeb2cb9b359ab4ffd1293e64702c53a927288d6ccf579387a48622df94ad0e4540b3c0a4b5007de1ddb3d64caa4bc2bd52c75c9f901a973a0a41850961e52616f6aa27e4cf08c205b20704fa84588c891be84226479e1ac6411715d6dac3c00855e37e8633963f0fc291608a95132c856caa8b9b5a6ec7d62a15ac6fb4fea54299475597523ae48917c093ca6ed24d70138c4e89a67e8df9bcc7b7998fcf8f6c32a4f1d5baeeb2ac984c382b70d3d1c0a154c5dc28d60c2adbc70a01881e7d67c980f2e336c783399058070b2209bd4afd09e929226780fd5baa2ef7ff464031b05824a8ac42e889e85d9e6212732f4c3f30d3d8701ab37ad231a655a1ae023728a303557d5e6e98b68bd7da95b7b15e9ffb6b4de2daade39c9abcbc619885ece1414cb595c3bb9132b41e142c87403adfa706f0daef98af834507c9c0628962ab657a01d6e07e5aa1365d2e248eb7cf47b0a5cb42cc2b3c32f91000102ed721b51bf1602e7cd3d5a4f6606c96b6f3aa1745aa0b1a8ec0285e46f0a01d91c846d82b83100475fa9ed12250e13641b51128e5a69e7e123fea07aeeadb0d1627483797071053b81628c8bb42d0f5bc20cba0c0df9e844854704a116c3714381c7478f64e1daf2838d8fe7fcbb629cbeb2c21c3e060d1d39baf60068327c9284575ec96a8401c4791cc48ca3df2c1a1b421645ace5c1f66139222dce0adad47cea0174ecc3a374aa3d1f2b5e3e8b48748d85e3a940d7352a805bd7c9591e549b6a6330f31d851531a3f2780ea1729b0bb145e920610369e00446da2a428912e1b8509118fb39029bde68cf02093b047533bdf8d114c2ccb956ea7a1b90b6d63684be84d37273b9f06e65553e8ef6db3d2bcdb4e2bfc126edbb54aba3015dbc3e4227b96648af98671ed6d286f375d6dce40cccd60d6b2c34f910ba84dd22aae682a23a4676b7e34f3f9e0f347301ff35be4b1808765246aee8d0193030570510c2d16471e8620985b52f82282c0422a2d603a3d387599e1c247324cd74b6ddc7ca1b1ecb890a1c857454728a2b0474d72a7bb5163fa5bce701076dda943d32e8bea413af0e13253de6aa3fe9ec13016f30b828bf3be7fbcffdf3b36ef11b2a2bf4c8f95fa36aec40f96d5ee2085578b8008530dc2dfe6fa573f21702dcb2686a64fef40160e8738c1810b2ad7c298d37eb0a3953985f0af10e08d9ae67041d50d800c38028a0e38c7b9aad57b24752a67db9f771b5a959570e2efc39c8f900a6290d66f26cab4d3bb540d069ddb31bbb7e96d10cadf7dfd9337b95f65e006f0dbc8f732293c510cb8530fcd93e15e81564ea390a80ac2876b75f24e3ffbc217dd7d05d35bdaa11bc619e7734082034c7c485d2d4041b9610eb221aed05182242cde059461c3c0b818d4a0c03c83bbf864ddfd745f9a937ed2936394287350b4ccac57c6601b65ecfdf0a81c70e6e285dbfa38b2c47654621f002f12c1b33a5d4e23b45da467eeb6578f86f6840cda781b221c3f18b97ee9a2dcfef2a9c7fe16ede06004d067436990090c2585ececc6ee86964e02dec5de68eb3c64cbd016e25746bc18c491188c1d2c7fa4115bb05d1c8285ca51bf7090fe68ac50090f3a140ae70d88afd2d085b8649d146923f1facaa537badbed807b80d662ba08b0b728dd9f2e1193419b1a45bc6bcbd2418c07ae1b037c0b225487c7db855d19d05c4dcbd3537765cba5410df7b2fd08e030a4ac17b207bbd4cb9dfd6baf0e3d31db8918784055bbc3bf077316ea1dbabf74c831e5f7518556550d3e372ac08b28d3082ac26f5b145face5a8253143239e8c214cee939005d1eaf77063aa72b7e1bb6f656eefa5ab54d451f953ed61f788461d8cd50279074d341d1691668feb7ca4858e9d6c907e4e2401c4dc29077416014ac9f1345935b8b59beea17ea7d924471b9efd8c03971ae0d9bd40c663029954e9068f3974208b688caac6b1e90db97312e01d8d8cb97073cce05fcf85ac5fcbe831613a58299f07d9869d42bb3c84f0f448a1d9ea9850a457d590353d8244d423ea95e07fb494530d5ffd538fa03b85484d939dc0d59238cbc4726f7139d3ab699bd6a0aa99ba728aa1226a4235e6c09f038e7d3d3d28ca16ce021a2fbfd85c90e66281bcd2124808e92d5f1f9d46baa3577d93e58ebda38671baa9a212651f8bfc72b61bfc85b80da9f4d6aed850fbe6512c3b1740236bac56463a497df357cace294ae31493ccf5915e9d6313048da035c809e8285a23b711052196a82eca830585de3fc564e83cc0a965dc1b25cc0432e1db47f6d89c0969505151d5bfdfaf4cf02382dec9ea91e409b6411a26b5940a1bafc3da0c14b974a5ae1ba534a5afd108e6341f8f4f86bbabd07300007223fefeef8543fbc207342018e957b88802d6ea17b398ac4cb5a05133f6eab839537f6b0c89133f3a114aecab88bb776e409612419f09dea656c16b5f4125a3cca32a2c0938707e3663d6b8b2c0a982598d9b4c8b30ac2fcf5a71f858c0f121e871a09f8b2b259ddb6219cee464432e93b2143d405bbb5f30dabfb20260b2bad0be4cd45edd8979dc1905b57f50652ccb16ebf50ce68ccacd66f35816138e06406d5a45cfd67d1ff2251c29fb378f11410dcfa5d39886f3d6d2e13838bbbec5936ad912c562945812d4a02926e50fe89ccc29001572fdd5a0bc87c1213e829ac54a8bd5e39294e71e696941c1678698f5c9eb4bbd0c5b3dd834b54bd629eba7d45808c983b1f6556570f3d2ddde0860306105f3ef18b52632fce3f7d8511cd4d129180758890113d29c1f46e3910a1b7fffc8478a7e15a0c8a86510861614af5a86c66240533f6e2f5e79a997bbc05bb70fe5c69bc5c0dbb1242b2018f684a596e6928496b605db8962f91ad2748132247db5855f27989e869b4781c37df347cef335cd61ce99393e9c78dbde19e55099cd48251574931e615d6af68fb1830e742b6141c7347d0fe89f03866d4665cec8e188333df6a8168510f52a929de4180b4dbe97d2302d4dc733d3d057a0d44200eaa8562a464d429005cb804e00d4154df4a7e7572cd8c56c6f35b860416d80f4b164945f3551ee171b0e922ee3dd3746e77432c27457e49c9baf2c463c4eaa5fc1b2e55e84142773acf6cceb70becd25377050d1901fe5dc4f82777651ca2671a0994457372a88617f47e896365fb7d0d2630ef70d250fd8ad269a1d1b8d615d45b0e7947953867fbb8f82093ecc07501ae06f0699fd383e57d23427fda748868d46831683a375a0f2d522b6bb988dd8e5693d693882cad11b7fe74397496c79b7cfacce01232020fa2b13ff0b4017aebb24b37d2cae2ba2c8680ad70684ccc01923369eb6c08b2ccf3050a85baee3b5c7657c13bcbe4df0720766037f093ea6f3a745f7031b3367d24ac6f694e34c533c47838623335f49ad5a5f7b85e1980283841adb9be0a4b930354214391e796c051b3523d11987dbf4ddcc3aab83bc818191c76278c5e9468d01a3e9c3e3d08526d9c53b39e550b80abe04bc2193989f124ca73b3e4d373c7672e2df8d45a78c85608d61c02f81bfde5afab9fb845ddfb173d0e7c7719f65b89e3864524ed14e54887da35e192aa967a49e8712947449ccd465497a1e3f8e5aa4a56122385703260e781dc0e123be0688fcd2b4f655523941ab242aad40c461dc386c160a8ccf39ecc017c52d19ed38d1772b5c70d74e2a53232b020d75c6a0bdcec0d2a7e97114ff66f49aa358b8ab63a4eb451d5230351e20ebcfe4c6470b9825caff1e82b2d7fed3771902c1fbeb6dc2840f31e93b7bb44c18ad3244d152521210510b6e3d507eabfcc8f1169b1c503631c8d29d95fcff95221dc492a06a5d8f55decefef653e219e8d2d9c655507fe0de978befb3b694ecd660db1927c5460fcfea93eab27cbcbee0521ccf339dacac74f9323b149e9e50eba2bbd4e6d6f21c54e2fb489236e3266a0b0db1f27171b70148cf8404324384b17013f43c45f32690e92d3f36d24a32aadf180d936ce5e22a2f52ee46923b44be6ef6fa2e25bc257dc63d0b5ea5d6b7f54ca1e233f12d5a6c3dfac15e865f0aaa33e5d4b3cd44b62b3621c592e312da4c3884468c861b7e9ef5cf32d613a888320b8d506ebd336f404b7532da2bf93ecef13402278d1ab0c3cc38e2faf85a00508c675fc1dda1d407af9c9c9f0cbd45db155167fddc2a4fc702f7dc45e1205759e50691d2bb31c9c6ee900b63ae15f43c32c99c3d7843606f724374933b22405aa81700fabec8d034a6453f305f68eecb3883a579d90febc625194b9a84034fb72a95386aaa662e6b2b1e6353d5e3416ce3ca67e7463845179c43f633199ac4404ccef2ad050cfc2b1c575b368e14d928a0759f34664f3d078e56b17a027f3a62e19cfca5bdc81e5eb0cd7f73aca6a40adc8c45fd6d3ccbdf78362ca2f9e8e2f07e39b571dadfe9b08a959def576d7d7036b8421d45ddf888e6a0cc384bc57126488d1ec6779d9469f4494e60d4467c2e12223a7108ec564e2925b9e8f5c91119c4715a42035b4291474eed855cb294d69f232029db6b03b1a1751d34b038dbf934eb81d46c078fcb474516e9e04e72c4979478a2fb8697715fb18114e0e0320c3bc5e79835ebee5fb3468706b1d93d96fa2a3373ac7e5e28182740483bf5918405222e13641475310e63ac60295206a11fa141d1014101a4fbd0565e8845760fe424294b458a66670dc144aceae853a3009b12770926346606fa01cfd1422cd536bd7a34849d6d331d7370dd0adc365ad720063013bbf393dbc3abab4986280821944fd81837a65850e83eeae9c9e52c5fe403eafebc83775203a14a3d0d351cd0f0b40f6a9bc8340168dec8e6aa8b588885f687a1758a2a894ee7f7e5125d522cbb5f0389764815f3da7467e0e88f3c03ec8d2e15bb01ff53e5fbc22fb4211fc0dbc0472aaa8cee97bafc190b33b3cd1ed1bb281cf6c19c94617c5f496752448656e1abf3e37c3946a25845c2efdc8efe6df563c24334103132586c1aa7627fd95707308f33a693e7849698804bc99b6b0874b5e8ef94414c3320406021455faed68f05376b6290dd288fd50227966070afad681cfbe241ac50155ba00279adfe2a6d842f14c6fe54115efdcd503a264cccfc7dc9095ae909fdbbff6bfb881679b9e3080c4aecffbfcc043d7c2d731a652f60737a209361500c02e0099f2e9befd39d5eabba98626baa854beca1d51cdde819368b67195d12a0c3e7f0bb7df218d6ec85ef0757e764b87d00674148dda870d6e5cbc4d1476b30890f74687b7f64378da481c37183347c4143b0daa1aa4c6cbe35a98d9d61175204c73603e6814dc6cd00953b0f10ce6a5b3cbbba2e703c9fa0879297458793d95ca1575e03ff265f9a9b569383dc5e94df14ba7204f34aa86e51c00df341bc3a8eb57df6b135f16b01bac7da6ca265af0e8ef8e53d2f69dd9cc1b84d0a1337a8cf0901768be4d5a23a82deff1a7f68866d9280d7df16417bfca2f30ce5b117c81c7a2b2cf789ef2372465e31a9524fa965c0b0a2da0c325d0f69720d25bd49038ab1cee418a6e2d4b8a129f9dae8d07e64adaf9cea2485f477f03d72609bb99b0ec600af2a1e0a11946d18910f495872d1fbe0e3090271c03a9e74095da90234ba249616044c3d1038eaa3183a8aa9a8c84d3b673c7d840476830e244d1b19e6fdbd6c05bcf336eafa2128072b3d97dd345061b144c4bbcdafb0b16db59a2bfab1b847ea40d183c1bd7730787bc1f91db15e7b758a966992baa6837608eed4519ba45a1b611564788fce29e0f88d5c11a26746c6ee0a3e3327efd8a31774adafed080d4a49f3b0164903009f97ab9e532770ad0cef7beffcb607216eea8026fb497d6a48e4c0d8706282abeacd1a58bd876d39e5ad8b54e78a5f28881af27653c3539fd06f6d0d66463bd557441440469332b0272793e782eff237c73575fab3a030bfa284526d95d89c1b9c041ff0b68113ba713e191e2a40aaed97bfe44420a1b0954480d8fd5db81aa31e003afc0c292984250ed599b2df105124eb7afef43045f617c544424b07f241d151509992b6f19c09479a5c92185165bc595bc5a2e02b72bdec2eaa386667911b5a54d2c1f9e75886ecda5cc1a5af1ff3203c863f98a20f342da55da62b01ebdc48c93d3cc5b72171df8fcb62b4bcdd533d90d493b07ad2428261984a075387f702adea3ff1446183ca1bd706d870f82d6fdb22c8ef905a7cf0d34ff57570eb262cfa2e6255c331b4028c215f1eef3de545e069eca52d5a3725b57091296aae4b1ffc87987b28b2e591d4717015e6319c4182193e4269ec515993a83aba7c21b935190e56dcbe33aec6d6b501384c590fd7524464c370043c2becd636f574dc18d26ae1c27909b428be71190859d94ba77d6d64151825280178286b0fefe7f6a5cd8af505d0ba29c05bea2556acb0fde62878136fbd99d26e290816ca8afa03477d6be4b0eb4112c5bb7dfdcf962828bee15ebce9ad9bd001771241604289db16074947eaa6201a308eeacb2bbb75360e4ce80c32a2d0de4028c00d3a9ac8af175a42bf564b87579c1cb37f065751b9e53e067a70683c34e02bb34b95736acf6b31605654b86810ee64512a751d395159f52bf1905d156b3ab709f68b94b976cdc737eb6d96d47b816e91230cc51ac78f7de223c70364cc2214a043d4fb9349d26e94309540078b1f1472bbae0e84893f523c31440cda29c352a067983868b1cc4935c0981a7674a89c48fd3daf4524b300b9956e54e9359ca7016a9f6c29b1fedbc99ede995b41617240a0c9ab53a72b7996533f3584d3e2c6a0011346173ec65ec01b23578c0c358c0fc7af9173b081689c5930ee4b510967c602eaa2fbc28f56e66a41854363cad7b2ae0984cfec4fbeb4e96e5d559956ffa7fb02cc8991dc870b08bb8606ad9455950e9406c6b592f9d210950fe58fdb8ac101cff9f60f0b60fdcdebbb473122ef8730552e1479f462dad68a993a7dd1981b21634bbcbe9294d41493c87ffe26c1344572f38ff84900666250402bbe0fc1e86ef30d48521463e973a42085da45dd18ed0c3d59a56446cfe06fb8d63941d79a62f20715800baacf3fb40c769d0a7f0c290dff1764538a7e37f65a996939fba1d841ba9b90abfcc65e757b9488d38f884421da665881021f1c4365f1291e56ef5ec76eeab0d204c54835f82642a13fde36fbb80ebecfe4c8eebb56b13906ed4dd22829b48a0f530b6824a262be77b9171bd08d8c7154f5c0e90fa4691d8fa696a7835020c1841c5d549679d3473b4f07d3774390e8fca31f8c8f30c524a5eff708ec4e0f53b3ac9da378119046f14693918ff3499cbea0e0ffa0d6dcc21857c905c558d5039e1d0505fc174d2faeed90a3d1a24a80c6b50919e7f5af72841c03bd895ea3ec4c84fe85075c307e239f915c38262b98d49612ac3aa4ed0f464480ad3789251b2131012e1bea8efce99b60d8bae984ecd3f6696a61a0541d275d643390998aca62a9789c04de27e5644fe52182ca337614e3961df5ede92c65cbf8a5c6c0cf4045c229310dbfe9ef8a3a42fa34abc7a606e7e69466f8486615ef139d4e257caf13eaf634f6188bd04c037736ea6a7bf783463a23f40802f43bde08c80528aa6422f4b869541100a3ad677c41d28185c86cc4c3b509dc01dd1581a1f09635c547ff7b3bf792cefe3d3104040fea7302438492345dde3ec9cb916a01891323a9c34f5b38ceabe47c1919752dc444711de5670f702142570227e161541c4d1a900e32fc6b09b4e1cf4082e676647ecf0404c70c575bb90384078b340a4f4ae76c5c1ba1088bd518c4fc14d8c32e0cbc65c0a2cee3fc6edd5ccf2228f93a93cd622082c2cd4ee614776d45c67021e28ee6ec77e8c437f85d923ec6375a8d3d1500eaa5d465d08b9eee0ce59b9191dee77e77c37695a57a546d13b9ccf42bc8fc5bbcd2f70952921c20694411858e0225ea94d7d86d48e046833cd401b72ce94a4415233dba5b983e06a662b69f21624abc613d80398ac2177667f56489b48fb45e93c95636dc8c465aab680f040ab0c773e10e05cb129703c1000b5fc0dfc4f32ba4be3d9c738a9d6a4086c0706ffc4d1ec08bb5f9fa7df24a84a3924857410f2cd3e9186b42a0010fc0545b644de56e9ba2f209d9b6f95b8eacddebc8e2dafc10acec466e455d186e27443134b6e27f591f096de8bf365756a7eb12743c33ef03531a8e950b450dbd170524385d6d2ac9469357be0fce6a3401f647c3dbe8e8343f13000f3b624ed16f704855cf53804dc2af0fe7210abd5319f1f6558d5ff40a4323601c95ad2661046d2ec327f660e6b69aedff825b90b6ab01f5354e03d653ebea6270688e20095c6492fb5ebb6268851e95f27e167b77b19797ddaca6518d7283ad3e5eaab8ca880cedc37f667688085d4a29c1d791e002d983b3c31a87ef5229d3083ded95ead402b78f716989e315619b641f11203eb49b1a451b00701383e382e080c84d2c675be7eaf48452879f88d7516ca04baf1d5fa54d610f9798cc7928de56bfaf705389d6ce4438a16b7848ebb9f6ab418369c51f72f5bc16e926628666dc63148df66686deaa9d45aa2f36e54f14538ce105ab35ac04c44567be15387643ff0fdb6cae0ce9c3a85ec15cbe485e0b4e96cb12de946be7319c3527b8cecc58fc1a92e687ca66005e87e91fa5622e978abf8f508316a6ca220445901048167d8e7bedf98347600101d1c120506002028eb1750ab4c2be3473d6e4d0aae7c2cef6f8577a200bb1e6554b26de6e965c2594d76ed3167b341c0c49b48946902a4d808b82e1a36af0594909891cec57a4486581e37c04c89daa2181de7104f1c8308604ddd1e029b747f9b7af0f228b8690cfc0c9b68c5a34fa7c4d078dd5577bdd7bf443d9cca7823fc1635b5c0fae65b621276768c030c35d9d073613591d5a7916ecf5af7cdc0897fb68432d9e93f25ed5318b86a6a0da8e4126a42a011be885f0bf5df2071412bfecef2b8cbdfa90bb7e2ae8702e85bfff1e732bf541dd2cde0f5e39f498b65be3b83933cd624465b722a4e52e3038d4efe310be15d4735ed0570be421de546880fff9d502ea49ddb98dec4eafee9c93fb1bfc77461fdc35fb034092401cf74a125015b6f09e52eed0138c4ecd49ee64380515da49957512dad8ee044eef060b7b1debc48358e596e49f3f82fb38179e4d3fb25cd99af884caab2f6764613ef18030df31784ceab1bb40e9382f4aa696f334f908a370ddc14b70d316e9ae93f82b81b2e66a5742bca027acc94c7f1290aaea9130b9ee841123dcfcf6abbe94fc70621c8af7e09ad8b3cd3258f3621e1622fbd99c661a8431754dd49f7dd1bcc8a40c81628f2c0df6e23fb897841dbc7b82c2ab877739ad4e43f7466a987c20c4a4bf0e286fd1a34c02194c46280ef30eace9cbc62dbb1c21be78599d20979a5d8a4ecb31b5e8125031da50d8b3e645fa062639bd1ba340b2e22fcf06ba4bb2c58424ded7fd7c35068966bc859fe48fbf1c2e770cb89de049fc393e913a487a0c228c6c66f564f60d4c248247a51912a1b023ee9e92d43b3900ace1e1ae30201bdc4f59a8bbe512220a65cab16b6e952e0f98c7750bec33a11b96bb3eab5b03b4e03f338748eb3482e665a6648a6349291d068c522be61da3689a75749551c5c9e82aca10e62b8f05bc3e2882d4a30edb55d8f1b3ffa6f2d10d26f52b9a38e590aaa0115e0992e3406e09270f03a3093b26d5a9feada1e7aabeeccf8fb128be603fd7313c5e9754fd074a6e533d6033b62625ab2cbe7f47642cc719654b3b96f7aef73402b1241399e6f87b63dd6fdad57aa28caa1fc35dc11d0f6abf552a2a6a12b9739d226eb8029cf4a6b57114104a492ad170fa75e807f24c175ecd20a31a3205e51248fbbf7907e1887f013634fc21fb051d6036cf8f6dc220ed39e8d3056beed5f593fa00357b9c17327d14c480683390b57888e0aef319fa56805642d9c58965676340629f1729becedb473059650264b1bf2d6e595e7eda059f595a7edef8792c9e5210ab5322f509ecc5037e759e8242496f119a56d6d7a6b28985d7c10e23dbbbf127227460fc439cd8e9c30874c80c2a1951c1a15259f12644173088300c6b80d8b840cf6bee1f2e0f98414674ee7f323538b29a5f10736aa39743978479579f40c5ef5832fa211e315415578162ec1cfcfc0ae524fbe754e3c15c56adcd7e1d1dcd0cef8b9b7f1278f56762068242138372c5edd1f883f9ec903a12333966a5c71f6293623f7a63648e5e3d7924569765104b58374ad2c2654158b1e94c3ee075fe40b20c52c3da7450fa7a3bbd2830652121f56299af10a5739b87328886e26d810334f30481695bdfdd04650eb668628b8a487a6f045d423a90ad6fbd112059bc812c9791f9c4cdc9d68cbb791b19af03cdb7756b30e9a0d7041b58e091507df2eb039052d947011eab0a72046e9c55bc1a45f708633ce5b5cac508e2018c9a512fd48891f21888094e6ad3ab2bc8cb4ae8c804e0ce9ffcd5004872d3045cce3680e2f676912562a57f6260778ff8100ed544454f6b672e27f5aa0505ba5a648c61c13eb16579c76fa986fd82e6cd63cdfa1606b17d2004f643ae1b0fe0b13f3eac65dfe906376bb02b7e11c17438ca458b01733af6915ee5b8690fcaff7c0af759dd167b599dfc6cf9aabbca6cdcace2a94deebbdfca5c11e917e64cf961cc8c29168c410eca9f3f24fe23cacf51cf6c8317f69c2601b90ca761b92f3e2c063f5df41f9390af587acb014f8a19714c8de40dc0b234c71b2c5a5b8382c0cd9cdd1fc6e1690d48cfbbd17d60e0f325ae3feab4f9b7ac016bad58e96687a308263be84c924c03d663617ef8b2f88ce62381935dbf9a5b275a3ccebc298080083cff471d819e23713fe105c30c203e9ff99ae052fdac47aa6ec924e5a53831a5fdce3251ee85824284e0b1f45676e5cca603a45405eb98fc64f4bf8003d736e869ccd99c66fb4f4b4ef1387e0a145d81eeb6acd91d7ef274b60470a04a32d43fe53f98c2475149ebb0a425a3e87d8a16d664e5073abd6ba43d7748f54757d592b8c4f857b51f9d692a5a1b4620da0d95a48ee1b2626b75cacdb17a34264f91aecdaf6ea5e1a2a381a92af3546c204fac86a40a2f01f606264f4067a902c4261e1af98007c2c46e3148784b0bcd7ec6176197d64b76e104d7236eb577cb88ff4a3f6530b4bd8583a5d100cd02db88c64b6f5a2a456284cd022cfcd2981e8c889eb54464ef1650c1b0ea803974e05d727bc01723ec65d5dbd2c64195bc2f02acfb62165ef97ebcca9295ff6a31bfb96ed8b9c43e70b0e29d3925d43d6d07c1d058398c570da909dd23be25bb2c02c1b9a7f1885a5d0f7bb13317c76a772e698369948411a38a4a8161d0dd8be8eefdb6314702ff4ca8ef02da3500cf1b399dd291d086f008708614a1a5dde41ed364206e13f15fb00e54e5e4fdd7580e5bb09b507c542b09e2f46a82d558e6c640df3164e51d9aac1864cc9b982d6c8057ae2d6fcc31ff22533ccbb95a2df33566e071a306158dc01efd77ca1ff1a7b661af5d15330f2ed55efb73d8008efe6dbea3c03743cf21e8f5a6b0828900370badcd8df6e51610c725378646c5fc300e886b22104343ea1de191b41a05796c850e3b59c32983dd6feabbff874253292c6e464e00442ee1586b0b9452002650b7e147c55ea8aa6ca6129ef227f7b6d7c13442e7b01f48d98c47870328d9f961d9bf49911861d935cb18ce5246b04e0f3058d33e46a9f28d6e156dd62b5c633440c8221d71a5c12bf5b6104acea94e69955e2119ddf65b9fc2ad3d5a0524ec4fd765ec97cb9003135ce31d674d239b0a16a59b8021dd91aa215589618ed0b2e3fe62c57f42d1ed600707b5b764abf009daf8ed9d54d458a4b7d91f51a8961229a4ac01d6cecc9d9c1e0125eec0f2ccd9bc400e3a6640a1683e8596760ae73bcd9469bcef912c2109f0675eca2438a8313e7d4c06e087d1f8a07d8f50fdb963142c75e43d59801921701004f08b3ebee574a90c92066232a037b9fdd097e325f300b6f1d6793961180ca73dc58d9d5654cee84d62027f215736c2462d8e8694ac8039633419c563e7e0eddff8c591c5bec7f5e34d809d010301a55c65272a1d50418c7ed0edd1886e49b62e8e0363bbc8fecbc987fd5bbe31d1805251d88514cdfbf7d1a113504bf64133bbd776d5af1fc3e7785ba58d4334fa0033e20b4ac56974ea8b35bb11b8de12a3f2af067742f0774b2cc99645e6e3ff048887514dae88af3aac78f2257a5c627134c40a14aa10f42c8183d01075a6e60ab94012e58a7164ef0d714ad96b27d0395a7d9df22af6c02461e475fb1c03aca0e515831ce2164b70877ea4f359036289e1c257963aa827eeafd8f1a184f3dbec109f7728a9f03fba01846a9d7121c5bf6957a00a3c95e2e3effc5cc96c214450a383cc00eebde608a37f6e52c53802b6820b1208622bb870204332df8cb425c0230bd3faa6792f514462c0554c96b39301f7bc884c07d0b2b0ded407a6dc6829202e89904385e4a6c2d0bf3550ddf5007e5d4a18311882362ac5792cbf12b0ebc6aa7a358f5b18a72cf9821d7f804ca206bc804a0ae734bc86573c67e1df8b8e5f119274766ce6e6ef295af6706876065bf6b7535256444c58445f70e80901587d9b333a6f9dbf1440173af9e1632565ad9bea741eff3896e64f17d73427b24bcdecc50b80fe31ba345c84fbc7b3d82722a46070faec51879c1e5fd0b854eea3e09e2c0bdb009a06b7d5188c9aa1f904185bb3bf1fe192b7eef5ef7791c7c081d986b0304e3f9d3fd173af2d3bc06736aa03f50ba787f0d497509a0cd7a16629853dc84a8c852cb0c9b1cd72112120e5ac57f339af7448fdfd003dd04ecbefb19fddf55778b869d191581182c84da051f55169196193cd91a6dfa028b2e6411c7309141222c2d61947e5c5230cd69df1391f3312bac67045c1adfa0caa9022f5cab051d5302e36541083110d2abd78e7e1abb6222e1d488dd89a20e75a1112c001c4fc9f8747afa88516463aa48bfbe606a219dc30cef0af2f8d90fbed0344806558cba64997210f779e9e45f2b080c3499416fbb3eac1a978318ed9f4fd34c6fc764a689177105047697acf52d93e8472a3190c6798901b36dadae0864eed48165ce57765d61272031a229d6605be903e38b3ab044730ca7f0bf28c182cb4d892ecfeba20ee72dd13d7bcefcf92a053ca91ffaf301a486ad92fb396cbdebfb3920864116a41a1d9dd27f5ebb53e4dc6a6fb4d656a6f4678b7197132046141040add4b2a135c027da490c22ce2ef5fef5d569ab94275f00b9be8a1055fd0055a19870788f1f37e61e9bc24c8b4f1852ec1a2c3e622a73ac3aa7fe57c0d2ae1bf372047d1dde0b6e46069d71113e34824b9bfd6d7206d706b0df6b389a17441954b45665bad78ec6e24a5d222daa406158ee49db4f3d964f8d7d908a132a805b65b5439f62f2a679c764b7260c2eaecec3a35019a0ad9fa4b35be9dd113fbd9e3858ea8f243c038bf283856a14642f6cba12abd3b9c7df3843e148522d2a5906b93e035ffadf996bdf6c648c45c84aafbada85302d3053b4c9ad653a57490ae578358f6bbea9901146417586e9a11e57cf050a4d574fe8087db4b98035aa6e4c84c416f19832c0737f5578829dfc64c38ce32768adb9c81cc655ac1d12e19d0d696da098f8cc2d10c66a7fb8b7004e71f91376f2f35230988784b8e911eba83ee469078ba79d1cd8097e472ecc0d5f233bb558e6dcb17509010f71c225cab060d5991004bad2014e0ff4eacae4337dc325acb80e63e9c5963b7ad6e52012ef07748b21b7b5be9eb79dc97fd8820c848c8ded194317ba556b9b58e364ef82b6986629f56d4ff65aae25e90e94271f0825996302c5cf2833cb825895c97431e5f417a483b8e7f3a61c3fabec64f8b0688be30a1588d2a337a16eca94e4228e6c5e608586cf6dd13345b408b2650a62073ea6e3038a6e0aece2bf6800513041f4073de9d53888969ae66142d5dfb7e2f0ad8a99607f2deb46db4a1dce01b311608e7562587d1adb9a1e9fcb326025410c629e0aa2148a5863d6a9c76b353c67c43ecb158ff0efaf596570614edba03f98e9f55c31daf8a7f4d8200defa06055e14337a8db6af16049fdd257aba5f76c54556489ad91466f800751c79af635c37aefd3e665d57c830805428f0ecf2fdb2caaaf55e2d8ccb7f96219c2fd5306d931771d8e206a761300e50a937782372767f7edff86c5adea81bb92b8128cd17c23313bca0dc00db8bae083496e215a0de67582f6f20ceb75dac2bb4afd98f2a6cf9dd780eebcd6fcb66203bacfc293944cf93aa72441c23df722f07c07161bebe940464f8a8fbe6f9e3cea2cc834b7c30236d3bd6db17479b14bf63b8442aba68ac1e2573b79536e6155aedfb17b82417273558261b97f9bbaa2a80ea96273f1e8df597d91df824ace3452377c5b96a48bc8b434e838d2d95ec218a35799e81833c067de69ac2836a0f57cb37dc07396ae0bebd0abbb1072bd0fb0a2c30b2d9155b304754b175cd6d94cb4b44758ebaa38dc8aaa5957a9dd012aabf7620fc425d05a30979ef357253b1227b89569b685eac691d45aea740f30701b341b51869572430f1a1c70df06150df65eacc14a1df08d446bd51ba730056b143b086031a500a2a0129211b4f45fc11b311d37b0d9e273fb6b3dbd0f0cea5d13b3a67e5856a51af7835de2fa32904366fad847229dd4af2a72d3ec12cef9e301d8ea34b18c6531867e4127fcacfe419240b427c662b6fbbf0a7b969abd2fe6484fd6da591b8e03cbe21dfae0809d6de5b0418966ea510503b8f3f3599050b6dd7e11924f429dc69b2d1f82d9305a7401e716fbfd53f8d51b0b7fd5f3ec53eb0fe8b76062ed9260a740626f8ccd3cbeb38d646dee589075d039ef7208f83e3373bcec38b05820c05fff0b681ef98a384bfb1b933198f2df981e8445729247cfe186dfc7c756683914e5df31296b162623309c2f86db977e1acb8904f8ef9e3f3665826c1c2ddb5c3411f8424d040afc982042b8845f734d2a7c0bebf0f0da25684e54bcaf4062271a080c3a9b81f601378c9ab74a3a22d8b902256efa8c48c3ad3dfdb295c5fbecf286eab755cb8be7f5205ec61f17bebf8464f817713224da978b44f8dcb9a7cf720626b2a47aa3360b91d712477ed3c61220e6029802a904ef16cba696206f455781a71bb73f0127bbd002670c9ccf1f132ce0a32be28fec58d7418c1f3c5b4b0681c3b73fca26077a88939f4960ee07721f1859e33f9b8b2191022883e724494588741e2fb858c1c823b34c20c2b1b96c609cb396d4ef7891870ac572c5d5c60e60f6709bd613e3809764109305f01adae46105e533602452572f8690f09e8ae449ceaa82386c90d62c22c5236a31aa3589c6ac38f864238ab0bee3a86dfd674db6281557a9e034975d3da35976d6a3a42cd87494821d9dd5ca0cec2753d1a45f6f2dbae438fd077500bd3268389c57793af96d834854b5f02124dca720def1c66d285c63d316ac4bd482bc492147c60720ca0de0c1f5bc4608679e1226a1150a717a45c66468b04714ef17f88ad968169a43c80d6c7cfb8f50dde222eb957e7ddfe5ab517e8d17bc5aa3426097ab67ad2b6cb2d5a56e70494673a465868277ba7d2d3f5654d180fbb6cf727fdadb85260e3383e183ab0db365dd8b661a2189df0a9f93c0fba98640f5699859212933ede180d2056bf8fde789aa1317020d220b5236fb351112c838d25f3bbc054a197e76e4addd2b0edaaa3ba1eae32a39aab52c1a40d04f85e281a69e5f24502deabc3862b004d6ef22c6ef0e97bc0b7d3abccec9f40517bcc06cce9e5e83ca14229120ba21b1e4672f6a60b53543adb6013fd60c89de7bf59622350b5e054260ba5eaf461269d222dbcdbb3b3b1ed46269929396bae63a0ed98db64a35791f5a307c308b242182d1f881335e7470f24145446a9d71065a02759047d439e714783dba45693f8f9de0e490b834bf861c68d9fea284f4cb9c523efe3f9393591e25ec9cfec12f8f37bc8e42dc3664678941ad2768c73927711dea2f42af9256617bd527f339c68c3f95bef715b442c3eef239e07525cc8e68d2590e6d4386b111b42ac60507b9c4048633a390c89ddd42feb4995b8bcc71d27acd4d4452d071d5f1a7327f51b86b8b0a747f1b46faf80789b81f4c323f67256603040095a34d6477f47a17098d4661829be1be804e3d69e95fd3a0a3fdc4457c76de7908b73dfcbd24cb5cf69041be019449a573b5d6a972b1dfaf351e4c20224e70323cde8986d509413cec84b017c7f3de1349a6b78da62b5389060f37d5aa4165bd11c7f6238de1c0c08deb6c760e108897a9e5a53f281e8863d1c4758dc0a15f3e3e927cafb6f5bdcafa920bb86924c119c44e1a482208278e222140bac402068280953cef01e60838b1da8898112ff941a4969c496309ccb300fcdc7ed72457a131f67886587344b15e2c0b8753faf395b432ac1bc82af3a2e0967c250532f0858fb4f6bba4479952b1795a2164a78fa9e3aec4203aa5131281faec613b4489109bfb9d1a6ae516af4f11953d65f3b1bc15f2d66a46ed473caa07e321e697b06483957a0f1aad5dee791b005fc57580cb4b4c0872f597d7d7b5ebe53b9c34983eacd97a18f83cfc5697f8f9d01fc280f2af023efbb1a795135a1fac6d66ae10285bfe7efa17d4911fa650b088dc6e393340ec6cbf9879649c27f59733638e4c1d260fdf808ec9762254e6cb8b66e2694933db23a8052f11526c75083f212ce0b63831f5ffd93acb91f9716d042706c4d6ce06350b72b341cfe54fb30721a97e5a2e203edbeaddc482a634efade976873fe04b93679877fa4e17e1ea812930c4a755365466517cd649e53f9c7dc99406f9687c1620636971419c85cc37539c80823c9c4472e50a7c5e95ec1bd725c02bc768d31154bd70da3b2e7447ae8b438c6387d772904551fb9ec94b8b4ff52c3d9403a6421f946e10197a016be6e401c531497ad9ac3317465d6dc5f676bdecf28a67b05380d5205f05dfcf1ea6bd031f704d3819bb332e8a57cf1d74d66c82470db235f66f96483ee3b6f80b4e6ec1709f26c59aebb28d54b757a3b5e7d4b4ac6f53f596c6d9f0deb1f2ef343311bbeaed4320f22837114d58880d05b250203abbda419731584f001d30e633aa6f8395c04e1597162a0c3ea572e58c5b423eac970b180fa25b770b9380c120bb37e7968ad5da8fb6a58bbe1ec88f368a628a8cee426274f5a20134d933d785697cb3b58859573addaaf3e0adbfe6d182c2aa56d8e52d0270c2b44813560bf3739ee468ebe74e1e8d910885dcc48504a661506fd7744fc0091f8f26d08893dc85b0fc8917e273f129350bc268ac897f1792c2752112c64d3f2c18b4e3fb38782c3354aa0b81689dd087d742895d23c98aa26727e61125f764f4b78d78a6a82c543ed5423d88e395c2ae7e279794d67a6159c0faa5017b0c4bc03d231d53cdfc3abe089dee5c4209fdcb93acd3c802bcda62170d47156adce77cdaec5b6801e07adfe56153b80325d3a37df6fef3bc623423daa63640d08dd15a8b7a2a4f4650951702fcb5307980ef1fa87d53d874931b0d4e7198907b9c0cd1fdf87a79111376184e88554b08143c091235197971cdce96407d2749fc271d286c752fa1981c2871cc049990f6eec5c51a657fcf4ee7862f578562df757d674f80edfa86a826d080405b1ee0246e60e978241a6db45ed6f59a90b82843820294e269e2ea0e6066345992c4a607641c1fb48eabf528b2cd2ed3897fe3be18992734cdf1a1b6a0497443bb6a7688f70e0b3220c5d0c73569e889976c70e0cfdd9111ab1829919d9b8a840e6435dcd7274238cb8689abdba6a0b6abde895655020f6f4d28f7edded0ceedb8d77c64b098026a210ab6a5261ee66b6d73a4369f8001ffcc67b25c6760fbd5a8bb3c4ad8f3f50b00a85ff61a8ec6e96ed2ca5aa537f9b115af98f82f46c15780f44a57409c69884bf8ddc48625c4376874eecaf928426e751d125a4db14af2737e227c98ba713f69909b2b46ff00cdd9c58e647d9b9e1762da520607e8b39baf9b707584ee277c55e51b8ea0e9cb039f2badc3b1acb230d6da22f8525f3054418711c66c69fcae312bc3808f098c17ed489c9861702ecbc2f9b9f4e48bea2914c8c158d1804b30df29af34522307832faf004ec720a0abcb5199a18ecb816e6fc5a14f5090a541d264cf9dfa83c31ead138ca5615b8b72d94b321feedefa56e8c9b4baa64c358a974f9af29134fd304b798243ceb3477b032f2c638f73260c9685623c57ac0b33c450821c78b6be2f9917ef5779b137c3a4e06c28f287d7645cce97c8891825c1d442cfc38af9b9b6e452c90a71f27b55388ff34a4f56b5c5c1cd69fbf19e7947e1aace7b60378e375ee65ddd702f986d9c24c19d1ad712026fe021b3e98b7254c64634364626b2ec625082138c9517441677e0568326eba12b939c04e473641e49c49eb2ecd72bc0cbfbe8aa3a11d91e2c32db1ab2d90dd16b001f8b4d9711d3f684124c6d9bc2a9dd885536613d295d11bc51ac7b2005a4d299a6952a903c06235b2f97dd6c4cb364d41e43bc3b445b4a2afd81b86b222729418a62806ee42af7b877b67f9a1b17ce61f9bc907f2be311b7d74f70f300b4ad76f08480d3b6aca0b48ca68a57a3930f54f438fd046ea82fbda6c6cc76a87ae52a03f006b2b75b0b6f4a832e53d496455c3a42dfa667a8d380a72e8a451812f4964b21b1a67afc228554a8488757a01523d59048a5e4a8b1ee4ef13a45aa4c98dc39ed77642bf094d45c41e83919ef5b28eb06e99e8cf76066173d4b6cf7fe5662d18a8afe4c1c26dea11e79857135d25878abc0a02ec408a62eda2d7a6f861226c9a99b3daaefb4fa3cd07b9199609fb27c0d1af0810bb335c28dda91425542db993dffb7e127725c5002cd793f33197fb82800a076178913b54359a44cf5328e149cde8c1576b83544eaabf341deb45f394da6230e0862a1c7c6c10bdc66358771b5a144dd2db223b595f8cc32bc79225034fc05dbaec1302904c7291091ea791793e7adb41ee4b22564198445745750b274751cfa052e18f550c0b4a60645c6cc6eb748a4836e3e75153787496ff230f6b5fc9abcca6a19ca4288c1185fd1f27988a21f8d1784b0766304d2da89be3781df4b8807fd735387a4299674a0c2e6e005fb953b5ff8b1f6715fe9c71404a246f1cb0f06e96c8897f271a17556c7ba0896568590e2aa41638317bedd969febc50112bfdff8744e43475d6974bdc6118a322f69fdd07493254c9fcb47dc2cf87d89a7cbb65e2d2399dab7667174d7fb21e427b39614e2963155f18b22ebd8ae875c2dcbd8e864b903c13a8d7d9e1fbe7b46df27820759b844fd5bf47cc3a5fe53d3d48a2cb344217916ece7e6c00cb847ed9918a887c5373da3ca224341025ebc9437b903de8bd84d977493f3125774bb0fc9f6fc94e7a4af7397f97d60356babb2939032bef769694d79c291356d658ce5441a67ea118ccafba69a4c8e100d730389aacf46a6a49023bcdd9aef4aecf81a3a0b9144cbe9cf3cedf8d8b4040c3f6dae8e44a38c7acf6e0582b709f90fe38186b3d82ff56b800867b1fc4102bce2ad5ffb856725b82f08a7974f2ac14e52f75422834f1c589c6177166ac552fea5623afd8587d512973a993551454aee3e0ca01b7d9a2ddf44b0a6b563ae335946e1c485a2f149787a4c6e410586f57b1952f53c75be5d87123a0a733d3ae465651a508f172ed5a1768c1cdf1470cd7f1b61fc76c67daff94c3dd33a5de821f08b6d128fc6e72ce24821ea945366e05f0cef469c064ae33bf48912f1b03ff9ea20a5e7d0f2adfdd48d5de69812a8a921ac48f7ff90b832819e2c472c82aa584360633c32185fe92257eed0161b2f8ac8d836c7153f6a51957760b35a1d6d665b58e35a1006dcb37450e0a6bc425ddd4cdb281d5254f991c0a193ae1c1824191b8fc29133cf0f180cc62b2bfee3718f875f909792f7c51c2a228945b5413e15d5515e721576889f942c97657382c29e6bf201584929f4fbd60cb1dedebdc87952e8b516c069a701a15e0bbfed1af9517de5e85ddce3281d07dc20f2a0a65411ca6d54412d5763e3f8443e889a7aa49a831bbfc9c0e84fb1f04feec07c50031c2fe91e2fb02ce28261508e48fcd42027aa03d27da747b3de5f95f95ec1439262726c9fb27a676d70e0ecdb2472570b1e70a40f853569be95cc57bab0d7e57d7a6904c749dd7955cb55d35113aa481be3b98effc3d662f7a595dcbe57cc00cd7abb613a31829756462e2a971b99e8283556ad0469a981590da86f1e14e0c7fef61ea00ddaad8441bcd631e9f68c3ad1e420d59d24d184a3c5e0d1bc3f230a4d24fd4430a2907aebcf28322a0cd9cb368e53a3420f121362dfc4843b190fa026f56f9f006978e6913fe3afa1ee2641ecfd52cb0f1f5f1fa68517d9a4567e0958255c28787f6eb294cc01dde4a82c7c3ddd2934ebde822a2cb0c1d0343a7e4cdbc638a9345a78f9380a96af9ac1131c6d3544e4e734cd33d4e08fe77fbc32638b88b7a61359c79462b131b4a53dc3be738de25aef21dce548b2b462125d6e6e167c409bcfadb1dd3e8fd937017be40b0e2bd58312b13438d9aa92e10148bb1b496aa41d818446d48a688b4fb65947977f6fcb0d6a113a053bbb363a85da5dc6f5f91d54db2892d977f8c2c2e404ed0d403c8d607a6035e915164a5b5ef8bd462a04f24073f528649bab27510f61af5669bd98ac7a15b3d706e751f217e4f4582f9cac9e42a310c3688226076f761f5fbf102f2c9dbe30b573340401dc9edcdd2b3a9459a00021959d441cd1f59b53b2ee7bd62e8382351bbe8b5e3f95e9e282bebe9c48f34a24fe377ca46f559c754e46746ae5a2ec32367eaacf56ea9360b92b31a647542b99da86c3c8a6065c24e5f9d1cac23d41b8810bd2a2ec55f4a52ac3cbe8de1d94916d392497c31c0767828883f938c090f18db508274a4e80d206f0aba97f07f6aaa59b062bfb7c7f5f0f9d381ceb4b9e54103be29ffb4efcd43d6a076e91e7ae3bb79e2b1bc512ae0e54648327e0e3f47d347f5113aec681dfc71800d151773c3aed31c03e45d7f9a7917f144531fb726acec0fa583849fa8d7c36a39a7deb0c8c150884985f402e50b7b384555610150b7afbdda9d5a46c19e6e79ec9e14c835e9685bca997141c73f701cce9d94f4bccd5cfa7e1f25a0411ac7311b08a6e10ab633782e8fc2799debb0454b5adc5dd77ad97aabd3eff8cb373ccf8032ba461549508d74f7062aad45ac7e2012a07bc91ce41a253bfe2463de0e5ea2ef32186121aa3c65fea6c667c55304f776c621c0d99189ab5bd761deaaa021c011a6681084af1e61ffa6420b2a3ba73675505e7428138d47706aaa4b630e1fcde009daea0a84895a12bc1123826f9d69d0f3670778a0320039bc1ba93a43658d41139fd7f296b863e6a6ddf6f5ab75bfe707f1d97eea9fae733268e17e8f703400169994590c6f2da7d55b7a446c4a59e50c62dfac0670f261a87374b894b405cb3623370e0530aaae36982f31f57abf2c2f5de9f0fe8c5d2a226aba51ff0d13108b4f8c9d421ba9030d66aec6d88b0226974e219ccdf3617568483af6fb37485170b6e3977841048aa70bf493fd4a17eab2a20c7d41f4960e91a5af8d09ca7da9a34b62bf95fccb07353915d15cdbb238f53d6dc1b61b6c0ac853ce216c172183d971ebd9e90f19c7ed23efd3e642e95f8488bb55c8d4ec472bff9a8fa8accb8cc5a586990efb70f0b3445113479dd6fd517aa15e36de2204ff7c181ac898bd7129c17017d91cd8f8c57d01cb4ae8995f7043474a9e4fc1062d7f4e8d0d95cadc1a2f4f44999c02d962e8357981ced71fe64968fe72486563b6e1d070aa5f523abca9f9f99ac43fcafaa452c72684492e40ba4e8a2cb3abc21013b23508c34565318e4aada1cf6a645ae0906edf030b6b4936460942c603f5da29ca358473e7d08d0d53c1b5f059559cfae579bc3ada96ccf0fd56cb7a0b921e408d3afdecdd7cf36ea83ab805ede9c4cb1bd30ada118e2828899050829e0fa9afd306d53f0b6b42edb20014e80183d73f9f7d06c69950d436f8cb1be43a12a95a7b1abb94090d790875e92232cb213b516b36f3d9d4a7cbe67d55568fd095c70db0a52b2e0e18dff623b24735d002dad91ad0368e0f6f1b3d4fa3b2384056301a34e6406359f9d408e308c5b70a75db5f395b479ca2b001a2a5104a5f89c922cd4d4482539ddf20d589023bb0f07c70bd3405521b4a86b12036283a768defc7ec32b08a7e63c09cf4316cb6c9e242c56a207c422f4c97cbe0edbc2d3be4c7d3951567afad7bce6a7b8b1f85468f97d63e6fa833944dd5d8d31fd0c5ffe6af822ac9245432b75deeb6296058345f3a8ee36fe42aaef01edd48c541099e3182e76d1f19df1003f14eb997708dc25a42f46fa300ae10edbf73b45f748f002400f93de9fef9fe92c03b48e9e8bb41e92c868328c78eeb53e92908dd85596f04da1383a7b3e6194763d55187e3220780a7a20fd501b31e5cd32052925640135cde8219955aff51ea08c5973acba08816c681569be9120c785e0ed54b9659a071a036ea6b21ef874512193d0141863673402613c2b0ea3c6161312f0007c08161607bd97bc404ddc522b42abdc1a8af10abce2eaa93465d1ca1adaeaeb3535e269beec6977ae59c76d0dd5d5668c92444c02b1f48dc356ae6ac0c9891f597710e8940217ddda28f1d8c815af094c48194d96dec3f397a460e1358919defcbe6b6011f7e04670e96e5928a9e08d2aa04a8852c9ee8faf5dfa748b9b063d7013ed7c8c911b3d8f8da1f3a62a87b4dcb4da545ef17931d8beea0bfc86dcc99160aa56ecd047b54248b2f00ad85d38d7f693afee7fbfa2128d018f99937dce7094db93f6bf0eaafb4d736bb8cbf5b7fb84efb93150127f16a30b353a6a9c3d9273ed790d90ca5cbbea36d80555c025cfa488e25eba5ae7d5c566d8540dcad41c80be280a3def80daccb8be3958f4e144d8a52290017864e4d2fbbeadb30200f88fda044e0c7438d8fde36cc56dab54264ce91140c7cb7ca2835e28748aebdabff0f0c4ff606b69fec88aa00b5dfdc10be5c37db87e3ffe8c858207429d37d1650772c9e7acb15bdc46af3353219b0919c792334a74a3ad9fbfa646340a8961037ab4f5ad1879aedf735e6d092c6b778021529a342acf125a44047eb094a8066fc2803844994b947123c1d4548a7804a9a642a1b99636574b67531884f4068d3f3e84d795368fb21399462c01f79ed4c89f24c58755c8423fd8b821a96257874ce559d8bf92e8f00309587029872e9a0ab108f846a386c245dcd1892b5c3dfd9a86ff085478f350d12b4f9f11b0bcca23d26af3c53ffafc6120f653cc441d675ca61e6384a09a7b75b347b24409633e3bce09011330ca3e3e0a04728a926910a36e0ea91c9b7340c50e2347270ddc2a8da6b816507f0657d532678004a8fc4b13abb08d28a85596f293346d9d8a42a5a70f662fc9cb07a6703bdb242fa139c2d861a9575a8f6207a10f8eee1d6a0d832fcb39f65461e7e734be83f1795bad95d21646a99a9fc5f0bb32d672780c27fdd0bcdd9404d343a7526291ce4238dd835790efd1429da4bf874964572044e403430fa9161dba21f70f703e2f635fe0c77c846b517fd2203039cdcbbf8ad13c6f3af43af3a8c92fc144ad1b4f8bf6a71c1ce9a238d256277e93aac5a4c0521a150bbb01a873c8907479f28d820a51639650d9c6cec2d0885d7724955b670a346725a04e2fb3b33082e55c3191ed6f823abfe671ee28921d919c6a1eb5b11aa0ce8346d739ff81fdf452ae2d74dedecc6bd93c5d0b45f94d5cd1eb40b15ea3a2524197f3ebd198520ab466c97b0231fe8629a8c273399e17e7a23a7bf9b76c281555653a07f986c72bfde7636d4616592a953a79afd01f01286a30f83049e489b395903b0fec128f9f3a074d986f9a4764350f0bcd0e54c6a3cf8b29778b775fa95b50f0af0034e1ca4ed93c4d3206e7541a01c134c62291125625a940b12a8e308e2fab3962764a80735cfc823d0d08aab4728565565d1fc6a205fcdd42228492f1c3a6f17cb0f6b5f9ca0674a8c592f11fc6a98a249a52c1e8f548cc28fbb73bb6324c895f862b2565a60b31ca61cd461f52b523193e27c598477e46d043e87012314e7234649958b8d3ea9fae87ec089af840f1304e061b65736c7d8809075585dad0d3699a7904aa1a3a8cc510ef7d5331ab1aba16bbd2d94d080887f8ce7d5d42f4816d70ed637486f5d00e654ffe14d52c3081021a8cfedc416ba75d7d6b2bc7111729e2409f4a03602f510ad65b33ebea2e98a54988e063a851ae3f3c0ff43b8136206132793b4a3ae32ad5b60358ea4fd3fe03e4746a945183085c6127e3c0b7ae06fc6c0b4bda553f12c9538a0f9f0fb86925a00000393c60b2c3b410cd4532ec09b069c89a819f95ae60af26ec66cb619c502b69188ff2c7ac883445008b909a77b81f97c20e5027487020e1100223817a1ffc7ce380412c0151956c1884e72bf1dba0202a6b25e2f2ee1c876f2f0296bf6a8f790fda128c9ce581e5401caecb39b0605606d6680cb2c05b33e8069569f9150d0ec2f9defae1fd30af0dd01e4ca75f829c81d6a29080cc9667985b76bfbc799a2fb65ce1a43382a9a512bc0c4185d562977ded362468a8be1e9a3fcefaa1f6f33374fecf3ea3e4150b0dc108e5335ad6e317f426e4055dee4b0414ba9e6df0f73f270e3701fbe2031cdf2927723840115c8a08aa8f7072f303bd4bc6554edbb2cedbc12036ce2866200434117ae83a45e59526342d1ead13981147d1b91dea622ef50c8d1f378b005fc06d5ee210b61f92c6267e9cf46efc41b55d37b800f2a619c2c2bc255052399650e2a5a8b1f2818fef77d1ade04b052d45295a6b561d92455efc1981e3260cc568f25a496a164013bf83ab9ba1b17df021668be876f9b61a194cdb92a731269fff309ee834e8563d5ebcde36b8f801bc300c2c1140cbdc12fc28e978889f12dc25299acbd37b8fca11effa843e0b72cf0b0cd474e678844ac4a41aa39b4d6a0d4f43a0bc05c4034134e14ac7444bbd301677a2b10837d4e24cd467058d47aa21cfec762bdb72794a0a2bc0143b90596fb4a0ef9e67afccb35bec6c54f25aaf8d4edf6d2d1e73f3cc18eddc61cfc89ba29bcd1f5aaaaae0952e79f042a58ad68628d515d2a6ed0e9b10e5a42736b3172dad43e4659fcdffe275750c9be96506a066e6d1d839c255446991457248e3597dda864b4f00176959fc0f3a7c6f1bcc672f2b0ccd4a5ccd46ecdb3b9a63a05a85beff49e8f88e98d3890349f92207d338e1337b34472e09a0b098cb43c9c9a034882dd34d893a364f65131a07a2f5da74f592834db75a9b3066030ec30f5f28ce8bf08af571ff4c47e8af12c6196c85b0975f88a3f089143e835b7a1879ec3443460d62b2b60915b88ba0791c6475388f47c398d026d3ab1ee8dce226203e9fb13820fec0fcb9b34f82e16e60a8efc192e0c381a1417306bce978eb8d05e958931c9aa533e8d0116f32e0cbe982b0e6fff77e19ed904fd2ecede624924e8bff0545de88763d4decadb6b07dd6115e9d402b999da3ce19a66317c02215914d15bd016d5cc13e674c27a7d0e2221cbbbccb0092031b42d52749212c297ecf6c4ee983f48ec0756a318aa168b934838d3c3324a0d2498ba3206a7cf8fe2dd13a9a5c29cff5c905caa9469760c80739a75f67f28cd02299e57f46b8623484cb00cbf6859a4298e5117f1cc41a61d1943199dbdeeeef45040870d5d3aa48763433f2370b0cd2a52c4bcf9440c55f3492e2e65f3914b0b380a655cdcd48d49608c64d981a47de3933385535bff5952d9376b4287d3ead35b307663c79d71bb4c007e68fa298c080ae047064b2f636c189278a4bb97784de29675ad1d8042baa85a23f810b601dab69161cf493108bcf7f6a1f67c397db571cdd169caec777dcdda34abfabf6ea70beacb82593c7f680a58f8a09e988f845616d2a7ea2a15bb166d04c1d32ded1ddc3be2562f6c252a7e49de157a2482a4ae92715ea1461a3a0159a990fcb344c37ff81bb0998100252ed801d8b98b067d53d11801145e7a6300e104ebe01405445f37ffe7856b66ce4ca3ef3838e0d6439df1a4e86fe767fd16fdceaa0d20c74fe91f3bcbfe7078fb3cbc7a67e61b6ebc8044cbe1a72fe2f7015aa71bc464cc078b630038a555d985dd73c2ad80e4ca6bd372829564690c857f14f3671d7f4737d7ff5367ec68198a1569f563859f5c60b7443980cc1eca7e4a0419a6ac875bfc598fb890cedf31f430ceb416d344b7eb007ce3eb9c8483faa824c7313b84269850ce88471b05838889064ca57c31ac480678f38f3d94c14718995aed836bbb72986dad40dfe4a65a24f2da03fc25042a6157a95a30d815271df1a9248c74b1cddde5f6e2a8e6ba65edffe82edb4892c5790a2d1d4de854167a7c90fa12714c2f6e57e904a07904ec884081213bc56c66429fcb8f0660d4baf7a451d822a6ced5476bdbdbb61142c82664ef2df70e5c0acb0a680aa44ffa19defe76eeb3fb7cd3cc96d0e74806897e589f475bd55a0e5b49cdfda846f490ca54327d6693e964f2ccd8fc99407307fcdc79d6a51e587b2f0af583e092252c968c138aa9a43de5db8ef2713f7db7441a556b62cd1699b3c3c3e289f9c9c0cf8f063210f343b2dcb5d7dc6f246b4ddb4d1876bf1dc65dfb4c4bebbad6b49efd997ebfd2533ab47387d96e4f43ae3b636283b4414afaf6c990be5dfbec35d35fdc4d18a6530fb3e75ef1ebbe8461a4dfbfb4f697d52c69c3dcc8eb42a6c8403991647cf6299ff6d3c71de5db6efabc97beeea34f74d2177a0cc5560aa9a5b4565219625c99819f1f0dc4fc34bd01249555faa6efe354865da67b718a9e647fffb2bfa7bfda4918b65d3b8cbbb5daed8772ed3bddf4dd8f3e12d54894462f5c6bb3c3eeed33fcd27ec23052ab6297e95dabb3528652ae7b9209d7d488ec4b58dbba90e53c91e88b2dda212d405ba347b38436cbd60c3404a1826290cc913b4a3eee337cde515fe8299fe9324a285f8a9e4895862fb65264a0f0f8087991a0eb22080a1a81b5f7a2503f088a404890b5f7a2503f08364a173af71a138a0a78b225148e3bd1c87daca9924028b3e2ca7cc7a23964b1240e6d3343160d59b8b39ee4b8d039cf74ef332d1b02b283cc7d1b727517322513e9d3208c865d349443287353dcce618ca57470efbec3fbe931e442691f289b18f787931a55ca956a94777fa1bc8b13e58d5f29efde694ff94ccbce16946b32a77be7beee9c0e1947f94bc651304cc71ed65d068ed3e217ca5330eca461da9dfbabe3ba9317fa4ab8a686f49ad125cd0dce4e689433495f6c09098a693d1283a7eba2eb64b4b16449cc85424ae1b6a3bed3657cde51bed2492926ce76295f6c914cae26ae26381abfcc0cfcfc6820b6b5f7a2502c6bea80f47bbbf79a93c52e53d7e1549f8ba6eddd5fdbbbf73b0cd3e14aef7e3acc2bbdf3be9d708aceacab190f8943dba474a4fcf457ca4f18a6d387999ef23a1d05c34a3fe118327dde4b5fca9aeea55ea5772fa5ec7778377d47f7d2a7c384e3f44a385aef761f8de109f974fd1095b1d8be96acb985bc6ca150e157dc0cdfe9a8cfbb8c0fe5a42fe5a510377a7767f8620bf5c5968c2fb648251cb345e6ecc470cc98a99a583af9f991959be2ce4cc167e0e74703ac19ca40cccf9c2d76ce392de5b6a3fcf49a947bdc86537d199f69e9b8489f993bbdfbebd47da615aeae1637660ec498e832f37c8fe862f366a79d38b4f1a7a35e8ee8d29fa7400c65202dba0615dcd3bd7337dd3ee52a1db61da58461a5a7749d0cda60eb64f8e55d0686a5600fc350f009cbe86497b1291ddcb7bf381c67865fa6973009d7d4dcd7885ee39d7bcde8db4bd36efbbad0c813dd98fb0394f568c86b8484db970fa515794e94cf3fe5d38ef2653f7ddb475fe9a42f65ea2ef26228555b285f6c9dbed81a7db165127db1d5c5cc16a984c7496318895df49d8c19dcf0ae061257240e3102c547d9f203b78a2bb3f10f6cc59597178016ae7c877e48d5f3105ba45b207ddaa7407888ad9a1aeddb6bb297de3855b18bf498ede767ccb46444bf9f8f3ab4dbbf2c86e9347e6537615809cb88601b96d191317dbbf6995608e6a923633ae9abd922c2291da56f4ff545b7f6a2dbcf6f2f7dbbd9e7bbd297ea974ebae84be9289df454e35709c716e9a2df7b114e354eed4891020f4ee06447e9262ca3f312bd84613a2f7bd161dab7ef109d034a20e1890b68685a220ccb6e2c7d243cfa745ca2675f4ad3b62f25c23535a1d774afd9aebdc67b0df71afbecb1b5512153356f3fee9345640ecd4d6cd9d8ca7e4cd5fca4204933b33922ba947cb8a23126ad953597559248b0649784e879a29914095aafb821cbdec8228d43959c9a9996906575958078c3ccf017a03daedce0862c1edd129b7559837af2451799c562b1589946a94a5c913ff97451e5345d4e1f0e47e8014fab93dee9935538a20a39ad6e7a13264f70c3049ed6e91fac04318b214a5a76dac027a8a5332f714a0753e9a6cf64d267f2fd4c1e7d268b3e93439fc9de6772f799cc7d266f9fc9f63359fb4cce3e93eb676025c88410d43abd0434c2a0a48582f279b1c13b1285bc8edbec923caf61f19dbea6b1a2a06058095a51a54b5d81e8ec0c51d5f79ec3b3c4a851176eec398305573ec7c7160c4fcc22c9933c9fa387667122cf4320bac4aca5663847573c4fb36f2f8e9117aeec9f79d3459530fee40cdbd0d4e8a4224a33d1a45f77c9bd7bd7494f7bd7c54945b4a3d402cf3ad194807dbdf172afe86691cda2edf6bbdb760f257a2612fde2f08a3ec2212a775ff82cbf98c3d71cb877b7a11ba125fab65f9fbd5a955dc3ae2de85a8c03bf3498612737ac91b3d7a89fcc3294837db5e1fd667827d477950f9c1bee8752da2c6fb3c84c4a8baff59e33cfc69ca2077308e62035d71f2f097349ccfdb03f06c495c9720016b0c1d9ad7883767b613030080c0b813568823c4f6337bc12e8e6d44bbbbdc5b058869686532e6d66a69544fbe254e510cab2932909d3030d59b68087c7933c234b18b178354b2a521b53ce796f63db51074dc3b012b4aad0c5709e74b13b635629cd689665d9a946c4cd88c0a17999aa3ee5d9999167a785fc08d260cd3020ec3c519329f6a6c5d120fd15375ce5db60f78c46c5aee675b16b25907a5601c306701557faddb11a675927c4aca43102945393b29364c9901b213e3f82c0accacab31764d5b07142d59a932787489aec5dcfc62c91d56ca12d1db4b59a2a6a69dcd16c6896987b88c151e4c5460e14ea07c1d5aa8bce15f3c1751dd73dc41479b19143ce6957d765a74a4619623e9a25b4425e2e986649913435437a88e9e1e57ac1bc62ae18ce1671b55a1d86c68ae58ae1e1a3c60b1874151b66a3507395a3596068ac58ae181e3e6abc445a83c62a7a34583456ab154d0b2abda71936d21ce3ca6b344b7871a81c7c71bd7ab061754f36a8fcde19570554a974028971a3125dfaa5db565cd7719dc3c5b259d1a811dba28cb83d344b687bd878c111f323082c87cb7a396c34d8e2613b4fd6060e1b2f3796090f2bba34901cf25324c80f2137384790c07a2889e1e1831557807e04f9010403e2e142175552dd16635d276d78c4f49034f2864804830fa0dcffd16d9ccc71bd9aa55fc0d249906149eece85b4d08dfad260a31a943e70ef1e72f7ded13a49f5fdd436a359c2f853eb411aed57346a744bf40250184836aec6f0a033e28a6cd06bf0d9edd7e125bc73ef66e806d71cf57c34d87fbd77ed5ea4d5d60d5f1be98ec6c566511207896788376cd886384f6933959ecbf203bd3c4d9d81d7f2b8c71c230bf7d07eebece779d693527ad6fbe6799e77ee0b676c0dddf02c8b7cb8bdf33e9abbedd3b66bdb69839a77db2cdbfb1e0e376c716c508b59d34e3ba44376972be64d8cdb03c73498fd93c2ed87f127025523ba1fe2533bc46bef3c731ce5c4167ba459ea61eaadbd17853aaa491399a4c12a711aacb7b3451e69547d9548ecb5a87b14f81588caac1ca27245e57aed8b1905ae583c3eb9c678882d98a9aa625ca9a4c1fa0ccb9d06eb27852ba7ac4d38ddc88356e220f105e20df3d3621c110a793ee4f2f4a1691c71a50c37bcf9d6e89621ded2eebdf7765d4a323379a770c3e8130407bda49138b4bc41d2c8badddd6dfae28692c607874facf206f99b53cd4906f2071d0653b001142d450a72761a5996652846e415dc20d3537a148b434343938216b5414f2fcd810e4c34410c5d48c10bc400a345a3c8947699524a293d856e509397b36cc615395469400e6da091c525648f61b42267437c51051429a8428f0a8030040539fb8a358427677f3122673d446021672f856e64b11e1c81232160082264fa1bba416f2290f4a8c8e189460e67e44c256736e42c873472868224c09cdda2a0276747d9e4ec6f96cca6670814e4ec2356a6032a9a082253c0135114ba9145a146a60f152144cf8c9cdd339249968e4e11c418999e531142103b310959258a205240809cdd460186abc5584d105a90f5d414c139ab91b32ccbb2b39aa53ebac45c4f2917997edacc467616bdb3e50718b2e8c1177e983831452b83226755d080660753f8020c9d24b4b22539ab020956e4ec349a45cb599665d96807379663de71b133bc78b4f79e5033ac56356dc990aa0448f361013da19e68925920b23062064dd505828b18f445391114041a9bae77b64414bc40664dd3b4d3207c11842733d35059eb398112596341ab5efba34bcc5a56c49156c53c8125485a1573682f0a8922f754837278df1141bd44aa3dab59560f20834bca91878b2105f0220603d0c2f572e4e12227c9ed72e4e12249017eae8c1c79b8904245836b73e4e1a209aa2cae28471e2ea4001ee16639f270d185ca095c7ca1251f01345531d397f6420a99e6d873040ec5e962086eec0102498e3d401891b721c71e1300753c59a2c0f86329cd33356dfd992ad9627dfac90a77d0a499a40b00f1939f34283f693a30834c1f76900ea52a903a48faf8045dc004364b08a50f95ed65593f6995fcfc3a2805d75adad59fd962ab882af92b9c3801284b2a04fdcc96d8720214647de28a9ca1226d00c1556356835658a10609e5809a856679ee8ba0661965f90ea743d22df2c80f67827086d70a2f69b4a60f92d84e93243f4e0421cb276450b384bd24cb870d463d92e5659d42969776489697960a4b45db6021b5f0e9427ee15333443eb1ca611fe138eb0364e346e39721cd626f6c911b7b636fec8dbdb145ec8dcdb13b56490ee993e5b9d08dc638ea14341acda6fefc6ca11b5d8f344bcda93b35a7e6d49c9cd9125b3b4a787c6aac8769433726860123ba00e0e7a626cf7469b2836c6caa1a871588c70d9ed9626353257fdb07757d5041d9e736e1b13e1ccd7dae3f3eb21e4e9ef8110426a483105d3c10ad903d309338c2c429ba45a6817e92a6c11a9a2136f2ed4474f1403d9039c693ec236e91a9cf159ee4e863852d72687d7cb6f8c9d6a759a2cf15ac681612901b7d8c009467c8910a3168b20c51a04f9329f6000dede08189533be489c31c62f6c147dc22630fc42d1a89db3c3434b9af10db014dde22d74f1291370dde1829522409ce9128c0901b89f5c9c9d9d949d248942cf16162254f83f230e878bea027490c1189e14e2a633c2af5f80331e4e9cdeed460ac8127db49170d368bc4a913d56205c6624e6c036e0f4d1557b493a8ea8709c832f718bd85c9823c51d060c8bd0a23f7971869b01f672a82368c1134ca7d181905d0184e40d9438382ecc51772a53396819a259c4672374b7711841c71c88a0fc137d8a780912e3418b97b8cdcd12587987f40c5157a525c918fb345c3332da94d19144a1c197b8a6e1cc24dbdb6db6f384efba55ef6da2dd6709099e7e35313ebe85686230eedad8844c99e55d65a7b061157625a637615bb553a5bc64e947a79adec342f917934935996c52aaa2a204a7e5bc7d1d99ce4a2260a81c82f9c91e3b3500e5e68c38d2cdbb8f285b44a9c5c6bf5116333c680c46b73688e0be53033e56e846883f15d77e3dab822bfd0bb9103cd368a3a11271289449b4824125991481365a24a4553d42229baa1b6654207e4071d6e9e77d103c921e6781e628b5ef2f85e1f8c18b7c7c3f80324440b1720413daae2251039595681849decb595fdf0ad842c71b89a715483a89924c6a03b73fc0cdda89a0dba33b4430fc9d95f6237118c394630d59d7b97b7cf005914c5851bc626303dccb709721afc4f13458a18094216326d5ae9875570c3991cbb71634ce9aa473a6822884c7118794ec793a2c65092572231dce77005529ce77972b4aa51524ca49208273b59d2c7fd7a5493dddaa4cf5e633f7ae314c52e92f6793fd39ad9b5bf320dc3742e86d98f0edb9e5d510e8943db68cfaea3bde257f6118689bceedc76ab3d7b66394fdbba5006d469419f3c49e10923908c138acd913ba3fbd347bafd52dda62fd5da97ea6c33b99ab89ac8202020a01e69f1ea2d5a8cdb0f59150810d00fb3abcdbad61ad2b9571d2ac35dfb0764ee7b9cd231bafd0eeeda65745edab9cbd88f2ea3f3e2aee154cb8c6e71aa239008e20f21d16566ae08398434811c01a55ca6d2f6d7e8dbeb372ed53cc4d6a8d5de0f201a6e0cd6de8b42fd2048234392439b5d9f382f456688916232625e4cdadc948e3a9df4a13ce513ddfb42efbe7bee1b75cacb6c9145705c1966ad563a622cc678d0e18a2b73ce3963766b310f40329c6aaef4d36b4c4739c5a9c62e6edbba14ae54ea300ce5a6c34e2f95bafba5fa5d37a58d597d712a7b37c2a90c7bdfb68bbe947d77d1435f4a4777d15316efe82e4aedf01ebaf7d04cce707fb40c10a098a67c3af528dd63cb7b6c99be542d7da98a5d35f735a353981e427e4423576c47644457935db6b86e12ebdee91bef26f49d6cdf7491c6e99c50ece726d6a46f6c6868fa2674d15fba6f668ba88814e130fe28d13e9acf4b17d1377dd32de822fa265e30ff00a4def8a1e254db7881f911a45b9a8633591c25695347afe17eba992dd2364e1ee10d631a2c5bd97723d8ef5354f58c1b0dbbb6bee924ba88d73c681cb412d987fb4b7bf387c3b099968c4d9574d2431a049aaf466822cd32b3226da459ea90fbc596bc29822377244f8e92fbc956119c9c1d253cd927fbaa0d5da4735a49fb344eef74ac6fa66aaebc99ac4b4328994699151de5d34a1fe9a65166b78cc7498c931f4052020102fac1b4b5e8f735da4975db4aa34fd1b7bf441bd660573412e1687d709234362938397207f5d99f3eed29a3934a753b95eeddecb9d7681fdd6ddb4efabc3d75ff6a3761d8e81a9631c9a4bc64b5cba0dc3ebc2b199d17eaf73232ce7df4c9a07e9ff2c1745e28144ee9189d7baabffdde6ff81e86721998fb08cbe8bc4cdf2e83f2d139946f9fe9a3efd6c3d88f0ea37dbbbd8ed1ada69d3e9dfa6ef4d2b7d2a8f4eda4a75edb49df768c4678c7f6d14730edf7b1451a7da5ed2361ea1a5121d2a6484e698676e7822eaa507ab984d2f7982d324e23bd07572cd4c75dc6d73de543f9fd4cb75faa6ea72f551f4a652f7d4b94528a83524a51534529a594d24829a5946625d26b45a174ef122551fa55a9547abfd47587a19ca3b4eb3eee4b357e7ffb4c4bd3629a45667a1e37d248b7a4f8a08f4fa296b58bde94cefd553a87613a5dc2a97afcb2ef308c746b755cf7289f8c8ecc09a7babbe99321754f55d2b9a75ea47327e1780ea72ae621b62eaee941efd18be8b72e34a254f260a193fd05bd7c119a3c67509e4ea69db13c451867f4893e7724d752c293a74f9e9e2c325b66d054cdd9c4499e4524cedca1b8831a6806379c3bdbbc22a88a08d4134e27363c441e160fe6cedc993d789e3ca078eecc9daec1bd84b7c84ee835dc453b9ab5386c9a5463978753edfdb5ddc31b27c21268aa66ccf327679b2d5e1325b365eecc6edaed93adb933adbd77a7795a49fb28992df701dbc3f6699eed8bad0edd706defe6ce74622a993ff38a3c67153dd349c7162d21ea20a01db225c2b09956037dd3e666e27411ee9c4c9a25ec2e0228dfe91b9df4d7e824f0de843f55364e95bc992d323af332a48f2e53fabd7d0da574903efa8bf4d1c4afd22fbe53652f7bfec754d1897f78915e3a0fb1d50316838148b8f4b9a68a9ef48d7e1fc6d8c453c695b8321fa76af4f5177a5c0e1b4702149863e4d0b756a29d3ec66791a9768d6aef6f9f0f2dea6f13e1d40ea2cbe720c2b1c14dbe747fa16f5fe8338481b293af8b741291652ad14a9c14913c3eb14ce9c4892bf41d96390dd27358e2b492cdbbb36b1a4c20bbbbbbbbbbbbbbbbbb5b7677db8bea6f49109bab17128936d971b6a6e6f6a5e67cd7ddc3b0d0bbc33ce9356d7920b0d57bf7852ee2ecf62d13e14f76dc66cb4099306bad5ad4773f0d6e91ab3671e85e8e07d4a9cce0662fd246fa488ce6c5f5b1cae10766c255eede5de6ccda7de2d05ed4bb87e04d7409e52ac5edbcc3f4a13bfbf61cb2778dc3794e6a9f30dfbcac974344962e2282f5b58b7c98d9625bf5ffc47d1357ea43f76e441209e170fbdcae3d9cf7f09d1f6afbdceec3fca9c13aab7d2825f76aad943951552f71c87da806ebbb5a67d7dd13964570779106ebb57ac366a23613b9be999038f4ebb970c32ec2a2d22c757ece39a7176b6863c53ef44e280a1ac11f4184fc90427e48ef2fed7a892ef337c796ee947b188f9029f707704b8cce7d36b6157af7d508c16a856e635ba38732485ba0f7d116987d555f57347c61dc9adac18633d9e2983f32dcdac9195b52d1129ee8a1bf44384e5508a7ec438fadd7a74afb08a76ce83b460f61191d9911aeb161b5ba0b615bdc778444b8665e08dbea1e5b1e76bd7c39aae85b3542dc568c090863121610862203c25845a6bfb305e647e2e6a1b75fa4d69334190aee0bee15fd35bae8f222d14f18069b91f2a5d24f1806bb33a4df4b895ff7270c83cdd819aead10dd4dd3f8c1c34402cd1a1bdc340dd2110eae8ca3340b77da9d5e2a992d293ccdc2a45942a740d1a5e485c8f2d2a01514658a61d1448b08441095f2c596e94727c5ad398c403274e3be93e2663f82d450c4ed3cc369787dd83499c62f9a88c211b01006115f006ad1d3f0499b4f0060dc27cd92d96878862f5632dc50dad8d4e98cd3194e25ce8c2fa5fda92fa5e11f5ea9cf380fb13503c32e4e3db66678f8149ef1451a3ec3e50c34c8af741a3e199fe1433d86487fe9b8a21df5c9f8a44dabe869fb1c9146a8cffd2493d0279334d8207d0acae8933c9fb459c2f45367c212d0b27c04a81722486f634ec99ccc268925d08659ab06c15879f566d4dd4d273fa7cc892bf311cb9d06230f8e1fd927cbf679992371f0e28a892d8748225922f9b6772a49dcbef797e80244e6eef3f9cbd9261a8d46229168f4eddaa7e592c9f4fb1da5d26fe932285f4afef4d8227d2989533a4847f98efb139639fd1e05cbe8bc4ef85e86f4d8eab24fcbda2512890359d3aaf921448ca08efcf8ec072b7d2fae982fb644210dcb1c9b371ddc0a06d7b605940830b8f9b1a146534a299594de076aada596524a297e5981ac06a72b8c44c861244296407f3ed62c210ba6074ae99663cf12262cb511e7ce6740e858a64db46850cb6d93a495f4cee5473b3f3932269bcc16dba26f9be8a265fa1e22b2c03448b7e89698a9a27247f2cc16ee07faa2696a32c5400fc9b49443885e8a6bf397d2d1ede84ec23542bca5935d473bf7fbd40eeef7dc2fae21fdbef4e9a476e8db374ee9307bc2589283344e499be812caf47208a594def9d5a01bf64e2b992da553d3e99ba777327d67fa52db675aa52fb5e11f5ea59bce436c99300cc433ad9055c23a1d82a6aff4d100c38d2daed59deb1b7a243649a64b32bdc1992dd6de4b7f3fd27782d11830cdd243440cf490fb23b5904d1aa4177d92678786505bb48a9e760dcd939f2f32a5b28b4cfbd3b2b599f4c556e672b0b971dbc4151ddc8a5d158cdb8f65ddc5baa8427ff821c8cdf24066196872109bb3937e1fca941ea477c1348823bad8d3eb105950f4acc8928a200522c7a0adfb81c082e0e689551ae4bed1bd4f14fa40d64f4d5cee5c7d78999abfd3fb8bbbf7790fc374267e79bf1816a7273bcf4fcb327423a523f4d15f211ca76a845f9faaec33845753458f23aed07bdfa78abefb5834a54304b79eabe1a6f1a776bdc110c4f0931590e310ba004556a7bc46e965f6507ec391f6a53c8739eece7338dc3ecbc50064798e468a71acb8707978f4f07129b970c3f81379785cba1379e4b51c7ba0c8228777053702653f6861cd29d2826cf1405bf52f1d2324bac8cf8b20f6704d5304371408892b93155d4279561c2382084ed7d67329ae5fccb985161db1d08105686a9db3d67a591bcc76b3a19da15b35cb74e9af441c62bc2114b78883a4efc6e100b27c78036a0000c061cdf2214aeeb8d26f2d6e189b8820e24043ded0ef5f20b6b060a20b6509c30b510b395221776c61916608bfdc8f724e29e7074a1c06106f90af3400904359664ef89899899cac3bf372f6f8653bcc07913ae8e2bbb3ac3d309bc956cd128a38ac5bc5f2f7e44597ea925db6962d146e189b6020ba3421cbb3a6aa87980b441c5e30d1a5de47f2842c1044e6791f335319ca81c6184331c31ffdece7810dc6b366cbed97c4a1c61be2258f96e3c3d8042666d0c5568d7c8c57e9d67b6096090b2cbfb3de5213216ffb96e60909db431ac9939ab860e931a3138a438778c3fcc42c23b05e592fb86174d2835a7befa39356d95a71443de8bad8639ece5833ed2079c64fd3deefb2ac434a2badd92773d5c1cbd9bbfe428d6adaa95671d75f988591ce9a793674233e76ee7e16c3997bebf8b067e7b066cba4b392529a6913d3ec21a594ee44eb7516636a06a81c1f67a593d619638b26a594dad43cdb2c61bc8c93ce39e7ec8e0b5aa0546a418a49062d68215a21597e5a1a032dcc89238ef25352e9a493bdd560c5799b25c6497134d88f361a6c1afd15adda0fae0d2058b38cc2e0aa3cd3ea0ceaa4d32c3dddebd9d922a3aa3e461a7d5e5ec6b85e965846907a1bb789626b53cc0210714111d900446e28c71e17d428e17239f6204186fc9ba172d68344177296654560308a9ba98aa018ee28c71e247e4023dc9b630f1231247cae82044e0ebfaa2790544a2ae795d3e6d974d22a86541837a4d9569b9d5a39a2b2fa7431c618230ebf1c278d94ce397bce49bb637777ce6194360a3244dd21ae7cc7d67ec0dcb4717ee243b905503f8632a8e7082472d8345dd3437213c91da3a1a109a29b8432890872b408430c2ab0410f6243c8a1f4d9410e654c62d1319913ebe8a40540c8a28e4f72d7cc9c808830960862084bc08101931cca224608914389238fe48eabd9e24310648083253b4fb65085560b213721db196b42104f8c410b1e317e5a3d45ee5f9cece54682152a4600c1003f99126122532264c8f43674838239461c0c90b96ba11b5c4619b182fce5ed59e8c6c6534365468352e786a8972b9ab42afeb42a66ea04045a5d11c4a26955cc4ab069d56c41a1622d6092eded9b25a4ff28fd8cdb90e2db9843546e154185ac69a7a59f9c653c4520e9236ed8b19f1690868606081e1a1a3b5bfae7e7a68b4c234f27a42e58790576e1ca10e544e264c9495114f6ca1863dd682127e0450d4ce0042a827002103d4e94411453f8a6894c4f8375a2072567286591e98d0d5880844c8f0adda07726891b90a10c487821451450340f144540913296b08230327d4a8480154f38a2022504f2618cc1586b2d8ceb158b7fb960228d1a8df1f33dc4263e31c618e3a682ac09b3091fa8481423dcb630ae9cc748f923251631461a638cf81469157725c4adb13d7b26d1c959653fac92892b1fb1684a29a594524a69a59452dab19e2554fc4049cc162bd151634716d4fd4e344b89c32c561da02eaaa88c9260439e3f9805716b27133058b58cb3e7258e9f0ae8c5157a0a647f5149bce8327f85ae4f01f0b933f245e9e0cab7066ed7168951d4663c7d7170e5e365164839f6576badb931ea92727833496679f9cc8594e76d6c91a48c73e5a3ca6c25745165e200274f7ff28bbbcad6de3b43301603820f9cb83b477cea1463c874708489c9cf33450bace8eea69466b5577727c623d6c3478cbe6009cd6685c4b559ce9d314615f4102aa707c6cda1bd3f3589631aeca15dbb0f7d1e0dcec693ca39e59453ce4b1cf6889973db68f3e83135ec9a314d9afcc4f468c2c34793263051a820df0cd44595f06e3d37dec881916e8903144f1cb9993f11078e1c4bf29c3c730a2249b6b879d6501a2bf21444bac8da125411f2c431ada5d1e012f6abd962f1276d7b848a1f1b6a4829aba4524ada5236c1d3b35b543ac618e3a3c4df887059f3190d54661744e4f4e2662339311be4a9a494524a29b5d6524b29a594ca15488f70795e6ce0c841a9b53725c79e2558b841ac0875b670349e4e94c3a756d5c7305cfad076f40341672b23cfa3c41b503d327dd5e8274a5c49a963a044707e86700399be5a8066fb6dc3a7b8321fa2783ee4792e8227587e11661553d639e794724e29a7aca11b92ce1ccdd2b3e79c731291854b26aba856c898f4913e32267d60af4019963e71a51e003cb8dbedb577f64bedc09c2af53a890a52b841196a603ae11236b265bfa38629705a4038a086a755821aa6c069592c839aaa6aa7aad630058e9d42078253e5801a9e96c51f0d2f50997d5631a2a422d8cf6c1ad44595b0d264da302b1aa48ff93448e792e812338d5d6be9ec209a0eea201a1e164b071949b89904b61dce9b36d3264ecec4f521d833dbac26b38c7e4183324c812850a6ef9d69edbdf3872aa1c8d4c79b3eddf4e927dd12c21d9b35dd90da759ef499dc073355f4351c71a1d8bcf8dae17ed238b62a9c357348a6dd1de4136b42df1d343b1ac213e7389c4428928963278e3671b28933a5c8f491dad4bad32db932ff40e9251d22aae88bc834894b95c8f41587a84c73ba90e589a94d5ca19f5c1177d263267786120a9c4c3f71664b131155f447e4c4c0064ada2674a3876472c68c8a26964bdac6da7b51a89634422a8de905b4b9276eaf4212e85ca3cf3d71b5980b26ba94327d0f99e6ba7c0d5fdc17fbe1c81eb7e87dc3dd8e6e7b5703127706ad794c00bd10b6c686d5daf01755f442d8d60d2b35e4edadc4a79095b8c6d6c5c5f71e0d43bd6733e4a641b06d6e6ca88fe842730fd17ac81c92da214b452a71673645245132809be8c283835c95c835895cdb87c329b9e18dc3323aafd0b9cb78dfbe830b9df3bc875216bf369cb2dfb8879e7a710f9debcec9fa1a90b832f4e1a896082f727df7b54d6f32c8620964a3c4460229b119722381b61c1b4cd5b7cd270c44a14cf631f10a64693736077da4bb69cfee96565861450e591a62d20994859f5a4317eeaa4392c644972fd3f7882e2497d9b6e3fadb2605e9690d5f5c170ee2dae7a05fb1bacf292ffa4ccb1b8930ec7e34d36a199dd7fde832a48b2e733fa29d6374993f19882afa3801c0832b3ae9323342291da48bbee37ea44374d277884ec2309d1dd20a3be0e2062fa0a16991f09daa968e9c4662cb0be11da593b08c8e8ce8252c33ba5f6c75a21cd1a553de67b28747df2a4774e94c65e69eb85a5c999d3d9c23aed05beef3eea98b9b61f0ab22973ff50b72460e929d9c06737696d41c6a9454a0c9a1a4024dd67a882e33d3675996512a8f9044642a83c840f33386259206692c94e1304621d37758823c710f394d7bd29eb427fd86855bf151e04fd735721a046bf491adfa4e466a73844597104e7419407499b9be91ecc0a20bcd0258c2cd00b073654ed114c52eeedeeb87705fe37c9d33557513e3f60d8e146e289db48d932adae64b7243e9244ea18323b9737de7c4313861840b4b2801c517805a7d443a914e52605c0d770e1ec2adb48b066be3cc961cefa1eff01ec232ddb94e86bb875316df06e736c5d5b004c2d24983f5b80877c2d480c455d5fa6ef694dd54442986c595fa9211892ab5b6760521503124f4840655e8a20a08be0630dc50f2f0742f3ca28b448991690c43a6d18b4c1faf90e9398d4a9ed992b2b4ebcedd5afce2be61d8760ea7ec630bc6bdc3298b6d8b6e53dc8aa5120de322dc50eeb4f66672b51c2fb9d76aaa68c6e4d21c99dc28a5ec2ad2308fb8428f852b314824d2189dc2912194083231b129c1e0081f538e3d4b96a084114422a571efbdac958bd2d31a4b9ac8deb54a744055634224887e712bada75bf670db28addfb66b1bddb62daae86128add46a543bd55e1aa477f9c034cb8b521bcd224fa190020d6f96415759d1a55b624b8cac0b4417fa18558dbae25611df80d8e29aaa992cfbae596bad36f6d859638d353eca1669f871657ef20702ab42f0f9810abaa88223bacc56b7b8af1475b839f440a6b2d5511129ae4452a68d6f5cb93adc4c7f587b5936bae5029dbcaa32ca31267b8e115d648e4d5c54c84c0306c61583b9f75e18d72be67ac1b85e30ac55ccd535ba67ffd8c8227ee479d8c82e71ccd7a3c196f23c7ac4f9f81ecd127177ecd851bb7da77d3d627834d83c9a45daf77b584dc6f0e8d119c681590df6bf9706adb0c28a1cdd3de2974c91c3db3fb8a1cd194d271f35d20580501271ac001d820c60a045567d2ceadd034518446826788191eeeeeeeea63dbb9b3261c2c4a7079f263e384a39f62c319243544f7592592a8c484204303ca9f72ddd979d861abac171b53ed380c9cebd725cf6ec3e7030cdc2d5d75aeb39bc7d309febd560cd7e3badeb05f39cadb8706bbc6aed898248ceea911bdaf823457df7f282ef6db63be77d355a555970fbe14aae7498fdfe61766d2c2348bfaa24c8fd4af30441e719a748fb4bd355c7d16870fb3e55f516b5c44b532f6eb8ca3514325373ca6ccaf53d82748ba8de064eaebd53a35922cf929c5cebcf92277218bb90118b5c3fe9aa59263dcdec8c08223c8944c8f133a24bccf2973474a3b69438e8434dd3341ccda2e16e1c0dea109365966326571b3824061b6c9ea661a359ba098d1e2880624ddc7e54e95ad34515ce4991b8420fd420ed2f32d56692a964ee34487170399ce812caa3df67f473e77ead440060dc89a495a4e44b13091039464ae2cb934a1876b19d2aedb0119e69350e9be7c6fb911d7d8461a393704ccd9e58f2ccb42c0ebdfd3b676862dbc9eef9d9fbd15ff7a36b3a0a86dd9b1ae5a37b6bdff7afd1efe8250c5b8d7034dda27c2f5dfa8bf41289849a2aad278cf4128e5355fae88dbae0fb46dbb8e38a761957343b5517a859681660ec9b21779374644b2b99aaedfd2e64e38624301f7ae82fed63d5d1c073650ed10fc19138589c9e1e239de6cd39499ff6d117e7d612ef207d8465644aa35f1c5bb195ea1da4df7b1930dbefb8279d84654cf8e2b7ec6db9433bb774424316985802354849a49209e5f6d36442e9e429e5744af9c5d7963eba45f9bd2d7df457e9238cf28b47275de6a67c32a593a8a4ac1775414c95a2190120002000c314002020100c870362f18040226b931f14000d83aa4e6e4c1ac9d32888614e216308318400000001181111d2b40920ba942a8cbde81c14c3fdea0a82824b6abf724bd5fff5abba7e36cd081f42841a3e8d589954911c46deff43d4a417d352a5d0bf67278edc51fbabef31ae69679b2f1dd7c53718eb4ee3afb8976666a8e4a974971f14e7bfdf38fbde0ca8c6dc55ec5d42fa48ad8d2ecca73c5f6996e2f723d936103f66e275c9145969712f4612c0b4531b223d07758f54d2fe9d5b254d19c99c68c975561f8a97eb15c2e93bec2a5bbd2d9fac9874f4cbf027ae16f95b9dc32c4f565a0d2dde3ea347a88ea53a6e693af227ab8074de3b2db55046dc750c71d88229e79b6ba4ccb7bf918896d0459cd1e659e21344a9c2c948c1f580b88168fd34359736516b624f824f43d4cbceaa5157c429c9d4203232548654acd9f60cf4d5077d4f4e1249fd985da0b1dca5d7b1915f5fb94fc274ae5616a5b25e6d5170303271e1b05d68bf326a82f8d260bbf471cbaf7a35e9025707ca0043f5fb7ab67a840b8b3263ecc7a7ddcd4ca2c30387d8b46b73f237b8df39c7a7e7683cff2cbcbb2193eee4b46756a84d1c2b465119c01182ea14d3cd3412a64fa67c286e7f82c3ef05221cea799a616fb80a57c5270400df974dea340696b8046bfadbb678cce68e7c8d00faa8552057e02b5fb713560ae861610cf83d2e4e6bf81943810214c7fd0402c44a772326ea421f35d8bb1800341d473566097ecf7143f5b041522aba2f3f8bca1d41f35ad19a653f799877d78ea3a01b13047cb28f6a8a34ecaed45ef33d6bf160c45298e1c6ce52a533d086fe5afa71844882ac39f71a41367c99d302a77f10f59b4bd36df93aa7b2d1552dbc1232add751559116803536225b4b0a121dba218946e5136d0e835a344bf0ba5a7ce6e16d03068bdb3a852a69785b99a7949d32f24b00937601115dbf53e1e5f7a7287eae0f4532a0d76323d926d91bfc7010afc08de1a571209d26f999481337e3352418b104402042636689f359abcf1d93235f48d5872853dc3f7f3ad2bd331c7cdc15c9d26996c706b6d20785c8e81f1f91cf58df09529a6e46d78a0955eed735b51fd8ddbb65e6aae25de354b86647473697135a38ab943b7235353d654ecb64608e63cb11a83e88a9c62b920735e21b7831b5c2f39f289cfded3b3d641d8e781679335c8a9079e768c77461e814e3e0021cc9f25738c7225091cf68dcb2006da4adb010fc4f8c0546d0e76c93b1b3224ecc30c4ff942c4781139cc82ff2293312b3bf47f2aefc7eae52618ec3c09fc1d3df7203bc0c850d1039b68cbddcea7fb8c622d6b9d9c07aab8a1e94e21dbda163626c5317278f0f06ca4ba80220e22ca74839e36d422158da641399e40921df24305e7e82ed435c376bb999e24e1260d285b6a889a0d7228a209187a20001d53b9d38256b37eb7b20a24fc8d251f69ccb4dcf071ea9432bf5f31467bf588e446c442d0a3fcac9456c150de2f062ac465c443a4b26f569e763cc131502652ff50963634a3b7297ed7edca438a27f163d318a37d428c664bf4e2f501236ec7c18c1d4c97aae59be2c689146c1d4d0d90e5552f8216bc9e2b6de2c813ad9c1b7fc487a7c1bc86c4d279e2870207f616f06022e5fd97eca0a0f0c05f5271b51f3d21e15cfb147bb692a72ed7acd5988deeda8b571b821aa63329dc7676b69b753e1e288a4263bd9221bbd1271b345f8a62c23ca5c946d31b0ac65ee7c057af86d8e3ae749202f030baa6ca2cb060cebcdb88593f0733d995bed518ad7b36225a57ae709c23847026329eceed3d0a0b680e29db470a3ac7ce5b80c0d368bc1a3993d949e9a83e1a4319b740b02e58ad6a0820ab7a017adb3d06d00c8ba74dc4ea33e52ebd244af9152de63303c1e2b38e0b66d317bf611b9fb039518c189f8db5f6a5502e2c9210746fba55065f4631a725041fed2511b0da29ec8373c15a5efa4bb7eba86802f5d5a1dc854bae79daf08254c837fc2b36db3ebe78d09d7950dfbe9a5405010ea4de4aa0807a1340e88284e10cb8d90a2f13935479c3f43df595b3440f2889b7f35d87b3356e3b162afe6b4927ec0ebe42392ec9fb999b4fcc2588912d430e4558d174f54bb361c4ff8ef4b163ec0e790f9dff1ed7c29cf8bc9e64f572ff7dd22eca03f609bab34fe18a4762c25593cd178674ea7b502874384956254f085b83fb3e2d7f7e318f08a01cf8b8c49f495ee913b6101418c659b00c640104ede37e2a3a4024733529d721d4003b5e5b786c29a7388450b3a641ac258ad9091b9bb5cff234c89b72c886b043c21cbddb5c6102d24264426ba7fbe73faf96e1dd2e69c50fcb430792c64a0e3ff061b0439e4c8a88794aaf6d299ee70f8fb3a79c779936d6fafa153a11be724991b0069a8fa52ecf46709a6c4435665917ec884383125606c7775c21a035430de4293ac57cbbda062ccfc23f546c2be6049383bd3f83964a411475d24fbd8c9e43a6712183b120f5009fb080eba7d0b0aa3ac2149a8229f3091a94a820c14219ed5f5914b32bf934fb7ac5f128218b94c128b2b691350239d536e907b74ac039f3f2e69dc721927668118607c1b68fc0ab47f85e79026b2ce30f14c29834ec3f14ce5fbe05d0f1e4e60c1d9a1ee42a79c41d659f2f24b0e1f82edfd56bbe680cacf94694a1760c3144333d55bc7f4d9529d5f8489e2350ef480b787f865293c5152fd9ef71c432a358a0829676949b0fbacc62b9b36f623f03ddffb1e3e91531ec8c128d54407a1e2b82880c465afa49d2fbf0fdf099a8b6807bb2f881ee22caa658f6ac1f1616f133d5ca35d2193abb8d4baac97bd1c4cfc3128866f8eafb35ca1ee82f2b005b8575bea9f27ec427885d50214f1fe6277d71b09b07130556a2cabf00ed30c9c20d0dd9fac0dd6696baf139e6c07ca531e1c8c6e1a3084b0e6e0d50ae7275a2e14140c8696734ef8fc243ed3bb27ec253e2366b99804ce1f90b094e00a5826500b1ec278068d9c5bc017c71c307331f2b332ecbea224bdaa14fa6357ab8b300d2dc81d97144029a9c1f87efd592fe367c51b3836c8ebc51fe6b8b85c123e6f92a9fd7f0dd62bcefe2b0f00a51c75250851df3e74ba4e678e5c49d88dacea588556380fce909cff8039b82d811e0585eeffc727834bd8b8dc6601573e8fbbfba51489b97f8a12d8fe4849761f6578df5ffb8cc4a5ec3a3cde9060385227f28411e8b282fb124558a600deed95789138c922928e4de8c03a82eff7fd4d05dc0501e400388efbd2b91920156622d7f214b60721ff5e1fd3f75e121ad75f76fef59fe249d860b505865d44e486c6114580aa86ab911ce832c155ddd186b5ba2279c00a40c89261e14c926b4490364d17d1559fdb6f79d73482d6b400d73fb05aa973a3fa096317964ade506c46db2aa910f91ae70267c83d62a701817aac49d2d8db3bab47cc7b6198bc75a327cfff3695f557dabd673448d45115a151138d2d23dc5a31788916aaf88f3ca1bfcfb1240e0c32ad0eb13dc1f5b6342a3090695c1a87c735bab6f26a0af94b57b5480d4c9b4e91935899a82c662457cf91a7616ff42d541ba3ea0d46724a4729dbe6f5a063af1fcd6708ca048152fdab2efc64e85f9c2f93054524e7a21a743ea9824712d23921a4a1302109b4783609a22ef39407ad727ce30a02ef619ce69d4886502ea8703104510f52d1d961f642d0212b0bc87d6833b8e34fb6f4cdd55aae01a63fa305fe141d7a6b20541e3054642392c0dea3984fb6f7b3a8deb36c1111eba7dfcddbee491bf7dcf1fb590aeab2716cd9993af260eabdf21e3323295a34bf5f9583f6ad63030ccc6e32a2025c429ae047640390374f01822fa23c72ce5a9dfbe386498e706069f4895cc518b49f9069d39b2587875200bdbc830372e4003d57ac6448bab4402224bce39b1d2268de6cfa0e1bef3232dcc66810fa447280227ba8c81694e1b55a060295b66735bec3b69929d90e8d51db54bfaf4008ea06e5f7eec487dcadd53cb657495bff8dbe03d74bad404af68385497799faba6eb2207b6a943a63b3f03c68c80f3275bc1a0bd332ad79416df200bbce6ec7c8551de0ba6939d0f932a171ce482f1de0fb926b94ef5685febe1610cc3edb4aff83805153de8ea2e9f9e5e332a9967feddbc29c5a00e17b75b7d59b2a6d53c249730a749b6540c870f18388f102b085e44bdb44889c2d956a70360395465bd0e34b40ab5c009f2896278258f705fd28c2f2596a79c10de9d6657336b44f658b390e4e4149bb3682dddd2b2fb48dab1750726bbe1a059bc60c0831589ac33f750ad1beca08f5cfcf0a407dd8c09b28fdd3d9ec31fbd5ab1fc54978bcf9e6a857bd794c7bdbb3a4e9298d07ec1c855ad8030b0520d42df0ab770317a3235f78d1281622604401195becbee5143bada08130deea7f2301e3b6172d7a8715e223bcf31645b27e0e558e5e5ebe610ec4a8b73ee667fba15728dd0e2234009a7ff9e6b96579a3d04fc84dbb5a277181fb379315dd0dd7d820afeb4cc69668a8690d493f3a54604f06fcb73e0dafa07befe56198d76bc7e67d47da62698b0f3b1047386e110e2b5d84da07ce4930f25bcd6ec27fb98c2c7e52f5a4e3922fcd05587c3fcf4649449b8fea7cd947df28f9da4f6506fafd229008bdcb101ff1ad4e0493eff08cf720dceb6852e5d40827881130e003c2ad9e54f15794eabae60b5a7d29075d40c7e9c264d7c5c426b00719a1ac3369c1fa29f1c89d192e1304542709ebfd8a15e724912936aa3cbb48ade181c70ae8b4635c3d604a9686e8432bfe909f6365c1ba7ca85fd3c424f9ccd91556dac0a137022b57cbd5615a189f8c4ac403204bbd61ddc4fe568aa1b24eee6676d8e1a50c4e4d3aed42f21d0b0aa73a806cfd3561657979d4c335be15f0579ef847107b0562dc126b454e39443267a441722ac106583e7e06dfbf9704290b659e9e11ec901f31b00dfaf26e5edec1341ee2bb3260a1872ae7b471307bb97c112be0bafc848de2b7e7860d62e517b6f7516b2c5f26493d6f7cc9f2de67c93e44214cb72c04d46277184c4fa5a31bce681153484768618cd68e147553953517ba55c0ea5867a4088e2d480d31ae67c9b6a3c6d6bc9e21a32c85d89a9d4733ec97e5390942daec9e6d357b2609a6c58f20a76b7e0040ca48d0bc2fcea5132e9ece05fbe4669e275c5cdc02de7398513c79190f3ea0d16410e7bec85e7b055beeec354deb090617b64c72f4075e5ce16e590b599ad61cd043b603f9085be0d6af7d586e7359016dfcf26e5e18abfc99b32e4915346d9b76309d25ee98f7da743adce963b25b7eabf7f976cbd9d1af9f2f46a287c8cb79ec0ea37861a60838c00d2ec566bccd4e812f0842d4db134ed77359df06959eceac428239e854052fdd86d14941c71bb9adc208551f39e3329769104e0c8eb24742735b828c2e2247d8d6453ebb881ae45ca8887521833b545886039302b9ac39ebae366307a4d9d9d3f4f40276d073282e3c51e5f510da1cfdeb6ceb0f45bdb38a64320810a8efd61e0527d6290adceecbd775d47ea2b8f51cf6d06b68516b2dc305f11b656f6c3b6d8da64d4c7741a2a6c056cb64713abf8bdccb655be03cf8292f271ec0e8a468391b4dbf66d975a8005c418b8a56bca370a448b59aa1b37294616ca10c59fc130d7187520dc90a6b6864d2b7432d4ac0e3a7c87853c8dacb90c9630d7c2272fa499214ba803da96f7aba412410cca108698f74e0b2513748833427eca73a3e79ca3c54c6451611367efabcdd872406d8d3b28abbd91ab70c0ce1542f4464e957a59993dbeb2518961ee07201170b1049a010c407c01d9643bc42948e676f14b6271a416e05cb140832cd7601a2906a16876605ddbf5228a00b78efaab6662889966d902bfa16467ca55db9c9dcbbf43fcf4ed74d7bad523288f494f1c3dab4e2422c0ac4fbfc7e7d8cfaed6f97981546c5171023e460c447414c1bb1fe1807374da41f78d4240c54885655ce55fd4429484da394f1f7949b0239512fd02c2ba92398bc4d7eefdd94fdcd14532eec727be4e5c7bad11381c7c2544a7d9b67a5826f11b1e4776aae6eb636c2d0bf32ea995bdfb803ffd8a7a565e23587cd66c3986ae179f6b129399f286aad7e4ce26efce8532c01e85850ad58f609eb6370a7dce18b8018c85824f37e545b95df1b7e04638c11546c006ba5c904212833b48c0caac3a21e367370cc100df488d20e221d1c071e06c16c28682b7a877475b6e123dba7c4420809d3747b653928c2fbb55a8614be89a5aec4e20b36b0430939971975ba923d92c54a8fe760f14b5d9e78f6141234b6507fa84b06c87f47c7939de2dfcbe6f910c3d056fa4977b51eee5781c4c536ab4af17fd623b417e3be4c567f18c29353d42534ab4a460b55be0bb5eb85295b16697499d165de1d5f3e402d51563c83a3e25503ecc45343253ef75d71f358bdae4fc65dbd7397c8937530371b34c803d3ce74b464840b595b84ca08671a610281fe8c1b3db9ff89387926d21acaea6967ca3174cadae1dd6585dfb606ab396b2827e0a57195524ff9a1326d427964a27fa8d3dbfbff44e550fac481ee644e1d89552af309c9efe2cf841d2126fee500d5802f22b60411c899e14b0ff836ccb85429ca8a2050d4d623e691184941ee4afa9e29ef8b865110d6861c4e2f305d9a8351979f4ebbbe759ee0cc9bb1637a78285897fd321ecb937ef08cac77666e5caaab72e2355b11416ae100b304799f5d416e59066edc0b833c01a3410c1b9f04959a1b62e320316a9268d7f1774a215ff21b62ef2896b8353335a32903abd275760197aaba2c6d8918d24424eb0f492315abf235c01b1a29a8b713d121f9f32e0d48a952c2882191fd836e085cceac673cad2f847ff40292e3c12f2764d29c4b2931f2c2b489abacd2b32aff72b1b0c58d242fff95c88cc99bd122d147f6d6a270403c0d8569dfe492478fb051fdfd5564286104b78c1c9155c048000d26276925961efdd6b8feae919f1a5de380f474007b4f9149ebe8bb5e229e4fea7ca54e11efda06d5ae5b930406e0d8a94c16bed42ce73e6d217c6bb5fda479247be06b8fabec7b99954952feab89ca34eccc4a3039c527955fd78f12c85f3069a7758287c57c27a9651e4304e07e4acafc3f6b56152fb7f5d0484656d3c378f5ab38346e70735de0461db6ed4d491f9af1699a0cf3dcf6aafbc3b7a77a053433abc351a6af39d59fcba3aea431b19d7571ea22dfb11c71321ace3b8c37fe23c874766ff44a4320a75d54130d80ec7621df0219b8a8d3e116f5e1d0e85a38f0a84c15dd40af3ed4796a5291dcdf7b012b89c528a348a06b5f9db31b4e18d345f36ded3be82a879744cce0847b0e13fba15c013ae892f0ee8c7c6840c1a5ac7318de809d9c2ea43f2e3a5136e8858f82bafc7b14ad066120226191be7ea480658ed5103c85d3224b110359236e647d5163f2e2f9603a6851bd367e395c84b4898196faabf63eebf1e4314493bd14f09eee37c4f42b4b86de4afae186f5b5d75fa5941dcb0ed5afd66c7f4070ab0b4585632d3c0e8de767d1865ea03bf397cd50e4fe0d7e0203a00b656f2003d3f6f591ec2ea1c6ac7542a567e39c366ef52ae892915081e360014adf7a594227c8f4bb03c4371343f4e24ec34bc23ec80cf1e7db1534186dbada216d7b9cce945e87614222eca43e0e33304c05939e44f5754e7ede6ad2f6268fc142619351163e7b34778a5c7807ead0695d07113e7a11c176a94021c3ed9d0cef3e4b274ae293aed5d9d3e9cfc45e35f5dd2d4b4ae761229b05f8c81db99330c207131b319a13d527980cbc82cb0704fc465ea75f02c4cf9bcb9c320a9721e27a4e9c333133737412cf8af2b3692334d3c4a0fa6b0d6eec86f69c1f88582c6d7645c9cbec53a8a47658ec3e1de06c9cf5fa67e4031e6db6895b6ae9315f67339fa290511850ffdf64f1dd5ed66c08cfd53fecfda9b66c7b49d9fa242a835a9da124ac62891871c1fa5be35e0ad9eb2b27ce3cd42e767ed91e4486bba2ce72df8c112bdd0b79b3562ee5f531d0f01a97f64238ea8b28f054fe4f37b6ae859f56d17acea721d3b7eac6f3d7a85f2e8930bf1856b8ac75cde72eadbed59d53f717cb8cb54c56589f60c995801e94391fc4724e35f0551e3b301f17e40584ee415819b3b7fc12d92f3429c3b54079acc852647a023279c2d036a6e29d480618a9df651e150166a137efcc2e309c3f72b8bb590cad436477909f0b8b0d94b9e6c7e1d682d248d67403c55ce1ca82ce8207b626e4ba6d737a16c7612a61e93984dd6c28a46f281fb34122bcb7c248ae39207674ae287a1f79387df61395cb6679443e482493926bf10290ff5666ae014d6b82b6bf139ddd89a9e34460640b3caf0b70485f4d37ab6ab6362429ad0c8658b76b900602ae8ccad4bfa3582bc323ae946d1b75e44d88ca86d0e5f637a6150882ae0f6aae405ea12d6df77ac663e28ab6e63ac9d3b8082c951d2f83df4550f2e645d4892498c52a3dc023f4bbc5500a3dd810a382966ea97d0697cb4fbb338a4f0b078f367ba4938059f5ed83a54f39788bb8404c35309bb49943d730237444bcd1467f268d8c83c6356bd751820ee3c9c576d3e538d5a87d63e0fa4d35ec0ecc6c08976f9c768e593d7229d4f3e4670f7063698fd873ed5712c0bbc2fcb87a08c432d3523798cf0119d51270496dd5e2fe3e56132962dee12d2c2f3c8f41f8fb29fc13975fe24ac35d696fc52bde035f4134584a005936c80a4102fb56d517fbca6b689da0b0cbe0652848599c0737597fbae03741fb0bd756fd40ae849d8c15ebbdb1adcfce40091323cd7f0a2d06f0fcba69a35c0db345b3170d6d03b7cccaec060b39402f8ee244c649b2523db53c25b6b4ce6512e08d2dec88ec22b203a2901ce5f433f6748b6aff81085733a1174ec4a3c55219f0962c00d8dca47fb4953f768e0a7b45f2147cbb8fd0871a2f180d82af43d00b3cb0b88fd63ec21e89e1e2221d69c7bf8a01cac477750127a4d4e99ad4c9bb999ef51e493b8c8e39c305fa0b86284dcf6c1e714c40330316d555eddba7674a83d2bbc8762a22f69d4ae2ff64381a2fe68a6a2e07871d4f27a9df04aff1ee0ba714262ccd301c7c3e4f18ec3e05a0f9d49c0bf6bb287fc7611aaacda61533305e7b488d4e03701f8d26632847e523797198d2ee4c8c8865cf1876e9b2bcfc1cdfbe6271a231922616a2aaf372894f6f30d28b065e3274f89faf3e02a24b0a541f3d5995d509511d0eba57da48e857f40882b2a65683b1a48d59bcd823f34094b754365c2db67dee8857b277a206a21f42de6ed2c55fd11ffd8d665114c780d8b7de7e685dc7f6fb36b05db99afb5fb745d74984524677c6babd41bc08b1228a2288dbc19af134abd271d76de955e0c5f4aee350a12487ed1013310a72ae9bc44af61a650d1a8b3f006044ae90930423b0b45a9d669502b90bdb098f9b4ecc5c8622e7429a0dbcef72b0cf84c865a8e938138af67523985787f0ab9b97da16f9cc888beafe8823e0a02b8b200756d81ee15adeabfc0eab9e6a3180a6f7c7bb1dacc65de75bd082d13b1b5fd055c487c6f1bc423ece879ee7f87348cecb87b6a8513032635bd29017deb751c9e08582117b880f6c355db47ad76de14a141da2bb894063cadd5c70520ba0015b286f61eabed0d88ded949251b40a1a23b151a1877329e86cdbdb1310936f05cb4550e069192a93af62573c4b708ab221d94210b874e5825bd6b2c52e78e8644773420f3759c654ba1e5d258ade0963191b5a1dd2a1ddaca4669666deb77496021ed3a14f0ac93b0c7babdc59789ae8a7280149a8d38d1ff581fa38b94c16ee8654ca1b6507a9a2b8b0bbb06b58d49e109e2d9019766926667e3fb0c2c2a41e783f7e9eff953e86750555e0d24aec890e9951583924fe18987eb161cdb84b13161c8d41caadf7e0bd6f64ee815053ccc00bc967c84584fcdbe49890a747cb647d014d48deeaa024afde198581960957923c48d94b32e93af7f9eecf7c635c68c2742cec1c880f20207dde5f8ad586e55d6f0987ee5b2519fe8cef1379f8dce8b39a42e38889f7e1e4c6aa3066b780d7ef584c3fba1b54cb268ac10ea5db8aaf13f1ee2136dd87325541233fbee10f25b3d1515bfbf8ce2e3c66bb0867ff1ae219f8805caad54b0a3191ed46f7e61d18020c3c9d8f475d077e188218459fd796c46b7598899898b03a0464140a2154bb93cea55f60d6d6742781cb0494b0954483360abf280d7460b52d3fe84d95b60ac9ed9b977cd6b65cc9cfbe7b08dbfe3af58c2e8d8b847271e0bc4542c1989982a0a22f5453e0852eb76d6eb362ffe82e709133431e846acb9184fb4f44918fe97389528384c01f81eefc1a1fb7157b3b864a2007111d5fb554a6d0ff87097af4ccca2db136b7e23f79d6aaad245ff998800442ab84a424ba24ff38599655418db7c07da9376e5a474a9c7d81668f8acdbe6e17c3f49035e01954d57538a98ad3667d2b332916cedefb47e5202d76441e6acdef3c748ad0fcc267698327f32b8331bf3234590be98077ff14537119c4f2dcc7f64c25f2f76bb187dbcba8e5a227b4f41d43a56b6449279adeca752e449362c7d0818fd99a4baa620e7e057426b15b43daf1832c4219959dbc6534b6667d13312c8ab12d38f7f5ea7f8b69498eb8abaf8a716db958eb8706451414f956e505758890ccc4bcdb44e3ae04d1d2a21603c53367a9e6eb009d4171bba4779cba21816fe0ce2c371dbb09f018c59ca15813e0a04119475bab3c1f0d3ca79884a1ba3d8f264367db973ca699a26bba920aeffb8c98d4acd309a8eafc04f52a4f5d4ce1adda7e0dc7d89e759e2ff479463e34a5676f56f796f6ad4e5ee454660dd642cfb5a874d4886a402ce029744bcefe5f81bedb06b1dd5b1f63d6da35bf22636efe884ae42582bf46baf6e0592385a31c853d564e89ab538bc12ed56fceb7dfb3e037b93d70ae7c315edab48a9c561ccd9dc91955660d371eef1548c88cdb8acb9fbe5ddc4cf7e4f461c335e0f8a3e8f2bfa8e3ce1227b17cbb943fec9e811eb3be16b4bb442d3cacf352dc9feb05c0ee0b3f044dca5477f3994cd25088209bf36fdd82424d8d695d0c5a722aee9ecf28c352958b2882a901e93c4c10ca49e7973e17f96fd56b99efa37dc15a2f45ced1cd63b972b18c1f1d7a7fb6dbe4e483b91a6d842d8e141f3bba1277310b41acee62472f184d24aa2e6423a3943c05e408a20231099174373c763e690950b0ea6e31075d414818c4536aca58f98f5ebebe3488b4a1ffd1e023ea9df786666643d2a4b729f0060eb53886182a9f03435cd75f318c2549a79366761dddd71ddc21d5ff01aec0c9a33d4fb649f99759a393764df9c3db4119e8d73b53dac303a8ffa3f92936ca1f4abf8d0629837090a863d85775d3d4e26e99dcf03cca4c9cf32c9d98154e391b6c5ce72f28e912181ee7fda8046181f69caa360506644df207ee55e8a17223bcdb3719518dcbee8796a0a5ca43003a757a841e88563cd4b72eec2c62f2b081a60213e25b810ea0fcea7dad26861dd7ed56c98767fb7b5a5a6aee612b843a9930bfe15bbf25b6c2aecb170361c67faf22a5066ac01b1c4c9f416b13f4a1081604bd2054f9e46363b67866577d0c621046f7188ce6397ac60844e8d8a818ef1fa09e851958bb252381b84afc7f382fbfb9d7c857fcd79fc8acfaa2f78f88ce3899e0cf7146b5011880d1a1946103572b4eb3092ecc77c4d744d03229a4be0599007439da4ef36fc69c5f59b229a46e13c616064829997155caf03bb0fc07dcefba8477adce1d668edabd14a708fa5a496950148901429609dbafafe7d6ee9432376415a9d4ccdeb11446da6a17bfc3015c6c449a0c9a371fbcb7b24c9d75757ffdfca3f0c043d91124c776ec3b5083ff0cf93448ac917acd9ebb2bf44aa4f5d78b138c50cea407ea53887ba4c162dee9ad00aa1e7ae861a8219aedcade0f05b14374f8d12cc7562a4058c2620308902948dfe6099b0d6ab962ca116d6d97668413fe562fd1a6308ac0f25e8034d8366781757775c88417709273b14e5775f4aaae8d7592b848a2eea62cfbfba52884742dff83cb50b1ce52b31c2896077430c9b3e633e9784b6876e32d065276df0451a1d32c0cc6fbd87e75b93049d37e6caa0fc6ca82250742229863dc0c0dd58ea17249bd06bf7856b3e28d0f5959e9dfd0de7552a892821c1e9cebd1b54aae2bd5adcc79d6937997a086f2ac7e8d1491a27ecfa0e6bddf443b797689d04d576469934cd21fc9f80e65b1c8100fbf423a0a1ddf908dd24ff14bf8f05b9fc8506e4b8a90be677da9560c2afc83de491cc4b97f94403abac0f0a194fe64be626019bf8426dca240383b6b8b7f8ee2d330d5efad6939ccd898bd0d0d8e0142389737439d98512164a7c1a6882194e72292abb10cb6590c0ca258fc4eb274ea4e3c342bf379927aa4477b962f150394ea6832831c8fccf69a5d8a0b32128e18a4344f1551cb06d2950222ba23b1c8c89655dddab670f4846308349cb9c0232deec73a8f44e26fe46f03eb45e5818b704bc967528a9e3620ab508089ca2b9588d23a9b664742892c6927e758dc1223fd388b65fae4fb55b416935de3d050b654918b8e9f78d91b49b994a69c179e1a2d80789b4e1a70843111a8d3bb7350fcba57d154838bbc1dea4f9159001b5bdc71f35a629f69448d2650fa0032919423aa1ca27b606a6fb4256346dbd0a60e3384f5bc9e1f177a4205e8be892ca8f6919246029471131f4efa04f69f26a3d23a3ca07a57a993acda25b63457e1254400a61d9e4e5cdfc13199bc835bd22a848d6fe05801ae7b8fa8a10b30bb7d9c8e4cb583e8aa5ae4830c9f14b933354dee709f909c1df1c744e6124d06a7708e5b1dee475f0a2c06719bdddbc88e4e43b50733fb956e4033bddba17c3c2b66047f32f304e54f56db386d4a0d10e52f93b8440a164719928625ef4a683e9666a56b0320717c0f9f14fb5edf9fce986b23cff84a2aee5271ce5f9798bafa4fe6f6c7e7812711692e83363c017b946c0f91d6f8be40eacf2fb827bae3d54d6d4f9387b8e92f3f82a1392b25f22f64174a19e14149a533725048908b6292200567ed9fe2010c1a7ea3347727ee569d472d03e260065991e26af101c251ba23a7d7480643295fb203d5b7a3b3e6e25af36a9c47ad7b9c38498e771dcfcf31574b8390c9191075abab3f89d01bd559effae6fd0c75e488cfc6379b8bb0dd3be1f5374c87758c011ab5b0313a0b34a32375b190eaea9760f336c664952e07beabfc838b9b8a81a5c420bc1d2856a83b67e6ec93c1b6258aaeca26b04145cf218cefd7b613aa83e3b9cdeee9fb3f1f228bbd6ae65f874212705c407752a0b1d740caae552627de4ea66e87360a180e31827b7c9fb0835d9c7ab168706ff865dfc7e1deb6e85d1a544baf53944d11fa0f6cf35c6355e202dbcecc76c52f3d188a43db6cc9dd73a4f8469a824b196f62124d32370f5a85ea45c14abfd037b752383cb7c5ce4725f42251b4ddcef8b6ad07e5449de1e58cb0fe500f6251ca865ad36c08340110ece19144bab4cece6964cfe2cc3bd1d29d1f193260ce92e734034630f2a35dc34103de3a7dd0e8b8c03d6edd8dd277ab4a7785710528ba32afe6a9225ae4c188bc5ebd50634fba8cc59ea7aaf3c7d0d052325daa473d2871e2f3161fccc44876a1c79595f6a46366296ed1b0c1cc49bd5cab8e05f59855139b602dbb0e1825889b2315de3594e6883d720db5558ab61b58efb142ae4c35b26a14972fd034ce7a9c831207f2a17aafe4de3d0bd1dce621babaed2fc8e1f561660b9626a7902a80d0ea0867508c9c0236c2900dd38d595b3d1c5e5d787b3dd92a23edec5716aa1e56d28f1987b215ce02664fa6b212ac3c842ebb7c579b3881c61ebebced8a06acdacb30ccdaaeda6ea8dcf57cbeff0eaa54252f18709be6c46358d9656054aebb848c7cd1b53223f36e94a5262215b004176f6963f05102b736dde71a69ffc688758c0961515525228552c4f630ec468399d151891721bb00a3a1ab5a92fd29ba2f3f465784c904adae0b00190ca8b0ff5cf003e63c258a2dde978f139c25b7f591c064e854521d02336fa3ed5953f9e6b66795c1a3c58affa386f8e752d4d57b8b57a88a4bbd31a297bc82da93217c67f803278cc3b8c6618154445edadf6e512e79dddfd81383ccebeadbe3ecf42e7ad28a18880963254c9e661bff80a7c84f7d7c53aa7fe71afb3c5004ce2e37ce595e88c642599718affcbf64c904780f035931b5c98985693ff9c6403a2b10be6750e6eebc11867ea182f0134ab5bb5c79a5fb7f527750958cb0e08229b4377591d30f91d1a534ff23e87f5c2054c11a57ba12fa3c2b976c00c1346185f5ce14de8ae218266e45ddddf98344364f4f214178f6954bcee6855bd3296b610e0fb69c8bd219aad27987fa31879e4743006e5e0f92d6bd15cf2f4b95fa0d8b7218ab21647b0bb1c0b83fd16ef26d9148d389bb09c5963cf9c9d4f6f539d827fa269cbe81c815c4f0752eb32d86352ba3e3e0427b78244d1e360c5504f830394b40948ccc8686a6578759b6991a4df92984876467262e6636642cd55877420344bc6569715e12071e692a7cb22711d90feb500f83d3df3f1c32f2183ef4fae32de6e8afdcef1facf6bbd531703d0a2a8838a503aabeb506bbf9fb87e2836082e1c9c55b0e5c082c0005b62a7778580858ac9d4d82171888b2c605d24b137281cc4556faf70d786dcbd9ecb11836b15e5fd7ea078399ddbeea4c4394fce1c704fde18a3588ba080cec67ea2bc3312cacb2a500d60b8e54ef07e95b3ef689ededb284e8c9c30b587443c77a1628cb3340ca7796768fafdd32442bc2b182d8c30fa54967c312894179aeb37e6002bf92f76af8adf5c12a0a43a7b419a178ca582d4e7190fa735411f01b8246ddc10d866916bee3dc55b59e58facc2713350bdeec614c937012efd402a99c5ca83b1c801d879c456ec4ae621d58c01d65680e52329ad87700789000a5c802ec7734bd7048b6b03fa009bc98e4a7eb2c34d3bd5b811042424680f5bc6c257cc32726f24995f53cd1ff4c746631f090e60892fa7b9e7d1f0d683121e2fa89b09d8594974064b33d11a9ea517200b2d570355eeb9a39f8cae722f970053879cba5ffb90c551e72adaebbdd15fefdfa0f3cf4e04ef9b67f9fab96bfb5d72d6cbf196fa98f1ee1e878ffa145c9bc4aa3e411954494aff8b80bdd7049fb3c814ec70dbdb414c34db693d6f5787592a0f1ebf7c1b89bfcb3fff936d7d7cc07326a52a57206c33ab8571101a11ce9245a6196e1e5f922edd50a3ad03b8e36fcc21667453c4399b0f4ee39f15550985c911cce8d26cf7ab1b815e7216a8e96b8a812462207f8a51e9a54923138a0ac220bd00bb11ef91e1e79f53ac8a70783e7a6998680e36000d002d53ba97fd51a09774f18d9a4220ce50c39fe527ae1024f2f285848ae29af638a1f5eb056305609f5e15cabd7b90f15442765684a010cfe570db9ba66de5ada9bdcc9456a3957bfb48d7f8051ab2234dcce2b8b01ae557c182a8e9500d4867951925f01016cd52046d8325f45add9128200fcd952cbc13230d6ef0563b0bdaafc71ae3c4f3f6e68d5b873f164fa1b479ae98428bb1e3cc034dc658b8ea4980e0fdf7adf91d61992d50a36597734fece06d591028ac47ccc25d7d988101e6b5cb656c34e362af5094e92c20e0d1f3c36dbe2f8cfa0a84372210ba20357e76f18e47e3e4006f1cfa400d087a61fcd2f101b11befeaafdb2994576a78f75839b6a69174175af485abc0e0a6dd81e12377ef180101d3f6d21c36eb58ba64c26f12a1d97c086ca5846a36eb3462bf35697fffc9bc1b97d0266f85c8118dc6594cf4ca82fb8405433583610c50642728ab66df25b1bb94c5efba828b7df6eeaec9530cc9ea33106e18a71790e0e4e531247379efde8299d39980e50a9176e18ce6aa82ff11b5c7f1694ea16294f737bcff2a0d9b4b26bd39db9d4961a32210e5321c667a95be90acac699a906accaf29ef36762e878789970f13fe6160db446ad1d868fc6857e68ee66d6d085b863ed18846e55e501a8aed72dcf5f08bb7ff70b48e50a144e7a037d214043015c69b0f18f16fdb7f9166dfb55a68480567cadb4fb21fe84559bf57e35285b43e3aeb1e5ac4feee183e701a834a55b0cd4b2f61eb9f37ca8a9e1efdc97921304be01b18e1d8a2d50f2c7978d090590f5c8ad4ace6b78b551e65cea365c8e352c5b4d6ae57c5a32e6eb632426b6e6cfb290198222b29705b4219c632e95124ea3d35f2925e45358733ba4ad75a7490ee8e77f8c623bdd3ba5a7c054e922fb8269e684ae154ecc668738bafe1a0188dd3beda462d254bccbb8187a9cc0af38f7b86ba2750b4a83b7a69da5a8316aec1db8a4f1fae550f37deea4ae9d47ece3f63fcb8abd655a94e4a13c3dcea1c662ff564e18662602ee74df9841be146c1c537454c28c53f4ccdac8c632c4991e8eacc8671782fec92f9b7d2495692f01880d715eeb4be0389c94533b93703818e330155076f9aa9be168007635b1f526d1ae5c92df354b5595c8f6bed3cde41ad916a3ee440ba1b65a93bae9994c4f8e601e079493963ee67061874ed5da40cdc0dad7247f68596979ab7105188584309ab480e699430b5a433b3c3013fbd4a1103ee8b570ddae0e9bc2f4567a94da0d98b2f182c944f3b919caf8c009fb15b91057713ee370272adcd8199de644ded7c908803c28b47eea19f486e21cf39d0576605ad7018612958263f1e672d044026f20eb1998ce3a4eb5704353e56b9b34b852dc948dc9bdbf1e8ea0a92a11c0e69c1ddfe16f39449f730681897cab56c9ac9b7d010005b1a93201adcdeb8a75390c6ff54b5bde37028332de40ec70694ce8fecd780f66c031cbfd4aa7c7d4383e5db1d2451baec921a76bbfceeaaee1530d7bec19525713c63f37a1b18df145dcbed6e981d600a6e1ec26b540e92601e841eb7114379313738f413fbade80b2f501f9bea60d12a43a045780cb4b373aa6d610e206ca386b99ec7fa1ab6681a9320bb86402d6456a8866ef352efd816affa080c327aeb164b11c3a51c467a5c78ee303787b0ac4ff528feec12ca3914f778d57b31d8a7eb2b8763d95fc40662d230d0b38b0e5054c775ff232cca8182b3896e6c8d1a2deb67ef93701b60768ab267073c7c89a070257ab1c771f9859c63414cb4f7b00371efde5d423028016cd0110c06e83069ccc4b26762deb14628080bf62b3700526069166038d95e9b1b1124775efc20bab823a52505ba008f3be66d99009d7730983766a9101f6ec5da1c69405036fe5de161db26d6ea785083ac9114148221fb14858b5f911a561296961b2fb232b0adc013b6802a2df0a766aa0524007f932a585782a54be574ef37a4db1095cce6be681302874a9fb8b1f992e2e0fe8ddbb4e84898cc168153e21d70f2bb2c8b9492782e169a753d9cb525e8d49a2500d34eb0a2e938ed7307563d48b321749501595da7af439ccf5861677e3b90632669029110ba5d59e2bb4b6f832425830e954a04d994421dc8a739c9878282fcdc5a928e20cfb8498b4687f921469938265809e04fd7ad21a0773a96e034b02f066edc0fd0afbd445f88487a72973d333ca5037a2d9f90c5eb0619616dc4c575d80569d9827e26a277d841586f9eef6394a325b5a9da5816b0690c558aa29a1331c55c38ec713216efbfb9322ff24cdc82e4d4034b3d65ea8c84efefbd3e5b116471a9920b77088a9d898fcb49cf8c4db764fbebb39b39d7b7b25b121d7ef7958930de12ab652153b0e893673c23b12b60c96f8a7c28d0ba6b5fccfa50594cfa6890e32988cf4ac0cd60480a57a84e4e25f21ac2eed7c9ed841fad933e2777f7f09a13bcf80988dae86dbe877d4e2e6d57c49d22625db425d9c44611310c5f46514c1a2c0f6b61ff1620410c05dcba6e427b9650d3baa8ab87e031f5a8cab39c1fde0be43302f8404f5571e5af37901145875c1d3fce05d7f777e1ef2f012850594b004888133fac7d0e4dff2b04711364bc49509c2f7e629b3a4f991497d542efa31b4eab2e0625f9d3272247e1c4030f9a19416fb5935622bcace9f200fbd6b7704e655e223135a99f78ff664764b0174a824c4ee3b0192c2768094d7c30fc2ed5f70cc6ab68fa1b493902d21cad675f0934c31e77b79fd45011bd0e6c6fa386f39f942d62ba5adfe773eb958318a52ba4c81e075e1411f7538ae17ad9d89d7f6875940226f6d00f823a9c58ef23480e25949414a5f090ccea2542db643f22c2560e906ce76684a618e231164ebace175f8b31e3bfed98153775b3a2004dc9314832d5ae47daf51ee6a813b073832949408cf1ad7cfcfc7f51cd2bd4bdd969751863183fe118995b7a268f35ce53443b6e12cfa4bc34c084e6d3e9215b6b51e477cf5248998da802478bb3192781efc22bfd2b2f85fb536a2e55e0295c094f92ae205a15bbf77e709b5a6b8157832c2d3e67669fb054c1f37c535fccfa7e409f4cd81fe8fabcf52ea3991ccc37016b661ff5bdd1d362ecb0d547a4351d567e1a839ca9e3c5bf27a9769528d86762e0358b7cb1b5a51115ea31c2afa086c93865c2410096e29f320b21f1c0887f25bcc50b320934fc9daf1d6589cfd66bc267f77d1128003c18b5c9ac345e883ac5ee11c5c41f543df29718dcda23807b1736fcaf2613003844b709a76c35d89173668af77459416981f036830aeef0eaeb1ca09d711bea22350f2a49db81dfe1b5a97886d6a15cb98190dbcbb877a3fdf629c30f16a6dbc2d674285f746fb1c2e4dc81f42b52f064ffb8a32da578d4ce9b76abef5a91947760496d1281804ab6f5bc079e19f02fc1ac63f76402f7a01beda48a08230a9dee6de6b8c8998cece6a4b3d3a37e880dedabe170153c9abc9541140cd00b9be5a0678cfbb72f268f145f265eaa781c34e8572b96327a64fe48d57827d492fa52f48907c0726062fd737640661d835221668cbcfd44c18b8debdf7c4efc016d4def3660e89921e037c27c7cb8c350741ccf2ac9b16c7ec14acc7e4b10d25ebd205d7728224187ea93867da359a8a6ad25d56a6e55c9b56c7b5ee3abe763d6b672725238c8c6037e8435fbcad8120f61617690bb2c454aed5e3c3a96f7d9c09342a3170040ba6e8d29021c70326cb3b8187e14c9aa1465b010a933724962ee485d4a46e1aa9d2d34d485827f3cfe37039050029d6d9625888806cdd36c0e1735c0ef6da4e709518ac074949a3c299b54168abce3198140d1de4d1bd92e48d59daa82abf6efe19c71cbc62f58b4efe1290d3e53381f0fe6915aa39492fad9e830408fabd9f25f0aceff1d41e9f51670e3cde28d1f89dcca8c1205df05b2e6c49ce41a81a9bd1907fb8fd2827f41952e5cb779f1401f3893b6a09bd0aebaab5b136590235e02f74f98fd49390757d8e55df118b7accaebeff5e707b4c2dc06e0cdf03d6444b63ddf3f2b7642e585774130b8433a322f989b485f6947a1e9f961a9dfe9fc3ef02228fa1586503d389ccf054d5c5c75c3bd96e065180487783ece3d78f79fd925ddda2c371ffa5fcce34c1e1462d54090d56ad53fc0090617983fc9778893d016ffdd7d809d440149b3447c6567ab62e4b43a90c784f4d42c777dff2554775e325d1c3100bfaa1bb997aaeae09f5c8ec8418345aed6d18de9855388c3fc992e3dc70ddebbf76cceb7fe39883301da39e36de59900d16d9db725f864746823bb16eb2820f1f6e4f39a3026e90d14a0f773fe6ff0ee3cfc8ba0884e0fed0e24c5a4f97c6d0e5ed7e07606e06aa4d5171cb43105dde426b9f227e69800a77f0797874bc401cf356cbfc83f8ff46cf1a38163d58199403ff40ab0177d994b63771332b0a7adf6674aa05b720dfe350fe87907ce62cfbe711c34b75a3e6eef46582058425ceb36c6e28e091e262054059cac5b54050ce32e8c3174db761ab30fc66972d95cd0d64961633405807e0d007556f5102dc2a49007832771b84cc8253c71bd62b07fea9e4772a7ec7aab58b23316faa9baac9a6f32395c986bd4398077f13cf5d2c0ddf0e6ebc469f46eeaf22f1e21714ecb417406a1b90192da3f1d4495c24cf6ffd5d101283b4649796ce7d5a802891d59ece54cebee724ea25adbb4f35512ba0d03570b1990b77c0578cd3364b7e51def8863d4268d1d6f3ec90d2bab2f557233d0d94e1a364474485c72e07cc9f90dd393aa587200c7680cb7e0011238712be6eddae7d5658a5f87a60432fcf767210978716a2283cb0220e5f9724a85107dbe64019e2763722dc8c6319909ccd96161512e216b137d044536ff5eca2434182c31c0662e620cc56752162d5b3fc894812f3b6eb862b4d7b8da5921f73e5d03dc8599526509cdbda1d6e8d80198018a5a91665a9d9fa7b3e841b769834d97d49932971db5659070201c4116ff76a0ee14eac2ca39ebd5baf84eebbf3cf8c2e3af8bad70d973f1067d12744c4b652ce27c2c1b80112115cdca62fff9e27de482a5ea08c6a63b633c514c73b3b52aaa47b1d79a65ede2f58f5b8cf5caea9070050d4159c71c0b9a4d6b552531ecedfec19e61b69ce0591ff82c6197f73478eb6b8fa7e62743169c92b5dbddd52c185029dfe5b558c7af6c7a919dd947c7492798112fd9772a62303014c62702ef28846a4279d129118662791a8ad1c157b9095dc022165cae6f190824e5841cab8c67a9ccbb432a5f18b8d2d3c89c57251bd22d4b62e4865c1d230c32b4b81b825fb074665699c8507af4c97bf4582ee10a1829b3994710b27e19cc96a1d4a8b8385e9b3b95b13694750eea4820d7917b322e6817dd048ef06d7bb21069cb5e5faae526cc71d9823d58635d49cc3221eb57db3aa779b5d38d94c87261048282fabe0afb000f5c53a3ffdd98b3eee06d37c8536e50fbb6b54f197a94bf8ea7ee7036feb187fbe2dfe1cd57637a54acb74ba3bf009e1bbe308a2041c61584b1644fa562000ee0320f33f50d34ed1900cecb47f91eb82a2ccfce0c8707651e422e913aea2ccee913e8ec318dd4aadfd9f687c8d27e61d30695ca6599c07f7b0a8c03d2f0f4859cb69d082462aac6634c59928942cbed1a5a18e439f5e9a3e8dda433d092d38f4a1e2dac01f36cb7cf9e0ef41b21cd4e9898557d15c1318a884a3090364245b86af30149a19866506f0d5e80d8f946142bd4930761b994c6fee386a891704809442f8b43fa770185123c9dddb3918e6c9ebfae5adfe866f66c0627553982f7cff6112636462ab2aba37c4f6ce5cc77e3570354785c2436e49d1d774ca2b9b8299805896a4a88390ad8d821e0c96f8efe651e4f7da2bab403a1c7fb3c74529390ad51ada3c59fa0b02bd22198a3ea84e291957a0554f22948ae7262f74fc312b1dbfe4363a32c04f7d126561e88d0806e2658c38e83d88ce9ce88bffefba70cad88c090f9ad9c9f5c7f933e80b69862dbf43bfc3f0c726eac154eb71e6a8db358a60670b5a310b6541c22c2d81dd250fea72f899a641570219215d52145c703d39008b6e5d31775ca33c57db4818a6909f69ec5ab55a5a6c680775d281c593cb21efc3dffd4a1b78519a893d259d525ee0a9c8e980c9d339c348dd0315746f14b1d2276e5b34c83c60be9ee5bc173c5a7ef69a5fc66a93482a6cbb45690d0ee970e577ca1998c614abb5f35711e00bcb9f90fc50e84b8e47cabad62526873921bf58b5f8bce088c99cc48a377114c0699796897242ffb2d7e2ff4e334cbc1330d3ae17b92e004a56c937c58a6a44b386bda191846ba8d140d2a09bf46f374987d330d8789607a353f5b84bf979db1fcf591174074e6fa06141bc046725d481b39c60ecd7d1a5cd9246f3485719911c2e8a8cc71ee9b7897ab385dc5836d28aa8de7da5b44a6b16545267a718db680765bde65630bb513be1fd796097da98d13ff347daf38a8e535035682b3c530fca9438f7596c07b04ca338132899c851a09b0f0b9cd22807fd9ad4188c16b68bf28ef36a1b1942d474886d7d87518e2bb7b8fa8cabf6ff95038f39e724b979a600f73526f0bcbc59155c609ab86f5e13a009ef1c201616d9f3dfd08816988631464ea60baa276b8f114a8b25d11aa70871de5928f7c6924e22bfd23f8a9330e5144f429573b65d72c21697a070924a2ba0c96fb84b83bc369809b41dea790ec0ce2205e695cdf5e74f53a268ca6d222b21b134638ff7bc1752482f8c47c3bc972d15be545b56601eaf81b55f0d0d3c1eb7aa26dcaf3831ed471bf02cd247df61dd4a6919688ff15bf80b1f16f4d017a30d7121fccbd875372f28d585797202fbbd9cf499ab0082c1dc4cb12303689fbfa89f73765943e4e9c8615c9ecfbcdcc0554a06fb192ae3bd403e834b1adfb0fbf1e06e4f8e921afe7fe707fd0ec9aa0150249c80dbeadd8c3b00e409f83501c10450830edcb9c578e443ed9a81130731c15e41cc04bb00d0c19e06d28f873915cee7b67fd4bf4f1e56fa0f53a127d00337bef1b0daad2867259ead6dc3926d096c053bbec653f99cfd89e433ef5332b0eab9cf96ec78f7e4b9284330729fc23464064200f9e6a9d1da6b487db3c210763d0b6d3bb3070ea95b5d2604946214aecab35051e2f8bf9262be2bb49125ba70a599fbc0377f029a79f32460dfb0df9ee305211a2b4d190a82130b26a0fdad7270e88a5b455d73dc8bce88e1c9f4f6e02a2d9a558a1631406d2db441b32c513dd7b2c3739be0d2d5ea4b5b4a460d3d90af5f9ec20337c7c2c784cb9fd796b1b4e2ad9615a258bd11ac8e92652188896e7799e57f7f249ec23233294b88c8151b5995739219ef0fa17eea76493ee56ddd7c5cd47ff779c361a83f2d1407c584a4c9b64193f61d14bae8bfdd45d151484fcbd61566cae7b8f1eb51e8c958e398fab2bfa08c2c8f8611bdc7466b23230bc2469be14d2c9aa20de768e4368a6ed3848867296d88a165b009de62a00a4f512c93ff57a70dde11ab1078a74b5115be11e43c39517a5ca141346654353c9094d0d4bf35ceb90f383776c47d57420a0721040966b6c1d62e0e997f7c2d3bab51e0be7549c17982bf84e6950901acbb71a44458ef48191c6a7bc6965f61624b48fcfe41ea4c1d54327cb34f7ac221d063b2e0b25520fb4db3523d39be5f20c85ee5a5d88c7da538110dd9fda0d0a62e2391945220732a02885e7b98d0a58009d00a3a723ffb9e5470aaf7e0ef1eb2012ad42522930da389e2b7b8bf6dd8dd44687c5a40585df8dced13e219fc298c74662d9714039092aa6c3e1642880c6f8d4d5243dd87be90fdcf3fbdd9bf5813124fcd7bf3dbcab63201e88d252c876ac5194b4f6d086296d5935ffcfc5cb99cb54b966906a39cc28695a1851906484b392c27b7d195917dabc9f5b3958969dc0cecb925d8f55639722da479ff0d2c232c87d3981ef17a9ab12cc8a61b04ae12edca7f2a48f363c9d18d6d5819b19ee19003bd2e87455418272156ad11ebe17492f9dfb1d3e128689c15a16844f473e628f61fabd0b88fadbdf124eea6dad220042b32e11d87c6a1d5787dc6761127566bdab8357ed1b4768f90d6611483161d22b4a8389186d6996f643496734388306ce548ec410c36c00198ff25ac2f229b9ed53a29581cc744e2e7d8d92490dd28360c14c93598baac073f86fa123f80c531a66e8affc021df8e749432c82839a3bd28122108a8442bebac3434c8a16512ab55955018555f867b7642b9db597894a75ab0000c81f2d1ba821a1a87a1b3ec952e4d2cb8286934537700c6a8603d545bd127f6cfd3e51b35422d0b2e2f007e9a55c284005ddc70d85b2f8df037d3c75406614e0e6a1c077ed9d8d5632779eb870c6ffea54f21ce636b58c94e3a5c437c7d6976d98b3cfa85bde04e2de1553ab7a214abf4ef299652a954418e423bb0da3b49a2d341cbd563cf535779fd9630e88c4de16c02a17d7c36ffe88c10f29c1558e764ba7925ef6c544493b7cdaa8c64c604378ba3a02986fd92a4acdd341a8c0c7bb3f1f7d9d9b7ee58154979b526944e9f7751faa3274230904d4011379cf228be33625d4cec99b9baef68f972fa9f3211a9576535938589bed476b56a5729cb1d9707d4cf43fd6ed0b4905db78439c4318620aec3353342d9203c745d171e85652ada68c2171b556c98ef3f8b0b54a9d2cc0e83c6e5c83ff7c4a6ff5ba22e18d42cddedce718e217d9a21d08d61bebd244b612990112b9fe9cdec3872d4be6d386a73ebb4db0ba5d9f7e21476f6e9ff929a608f52fc711a40d68bb7fae3b06a76718dc9836f5b2cf5a0eb86b529d33183692b028de22db2127c7201a1522de8f22716d09b6cbf1c49b72fb7bd212cc57280db71fc19b9d210a6aa08410440ef76b5f40f73fdf1b7abb41b532e84a03d65dc1c8fe63badb0f5227ab847eb58772ce7dc80bac4287e1e1f5677e908d91a035e5808e9c907eef7876ccde5de7987da71a8f81ae3402dbce411f4515d24fd23bf26d7005d4794ccb965ba56dd83c7cbdfee0cbda9ff7005a1c6ad912a44754dae1a1da6ac49bf288bb8f89c8e7df2d49d98e0c92a304bed8974b46f3b0846296b6dcdc1910f82adfe392decbeb5711772e9f89cd384dcb39c44a1ee077eac184bcb95133c273b7654fbc32ca2a19d783555687aa08388391ab83687ba635e7c8117a620d2658e203fdd06f835623bdc0a9d1f67ea1899e5ec8ffe6224d1db6b3fab67e1252a5ec256ce229ffd39d41259bad1d69a46ce425a005628427d395c0971d0711cc7443e23f3e34b53066a47ce124d8a70c7fd9bf5d61716309d63bfd3aff346054cd1ea73bf0eefa90a1b8cc407069946ddc5b35f7affb6a2bdaddd9792c056c9aba563027e1dccb3119e25f723815bc87de3cbcc770ccbc8ace611f10e47dbe7bad85a2a77b17249c3cafcdd1ddac81f09bb8f70aaf17cb8ca28a5c3a4ea836ed7153f86773a160bf24457ba03809a6469a292ae03a58981a8b1fd87c42431a23ca944501a3c8be85c339c32aae8344a51882a537362f392112efd0a0f62e1b89ce5abe7b08e50a1a3788f8f5dc9e27d7acefde45394b27f77cd22ffbcb26002665e29161b6a908a2801615842cff2491dc1c32a4319dcf622f9a438eee9a029eb80f8bf9e3507a5b7ea938d7cd82169964b6e7d08118982b1328c5316951d699ce2b967607a94df334ec8db489cde8f03f85c1953dae353bc7580d248af29958f941655da334a4135a24539e0e9b88bd898e944d0005640dfd4fe42691ac8b002d08bb1f63c602bf4fed452c190a7b00d93e16cbbe4d24eef721a64d3c84063a32a86539dde55e826264557fce17c04e130918369ab05f5efa631156d2c60f7469b5ccd7ac47dad4714a4524af2a9586f3908d7b2b8a6803f3d6fb58d5a25a412963ac1417ae9edbe25ae17a05e2debc7b470bbaa96e75c3a5966d9daecef89f8b201277a91b8f1467691344bb74a9292822fb6a2fb56be348509e9fc25c1cdd7a0397fd918aa89d6cdc1d51ce9ea0f12ea90cf71682ee34c3362e86d4438df91269162ec7afe9c55b9446a957c4c54a3ff475145d9a6b6888117db4e5eaef1386c221a36cef00c16a2f045b9975e15386033a40ef364c3a1e6c33b74e531ed4592de4a34a84388d2c3aa5a23ded91e4095e5c759960fcfd6f92aaa77372c47715a99b2c48d256de89b11c09c7b5fe2fa78ff22af4e45b99d2c964e4b11c5a37764aed5b30d611ca8c24441e99f960107b4dd07932c5a866d51a0e0cc1ea511541673c44bb94063254860f2679491490640f8c9fb0eb357dbc7ff6c4c2c0778f245018f4da9eb1ebd01ded5c649568e9c6711d811727595ccd4fe4aa0d30647f6796dca042f24746ee7bbe4b5d8d97893cf04944666375771f159ece94482db13f4715a4cf55369689a23c53e170e0bb06bdfd2ee15f9a89d5fc132ade3713db680b7fc368d4bc6b39f6e1f68e7aabd8882deea7461df0211c5426e8cf441e863fdb02e051f5f2f3a15ba7555ebe67b8fea237905bff2e1c2722a412b7c860c3317b6b882db62da647dcaf31a8838088e0c3c0bf4dc411d807e81401e72bf114dea5c1c6436a1a298f332be146f2fec813f1bb54c5707f06be8707c016c9c5a31013e10581bba3c5858750e91514464413e7e88f71a89c92b29b5ad9859b920d7921c85643e18b4f28a75b7060a7218eca89946b62daab95ec39468e071c3861bd773c5cc8b3b639fea3fa278d65a75b9693f2dc0aae433aa4758816d5be7c52e8f64facaa5e78a359e4e39da8ba28991ecf200a3eef2564310bd5b2cd0bea1978c5a89b1a8f5898c637afb718dded1d2f173809134cf29ee3875e5bfae417608fc2bd3dff223a854b9702f9aedc06302d0681c199142edf24eddd33505133192ea41115d8d5242f14791067755ccd10ec98b1b8c4090d4dc5d1ed2bf804ae8eccba4a2d6b4a1b017080e105656f642bf99a80f71d98b7612a1c337048da3d10769c12373ab3c3866560abe4dddd8a01ebc0f00bf670b36e232e160ed07f6a4794d1f717cc2e2daec2e579baf1eaab862be879a8c33f7b9aeced65941ebf2bfa66d2662585caa87724d639dc1a87662439131371e71259f6307ba194d70fab5f59190a0f057b301220076dcbd02320d558602f7698781f5a609ffc291d339618cdd6f1a69e4fb7eccfae65c7507d1b361d880b2982075b676f64d5f3cc909cf50f833a2ce390eadfaa0d41d7996848ebcfff4333d9cd080eb120ecb0df74f92347bbdddcfae579dc14eb05ec23fe354b5d0f09fe94beb6d672706319ef985e5693703c818356242601effec71d2f204746608c53a1ae490d4a2dcfde8ee032c99d7057d6f635b260e811fd882eb490cbc510f7e6ace0c08a1f8e38406963829bd55db708381155a81a52c2b2b4f67796144d965b2c8d38908aa8f0372c3d91e0559aa37365a669c661380d49c107eb60a4c81440c33ab19a71234d167f8a89dfbed148ec217ed97447ace156b4537e0d460728b5c280385c65d313be39baa27ce3f44a532fcd56577947b2edb9bb24c95b5932778e2a6647b2e9bb4df2267eb0ae8329ef9ff284191aef781769b9575becff7d51c5d028974b4c3981c4f4c7b0fc47614d2a99791868f6331046603f3a2588194fdbbfdd998f8049a565bedaf51df07d6892174d19b4876a3f5b1c4f7c5e414373a2ad9874351fdd092dbf8f6fca5c3c645942809a341e7c16c2ad354c759dd39a81faafd4cefa27aa40a914d3c4a5c0419d12e10e039e1166564e1dfd0d1c11991b2f7075c212b69e1b2b1287a387a133a7945213e6d503ce976a5aaa58ea3cc4b0445b24a2c145bfea82778d1ccd086baae94c9518df7e185e404fdeda70e3b0f2dc53b1928b977cd4d9dfb526cf374b9e4b34e740e4bc975f1ead44b2632e1875773861a996388d9566c8037d19943067691b9d7a429418d5387314e69454c91ae403f99d717c92eb0f78fba6228e61ae2e7d01caed2c9fe645013b56b9926db39f0c4fd6d067c8b3b9c9d0bf72e040195ff6b7d6b8bcb5b78b302017869b8690e2ca1a728c1e17d9d154ae12031f01129fd4e955090f31098d3a1655ba9b87b307a9c2c78af427e7196470eb453410ff9870e70669b60651a221166ba6299bb95571825247aaf92e028c1bb5ebd9915c5215c462c8f4b1dab28a95dde7cf2bd1f5f76a7447dc3fb6521dddb6e7b1bedfd76c53d2eb43eab18b23a4bf06d4503e84054e87e666ae0339f87d1fca555bcfe01459818e053bc99aaafef4d5df2810e8db206e31068459c31538142c9311042036c65e98090f331ed60b0bd4b77f30000ed4636610e83a2a664f2e3592c7144fa89ef3e78fecf4fd4072168168a2c5620a5c60119e48cd8393fded6962b22d5f9091f83626ab1a0d1e47aaf37cc3c647590ca6450844d460c4bd8bcad09294af2f527bf63d9c9457826755ffe4d1494839a39b7aadf80198fd179f99d2260bd213770a450a58b7f4ffabeecb6c0cdf066210570ea29d05b2905aa47413d512980a404195bc1ebcdcb6620b62b120142ba720cdd4c36ebb705bf42098a0afa6b4287782b38f514d22b5829ef4ecd30240311c795e886988048f34acc6009a2dd95a82aafed03326905af4abbd258a09f834a312e5366b35b7eeb502ae405a21b5f8b2e68c3a9e71a28e7c2699908de08a9385ed73ef65351af80faf37f78d8a2473742f6df73d2f508cc790c8c0d23539d37f74bfa2a0254fda72ffa2b3acfe4d2fb99ae727738066ddd0b6eeab8907d00fe444a38ac1e8bf63c67f93a9de4944a54deebc49952878bcccab035b0e4871cbc220a5d7dbdd2aff185a6bd5a118a345a29c6c9111bfd96833283b6839664153dc43646c2deca06755095c82098e30213c985dbc15dd1bb967370078c65577a50302ddea93917f2060be444967424c96f6bf3b2dfcd1002138439e400fe3d66dd0d38969df0f61cfea2fa041ac8aa7cf89b02bb63053c040cd416af653ba2e8b7b9271f0f56f0438a491614917de7208e63e4c926cf514fc97a7884602c348dc20d02d14cd5aac8ec0ce7fba996e2ac4a2dca2a347876f88a3706f6274e21a91ff43596fb4f088360d314b7054c25c127840c00a7338f00ea50f952ca8b5cc6c3619aff0278317a7700498a3053c764c0c85331585d36466ac76e78b7dfa88e42ba633ddd3f1491f84a0c2cc1a6d56f78dbf4bcd48c53b9e6234e32d29acce12f06f793634c236afe1f360c294c78feb8d548dacd6cb5f861ff590b7d8041037cf918c25c5687e0c2b69f30bfeebabd6b76024f11fd04855e36b2f5666de151b8b681cdf2d97088a20388f0aba54c08123bf6982b2ee4a300ffc1bf292f25a5007df8b73a5dc5dea2a3226fb400f91deee720601ab4c63df6dc4ecc54661b9a18c93fcdb0199bc410ee40192b9608f1000b3a986ace01e7823ba0027cc1544ab4a003650d12af1227d66db74c78934d7216f378a3d20711f15723fa7b4bee9a880a1fe5060282738b75afb5661f95cb7cb635f42386dd299b28b9706f6c08b0a10cacf71fe250bd90b246b430972b6cfd8a5727b62119ca3afa9918aa36424121fa1ea0638cd4e4c0200585c1023d5127e93cf585a05dc7d485fbbdef7b68d6911d0ed0f4ab6be285a842ddd051c4958d8d9d89d403560b3e78a2d7cc37d08aac26783f31e607dd375f4c552ac477809712073838d30f4e57b24e729d580b781294d763b25c7d3f55e34fb5ec46922d948d2d364951129ba75bbcdc8592a67fe1355a5a4b42707d14b938467a9d43c5fea7a2ae564c0467281a6e3ce255ab60f0b7eff7f7f24dc7b057f2f02fb5479dd954f90f15063d1b4db248429d627e8f234dc2df15d639592e550b3915c7d8ad4e9e7fdcd4d43480930c450dc942630f3238c2e7b176e912e10d7ba2c590be9da0367e4ce7ca02c63a23dc3e2aec3f6e437fcb0753447260a556f8409e24486b2fa72ded0c624b14b0c53313c40244942de5f76e89cc44f1cd62124f48d18c1a0da12d775e9c40beafc8a36770863c9a58952e03ed141240ba04e0dd4cf88b11507b9bf58bf4746bee50cf3792d1af80db365290f25837da91f429a3d064a195876158a32c6ede883a0e245951777c163106de1822c82f093abda3ce1b5fcf61a77972af3de3f7e2892ef55dd25d25f6ae7df6b01927187925ec17feeca6a64f00c64f1646e4d3b52cf75706bda87454d01115b5f67202741c19b5ea04a3c9ae94a999346ad0e90c74d519c4aa4acc6a4a58affa368c5b66afbde5a6b0f7c4fd1bfb24dbce0df84693c0260f22366cb1b2d2aa24036ac494717f296bd1f4b6ce8e75a0451910c3c3fa2a5e48ebf0e25cebfe2738a50b44f313e9486e6ae1e50cac3f20229fe1934a7005665dabffbc725140d13788984a5c12b0c8095a648699946bc37ad362d2ab79a84cf8d4bc0346227a52b40b2ef6e7f1047bd98022b88e71767e8eb8f61f70c81436bbe0a17abad222d0010c26f5b0f26d4b28a3feb1eb2a34af3ec491d63ff6a7da0aae0fd8ead6977fc02b61961cdecf001a6984e41fe7bc0b9a8ad24dc6f3789b8d3eeddd443de6bd3a12abf34ebc2ee7152cc298369f2720e10371929b3b5c38e9a6d7ea91aa4e14c00115beb87b0aa0464eee762d5eb474e77a11bc9a8fe1462cd018dc18060dc3bc8f0703178b3177ea984321610afab35cdba70e7539a8120460383d11889645987b070c481a35e713a2db71acca2e9d06046a41dc2fff6d4d9c916443b5b83b7d99fda4a3b0998c292787b65e8219da5edc0a63488a1c04c37e1ad4469a2821bb38dc9467f05b70ca3f26e640c9b14675042f5125c54b060c70b3f1264c61d0fd0479034796340290a7230060b9be7969c07dd2fd7c01dd33f8542c67665266e51f59946be712209e1e8bed1847982c4788a20839aeab4cf35ffea55e9eeeae1eed2fe8566c81063f3fc8179dfd25004a41e41e347012c2dfe66809d1d659bc6ed1e4a0977a229ad8ecf356105dc845c1211758837d55fbd9ccbf6f7de4cf0fa6e9423e2ee376b303dd4dca73b490eec3ec7fc1253f4fc898802d593edf976c9b38c5d1f9d37d0f7e6d5cdbac952664913596750ef5805413101401176be1fffa292bce4dfaa2261300a0041254eb1ff38a75645bd4c1ffd0446a281ff6f5b1bfe820ed69d32dd47f8cb6f81843801e0f2ff3b52c103dd60abbff87388133e294c9411fb777bbc9818e26e5b80eedc054c40282b280fca915e313c911aa5a0d8f0348ed7072d0147c898f2317b2490e16c0cdf0f3065c035724a19245de8926f3647db59850f32e8e29fed16aa27df418ae91f5c93911aa0205a29e9c70aea54db6c261a569a8864f1002e89db5bd07ddf76ab8c00c767d1218f40f3db05d211f027cdf7debaa0e731a95df72381ef094df45cb9ca0ad7a81ae600c2df4977fb722de929c7f0922f41298f1cf79d0ae17a3e46a6e16cedff9490356a5d211cf8dde32f949b649f493065afa31f1f34c325c425132d0b9baaa196b8302354527579ef2b759eade6745eacbbc11dc4219cd6f1a01b9f05f82952f8233485c9f57079107a80aeb11def45b06c5b0c46a3c7040677bc09de93c9c3b21be4877e02f90f01e77492f25260e3ee9764df120b830c48376c86d206b5c0b2a4b4311b845fe757b7522f500e92aa636b539c14c01fc2c20fc956ceff9b74fd087c3314dcf81ff8e24c06adf0ffc669634bac29c1cbc0d142588bc3f52de2881b6290ea00750ecb9b2a9e204d45102405fa58af25c02166482632df5cc525c751ac1873aeaf9cbe6893f494c999b4bc2c68837bafa865949bbdc3c445290f6ddfce018de6d9bc19ec01931b1c1bb918f34755fbbd61f4bbb089c60cf9b14b169ff0a077754b47a9c4945a12ba50830f3fae3d6fa0e199f5a77211a942351979eb19cd1b9d97ad5bd340873fc5c19e7a83028fefc6f571883490dfbf1bcc41e89c0b7a404ae77b6b2318c17813d9f0ff29b1b147d49cc7553646feac11a47bd2a20c59eaab4b730085f06c0e6d19fb26922f1ee1f05328d76c7e11f0aa90aad0f7aff603d92d3d5eb7b99ba39ccfad6ee3abc1f400c2d770b0af645bc652dc6666bb4f24b8c93400179e52862beb6a1254c97ce6e9de196adab02b623ddde97d3cc766173290cded705e1a8e81519cc19836b77bda6478edb5ea9fa41ef2ed53561d6a4bef3e62ef84f0ed339b1eb4c9a5677644b12824557b609541f6690976ec64d7456dc1d712ec6fe573990a63537742e586d07b7a6d5d6b7a4f2ce3db2328905014762c809bf622ccbe50ccda3742ad30c9c4c19895b5681e6f338b92895fdc217f2bc408dd02502f9027723ca9b8640abd22b39a4332240a7634d9048838e0c3f5c18acbd36f6f87e8cb9ecc7abbb8089366a7d5eb6c3d01a7842c4af95fd11fb3c48a793e2956b3042e8fe14bba5aebd16b71cce0d5d892645ab16ab19da271bf8b755d4938cdacac96e64da98c20e29cc4acfdf1b4f56851e2d80e1dd277e1fd57c86d05340b602fd52c8059e3e3d7e90b6ca7bb78087c0d1fefd90b1a17a4db4cf817b33eb1d507a83929f9099c169f67c27eb6d83b85f48da368c4ab28db4a76de41bd9967f55e0fba0a4c55aca8222a3b79b53a7cd58a462f6234c2d210c60a7adb9d69370a20993a7fd0e60f22c9a0c84f3c4b8404f8c09a47f412e9456f8babe3c636f2500f28d49dbd3abe8678d4dc2535af179d4d2dce797e0f26881cf05b0ed804922dcabef989484114dad59acd856edb10b41400c10dd2dae21503072d95f2615451580a6f8e3222dfb890d5dbe27e41b46ae2720546ce8947d2e6c8866e7467366def50ac1f37fad5169d263cff7773dbb35f8b563290a2229df50a8df80b2841ed830a81e21ffa07f2435b0652bb8c51a279e60816aed7bbfe5ea35ccc2450eb2b3fb5728bcfdd1e5357ceb26b597bf2b71872231a136e67c36845990d8a959035456032a63d67a5a30145ea503994e4191ec1e2aaa874b6a029bdd3c29cfe5f63c1694a45895bb0f30e2c0a823ea2fd6918ebd058120d24bf33f000cbd49fc7ab310afae7e6b5ab39dd9481695cc0a48eca57e98a0f6edf52dd99b6cb9a59429a51475077307a907d099497bfe9e821dec58885b34dc62a1199ec80765f70df91ece8dd6a03c839ce9b1ecb949cd63268ee59e7a64b0d670344ff31a0d3fe4c73e9e6080cffc5c19cb9e460334199c3a78bc1b2e2e48ba5946506f7a9e8b870e560de892cbb5e41a8ad262c245c9e55a720d452162b96a66686a6866a6ccb2168b8b926bc9e5e2e2ea2f5a0708a00ac46a535b6538fee632c3bda79cbb56eadad59ecfb44be9071757abd56ab55aadafbdfc9801c9e572b95c2e170de887f250ae191a578d2b860ba929caf52ecfb514d3329454f8ec0411bcd4f9f3b3e7e745aa5a8cf7776d32f33edd62209583542c757bc80191595c4e539d5b696e37a05c6568af3f08e7466b50f24d1c0886542ce34080d948e0b404850a25966035a1f90915d0da63da63271858d37e25d39540e6655e93e156503c009fbf04503c003f7b7982811f76363054e881cf3a205eb5af407bdc8f36f39b0e9ff619ae37ae57edf9db88423346d51b29f413126fc00ea73d98b781049d9376a8da62d29e3b92f6dc6d7ca19ee448edf9c6c4855a67be7f659329eb3efe2dcbba8f5ffbd3fbb8c6cd6721b1cec73205fc6ab5f2b195bb130cdb84e924242714a93dc76213047579c1d23bbcf26f2425cf5c9eda9a755f67a9ce318ca53a9defa9786ae79380fe01d37d3ca7ee536d1d4472ee6c0cd18f4e28b95df21d99b70983c6aea886a391e11ac991609ac6096d6ad9fe9d5aa0f72eef4e2dddddbddd0db7683c4c544a9807de2e2a3f90de591f13892641c15ee63faf2148a1f2cecc333fceea6cafbd6927c6f3f3f44ecd0e7d7ede1dcc621cdcee803755724de8d3224f758aa974915cd3a6c970fbe3dc3e5127e7dc1ed128e2e48473ab0326f3098d829af227a1981cd023e8e6dc52716e7150316ebec62fb3859e34de3c2d736e6ad9e6cc6cce6d13319e38b746158339f598266c621b746e8b284e14fac1189b7308f0817e53c6e6dc01686824cdd89c3300adc9e21c8faaf1807ed0b91d4ce6131a0545c5b3a27ad60a9585733d558bfbf313655e724d64ba7ec935a1dd760bdf66b3395700cac5cdc6a43b0b0f1695158fca4f41457902c40a6a1555b138bef8520c2eae2d6973903c65e5886873aee01d9cc4e2e8ef25f848edaf41829eb039b7332305cee9987840bf89c2e65c0b262c9cd3f14d709b7304a89333bd0c87c3ff40f1d7b83dc7b429e0f838c939f61a0aa71ed88d630ebbcdb91cb4051ff4e05df483ceb1508344bf89b339a7faa2699173386a70a0c2e6dc00ba702ea79ab81b4d687c6cbed0af860ad114ed6d4ce8dc0d19977302d887ada453fdcde449eda7e198885b2e88f66085cdb9154e3882b7611ce4362fb5f151eb508ca3f105dd0eded89cf3b024657171692fdd45fb80e68bc780e8077336e70240e96989016ccea98063730e00345a10aacd391c9a1816b0b03997c26f3feb51284a592cae88cce3f03f321d94cdd2a9fe66a22f9a2af58959fbfef321fc98d75638f598a9ceb5f445b562226e716cce97428dc7ada76ab8754a04b3be3d3ba8e5d89ceb564504d89ce3e83c42bfa96373ee6990b02465e1d8c5b192a7963cc553d47e966271d07cf1543f0be80423d06fb6b0615c13da563ad59f718c85e324cec22e14b8078ba3df3914aae909fda04d127636e74e989ee27678886c90406980f563a2130f28d6cdf6b4026cced9548c6b32f3936322a6d21e4fe954bf9c827eb06773ae46c5b826f425c7454c45a66322203467a3ba609a01415a0cef88d5c60bfa4158dd7fba37f48636e0d3de81f06f1847671f7be4c36fde617af89a16398f5c57ad6bafff97d307a8fd156e8e7f370f7ac375e56aeab9f188df577fe7c19bb3a71ed039ef2ab150e89c066dbc58c85b64b2621dbaf70ea0f70e189b9b717c9a9c01e5f7b8bed19b3bc640d3ba09cabfeecec528bb5bba43e85cba4008210c1146e8485d60eb7c44e0af167d1b07be38ba75f7d2254e8de30df30e3861cba97295ef70159beaeee391e1a43d825150e835bdc39b5466a1ca2716aa3470fd3889fd0ae5c5e124e88fb168dee1244ef5735d15eb6c7b9c456d156c75f38e95d1575e4c4a6b5037988c527a49baf0d07d0df04244514a5a355a609dc510c2a8292cbc1861e2c50701e4404f758bbcbc4005197891818cba455cbc1c89777777bd07e70fb5226887eae88bde689df87d80d8d92cff77361d4df1674ee8abb808b63f5175d152151c8a8a54b9010de20e2fb943fc886508b14b0e3b4001862b2a7191c312e10ae6840653d7284912499e4cd102830371050cf08570922cf0935c1159052076eece76842f00156adf50a576078ed73c6ac307e23562988cd371f8bf19a7c296b638951fd6f0135446a11d028f69b9bbbb3b33bbbb4b8feece746803407c77976e7436c18068d105b010e5e66616742bd5b12240bdf26cdbb62c7b53f7e33de53f99fb28636f7a1bf9304fe427eb6cf8f4dadb48e0f4da9f3a1b6c03b5fff43612e663303d8ca9c3ba184e359cba205ac79265475459fea8fd9a8f8f7f5201aa9a71369889b3c13a096c6f83fde989fc642f01eddf068b61c3be86adc3ba186a78223fa66f55e5eefb51650744556568cf67f061ee6ce4f3dbc8ce05acce24b074581d56c27afefdeeee1856dff9cbe6388ab23226d67fb9fbb19c0cfb31b4e72bc38f0fceda407bfbab045b315a051fcc48a23c3e1ac6cfc3a30b4cc6d051162e35c6960ef55bd7567494850918b78a8b1a8150e3df88b2fb9b257e7eac5facd84709b335a2f163e499b2e3a992072e9892648b4d0c7376447ef6b7bb522ca6ae51932c6a7758f74dfac3a2dff415c4000911484a29a5921d322600e580a48909905026a07062d54f7c71021efebf091640a8b6402285d0a2c3115478d1610b0d3a0a2962600458e504592069a2a5dea8a1355ed7a889154e68cbcdcf809d9dfd3d39806de03e22bdf1a9477cc901cd1ff9918ba1573ff297fb4a50bf10c0daaab1f3ee2b410cf4477ef16d1460aab103414c855d13f9eb83aa7a9fe655c542f3d7c10d5f885054b78aba39f0c2a546098f66bc25a32641ac24f9e32f6164057dcc43edb701fa98090f7a4a6d278cb8a27af7b1d26c2c5c8dab803210cebbbbb8b104b19cc548ed25b5d7df37f04e06b86dd89e2dbb535d3c3d3d3972701c8dd861a0f3d99ce6d14259e9a7b99d6ccf3a8abfb8cff6b030b03dbbcdaecd699f9882148a39c675c03b1bf9fe1fdbc82e067ee8e37c54bd93b67648506f43825f4b010a9d2aa03f54a420b60895795136ecaad50a0a0a0ae2968cb0b165a32756b539cb4b9be29f29a8fcdf168157d4abdc3577a94c4434b9b5571ea0a500853a11acb3ed467887e7119aa4f22f2fa9fcaec3f6cce7ef2ebb3337c56f43e5c9df4a73528a4211050505316fcfd3c43fb66e3db55aadf679797fff019bb31f143429ea9b835ac789608f9f6750f959e096f3cfc9cc44ad266a2566a1ee100d910f4d6da373569f1dd42dedbf0ee22a9c72c94e36e5bf4d6c133d3d3972701c0d1adc921dec5848e3b8a5853ad1f6c0f7f7d76a402613b210b77a4a7bfe454de479d0c32dd53f460ff467d51e507b9d05ef2cb7d1f696a2edc1de86af504ba046d002550595c269c81e7b4d4a295fc3ae547e2bb2d002938f715c8922514f691da5a5a6e2de49d5db4b94b0e5cf4cdc280653aaf313db93bdbf133551018a7c9f4062b985663df889ad90fbb88adc42f9e57452a3d5b18daa176d4e134a5390424d2b45e58f2888c8777835dd79c5add841c0f49ac641272a47e4478b5c07606783d9605d0cfd3df810f0518d1c1f55c86dedf721ac6794a0f5c3ca5159541657d2c6e88162bb9caf529042b3ba3d337b7eb66103857228fb5dd230bb4e82e529e69ae81f5a89a0cd518259add36a9d1d6a1d9bfd45d23a41adc384db7699d59e867a42f7d4a36356ebb8bb43d6a232e44056b7d005f2d4f2f2b28fcde1ef1e720be5b81fe887aaad04a891b4cebac01cd81cf6b13dfbb13027a528d47a1116ead7409cc43f27a528d4ffe7f1764db439fc0ee4abf6d8573bfbc395c50de4acd9a720e452ed3a84973a80ba46432c51b5de94e6dc02d63e3a3a6a94114d62a72c22e65ba8a7a8f9654e2527aa44bd71059402142772c5852e872e560a4850fa7209efc415f32fb3b4616510db637afafd72b53dbcf207a982da520719c4e62ce15467dd278be20bb0ee934ac0ee93466cf749225ce82a579bd38f6384d20eb573ebd591efb0ab8196bfa0bec609fde2172d58c787faf4302fabf00ebd82b282816416b55f3e219dec4e5c49a3f8257e9142dbe39b66caa0cb7768bea78ccb559d88a6531a6a1da905c3749fdcc2c8897c2752147b353a510d117465d8546f922bf90e7520fa8fa2be4936d4fe5e6d8f33d2f6ecf397dacc456dee52794b6daea2f6c7952bb9922b05c09536a73fc6b81e520eb2c0d2f289dc1c00218b2bf90b2d9dd48e4ad854b752f003900ae04a87da3648a12446628e5fe2176050a9fd722585a2f40ec792455a678dbe78523b4a24416040513fd94232022308f5934f3e0954fb393252af1118536a7f2cb283fa451e221224107018aed43a6b34840a6abf7b699d156abf73f207949f4f326c4ad0fd4d09caef10908fc3fc99f99898a72fa3c37a0d7b882814a548744e4a55aaae407b5df3343f239fd854bf14b5bf0aa5ba45c21c6a7f0dcd8c944ea4112a0959446dd9fa47a1565f6aff4a0acd2003735e6391f65a0ac52f9bd32fc345256c0bfd948b602c01fa63b818c6b2d06f02a211287e59b9746a2e8af6d2aa58eb6ceda56269b55ad9f0b3c44a11205793fa39abb6890913f57325fda5f6c36521b0d47e9c661254fb864c5d1a59e5d40a75bb1c75bb9e8a6aa2ee6b6e05eb70506bef4af0ce6ca24af52bb5df93f01f76875b9bea4715f1b55210b5df83b647e968624747be33f30dd37d936edd87cae487a25dccd73e6aafffeba30fd68f839edf63a5d78e5ac7ad60cfb1b052db7fe8209f93d25652f220131065a1ae4382a600857eada4a4a198a07bea74a05f077590efa8828e1ec0eea9566d0f7caeada4a41463ec3eba425dba297e5e4e6dce7e9f3a4606da5229a59473e54af98e75b283b07b50d5d6d322c671c58afa8227155279c1120c6050210cb8a85015a5385185922258441942f203ad547845cb912184b8c1d5c5163f108a20c85042b7f20ed3096416355601000b02c1aaf04a0ca290e669fc385ef7d57736528ffec5213254fb97e3f83c1e0742fee6f7719fbee1a9452aea1cf8e112c40227f343082184703e67a6ccb565779ab529ffd9858bea4d5c2bd81d6eb50baad3d4af5932a8fefca4394a7d9e2cd45e2b5b84d94eebce6404ad47726c38438d147600be90b84e57cd1f0ed315e55ff72c4d68821236b129236cc2c464844d684213fc1d09249cedd1da4d0e3388b33dd3835128f4364e1cc6f1f90d1e38aca3539df26a04b039fdb1f3ce9398c4a2d79ee76d4e1838dbb34daca71235e8c8448db1c6e78931c6185315fed60005157ecf18638cd1852a460405810809546cf9899da5c62d6ac46a8c31c61b75594a961c434028e68c188649214241d7a0803c157d67a5e840870844d19212450f11e081d2cb2e2855ab8abad862071981a32f9dda5547e009a04e6d8da927ba50c20a5350d30d01d0820715b878f1c30bbc60b202165e144151188dba454b10a2c2d6124dca0ff55bf85ceccac085123ec16e0e413b104695dcd9f8d8caffc256eeba0f7e0888549f765a5b6653fd348ab8d40c28e8477d053ce79b9ea1474c4e547b0a80d5d4c1d9bc5009e324f562bd39243ec7249ac6eb0ce34f7f08b963b7504b11df414a29e50f5030821720615151855505952baee400042b60384141144140c8a658416b872794ae8851b4608a1e5e43614bc056498e3046080097141900200846528600c011c5ea1a4979c1f4a72ea5f420a548fdbef20c623a5ef50e54edeea851942f5555d7e88820542d46b9c3585aca855a90ca1f3dc98e91e00ffdd86584c414779ec244acb6a2c2671eaaa8f07dc0e7221d42d4f4df9cd23aa687dd42609d3f613f44a9eaee6024f6e13bbcfa767807d3c9ae7321d67e46e21dfdc3e2800f77d48f5d7cc5d57d2aef3b0c728783f763020ee3070af69f10af3aa0a08b650f5f83f1e32e60ba0ff57468534b5424431847da9e609cfba60386f43875db1eb7752bc4abf6262e6e409b5ad60cdad4d68a446b53bbfbbd9bb3b51765377f92be2852fb073e77c1406e6a3f3f7f6b7d6ac23dde0d55054880f3dd78ab03df91000bec7cedfaf2582bf575f8e985e8b54ebf00ab8f28b4b89ec9b471bc9e694d23a830fbc180f6fa3ddf89a153bc43015e60e8f6cec7e6f43fa13efc7df80560e22f41f14585eac1962da4173d4829a554819722546104282e20c2059d2404138694219a8a2a264802bc92859222f1e33671c5bc1206108e6843b8c088c88a14459cc00737cc2956469a8afabd0f2d1e27bc07f55b24a4badf2a1da1a56e171f688110aa68b9c1122d48e2c7b11c61a5ba472104510a8660a2080b58f0e3af4ae1080d848c8c8c5870c38fff0cfe85d6353a62ca1147645a767b365effad3d7f17bcef5437e7f48257f7d30b587517e00bd9e905b9dc524a97129a60fc7d7a6e4e3fbf8908b46b3f991e802350adaed11142d81b1d11449552055a943c95f24265ea1a1dc13a0248059de8eda671bcdef13ac5af52f114151d1d2121c9d87da1df561f7513655c0f821d4542f977773b082184104208216466ee7677a1bb37ef36ec61629cefee32fce973be4318338fd9c4a07c87d9c4645c8a02e5668f2d73df56b185fb37dac389c9fec9e35aa4221ecf3d1dde411f150f8ff612f0791b17949188d062988d8cc0a1327ba89bcd41fd6eaaff5052752f0249f522aaa83b447ebab97768da3069a8e7ac1046201f9610621d01a38a255354f80fb1183101054208e1a90796a37e5b0e8690d0208a2a290a5bd48c410aa0b8e0cb1660e030742503362842020a1f562084112994cc304b99c54e477bce037a315a70a37e5bc56680a562180d84a818851edc2a5b2c11e40589269696fc402427a298004e3a37fac333685529df291d5aad563b1ce105cca473a3d93eb1031dac503f1d15f2582a589c7051e14348045185464418c9c0880c7cf004880a9fc7e7891315be0f1fd6088aab2655da3a271acc099d60590192a7763a1182110864594ae221b1850d50758b8e0045b5d28c8c60c1beb0d59f856ce4b141961bb22fa43d66e6863437ff727333fbe021419613b2b44186c8204286ac8fca0b32a48656dadd20429a0767dbc9757f743e8a509cda36b406d55e3f1fd12bc4c7d6bf0fb70b226408f6dfee90d6599e9352f6b129fe211c7bc41844c8108fa73d5e0a524377b7c733a4756a689d8f37d03142d426e1ec7791cfb055d7263ed728e37d5a4b323284ba2e33011b5a49467e508178980c34b763f4fc966f0f4de5e5d5af9432cbb239b1971c08b03a1f62523a0c1c06ac7c9a0f8ba8d4dddd2625e0869658fd9c60e0971d10afdf7cc871434bac6a9f1c306374b2f380a972c7455a45d0cf592c2424f41d47696f635ed39eb34d487bcb4166e64c2205e140b043b86e0b5c5ea406b3ede90598ff1689b7188eab0d29d4a789ee2e96f6564224ed2d0cea20ab3d56dd77972bb5a40775bdbbefac2f4b4c7e7ddaa769e6b7ab756a7ebf5bbec3937ea8cfe3a11b43083f20f3634e3d32245de602e78145aa1d8043754f351d92b38b9fa6fbe6ce74dfa4b4fb509eb3f6b2fb78ea56f82d1598d36bbf9d7a9c3ade3856c241ededdb9801fd980933691d5eb5b73f817c27884dffd6227d80dd656dcf4cdd7724eec3ee746b53fbbb9f759f2ba9fb2dc4ee386bdfa609f473d6909f1833289d018d988052f92643710f399cbfe1792b6954fb655996bd7c9f6ea19d936ec6dad4b6507b926b26ed2dfca73931804895add6e12a65c74aecda82ee774ba875be5e62d24f5a67c906beb7eaf2903c3ecc25a04832c00717ea4a5ae7e101647436fcfb23e3f77765644e640c3fdadb476a269dda8f5cb7322c5a75f96568cf480333b4b7fbdc7dde02121894df1ec6098912c3a694d23d761edda5ecba854bdaff407dc6d8b967d3e1cbce3d7670c2d8cdb781e653e343b8df8120aafafd00a26a61b98f85baec39eac28f4788ac0e39fad9cc1a5ba8df6e0fef3e8f70dbcb511bbee6de428d1f2b6cd96d1c39288c4125a0220c5457f8f0a8fbd204384cd01d361b6a7cb871c2232a61c224e878f1e85015c173e2068e14aba3bd8552aada833096a0db9768eb1be37f83aaa11bb682083b1d081dee8e7686ee0edd89488e476032bb2123460dcd8c0c8d81396d5a669a37ac563c28254d89c51831d61833bd4f9b30e639e7d6850959c4706664680ccc69d33008fc40a2800c491998c105a4cb15083119a974dca360e3841b9b1a34503364c4a8a139c26225090a5ad26a11212464c4d0901244444d141555393aba828464455252162e97164a4a4e3049e70ca537287884224c663c37363568a066c888514333234363606e58ad8c00011d61b19204052d69b5881012326268688a92942c624f63604e9b86fdf8142dc0c711047e20558007ea42289b586366d6186a52ca8e060d4229a536a1d4349865d9369526f59245ccaba19991a13130a74dc34c33d6d025284609b1a247c1c60937363568a066c888514373c36a650408e8088b9524286849ab458490901143434a101135515454e5e8e80a12921549494a2e083119f9f52f145e365e27bc6e5e36af1a2f1a2fd46bc64bc62bc6abe645f39a79c9bce82be605f33abdb697f632bde62bbea494d2a7943e6334b9c7d8d160828ec5d88ef1438fb086eacf32c687f26394cf9551e093164c663730a74dcb4c533260ca528cd221364d28fcc7e3ff3b00f7df1675ff6d134b2576dca360e3841b9b1a34503364c4a8a19991a13130a7adcad1d11524242b9292b27069a1a464d2dca4b9c91402697a8dddf421e82ae794262d738f99d330db7bcbe0e61daf87612eb3cfa64f475fe218260a26b3f87abd5eafd7ebf57abd5eaf215f6e58ad8c00011d61b19204052d69b59e2879c164c623be6cbc4e78ddbc6c5e355e345ea8d78c978c578c57cd8be635f39279d157cc0be6757a6d2fed458490901143434a101135515464456935a4a3060dd40c19316a686664680ccc69bb61b53202047484c54a1214b4a4d57a22243444833324209df01f8f8dff7a78d838e1c6a6060dd40c19316a686664680ccc11162b4950d092568b0821212386869420226aa2a8c8cad1d109ac19f9de29927d111ca8093c8c307ea0c4c741f50365fe37e99ae69ca1f406857a08461631c905f3bc1c2a158f2e72c5ba2123460dcd8c0c8d81396d1a669a37ac563c0001c5e0998586c9ec690ccc69d332d3941590c115c3d80d3730a74dc34c3332c0c789a50e8a68fca7aaf15f0e9bff74dcfca7c373635383066a868c18353433323406e6b4699c24286849ab458490901143435388888a6cd85484c92cbe5eafd7ebf57abd5eafd7ebf5ca5eaf9764254a4b344c66386b9a7386d21a2854f7bf82e7e550a97a78787e7c8a16e0e308023f902820435206667001d90076b4c264e6c557cd8be635f39279d157cc0be6757a6d2fed95bd4caff992af1a86ac260ab89545ecf41f0ae6bf1a31ffddd0ff6cc8fcf733ff7534ffe1d4fca7428cff3c19ffad30e3bf1ba8ff06a0925c503364c4a8a199e17a78787e7c8a16e0e308023f90282043520666d8b86f034a34045912c1901b562b360204c44798c54a386806674f3099c535cd3943690d14aafbbfa11487344cce68ffd56cffc940c5d7f6d25e3550a8ee7f054f7239542f13cbe70c9a583e67d004a18931c69e1707d65d319618f6d8f27c3c11091aefc8fe9bf03f53fc6f9bffc198fea3d80e5ed39c3394d640a13ac6c138fa2197c734886910c3b48c7971ccdff9b0e7cc76fe3a176fc02e4a68b3757625774787f0e50eddddddddddddddddb0218410368410c2861042c6b1ce45dbd581d07b4743b82b3dba943a107a775dbbbbfb61175386685d232f5a35092b758dbc4842d535f2a2c8b99bf93eeddc56ee36fe1a6817f3dcd1ae06a89d1e06a6b379000dcdcccb3c09683a09c8689d4cb73f9048e537c51334c524787200063f21b0deb4d7031802a5375a68ec4e0dab68b375761f528f54833176b32b4177b300f573a5947008ab1c080dad8290c3135aae349150828014456b28b5f2c5529ce24a500a6a50b4260917248104126134667d2eb62fe1c77d47653bf3bda34e3d078d7f5de030ea6eceeee6f4360cd31b1cd00f35e1eac7dd77329e1f3304414914b7ae11e1f620b07824f49b3b284f39ca860a14109e6ef6807e1e8fd6b9d13ab1759689708344bb0685e3a9183e0a158361ae61355840b1179a4e87e93e140c46f331cc3c8d4cf779357b98ee9b35bca7b29fe9b8209a51ad63de61defdb5a3fa09e98a3dccc7701f911af37dea11f31a9f5c680e7b98a732446a0b8995761d04c6015333339853f7cdb9751fd53a1397753b6fd4ee38f6625454b2551886614934d635222af2bf0423300a2459832d7870a4a3f84f5d28e8f71ec70382b40653f4c0d81596cb1a1cedf02fc591cad403742e3249f7a6e7993ac36e4c4079e3c137aaea0bc0fe7e00c87e46f9180facced5d3a90767ee9ccd1b5e53e1110015e47cacc33887cec59845397fce2e4ad3ab4c3894670df40ab7530fd6d89db9adcced796d08bdb339a2692b6b6b304a6c9adaa3c466a6c9530fafcd4159db9db92f46754ed5ad71a76e9d4f067f3042df1cc6d8157e9dc39999b48dfb6454e7beaefa6b9a6fdb729f7fc3f63e186fdea142a8c44cda688296d10c0000044100f3150000300c0a870342a1509646cab03e14800b739e42745a329ac6b21c4761180419ea8001c010430040000666a4d100d0df795064311ead65739644e40f58d6d7d089910dde5a6450c003bd139a4667ee089c9e430be73a8ea1522c421564652a7fe88484262d4c7de3442e4ef095221ccd1477b1c01742d59e3aa3c6e52e799b1cff4561f37e28ac7fa8340ae0d1068d3c4e120163cf1eea548dfe0bf4beb6593d54af47777ca04379f0fec2dd2023f1904834a15428953881b2ac036fbb044643a4427c471ec9e520e7ad5707e81ea4bb5ba0c62270eea9deb49f2f50599a0e9c6d610d24017d80d3a1af5301defb70de48e57814baa059841ad02f0f1d09b6542fb7009c8d5a04bc01bc8bd6674c5c8a265273a75554ffbfb4fd99a50221497fb1c0cf988a351ed7adecce5f0d696c3c5a6d394f4f1b5f0caab64af889fab749fd56ad7bd06a8b72d83b1130f942ea13c1a34ecbf1812577ca9ce88a0df98353b6bf924231a30d45ba4a4154174554da53a0e06540ddd5bbd80bda6c712eacb2d393e50256644ca044672ac681d5721d57a3d2b36f5405d131f362d02d360f7086530543bdd056e6eddf7109841946237ad1030cc04927252ec439263842eedde300f25ce859e460148ee615029f20850064bd01235ede6813610c8e1b4cabc0b2d70d78539d0182fa1f40e5e7ef2d82ae070ca3b048407507d8f2e7f7f9068c31a46d093d8b956fc0840cd5ae720f788626094a750bd69c9ab65e7b8035c270dd85ce8e1f1576e0f025819324b9ac586f30663ee34bd12d18f376b7cdf40cb4ed162c0d75acb7411c4bb89fd10db699bd050b32b46edfb6b6070ce27a5c241fb61d386cf492097ccc059af8a3d4e4151d119235a768d469e6e1d565b567de6a635441691c91e989be5333902c92ec5e6cd308b17a601a8b6fbcec7c83d96cbb208671c05380771a8d0b0c3fb01ce3f1dc746f70d426ba00501d3f5dec5202669d090c68718fd50413988a3736373ec778e79210a88896951cd1e514514a581fab28c82f151e4da366e94782f3f1cc0644c571be3c65830a381fa0364adf7ce615553fbc65f8f26a72f4d6fb6c8802e65f61435ca39db3f85d08bb1f76ba6c8c3e0712a57d29c52e27e3dfb34edbc418de55a8696282919a75d60a099e5817c4d81b227c19f827ac932a455832c5bf4be1bba015ab1742b8b3290a2e9b9c2a7537624ee69173af4e34ab5c49b99b3acdf78586521b7d628addddc1a36859b8f79b8217b781c8f9a4b6b5827b9a1411aa811218fdc68b5c1c0678ca25b5081c0e3152db45eadd4e8cbd613bbdc991b758dd7a5e765a9b0cd76ba891f5c209e04be9196809ff53ff3f22632d9b47cabef33078b50740ed0f2a020bc8e1eeafe34422364d82264cc67994897afd3465dfe7944a7bc6e2a997e58ef464802fcdee263af90836a2f6456aba9cc6bfcb329d99f17daf7ff2a909b494ce5df9bf31ce92bbc7ccb9e732906ad4ee29a87541ee6d76349eccf5c9d7c0814262d4badc4123a4c6456da52462decc5c576d7df7858901dc2d0dc4e88c8712637cea1d1c82ade48f21dfc7ebb2e7e574b54cc80eba0634f80db44d8dbad324502308500c92e8eef143c24328020d45474dc3a0cc2bd01ea2880f1be68222e47278a9c6fc9382b08621dc09b4729b04c0f0e63d240dad460d77ff87d4595350c42eb728852205a312d2d76990c9f1c8589fe4f5b130e2194834cdcb54bb388c7f977d7a138e3d9ff8d5caeeb69268aa4b9fc0d43bef0c71c3dbcd5f1624ca8388d0b2805287b3f16fd9a66136baef238a7c3280add275d7ef67c499c4f69a30e7bc7f668c2bed1f33779ddbd78c0dc26615d33e7df328ae240e8c8fcf4ce12012810aef8ff1cde2e74124317de6ca06c6e5bbb7a182ad3c47c65067bf7135b1ce697ff70979407b833d413c20ef708f8007b0371c9e8db0f939ecfe9e184a5acd59e91c9d118338d091aa496bc48dd9d280ae2e67272dac33c635f0a1ba7dbaec6ca50c351344113856a3a59b731fa4667cc076c0158307c29479f835bb30b24c4523cb12808b0eedc8e31e260a18082814243048c902d9502516e584269c4fc8e50a923dec5ff2998a842f9ebc68a435962aad1b0ea76b7e793a917275110c2741018cb14be13ce6526387427d424f1e13ba53b4add431c9349f50b3c61f3d8145d6be94aa1b38a8e89213b584e434a1d253546f1617772f5e838c071b9127e40ace55ff197b06f35d83245b85ea3de48088a4bd2c4597f3b16fb3a7c36cec6d56d7b1ae3ad5bdee3ad791f54533837cf5a69a32628daf4f4729ef062f0a7dba733b7c8eb1d214936c081c953662ef163e7f0ef3ac14ee73bd0ef6c2049e1b0bacc8f69514a06b623842675d886316682a7fec877e8fdb7e0ed0eac30edfc049b69a97f05533d1fc3b387705fc2492301889e45595ae072f3c8c7eb33e420846a20a5c82b23668940c50cb45c23d467fb1ea415cba14f47f037a70ee9f8735bc0da0003facb729d6a0d0c8ec68413ee73e37905d0bc6de20c572880da62e3251ffb982d41c8f2e90906532fbc2d2a7d25c6747b0511a9653a66f7ac49be061bed33989e6f61b6c90dc4be5bdc3be29e1aeb4c1a9ef16a63e094e6390511fc83e453f3ea7c9c69d8e1b4b8eb7185e9db238b3ebe78810e2eeb7ad202bcff13112d149f469f42474bea1ab1b469fb67a6fa51577e5b8ba236d484af528b1c59fab24742877332ee36c2b08022aba9cc045685f7e274b3ab3c7df070bf839f6f3aa7f978549c77b6daa1de3baf7c5921f4386ef2ff34066265667e2c45376700089aebde43b96749b8e7fcb4a5ac0119fc22ad2d841b284f0f63f353c88fd675a0f69aedfa8c5f655b6caa866868907981936b27d7f19619686d503d90c97178534ae06ac93a59520ba355be79ccd1cedd67e15497a3c997c443ec9658b23f1b5f79164d52132b29a612c6723d1eb5ac9b8fa9f21e760f1d2c142b937cf45b41e068fac75015587e3e36fb249677e54df3fe83c318075450f819cf4f4b843a55914ca0da4ea296f3f1ff1a510220a5d8ec022b45f7c97acc9a07e477da97c98ae1abe554431dd3ce5725b9888549e545ba8ab119cc04468b444d5f11ddfca9cad6e7c4eb6f3b9105d5d57e996b4191d0e3407e46d068b36518a1d6ea0a3b52f7d87251df3a36fb24a03f7c5db8c5c6302b94aa5206179293f052dc9b3eb7aabc04b4cbb80848953ed738a210d7ccda514ba9cc1476f3ee61d96f4ddad8ad5514f76c840e205457dee2ed0a9479217e90e66bcd8c31079dfd0bd32111d8208e3091f20bf4f6f4332f128b596086ad7af5e09de29efb4e7c07e657f098fbcd34112aabe7b9fe07accc09279f643e3b8541f16f5ba4f32019bcd54c1eb531607796c2d789e33ec72c23216461ffa8ceaf9fa3d866307c0a8f6e8ad6c5a0b1de239f2ed0d923623fbbcb1dd3dc3ab19e1a610974a85f73b73ea315834fa5a42b2f7f88eaca71d4ba180ad2ad6c5c77d213cfc5d4a8cb2c3e633c4f38b0f9052ec8ec2fa0ec23087d612a77d41a8ae25706f71d8146c30a9276ff05138c6b008cc40ba9bf70f9cd2bc6ec67090291a34232045387e6d696d03b96703894d074ec635d0c15fc9cb736850c7ff4f6b98685ccc06e4eff2dd1026323aeaebfaae06e2b6633b998790b4bbadca9fd68700d9338de539cc53839f6a9a8b721d880bd8d9fa196dde968eef8e37297c57dbda24f3c4def97686c7350027db4760ee218dbad664b4c05b1735c77cbc73b876000e0dab04e5c7a3b34a60dddbb1a60214d874e29bba9155e5cd1475faec963ad9889d4abcac18a2bda43ad971332e3cd0f961263b155b63b19f6689acc082ec27d2b72a38bb609078dc85bd576045873172549ad4c600bc0e1706b5bf3426dc6ca74812025330451d81d8dc643a16817af9e13385942e3c6c0de4302501a43bb027fe3d18b6e156e19aebfe6b0bf6e3bcfd181cda0ed654b92a432ba0ffee05f613fc4bea680446ad040f6d0a2ca458b65c050769f94b1fd0ff492b6a0b6fb50ef3bd9063238646f96ae75a893ae15709aad73aacc2b8a7b8fe44d356fdf879ec319ef9985ad49e17cec4538b4e00bfb2c3d8495d1480fd1f1752767a18ee8459454fce06c8b02450d6dcf50dac32e951453b0c98e1fe7e2e3de61566cd3079f4bac8fe404acf07237d0406210e2f7f800dc2534ee689f48eab0fe0af6fc8e60a821759fa76383dad01a00b11f260b7cbad6079e05eabd455f88e608c3615ae191cd60727e7de3b08ee483d60689edf9bb43f765e7e0c47ffd1047d13dac247f1268d2c8e17a765d6ff48fc1fed4b3bb443605a5ee55b42d54464497785436a0f74fa4669dea8b3921012c73af3327dc75f82126a68fa36b0e8b059e0de4009fd6a20d715fda45227b9c1d82e37007702c52cf5cd929535a777269869596a7766301d9a734b98e9a8c8701e3e1711199a014feba06b91cba96b9234a97e3fca33b91d122db83a8d142202854ab2a3232761483f03f31df63de5cc63b58cd8b61252b9507ef8bddb540dc9d8b776b8b3174dfb65a9d626ab4219b01b1018d9146f59ce21a0e7e386986366c79d2552b165518b868187328943d20724055a8b1cded27b78c287b9494110f2e8abfc61794176ce0f999953ec14992ccd6cf66c85b5ffd798751f23458e4c177ae9fb3c30bad18d0ec743b40924b4a1f2b6e89250695e6d01935e3c0dc9ce3f2c61b062e739390cf475be0eccdab1655502b070949a5f499d59b9ac08c19cdf28c1850e5d2d072bd0eb64c442f1810fea0762972bdc05e69f2dca7de0a19b63f62c1f8bad0b49ea1be7c963d85f51195ce9f1bd0aa38a80b89c22216d3e88e7012934b65db8af45671f79eff83301efe623453172d0ba0cd103319619761f18fc4e34707d1358c32015e5b58d2e1774988b84db402d051572d3f9497e57ba965e3e3cad30a43a1f25ca25b43901580108d3e6170ce01806ae1a09ebd08a04b3e1169dbab9c1aff4cc3f15fc8e5e0eb16fc9ca8d0cf9fe54a5da107fa6daa580e007636c7146ebfdc2ae45a23059b58d90f203b48680743b458c6fa916e3a368d1720cdf07855e13bd1f53e9b0853754827d16f96e2d2809ab3cf1bef14fc37c0d36679f5a0ffbeae887b99a3959b64faeddd853285608e2fa0c9bd35febed6fec065e0d983cbd8c187924757664a8a1a52b2b8239682a5f8d825fc5d821967a86390d3f67f8587cc2a46a274bc406449f1052635c7680e4e9e36f918cf459759ebe40f5cdb278094c7a3fa635c163f922471f31902eed7d9e3cde038abad625f7da1ff2405cfb3bfe5813747a1607bc223ff872c27663e9c8113ac4b81c569b8adb78072b471bf497600deaece3631666a400f9d1d4993bd3ba4a98d7598443f529d7b2949584e26a8740af14e66aeb2974dfb2908a269e07a87a3b6d1910f956deffaada1417e463f4834feb0eba9bc4e7a58344bafa5de615bab8fbebd6c604a6c28f82923c423f7b18ddbb6c0d221a0839a40a9884d694588150112893a8a72f7c1b4984fcc1f0b666d4868b0b6f46490710d47defe952a20bbe4d46929a59086fd39989c6ff4e01f33775112dd31f0a1c6b9dcdd8b1e40c4aa7e3578ba4d29742bc796cc7fff3dbea996345ff02b705e253a16c7400479fa29d257c9024a38eeff5a36f386354167a59e86e7d04bd74238bc3fc1c020d8ef2eed09bf0c5ab03ebc5dac26c2670a53b3545997577faa1e952be4843c1d474001a87bb15bb92d4b919809bdcaaf77a2427b0113d4248e67253782de84f09487167295a3aa038e9091ea44ebc09b7b2e963b73bfb07312b55255e0777fb601fbf2ad561399947d7b55b36cadfe8c39eb7f07a606a4c348d4dfb7a7fa07c989f72739c42e096be1e0d3c35525f4799d89fd26273a32b7e1f2a7e01a83b7e4029e125218276b6e5568d97e7d5f78c449bfad2b66c991452890e2ec07bd555f5c99dd50e59212de51211af6cb6827cc6f9b17381056e433a18e4f3c132a4c6fa4708fcf3078143f80a1559a6d8b9d10a0689f4fc61425db27380b00e6cca6a2b6047de0ad42af6b6b7dd3adff083c0360a6214cd9e984c8f0a48f8bab19ea50a2b93d04bd972a3a04c05abcaa20e346732c91b58a5944c1505ca36ebcfae15ad9c5c0ec3f3cb6acc271be14d2927f4afa1282d5fc7673887d6c08a279152ec4292fa2a7e6a99bf32885ef541c4fb45c9d15b181c8511ed5c9d4e44d8b656374146cad1bd6c602b892a816efe15f4234266ea2f44d45a8abd90b06e54939d05055bec508c5b76017b815c893115f164dc0b9432d6417efbd137f214c8b4370813d997f26f2f0e8c44ab8ea402d6defb8d32c1879aad03afdc204fa3b03a8ad00baa06f5196b4e0cf582ba8e2c110856ff46edd4e524583796744266489d9423e3782aa9b25c515402456613775356028df4b348b77d9fab9612c22285022b40352346627a1388015821ea8a747d8ded86809e215402a9017250fd61b106f216d465a4c6e822f930ae7f723dd1c57a98dd91bd0bc3a94b7d01560477c0846323eba415220177449bde08ed83a2c3504e52958936ca2639b5d8e6a71ca5669686015f1c645e520dafad39addb64f3cdbc510127c4e495b55201d0afc324cdd87093bd90b6f83d0e0404a25d82dc0ec66a1bf7b601f80f65bb70edbd5edfeba657cb077f4969d7c5e323f5719a7bfb8662ac24996a8f4ff1a8762f057fc9211fd0cae983efc840ec734e605f5e6509629b18e64620206d58253bb22bb03f480e42ab31a796e0f5c1ef57625322cfd184843e6bcc2a34c1f58df7222a7c3790433b7b6cbfd47646155e420cdf42dfc1a27c366d1293ad92df4a7d01a81cfbfbcc98a3de66d935c8afe64335bf1480a8af8d443af41b0bfe830b940a718fa880fc74a83a85b7952e907385eef739858601124a21e0f9a4c898b21e04eeaab7c59315e9f40da080a04b94bd2403714ee0caf44d35319f9ca87e1a6bbb5f7810a100f957f95485e85713654d9fed3233fca2ae425c10475ce586bfbd55db18562ce13e57a293b11411363781cb950e8e70e38012d6c02e4f06251476c14a2a30e21f8d950860e20028f8bab834f1efae6c6e81c0ec8f4c834273398f06eac1a065717b58410a03c109633270851a1f7636684415fa4cc530efff811b7e2571760fb11ce09e797d0e599012013e376f2e04c6c68d47cf077ad701153f3ea2fa478348e50fcb2e05a1218e862bdf9616d603a8fdfd2adff361b9fef6f8b19edfcf2c1dfee1fc8e86ee47573d83ac16d7c871877bce5751bf1fb09948bf858d43d70f96347b052bf2054008810e84037daf658b41a34c7fc8678abb1b11eb37b2de8f8389e05bcf1fdc816cc736219bae92a531fdcd671b2ea485b6e56463d81ffceeaf34ba5062eb537249b136b9564b2a83373bc8e934bfed3d8c66ad83f0e5d851d6cb23c18832a22f4ef93e2641ddf13c8791f06cb2eb2017ae72138f89fe1c85f2e57f2438348e6999c0ddd0cdf5438622640b6ee30796a4b4d4fb517e425b1aa9de428eb56285414240aa00a418661b5724124e29d1f08af8f6dbc8e834f574e6c085950342ca7209794a794e14fd583f4c807860e87029112a7c58db8915d116499f0af0a1cde3867eba69ca6fec51a16c8bdc9e750c35181d4f7b54624dc6692a9e083ad55f69109ba79b2e5a7c8706b0eef5df4ed13f955833c4d9949e2e0d44894694540fff8cf50e253a26f4c094f4a176a162a8ba2be16d894b1c4dcc572d01e04e8d3fa6c19e007a0886600ed5e5228041dea74138ce05528cba891a70b935ba81b208001a75e29d2736b124a85a6ab50e440b147dcaec352575167876bb07fba9e06f3390bf2e46039aea0188f8653456b810805cd2dfea5fb7bc626194c123e97da879e63bb3be3b830c86661dbc29d29651e32fd9ef156c95128652ab3a47c6b5fad20fe61d18d1220bdbf6ee0dedbcfb5f549e765ee92eaa151908ef72b29baed959920547f96ecf38fded3dcb668c9c703d66f37efaabc092fdb4594b46b673589c67e4df6a2dde5c9525290f9272ccc963edc4cc47133888733c4bcfae643754f248e984f476e63653a7623808c32001a191fc4ede45b04afa77da9e2524d9b7e62f44bdcf8820c421022116874f40f2338146e3137be335016a8271ba53dcbae0485fa7a147dc45253e19c2995fb68b0385cfa8ee0e7b51b0075bd75d651fe7d3ddcd948edd303d9dd149c607b32d0a90174e7fc22f4828b0403040a1d602dc520ec239a1f6ddf523322c9fcbc2ea02585f92dab2683442d7fc519df92e6f0b0a4925c5224814128538ab53962f56acfb068e00628c54edf44a2c23014629036ac7211e07919f61ca1ec7bdd6d8cdc431e8a17b15f65268d5c5b46b84bc09706b708f62613b480bef02adc1b69a6d5bf041afedfb1015a439e3fd36516eae779024b7e607e1162b1ff69c5cfbcc25210fbe20257827019ebd97ca25b9d8c639c913c715a18636bffd41acac736f050da63012a53f6c1bc918b21b064008b7c952b505e20fdf3e6ee9b3487d92442d100326ea15da1162c12039e7aeb3698754ee526c182ac4b327596514a2dd126671513eb609fe787f0c98e03b048881f613a66cb636ef4b84e0d4066f1608439be18e23e60e42ddc1d423ef57f7094587c682ef243319eb1523482389fcaf2eda2e0e9a635ab67b0b161d8c4612ae2a8f7a465881762b3bdb04203e209d4c3a5a1d307d42af73016e1c4a47140d305e5d3d32df9b6780e7f32a5b7b7532ed50614b8316ca9187ff652744db4e18faf3658f9dac1000d88cf283a3fd0e5d6ca229599e6aaf4f8ead73048d5c0803e47a5516e8e80defba2929ced244a30f268398d2e6aca80367861d605b2b6f7881fbc4bb86d0b6c03bb4704782251d3935d398863df66aaf9531ab00649afa44bc478bde633e739abc7aaecee9387af290898538521e1e1160f9429f50769daf79506e85f3d8187c20d89555cbb5c398c502e376a80b5a5d8f853ae59648e99eeba0672e121da01c3f72783de68543eb9f24feb5d9dced675119f758ef2efe309099d5c33536eea5fa0c5960b9ac90694b498f4eea9ce02ea426d14a9b1bcb9bcfe8f743c69175a6d5f83aefd3b8a60d932c24bd6bb1945dc905949138db8b41f2da228efe3e7ddc80a689e77d80398d4b50f1a414ab513a3f9abc133a91623d6d20b8ebfd2d65d872291099a3fb4c807b087aa0c1902bee1e3501cf16b2ca68898a045e3c70974da81b4a0771e81bd8e54cba3ab409e92923b0a21a569701e95f800d2f061c752c3abb3d8debf906bcad8b952344817d84a486d4828201d84bacde7a8aa2948dd5488446ed65d34c3747b3631ad3e9f16735ffc227092f697a856f366b8014bc69557d6a7062139dfcbafc5c7b1c36662ce66ff97d3778d183693ecab98cfbe941602d6e3c60bf6a9388147a45896e29d4925fc7e12c72d32a25b5b256206640e8fbd7224202e584188f52ac593e13fdc7d4a5e81337af345796bbd65894e0b5e86c19d9c8bba0663dc6bf4a4663b167bc14592577c803916b0d4e2467c1a2d12ac1463744b18af81d28cbe8f822dd51c1201c4275d6b79e3acd08b00f475003057088abf5fdaa30571ff0e62e419c8d086994cb5163abb796e86943142a8e622a6efa5ecfab5041db15e26d1ffa69136f7ae3bb9eed26407385b72a02e81f3a2daaa6e094e5208c38a84624c64f80fc81d7d9caad4b0aca4953b0eee10529b4a4272f97ef05b49630273dde627f6121e39afce8e575917826fcc131cf113fbeba77f2a265f3eb38611c8a5418bfdbd916cfaa06f4b95fcc0ca5d1c6e5bc48cc1e5d221e3664f098c008ab98cbc929299bc6dfb89ad30580cb2d69830d9a885934218be71570c08f8aac52d4e32de961368063aa7f72a913d98ffa0e09c9d377ac42652ecfd29241ee6369a7b3f4b167f4d7799ac227378aac77aa1c431796c6cc27a422919556157f6f7e7aa17aef1d26239afbaa735b7f0d4941546e6e0bc27bbd4b848c2b55e3f7b7bd639fbc31ffce601225043bc09205b307f020fbf96e734a7f9fb4919dc3c30f82b8b63a35c88b6e007f6f82ba89c30d72238288599711e000fdfd84d8d05ccce8b02f63a5ef4af63764936f48a9b443fc621cb15e439b6043e98eb58d7d2ba85d0ca354025c35524c05393588c75d4ca621ed4f66197e893b5c4e5b04b9496ea94334d32c446d995da7c8d59335a42c5a0d4ae1cda57b3c3b5b6d2c68cc873e056726b059a22e7435eac8a5889bdd2d456c6bedfe386bf519ac9dcac4740f7724eadde37cf22de3a243b3ed97a4fb834107019bc8b97092036d60095ef0eea350d29cf0be4f1e6dde1691453de32a5469d2e675f91846284cd5ea828a85746cd7346f2f8426a1c19df7cb0815d9e9d35290772034f21c756f88d5006de821cae6851125a5da3dbb48b8725fd3a31a25c3195c2e8ba295521c62e856e66d7b68c3d232e2dfd4170939f514605c2a22f3766d52b5005b92c11cc0bf8acce29789eb33ac008f303651b3ff7c645650391f46d4cb1a87a743c524c5842490101eca0a0b56eb74421541bd9f0ac39683603c50c99efc3704c9bea057ecc451baa944b0822355e3f59108a67da51103337deff97dca62378c1d07f17393775e3800100e73e5ff1c24bfcada77d9fe10226546939a9d4b58d995fe679981f0533832bb11a9dd3eb602c7f65e3d35cb15bed3a6e1d8b11e1aa40177b45ed3199edd77e0a2efd584996c46a8913c6fc8589617fdef789253f9279e76c161cbe4d3a8f439c28cd963cb8163fab40b9a0cd8de13abf7c826040a5241fa1a8fd1324eb40906f1f40f9188944362e983d39ab89b87d622df8b32b19c1dbc4d97c4fb9b4cc287ff50de6ec9487bfc6767b2f1b781c853c9f139e1dcebe1c1d7927514b68eaf2b0abeae8c442c775a20598b91456910c819132d19b30fdce330aae2ef32a90db888ef5e09fd5d747c5e54dbf130af6c29677c120ea94e61812530b76b1e60c6dfa31a3a4230c36a90fbfc8846bf932620e3e28c4e863b7992012014668cca1e7346d593a8ac436de93830077cf915f948917d0c21ec2511d831b32ecb99c77fa3c8bbf8542b01ca252d179aed4c24109a2bba3fce9bce75b80f13a3ddf96af9094e3157edf29411e677afe1127188f442ae59a6a5d2ed26c9cd0ba8d9ae3560790e40cbc829b4950585e97bdad5b3908e9a4e108f6a3274fc7915677b74a16091904c21b546bb01beb7c3fee40c8b41ffd07bca9a1187d1eb35cd2cce60b1cef23d58618b255a12ac6a5f0badb9af5abf40bf5d0f201b91b4b9cafe4b0d66c60f7146a6ee25ef7f750a81d6f935027d00ef70e9f48b75d800b0d305db87b8d9e39a1d152fbd1abe6e6dfe0340c70fd2887af99a04a36f173d5ee7210aaca7db26727a5e5e261dceacd56ffcf3fc2038d07ef4a93c12699dae49de9079616e9b38dadbe6bd1c3bedc967f150ef4f02783de16eb8b74121287049024fef877ebd8de1682faacb7962a8fecacd5370bacee8c2302191b0fdef9a79d4a18cbe5ca5ef3bad66fe55fefd8d9dc35e08e8bb11ddbcb9bfbe9b122a6b76b8dc66d99a064f26faf3096a4de5465ae8dacfdbb4840f9334475b8357d42140802c104bbbfa2fffe827859ed650ccbcf5c7dee37b288b34158a355889067261faf5af94d6d2e2cec9caca7edc1662e68a74fb41d9d5ce39fc9fb38539b89e1ad71a7fe2d658385463eff43d63f106f2171c92c559885af31c0ab7ae85a8e031208e899844278eea1d3da9aa5e4a94e8d9b73549ead2085088f13bdd567e1e2e23923072ab76220881d015e3fb390687449bc8ad829ab6286823d18bb8440dbec107c72d15f281e316e4c0548aad0fd8e9d7e7706a427ae31db9c2732d4cfdd671cb99d5c7ad6555f9f583a60930ec2da44dc5423637f9a3b655c284b8269e9a0fd9e446096468056e7a6d598190398341421726f366ee0261c6d0ad1203198378f1881d9ffe0061319105af7f2f9fe91357cb5033999c0c0f32496a4cf22878368556c146eb1ba76433e3921bfb36dc09e1fa738ef170a634a64ad9c2c93d485a7dd7d38a0895d936e29284af993f425c224c3c22c4916790c311bd120eb970b43307cb9273c98501ead19ca024ddca20ed8a78b797d4b164b3223f8a33790f94f1daaedfcdf523808207e8383cdfc5fa2913507d176d1630a08b2f8a090081c41ad355ea28606ca789cc2aacbe421e13bfffc564ea6e245acb8f7514f6f94da50b60fa95a3d256697aec17c633e1ab278fb4c4ba3acda78ffdd628cc1277f9432d2c681419a8973500ab4ded261d4b42b3edb16f4198e89302822bf11727fe79a5143b43d40dfcb1ef38a6ad491a788c4bda413bde92ef99e44604db7fde66a0ef8a3c4c3a30498e5ec09b2a65abbd1c10e906853cf88a05119aa502b8f8fd03e9647c81e1887ef754ae16fbaf6e387db28643b8b66a4e9e7faf31d087f5e1350257bc5c802a07998bf666a33fc3f60b28d9835dcebf8c5809e622378670814c30b9dd457159fb26b53e1c95e168660809b882ddc1c3d4549288db429866d34c76220154ddd4ab9e5596c13f60e13cf96bf5f831737c841c40aafa8e905cd01d7ab146bfd12aad52c7a685d8515377ebc87b2a9b1c3e6a93e0ac6a54d68a86260ddf635adaf7b09598a7d21c060a04e0c9273540f449264330a1d8d3f2a33cd214b7d4b76489a8eb05246f31457281204fd66872eb4057ed6284e4ad0f8095c26b3f54a17ebbbc427c68b12586e5104ff44053f4ac1f17aa6a8a59a4567d7e30b5394b69ac5dda4b22ba27d1c0251d2e60f627cf05e2c984cf15f84e0a9842896dc232d1347dd9a1fb0cbf58c2c039258059c52731179d1fcb8913b1c1a158858a408172d333c2f894e7877824f0b1507daa2733aaba1cfcbeaf786dfac0cf36624f6146a8173df41f0daaceb7d50e5dc2472c079d5adc8361313e20d8f575c031ede05dddbb7865d982f272dce26837d7fb61c6990e77379061ae6ce5b9fc9f3355bd4ff9a7db73f940fdfe0eb48a1152e89c4928723e9edc14bce90b22779c1b72f36bf24856720cb33f6a809bc9d2ad421fed5227bce558898c88f3f23114a453e7af0b0a760f9483396b571a37a4f255037ea0b2986f9a1f398bc80869d5d7d1de832cc72a10645984cd021c490e97306ef075a842a3791b2ca4816f3d23733ca16e917342c1203d8c846b1ab1be32eafe49924c8e1b81489d892215e90c59026138d530d1577b2f8010057e218bf92446dd05de7d6b9678420e0ffe060cb9412de06edc13e639d5a655b08ac6e3523aadd118ee1b108b5902e9f0cd94f59f9b21b25abadd84112b91a60ef254c0f394da418e64431e6eded88c865a976ae8b6332b92e02c324ce0238aeeeb94f1776b98401f04da5fb7e167fe892753678f7bffbfc1829c2ad70529723034e1bd36b9ca33716114aa3b90bd8c13b4fcdb9fc8a0235715a1463a91d7ef8814842283b1258808f4ad671c0e7611d7ff28f98300b79b63e266b7d0443ff9c99f7ca59936cfb22543b319f4ae0f8612416d7ee803b04c63c275d721f6fad0b60dc7a053fb11c72e33a78424addca64160ec17c71c5f0094cd63d2df90d565873ce629ff1f0bec2412a5253932cb1f931b36f566f757061fee36a8b4c4548793ff9e71cf318743f4a1c9488be3994f7b7e86c6e1281db3e77804bf2c7622474d9d9c4ca648988fed0e6d84bca4aac8479dcd701568470bd61f0c0b2888a1f23a35b559c0b816acfd6c1e53b892f17b9ecdb8650498a2eb11c812c800ff12a02b563beb2ef9efdcaaab337fa9572e92eba5aea0bad4877fce4843427637b7fd88535a30829795afff493e9998b5aaae17cbb6cb83b561a84061beb8619da0ca7a2e5142f10096d9890c65502ac423a1b164c23afa34de779d0ff0ef6d883f44c9628dab15b6d5dd7ce05118633e841f563d65ebd0b2614094a2e1b0de1f9c3f230f167c8dd67e2a20fe7f90ddff7b06f55b7462db05987eac44bfe9432bff0133585b7494c9a4c93b76edaff2e6d22a0e93e1e9e1985c9f8dda50822ae7747a8ab46ba238f438985335129a133fba59fcb64e969414be2ed36efd293956e3c89c5ebf846283b6a2d78cd2a5e3858e18c51cfa4255a0121b4af12ec4a3295fe949668d6907f7b5f0b9f9640b239d6d6367073680ab93f2cb1851ed0aacb307fe3145bdf49df5fbd414e3c225d645aa86c26f5896f1d7aaf13cc0a00cc2dedd5d3453bbba6830d804ad401b8fab7f6510dba3a21d65d5468e1107a7ddd63c06645ce6c7f89e82fb95439384811ad02a1ef00f16bca32421490c87f6d3d140d1ba6234ff9a3b74e66e774aec2807fe18700587a5090a6aa5f0f00e868f459d93831af24186695eb3c33d68cf6df43d2d3d1bd6f6420f1d7ef572d8e5d1d60d9f5071ffaab15d02417b4d6c94ace7a8246fb6bb4c2d45f04ec2f44400dac6fdc84798834340191dc18b137a836fd394798eed4ebfc39be3850e33893de7f928deb834d8cfa9342e2eb1250506f0d555007098d0a21cc8e4e3a636eff171ce4160a842f925ff50c3b1d257d699723b3b0d47225b092139d06c9043b87c0bd370d17064c7fa9b35ecfa6c0acd3aa3ad8170850d3c9a5d2bcf14221d77378e28dce1070af311c3c5a72247d9a43eeae38c0fbede23f500cb8a372df5b2c6a1437725dbb82f94dacfe713b8854c1607ef473d8b437bcc5866cb10a8bf9f7d056e6501d2e89ee8b52546ac2c1153f00b7a765eb435eba65bac805fe592142aeafeb5019d6d0f15a6730ce85837e2a08478c2dbef608b0c7912667f3576817d187ac4a22836bca24addffac70c15cce5604b0d7749d856cf75f7875c50b747ede9a43d3878708283283bf908efb14d4544ac28e96a7ef042a54ed91599496c40bf9fbfeea1a49c7760449c5d4f7008d7224d4b3829aac6b55a3f2295966d31821e20de126a7bc34a17d65f1db753ad4868a847c95a79b6d1b037ca9403f12639c5d94eac2e8087b0d302f914fb068290f77be34a0ead151b5e673273b54c0a986c85101d3616387644d3d535905e9baa83618092c54841479a06043804e081821364af4a5bd7151e77606ae4756e415a6debc80fc31e0f40665cb4521ad0eb25f09c307687630652570d83defe779d94d8bda202dd68b64b7f64c39edea7b911f58b975bfbc2de1c1ccd55bbd5c5d98b3e3a6a44899d4733dd5e73f5091ae98d83cce87a252d710a63b68b6b4d738e78b24b4413463850def2ac43fa0a87b9fae3d84a53df24655d86dd6a367f92eec4f52a2dfe029bf0d39fdf1562f38419a95fa3f1a9cb44d35b944c02b499304de70db1430a535522898b77eb812a54d0a8bdc3eb5c07f12e5c215def461a268a01fc77e2f3d43ae5daaf1092ad043548adba9db475f903985cf2075e1dfe217d44105466f6f107349c0290aa4f7e2f5d3a1dc1c8fde1ca8c79332014b6cc839bc05faa9e5405948cb91955c07e00dadeda8f0dea0a8e6cc6a6d93d20b769821f5aa4db2c0998f274583e6e5a648eee828a996341e1497c63118aab06c06c7195b80aacb62d6be6e8a10fc50ca56b9b94ab03a3c338e3a16be7aaa6b3d385bbea89c81b5b907a0c8c2fed07770ad3b1c8390f4c1215313e6981235160fda3bf0f46421b834856c83c6c42a575cbcd255cf4e9419f56565647bc1406f4aeceaa601732585496c4d0fa05b221b55179c211b90934b756c7c00afe15894635538d2267924c6c244a01bb536e08dcd1b35887db3d746dd7bc14923b54a3497da8637dda356beccdc18696cf58e69ab762792d874aa1e44c5a8dd577974d1c667f1e8a0aa72814bca3f937786b74a13198b9355606f8b11fb174a8b8b2d9c81bbafe90bebec204a3a93050328eaa3285f2f056199597bcc353e14b308bb5c3e22d91edd401124e03727bad236899b8fc26d0b51dcacc2b0da9414aa7683c3af2393cb106ca6223d4bcb37b77706a31bff303252e2c015dd0cd91a28fe85a9c28391e988ce70827c1db78ced988d71963e3e6de4b0588101375f33b79cb8d57c09a1c3637663da52523aab19532c031b883922e4ef836f34b515b3dee861f7907fa04c0cd4e083d68225d86851cb9f671c81672804714a9020acd0ca0be4f9c33eafd9286b844fab9c2a8580b5f79c7ed7992bb34c19eb5ee1aa38ebd620c999b91ffec0902d5fa933c47667c0628272ff3e9d58e623049ae0c3caa0f7223a48bd647a58ce1e95a62ba21b7505121cbd13411982ddf338793177c821ef7e2454dd2180c7e968f24cf53efbf7e962f769ded72c00c7312ea2dbd47ca95ca1eff0aeec8a8f331bd7405b44840cb0ba3b0cfded40a208730fe62e537c99561714de7d4f3c81e0824d7a045db8da8f9c88e1114a9a0c97e1fccb7f0126a95d942350ac132157eed442117c22cd69487a004c08d03eff97569e18adf23d2b54dbc924433b6efe4e20f42311ccdd66943bd5e31db7d4b1052446626486800a178347c5475274751719c7ff66073d2120652cdc9f1affc2a350583ede431cc85ec00dc441a6b529592a8aeea99047d257a05744f281982f8178c9a1364e21c9dd63fdae573e2b47f87b138a4b3597377a507e898492dfa61bb255c40fafc8bd9349896abe0a08b191cf1adb21432660a663656c53bae3b8f7db657878b2bb2d93e62ea399aeb6f5757e775b8e7fb3696da882d24d6f9465f1566edc0d2d6c08d2cd1a5dc56f30f9c47f78fedbd58d09ddac40b279026d513b7f9c1ab721dd1f8a6835218b0c62d8e03c6af48d530d6ba7c74ed3f11b51ac0b9a8f319ced2cd2846b6ed308a1551551dd598fd2f406e8731af3a8da3dc6fbbed744e4b22b3d73d5331bbb7245082ac9df6d72fd59db877d9b8bd442d0f671ef0eb2b872439c1eeb6b92368c52b662536b1e85645705511119f00949d5f2890652d1a71d5457674eb761b9d02657498f9b9f7bd8a2cf1cd6b1d5b45d64c00c7f2be2e546ab6e74d491898addc3e762c4476cddd4e5711f5a4ab6153e333204faa185c56c2a81628ddbf4004ccf4ce649c120d20fa33219d0196dcc0c7bafe2e34eaf327171a72a52c93528559ccc5961e1d502b158a7a701114c47f35cfedff455c23a67f5de66887b46bcda187dcb46f00c16e2225e88fc9fcbe5667a0676bbd527d371bd08eb4abec65801c226e021da0e0526dd1882755901482e8070572a43366ac23da5a14bc96330afb604b5367f64ac9fea96321efb0ddad7c329b3feee015b6a668e4473aa520c94ec95d4e367b50fbcdfcfd81e173052526cb65a34aa748476112c0c25dd60eb7a532d90f541bb948dc26018f4443bc714c6e67c0a18dacd5c6141b2ba8cca6bc820658d6de9523a8a42066fb0b3b56eed8f9ddf2d3735f87fc49ed82542fc2f8c4b1b71b1ae93c62ea07f1cc052c9dfbfa52f2f180943ca05feb7d5dfb04f57d4de9136542f9facb60af908e6c4a047496bf382a1658ec47d3a27be7027ec8555222f9f16dff7029983ba3bf0e99a913ee6f98ae03bf295f86469f935e0c0e89658d116a697009a0ab5a99a7434d55c25a770d4b57a741a55dcd7ae33ee33e819bad15413789fa3f1a6f116bd45c9cfcd8f0de0fe6c71814939633b8cc3bbab36ba416e0fc45a5ede806620506a6e5e222f0eae213c7e3a3a359fa4354a6d9e04dab5ced04bd140a0ed447f9ca458a9899933495f0337ae057093e52ad9aab05c6e4e161a540cc70e4f24bc7140ba401b836897e8651eccabf4fc3a1c0c829668dbbfc3448dd5cb20575753e7b1fb97a098386a10c9fc551021eb23eb681dca3c440b98b9bdc80723afdf5f22bbe5989755e2129648133ac04c1975118af613dfe9883d2982cfae922a19b44a875ca75ed3f5b92f704a2a0c8ac787e9f090f1b0c6f66223985544b68a5696f2afffd15601443e29d2afc6d9ffdb4f32b67e4e8720fb29102cdf4383e58ccbbafb106264e5a2fe47ff0f0e573c2e98b849a9f1b3129686a071e9b57540cbad68dd72e553dca204ee40ed99e0367ca45e42ca058f9c14da69349c183d8fc49d8236a0e81c9daed7082366627e9c95f72f67acacb320ed35c915d1abb8e49340e41bd3c04cc591a115e1664a9b6d8397097d9a6622a7c60e7869194589be5c0c7d6f0854dcb9f535625af328875c6266c3c0ff3df818ade7283bc1aaedee4b29e90666ccfe4e2b0d451f0972105ad695abde4cb846bd99573060701d477496c20f5c8a611cda86e46e6194b8b2974cad6b5fed3af15df6447fedb1922d54f71e325e7f27a8303fa42f60c14c6caab36ec22053599b1091540235472e37e774eaf461a4a64c54b9a482629495326b53b08c825e0d19a725f14d12fa7e2efac47bb2cbdc598af68f24e7f338c5d55950b2d47e09b6037e30247083a29038785da56134b57863725531cd427c13af0298dce83e68f17d13a85c2e28fe7d34a05250583b1a4732ca0090be652819091c161205f7edba145eb9f65351a85ebbce227e9aced84dc34de4cab1af36f52a01f287f1b8d58f35a17306270a19c057809e24ff4b78082df40a77de032f987d7827299f616be6249f4e5a3e190cc6aeb85ef605e27b80001a4ec66357ef621ad06649953c201770cdf8ab794ba2d74500a65aad04b9c8d793237185e79aca34c5b8d3399be558c70e4e907e9e70d798d62cb1888b1b0d6aeea4ab6c90c9a2b045221ec0a1df232b6bec70ebd5accf45927311c96e3a463d03039a27c137caff88b36ad06388da213ec1fc0c75c6c646faa14da7fc748dc01be9e6b62236053d79a50181fffe0700fd7d78815c3b99445ae88140febede19210e7dbb3db72b4c55b63cfd78aaefd598595faa58f9e72d00bd9b102bedc15fd31f33fd3a840c19987a91b1142b1f26df9635230eacc243f27b4fce3b1ef62cbcee2b30f3ac216218376d40db08bc35ad5f4a6f5ac0008a9032ed17e78798de71ba21709c63fddc113f14f320799c29e7ba89871e9225ac953bc3457e45470ede7b9fcd70714abc8dc19a4802bd06ab1acd59d69816778d55e7c9f10ed257d28e9c6e9ae0e3d53485a20ef6c2a46d6355a64ccadb040450dfa5f12c3f535c296e08e1183a685b57ada0ff8960a6a0d895e09c511fc79aacbcd03026b47eeb3ab4eb57beac82492303e129c3e168028c112518bb7ebb9fa4269617cb2ab61484914993a79a3f117434dc928047f2f549bc7fd0a3c4e593c7a554d046a94e549cf505fadc3537301f3be458add522bdcf5fdbc9942dcb34c158cb713e2df0ef0db5c7ef5500bff6bb2822c71a88b2c094d1ffc7e130a53552b4b3fab54e8372be661614df20cb7396a788eb565e30979e3c2b318a51257323b0f99ae03d179247f888ab731227111791f95134c2efd1dd44ab7857440fab93bbe07e6ca3e9b0d5468aa1c478ebc60606f5716b6a1bb53c366fe23fcdd205369178acd5dd230abbb0734270ed92436b5e73d0bfab9e956be48f2c31ad986acd2dbc06dea2763ce85e55ca73fb13e3d12fb5e8267cb7450057e4f4a0d1a7e3436ab139ec637c0cfd8d5bf14cc6e43cc61665973524a86121c07ae6611e43a5adbebe35549b42a4488fa0914243d47d13368da811e38483b67ebe5a4ea2f09a23b70dad0dd3693ba771899dce8da192f9b9536b801d3e0391968da8416b762aac180e30add2c730c85e400c27b419a2af339e481522297e3283566d296aa7691c15a87bd8c3a831ef477e13e9ac3d44e62731757b1838ae3b88ea3a5db9eb9c7b38bf16aa75cbe2ae9faede2366a17f9a208b3da4af81b2af3a7ca7b6a8c874d5f0d59e20a26143e911d4ba99925a47c5cf23c72d788bcb756a75653bd55cb0c13cb93e930b2e4ab236318e85d441b52af6793996abeac4e692961f7d335baf155068a2e99625def90b8434b80357e484381a791ed7ebf981d7dd5c202a15bd2536845660dff3a9a1ca6e9b7a2f904d1920cd7954918812ba863411a026440fbae3dcb3459d8a2c44695d23eb3abb56e2dc1935632787b33989b9876c6c3b03e50021740d3ba464d536e85a486004812ad1544ed10c4751ae13344102c44e50695a569ffe6d4083d5b26c2f46d7c5840c27856f45938bb13bd91e193c4f61c1f5ec2ce8e3a322a3b535a992633c3c82b62e5a3b81eee21d620fab80af635b1f85da35d204b07e5b6455d3db7e352f9f2968edce3a0c34fb0ac425da027b98d509d0862ad351763d7c4c9a4432b2cf741e2385bb26e7acb333b093aed231bc695330b188f140dcb89d752b9f9cf11716a076bf19690e8196f0ac4483e775625500edbb88e78a3befb70cbac0634ae3b998aa0b34db39f0c088f2196550552015a405c20cb171bccd981445b21176c24ebbbc32b2931c15a0f84fdeb8a9650f6af563f60b374da42601a6f7c9e1ac6ecb854712ccd5a9e811cdbe2229d8d517ff7e809e758bf7c03a88884bd33f7f1cff3c4e317d612e57c42af0da0deca619ec7a16f49131aaa68d42c4119565da3af3aab8804cf3e81b9c4cd389bf1c95c229a049b696d2f20b348b68d1c41441158a1a866446164e9fb4367dd861f287bd543eb164f57b1e80985bfbb0a75be18ada1ab4079301502bc7fbc086d2691082f4fd94c90c9429c99a703d724ea4287b70d58759a75ea71106b0e9745fbac0ac1d4b8d6b6c06b885413e3165f0fbea378507ff3c470a9acf63d241777f486386cbc66755d170e8a5a80fdc88d26b018aa35739993e7e8992ccb2ccd4edcba28e6cfb6241efe1689dd2f1aae17a4c1f765e5136a2288db67da39672195e702596a0b70b47299741f4e83390e3f342c046f2545766807f7b5d7ec0d867ba5e086ca36067a3e9c48ae593e101c9b517df65a4d2aaa16d3c996a1fcbe7fceb97a1f5bba29715407900f03f4e072335c34be830a347f75b74431496e01bea0f6767c3e7094be5669b514526fe0e55e808ed29f649542d2b641308cbd9b5f899d3fa8557891cc925bce017ebb6ad0c2fea702cc88dbcee394038c6f0b340db763e5fe15587df7d396da30abb4e3261a3fd41a36b17646fedc3ab94e585e989dab3ab2ccf4601e272bd1340e99ea9ed62eeedc719733b3077aecc88a1a77635003e81c1192d817f52afe089fd96df5f61f2c0f4bcd6490acbb9ee362247b8553860b5192d32b9592343c52f51897b45e3184c2986c2281572bc18a50463a2047c234fe0e7182c4f80600dd2c282209d207027be10db7e706e71f17e9c2ea51fec610fe718a5bca17387fed873dd0ecd822921d280487ffce790df1cf8e538c3d2864ca7532db8208f4bddd4a0fa9fd8b38b87c921d762cbb10b490af8262ecc0eb162b9d4218721a71c19618ec7e0392b9cc9ee104ed99474ae02a21b9af976b7b126696d075bc0c253106ad98fb7112844a6f5c91d2dc74cbb81d0225b2ca6baccdb8a21d08b3c500449aff7cf3bc704827acf1ed25ab076a0756d4f435b96c7b3f78793c311739242dff63a0ea974782d6135d49222f2d66f3a801a9f87eddbbef3d4f8bb9d14697587ef19a190562f768ee29b79563f4a7c3b9f906cf54cd1badba02d2ed7fbd5748988759103732fb186d2d1f2107ee1e8a706dc5e82149d7ed370d425a67c3f61686c652616e5eb6d782798ffff47b0a697a02a767655534c1bc1ecb71534b1798dda5c59adcf3a606a08c470d630c48c04ac34f6fca0baee01628d8b52d35257db44e995f3902ed32a84115168399107f2e4ce29733e5e9885f890cf53bab977906c42053d92b91831f42ac0f9562495c29dabe1162d57b2afc61d53cc893522818ac13cb266406b6659c2b8256401656e6cf45e43faa3b08a05dfd0307e6abd2365db2c7b13cce1b62c296d1b839899fcb4ddd31d17929822870756b93efd48f65ce56c0456ed0d376d7ac275282beacdba789253affafc7c162cb205178b92814a447c0a58563eb212e2c57693c25a42d820dbd5bd9df82c38e2e0cfb37823dbb5eb9924a848c83f5e87d53b324855499615300e8bdfc06beeb90bb2357e729f1f5c9c1ed405708ef046d602192ed4c3314b2e882ca19a175bf3a2cf2fb579ca16a4a008147684d7d10cc978abc1a15df0999d00950b92c34fec9340a25bf95e3b82f74b046c20be1ee9cea051055961e997e5c2e314141177a745c328aba7fafd05149b4430135836eca3cf076cbed9c34c3f7a93b6a8e8231266b24368a8b85c694bf7a30a046b74e25955f3615d404585759d215c38ced97abc0eec14251fd0f3541a6124ebe4b0309aad20459f750910cf1fbaaa49b7ab023a7244815e1ae4f5a299123548226d512bf52995aaf7112bc5470142587bad7258cad0ffbd9e5cd720dddfb467a61cac7cdbf0ba8be4be4d5690cc6bf9c2cc7ba65e8ba0ac2bda65aeb969502aa4a1c78e079b1cab3dcac5fad3618953660b40eb0d3487153decebee55dd0fe8f3da986268e7f980ef85395d02b277eae1a079b0ffe9df8b7a3dc7fa208125193c597c5cffb5f6337d58374fc5fe08c0c11bc6d7d114acff3898afe8a8eb47c8855983dbf2c57e0742f3e909cca9221ca770001bea85283cae48429e1466ca9669a4823e5b2842c89a462f6bad5c9b257e8ba1d1adb45382c5ac695d92683f06fe1838ee8da71a35741d3e208e664abccac8f311e8eaae2702f75199eb32efa0ba3d986638fa160096ff52c6aa2eccb10994de7ca31d1b41e75e765d546f0d29ca1596c66e0a59117b2fffa8965517c79dcc1dca80245f37960f606b468de54d8c53a481070ec5a136a228e38daae7fade7a9901044410a0622afd92bd0f75e4186316e3848d975077b9cd2f9445cdd53f8b12d9e9b84a1253f28e335bb5e6b7ca142fc70ebfb9d1a0160d6b740c7f920cb6156ace9a7ddb1eda66d2526151335eb012482b2559b3dca10980349a6a79919a7f0a27b29ac634589715ec9960c9368a5d1e5ad6a54b502b99f81a72483e6db177ab9e6a388a6350b3c9d4dc1a9568f6ab37f0ca9fdff3cebcd75ed1a9d65ac211bb086247334fbbe72d1f2a1ec892446ff72e425f1bf535a9e352d6237dba279d24f2c4cfcc15f18a38073955ad568e208d5c2fbf44f6991d6bebc2314ef3114de5d33c5825c412fe9b8e1ecfdf5e6895522a11fd82c8520579b6699f756cacb3ad361d18f726f57607606f009de77c45a37eac0a5ef501ad875632f44b65696370613cee04283529bc539ff8ab8dc71609c730c682f997548c6b0f878fa5c3a915d4a414fa995b038582c18ac14cbc38eab69a0cd06cc3829fd1955736bea523608fa040ca5f25a1c3b078bd821a093775a22e45f03aa4e3f578d18e9653a78ee96968390f999a04d4d8dfa929f14585c3c59118acbac5731584424fb8b054105c18503ba616bd91acd517c11902cca023e9b339c2745b1441bf5902189d035786038ef7468d2102e1803ee2d0823380214cdb80aefe2d9542231a63182d1a4d1e8cb9aa95c7dfbec0e45fd99e01260ccc7f6a8797f842dc1b1c4fd00adf9c01213731d793ed1ef15cead98ef8ad8600a4b6c22c0b4a6ed630fc3fa6edab6ff1cdb93da7fdb01482529b0927e00dfd849da00a5f0a3af79ca756dc81bf38b55dd992a5c4b195fbad7879170eb6583ed6e6da9567f48b6c29165e094ac78c9314885ca81dce5e83d20ad5abe1748a85a8d77797f425d0c212ecb2d9c4340080c304ee4ec611bc1b4c746c2028b213cfda833a8ab031bbdc86f1573794918cdba8b9b12446de3ab41f432c06b0473964832934b056df907d4d0886174e93207012ba0510a2a720fbc123df3d7973994a2d37d17bbde65a9052c32ea4a9eeca6376f6892efaccb15f2d2f3147e6323012227e06df99c3290224fffdbefddfc0419b35c43f3ff605622c3f1f637d705c6f85735332539ddf944f08899e9f10db362360f1fd81887d9ff1b19883e160d33e8c43303543637dc2ef471838e675033d6422e75718011fad2d2aa95b88ede49e63515e01b4a67dfdfc10a7bdea9fc44c367b32b5bf1373c93f7408590214f8917fafdac853e0ac06caf29c02ddf46e3e9fe6dd42cbdba99f3443b32c03844d7317c46b3736ff1790c121d27f58d02e031464a79d14cb8fa87a0f36cac25e161ae0c86a579ba9dddc25c9f22a894988eb3f082a4db6364d4124b163c427433707bd65f8749a2fe9e48dbb95ec522ee1532647ef4c3d3914245934d1956b65e62c480e138ea381c008cc36ebe541931164619c0423ad3f1b006b7180a670f8ec42a1c5d23adb34f94e8923b6cbdab6e9bc461c1d4e760aa02c9a58ee57060bdd61e69e418db70caaf5d627c8c28b279b0616573d1156edd31aa896783d6b9d8476c399596f6212b2118d9c4252fe9582c1d7eabfa64b63eda4f02831d2c26877654ba5bff1f7221ac21e1978691d5f0ad133cbae796a41c4cfcee54742b8c32b3b3ebf665920ff9c5064256242f9b3123960dfcd6b8b565b05e6fa4c242869cd191ecea62ea130c4022cc75d73722e90214529268a1116da3c388f7a8444f2e38fdf232d6d937a6f927ff639e46822a590bc08334165ed541fe4df286b04e692b3dab68fccca13f343cf2b7194d964600e40c7e01bd482923517bb537f3dca989c551a8677e92a8dffe401a379e020e8cfc5a50f208cb28d2277a3f0b757346c8c5949910935816cff07c5907aa121737b5ec576ba6eb598ca38df1bf9f9ce58e7bba9a326f1f79c371601885cbf5f869ef7ab61de86aab68cb84ac34d8b00719f0b9d79202e34709ccd22c865f825a4592229932b87c68c24e499a99b9be071737a506e80644bda5c286e0eb35d7619a4a482c4f07da3b2775941a499b0b3f5fbd3a203fa0d071d210b358c4f40f33f26ab5ab24db6090e90ce194403aeaecd0c710dd65795a035a4ffd32be81f52b095c2eab2cc1c50ace483e6767ff07df49f26d32563d8694edff2c0dbe650cc0e0d8a78f71854d8b0dc1758155cfdce898d62e813727465086f016468572999e70c8f7599d3a1354c71f3e8daf43b02ade7568ebf03cb076ca99107f81f04cd8748062fa4fe710ad3539493982f23e810a3cd7cb9e0fafb22b939132219f65f8402a77a4dcdf086e2f299026bd5731088b97434779c3a91f063cc9067807b9929ba5c956722dc00482329780a6bc4934025743e02e0ac2b844c3acf44352203a5947804369db5ca4da286d75d68d6f2422be879c3877257725ca3a84f81a73e5a81878164c130a1b53ab3e07a3aa990fa30144de232cb800f910443088a9cf5087ec0425b0832b30765179ff971f412c48acc27764aa2970c71ea67c2ac841c0264e80213a495c07cd7b548cff5f87453f314772d0b64523a4ac346e9a7638a9778531cbd0ecb60ebf7c801ca4a42628954b6bbfb9fe749bf421efef45f71b970627fd0832f00159414c0fa210346f2d3845a5bc917325360827db8e375bae6c56f410c79ab3a7fd70f1a0fba61c1a621bd058c16c7118420a037f5ba8d7260f0c897b19b656565d633d350859f5648c8160c63f2ff1b56819d5bc90febcf4eda5c0e7a2ef10149d7a5baa45afa7f7d423f54220f4342625a48a5878718f04888ba5c4826dbdcd4555e8075537bc473dff94aefec723d89d81838f646611219e01df5ea5515f5530b358e16783424811bcca5e5316dfb61a8f67d84106c745b0b170bbb7d42d0197e39d1a6d12114ef37488a0715d063e84e98fad7e342e2d5b3b0843cc1f456b10a9a2644297791076801cad64081674c08925bb49b324eabfe08f75c12f46308e710d3219f6941204b87322f0d3164ed50d01c8238460597a83a713ddfa14a8bb5daa0e983775317d21c24c17cc8da373c96620ff27c31f57978213bc7edaf03112cfb1fb3997129eb31d5708c2a6b605a7d4b109540c96803eb938dba7cea21c5edb5ca7255aa480ff3ae4d1d188366b91b984cde973022509fd9d301368dadd383439e5dc7eea2bb0e9cbb660c7bfc0224403fff18a8daaba7fed38c06919eea938ed5adc281643325d1de87365f00aff9d50c68036a020b8313f0a67a971cbb6350e14f0a8c06aef9954c8efb6b4be2fb4a13d688109098852e02fc55bbcc01baf052648cae44a93cca4fca29168dfea1d06b9c9344b8a374f7c1058344a47001c9ffc08ed3d0156652d9f2882615ae76d8d1377224a84dfb298c83f2cfb6c3cef1f849519c9ecf5e7e52e62ce9c66f85a9400c63fbd02b3d37e13f2c89809e12741be003afc88f995ba60b5477650f63df60bd30ad03921bb65b4c84582d912426a153b537c7d824cec8e1dfbb33b2cf30ccb31ffd013232d3d53c691cc262cc171a3ae9da97cf5fe677433728acdc3294ea79dd250cea4d9aaf2bd51a0010b586dce393017e292cc0459a835fc652d7cd66abb0500d95e3f04eb309f3ca90dfec8c1119c691fff1a04a384ddcfceb74e3162940920b76e64c80cf4ce4a5b3cd449c839b19c2e0068dd8c46fc506e9a905a4a16c3d21665a0916014aca5468390f8ebcbb375601afe0544b194eb97283aaa499733fed3944241086a1d82001691b75fc051a011e1c98651318b48b8d109d821cef3663627986bc49ddb55b93d60fd720a8759f315ef947b541981c32c403ad430e5b9cc87be358fe514d4d9b6ed211c77e72c71c136412728fb037faddf07e84a03a91bb28feb3c44a96604d781e3b8d100b51b651e029d19fb6b5261307ebcf27bdadeeee7c8fc6658785cc1323aa2a7f5d12d639ad71e91332f80ceb3111d8e701a815ef294ce7e2d247052c99e9b64413e61c87e1b7e96198f8a6244b94b124e7b2228ef653dc35b48e15a148664a360d21278907392cec5b37ab60d703ccc71f2016f33eaab3fac531ff92d840230d2a038b5e15c3e482c42b2465ee1e0542fa0dda7d5f5241cf8536fe98c4de80910eaa0bffae1397a1967e7b00f2afcddcea6023357028ec884ed5e5dc38da4d704f6259f3058d4bc2614739cc6230c9f1467c35c8bb4b6421680020c365f619a407f45cc5bc0197069e6991aac28e3f1589bb59fc99ca1b5aca2284b08e441eb576f2b708b64d414b2223e46f6fb55225005395bb1c8ef94079a7df2d26437051e149f6544bd10a66585604ef294c9bf9a783b17117ffc3a05ddd7073b629bda6a582270f3ed12e3ca40a9a83cd1d8148aef840e8fde04a4f2942b5900b7c79dd941f72be5dddea884c8421293cf08e6527400099672e1288e1ab957df1ba3090fe9aebeda5b48ef6bac2a39fbca30b5baa3691d4ec7661bcd2bdf5ec4f2d7cc3018e9e7da8f9571aa2501f9d3d5678091ae413e81bc9368f1193fa95ee437cd27447d944094cbd034a04639f5a304cca9a0e7589ed157bf66477b1ff7608fca9fdfbff9b9b3b641b094267b4eaa879bbbe76e5fe3b015a3336b8f3383f78bf2ab4a968fc14f467b68e18009de8475ceb0a13775033867d5ac9e00a1c4d5a6f5ad2f69652ca9452b009ff09ad095eb8a6db7857f58d46d363fa463fc134fa3b11e9f6771a6f2818488a1dd0eba24361a01fecb7e7fa36c95879d371110431afe5b190ebf9bf96d76a7d656fd916b7ba6a3df72a22e7f8661fc8cb2f2f8bcc402dca7903099ea45a6bad319c9925d11b7ab228d140b9602559a94af294a5861dda3ca1823d7102aa82c902ca87294fbaf8e1066773434f1625fa54cd98ca2689c6d0a40b13830c459e89145190512203021688f0c2e48a270f6aa9dadac31319ea931b5ab0b8169a685f3248d3b2da7452da462ebdd1dff1a79452002061e5652d38a4db6f474f0bee86647a59b8e61d679836600c2ef3f08e993bd2f87570154d0a799e770156f23cb6e23a6b711d851f5b85978b599ed8595428782a5c7724fe3c65b26432b71b8070250ed368f7cc73698f10e4ada265209c0cc442f66c09f6e59d1439ce10eded5aed97ec58a3e1c4d2c8c1810e1a3b3d3e1198d4c795cf0a885151974f5e3c88b9590766f6251ffbb0eddd521c6ebf7d7d1222190c84c4ca6019d3ca72c752daa42802f57dfdaa61336d4e2a72370b4f3e661bdddf5a2b8714b8f2fb8bb96c96534a798f6af795efa4cd6d9fbf688773fab794d2369b062510490c2d24619e2419a1460b12aa27ac16992254f8d61a37355902b01801451528a84070c50a2c2c9cd43047c0b086504a29e5374f6e562062840e55508e65864ea1c2e3bc10224bee8e5fab51fc54a5544b96268cf820c92029cb113b6a94c8c2e58ef6a7c097f7dff368fdabf55feb83c8f79ee9eb61cfe3fb9818c4f5dfc3c4204e41c07f3ddb2ae879dffa59f74734f9336bb63694a306866c035fa2c758d36511efc8fb22f0bda7542c7a7deb0556af138b5a2f4523ffd61bc9f73e5777420326282dedc1d7acfb23db8e5927ee402261040d9ad896e779b226c2082f9cec2281af50be8b6afa2443fbce18305f7ed17c5934df138d48903ff4a7c8f2a74a833276bef4dc9334917a35d83673cfeb231b0f31484bb4df75eb7974deb7bee547363adf45084708e97be883e52a11e80a5fff0f03cd9e6be5fb17c97759c522f99e68f47246e5f3f0ef5c9cd978487146c51dadf73a845d96423590d65961e54f4a933ff3bdb09b73d22ecda17817ec8c490342ad9cc639a409dd751fb61ec4c2c73ef20776598706652cdb8c9c8a5c2edfe57bdf2c72bd271a59978b46523462514eeb123d7fb67963ed4efaae679b0ce57bcfc3df25067105b1cd04ccdf0104a9087786ad188bdf54d11c1c047105f9bec500efe543c0e554e412adfce9209ea844993eaf7eeb8beab7bebfd5bd271a79dfbd518bbd8732eb56e87d51cbf3de251abd510ed9f6a1d8514693b2497fd2bcccd77dcff53e88fff7dcef7a1ec2513fc793f0c3856c5071509f6bad5855378765cd1df9ea7aaba3b48a85ead67fa13bb251f9f38be4cff9522c7a19c97f8947362a8e3fe6a5dffd9ce6b4a6893e4eab5fff93e0d0044ad8d1e7d66ffde873d9fffb22ff4ff4c4a27e17787dfa369da2e0512ace18505f3e8fee5d0ce2dfbd14833815f9771f447efdbc56fdee6794b65cf5bb96ab25a97cb16e6fc640954f9fc7fc2a0611833815d59fe2cc16a47b29ce5adcf179dd34f9131e712ff4c1f6f31d6934ff0efdc9c73e9efc99fd4736125adc815482064e6cce69b2d6dc8124dfd6ddb5c4bec964322677cf75e00456260c8ac359ece6f67f356883fb61b1e0e5af6003d806eb6b89c81f0f2ad8facfbb76d878745be0c7be88be053bf96c03c3991465b656947d2c55ee68afec6ab70377c04422f5c1270212fd9275fd170bd9666d38d47d0d3656e7777e26ffa8fb991477207993fce9de99e48fd2d0ed5e03275609e7bb9cefbe8ff8d304dfb1ad7b9a31a03ef822d087e9fc4e0c49e902490a7db0f2f90279cd5eafef81746fd47ddf3ca8bbdd779307517121184fdd3752f7d475dd94dbf591dbfdeb7beadea978902b7dafee9d18a8fb9d1088fce95ea7fb5619eb504eebfe25fa93d3baeec5275676bf430ae588b3ef63247890fcee7548211c71f6893c40fab00f521f7c1eb08f7d4c0c421ff66ceb5e042964bfebbc8060c915c0652f3454dd0e08fbbdcf3748f4896f47be828560d81a796ebf7c2f949d2bcd1725564daa246c7e58ec8e9c5f65ea459b78cdc91d193765b2891d6560083e4ef31acfe52e3f20dd1935d9c48e3469e5abff4db6e1dd4343b50e5e5696071848135684115a6ca39fc19dfb00fea93fd20ec0dba2080c543f47093b7653d61bb621654c6c0b5944807471eb7718b6c1ced46a6e8de25632b70e89b9d5b3b2da4d4b4c52a889d6818586ba69484b3714c4d8ba4ddf3def251ab9bef546df473ba1c8f5df090d609b953ff54d6800dbbc6f00dbe67fa291914cfed43fb2c91f69b24619cbb6d6b36dfee8cdfc8f6cdd14ce5cdc81d438f953bfa15a8326d6f5f903a9ef79d666ddfa1a38b1557efd7e924250d599a450ebeb7b9314aa9f58df25d67770075238df15ca5752656a75538c27ab81be5e3ff4c176cf77eca66eaadfb5833b54af56ef755d68821df7a1b973d87ec3c73edf87c5c23e2c96079c31e08b604871662b3afa22850f30d8ba975fd4bd148bea7bfd23db2d72d96ab0b1ddd7df81d489550d0e25d95116fa60e9f31d87d8fc00a5852cacb842c35ebab9964fbe9295d16e4bffef0322979ce6f5482e8d4777be6d59f3ac1c7b35b28698a24b1a234b7a58c346bc61b2a68d99124c29520596272d1e2693c89552ca1987bbc1063254a4c8a0851a2c2c34998207a90c0e862c5ba496f0ab2b5c58f71cde04eba362ce39e79c73ce2b5cb430320409a41470b0cd3477ce39e79c5fceda862e1560acc8a003939929407089818a2b4c7460340095a58817485071e5031a299d264652b850a384eba1c817567ee5ca085a4c388922aa34fd80431a2f8e988200586438024a0b16397421c50f3e504a29a594524aa1585862045a72534c0461a3515c4a29a594524a7718c87fecb9fe3d6c835d72210127a2f0d084e588ac2dce5c5121a74991264fcc60254a114c707028de804e1c0cab01a3d3c4b160198105154cbcc062c604ac2d9e3c59a214849b27babc98f8110f63de2031c40b356099f29c95282a5ff28d99c27577e22c2bd084952d597c81c4cb5499194baec0f04407295c38391182167bd381e86047cb36baeb36b7c99e76dda54c227b5a7727ca3a83da878f4c27c1cc2cce21b3bae7dfb1879ed37cd049a778c2bc3e68d72d95c99852ea31f3147fd0eb2fbd88adcf5eadf2877af2a7abacb2ca8e79dacbf3798a9465e8e38e3b578eb992622945963ffd1e333f03b5ccaeb1fce3ebdaf80a37bc44ffb8c1652019069779f8877f8a2f83feb43b0ce442dc7d0acbfc1ecdc65a5ecbcbf2644e29a7b5ded7f758a8eb2c9e353c6be49c9d0b3feae59fdf2985dd009e73ce89355a2cdb3fca50b2fd9ed378ced7654aeb0b92a964fa94a7052b2995799776b13801dff33bdf231a39ed8845e1fb88463d1fbe518c17ffc866413863c0051fe3828f312bb2e07bde82ef7919e18cbecf1fd93efc97f145ff329ebe0c19ef231acde81fd964bcd18c8a2714f93c06df00b621c5780c4423230c1e03d1473cb27dcfb3ade747598fc8b618e205e1d10c7370feebd705e1914d0cd916866cfb906d3242b659101ed9186cbd53fda8b7257ad50a9647f430a33f4979d0d4070b9de67f3aaf0e5d5716b2edeb20e779fe6370e771be43fb3ce128de1d91e773489c30271c7d884e8b85f07d7ff0bd8f219e7fe1c8253b8ff339e17874731e0724caa1f43de0707c9e709c97e777c2b1efced3b0861536d49138bf131e5d1d71004d03e76738d6b8389f43e684e0e384ae8785e38c1b7b1b8eaf1b13619f43c2c2fa603895427f721a94d3e4c34022973897785fbdf9e5ca3975a5c089ebc18e8ee39c8e30899daf249bf9a79b6f87732e71c7c578f92ee5423df4e53b94074d318718e29c923f522a9c54f207e7342a6a74772d58bee3c44d9c377138a93495a6d2954e257f80b03f451c8e891b1af2260a12e5e0c08d4dc0e4dcac6f119594586105218c501652104e80bb630dcb1ee9788c283cb4765ecbf5bd4058cce2f4effc8e38e67c8bb30f76bebfc551e777de493af9f3849e6f30e7efe88438a1b58285af304aabdfebafcd3e13c76791efabeeeeeef4ba4f2969f2a7264a29a54bd9b49aacd297a27bd3d04567f70ef61da197ced9ad3a67ab9bd22a25129a90e70c0116e28030420abcbe0cdc7e497fb69c68269a2128173e5f57cb342af2ec98465ff67c815becff4cc1f6bc4c26853eb0e67ac19e6d36146301b874000bb832a7c509dcaf7330609b81a890d9c47b4911dc623f5a69cdf39a44cb3a5969ad34031fecce652d50b840ebc1e6bc01c01bdb61411bc27a6aac99322e18f9e23f1c8687913101a83273c41d2d2dcb0cee8e5ffbe1e3037c9cc3ce7f2a9b0a4ead31235bccdcb6dd563a2a4f539a62f676bbfda84f4d5198a0c0708e48dd8003e788940d51a02cb5129813c58b121f43bdd44a9dd4482f9daa23a0a8d1498dd437201f8f14313bd478a48891d2b7ac36fc3e5c14d8d086871914d8d026046daeac466859e5c614793e63a8f8757850ed3c2a291cd5c74b121c6ef776766f3b291c5dcd832a0d8018294bf89005408c143143b5193e9300307c9962070402c0f0458a199271b47bdbc94f4eca62a488d9e1766f67f7b6eb6a53ef19a45e776f6d37e7f5f0a86b9da68f7db22e8da56d9b27853c2b73a197e7341b7f87b2bbbbbbbb650f0ab6866c9320d10efa9ff77ab9d0ab461bdb59f7b9b13c293d2576ac5df953865eada291101b156fd5c9f45c36bda1e9796fbd9a1372a6dbed96c456eb53857aa2893b565c95fa1feb541d13e5e54d1e99e18e526949ce8f7249e74719e5098a0cd51d254e4aedfc28a7787e9461aebfcc492ba43b4a2cd966821f6516053f4e24a52527aedc7132cd28b21f67138c1f2795eb3f71e39c7222eb8e936a86a9e0c75935fb719eb9fe136b66e59ac0b92345ba1ee329136d9272474aa95cff9142dd1fa9d4f5a754b4eaca1d698e9eb9fea61e79a2883b56a5bae4ba6749284d5f64957c73640a8f73ccb4a24bd40aa557d76d236d731dcc94ebd31d0c133ef5a58601c3d379bcc40d2b9705d21554a67021e608054d535c3db504973b36d51d3bccf5aeea31d7cf4061024a0c848dbe347a13099cd8b9a353b9ee6ee5ba4b5d7f1e9dcaddcb1d3de767ae3f7390bdff3240a2ef8f80673968e4d1555421c2e72ff831079f0b442136a71e3ec4fff7219d942871bbe27ff8168044e1874cc59521e3638044322cf81e90c882d78db17363fc8cf19e991be36d0eeac628c3c28df133902886ec08a5dbf31580443db323eeec618044b30a5e06125500c3092617c6530012c190c97e029048e67329c85d0a9e0724a26082097e07249ac0f29009c1e5791d908867c7b6f33920d18ecef398aaabf33820918e276fce5b9028872685867098c04102078704575c1c0c2e0ece5b2717a704555c9c31572ecec740229c21da0fe530c25a31d61ac0e2b8638d6b9f77b8b600d7dab74c9cb9f66555415cfb9fe4daa73581c4b55f23810dd73e0c24b243ae1b7b10248ac196007361ff02896042e07f2011f8aabae3ce7d8577ecb92fa0fb7abdfd725f4b44b92f3134dcd7bb40a2d77fef72595d97eb3d90c8555b60baadef40a2d6f5c254b99e19cf76168009e256f96092eebcb9dba25b90c30fd5a65ad65e4d8937b6bead696ee5b9b5d65a2bce6b47833bf630a18416258a48820a1f3276f4e6eb460d246a88a105092c544b77fed3a8c0dcf9b5249ceefc184834876aad3a2e14e941892a374954f02d7aa0c0823164a6ee38e34b0e253022052398b418f1e68826ee7c1848346bad74bbdd54a0c2050c324dbc6a2c819bcabaf3655ec4dcf94f4408ee7c5a8d0821eefca122a898829a536d9ca25089c2218b93275c8b2c3cd82f83a924b7be8bca13b0560008d0c5aa4b955bdf135d40a2c9480d527ed022eb4a198f893beef4383144d4ad5f65530529983441040b524800e5a6cb08902845820b9b5b7f7a7fc4a44d15140d48a84052c536b3e8d283102f0baeb0a34cc6369ccb9958106768b398d99c71a700ee9c5235dc2995c59d5c906cc982021d3c18920b5f0a3930890bae214a2fa6262da4a7ef07275e0a2e235ee850a86a458d515289e0bca8354be810ac6ecb902693470a750b0c2f48f6a6c7a30813e52d4d30f01882dba225061e448e6c19f38407529519a8cfc1a2659dd75a31e8d245959f2bb82ea6a46c8ccbb82e8ec840073bbb8ceb220d00907851e40d086a489aea1839cfb16f31096e7b9e4be9628fcbbcad3c07d5583efd503220acec89359a459250e188cd68e785b89b9bcd29f66ddb098500e0c8cde624e5b374cfe8c8858923900b7b0fc7791ba55190204284e0ae7f8e7b61a10907f8e28bac5166dd9ccff910bc98788209fdc585b12d278cbd5b715ae3a0947a1022013cc536e86d98d75f6e5aa8609edc51d4223814b19c7f82ddd35cd88fbe060ad49b9cd0b1c00e625c173dc74057566718c8a75873fb5d7a3206732c35b718cc83c2cd46b080d3ac744bddfe5c4bf9f270d655fe04d368878281fa5b0ffb58ce83d03f9c56c28f9d43a32302c9f27ba38f348d2bb6e1a9e11bb06ffeeee839b631bbfd7e866dc8b7dfef51f08dd8f7fb146c23e763b19b63a517f6d591b173476e57e574ce2879100bf510b7bebaa2e2b97ed3d2c9f11c15da9de1343f83c51dfdeaf663d52072793be8cb9de3a07339e618dbb9b66a346c038b6db4d886ebfb3b8b6d7cdfdf6fd8c6ebfbfde6485af08d9ed234fa463f358dcef9b1dddcfec6826fe4bcce8fdd05dfd0f96ef2254953b8b86cd886f7bf5ad7437965d867ae988172574daca3601a7cf93be92a754e071ba3ba7df908a5db37a96f3495297da3ab9a46f78d1ed3b2a6536e3ff912032931902f5172fb1d89c770fb1d06a6d1af139e00fb1c49a56f3813a6d1ef4e6e3f0e95db6fc39ea2a3601afdb0b0d5308d7e306c2ca75d856dc540cd40fdadb0730cd41f463dd891b360583916a8aec8dd907076ec1bece948d2153bf6111eaa27dc0eadd44b5cb0632bc16e4f550cc19406ca12942476ec250a8ce45098d24c1de5891d9bc93e15914295a62939d8b19b5e4c8ec3228527a6272a4eecd84fb72e5c18e9e2e607282b33d8b1a126c09acaa2089b348d6ba99c1c1555509921d253fd85891d7baa02aa21724cc034558769c18e4da581522e4d19aa9c9baa3141d8b1ab5e4f5db6c0ea92c552e7fa8c153b76ce454595a686a099424a6275f5821ddbeaaabb4db539c3e436e50688a936423456b779b26363d1dc1553b92bca7456bf01811d3b6b3e2d2d21c40dc9c98e7ecb80ea48921f89620a8c4b1451a2b8442186cb992ca52558ece84a4f309eb8c0d0c40506325c9450e1b2e4c51214253bfa12ceed892c24a6284aece84c2da521d24861d5e44e831d5d09b7c50d6bb29ea8f8d304538986a7efc6a40a282b382911d8d1713bb928235f0c29e253fe658a1d7d6a4c054a5262c24889b9024b4a892b29313938958711c28e4e0584a3f2059557f918afaa54b919d8e0925879cecf006147cf71172afa06046ee5576e055261ad5003c814b502ceb1bc0d0f7674ac3617e4864073c5d4c2a73484153c0c8126cab3fc4d0d76f4ac2532aa2e5a246175e982885c973650ba643d7143a2624779a34f4833507961c591a4263bca233838a9b42489ebb66407a5255176944a4762514837798b2262064b0b17555143965882e2831de5d21230b70549d29314a62d48ae6c51ca3145b9c18e92a975cb59110595155c344d81623f6ff965eaf64b2a0f9ae0f6cb2c59180f629c1a9c3c63e5a3c4926fa6740975fb5b090aa766c9f5ef97563c68e6a27c725a3fcf95f2692a197971b5aa8c81fc9bb3f5c29b5b94239ae4a465a85879b57a6d6a509222a5345de1aca865802f4a3cd95ae2914d4a59ef63205bc50ebe1448d0643372bd908ea266cae624ffc8f6b54257c82091d7d55951eb67452d57382b3ab2e590267487d41582ecb68701d9ed6f2155b7fb1a8edd74ab27a56ced2ef4414122da6275797786de6dc9cec12e1fb3003e9642d236a5b777f05506f244727a908b3d7ec82bc48fdc35f0f5f821af8b33297ee0cf01797bf077df78a2f365f28786423a2b21b52648a86c4eb3fea3dbe2cc028d1442e34d53968d7d838f4e1ab29a2b2c4b36dad5f7c49b90fad5c8a9fbfa4642e8cfefaa2864be73d9a0ff6397019070b619b2ed897ddedddd3d0f62a64dc76eda83d28e763de83b9deeeed5ddeb8feeeeb5dbdddd6b37adb46b57698fdad5aed2b047f557ed78ba5e7ab35dca1e3de99c41d26ff4ed1eece8dde9cd3b6ba55cc1c06030180c06832989599c9c17ceeb735e39e0386f4c84812f58ef4cd17df7bb9444b97f9dda703d832074627f77fb9c507ff495df0ce57fde4f7faff6140c21f43c1cf57eca64d68e5eec5d9c9e373de8aff733304862bbefd75b80840bb08dee2995219359ae79907d9ca73fe441f43f1bbe9c063e4e689d063e831f607071c4e934f073b8f5faee2c4fe81b0b4fe80b0bc7d605c597d3acd36efdc4f979a2e7c9efeebb47cb1934a5777b3f767777777beeb3d66e6f47a1e594b8fbf1abc3d677e151ec82633803c17bdcd6bbf75edf0aa86cdf762f1cbd1edefb4f30047f31508ebed19752cfeb72f878c4583b4b782fb95852e537db70792f5d9fc33de48077fd7d4e087ff4edff5e14a42cf4f2a752faccccf45be2d8ba14e7524f1c615cda458a2fb782cb5da4e0e17eee7d47df7bf672ec6875debf14caf90a03ff25be2803ebf7f55ba0582ba0a28e1d2dd11347d98591b3d369948ab30edcbfe6bece6377fed49f320dc77eefe70c9a72fa8d13fab2e7795ef764af2b90c1f673b7cbe512e56d47a1a707fb150463f9c71d7da36f6dbd8be3bc1ce8eb89b30fe4175fb03b56cb6d12174873ce39e79c5ab6d8c2ca953f63c8c7837a9aaefcd7e8197dec3352a52b9fbfbf7850470025b6fb640d17727d5f1084f92ed1fb1ed6a0025ce2d823a3ff8130c348f90a7388bd2bf6333441c8ed76f6d2fa1efc75fd9c52a34b9cb99e8a2f97e89910bbde832fa9c658c936b7a4a5284f56a6c2e4ae661645f22047727ac9a90a8220deefbdbe1fb712ff97962ddadcd77f200852ca693ba29c0af9b6402b85fdc04ff080afeb3d68bf2d90081499698cdffbbf9e411082dcefa7260882df4f94524ee340dfeebdb0ef1667ae7f072a20769981bc10e4944c8281644b34a1e594d32437a5b0f9359461c620774e5152814ad851e2a86efb3355f29c2633b3bff4319fd2e94d3e376faeebe75f5da0cb5adc58b99fbf725caed073c8913c612e51feb844ef1369e174212427ef918abceb8bd06c4eafa7dff77db1b0ef9c733a61c71ebedf2be4eb66ca35c0652d592c5ddc652f97b56425dd9d1e0f8dedfe73c2ca6f70becbe572b93e27ece77a97cbe5daf120970873c55c2e97cbe582c580b03d4ef3d6cb157af7f57a851cf0ae0b043f19c85fac000c247362b1e9e5844e385370fea3af1fb621f6e070c99eb0d8b740d87f1ee8c1843d831dc4fe85a3033bf060de97f862a059072fa7bdc41eb18379633171c86931256ceb7b7a767660b158ec5fb1900023b0b1afd669fd31716708471c3d02046169072fd08a218c7d77c2eef6dc0edc715ae3709a1c62200240d94f1c77ae164f974b70594b9698fbb95e5ec89f1376945d097ecb55410d33f687bc2e79bf594c3c0184f9c1f7531c5dff89de77f70b47ef8b8923901b73b99e9dc6379675edc73e87bb4ce8ae15c7be31317489a06520091359bccc409d43ce5bb6f1bd74fd50d390eff964ff999f681968fe50dfc0611af3a5ce7d39cd479f11e7ce2b5f3db7e70fdba8f7831f06ea8fb90e13669ecccccc93999927f374f38db5878164723e9db3328737586571e48e3b35c6da8ed3e7015f134c9897190887813c7e859681b2eed853c1072c6bc1c2ea71d846d8d7f320b681e463af49e1103dae1d43b519bad25fe08338682fb3d5602199ac7910ecfbb99b89ed86751048170693fd912de719023c3fc11751f032d1c8a973c471e882df6211cf4f70646b71ac5d1004c57e863a73c10745761a4ccce1b19c3f22b23018ec3de070fc11c679753e271cfbe63c2c0473421c9d30f63938e2009a46ecc170ac71639f43dae9b41a56d8873d188e5c55e3824d60e875e883abaeace5b1abbe17569f41b33b12c7bd2b7dc4e290695d66c22acc6526aca42e1017bccc84150cb73bf0b1cf38b9fb56add5937df7934da92696bdebce0f5609c3ef41f6c5b2d0c3f88ffc0e3f9c170e48543f4cad46cee34c1c1c1c9c9fa109325e5447fe13159ec1a23f99d00e25f54bec3895c239c36e9eb7d3dab73c9fc33bb43f799e41229ecfe155f729c0c1365cc8a9e48f95424426f87f22305e06052f3e910a3e94fd054f64f616e4b8209cd1bfe0637c18ce76c470b623ce18207ecf1789013a5970d04982c5883539580069795d03a1b33618207ce5e5ba216e799db367bd4e66ab4e0224104d53c63ccd97d5837c24b7bc4ec7649a2c6786b1afee641b55555521034d1a65ecdc9de241f25fb46f592daf6ba6dab7acac2c20f2a78d1dffceffff7faf8a40fbaffaaaafd2d18940fbaaaffaaa87c23e791015176a1f206118ef2f9e959595e5b4fe2e060f1f5fe0052b6ca6d8e4961cf12d2ee042ae25edeb738d08a49c4fbb70a4728a3d66276957b1c7a44dbb6743f1e4e30bb880c4e6089b26595417545703de352176fb93bc7c7c811a97a358f8d242d3e5a8a42fb788cb5149b7fb3eb42f36ad6efda82bb26e157ff8dd80ec8e406eb7dbfd21bbfd6065a6cdaf5efd49c51ed7ebc6fa7356b1077d797bd4dfd9a9228e2345a46d486773d2c0bcfd7ec3dd6eee21d9d1ab4924a8e4ecd9fde524e9a0ed124bf0dff153af43fee8e4ecd84c5f241353333593753925ede7fcdc71e24d5a86d61c34b5eb81a5843cffe9762967cff939efe998f9d19ef7d211823733f1d43d5826f7a1c5c4403558a6afa1953f4dd5a3c4ec266e1eda9f81b837171b05a621bf134b903fb2c5212530501776ec269ab73463a96fcc97efd1a858fbda6b799d7b1e9691b1ba894f5337e91b93a984eeaef94f0fe508c5a6d15f4397459cb9ea2a69951ba16fd06a7fb214ea3c76b968dcf2baae25d6e48f7ccf7a9d4ca4c91f1d9c1dbba99b9a264e0998cee96a9da41c1e4c24aefc190e154046bb4193d17288d8916941e3ebe6c0b96c2c175a2707cf6b87d2491e340a09b18d66a0a6c91c363de5ec9e3b4ca64fcf0e5fdd4973da1c3a0077a131d0147f6402b8d2935db1e3506d2866c3c7e3bcfde1153bf298dbcccece8cc3c809cb357a54b02c8e3db7ad1447a32b9f8f26f3d16c1ad51ca5c29b2815982e6d1ba54c9f7ed75adbf5dc6ac960f00f7d119cd632cc62e97fb114ecd838cfb356267b991403d13fe3340f8d8dd1c48eb599fc7ae79dc9e725a2d8e2f212516471c7ce758e568a8bb1a455633e076c2376e988c325ca1888fe055d58cf72e541b866c25fd84cf887829d856d34e8f4c2e152974b1c5b864b41d90a3d90a8d5b53a113665297d0ba0b0f5ebe5a823315c7939eac8d2a5f5e7bf6a388d3e1dc01256c7e5c0698dd5420363ce29ddddc5277777779fb25f3bd7ffebe934af041edb609e724e1c9df8441bca5db5acd166481bee58c39d597ef9d8a756ab31820f1c595f86a8aafacd15bed16a9886cc72c75a96cb625fc1a8902de6d0c6835cf4b1e2683ef619fdd603a8358dda320b856dbd477f874cfe8823b05feb86be7f3d0fd7776290ee5dff1283381575ef1267fe3571e6570c54bffa19db594eab5f3f0463a5486b2c97c8a28cdb1d964315bd5a9349211db52a73d4fc26abbecadf1c634ba0973e093e74882085be2394f21f61f9c187bdeb3f4f6cbd80c81fea33f931ab99c46ab562e24bd4d1a2af1cf2879e2063a06efdeb257d5848935d38434ffecc8ac07f3d28b2fc697f7deb5f624b647feec0ed102b4b9039a4d0f7f475009142afa7fc632dacc91ffa3487d653f6a4508d7f78aa255af9c37e3b246ef669320b8ad8d90e24efbbc7b97d5da3cc2fff8de5cbcabef043990cf6fbd7fbcf9d1d2cdfef5fe26cc7f7b306b0ed13dbf6fa198fb6bdbe6d2ff1e58f8c81eac7b6589907dfb7dee8b50309c9f52fa4d67fe26cfec7b8d93462f9e3fad6cf6fb5fe138d5aa251ad32185aacf572d487affe67a590b4d51ffd368d2a59c643cfb2ca0069eb442b9fb0f4bfdb1bcd910d699e68b24203153a8c913514b1446e49c9932f5524c9a4a8335777e7725411365a2e3ffd915d0be36e0ffee9ce74ce661c53dc01986760fe5cccdc62af19a873af9479824c93eedea07706c6175632b851410635b43332e490666882f1c577b2d25aa3ae9248b232bb237380124c0188ba228204575277b4566677c4cb512c48b16f03922dc858d855065b2c5826032d1654a2089b7339aa0a2e071184a91285829cac65b437510c9171396a851ab620411249282811c3840b2637de9021b5d65a398082439aabdb11596b240d4542c026290d45e342eb7294162c53522debbcd25a690650589ccb5157b8d09454f941b2f672d4151c9eb8f2441645a4086965aa0c915a6bad33cc550d449e5a628604c1c290145ddc91712c1525451452e48cd4bce63f4e4fac60c4c2de6470439b0c6ef6438341d513187861e9e5a8dc981f2ad6bb1c950bf3349b0c5551556e789f28235f5c7a390a8ad51dbfd62447dfa33e6ad77a33dcf16b90e18e4338b424943b32d55208eec863ae3c73c44b142fa4a2aa9031442d3de9d45c5145d2940a498419e248ca1b23382c490551440b36f93aa4900796a678818d1a1e8c28d964eeda1ddae5282757dc9ccb514e7277b44f83068c90220d0f52acf04413f904eadbc242adb5562b48da90a0852e7e6823612002ea85c90313921e28a53074435c86247838628635e4b9bc4f9496a66b2f476959bae3d73a2f7ecc60f065c99f389a21c907334850b88105304b6868152d545beb981699289076d6b82e471589e2c2b81c55e48b10b59ad76ad349e9a44961e5e5a8225eee68a558bf1c55a4cb944e8660353de09c308249912f2cc8265145b87407966680c10b93149535b65a8d2a92e58e5fab8146abd59418695fab81a10245052a0c946432c40a0a2570814c0a6b9a904b34704314370481c20878b805e15794c8d8e18b2263b07893c3937f2899e48eb21741d21434ec60868724ff503cd29ccd1a2b68b24082839632b51a184fa3064a22e112e4b0830b6c567022428bff20388cfb5b28773327542a31c0a188353621ae289bd3f48cdc0808bcb06213a2010b36a749a9bf6ad83c30b9a264933571c6a37e97664c931b6c426057b6fa9d68d44e048182954d488e0bb62ad27c83fe23db375f7456548d3c230aacac10c2260414c25637e8d0680055aa18926513e2512bc860abf906fd95f61824b26461a37da553d4154bf787bc19cc78d059d39f4f43ebdede3e1b0528eedeee2e9f711fdb867090f083032a574a29a594b286d736f76e8b3ed3e50d1ffbcc781835154fb6fa4e10e89ebe37c666f48db159f913847d79b98b104977ac5d2995dc18f4c18eb22b7f2e40bb1c756b73a36e672e47ddc2005d8ebae1703f64a11ad45208bac45045893636f90d82d04d10c672d44d863bdfdd8bf8e4532674b87df2d541635df1847e138e6ef747dfe64aafb790759b1d69b1eb1f631b5dc17510013c639c2fbf7f8ade112ebfdab5d2ee64d470afd49bb8e8b47d8bcfbf63a07ec97dfb7b6ebf8e22179281a5c50b2b35866ef317e93c76fdafdee9d806ffcdc969934f5934b998b2d8c273045600545d5c58b20605ef65e50e76b4ddc213160d48724aacc87a7ab09e10b164c8fb2cb1e3d36684c08eb44ea6596a951953ab21c58eb5dbbca209d7550b433898ec3834a9a8b49082d3224b0e0e60b023e3a67094bc48c17991e28497262b2f529ae84840163bf2540c8b6a4892253b8a6cb0235329cd2b8e7c57ec204204b6d891ab3e2c2cb0a4a061040b70ce958b62418a112324f880b2235b794ab92ad250596151c28f2a7664ac1615152b50210390a114a884ccf695412c95b2810000001000a315002020100a87842291480ee4d1b87c14800b759852785295caa34912e428884106194000218400008c31c42034662400841b56c86f71fa51417e37e4a71efd4006fa43a135d3dd9925766406335d98e1f3e42b414e52679ac03f9b8fadb2dee55c91f30e1778182846c5412f0ffa798402a4f097292e3f54c25f9bf50c5a21bfbc262d9f838ccf13512bf4298100996c1df0280bc341994c870ab3476634a005339c3ac35044545f0142aaafcb7d202390cf0e4619ad6342aee4896ff35f326500502460c97e021613232e30b7204cbe25e8d5798ee4187c6478ed99443a591507d17ee3f4044db28af9c22619afd3a4239f17ec93c277f28adcd5b12617c01685ea8c298b4fa3d65432d88bc90533c25157b51de23d6e2949123f0c450d880224a9cbfed72488d92710e8b226ed4321e6827097a4e04bc62e2e0a1d64c3da301fbb474a3afd9bc9cdbfe5fe9b20f199d9f0a907e4e9c9b7781d3914c58569fa7036dea1014008d1ca956e1b0e3c39d5e9635d5bedb8c92cc67d04a3ffd72f4696a91e01014c7d70bb6622a11365cd44828e603513073a02a899d4b7a23e85e5a2bda008ca259ce31112c100d47009363ef61b191bc7c2f60784631d254fd276a92219cdcb8de3125580c78bf433350ff14ba7523a0f1c12370fb631cd1d121e115a9be519f7dd5c922f02fa60dc1de55cb7e4790180facc5afa2332de183dce8bee354b21b45c62d69236ef2117f24b4405fee26f65e12f22c25bca9ece60c8e7d7dee37f2c11ec111aec2cd05792923b459d8e705ea15603cdf780e8559bc0a233c572a11c44f6df3d4f933733e9aca49e091339e428d9152f693fdc3274887c1ed38afac03b8162d70c32bcd8c4cd123276b829764c4c55610929aa9823c6b90c8ce21238edeb5e85faea737c4667331cfe8021ce1b669c6a789678861770b2a3962896746e7d7c7e501f18f50377e6323d27fadba284e56d9c46440ddd99b80e0fb7f5790c79193e6991c8db54f059fb01842936962dba14f3a73b0b4f62b1ec615d9d9c4d6d16f8b58f5f86305e517f4a1e0f6f61257baaffdd9b5ce6c01a6f85ea0dc58fa0ea13706ffa74c43727a1352e52f47229761169b983a3e23a562d7721af8002cbb03f866f0426f1e1801f62f99bb2933ba87670e11faf1e3b5903e40da79c3c87b1f4f819f9e01842de0b853f88576255d8a9ed0af895fd66348f2a07b3a5088497b9551c71de3680ae0322c1aa22931084f63b6492df2705fa031976583f01e568ece8af6c4d79ce5f5c59859e18135340d925f33139bc3a3731fb57177ebc4469508db24e289f37635f6008889323ab619f542c1f4b14be1d67b08fba2b576748c32e764558742cc1c40d583fd07b256ce20f28c3484c020155d8c8e2adb1110e8c9e3a4df7c2b33e58a9a7e91b64ea71afa9de5c7a1477e44288d3969f0dc465e5303d132924e1a10225790e101df415554b9efb7343201ce30b3b7ce9b45930a0b5ea74e296274da48b24628f4c94f004e06a5c089d88ff510c7d32d020ce5749f9dd54b6144708df323539aca15de33859670536ada3ee520a7b26f779c21fd5ae9b08c9298dc99eaebf440c4ff7e789ae30a8d221a2818c72bc07627527953e2e6e5037b9e2df569de38a95a5297d9c4db126b4c17b7054d2185549476bc3a402612353abe2e2d53945376a5cbd34cea40eb305f028f655648a4db4fefda2b4b67bf9d85739d13963ec899175688d8a2d0c33cc15831e10040f02fd3d316878e79a528b78a0c6435da6c25ce340d11c9fbc71456e2794791c94c214d9052118ed20add31cade11088cf3a407de4bbe8102d7d28132a9bec53c4881bd99f47398332c0cd517ecf456fcc1179316f452c7e6c3249c4a0165eba2ba422eae611a5465b3e8a3faf6b35cc5c1a216b649e4bdb8afd3077940ac2635f7eb166dc10edb075d5e1735021fdffd40b51fd28ac28cd742b756b4db53133ec632cb56f5942347191cd7e6302f14fcaa6645482478598055d683c69c1a592c3f6ca1666aeba3d043e40afd031d11c79084d629c932679ad7f8e465826f199954919508baa410c204cca2a60446307875713040b242b6a223352e6452fc5b9374f90ddc0684fc98add717c36c9fa0111575e43c2514e535fab39d6392017714691276deab6621e547308212a62ac848a8d78dae616383eeb0f4326fc1ef4b460d6060eea79b96318c8ea79513259e56c95160e62cec5ac56e93331a81a1639eb4b8a6a687f4c1db40cd5850b11a45db9b946fbfd284085c10500c37e26354723ce43e00d335e227acacae44a6b3539f363cd35fcc772d8726ba3f44ba304f0c2f9722b894eecc9c2da2496672c8770a2ca56c63764ca5738923b8b37d5c42a0251e6b66d6c7d9abe487c1e7231dabedab3215adff85e1fc7f6513aed6eb14bcb70c19aaf24ad82d413e364b531ea42f54cc7858d969f07116be9bb2e0300c32915da84f61a6b1298c5472abd8e95228a1c660a62563268545b54ecd57ae16fc10aeb2c9eb4855e941ec079276aeb134b1c76d03d8f5245b8379f22e82b500939b4bc17639f547948145660c96af5ce8a391dc34fcd46d3fa2b3ea23c6745f799f3c4d42e8952de67a925d8f1189692d24d0f8049f239026bda8b68ce7d62a5c0ccf09ca400f349a7631529ee4e26e7332fc4ca9f623c486b32b20edd172fccb7354c6d032b3b9ecf5a5cb960c5cfc7c12ec65bbbed92983280356d989cbb4ebecd6569020aea7e20352b3bb2fb69aee8a6a51df0d067ce3790361571e73820820437ea77f28791374a8904907a70ad84b30595491009b21887301019d8cde5688095ca3b56317d39894ddf196e1128c5629707ee3949ccd528ef7c4e7a0754c231dbac62e0577390cbd9311f60ec3cb8e96c3d4f60a1b4019771bdde4bd4a7e4b780071829878b16304288c69aebc0d34444f6ad1a07dc9f2a3083f214f54b53511246c4134be6b180219d9fda0be16a1c4d1ca6671df8f1d72fc22fa607704f52c9c810cbce163c7e8bc8843a770b96c958ae5387cb6df7f36b210fbb3459225f06b83e6c7bc8756f9403ba56ccc47c59043d04d3b750a516e60f56928e3860504956004554f6b7a0affb84a0b536980414e880133edd9fbea08010fd5c0918a1ead00e6810c51030e0d6e17c14d3ef862857e0dd2c503b7db37c234497773a5483ec6d7d9fb7beb43b7cf25b7ac86ef370a2c42ce03b60848e6f18930b6f9fe19961a6450ac39d9a55de5cb6943fe226ca981a68d77a989aa17ab483c4dd957bd2ca80f6e5f9f61c0f5a5f5967b02d7f6161d0e58a7a2176bc64f8bbb0ff17797b7a1796d40b092b78d907b9970055e4537e2a6e3713772e07a58a7a326cd600c2add81aba8dc9f1466474ee7e126fe0e021300c97162ca537083d4c88bae6313e61c46bd7ea07e456504707cd1165880b636213703c068645cf205d855307ced1c81043a691c2351e6e571d93375ecebf127be3a3cb2c2094cb8a66d47dfdf26071603385f8661f65d099ddd7c22648f6c5e861de69184ae19ca63ee7a1d68607f56aceb9e369b12e7b1ed37261d9dc1361910e633806d77a8ea7a067d6cc101f39a2503ef46247f505726e242c3d2103140f401986032a030bfdb7ce5a6b1591dbf6381ea31e88dc47b03634fb1be460c0d9d7d91d7965e67c9e70a5c833de5d52cf12f64e94ce265efcac479f97809ff9ba13cdb4d7094635a1a63fa04eba1b1b1d3b71b3f2c2b2716cb92b0aa7922872de90d4be1bcf06ea21cda6653e7f2df5f3aa0b33ebead26ab7618e26cea3040d8b83406460f79390839cf83dcb63d26280333194036c26b5716aa10542667907dbfe5d3f1197b4aa0c4e85596d297f8527defa46280de22946203a49a2b48b363850ff260061072a49e42a7aec7b10d5b4e672716cfee4ad125bfc9741eaeffb3380c34cf02faf2be702901256a5ed80e4f684b87ffd87d962c46bcf7bc11b667d728da01e4f5f17adafc0e7e61baf43e3636b695266a2433559bcf5d7698fc913c59b518aeb4b6c66e8e8c37d30ef931212733329cdbfea178dd2804411f2b6591fffd5144a787f4b3cdb8cfd083257d81bdc4edac8fe5c2844f8228b6cc45da57d72b8fb0f62f30e42bda5dd50fa2e834090f509caf7e35bc5a4d43ba829916776256218d8d43d1094fc580796356c45ef880bdd476bbe9e933150892ef8a81b897fa46de126617d52c23c9d9d0aad823cee0e4f2bbbc0758b2ab0ce0d4d343a89d4779083544521a3b80480319c6f7d9587e08064381416a74e492478bcb0830a90598b90a2e3c115ded0db6fb04a438da79ca254c54bd77558ce2612800a7db2cfe7917076b8df4cfa1cf1faf20a668dde5d128fdca12374ec36fcbb1f77f7f1c9ee2bf12a744b6b53137edcaa9b776ea94f34c1e4cbcdf2bf02092e0a691e2883f16c852b119eb012640e7cb697426b133703df66e6615fc8753cecf57e28ac8ae88a6bf800b5da25343b89b6f868393bf0c79ce2719fb825b9568569be30bc3d5806aaab6a19f2d056857d275d563211d0c11d40d06f65d7a260cfcbafea4a6bcfd422c60ec284abb50a98465537bb4a7c83322aa591b6429b3a6698913153b799a0bf09cbefc7714b60b7ac6d3e20f984bec3620f53ae44153540daa67034fc85b09aff1f29cc015478da5559651122ba8501a67ea0ec02db71dd865bb49b381374fb84a23e44644badb7f17a6c4f606f6befe6b6f89a0510a10677bbb4d7e9e165010e3646b67beb85eb102a628ba121075705e536eaa2d6fe55bf0fbcc4250cf007ea711cfe59e5b6720850484d2890d591e5d12de986e75afceb10620b96f6ac0e27c8ca34fe4ca17b1a0852b392455912f45817f39cf02a968331031c823ed8e022c0d06789c61eda6832e9c192ce590b5ddc885d74fb570828880086f5ba29124dc73c9e1f18f8cb83a1eecc8d51ffbb0bbfbf4e671ea104004c4a97b1aaa346a87e02fe9373371ee006d5a4d4012c166c0e3f40a72c77a96bdde65caa49d3f084aed6f65c532abf408ae08cf3b513491a7e09140d7355c89410f16444cd855626314a3bdd0af929772252f17672207cc8239ddc75418d3aeb0e598d4263cd3eb0c4122b866b042d4fdd6cd32b200b0ab1e8fe08f5ca4dbc302807c2cdd494b91dea769564b1aa9d4d83529d1d786f471db47f9dbf812c3843e433611dcd4b07391db4d6e4db7ecff3cfc0c1ade2f451be34cf7d00d86a482614d1b015dc99437ab043756346f361466ffe977d8c6a4bedf4a37414cf7f8814a98a06a4d11ef139081432eb85e856f033ec62addfa2f91c683f52f6bc17108662b6cf4a899fcbc4e840b858fe3127d9912e4ba84352f88c8d34b4338473dc94c206deef759957c90157d712a0e534a485a8ea755d2acd8ff22b5ec754c21487e5d96551e275b10e20338bb0fc243cdf6511a04d66dbcd9a0c59b1657c9a924c78be832b1242a65c02f041293079ab2c598aeb600211b909fbe694ceb1a91faac3f8037dc370d8af630e4637d8644b517309f9074aba1c8edbbfb857cc617e972f10c672ecac8da1ccbc7c680d4d8e38dc6f83865446736e90b4b6540e1145a7e7d4f201833df2c22317721320a43f922a629b197460b21bac26470e3012cdbc5d384c5bef5209805648a41a4c5126e3c7129dbd1d48718bb316f71012f504fe1687cbffa8358f0667e528e578a0463ab8839ba62bfd36e8216d8d9fdf301723527c20f87cfa612652e6f657fc92d57f98359e733d481e8f1cb9ad14cdf7f725920572c678782d9271c8733aa1629a9c9dea4012e5b0e8117f1bfd81c74324a28d92661c9376196bb0ee04e3883889fd99a18a4131d189d9a0ca03e73d0d6c167d53c7f58db13aca35a323c572473bd03de31c1d69aa4337b8a794cf5ea962fd49c035137dc600bdeb23f5fbb080f30e7bdb3c5de8a24954aa534d0ea27fc20270eede74fc4d270781c39119a859af32ccd19c9ef0875032448bc93808db1c4304694485f53b4dc507b52879a027adf3476269a8434272bd12220505682fbe978702073925255462b3a5cb14d90742cc29592266dcbbf28dd6c3571358bc6428fa28993e333189c60b9821cc89f813ca58b0c6a3da74fcf0387f888f1943a1e3306ee4854577b25500dd28a84ac5fd1026f76933435ece7f38bccb0ef16c744284abf84f3a24b9f03d81a3c09449f6e2e7f684ae73749e08bf4992cfc9d7e4e95f3045ee15746c3aae5ff62b10cd9fb705b9eabaad56ebf5e1d2a4f975dc27965097c833016d59eda716530ad9e29e58e2f2ba171399cc3303e5ae3907584bf5346e6b119f0b0a9fb2f5a617bdefade95f4a36e2861a4ccc047b3a3f655b4ff9f65ed139f7c1308f32a22c6f5b56c0d38054d024fb26ad4b7c6220ffd7912ccfab37b59590f1a3490c53afa5870b8463d7e6251a9448d575e4327039a2416bb65d25cfd6ee136d595e6bf28e96b60a58590a805f166d4d0c8ea09eb431a81be04dcb6a5475dea07f0593afbc7dd138a9ff99aac9778bf440d3e8473fe34a663d0c018faeb09bdd5c1d283ac464cefbebc942b49ee6f4bccf3c2e59248cd7bd3c5c0d93c9b68e34c70403a73e2d839b36ad4b424adcd7eca3a4b34b2e4aa294c847bf977559c58eee92299ace0fabd21d7439c6659d7c74ddc78e9d0710354e45f45f6fe396a247be17fb7067eeb344b000787dca4e003d133fd045d75c97e176320d3264fa91324411d3d6080cb99eb2332a26f204cee4d7ce2d8d399618481dca8d8bb577a183f1ecf971c9b94ee228dce1384ee7c76a9715861055cb09a933ab602b2920d41091eb5c14ff15809430c901aaffcd70ecc051e87fa6d62f9a97e48e4a60347f96d0bce2e8dec8ec3ecafd291b9a2dfdcb1219650d1bcd0b4685a4b6302c3351a5f030a799172a6464d3032889fbe1777d41f42a5959442551cff5242c8247a03750457c416f0662b6041352d1435855348f81566769593b1c8eca46e86ae44ffa4eb90a2af3789d3ab3c893b54778742271342e390c1c686d59a22586fb3ac42439011697dfa5127a83f1412d36e3dcc7697087d07b6fa472b9abfa79535fde6adc820c5f0e65d1cdaec158fcb0fceee5641ba6ea5623b95410ae1367bd55a265c78805db58af2fc062916dff637a9ccd0afce96c349867ae41c7a1686d68f056219aacc81a8c291930f9f4602a9e861aa0af51710e05c594434d56445f23cdee64b8c5d43eafe2e6904e97a8fe59b36c8ad1e7e8574ba29ee1478a180d0bbffe8e5db0eac4b47f9f78d91285fa3414aa7293409848037188c0750cd2a349948b7c32795d0e48b4566f7255249f48d56fb2d1785ec3e4934a3365cb35af90b02ec768685ba199072041c4b3d6fc7f099495ae99da95255adabc5fc6b3ea26ee056a445950003afdc03891bd0964ae84e6f6361b98999515378da4037c2b2dba5d5f91da039afa378fc7a382be0079792ae9fcbb5e2920e4514c155eb025bd08bd142b5bc67fc3772fa6feb355d02c50f969b281e43804804b5b01703c9fc386888d4f3bc808c61412de13a3b70e66518e56ec8de304b2901d789effa31cdc8d12331663a1a9c61f62c0584c99f5ddd782158e3df32f5509d191e06bf97defdf704688d80fb48b68b42b0e009005d8361b758ad1fbcbf655c4b2a965449a359738c91ea7f13a528a83880022c893f51020941815c8f6996c36fdf537c9ae3e11272e9334a57a911f8a563c47089a4f33c6b05bc95c002db0e040089ba31503222b6b54525837bdc6966df58548e790a38941b7b28df87bc6c7c9c66860a15913999769cc125ef58f83203833625062ec5675aafb700b489366786c14d621ca9edb5297f590ca43f71a2c93211cd7e7338dc1dd8ebb1015c5d12424470c46a0dd7a8f2862171319cb482092da1a9866e56b5f20afc1cdfa0247d783368164409d05f95807e1f7fdc143ead9269e916c125dac91d2cc0b267fcb641e4c08a28067b4508b77d106fd4849642696f820982df7344ed9910a2004ccd8704c2cbf10917ad74c242e5808f9e39122299a3ff4dc66f3ef957666564a3225cbec265086bd05351c3ecbff9c41219ab8cf3ef1ac58501442b083c09a8d43b26b8d1aa886ccb046d04e152f471b77432816139a18ff6f20edbde91351ce7c4f4be25a6109812ca62dd66eb558ad28fd6decc03263fc7ce143f60af7575d2048213b5011b78ca3e2c78c64dd6e38291b46b34ae6abc1f188057d17296bbbecd5b08c0395120211f3e9e2d588d34975f8c6cdaa3929ef58fd1f393b085bfb0deb11b433c449961c48195ff4011a493492fba7d4e54e94a3c38d1521280116ba5ff19b627d858898c403b4dcb0d1c80e39fb9acdb1b836d6a7605e93281cb1f64746e6913a8910e27abc301b4ddf69a77038cacf1f36a4ca51706aadebe9e0313585adf901482c0de6b14154724ff40308a4076d024ade8b088441d7fe1d17b15bc8bed23921b6101b05f9436b382ebea409e6be863b340cfb103608a87da97775e8cd056c688353112942790ed56ab9da46322977ba0c426af2046bf4025cfa04ad8809dd04d9321f7087b135cadbaf5126dc5c1182421496f8b50d230f5d9a103f0fa1a2d42b4407f876a92b416be125ac60f0c9c10ead93145134cd1cd8b968768bd42af51da46073c0a19dbb405cc30976c1f3874324bbd425e846303b8c8c01542d6863233a122c1144910a8b2396ad987306a4a2dc8273a40d741b60625150922d0167738bac7e4f6b6ca52ab1cd0ada5e55d5d5752d9019f707206290d49b31c3018af200419258098cd159141c34d1fd803ad74a382a4fd32c57d11a932a0c6d9561fe176513e0cdbb61cee338fb54769c20084255b3ded5093fe0783796b18b85d5d93e650f5e39c53e317bc0e881a75c542a36653e517bc0525f5370c68487183b6e3c4f7e9e9ef258a0d67e09474c70c475e761cbd19bc62cb9714c79e9cc13e1d884e4c226f133e181068456373ae0cdae6b2d141dc72b477b0d864a57f5b5d3f32b6bec7e8d262d8a694a030ea07071418da035f40d4bed45df3c18aaefb09817a51e667e81e97d077849033beb4ecf4be80b504de9baba66bca16141bc89bdeab8bd66af424b266e6fb79d09ce85775615d43a99ddef8ad2f6813a7b0f8603ab8f6e6ff9b3064fef8b1843c8915d4616921e58c93593760469b8dad6017e6b289acf31b8b2ca59570ae8bbb622a1538cf7bcd7e41bf59101d56634d39064217d21d63ec8cce3a1b746ef54fcd124607fb34893792122e5cde06d875e2a7330c20383d2af3b07dc37a3389d1ec6197ad2e0586a46d700cd656a339bdc00792a796e3a07131a01a9f47d872938a326074abd889f81f8fad0f2634c1b8c6eac8af67ff9fc9ad0c1a862748e04fad1c8454e6cee6fe83804a0f16f68a6b4737aed674b5caf706e8ac8ef421e6e7f434e799bdfe8426c359764492900d6a7f1742e3a32a0277731c82430cb7bf2c4fde836c50632ee7cffdec82c03e336a1042d3307239ced5b886bc13a23e0a7bfef22c1721d97214e8c50fceb2b4189a583610eda9ad21a0a60866a165a799184b520baace846a7e79a6ac1dc74cbc53c67f30ebfb622a9586a3714e9592da159a5debe8ef376708fd5dec1b17e83d5ebbdb41814935a69ac4c5194d13927bf7fc501a5f5fbd929251f9ec33800c5632a1236f844a75b49106adad7254e06e48a1b13ae4cc0f09463d6ff283e68cd7a01c52720e0c22c9140447295798afb48c93c242df820ff06d70928fc6959be3a16cac9854b572de7f7006af27ec864b576f3b4222d91ed2aab11e66ae912c1040e0a1cf1cdefec6eaa263bbb85ae81c724145f093520014597200a9710d9127f0b3e6601928453910e17a85f1700038486bd8c132556b894455130a96cab26441c0bc9510a2977e87ff002991331f5aa8d4700632fb2f11ddaef0203ad8c14e2cc2cd7dc0163e03582a78a78074d103cc4790f0325056dc2f044b37a80ea8397ebb9581c5d22123415506f9a081e9424dd3413000460cb144a8f093f619971faf6d98a3bded5c8150e7413166232ed59b2a4f099696c62f47a84c39930d26ba981b41aa55d0469519d6a4acb8269aa5471e45c94652ef611a88460bb31c7bb2596fcf65929e2ca36297de3ff4e6027e4d503135649d8fdd91669d324ed39a1061a3439a53a5a60a326dfeb45eec36f832018b5b444f0bf1524b45cdbcf6133bd96a4dafa1c9efca189f8e1260cb2246d8f02c4842406161ad13ed4a55a87b294eb4789fd175437eed5b028727ffe7439b1c3b4962407c30ae26991404edc855c4a01c5b54010584e81680ce4808f1ba314b00ccfba5f19c02ca4a4e14357bfc41f98a3377238f98de8844d1c3ca022591707774f4352b2a1c5d3e856ac9f5660bcb587e8c3580d6c76fe9c396e06ac5e8040ccaa46037549f8d900d9c29c40f96a1863c86aeb7c375f5d3c484fb534902702304fef264025ab229fe65e509871800b224b20ccea288fe7a38787a3b7442f300101b79ce690583384cadcb2546d468d34318fb3280a853cb6d60bd4f33d2143dfd03c062772ca6c9ba4317a3b61dc658da3e35c91a2fc6e2b1552579c52359a109e27667e7294cffddd04a0937808253b7f84dd977f9a1e00b88a219ea7dc50d329fdf0d9d0ca0c23882d1aad09353bb1ff4a53f3250949c67d1da8d420556b4d399de51a0c1dc77b64dfd5c669e6d84efc158fec2313fe629cacd1733c4a5f8c71a77218f2a56950376c64d7302c4b56c31fd3d44959d6afad7bdd39dbdb4d514855c480f23d106dd50844209b0a546723a4c4c82b2f1d71dc3460338f8d59c139c842d5b22de4709c1317182084200a338990fd59f7ce54cd90991d2128454a0823be45aa93e14446f90d808f06ce74c76f3ed2ed03b0dbf5cb80fce8936eee86dd636dba53a6c66c51b567c1262da575c4e3f6bae55ed01830892898fa9578f4596ef081fc05ad20c6c58fdd0dcf9f316b42eff73de75ae1737f10f5f9aa6898b7c8838197a4cffce9ddb44465c92bb6e77a1f79a172ded3a893f17beec8431da52cf0ecc0f78ae3d6ecd3da29eedf8769669d3e9bc7e6e0e59a160110e4e82af2894800721638c65f60fbe3e80b6d89fc99b52cbfb50292f3c7f513bb221f42234d64197a56260bad1f06df318236cea18bb5700149cb01473e335d7cd6ec47d4ae8c902077106287d67db85809837bb821064c9ae0ff9552405a68929e054cb5af4d845b6d342184b55aa25e7efd55e379ceaa9f8672817762842a1958162306ff629d0bd6d6c7e991d61c00343e2b400244c5f483a163a9a76ef38b8e8750e0b07d4be5716ec971e0eb409db861747134338c8989bd4325f3d7b5c999cf7ff595f841959225f04d5d170522941e4242a972e12f5abc280ccb6738669007b59871ce7597f9596b003b41c233b50f35c950793c3a625898fdada168fd0d18c81db19f51313161545845d8db20b339eb7acc5cfe4119b746d671b1e80c3fcb29a0c86b22088fa77ac035a07eec5bbbd364c0d7c4258c3591cc102844fa863353590206604838b78558ecb075a4cb6738bd2e4c203ef9a6990b8077c627f472d0bee8c2140ef111468d8afa022d9d76117ca87b7f562bd0892f16b574a924ddbee141166977844943c09b3e65004a5bf0c34ccc6c60b70d774f6758b860de80cd08d5dae3d0951c5bda3eb9f3ead78e19b26e2b16cd280641c4a320c051ce540433997b337f5f2110be182a954eee183ef8a93ac4d9f250531e5a921c79e78bbde82d4995da489170a73f9e4db0df0c203655f0e52f41305e35365908131867fabcbe1a54d0198c1d001d11af417a6e0a7e75a7fceb8645563e33fe0ea2aaa4c4d84c7b03c7f05ec37e2bb1e011bca3f17df3076dc4ad2d2d242c42ca857cdaed821a0a95438cb5018b43e305a8d188b890adb3504180c7536a90daa81ec34130865db10c82f9501b67a8f50a0c02fa63b8a8ada4967fcd9d7096f7dce32d854e0e25339b217382d835c555f6d0d840f72d0b1a8f8078f55d7afa537ebccdc6a75981a74f60f7f11b600408a7e417ed66b889f940fdd779c7bd194af212853687378f56a0b70fc89b47aebc32dfaff70e392f74382fd14a06f4e43a74479bd371bbf86a09ecbb77bd6acd63e2673d392f61330dedc85c4531316016c08a1c3ebe72060b7a0df1615da352da4b849123a5f0390e3536b2b020899d99ad0c2ef0d9f963e0d203764cdd806f92e8485ad4537bac7756ed400946897d756a569b00687daf7f519e73b75161f0cddcf79cd4ab672e45a6d8e97637a3b2ed1e520a983782752ec266d22ab3b75479ef5a9ef74f5c2ffefa4523dea50a3ff507dadbdf1bcede271233116c70a9493fb8943b903b84b2b340b63109b327c12914cf9f200eb131b0e0df5f7982482c37bf784881c7d8c294221c8b989cf0dfbd0367f07202207ce08d0590e9507db901a2def4478df167fc7801bebeb7633ccbbe3aea84377b533d6d00c90cf9ffa7e20d8809bcdad167cf47ad4ea2790177825eeea966c020f83dccf087dcc27cd05c1bfe448419ca557c94d7513dd0228139b7ea58561c1ba303064f3b5b90580feab4ac661677cb373f9d52d8044c40e120b5b4459c9f795aac1ed50be1c1d18a72703a42ab317bf108bd2c701a955618b240e2fc14c22221085061c28cbcc83c491780115367c83057491b516cb15c1fefb132bdf617eb942fea8cc97526fc2ba21cd3266d82412876b9a0f83578e7faa3e631c16f8749329eaa03d7cd9718b44d2c8429539cfa8eed9ef2d634692c048645e5f2c641539c83563e8514f758bfd2850d7bfe15024421875917ae698cdc1108304ecd83539766e0b22e820edd45121b336072e67acebcb2e1a9beadb84f06248e4f72b0a00288ba59f67ccca91cd94b56cc6c1e8ea77b385bf2c7ce0aaf026c12fd2d7428a5e482a0fdb2362ec17be85fe971759ffd054919a45cdffa348ab0e5e812030159cc03b19d78b2d57be5c076a2d09f54a91f91a49ba38deefc794483c836ea63ed4252b1312f2617c9948a867d6d7c9f95ab61fd6af15f95d66c265bf9a98fbd75c3e73aa30e0af0f8b1e035b13a9d0bb613e8de0556ae00be92606bc95a90ab20462a7cd6b5b9b5fb252926704257f87f4e3887eccb60620efbe4d17cb84c9424c342f989162ecf423588cf36aad32cc19b0b0eb1b1ace3a87092bd070b0464216d203aab6ddef2b2758374d2f10094b4152c7ba5da4995e89bd4765a7adbaa87d5762846d79d52e6621c10e203ba175e7edf5ede13e761c2cfaa5cc17e5cb64618f69853c091ef9fbbabd8b57c9e2d72a14e23d3ebbb67010d8f472c9805423df1e68dcd526e21891d5b0f235e7a59aab86e75f882fcfd28fe8e2aebdd6d2b19d8b9b19a2807349686eb1606dea25cf97b2e587d203232137924fca1db45dd2be7de55b6a760dbb01e5b2661b3381994eb61a24ccbda60bd322646a7ac4114ab145a1c3473ea36a6628a5fed87a616fc3ea760ca41579e138b62731e66143167f791c88a67820b56292353c9f9f318db9a142be455127972a298055e93315f9e548c6518e1cae6fa1406b6ac8f136f4b0a217d2de576672ea170824b1dcea9dd37431707a0bf6a12d411f163bb30d90b5d972394d7b6c32e87b7b5b7b88f6c88e265c2324565f92fcb17008f6c5f9e99323271165a0aa6a81494490d7b935981dc43c5296da0ad648d16551654ee2b7a98bbe0f8d0a5841d2aaad430aa8cda85a2ddab1be7d2049f241c3e5baf1ec8d341b36f8eb49c1ee56083c6d28f71cd6f4653cbd98507081e795a066e226c0fb4d2c6e1fd7fb7e177bb0d390319b4e0948c720ba8b1ae58c6280247b777a7324eb1d57c9fe7330ae227f9e4a49350e44726936775937367fb46b624b6f69ce3a06bf0e7f9480d29061d72c495e0d7b4c9d66289d944857fc37925368e3c34d03429e1e5273bc27918535a15e7336c140ac8124e9c2e9b561880179812cd08e33ce09ea7ac52197b34c9da3aad9435be0d6aa53357fa1303870de46ddb4358694ca249e4632b25d56f4b458f9b0dcf5c91764bf8a94524c589c3756bc0b0af4f2badd46e6ee0ac4e6156723f6f6746312a50495dfe4d5a1f1fc85412b61363b5b1bf5f6d29e1e2c6946868852701d5bb057df911b71f550481dc191d8901a445c12b698ade5583ae67a0f226d1247d3fa6dce4a78a0cfe98d9b4b0581b47b88f36c34abb8e7737964df85e012e77640ca78265a13f4d3428329c1e53f6e152cf4715f8b129145142156d8dfff644233ff2d175d79f87bd37e1948fae2f574b1d274b0ec13bde05ef8db54f1982cc795a40119ee01099662614e7201635d490e7243caae301d912c0b703d8dbba7a78d299d1fa70293202bf7cd10a7f8bbb3bd231bc8a97b0b0060817e13e747687db9876febade1be570d631772395cb676fab1fce95322ec350d9672423da22ea02b930f3bfa90bda31dfd614047feed67c1e4cf11c4ca91fc2f6b74f5383521e5c1cec9e3d81a8e5d948b5400c87f9a0fac49508eff3e1f95911d5dd13f3f1df388466a3af4b1c397446e11f2630b55b061d9b76f2ff7b71885a8ad424ad8f4da9f5b901f5707b438c2246983018866826cf7eade1b921ec3251f5a6283d37699964998d56472bd1a08fe463da4a91ed118af90bf3a7ff0d49fc554f5aa79e38836f6b19ba777cf37f8241aa725dee6d7fc0f1dbd3e059c6b69b75dc280217426f1f3a251dda3fe726e24a0bd10bda449bd55df63cb9bc0ce77f0a32aef85c78d69978e9de58392a631dd21c0dea658065d1a5c2597763c846ac512e0cd160e9448d1d7cda8a5198cd8a825434d4a0170cc266aaef5a78c7d030ae80f136dc41692473b059d04b1e972943d86086ea019511d615e21272599c051e54e458b5873eb3450437a251dd7fcaa5f6b549f1cd2dd1c82dcb680be32a1af7f38f20067e7a249edaf75ff4699cf5d25b52ccf8c35945cedb620f0b9236b267e804141012da3b543abe6bcde1f1e363621ae94da82f61cda86eeb1b2c0e4523035499201ed5ec1933f73104ebaf827eb8bd1fbaa09f829a7206e8369619a47be9ef37f6563e31271c9cb44bd580747ec37cb50f32629a6233c07239cd8ca0b091cb24cce0bebaba8362701d7570b8d13a960d6ba7316ccb9d12737436617e8cac9921a7e15604cb692da81213980b4742882c2cf0d38cfe221b8deae1d01bb26e4ec56486e02cda2b172a7944cc79857a8744533ecb0e5d8d59a67be46712c48ba5d2fa34fac8fc8b3b12f3bcf5a06844ae2c44462761d752a8de2cb38b0018e868581ebacd22690392810704661b5b3536c8281fcb7e1ca7f216892a1b09abe4260a96cff14138920f514a3e86139bcd09875be55d9b45e8a674980170c2731f53f221f7937c9474a400d35900bc75a41ff4d50beef98cd403e4e7b30a89041d6a107bb376dccbb081368e5d5e64828a2e302ba0b5123c048249c60751ae5c747716ec4bbaecfc1c687ee3c34bae01e5ca8c22e1d791f12a72be28d16c4e6efb85c162741b27f2c9d5c7cf704734892da45aa0cf00062c390c5275fb52c7a2f900c1029afdcedd88cdde606925011a85c42d6f3c70af95c399ca9a7322b5cc23e58543fec4ee52a87532d33106f8a4d0167e9b7db2f16e016a0c86ba70fe6c03a712897083ead0d3b31175c331db5963c30408370c08b11e7f457ea44954a54a6f1c15ed771eb2a0fb1335a42967fd05809a1f6c3c3376a6fc80657a9842eda066c91f9fd9043b889f822f83c11fb28e83fcaf2864e5785d0ddbbf60da73b86539feaada1b551eccde7df9deb238a963666179416234587ffa89eb4e19b2a992ae6d68a07e18a672a958df1c69c1f872a9e2c9b2cea80b223aa626b3b23d81ce4a598d2d7c374cc978fff163eac42c84a6c0cf88ca6c279579ded7d6f621ab72e180f5b0279c7b72dbadf5e04ac30d0764834e83d5f15b1e50a9046a94c6fb653af7ef215a08428809b8a58032e78dad5ab058cafe3124c000144920d85333c62dac934dd83c7060d7dacd811dc81ad13275a020e202f8f9b8632577249dc1a8a2dc25ffb727fdcfdb7f9af9ac6ec8a6f096260f7af709b689a22dd2f187ebb73df5e439d2f9361a14b98c48f9eca0c5259abd3722dbb3bbf98b83ca4f440b6552b1837d7aef7c607aee0a78e18a0ff430591b78f62cd99fa314bf7396733b2e7d946b5ebc34d0d86158bc6175d5cdc99560970f34e1aeff5650cf47e4237cca0b53a981862d0e8931b0ef3d8500496e04dbebbbf15ccae0da299dc0fddc597027e4eaafef71bee2d6d74829fe1dbf3d2d7836115a80cc5aace84838ab19e5714dfb010b8c79b0008c6f8d350d3c8687dcb2e48ec269d8ccfaa9d773e7207e1dd28a3857144a4d52057cfaac8bfd41a34321509e9b52c16358b068917d5832c6654647c4e2d305072df04c35b2178bd08b8c29e91a417128ae5ebb95df1a24a06d1f1b1eab3b5b1ddf1818d613909ce3234242c2aa8cc5e163009bd4403e03732846ceadcc36fa935d62209bad635a69c4e13a71f4bed4f81a9d514aecc664cbfc638763add6f8b99ec84a280cf5e9683e07b20b11f1cb0fede3eb5ea69af42e40ec973ab65fe40f5bf283aeb72556df84c4808cb28231132532d2dca7f78a99c857e7c67ef2b1357b28b994988d90a8640d8ed9083d315e71d5162c7ce8d08d4660ba990109ab991ac399b951ce4c46b3fe7873a39021e7e12b04e653a305ba793a54003c46769dee3197f2ad465a298cbc42f3d7a1f480efe98d0253cd95342c4cd22049cbf0b0c267a104c8b33f961e2af95026fbda6d204736b18b49700f8e87a21d096acb73dc1a7ff29f49032c5c2e7a45594740fd4800a368f7e08758f2450a803a717f5ec012549c04bac9163b8a99ab94cd5941671781c0f8fcfafde5279e14be8e5360e18d26e85740bd89085e1df9bc3189fdd34c6e9538f47d061a2c1fdf2529f4b2494a5f0f6de1e40b3156dff9b59f4299844a507cbb36f7bfda9c35454f47b3690986958f46c80710edd2d116c5e0e4faf7c5a68682439bb925a164969048d9e86f2474aba183af13f6dbee1e839e9c36e0a6aa342c86722b6e171280e1ed4e58531ac8129cc0beaa041af085c63c777a5a66e21c2b58b18379948c3f5e5fb3051517b889348deadb315425536fe5dde70fecbfe37a5e3660efe3c87e8ece9576674bb93db3d0b47f3a771cbd8e7fff13887b7bac15735d7c6d9c6ac15dd22a400f3deccc24949ae9219c9ca1e8fc88fd798cfe5ecc8d259a84fd442be39514c7d0b7fd08ef112fb70df9f231362776ed9a766409b8b809b043c681159bcfb72331de75cdd6bebd4c453fd704bf7dd295499067191511415a4536897bb943a6f5f5397ed9a8ab85ed1cff8782a3e889769e5306737b81249517ee2a105479b54f45be9d580f83d85c8039d9b34ad6312d86c6bc48c30a3dc1c3297d855d49ced13e2f353f9e41f252b6a83e72b6afd2a5e7cf54068ee99f2191935166c7d7135db821b3fb4b5b598bb0413b1a30d8bb731e19f57b969083ee6bf340b5547dc0fc121de1d079ac11336e0cc865d22986d3b95826fa6de54a45e9d09f63f2154a6da26dc785d33b36e6004414424fd95dca6682601fc1d61417713aa6a2e0ad11c03e6cdebe24ef83ac1191f5c77f43135db0e3f7fe515b185873d6f6ceebaf125fef4500e4754569303240d40e6d5c7308cb1d27efe375c5a6d613abbd2b196cb04becd63fd8e1612bdb291848e6089a5258991254c0f4c6f4737f27c3e6fe218f59b7e3fdb5423066cd2eed6fa539b4d0018ae4ad65ef9034d7b1592556e7d5aa8051bbfb112236bc6869385e19ced1e2fc2c4db4625d439cca0a309146071687ec3f7cfc94e3aa282c84f080aaab2e4462f841537eaa96378bb74e2c2c7e24f38a3f720aa5f37b4b34bc5d1b2e38ae1fc869fc8b558a35cb6e509670a24a4fd9d024ff6b2d2ce730e0f0359495fcd98a2199ca42b35613db27865dafbcc75c719767c48708733e65b53d052ea0922b1546b33d76cd9b1b0b11bb1b79aed07a20e8c4a99f0a37132c6b86903f556db9130b354698e3303e50c73aabeaa670a6aa297c1ac7e9c66f708fc5bdbf397474a34274f448d7935d8b040285192b6600cd66042fd41400815fa9e2f838c315c1a02fb820c1dfb36d5fcbedca77aad73ce9420e7e52a757edef19e21909bcbac302c7b1075db767ff16e8c933ab79b7ba4b8938f1ee2e759cbd383aeb18bdc883d21b8d03d0a312cb2c307d754889022acca3169054122d191dec1546eeb6b12b8bc940307c36fdb9105d512b5d631ac344dafd65096e51dab35d1c33fb1c1bdc6f535f8467dc5b54b888c6e58e2894fea19fc684dcc35e22e1126a7520d75d736a8ce3e91655dc1e8f46a99cd05c5411d72b6e1ad6b34e5bcedb860ed62310e96a431fbfcebfa14a444bb134a73251c3c1a33f75c11f66d97c813fadfcf0af55f00efd498ab1bb1ca18dda349098c881046e7989b162888d63841172a5bcf65b42af81887b60e9aee9f27b6eb1a1957e6b1fcc8f807642e568621dbbabae3abaa7d03ec80b915f155d01e6aa08f2f6d3e24bbb4c881a49dbce2de1d45f7f999c8336304838de2d3a57db7f23d8fb29026021268cd3200bd7d8937fdd2052eddeb68d8510bf3562438ea0eb21f5eb5611f4f8e6e0871716e786e552e4b480c5026bcc85b7103d65c7059dd30239ea05a4cc337b658020b061174d5c00e5a19ac7c3bc42c2b9310dc491fd37d50451a67aad59a6f12ea4b65a5077e7ea387589955108d7d838457d82c90bbdf0d2ff79627ff9b604de9d2cb55e10329daae942a4c5eec8c53525c53367b1f3e971b63f4a05e36faa7065ac35eb21ae0dd8dac51b045d4fa68babfece59979cb36c54cef4e4363d86377d0d667bf30263d4d70c9f95d620d1a9b1c584bc3f08a940dbd6db17528dcd1ffe1e68d28b54be1347ce0c91e3abb56dda5ba9b32ed448cfde6368f41910160b19b908f3b5609cd833827c990579298fdbc527868f01aa563851f094847dca8644eeb300d19dd419da702f8bd79b2b77a2d210d5f849bf6f771533a9e9a5593baeae695252629a61fb341478dd8151571873031793548503f53791692d5318d39d93abd6cc5e77f5683bea336673382b647829101cc83b2c49405d10a8a50a5b4ac733cdfd8f55d4a2cc36d2144ac51c95a6878a041fe2c79302e7d69b11112d4214a3cc2d6d54ad40b9b08953842f1ae0352bd98938bb615ce3b9197a8b39e25fc62dfc7ef784695315aceb4724bcf8d3c7a4c0105fdcbd8d8508adeec78aca5d536d34840f2d0d4d33019e7fcb75df9becc703243141d5cf935f9d42f6b36e3f67655845ab79add0f050c0ce40e7e28179673bf54c52950469aafb65303d0e27bae68c18b741ba8f7c1fe8f9633066d49c47b523c6f09d7e441895fe50a71dcfac15e0fcb13f5082d2fc94a5df8965b5e11ddc4d11d36f69cae7e93694e214a0996a0705ea35139b3f6b9177343ab967c6d848a43367be183948083883f4765312b5094103b66e8c8d35cadfe3c776154649a9385a8442c86a391b23bba19ebbbf1d0201471337522399b781b5376b9c3ded4db9a00f271904655ac4ae42474a2cc0b1d0f3528f72c57e5e62f800a2c6c8d19413e51c42f6081bcca51ef3d87f16e16db9597beff6801a76c3adf2793a939d296f302a351902a3a9dffbd185ddc24f9eba8d57dc001be14c69b5749c6f6c6a7eb6163b1e1571c6f107abb3b9a1be3f3fc0f888e69d07bf07c24be2e01913f7df895511c74ed0197681ae1f86559ecb64a09f869c50412a9d8ea3759f356aad509da3617f739cb1b95da1e19b5140e8c0ceca139274d2ad7d41247b275ead5039d27667547520f7755f9e2c5c2c6836e5f4027b0160801bfd3240e73a65d1a97f3bb48e431c241028e49eb846939baec30c42c717f4cae9f89a62d2fc51ad6dae1cccd268f606eca0c96dcdaae574fd31152524bc787c244c69b31543bd1c488b29f7101f8e425eef3c6abd2bc0c55b308b305b30d1e7387462087522117596cf1dfd14a079430e312f137220959a27864c8f7b4a2a1fdecfdf7bce75508ee81679731f32d1f7f3198e3f535bfdfa09170ff6cc0a95719916083a0e8ad9d3e47120c9e98ffc7e9398dff0d74fcb3361b51b4f24d293c722cb7a5ad8954b114192833d3b1953ebc1e8d12fbab74116e56d0e07519ad354ab86056234cef3704aa91c1262b4d3db4fcc12c7a554a10051f52800a0dc718c519effe3fd329057226eee2121d848fe075fc439955a509a5c1ff8506c68d54e6bf338995b6750f9433dbebc89bbc3be0b8c650f2352925dd5b22dfb43834f15aed82f3f70c32a948cfb11b4867088fe0569c2f5ba14c012f23df7076eba3b55e2cc70527d42ab4e57191f8a25ceb41b9d4d01dc64a8efc67fada379ff5d48c33a0e1c3521387fb0952918d8427b695e8862f40c222f20d30a13c7c6cc8a679fc2cb8e469018c977c31168ad502dd28428530d1b630e95ca75d90543b64da9470b0a08ba9b1d45b956360cd7a61e4d9fd5f339de6c508b4831b55a222159f573d55de262c44e3efea22dccd0063826ae5882ffa001f78930142330c4d652c067431fb9a574fd40fc436b4b04d3e9cce778c02569f12dc180e092e8629fc7c38a70867360dc6da0d5e37c9c14733816595ec3e418887e9b9119fe8100374c6823bf5d06d5d1d1469354971be8640850805827dfa6f0475dc530737d8e552701cb47a35498dc2e01285f910aa51583e810d622bd16b450a78089c1dd5731e1cc5bb4fbfb2911b3c8aea094426c16985f27b085e89ed84baf28f85eb3cec2d31c65116978a44a575087b72150ddc6460babe6a332962faeb90c4002887d174dd01bd4194e04488afcfe402d54b05fd70f9d19daddfa35e9427eac7f4ea0c4cd24bbb1fa04e7543df268f32cf221b57b1fe9156c034c4aab5576d7e91820be44060874136b4bcbcc6b4f27681575d3ac2c0f396b3fc9e8913c225bf35b8cbbd3e4825412f53c6b4ce56305961ae0abf8ee5412eae33462bc489b6c28a3821e2f89afcdbc34fac7fd573699e46f2b381daedc84b34f91718ecb7de5268734f547f15d21089c88b5bb1bb1b5df6ff1ba655212acacb9a1d7afc9927abd09fb7493fe49bd010d73e0c6f5597c7babcc094c915c4a1414583e6239f0b75537d975540e73f3e8bc0d1b28c073072a1b0596bb36a69696c514dbe9bed38ca8b624962632461e4a784c64dc0f258d2c383d0fdbca7a08c39224e0dfa6e13d99d70cb5290cec2d725181c86ac2e4dc3fcb2a5e630ac60ac4085d76b162abf6c90a3969274ed348ff8c2493ab00b7251da422eacf82247cb2f0b0f4f31bcd82ce43c6a8160ceab52db4d5aaede3f71f878cd109e6795a01b7ecb23365511cf890ce9a45609fe555ee41680d902141d3cb45b46400cb08e53b688df527b14ec84264d0ca6ed9b3615f55e4f4384d0c6d4489cd75ead20a2db945632752c965d06a402f9ce0b0adae1caf70a77ca665e2b3642cbf93557356ba98b4a46dbea590f6fbcdfe359717f1c465a2b7aefa88894f329c22f04e1e3199189a71955e727edb4638f849d133ea0e747b1f3043400d4a2b8d881794cd24cd299bce6cce65ee63603c56d076a4a427b7b86fc48be1f436ac3cb21d969014c803f0ab5dcf72a72e8293db0c4f0d8b4751349e144df9aaaefdfd875a98cbbf9858aab07326e3e44d91c51813cdb84861117bde4e5dc7a84f9bca8e960801c35d29e3b592543f5bf197982646e510b6d53c697bd3518e462d7788b04d66626952dfc99e652064cf68eb6993eea16ea02c30361a1e69c413590f44cae96e8d6a9e1931a45379a742c246bdb78a11497d6622eb2e0aceb201781a1a22b6c1425319aaaa10a9dad236d99eb1019b884ff186d6759e18948b8fdd46d6cf83f5efbc390a14e97f8781fffb40811dfd57bc684fece0db13aa6a1c214493f19cb0707d9cf72d9602293ca31bbe5b8d3834094a8d0e92feb4aeafad66853ae8c3b6dd410abc5c1a5e94137b325200fcb0545d6121a790afc49a8c8cec757f68f5991b0c00fa1086d54040d1452a819a5c1aef4f3738a6db25505a7b703b11659fd0897b62c36f951a4e0abcf1aa3e10e0c9e0e44fea6c1a95cb7c5a36d9933f9445fd45752fe1f1f680af3e92172c55fc159d0f911f9ed2116a323ffdd64faa074e649017f7cedf28515ce3d46cc6d01611617fae87d82df6df30546c589e9817b9694e4db98c63b0b9ec204eed41186b62e4dc60e69dd9fb5e3db4264293b1111aa1be2bf9bd982f2635445f53c1688e52143233a10d374e01895d072ab317b6e878de15a65f991f93d367e634a8019b4166927c9a24b98ac0725278a7dc9afc616e381782d503be9e49422b45377d34ac9bfc0089b037bdfbb0fc7eddc00dcfcaa597d181e39b7ad17586fa8cdea62d8dd906f56333cc6a831b9d03cc5b90c37339b6227489ea9900dd5e1d5908c28020694d9a3cf024cbee07ac85add847c1aaf2b0b62b303e2d512b7c08fe72e17c603a9b7c581a7b3fda3c522c3e30f080921431e3a4596ffcc941612685df714c506e0b216eac8cc7d9691ade7cde74edc2fe418da8b3fde3224eb882444a4392807d44d161e5d6c51dd3b771dd6a4bb30b91a20eb64766b8540336473dd097dfc3490e8de885f345995c8142fcfc62747bea4983f02bb69e1d567a7c0784e456d326a41e697ca8a8f342c28203df70ea7534e6df76201d93acc2f9951ea250261215ac58348ef6f32e1e7fb5e7e69d9dc8107d9a253b4ce4efbd5c6a8075b23434b293fab8042e9ab62161373a9679132b775394c8c6013489ad462c86a24431ed68abbda932058ddd126a4b088e3b4575752404072d48cef7d4a83e6ba7034ef779bd527ff4906b6a62e15d4869f51bd30047a7b60c9e29522d6b02cbe992283552200559a76552a5cf4d684686376d2b69572d58ef12c2bb560c54a9e17295c8727e5ed5b601eebb2ed7bc9d4f52001b12d5ab85123dd70db77b346b12eba8aa653bdcc49ed987dcd9b6fa9e8073c71a311a06c31442e39f7d6954f05400284403af0c880a71f3a096289f9c97f8756a485baca2066f230e0abc16cc07ca8119caf43d44ed5ccdea9af525b3fa5e9c363121d15e6e898215d6a9dae0764599d66bc88bc037f291c879f31b05e9be4467522ee6d36fb1ce9603202d0f6cbb30532a962f3881e604af270b9d639d622457985216e1657d25bcdcc0df17bd023a5729c4f151162514e1874e3d970be14dd08704172488098370c27a90f74e15b0a5a1612d29feeef192c412454da4040ddcd0722963c254b874dfb158253845fbf7b6015b0833ca2cd024c9200096cc9df8a4a41ca0881011b1392401da68cec795093af2e2e7f83916089733fd4ec303dd9457e17a2e44eca9615f62ccfa3870ce672f2f34a0e92bf60072799442adf708c17c163c0d97b483fce83f12893abad2b3c66c1b1ed0e2484170799a561b5a6c9d6c3fcced52b61e14cab90698d4dc9093b6e09aeb491e278eee4c6f257fe4fbdf77f79a672cd5515c78e49e9dc3e81b0d51db9f4d970bceaa75e531383c16420759d972e8f7a7362410f0a2aeb0014fa0fa76b4546b42c8e15cd4f7c0b5228240e4f0c6ef6007debb09c1b4c8e63e21798d5554c21044bcc2a6e7a2136a6757bd456ea7217bd0e88aa3226d65611c205431747d132602d7f7409cd74c167471f8a7041f60b4d41ee292d551377078a5fb9361961513278cacef52356f1064a44ae356380b8bcb6739254beb5a3a6cc5feae7c4f99dbe81073fa38fd3905bc159d13b4e896bd023bba3f076e276444d87360b3811eeede71b96cee3682a27d35282fd6f44989e73f4138c4a29eaacb47cc28f2c5f2f2933218476c497737ed4501ce3e624598b6ef0efe7de6a37d25a051264094fb366622ad6f1be5ad5c3dd49655f0977c802a55e6e570622135047dd0ac1984ad7c6b0ef865ab2e64d15ba658edf0a89595c2a96470eb134283976accf90a919931e1247f39f15296f667b09700e990ca37ce8a85b9fa32cbecb6238a493a416201f6b24beb3e45371c973744430ae02d85237d510c2a5ba7106eb4106cabf774d04ad3a08c66e84675739dad7b11268a21bf2f7e51baf213f4dbfde2ce71fed0c043d37ab246a288d96ef4131124c9f6aa4eafba1d2355ed51a15711e2b827329ab1415cf7c61cc6d2ff614a2c9d05500b310efc37d8bc05f853d6da21d0eac7e9a91ca31841402d249db15a370a44f75ed3991ca3c7a51a9b84e5d765d316402c11ddb9df587908e08054961f9052c1aa5327002f1e2f8bbc3bb594fff81d8236558cf5548a40c4b1b03721e3275c561aa58b28f1fec346091afc844435828685e71cf35814d50886a0012f3fd0857e3303d15f11b2a0a2b6df2d71a9fac43b0084e11063cb411400f4fb08cf6e9021a498f018f66bab530b20e58f0fcea73f64c6d71906adf320cff8f1556eb4c9bd4570d362d1b7db57e8115a8bfde565a4a2430ff5bee598cd0e01ffea8b3fbeba0db07286a4fa5d61aea62608f6ca5376ca18bca8f3a60585fd47d8cb7ae18b2969adea0748bed7ec7442d0f88f5cae067d899c0b9023a76c6388e22cc050eb0b55801c1634a971b4ddbc2d3ad671a57928a13f321d9e335e56ad02fa724fcae024189cca7587d498df9fb9c978a2eeee7ce73c892459f0452ac026b20f1f08dd4d3daa48b8a508a5ca9aeaa8f67bda8fd57c837877d5924a4caff0f185688c4336beeb1034b700f52ea05eca02e38a3fe60e813522e082ef724cab829511e0fbaca9a8eade836e33d3356e41bf2e1f156984b189f56a464fd8cea525b448225407c1a41c845ff5e3fc49c0fe37ee766f0c0caa2efdeae03bba2c0d1a28b28a1d14abef0274162e109a54fd0279f86e9650d038bd6e5648611f1dbc141d110eea7ae35468598dff70a8a31da0516c45f40b5dd835c419fb1844fc593d343d78a2a68d05794fa08d5630fae41036431b8bb5393e139eba0eba01ea0876ed7f6a93ded9cc30733f55d9e67cedc7e741a20f5ebac0ad89fa43b8cc78aa42c886032cef1c11293cf21d23ea0873c0ef7e5be5179918b97d783cce1c1bb965c4dc30b6244e16bbc3d56deac22f5cd50625a95d3f6e4272609e3c0d85dcf23d66bbc3197e73a096ea919a88b1f0c62eb4eafc28162623cb1e96e523f6f08a1fee78a64f6949d1b4cde3361ea87455776582f7df350a30768c5550bd648d6f4234a66cfcf3696cd337357153e2a1fa3f57a6fad70b369b6766b46d9326ab4ddc3183f9e603c9ac3409017acb59636ad0b1e7a9c65743aceed41ddc86b0f08caf5e4e980dd5f326eabc28e1ce681f62307a2f4b7e2c031765f8931c449e8178688fccfbddebf6456f475de2791e12f8e62c434cdafb0a84cf00b5490a36b4548e59bd7a9a40c4417cad2567e8ab2725dd0bb6e1f31072dbe354b78e4695ab14c41824da053256cf8a8c08e118f0d335bef1d324ab554dc01e4b00962f272e9c092fe1134f4153f7f41f4362306088e5e936642260e35bb9a8a7de6f7833a0ccc26ee9ee69d5c5c48909f1bc01028fe64a0fa55bafe8b02302f0f8c2182c254a9fd584ced26ae066c3c56fa68281489fe8cf0dfcf08cf6a93fa9a83d7186145fb5034485b99724f8a3603b54fca0ba520d87a2151b20f75931dffb2dd128b8d79fa20a8da46ea6b97e8d4c4f2ec12bb07b6ba8d781d3712af3ca4d0b1a9f37c57ccb2bbc4c53fc3324feba1c7745f59ea2c010932994243bd40268841bc44eec3e40bbf5ab275a02b1abe007bd399e0b8ce14b12a28ba0c77864c89e05cb5a78ac626ba25e9bb487c5956c8eb16f42b8423536878352dac51a1c92b25b5f583f3bbd52c50a235958cf45b0497cada21a28ca88424c69bf6a8f5e270e27599802c69fdbc0af25e8f70b6e5d66360f95499904e5516c39e7e3b1486bd57a55ba24e002028c164d3614aecb67d141a5bca59172acfa2819f860aded79af98e2107d4da7fea951f16803630644afff40b27e8911a23916f958d579f7780775556001a64a80c10b3547119c29823c8e6f6221ea62f2cb7e7a16bae65baf063169db7b4451bb545da46b5f77f1ac5df1ff72ef38898164d53b23bebcdcdb71d7d45e3fa3266cca0027f87c8e8eb6bfcd5213c01407ee2810ca8a1ac73a63fc5e345be45174753380949448e786161e1de2863e4c76f32fecbf5443b07c1e31c7c579d4fc94632797586f8f363baa792849914a98484f91255aec102e65ff4a92d5602ffef95c437179a5d2c483b724c403d0a1b84ca08f7fd3ec0a78abdbb7b05e1bf110308fdff06e141927fcbc11f340c27e9bc82fb043e1db113239202307473cf339cb291014307da46022786cfbe62bce9f8877ebfb67134312a92345d24a88082d63e0cdd4b7870203e874255b458fe5f3804f3ed0ec25670af750b50f60fb8acff97d707741ccf770928634dbf63d6dfc3925ff9efe8f32ed70ff71f2dc5907aba4cf35a661875ae222693057ddeb481c668c00656b714d9e0391bc1c8c9983f2c308b2f4e69c1620a6dc6c3cae2e3e1873ea13e31d1fa29cc15806580e534e1a48c319f6f28f00e3c817ccd451c82cc28bb95623f4d8adacb54eb9ad0fe7e0b52385281a4930c28c2a33d1b2b2a141aa219dd6adee1c59b172159e466d0071b092368d658315f9e65c6f6ae63c376adf3309f05ca3411848e14af0347c2e446915951dcb91f2ce9918e09d1cc1201cd1da2e7ebf6c3bca0fd8c40985e2519c892915de2ffee9b50d76e54051d7d21353230092ff24e0dbfbadaacf9f85d56c9873974b4382f424ce4007b93d01a37490155e716103de87055724886d57c68468f440c5ab75317b0c101260d3d3f70b790b4e8a10a3fdd846b40ad1731a868d48bd5dc0ffcaa7c3fd6c0a4ce9e085386670175d056a53161b7b35caf514e93f4e51cd54e6906356c390b8c93dffb07c7b0ca69ffdd83bb7e0d309ed6c788bcd3e3d42584af4e49e103614dba2b78cc23b35b2e8228d81d849c7ff133bab5c8adf8264479f86126e1090b897df48f9b3778171049ff92b44010f1cf03f02e32e196a14fa09717cbc829c7cd619bf03ef832d995f2848d0a265abce8bbc8fa2a2d0e430071e8feb302bccc320a05d15aa8a99e88e414f2212bb81466179e51194d1c5a01d9e274d85782d49cddbc46cacf3a14ca1072be7e9bd1799621c41e050e4f423078e025acca7e2d6603f63ef4d61a3e79518036f0e1b0c4521b6dfb4fedc938308baaa7f8833282e24cc0037ce1b27ccb0a411c3bc735a0907045a6665f80e92ad1a844fd47eb4004ee922f5c7a586c46524242603aec740fe292eaf876d47699158faec3c784d0267142d2b9a4dab63acd4ae53ff7caf051615eaaa09d4293b0233e3498667e97a475e6472d8e9cf44fdb26729a570994d223d1e3a424ed686accf7f895d9459b1d5a95d79fbebf67057248922c6751163a1dcf8471b41c49470c29f1f922a2f4d77eb6d00da4135cb688e9960eb28eac5ac380a376e04b86b623b7b4a2cb1a051f5851ef0489afce94bb94b7a8c3682c00b4882051e8b9aa6aa6bd60eb0fc43410255b19749c08d4091fb20dd8e11bcf3f810ab03e7ccc98341747201268c649bee58110048d3d2aeadf623ef2a1fd0c77c30bd7224a93562dbf6377fad34b0dcc60445373e1ec6b2c591b0a450ea32f78623bba2cdf0ae3b1fb4661d4e68a865e6e4869d9727b371a78e87d7439b3bcf7125c169d47ec8d13da26820b215ca0cafac680bde1e66288d7cd7b5585dcd062272350ef5ab500a3644d368e5aac7379843a174ddfdf46150f926dc60d0fd5eb3b3bd399012c1ba36ad0d9d5d77e88c601cf410e56f83cb43896929e6745a0f8e4a43a30e5501205119577328783ffba93e6ece4b52cdaebea94e26dc7c228703ad2cbcfdedd949bb20ca040b786c441791a45a7c986ca4bca556d2b9a42c273008aa13bcd3309db652f608582348187809e4adfeb4a24d5abd7aa26da0791be3b799c1f3c83586ece4d63a83149309296f959fba783ac627cc009ad18f050c88a30be908eac13d503fbed0f6d981f16de606ff194cd97ee6c43407c894bb5c2ba7ff3102533fe649df333b924c2d5d28bc0149723578881229239fd802a8fdf6a56f1bb53c09a91a913c499649601248acda2845d015c14af42f0c9971346139de39630c20c5a60f651f02f6926b29632b641790516582f9f0a3e42e626228feb4ed6abcdad4a614e3cd57da7fddea64ce6303e4e2e6f26bbe629549943f6d6081ddd6997b9008f0dd4e78b761249f84f5632e4800c4fb05921191a6ca173fd1f17c4af8f2171c00509a48c4f8d960906cb86e3aab7b0e79fef798ef5938c8d822dfec981f2dd3bc31375fdd17635204acfac307a3487d0451171c12ee1a209f103cb2b7c45fe2d358e9c5d2b2dd9d300271d3735bb2514ad3adf67699c2b3ef3f802d0d1a08aebd49361c0f9c5b134d1f214768bc6e1cc588fb0fa3aec1221bbc1bb3757378d82807861fef9c634f13d9e9b9c3891039de80cfae1fb7c30d78212860da6046beb7317bc7e760c7777eaaa046cc4f02ba68314657309c19a50367919a9f3e8ff1b54898cbec5cee96c4aea4aa90266193660efbae272cac2e61f18c5c3202f108aba1620d8c2f020182801ec0f2100a6d1b363a90ae07c5bead6fe25784bcefacfb1bd97b4b29654a32a5145804040483043fc46088142a539e9052c462600b059835f57a71bd18d1a8dee2eba3d6d0f057b6592fdfb24adb238558bf2d8ff555b64232f7d547ad61f92adbac6c7b96678568f9172c991048ac96ab86c52ae960cdd130002a180e31f0dfe20d228a6079ec8b68f9173a2d4ac074b27ccbf7fbf2a88d96502c58e6434bf622d34637e410d866399b65fd0a673839d69095ddc8b1feff2dab94838363e87a1c1c39164ece6549232d9fe2d135faedfa95ccc655ba715d2ba3d168a5aa5aaa2a23addc609a6be5b7ea476f55a51b3682aaef01c7cacacaca252556246f304d2565766394d9b8c134bb7384a3aaaa1cd670dc108bc5fa4d8b2c8bf4625996949794524a79c9eaa5bca4252d69b9110dabaaa48d1ba7972775295ed8cfc52e33d8a5c205529230062e84fc4197f7eeb7efef377f98b53710deedededf0fd2d58227537d6edb0fb750bf166e2ab4143448c023aa19c1f73a113899452d3fea6d550a0c92367f93479ec98fc6e79dcf26c0f931d22c49789155a86425608733770ce71b303c2fda658cb9895df65cbec76cae9fb47d59178c75c6cab8028cbbddb238898a6a94a7a86e43712c800349a517ed49ad1cb6ce3afdee22ac6cc850443c68f5a0343662fe58bc7fe07cbb73c101931dbb8c25eb0582b2596910e2396571942acbcb3947e20a9c0e8c6e829a03256328de44cbfa24026eb480e816de68335ecdaf877db87d90e1cd678d7adfb2ded60cdc9dfc132a2b1d241964fb125df6df22f292fab64bd351ad1b0ac2b46964cd9147fd3118db8325ebfc5b7de8aa5527c7959d688c675c9aa5ac96854d9c51ae3304dccb6fd6aab321c9d384cb32f7fa755551fe3ea5428d4cd0cf86e9b19925e2084f0dab0c931cad2cf4a961a4208e146194ab028f22aa6e10c3de841163d903b4d3451d44415192eb420d4d811420861b8ac86f35ad6b2afe557cb57825143fe84ee1d3e7efb30779e915873dfcc211cbdaedc5d811e1743767e6420a4040c51e2c3f70f5e315648603fa900c217865c1a7b982afe00bc80d980ff227c09af8fa51e3ee1e8537c1919653060f6acbdf752098c0a28ba98efdd0fb01212cc7df5a92bdb80cceb65bc7e4735e05bee610f9faefaf70c2be8a073f3491852c6a7c409cf7c34ccf782fc5021ca3f29ea7192c4542ee693113e148a69f4c97c0f3303017dd2139db0d4d2eb033be3abacebbdeff8de7b3038ab015fbe7ced17b9c03097caba2eab8a80cef71edfc1a8117fbed23eaed8e224307f17d2c77e349c0e3a6174432402b044f38a111d57b3190c2430fc7d78cf26b0ffde87150249673ee8c31762ffbd10485ce603ccd409f1b21b9480adab29a989df14f9f92f2ad8f614d08782a53fb820899423528278ca114454a4f474a11342f8e0eb52ab6624c69d2396c2583e225c1fbb034405888a8bf921fe7a1b403afc36808080522faa1c71717952a031dc3b1f2b78e74365dc0b1f6c53206c8642d127eae40a05fa1915d113344b539518d1641a819f627fcf7746c7369719e1791930eb5ea559c2850648b8ac80861c4c71f1a1a10916eff251a2e779bc873023b06d77b41d229e69b4c741c13a6a138f1c6942e707a55924148a1645222c45c0b6f5f16142476de24f1678f972478029400108a00cb880c6708f4365acc0b41808d335f879453a3a7a042d113bb6e7b5f7105589cd8c065093d4d882699d796665db02d17126f73196743e46f72265541bee1f6804531b87a94d4735997879f898bc58131857035b31777ffee4cb2934c4e0d758c75cd7b80373bd45357e8d8198ebef618ac5ecbbe7c5fa1151889aba00379ddd9d1dc2989b1467bd8d85f7ebf7e083dd3b58b18bb0eeeef6e8839dbf113c9722780dbf9d045a8c7d02191a3b48c2ee4b62b06661caebf7fc79630f0667c5139a05afdff3e73d22c21a28ee04a902617b15c8e0a4bdfbf573944382f9bbfb7bbe648808ca69c60b9b9e417b601a7ffd1b3ee0e4d880d6a80c853c652663587337acdd50044c8b790ce1ab1e84aff2e7f253ec19ac640c6b314eb6d40a42c055c95635974073c7f7b2205e670fbe4cb3de7d8cd556104d8bc1c931a34c4e4142cffb47cac50a6cd3283b7ac44822318dba3410ac7de6c90aa32fafc4101114b70c3720bcea7ae5724f340b53dc498c6bf1c518939c463b38275d181244db97f272a2a3b3fbf3c7cccccc9c392f6d3de0e4871042f8aff478b004999cc1e601c6ccccddbd8ebbd7b97b9db9db77e3c608a360294c3fd74419afdf830f767cbe6193f302d6dd1a83bf479cac32fb1abf63d5de0826bfebeece56659663ec67448c4d42475595a5094c1fbedd1ed696486683b525d2dddd4b647b4a3b647bf4692cec59225de5e4d8d7d26e8411427f858e3fc5d2aa5e96aa4a36aaea5996f5a41cc9fe37aae17fbd0d55eb2d592ac9973f83b5eef15e4544395e5766433f6e31b391a584307d9dfafd02a834aaaa663e58eb1dac45e9d922f05b1deccc331d2d59609b0add70291bdabdddfcbd0203e75cf78e94eeee0f2ecb88c693b0d71f6cd75d42b573ce41e8205ce71e3a084fb09d73fd9e7b19cafd7b7ed1f1ce38e33d84ce41f7dcbdbb25bd406cbe2ef55477f7dff6e7de95600183b96285a5ab990ac1367ac04197c33937849c910ec59a0ed3c4f8d6c1cf6199a3c3555b7e4508e1cbec848270468c314a1d7e8d68b8846e79749f21a584988eb6fab78ebe33f60c2847343cba8dd6d1513df7482fefbd6ee8ba1b967e3ad70dddebe79c73eea4c8589285c9b41ad8defd1cce98d5d10e1c64deb0c97d8a515530ff4d0b01434063b46bf7209c4b30d9f735d7fe5e5dd3b217d6629c2efd8aec4c54877aef596f35c26a4403be3895b9e4dceebe760f3b704d20fad7c8f5298e9594efd9893594945e954634aaca23af389837aad16f392a7ef5562c954e712687bb4bc8f022f5a8068a6922cc50a7c928ae20c3767087df33379bdcbf0c87693ce30c4786d305ecc47f61158f9460694c40ddb8682edac60005d85545bf7e0f3ed81da7a0a0dd06bafbf337c39d80e955554546de17b5e13190610996830b62da0a4642069827fd6bdae904a439130e4370bd03054cf5af61ac397f2a92cc28606e27059c4ea7d3e974caa1323910718299519bbe809a0a1db41196bc7ecf9f7764d40d4980de7bfe9ca4fa5aa3833953ec42843a383848a4229c85333f6e899d9d203b5660c1ee6ed3d06c9a768108c484859a8914371c61db62f8b6f4a66abd9560baccb25bf55e611a46627cf32746830ee183cfc130835876e102b6ad0fff4cf113a39d4e33393a6e8e080d3d39d24414a7d3e9743a9d7252806dec73f26102867f9e3869a593ca9c26f39ed486e7d545c0bc446facbdfbf5731ef1c032e1c764b28ac482e8803b53d5f8da69820f112245889060dbf69c7a86c0a498462f1f2208050535219a1b6a6e1a4ad3b4d7b409401930c371261b54806d0c04c4049e5c0d94d12ce4031b4a2d2d9d6117c768afdff32122283c7e18e9e1f9fa3d7fdefe8a520522fd5cc96cb896ab840035f15725abf480d6d4c4eeb4794096d80373bd6532c63dd107c5348e089122fde4891327575cd1a40993b9699aa6695a0e36b9cfc1010b28a6713e37f8c1ebf79c6558ae3bc4aef184212228afda2f16403cf5b5289e7714495acadc5c5cf80626d8b63f3fcc404344507c5a7b01c9a016211b50bbeb2bcbf16a27227cae3b7b374cf32858ea520936cb5db0b43909a67ff3e0e6e387250622e59584c89c7a55e2ccca162e3b65f08135a1671a850de123826f3be57818ae19bd62a3920e9d3662a808c771fc3d0e0ee78793e1e028451cac392f6055c90de1c877590faca11c6ac6cce30724574604d30a2d85bff7f62d33ef430821fcb74bc4ddfbb9f773efb7ebfebabd776494214bb1845dae08330df7ab7dc7be355a262f06afee41f74f4387f00d114159c93670c7e8842a314b3848151ee22176e278dc107ea2b1141e622a6ec70999ee9f778cc638d818c9a4e289a955d66750356d8c646e3ce42cf0214e986e7bd1b8a8b95002dbbc4811a6790232136ac33e38e889d188f0a1280a17334cd35595654130c5a9d48c869026bed48c979af152d97b103e1acf66a466c05497092ea6d1d601cbdc1f690c6c87e395b9414c8e23d5f6d703936377cb6bf481c9afdad563f2bbaede7d4c75314d1341f99906a51a8cd6905093c78fb9af3a73df470560a647f6c8ea3e715a263f1bb442eba465587b9d2c1729988cf5611f159c6620e9b5d3ebeab6ac6c3b36838197ebe2466dfcc6010e40296a4335401900821e2a93c3110c6fd4c67b0a3e2722448a14e190cc806ddbd333e4f4989999b50984da04055058292ee22286c251e8e400036c632226e22729286e681540b8430522e810bea1202127344e34cd3d01db5648a8899290a48bbb7b555553f66b20e9d40080ad061b38e0520310bbab23c6a7043a84ef46131a7aa2314dccced41dfc41414242397c3a9d4e454551a250494d1e3973377b895d0574081f7c7e5d2e2e2f2f303537dc198a351bae88d1b58fcfe3e0189203a55272a8ecce0a41ddbc6abc82890bd3fd86000dc374bf2da0a43516180d816d27d4699f3323703398579baaa66da3a0408a2387ce0e95713cd4e478a2b041a6734d9ac09c50674e601bbb2017238675cf885b8e20c2c47d69bd04846387b183bebf0efaee3e04feaea43073615363320bd853e1832bccedc47cc4d21f9410d242480b283f509cdbe0101fbb0b86e3cb8be68af5dc624fe7fe456fe791ff65f3b930f02b1f5896aab2bad730ccddbcce9fdd7719863575cead7bfbbc9d332bb373ccbcda0e33332bb36bc15a67d700d8c63666deb25619552765dde3f3d8afbb338deedac58e0d9f7784cfabe817d67bafa5e9570783edebcd5a6520f4ded8cfb5eb6fa7704af9fb98cbac9bb461af99661f3412218470480adae7d160058e073310f99122b60842c2c303173f4158010f5b502942944491dcc1922e9a54d1c5130c3006aca74ed10591c9075d3cd186740125092225446a300322b438a245920dc8dce732401865f7b231b8549300d574360603d4c4598c262f99cca2433cd28177e7f2af940256aa02ebe74b4d2f231c56a24861e90f48489912450b213f54621005c67b0d3a8194a94127e862eacb6a6c357ddae8500485a5038c11f74cddfe0fcb296408172ec32a4c09218410c22020844f4addc27237fb9dafbbba2eb391f79c70c2df69d7775f73777f1d3358da74bc533f3fa90cbfe7ce637c8d84b5c8708d85e53a87cab876ce0531e9288e080b735e776b672eb2d6ceb96f6e02e327aa6ae0fbdf85fff103e27929e60a6cfbf8de1be2ac192f01ebdfddcdfcbb5b477392d7ef515fa639b42efc8659103d3d6329b8a7d52c805c6825444586869c14a181326099b0c412459ae05004b6ad90d0154588526af386869ca4b630379da2331dd217b06d8584ae48a9cd9b4452284263ad3252c2779aec1f137a0b163688bbc2ce921aba055878a719f705f7f71c421dc23f7952852a40010a4c60021290d080b9690dd0344dd3340568cd05d4a4b50d3b9a8f108478788444c1644811137b647f36898e0ecd622a16a652406b58244ba653844da11bd514217c9d18e1f2c408e52e0f0fcff22c4f559d2047336c11e5d3622c2dc6d262acaad262dc568eab7e107655599f62ab6a29a5bcaeb6b690a51baf2606520ca5d186aaaad0ec1b737fc75f2e8b18049ea1a5a8c1080a8542a152286772989017f02e84259e6ced0e0d64fab315a55ef2922069a84b357eec355447d5b875db31f78a7db590114c6af3def3e7fd1bea0028140a8532c0d2686035f7a80ca8cc4a91496db897f939e66d752b9b951cd0c95b6b8f394d406de09394308592b4484280d668cee4da5d8c1849d329801c61f2db080a0a7a95d912065e7a6ce0c6f0a236ec2f4a524db57d8ff1e5a53be3f1638bb83406662f31103f5615b73f8f511b864668fbb2022f6ada1ca4c9e3c7644c4dcbeb8ef0d81d82110f0d63bf0dc86c87fb63eef36a0c765e6776eb56de4329af070584fe5a922c55cdfa8dd1923700f568c4157b0a96b8e0744ab91a949af8af203e3ee48e838367480e873dc43804db3468eaa032ff026cd321140e1dd446a5081eeae66d2378dda81bd40deac6279883a8b48d9b0731ab118004100053160000180c060483e180703c502451f914800f638a3e745234140844418e03298ae228c618420030041963084146a1a14107e32dc3d1252b309dd7fa2bc0260079069572cfdd25f0912778bff438acfd9fc4976d00892659e065144eb16a5cc5703f884abc36ec39236ac9c68d22f6e8dfa9c3a2e0689183a8b9b2e418026f1413a23a74a79d2f4fc72542a96905712f3deb2e4531df19e7c0e10e287f6d526f88756e52b727ec51c9fede104e2c3f650d0d090a14c493773849f2a8d8e49a7722596df977ff473ddd6bf929325352d82d02c772d2f10a1ba7c21bc6f6669c807882078fcaa7754a09af00aca0de2b5c291d08c12261090a0f716772ee185e9a3f5fd7820abd449c3d71a03acfbabca4a9aa8ead156be86d6c98de91fbfda227167382dec6ba64cac7a1273009f2c48c16793ba387921bf88293cc582e19e2e058fbd918441e4d030758968975a6bc2a24ca886e9161b84f4f619fe4cba73e775f2985728c8d7cb80f84baef87cc221d1fdffdc0e8b36261c39ef596fb889522843127c9f02c5bf723e22d9c77103d53a8b3b467cbcd70d441bd9043c416a5b9894e80a28c580a7aad37a643bc85460e67af37ace7611ce4517666e2d1737b287fefbf53b37ddab7920e087a6ada1b2dc7a9222179c8fb465cd2c27124757e6476fb7317b7b070896f2b2a5725683da7ebd52634c1d0d732d25de98b488423cb0a07be5e440b8c3f7173c48ab255a8d4f08920620a0b0262ae3858d66f92bfc215a462f4ab9ec066a74c32c4411c77cbf2f2af5bd97ca2a0b8a4f34ace709d8862ad4f5c15a5f074acb7bc720397ea0e50f9b45f845b7fca5925fe750af5e3c4e039c9be96becf7affca4afd7315cb64d276a998bf5f7aefb6d64f70c4e0497604780aefba6bb9580ed4470635a02ce349bf79a2c87fbf87fb17def7793ec92667a06c322b5a46ccf986e89e52c221293b95c33d2dcf5cc8f9fea810e38a4c257d615f359989c57ed79212a9e20f04fd1e045dcb9619677c2353b809c48787f9c1f4c119a01b0aad3a8006a23623b173de268a308dd1f286643599a366c277fafcc6b304e80fd667c9474b61fd6c82a1d998cf082bb8c6dcf29da4957f3286bcb93f6e6d497669a4a4084e54222410d0d1c543030fd0e326c4c6903bbe3d7731db407a623a41a3d7f5b6943a2bb6fe63d2621e6eafc9cbd408e214fada0c4f7c6b7003b9e22c9ac29d851bda1be502af396da6567eef582742cbf80bdc1756e1974d332d2a90507934be2ddb9916c97c88fae15befd0b1acaa06cf0ae97b65ec5104b13e2b729a5e9c117c6503c78c55cdf8e2fb3bc1ae62becfed8a0aa76989c8e6e84e9bd4a66d308826a604d0a170d1dc5d37e88f9a1937630bed3ffd4ca3899990f005511c1d3999bf833e5ad7136548c3977e96c1f9fa6a22fb7f72e12fbbf9534f8bf7960de7e0b86081a2dc883200ef0046000d7b5424c4bdc443b374118dda2a3008007b99b625e52eda4b3d2180355a9591ca6dae395db38bd5e66ee8ed179ce12c97a46bbb4aff9a361f481487b12943c8e7bb34c577a180145288b34353dc659e999fe6142f6002ea0437a472dcc4d053ade531d56f2303a59c22a6ac0f213e5cc2bdfe40bda80bb0f41c4d73525fb8451d0c2506db1cc6a1c0948b986c243423d52ad65169166cafb029e770de7a25c6100aa75cbd122a5a29d5d2b23f92104ab1bc0d8569282a7dcbffeb66251c490c005c6f1c40ffcf6771f2230b80abf8468a92bcd8da023a0fc26269c3d574e66c9943d7c28614119e4e0a7b71b54a4a2b486bbfa9b8e6c996fb8cec6248db170f020a560765ae9df90214c5b1623cb33588f87c01effd151e26bf3b45650d0e2b18b0b1cd23b04842df3e687fa28de7aaca8436702c7a6e166569adf90da6e9aa1c0fd0c65f0b64812a2d1dc6a63924f3d3e2d13d648d97380ea56509c53526b002511e933543b46cb09463ad4bc5c3e4b121bf622f015742bf76835417224e5ebfc63bceb42200f80579eec1fe471a30905bdcad33ec7e36a33b3436a2617246af64f9543a422f53369a5a6e75eacf6b57307d8410b81e239cb9c130d45d143500ece27e0253baaf3f1095eb97acda7891c5755725c9452e5eaaec3e5fc740e707854b62185f74d26b756a599c8d82bb8126b28f0f4943d59a1c5d415d5597bc2cc0f643055500b84a455a768da16ee34a57696cfed198c89b6c509e0e2635c585229404e76f7147c5b6cc73d03a083a78e2c7ee96fff9aba14e925e458618540e171d3c929a094f3c396483ff9632e1c5ad8bfbb611097a85ea23fdfc13214ef0ee3dd06b67d805306a588029dd59873a9a84882ad100b799bbff483b44a54cf4af18961fe9422b20d9a2bfb20955115995842279365c97d77b145dc2d39b4431b753ab06bd573bad8a76b55d65a779238ae2c748150473a9bf3ce516a3283b03092d42f17d0af4109d54cbb324fe109a8998e40bc4341bb7fb2724ed875bfc04aa78cdf12fad6e0d73bb8370f3cd5dea055906a1472cf2df66e9f801ca39d174db1de9da875f416b7b1951d55a7d0a4681efbd7843f769ca8a55a7535f6dcb1df52df8ad8e82773acf407d7228d28b92851c60e2df939a7e5143b1a3eb0e8048ae7cc8a415bc276c6996d73f790f9f08ff254c4b7403481d0902e40acb349ec5f48407b05d5768d9fbfa0e5cbb88be68dbdc59740bf05ceb10d6785981635a8a871956b99dd85d25bbd2e8f5e5b3c44a83e1edc5b9efbfcb9cf902dac53f413e8a43b0942f521e2d6ab2ba237705f6bbd5d8d797e3f788b6d03d87360c5abaa967e74ab22d93fd226f5ca5b9a19875511abc0d883fdb7f060e520ede22391ba4e86ee181bcc5a1347a63ba29968ffcd16cd8c972382dcec61cc72083962a8ce45efad0611d940195a9b97e0b8a162d6e601507107a40efd12b09f455ff7953a34a57137a9cb7e7d95b8dac2068197c3786d8a8a11ba05213fe306bc99c070670be8a0926ef2a4dcebe3d09ac76ac206317c7c4acf6547dd51313e7f979092027c7951daaf7b24298a116736cfc1a2bcf90a91682571c325595548ae8fda47a62d8eea2670e7dd1a5d91c7abb4b299436ef807641cf19572ecc6fa4662979e1bd145efec0c26e39678c4d6984f45e143130c0071ae448a36758acbcf521560913c2600012135c879bb8dfd75afe6f568c571899c22744fdff38d2fcf8d72b90a691be4dcd0dc71d32953a37d0ae133ec1607304c42cc103e2092858d5301cdc7bdcc2c3d4090109071fa09e20142cd062af13d09f1da11e77a0711b97d0df886c21df7d4f0f23f5faf5021eeaf942bbb853d487048766646b481242ebc5e74d5ed648985208dc9c6a61943e7962a625c2d705fabbf840893302d6346f729c087c7f9d34ff97cb9ae3db3b49a215be9951a728558fcc2564dba3cece45a34799c181b5e2c6f7b03f98984262853fcc2269c93857a68ddd8a01c8a065737585202aa172cbaedd78228cd8f54d2fbcd24774d906e64fd8530170dbff84127f5ffec779c7e2a6a57dac009c357b00c9d63a8cc0c30ea20b6dd2b8916eae0f07db64199a8eecf0cdaa0113a8412866cb1336877f0c3d8a8800487888e03e0b0190ab936dae58cbbf610e0ab9e835963fc570b902d867781327c76d5d876268ff6642042c59415be738c04c48f1d46aeafcb171b9a898ea95bb7d2a142e4816ecfb96e5e67eae12f7965312111560fecd486b761bcb42054664986ac5a6781ce7f85070a611eea49fcda12684238fc73f90dc2d9c46ab11fddd48fd7e86d3d8f119ac2936bf1ceeb6b1424bbba8f0ebb8d1dcd02442fb92aaafd9eb9c028526a29d5fd1d34b5ed9c150cf915b81de6034343ff5e5ec45f1cb8cb82e2a0085772a728112c88b88f98caca38228e0479d8d2c4431a44dd7a4b43f513b037ed8a1167124230bb62434bf47786cad4b5ca2809ffd7af6a6c700b042d3c1d74e0590193f4197f65fb37885d86ff8a710cbc451f3cefb04bb7e13a538db8cf9cea37423d8ac44e016d8cf84c44b9f63f01b170b0a8abd869d824a399c46240043293b24c567b36b5a1df40a3119d4d17b5b2f21c70fbae7aa256159a113e1813f0cd272c8fa1edc833da0ab3910523c2577b1dfaff6cc93c5ac8f5ac33ef680c14786c6fd600f2dc180b9e96f63c896e35e43e13a60b05224f8cf11fed8d85b859e75d0341235f73e60b8d385efe0db1ca614babf3fdd073b7ac3eb03e5e8cf34db305188031b1899e9f30148f444074691256d7fdfcde7e7251e08697175d25b1a2cfc6a69e63eaa08a3bd3e925513b10c766c502eb616e5e2a34545682e7f4b12caf7d6a41c81197553d3185bf0fdaf187ca54fe7cc1af5c6463a41ca786c4d60e76a2cf71294fb0ecdb9da3e2765f679e3157bc48c7b684116d5e76a13a0474db2bae834e93e1408944a3377a0ee3e48e066e1a4b6d164a1f0dad493a8bcda73c8832d256375f7c8bbc3beecc3523ab3ff53c4b59bd5d7aa76a2d617df64d87743d5601cf306f1edcf67a742b2462e652fdecd1fadd5ff5a188847c090cedbe91b07f11be7a0c5ead55724fde695667b2748da1dd4b4ccc16f3ac9d1d12e238451e02a6e568299bbc482a695d7907131f1ff76e69876570e9fbc590547d0a578af61018046788c64f6a36c1c683f55c665e5c597f3f3c04149894547d691cd93a6fd3ead19c602d385a55ae653847f812c071ca623b58cb4aeef98861a58432e499e991c33d61004b102443283018bb8ff7420a1e0a0bfd955e79fdaf57c438b6687a7c382a93a16f78147fb2087a20066d0cc3645e4a6c0a7b6fc3428af50826441e7bccbb84c92567935caca940479dbd78b569d9dae0d693d0c83ae597223bfe23024292c36d1e3bd9d755fb4d7d59db78e75d9ab5c06af0de5582791b223606790bbaa6169cf38fa2caa82a1e46d549938987a9ec3de7df22b693c620f3b885c37d2a604e78dc26796016ef5b81a3274cddff09cdf898d84f9e1e990026ffce732c37cb35fcbf8f8272c3613b9a2b7312caa9b7556e16bd7e8ca811c85d8d59d3e358c1b6f44fa9449687a01a8da8878ccd2b73697f225667ecbbfcd166958d2185041ce9b03b0908070f44e9827440c7aa87653f354fbdd4589ff4ddcb27cffe4a0755aa4b1fd51602dfb3c842dc1c0ab78b748c7afd08d1d5418d874921bd0da8b447839aee22371fa4c2ca4a182ad249704d86db449ed91422fda4311018c7da08645f46d7ed8eb207349a1cedf2e4bed7449c5cf5615c16b887f186aa44e69e0b52cace5e8948b190b19ddab7a7fa22a7778589d3e7e1964ad2fbc142063e0adde6750c238a40d39bd93e3c8087822c869c4cd8e12c6d075ed958fc7e06a0172d99bc4328fcc952c60069e14c70427a91c43d8bd5c650cc874836224f6157563aa8af9ab1e1536d1e6967a2d8409959ff1e03b6ec44f46ae70b6d48d1ce93c1b3d38e1466300a3ec8980e9551ecfc90299124b957dd5062cdf648786c70d8c6596e8b57d287eaa87aaa1116e406df1708a2a03396be27ef79338e1498a8e82c747b099b2a791827cf97bb4b70ee24d612db8f6862a8bdc9553d831114be17ba9a1223d6f919e952174802a755753337a7a7ad63e7a2d9e3a5d723a2d530bb703838b20567f34101fd1c40ba07e60ed212f1bb6b0c89f963bb1133d59dfac88f2eab5c7fc146f51d321f830de09760ed9d8afa6c4e50bc02bc560f6d2f026065cc14107471d3b40b4ca80b76a9697266e6a58b239236ee06ecc25cb4bc5e353bee892da74527310810cc6eb7ac3f1f40c3ed7cba8ddf8e270ed0c53966b98797d06c6e75d7b55014c315617ae4560af12e639aebf60232b7f852ec9d479010b3f328affea318075f5731d1f68f4066060977428b1cc935a8a9681b4b144c394f8719e80db9d01f23a18952387d513181ad082e87264ed2f6ed18a4dc1533d0a35997712718bedb972b873eee40a2a2df8de6739aa9e322bdbc4fb71601e03a5a5c05da08f179dd5c2a1eb3076383d052ba2790b19d992f1001eaa70f61f17adf456b6bccdf0aae90dabb80cb0bf303e5fc1ee1fd129f74198490dd877e7eebf9a2df0e6032432bd0699f0229803debc0da4e58ada9f4979551a51f5a9cac181db016d4dcf17090910000abfb5d7c89f35f803f2af27259ea6f8c3e8c2849e2332697a89d5020f46ef07cd770bf1540fea48ab0fd336905e9c347d3379fe41f248a388bc004a050d072f381513f64f7843802f865ff3fd9805a10e93685d59403122f68d2bd8871d5157867e9b7635842690f6532d70b4cbb22b930548640b9cdccbd0ff1e6928f70d2305472d6a13cd4513f6b46745ff86f54318eef2eb8a023ae06211e0f6923df53f7c36574664c1987ed1df5c5adc085e3e110ce91cb791d1fcfd9903b38d3d6615d3e90e75ef81b27de481e9b454dbc9b4994da8b4ce704616499645e019c21d90d0a3daa60760832019816978fda118ba67f67ea0f930fda00e275eef1c1b17d7973726cecd9b75308f9631508f0ec34f606cb92a5d1ec3dc747e44b22fa88827059ae67f32d7beb422ed85948e4bcfa0b9ae1b7ed355a3792101e67619c9f2cba9c440d7f1c51adf1c21acbd7c46982c09467c4463fad0175f16ad4b1130b9ab20671d412274338313d0cba4caf1202674d2144a9501991d4585c8ac48d00cbc0828bfb15149c7311e1210949f74beb65e81a49403401d6a669621b52ce362a8911f04a60ec415980b78fcfebff641e093465091403aa4206bdfc7767b97cc0c8e6da82e97226ffd6fd40644aefec499a666268996d2ee7f27f50c066d97d2118d118b5f754c83632a791765e500002121ba23234c49e17310ad04be4592f31221ea5faf4ccb80af8c14befea77e4d3e1f1e056111a19ba29bbf688736fcfd4a1f04cecbb170dcf51c542ff6a3df4610e173c2d79d0708f976a75f7e1e6d8476294a62af717c92afe490d6221f5d1cdff520681516f47161480fe89c043257c60ac8635a3c57db15fba1d3cd8d71b5af4b121fa3c30e27fd2562d953e22343c816317e90fcb18784bebf259a8a3705fb249c4a74f0e178190bbe24cd352775911b53f09467fbdb3de80c9424a1f73b15e82895886920ee8147b3d1498a4a123d3191e680cc0aaf7a3295bbc64eda3f32fc3aa08764c2492ec5d59ebf4a002b2f2f1d42a6308a00fda7fef23b5c892317b24b00abd135a58ce5701c5d868642238cf32969f264e28803ac71f5483212c94b66bb0b58a719a181025d17f1cc381b9cd7c3c877d236ab68be2e5ca94cb8a3847b7da3a55af18b0dd460742730c790bc19130f33f6ea11019865b1ec1937ebb98378a26a4085c04b00b21d1a62b6b8e116242c5c303bd0e5971eece4e54230ba9ca99cee962b2e89319da81566eaf50dde148327073f93e9a275a665e938342bfa3843675048d0ef8183ab4bc015775e11722490a02c399a29ba427133a25b618f164b496807784f5d8a5f6ac49349d467320e740c9eb6ea08604874f07b906293e8a71a855298917b7060a845e40401ab40ed74082a31a97080605ae7626e256d4e8ea3bf68987c2f0a7b5c653e6431261a0b3266307673cc330c8fc84441abca6d1a95707d621a2aaaee762b0e01cfc89bbad795fc71b5bfef138ed619cffef71b9d05e2c8d394954c5c21a07be021318035bd3f2dd890eedcdff3d024740c9441a4c228651ee48144a1610886d52f07d52f80046620d17dbf58830fa30d1e086cbdde0e8798379629693a9b107643fd51430f5c20e85f5f16084b0a5f5971dcdabe0696c0fe41e1de64eab245d2a2ccb4811b2b2a5de54b57357a9a80a6bca9a79e1193dd1a9a6098ecf8162ea7b2c26a21484f08d32daa41b3b1a1d9826e575bafb7eb5d74b28dd46ab98f57d2d3712fb55a5bd55b955b518b0fc68ac7b9878e61a3c69c629916a6e6aa084e4fc17d47f48e3f843c7e8010eb6c8a88d184400f1bc4198322c1aaf55863fddb0021d3b3c0cf4fee7b2c685617f49e64884b3014e836c0e02a6c59516affa8446bb119c87ffff6b2aaa2fe3cde9a54204eacc1e38d49f3dfd656dda91b5cafe586f5655de7e4658625b600c1dac54176c7b10e81a2dd75f2a25c9743ec394c8347e10a225c2e0e5c0c26a6015e6aef8bb078fa888329d40b450766d639c041d3fddc1048f99e847b0031f09ff9acd3d97455760c55cee5709ad1a40339f879af2509cd68290c6809d3d0182c4cbbd8f611a40eb62ec1c72557edefdcecdda00e3134b5b8e41fec0a003dd2c3506b4bb2dbcf588398ee49040bab7460f1bf2af4cd0d061dc7126e54988505e64f596be25fc828a0c9f1b62cb4f1c5973f0cf9ed439a4dbe68414db052caeefe64008b2ea722b080ff3d0fad241241999f9468ac3bacc28c2776644250268b7c3a4b3906b4b3350a8d141023797459f6c02ede1b74b98db06bf44e30822aca84daebd80d82c32291cce91ac304a80b4869dfebb7151493169da0c419e2d0edab5813aa2febddd1dcdde643e97ebc2ae881074762fceb1dd4ccf9a57a03ac831397509fb913f02663d6ed03c2e03a41aa387fbbc62ef16d214744bf9d96df55f78392a4d25ccc34c0aaa7ca7c4cf3dd47f508391df3e839db57f7b93838c3dddeb73e8e75cdb8898178c868e325028a86eeb271394e8ca2e9b12fb2f172405d6322ab678a404b091aa52cd18978cd32230fa2e4e6f04ebc34faf5bab8f35e4d11d6ebb2640094da629fae19de32868d75ed825f8864108873194c39ebdf25417c4020d0f721ecc4ef525f3d36e4dd63d62a0b878a91a1662315b440f3b1c0d04140012744991c2f3585dd68bf73bfdc804e394ce17c9801074e8b270c616d0160dfb2e0e6435d3020f45de64f1fd55e1b93dd17ba2e6e4a2592351b44598d732114a764c45c895546af728cc919bc4013ec5dc48094aebc1b477d396c4356aa4cd233dc3dcd322e69295e6e246af8bdbf50b410d86136e0e38c5d2ca1b887f07a99d610e5f6b8c4ce27793c1358ab34ddcf63f2b64b65c6ed06207810576fb591543600107e80e49ca619c14936c4c029fa50a4cc2034bc398da83ab6968452f451c833f45b3558207004dc060e77e437e06a17f2c93d4405316ec26c3066b6c828abeeb80ca85c52c88b8ceeb1012513dd2e298c5625a4edabbd1e8b2b495337085df65eb7f05d12e1d03e1234721541f85546141d233326dafdb85a93ab615864e96c46042f700e6b4e43bc65d454b7798df39359e2681b8b6fcdb223d2723449626f106fb9187e043e9aaea82be91247708ef1111f31a46db2788234ffe29800553e2d3a7e73ec9fdf41e6e95ed35c97a4e28513baadb0ddd952cddfa0b91002c46a7a82d802dc73f66522ad3639f886d7aa52f32bac2f1564b819049fd87d7960cba44dcbaa5453c9144df9d684a708056a596309922cae60b8c123b88fd951b0859816791cc1f7232d01ae4fd5d3ab3bc7e15dab9224dc799c934e89e06d231f5bc475a685d4053a9f3991434416fc83b103490cf4e1d882e64fccb6d4aa3d4d19cee4f91a11002ef25288c2c0085cf053b4e7a81fbd1df14ae177f66382c271f78c0f605635d6b53569ffe09b339fa90e3ba63c08a25eafa97f5c554f65373ad254c359e79ae98404b5570793d20f9a92a323aed1aab908769de5c0dfeb13a808b4a58b832f4c6e2e9d0c890e8aad48e063c1b82e04c83e67e34f67bbe963404148bdd8cf6cc0797ae998c84068f3bb3dfbc592af6268bb39cda9b9bc55513933ef62ea18d2b69b4bb5f9ae515f718a77fed7363393fa1097a126847bbc556df26932a3797968dfd63bc928f7c73575c72fcfcf38e70a93e80f9823096bf7b920c2e2cf69be33486e7c90f78c01a75a0770ff0ea613452340755cd0c83696f19beefa7988f5a0772eca56b3db6b450d829854867462465d88a5b278bdc5b75a2dab07cf2a47b4bfff10bce98576ba64bc72cdc12fbd7cf0e675ff41d002582fdcc5ff80d4bc476130ac8fab9729e746a2c9a23782bb57ee515d01f4036042df31593935ede2305d54eb2bfa4cb64aa752a952f60aca5fc8d4fac98175cd697206c7e0961674fcb1031c28b2c9ae9b2ba8a8e861a009cac598eca6a72c26541f219f2a074cc6b88040008d5234f704bb44fa1a28f8b1a4e0aba961da23eb93ec452e8b1ce6d450c01de456eacbedf8845c0ef1ec507aa684d572a6c6d510fa3569066482f40cbccb6d0046227e45264a74139d3a31106d13764b6562f82fe0673351facb4ffffbdfcfc85a30b99b3f2ceb38a6858a7d5006283ce8d2f9526f7eb942a8c886736df899e7c8c6388c0600b4479ff9751013380d0c640ef9445c2e8a9c8d86e75f4ab0a045b12cdaf1fd0d81171dc4396f2b88149f1d6a714e58857f04b954be2d48cd198cf244150c8431abfa02a551d16db246199ac36327142dfb42e57dd990aead33bf41764b8161274801148d3fd506baed5aa18a3c39ca078d08f8b1823c1e0ebf83af0c5a5697407857d580e32573f717dc8a0325c46a8782e7f10af319c230c422a5c6d4f23bcc18e016b09fe90e1a0ee05072fa07e799abc40b7ee9ebc2ce6d5d835b05c7e17db5c1c813d05ef2d3b9aca1d3388293edc512f0624b95a8ca8a15012abf54b67348462b4efa5ddfb56491804236ead4d9d2ec0c1dfbe85ba3a47ed59b12c675e19308b515132b2601bd62b2a10fdba37550f9451e2bf5c01bcf6e45fb66439bd2239f2773c38739eb5c119d26c9a38d6c7475726dc8f6db7acb1bb48d5cd055a7bbf87b22d3bbaab0937d306a408b289155f748d63683d464341118270e86b631c2cc6448e34abf3904d6b27104f80e8762c398e39db084559ae88c3309a52a999bc40be1c09b8ed0c9bc027fd753935ed3cfe4875ec0a2354aee2fab065e1dc10b9f428621cec5c23e4b9058701c4efcb1d81fa68f5a6cf5b87415cd1fea63d5e8b74bcbbc94593373e9aa8552754554da69cce451d28770459d622b7eaa0e057f5a5b2174ce039c40fde8eedee81339981f5116e4b27de56d4d42d1235897cce28375aa95508b14cccaad574859e1084d0b9d581a637c14aa8b132a66c03d1eadac01a2fae834b083c7f606d911c5fd496059bcab9ff82c12da259eaa2d115f395c2a91f91ed154e22e4aa1a8e05e4527c4a19943cfd1c42accd41525586a01fff8e66fa06c876b2e80be2c22f1aa9c2a57860e18f78704999a83257e5686b197feb97f9ebe47494586c34ff91b587b68ce05700b9b72b94f28f1522f0f08cd2a1be22a83fc826daaa20552baf3c9a9abfa88d7313d5c99767482814d9146cc431edaf7849c12ac1a6f7828737acded992383d8622847980e30d116b00e70eec3043c839259601e7378fab8cd9ccda075ba245fb6a1f0b62cbfa3a69b8c1ae753c677f517c8e52aa5e8f861e54264a2c43e524a8e36d2fbbb8b949adcc2ced9dcfd09e3d82f3fd703acc4077cb8292f5ab61cd830c6d43be9855461ab7e2da48f233d53df31c37a058045c76ea4c7a2046f572f54873bcd2db17e78bd488e4af2002949bd5f3969f7de5f7053dc844a28702de30b8b104411c2f8d6bd527ab3321efe83947ef3318732bcfcc6a74e43800240fb0f028e53de04540b80b7ec1bf15605707140b0c57eafc8efaff8182bffefa5627551462394cd8ff91c3f3e1e87fcedd3cb504cc49a93cded15f9e2eb0f0c27732a2cf846c6cc77da5b3383d3c2a50d9d87b4ed94472ea7b55ba2f2ec0fac5b98fba936aab4bea63e791b40bb270775d8497c078d046d3069d28dbda02aa3e6ae0663e83bd102dceb4464e80d0b8d4bd19f33568075f3d508c1bf060ef9bb8a4caa0d15aa6ea98a120a1b6a6ebf599ab1b74d526edf7c9e00851465f26843bac5df8a0a3f604aa6cca8b088ff5859141a66dc6c506be24347086309fb9f161cc2b035475a3424c0ef3eb0f70bce43c280698aeb960f58fc608e662f4cc046af85219b88f460786c5b78f97aa0fc8f3e34537ebf86a2c0b4f5618d042b5c99c428068c3e79a9c7997d35fbd35c0f93439e48f022875a432755d5cec569a6904097a1a99fc1348413bba428fd672553a40c595da1e721838e0a547b6ab1a1f1a08e8d4265f2f9273258f60379ac15bd15077b8656239310758b422ac3667d1b14f4c91c88f6075e742a98b5ce08d1ce57e2643333ce97e42a76083f8f2cbdc484a152ddf43f9b26368cea0cc58987dbc690527a80ea8cce61cd8db7774a6563a3cc92983ad3564bb0e83be9b52bdbd32aef41193da50cf1e1a4d755997a5ede7288e4d48f43876080bedd047309cf83d2f651054a42719f599343978dd444bc6d0f3ec99f30ea4c0cd2de5e2f642784efe7aa4d595e3d6e3c528d2c3cb1f184132500605e51c9e2642a5db48e116f650905e49ed81373d1e2ed86726604938149e5f33a7293a013e9bd0d43d29f594ca0536166e33987dc61db4e0917329cc93c2833391f398e6a4c0383fe4a1fa1f709d9176bdb92536ab646d751e59dd02db2d7a9e0108409e7170277d5f858dac385bd40bce6e0886a7b4b33bf4924bc1f2423e3cc5698ef272edaa02bca8cb9000e4120fe35b43e7e9c801c0cc5da4a93013dcfb665d05f5a954266e20a9705b8799582f8f8cbf0291e681740caadb6cbcdae5eddd814e49c080bce664cab1d8ba040713f0d3849e898d7e36ac411919fb56018b80d403dfe62d603ad6745769b60d0b6046e9d1b4a9c896ece6ce644c2f6b922273abf60e825b639a8034238450f93f822f4d96630af25b68db1b8be58a8316f2313c5f09c6ac8ececbfff4081a9a938da4e1efe57ab3cfe69115911afa49897f0eff48f450c962168644b1ce7796058c260570ba229b8df9f7e85286cf1d38d5ce78b4a9f82d4b448a0124b1dec0f47e0473a5d8d07d9c13f42734ffcd29c450538c8f6c640d56919fc7f230fdbc387130c9fc8eaf02ed16cb74cf7a4eb3816371ab3192f6d82ce8472e42653d22a5fae0ab296d935e647c5a1c5d9909ec6528cf83cf7a4d00b0f74b0e4355900ed127e64baf6836cb8c18985a0bb48b28e2cddd0c83c05ecf088f45fdc814fd915e9207075dd8303281660411f70733f50a661d57d2dfd5a2a296313ba43c7571ee5c46e464826ccbe1a6d8726b67d0a4e56fc2ee76bf0b85c36dff559c1ecf33d4a32b97f81ac0fb3a1ef5fc8db98723f4cc5d2aff315ce2e0a4010112fb97e9b527f41bd67b2a66d43dd3586207f1d654948151cd44ca321e756c5cba9d44b9e8f724344c4e94814d82314394c28733b4877013d1e6cc237e4d74101c84f49ba9a7d00dcdb668b3c3b507e900887d9115aa991fc797609217b6a3de8f7bcb078659a668ec21166c538fe894eb143b3df42cafec0b8a583182ba2c8ae314afcdff3b215ccc60a21dc18b730d59d0d47ec0245c60a65e5440acc76c0dafc93ae373f8b6f56274e079d995ea79ab561fb4c15a5c307b19a617510b3c20c215e3da04ae61b9036685c3b105026d02ab1278da66627f11990970f347d22e47170894f02c2769f5aa0d5f6db46cf3ee996c68ede1dea816bf49158eb9a3a68ea6f613d4c4edd46a9b295637a4c6b2c8c3fe690d61090ad3aa6ea10321e6b559d6a453f716bf19860ca76fe01e8d8e794643beaf0bf322a0771494a2645a95016531daaf5c7a21b4db01c558749e3e159d952b02652d5f81a59fccaf8ae3f280e727c965e1d3fe67ee427e1fe64f1aab09032d7a09f5aaee06c2d4a34a897c1134d9a220e8e0c9b7fb935ca4401aeb6942732c99b8401ff2aca2398249c590f0cfa0d5639e22ecf1aacee33491a5a4e2d78882659dc06d3f0186b09ff8f4bbec1b6e161d164c9a8e3cafa1ed2278a2c8b283a85988884acd6484b618395181c9ba38abe4f7c57aef001fff311fb36504a8b11e9ae46272f810111dcbb5a62478691b84eb9ecdbb514a69646b4ed801f3e8b78e913b89de9cd902244d9cddf12244db728227795977a1814d065e8752d885edf1b4d65d427ac0550bba5417281eb56c05753720dc997c56a08c44a1332f4b46569e13c0568187d8cd7ac97a158c5720167da760119e015bdf1db7d893e0e4a5e40bf0254db2ec91155413d9cfd912a0242e6d27bb974a01444d99436296944c4dee492cbc1a086f250db52ccd47cf478b501332138f086fd367ba991573affd6bc0382975ff0bd10eeba529c6d9dfc12d987ebc49b7954053fecd8d1725f77d75704fcb258c836faf56b7d1a04702903b5a0a7c4c217feaa9b2dcdda821d7eada0da9766cb4996ebeff3fcb283897ae568417b4bda3e3c4dc729a39ec3e4274184316e842adf06e6a528231d82d3c2ddbf9fefbd06f749602089161601e4093f67c4cfd1e8aca4167104346836752cce382ba0868226f0d9428dfce013ed1da11469c158678cd9c8fbed77cde8287702e8bcb3a5f0b12caeb141701747caf5a6d9e694dbb6c24295241a1aed81a7d7e49e22fc0e4a22756e4548e1a222f3da9d318a387db1ab0d8e54a62bc9d2d48a50e98d7db21c18322e3032dfa2d5df302015bcb687910ac3a23708fb32b3128b03f653dae55d9b7370de05305bf6d5e54ee9488f2e426ce857a5fa9b33c3863b5810a1dd8664aac0b703439ca0d038c18b2c63fa24ce3575b5c4f42430088b140e7fbbf1420712f8bf92043c01bc522774d50a33da91a2f22e476bc05dc5d8a308a9829357e1b15477e171c960dec328cd521dcc3c8330ccdf52910b11223069e672ef024fe761a618befd11814951b2015da51ed54193c22d1d6836a6d706f4a42af4ab275ddb9f6ded2339722c9f3fd029b5304c30b07b0fbc0dd092a386b502f802aafd202f554242caa1059f85c7e41fc1d1b866b4349facaba4255b0f849b225b10a513d496c6ce70ba6cbdf166992fb329cf1f5ceed998770d56174de6f724881cdac946eb34d1c506f8ba86cc69b97d90c8b2a0e4400b54debe16e69c6654d2b9f1d36b34f80272169cbcfeed61c5a22f146da42a9b3b9411cd790b2a8b569c7554557e55e555d10b0d0474d75ce281cd59fa3701545170407dc76070bc03e81f6367175d54da3f9dc3d10534fd8869fea87afb0c7f4c527066fb025c2ebed3f5d8bbae1082a2cb5acb1b7bc659ec2ce0f95ff10d8ba2cb1a3ffe56fcd2e4eac18b5216cad870c9b81f4c29acdf211990dff82d7ad422d52e7f8d8b097358a596dd3f19dad9315fb5c51f1f9ed9a0095a396d7ee36c07d1320aaaa3df05d120362dd8490ce469a55b89b0b1bed5fe317edfa1c0e19106046396ab0934af72be7be909835f651a153c2bf208269ee4270067ee0c6dc0e66a52c7340c6d86da2e5dda9dd30d8f7a34e931b7625a30efb196ea2b8b1a1fac2d3250791c79f3ac92a92684107218119ef83d5374e4d78538b497f7ccc8425ea5cd1b7512f0ee1ba2bd1c5981b9f7741164743808341b91cafa456038417a15c049f2169a44e87cc50fb549a62cdaf0cbe85a4bba4af70adb5d479dca07fc654ad1105120b877c4d66f979e6149a7ceb655888f4b51d4d4f5e6b3a82e00bd423b23d334856ed1997562cca85921dfcc512cd01bffa23b5b88053c7d279720057a0933029fdbc049f8ceef897411a919a624198bea23c89b3211c6c84fd77712c9de5b38cf2cd73423ed15de063ead87a817cb85bbbd8e58ac46a202ce112babb60f648eab22a3e9a71e702d8a5e01192136bc1ef4a619d4d0310ac9c14e8d0e80ff9dcaf887dce2795a48416f4e64ae0188a67cb9d1abc3f1e85157bf3aeb6dbd3f6ae40257f95ec3005d530def59031e6360f6468c30a4cd34514240e08a8cc08961bdbaab75590a5692043c17b8cc3d6780d955c4f1792b781a93f45612b932e221210f17429b00b4b35008145482f218abab9870c30e191d0871727ae944c6ed1571978477328fd986509f1276fa6adad31fc978fc5e96db5b7404db0e7086d5b373be097a1390c8a92e617be7848e0856f5a5e53d4376f12c3967fddfe04ff868cd428131ef2444b573d0e7e7899b2a5a869a2ed19da1b2b70453b883e74394feb55f53ccb510e5e0965e2828492022a8a7f88c6f31c5bc6d72f41dca8744a54c562b22401a5dc368d5ae15980f0da0554a0ec133d8bcc023abb40646344e6d17f486dde77ebb0fe3e49ce0acb6afb6e11cf70d874de6c7c2b484e57efde757ac154c4d53ac149bc8b77f414808b2a8b71a3091a81c3710e2a9d36de5747066a9b54cca337b10ea947944079de1791066e959b3192d15ea7959bc1bb39663dd9cef5dbd1f36f20d6fe7b0949c37b7ae135ef57e70de21042b54266ebe7a743089a783920aa537d88f09208d8b392a4fe77d24793fd3484bf6de780b65b2b3fcdcecaffc48000d252076a2d44fde6c283ac595e0f99f8f389780271ec6852f3f63608b47183942aa757f07421b718522199772b50f07580b7e4e98b848e6d5f6d1d784e7b8b8994d0f2b66bd1ea465724f6ab50fc4157215ed830be3e792b0c908a61ae01875698cef994dcf63b8447532893ef274cc25b7a8230840d1d442bc30f3eccce9485472ab1b3ae7a20a7d40089e7d7287048de485c6ade633549feb12cfec21f13e9b93877f32302dc77ebf0bd6f20325c6590236c9c0eaf156777b282724361d3099eb116948601a6f41f5abf30834ed32338f169e4002b3d026231a48a70bef6d6b72cf3df05dfba0b03e4a357ca678b885f3f542c8fc195b52b4ae1e34df456fc42d28021c8ee01c3cc9ec2b6612d17044c5ad99431c9dd7c65155c85b8e0e8760b300f60e4e41e141743820c73b011acbfeb514662c53121e0e8d5e752eb1e8c639dae88638e4ef0334e50374d508e8b31a74acd35801724e8be6f70cd5f0b3c67854283912626aa3d8f2d861a2813752bb55059354e09025da3184235f983808bc11dd8371668c39d49360a4e81ce740d03cf7a4926726b908468029eb280c08f6ab1f32fde9b9beab2ad2fd2217c8c0946e928e49d8e6f901478ab904994568bd4bcca65a92ea0ac847efaf359dcce8517cca9e42c62506ccd0e8732256e1b0250603d41223da7346b75962b6129b46665962307d9563b9813049b0c4d099ee2b31e19544cd33a34392f9a1db0ccd83c7c7ed9598f484247a2d591f0851bd728d13607891ad099345082e1592e7de18e938315063c05cb5cfda080aa52b2ef8e12134c8015dd4287f456aec4b3a842b858e4e7fe6ac348cb0c17360cc32910022ce688921394672bfa1017c757c0cbb871bd85206f64d3c4530c1c6eee1435f61b0356813951920a5626a4a1ba512eb232940c80ba154db09a4782075e87c5cd399bdf80f8a09358b22c0dc5a65f3af50db313913b5bfc6b70d93104f4ea4707ec5c6413e96a849bb9ce589060b725689d5b4ebba4b5306c0d795eb505495ff90675b2dbbf023381e3271c5631ce9be67ef2d838684d3178625ed2d5be716e72fc965c690602f36295f64baaa5507984d45e3250971d9c0f97211b6e67e44a431bf3e7c09566de3f59fd7289ca0dbe7dcaccc49b6e68d8ea5fe81d2b9f793848c2b35196ee684d15d026e802013ca54a2798ada5b29e4d5fba9398ebcd5ab3db9baad6ac6b702375e8d3dffbf6ed5ec7794ec8b28b88b5a5edf578858ec65deb1b6c4c2ee4bde05c8325f9d3ff17a72db12083265590da06ab3f875b0735971ebe7a0048d4767e330647796454572423045047011cfc903a82dde9bbd4338319785fd38c34e19014a2bd6a920f6480c39aa17de8790334c562cfb61494a90dc14a50a95f01a2a82185e63d9190f0caa75a3daeb79f8025848e472da38c13036ce84eb48a79d0b0d1267a7c5ad0733d0bb050603142edeb4a06044d17427b781b27039abe21b17024b3f273903c4a7401957c90709df19a4451e404221ab48f05ac0914e0556086e15447f7e90309edd83a96a7f9ec3bdd37c87e8970f10043441363f3f769bdc48e299662b229b781c53f7d00a7d39bd8eb771662932fddc90d40a2becfec6c0206fff2a6a8ba62f933111591fefb4367cbab6bcc840d8bdd7e052925a878bc1d1369f88629f75c4729e2c7036f652dea484396fd48124f4187d144429aa57e9106d214e7dc212db4365ac8f4e0b3d24134113d1250b3afd606ad1bf7a141affefcd99530f6640e9dde85108b04474ebd1ab8bd673b51ce328dd4e3cdb314f0c8f962e3106b68589df552e11e7903f87527778c3fef9f4fcef42f7822c7ba86ec3e69ab27316a8f96ee6b9ccca6ac3ab346815144563d07b9be14af51fc21cce51dca2aec0910a5d29a8703331df63198139f9004459a0d90638ef7534c9028ed620e2faca92fcecd3aa57cbaadecbc7a0a9159fefd2ee89f793007820d3418991d907e61baa1fc668ee4f8ddff5c77cf488059b9a9b0f5a277b28842588e75270c1e77fe0d9263e940809a7bf95224078a6804448f2c4ea20521cc7b2171de2603585228bbc91a398df4dd927f690972a8ad928167649d34b116bdb020574c04370ed6da5e7f928bfdf263cf1591a4fb63ec147d880d0555e40c022092959e0e7e9f86014334f7afc874940e1f9d15d61db7c8d45ad7698db4b495d238494df84f7074352761afc8f4dc48da95174cd84f9fc02c0f3da4d53e685159ebab0c2a6e2a7af29fb8cd5b555f091e9d20ac014d8e7a32e34b3a030ff6937ca54596ffa865199e49c4e5257b6a5a843ae49bb2c8a58789c4639fd6e769b086f174d0b1f96a187a41b0f5656b8b80237aa8066169d12eb690944835ef2f9160212ec62378a09d43153c9ef9fef570953dabec02a51439575079e680d23b5a8bfe370020ff32a5909aa828f3b5bdb6fcc8af6d75b9f8ae112eefe561f51602a97225b3580264d105ec6fba8dc052d6f9e2000be6ee3c077f6020aee3d52ab39069c4015d8d9355386e7566dbf237d00b0f9326de98cae0fe1c44c4bcf047ccde6a4baf9a7f8b8d533bab5f2dcbea68193da8289a5d068c162002e93047de2d35e4f07cc52b6a65cbf66f8b536b1aba664e41fa7f67e7823b70894dd5937649a7c0a199526f9928bdf722e8aa444c01c8e31e8882a80d5d7ecedffde4637795161d06d9be4960d40ee103a3ca9aba9ae5e4d9beaa8c18b415d7e790c966213f8dd29d4b54ee55cd580d06b99427ead75fed4d1b1ce42a4ba29a9f41039f542d130eb2a331c41ca853941393e9bd1f7596d963e66506c4e06371334b65b79cfb677d61b6bfce635894c06f9ae1d6b5e9144d0c10622a57a934cbca5f6df39240ddba8073ce6fdc8454b74e43fb229e9f7b5450aa98ba4b85c0b14415cdd4433a0ac0ce309c52d7ad733d845952cc7246bdb406ac822d95096e90d814baa71821cb4e39ec432163f6f305c4fb76c6561cb2923dde27c17e5339145b47fb9bfe450c052d893b99e4f7302364433932e4a5e26b0184387bdb403c6e26e646dcf3ea58269f083ee1911b759e9af8d141c0a6cd24a1473194c228bf562426ae37667ea055cd4fd8b7f6c91b77040e239189a79607acbfa7f1038e3187ac3f9f4eec9bf1ce4f04b67a4aeafff2ed629e6ee031d7679774afa270bbb784175cb0a7644fffacbf40438177bb03ee10ee53346403c1a284430bfdd0de66af05430fe8413a26d6bd791c7bf4ebd1acf637098711efa7eae8a6e73aff547a421587cbf508c9b5271e8a5057d136dff739a1fc05f45ef44d553e2b95ff1eccce39b7c5a8447ae1b78a4a6bdb4a09505a7d8f25280ce2a7cde2f6268062a07c8c3985a0136001bbdb602d5713543e8389debcac84df0f156f61dcf2c3e3e462e49b39d3f16c009f8cecfeab0ee65b2a5ef168a9f21ae25e4061a1c2df63a409ab85a4506a2eee736139612ff79044f62bab0a4750e56dde97248eaf9d745346f5a4d6173849ccc8d5f02c2bd55886162c8a795332c2673c7c857f5d689fd37cba093c0ee588926a1f7f5a071ea3452340d41597682663f39de020bcc3b60b0f2c2a3890a4c893d41f26c7097e679a1bc798c3d4bae849079048866861b2305b8f7e9a4a111607c9ba33fa594b7922cbb620de516aa172e6b0d41c033f890110644963578e61e6eaca32cd2e57caf1825fb93bc6f09705f06dbb88160168ac5fa76296042e17b5cccd9c3720032882ff476ff7000ed4dad02d3186dc25bf0b41b02fc53f5a292fe25a4fac97afe7965a377a9fd09779d3e5eed756bc10b6b61bcdd2d354b43ae5cbdc75a2bdeb3a18b6736ffb671d5d1ba2fd8ed4430fca2edd450aad22c253f88805e2bfb44dc8d4a617f37f49bfeadc8b8a97b69f40ed43003ca5166785b30ce424a07026d0563d3624b9e585045b675d6f88af2f2650b0a863ac375b916ef185a765479f0392e82b1fc2c59c513b167383d6704629dbda1392dda331023d02c5379d0573c7b3edcb730b3f3cff125217befbde59652ca246584074d07580746320713d115f6efcb79c4d8398c0a696811fb8f161b4ac638dc9672bb94c7de85d409ebb17320a5443207fb66451e29d163b74618e76c8d1efb8ff65890635e038243192e13aa3d09276358e289e88a8b89ea10c456bfd76b074de4f9b5d62ded9980c3d13770f4e72e98bdf41c30fbbef454a8e1a763b7d66f10cb8473497966a6492e0b6369e462ff0eea3dadad991765cfdf2457f233f7d6124e32dbe06da5c2c9189688a5114ac3d4c5ac13cf3a33c59dd1692fbdd3da7c9a8a9b96b3fd1de89cc9134f3ba7a5d576fe80ca2b355666f369aa8ca394a336b7a320bdd5e6ebc9c9e916576e9bb3d3c0dc3d719cbc76823033a5a1c8aa524ad90e020eed6e5b314867edeead0739daa7e368bd4a5a6d7733ac6d574a99d17a86a33da59436b576d4a7b596a3b46a1d05b1ecada3603d2339ed4caeb5d64a67d6d7959d2caed5522f5c4a29a574aa524a596badb5562ff294b56a980911194ac9504a260d194ac9504aa64c1a329492212aa30d7112c76a3808f57e64d8478fa60c671680016b43397f3e85644efb0b9e0b2db6c7b4cdb6e0b3b47155051438ceebd19cf34e68c9c26915f734d055630d6431a95c6834b4032171b43f7d0b21731ac914125a80c7a389ebaac469d13d9f4353680aa986c8546b8d6a427c35e31acf4f8b9e4bc920e9124fcf52fca4ac1c4824f1a33c82f59c488c9933e69c530236c4d0303ddd508689d6a444492899dfc4738e538ad1112c871fe7d2e4b2f9386363a6ecf264c9044a82708202d49a94499432b36676b3835c2e170fd31b520288899a49b09fde4b930b2945e68cd9657ece897482a04963ce3969d83c0d973f3c0da741430a134fc367783d34da490d92124dbec6b3d75393b3a7bc9e9c8a42c6a71ce5f5a45028f7bc1e54f6a204f15ee79cd7d3d56c79ce2513cff9e6f5709b4fc9b3d56836afa95ef3aa6945535e4b0204afcde035c75e8ff601d2d0e31b24b47cf655e1ba520550bebcc5625c3aa0043de5e95cf434262a86a1dc7b7dd8c205cd099cd0e204d6bad488bf4d90f99bfa7befbd4fb0ecf82d96780bf298fd090e7ffde27ba99080bbf7de7b594eae08c20cab595e13a323db164e5d132c48b426ae89971c300b6584b6262c38920135925ec91f71ee02043008426a1206d0cbc94f08582043e4c48720169a849c9400a8014c8c9c2001c4c216474e9e48c2020c8c6aceb09ab55a926d5e221991428430b088b20598dcbd443232832422224cf9090a80999c7a89f433f4c50f124ffc40e12c2a9c8ca1095119436528a1b12e7d0447d4aade9609218b3c4e8b2717dbc4e4d3bf2ef26c59b98133d6a2f530296b3e8ba40b94c773eb228bf254b756ecaaff2856cefa9defbdd8859e0be93e50d5a2f5e0c81224436d2d097eccf116477ceb2d5abff19166aef8c287232e578b3a8fc67d5db65b89a399ba09123298ca70e5600229685c689ac660da62362c571966dba25a9dcee974ceef7a67860d25b78f5c0d25198bdc9e5d467ad0d57458ab614f81984d6dbd6066a90e101062b94b5d91658b1d7e39fefbc27de1beb8900497f558fa419f66901018114c245dcff51ed02dcd39bb55c9e35ca22658bf49a1ca505b94d8dbfba99f30cef99b42736922993ba6d2c4d14adf48be5b1cee7a08390dd1272257d1cb08cc8fd4c83775b5d84a734787d163cc2a6aa3986c435a5a4899e8ca4b6e400daf81ef837c4f81c8344fe5ac431910276338c2c818466328b9b8202aa2a34498248941248653bf60cc9827a630929e8ee88f112e308ac0782ae3e933901f080864a466a5d3cf34c9a7e9edb125a963a3c2f17a86a8b5d62aa7d3d945e8104dd3d4dedf5376eaa686758cba6a0f5121fab2e16d1acda514a3445a795b25a34396070934d44343f410ee21cad44df488aeda312c2f1db986a8100da2afef1b1a1a72ad00c263a74cfd31313149a61a86b0d323fa7d97c7d3a1efa3b6bab223629eb9d6229e007044ca5b8b268f19a988230178eb9bd763712de18857099400838594297489985a6bbd7992479694357ec479eb7289b75e7126810adefab7e4ad7bef48a4129c1173344009a4a126ae2d4b6468112788626b0e2290c04494f1d5b3a326967873f563f7b58aaf4844dc60f4d56db64ddd0f752888afd8fab0be1889b1394ba7c77810be7eb92ba3648a122c3708c5944c716448496c042a00b1a4a40b20158496104999ae8c5e896d1a2a6fede4412a8900f6d6dbf5226491062644b691921d0905c5c893525a6b6d254a7d299ca199acd43b0d6b476186f9f5254df333f46b91390d93396d43961842501ef1df681c74fb0cfde3acf9d622736a97e9332db616c9a342af7a8c18305eacf826244c4628500dbb89f369516cba847de0640c4a9c80f102230999ba1565fbd2b7a2dc33295545b65e613926501f75524f1b4ed89c3c351967f2d48425f41c75b6a24c7f86da0364be01648e0d9fa3ab38796daba595565aab05a9137db24ecf3c51273379a430aa449774cc64eb29bf6ec1ceb90c95023d3f9239d4fdb90b6af2a88aa437a1fa388fde8b9f312fd87c7a2798900a1b70bdf819e382f1c7a3fa68a4443cf50e1ca90d638b9982872dc4f450bb9ae480c482a7def1a74e943a5198cca14e614e79a44ed4a99337757aea488410947b86dbde0d7db2734939e8ca488b64e486f5518bd4c71c96671ed1d65affb99ea7516647cd534debd6b954388b64ce9469349350c2f3ba0c0c4d7120ab03459c1651a107bfb528c5a8c529472ddaec5b53ae97bb20933cc11b2d5acf204eaf2c0b9438accf10640e94b7d979b48e0cb717e4c92473cd79e1640c565e62573aa350f2f376ceafe79cf34eebd65a2c28161c754ed6306b9a38a4903a66986d51253b76c5624bb228e72862619c33a543928aa2fbf7e55cf47d2e242a8cf260b7b021dade03ba859faed3d3cde92967b41919519eed888a1f679435c13823a7ad4ace9ae60eae0989630ba9837b42e650a7463267883c72464f7d33a23c0d83e5cf45f1c7cd6886238bba165444e22029112bc5eb2079b420ba72cd1d163671507769413e04c128b5520cd156bdce84a44a4891ba85cd1dd9d3c4419dba164479e813623cb530091be229bd22f494a67ecc96640ef5322dd2cc296b6a91d2316b921486f3e74d14e98970ab2bf2a6183386081afaaa62a4e90a11a22b36c03a06548a10ba62435191e6f055ede029f68b4e9eda524b8d24f258b52c5b142f8f88cb4a749f9883be0d67f3957105ebbf3aaf87528ea31dfe6810875112e9a56be0248f386585ebb42ee348c963ad49c04bf77e988180a41ab13c561a366080687fb8bbbf5fbd33b106ddc7820d3a8c73630fcce4117fddcbe604b9060f073b747043c2d286854a67c991f2583de8a42aab168ff0d966e70be797f5e086d27ce5072c9d7f0902019e6c96f2511e2b88036673251cc019d939620949494a4a5c66d69522a961ad1f783dd5034a716a98002080a7dc45041b2fdd032f64e3d54baf60371ba857edaa7cbb1a68b028c256de13364410800e00bc748e881bf39512411e7108f57573268fb547cf11bf9e50109b624c4fe711ab7d7a3ddc00c258c794478c03e7fc7deea2c89a2fdc83bff4400679c40408ea845445373627050802238fd58397ce8dd065d747c0f325c4f79da1fea42474b5836e5eeee0965db493457d960e0fba9a35a6aa481e717e52a73c6ea3ca54990dcdbd837fcb22675511271c47bbc3584addde7b25186bedbcf25e6b6dadaa5aa59492524a697777eb349d61ed30febeee9e362fa90447a9e5e5ea65d5ba9f86e30cdf614d15f93e8d410c2c92dfe418c460bc6169cf51fc35fb5a29a55d6fd42c7bea17dbea9cd7d3dd466bf52c08e69c93ca7b9db22e9d5d6bad1a94c9e0386fe0b0c2a6b47691a758bbc8bc5dd5a2740dcae4e9b8c5fc72529ab95a25adf5de7b6ba594d6ec95b9f6da5a6bd55cdab95d8b6515d265aaefd3b67a2de6bc32da56b39a6badf55acc755e190f4b9e35f33a6bd5b86dd3327bc95cbfb6d6cc6b9d30d8b56450489cc471ae69ad813797a20b558a9a735a87bb70a4e135d5f7691d963ac2b983a250e36bea8e976da28f4f18176a21e6b84d7bade332ceb3365ece53e2a01fe86eab92af732994ddb814d8e3b3010cb28794a7739b95a2a65d97a207e6a62d4d58a5ce759dd763dd9b3b54ee58e917d549a9a3276c09d6e459dab33a795af0c70a7a2ef584101c1753923c28805053acf84c8eb179db658c278ce0a919a454f514c7a9e32a9e7aa6fe35d1c55377249e22393186d1d3536f297982de6e915f473c15a47ed8a1b3e20955840f1c97208f092642b07561eab0e8a0f9a880897be12d4bdbd22babb8cb8bae2413311f7c52006302063f3419b97af0b1c2c8a8071e7cae10322a010f3e312123293bf87081195d09f2d9f2649368c0c5899c7a896483183a10c94174caf825121120a62042048e75f9d571c660a8a4d7e1f74d27b80a562f05196cd76e3f417e8741ec6fa0abcfcbf4585a2f05d902cf1b94cf49aeadd7c33927bf10c1010636e09f1d7e248fcf3df2f8fe2087782d244a86915d24151f19327f21b9a03c124c2ff50e75ecf61ed12e7287b467465be6b1cb1ef398cb637bc563dfbae49a9caab42937e7061b80372d7c426e1ed0adeb1cf806fe8542a60e2598e2490e5a1414b49e68524b94d74f4bc890ee86d613940fc95460a5f544f331075d217b89969c280f8bae700aa6d43b35eea36db2461f1ead11c67e9fc84c3d63dbaae48cebaa64ef477f295bd5ebf1b9a17e9d83b0c6e80a7b06de50df20ac4c4d7485bd82c92f649fe82d7a7c891edfa1c7d7f5f8be1edfa0c76e63168be4c9fce6b1db29bd73d43b358edd26491ded1f76492d938d8faa1fad91063e5a2a1ebb7d519e2cb446f87fb42fb03ac99c6a46aeb06357961ac6e3ba4457d89f62c9a37db5973cd6a5c7d8ab1592875597a48e49231c3f3259cc58bd7c3351235532b74774855d7a12b86e8de80a3b8fa64979a444449cd68d9eda22d3f0b19f28914d980ac74c54c3f1c35b953c5aa3948fd5db3b98e11b0df0468bd81bbc115bc42e9f01a9b0fac6254fecfd243eb5881bcd63dfb6e4544d78420eab13c83d57ad95c730db84d43c1af2680f92c85928e4490b995f0f85c8319e285af282c9d5ca42ecae9634616e3fbd94134d049916b5883d1552a21631f64b947d70f619e00cf37f481d9b63f7a13ccd4457d8714e61e750a0fc0d94df4cd825b6fdf4f98de6b9d449af3095d22bdc44e66007e50e7a84c48167cf6d4bb6b676addfdced582cd11a69e196ecdf1b68034d9489e9e808069bb1212712a71165fa8d62fc7dce8302a13fcee7b6701cc2f43ee61005b14f30887dab5129bd6ac72115cafe45dbd712722c91bb27f2f4ab9467488d90c917d47ec8d2c49205f038ba99d685deb1dffe02d0b7cf97440193058c7c177d3b0cbd93999424795b6951ef8cd40895d23bd4c9b77737bd14ea1b6997af42fef68e873c9d47d3a3ad67fcc0c918b87079bd5e499cecbd768bead5722057f0c909f2250cf2acc19bad1952f08547af107f5459a194822d4a90b6cc0029055470607008b01553fcde9b44149a8e3a73ac00fa5a3b89134014e32c35e1a46f7e578a7c2c588a319d398382f602054f38bc709072c3d367003f9014b5010542568ee0f2d2ca112fa0f19b19194315463f473724f919ea19d8ae497ec65b6fa30204215d41f2d665116f2d75ba02e5c72e7aebba42c58f3de5ad9d6d9bacf0f58ebdd6553b640fca4f90c67333e90529c8a0797b7611dedfc673d36341f3ee6aafbcdced5a0be19194a6e0f670d8e099107c830078db610f9b947d04e9951dc149cebc1b49061a7d967111d2a29d209216a36fb083f71d72032579eb1f2c6951e2c7b9f401985cbd4738416925e8a5b74292d46225cb4b6f85ab85b20048567cca793427bd9e9437889de835b0bb3661fc04717ce6e998c9de09f4a3b802835f7e823bcf496e6eb2ceaa859f731bd24f5083e7384e7a0270411e7da83e9b1883a3086b7ae9e998495af2d33914cc043f4104fcf474ccc078a8f1bd42167a6d3d2d4b969fa0cd679e0928ef10b7e63ce8cd747933e705718338bc3d7f6bae037163253f41033cc7693ec1d9d1e95d97cf35d6b4c7a1750c66ae81a38f769486a588c14f90009f8155e6d010843eaa03471f5fd8655d9662ca4fb0009f819dccf911a47e56c1f80902e039e9ad90b578a73441f319de4cef97f3a078f47143eb13ec6e28b5b002cac151a565cb4f90f519a881a30534d087eafb06eaa8052f85ccfd2f289194927c8633e585207e82e14b6f85cc259682c84ff07b892d853d75bfa0449a22f617df5c9748515cffe0550f84f31b02998f0a479b4779d722762e93b351362080f01287a30fd5639fe1383c77935ef0d24b013b0daf711e3dbb098af017f452b8a10ccea3db6380e633ac1ecab9eb61efc0f92660e7c0f91c9e586250b68892dfddb44ba4242b2f91b018f113bce990019acb1645781b3fbdae459473d43be1b62009a416bc94621291b493a246218f8465e92798e3bb934851bc7e821fbcf452981b9a414db24703a2b1209ffed2d7c4f7216617cc300df0a6ebba5c3b1478a36929f046736d626d0678c339e79b731c5803e2c61c28df03b78e4e303ec1b994042848f593531e9b60687cf1aa1db2e73304c14249e6f56991bc0d225a1332c7e1a87aec5cf7dc8979b4e4d1b863b11853ac29e6147bdad1f2e29238a80d1b8f026de85c81478c375c446346b1a3182cb684029d9317dc41e6e040cd97ab45eb63b55e8af0e2c48b142f53bc24e96879519239f2b9bbfae9ad1e05ae1ed7a6a6a332f248e56533fcd16203c55c73fe0cd1bc0ba2a7855c665353513657d03c3b613e9a3e4a41f389a4458d869f6e83fc741cf167467dd84c14d8403a5e720fd11f5d6c8ca39e29682ea4458dca167dd864e7d10d94f35cd609c9951cc244b9e833922bed0a2800f05acef3681498f318942a787a097b89d465cb5ff1fa0f5e227591f29cd669e06c0a9bba145104d1b4ce659d06cea7a32b623bb449d64dd488af8e99acb92c0a421fb502ca37405f86d94def84ce35ce51e31606a13f9d73ebf54c07e14a96e102d3fd65389d66538b5665258f3e6cc2116716678fa45fb12c5bd4326e03c7efb7e975285be6e8e94cf753bb94d0b491dea12678eb5369168591517e7d7a5a389f269a89e425c99c4eca8c9b4b8f85e94e7a678e38484b5497d50ba2c29135def0e969e1db67e6bd834275a89a02c709e4538836f1b4931b37a68b3635925e59b7363d28cff7a1c2cc7edf2f160a3d2a6c003fca2d131bb7804af5d35b6cd15a950d68a4c84cd37a32db87b72ee50863f3f0d6fb8824c13a0ec71f8fa7d69a57c3006a3a923a2e3d821e71c32ecd91d4a185fdd2e9925d0852c1ce688777ce39a7ca6f03e970c9b8f2a0181cc37b7de2b468e7b401279a5e596fcda7b7de320686d2f374cb7a27e91eb3e9ad6b7367be756d86b66d87dd3d7dc020dd42d5622e7030c08035d1c2d0a2b52fc0d0a2d5c01c39509895434bbece594785e3073e47f3cf557ee3c60be78652bede8937faa5853e3e8e428b3a54e47b820a3e2dda51e543fccce96d816a5909bda3eda0dcde70b4c05f9f5c4d2da0455b82064e47813e7eb468bd877c9518294f01f58e9d28b7fea3772447bd9e6906956ac187ea512ffc0cc7394e478152f3f1f3c17a1cba4079a63c62593bc1348f8d4fb7311bf035f6ab815ab41bfc04259252166f2d90ccb17e25bf28400515578a145d314274a548100541ca6597edb89c2ca1c6a57fde0898e1d2bb1a0464976e933397dfdc9185aaf98df91167ee58491c7646e8a1386d0d88e92a4bbab2b2f581972cadcfa2deb961c01c737c367f1cef4b691e969559e6371bb54ddbfcef78b9ab6930f40ee79b11a9a3bab5ae0f2ffcca34c47963b264075b953c30d0150c94a731ced7fbfbfa0556c03b08a88786fdea320fe319b628c5021c8a88690103072db67001c5481364c8c08829a264b1c1c9ac4c492c0bc5124058e9258ba6af8edd71b22c82b8588a135e9d91c5ebeb972f6fad95ec3858108de0d3996441c4054b5d50b9610cfa848932342c8ce044d1b26035ab0373c60757181c7c180a134410511813bcc244e941284cd21108588185b102041038620af365070854390ab3050f548ec28c910304a8307d354b61352bca8f1850641a2f917ed26071430f4d727d897403098c18d1e58627696e90f2c31d01ca4285e3acfc7047b63280b250e1b828595c2e233680968881c2868d0fc9880d20d7123150d8b00145cc754ea65259a5d539314c9a74680919c215910255a69794e9ced6789d3dafabf1e6161d949ca0256488a6248a2459e4eebcc65acf7377eebc1a8bf20e47cf6f4620ed27b40ca3e75c8327c8cf35ed1c38c2f039e49c07f57cc2c42c91f97bcdfcbdb79f832a79ec180338bf01e3faf5eb9cedbae92fd3df8ec9ce42f2d0eb5507c943c306d3abebb5c7f8f0372cf3d7c72ac46da1d650c8e4c23485a92564c846054a4b0317326446085ad587cc2f925a4f524ebd86d6061b803588aeae6b0056a126e13a05ebab48f63ce5637dd5787b69bf28cf34fcfb824638662eb0163f4ed88b0bc7d48c70acae26e1067dcd8bf2b417b9ba391c5bcc5fe75ef5f51de34290abd39ee1f811c9f56738ba9099b40488574bc8900d8a0fad27d387cc145cd17a9272ffa8e4b1632160c1b9b763134657d72f1ef353afc92e6174c6955e082687b94e5dbde3f9756aa4773abfde31a963fbeb0d46eab87fc3f1eb2ffee6e6e2efd85ab080f29b145232749e72947b9ed2209c4abdea6eead407369816af7b6037b578bd03fbcced588bb7c77419b09d5abcd733a516efd2bd1e02963aa4c2827389b1c1b9c46c6564e1b632b2282063b744327669e7c4d22b9808902035408310960e175abcb5928ab7d60f80c3ddd039af013318c4aa5a5ce13a0e715ab4dfa7815fd6b44e2a2053df52b9e67a0d28e50afc361c9c3983d8e7401c15c7398fc6a07dae3b2e03f33b9f5e0fc63d1e8a0b551d289f66dbd488641a9e401dd72fa13bb9a32345b9430089a50e96ccb1ae52b1b20b6bae098ec894295b1954a8e08cd475b5495bd21fab4d8b99579f4b9e04a84fd8e80e4a1ded46fc0cfd33f45b2d896cadcd42ae660244965afc2f43418380b250e1a86099b2f9b724872046bea68750648ae937edf480861afc1cbda0a14dca921d9c704438224b2ce162441811652b632b830922b62a990a9d9146738a06e40fa9c3c65b0f2275c8ef495e328738e21979b528640119968c028f164340ca4b4b34bcd1ac07c92fa985eb47ac85ebabedb14e85c821189169add685fe9ac81ab6529cf81a560a2583415ab4aedc2f5d79cb001089c37a0d7fc81ceb1c9c208ffe35ab2e843e43e41fd631ce4ed65a4b438bd67f380d3fc21fd4696302280b150e0cb7440c1436c2d8b8a19ebd06fbdc504f79f69b94d7f80cf73e2a2160e11a8950074dc941720688025d59af01adb5b65abfd5390faad6bab5d67a2a64512c99456dcedfe7f54bf6f10305f6a02b6b9d26e5ead662c0ba0fa9a3bf71fe7c9664787f41ef258ebd55b060fbe284c8921e63230c14ce35440c60c6bc9a7208ba612c81a523de709c4f6593f9d8f5681fcdc3d2116f388e6c1a6dbae9e9e441f274109d403c6da78e01c993e94aeae09eba0fa9a37ef3279341bd83b2b0837c64ce03e48a3a8bae288bf53d2da8d32b8aa3e4116f3fe69f8eb73272ff985fdab6211199f66811ca8f1e48799447d80c182eef4aac0d883c666fcf9e045269d2a449d3aa2d64978b490d6d5a3b22fbc8829247dcdd587032862f498a94c6a83f5e8620f535ef3a07727da401871ba8f116a494524a432099b7822c3d0c643e760e879e0aa31602b93ee2e93ed7418c7f8621902c6442bf7012a79b2a2ddc5c7aee398f6ea9bc0b8b741de6b6ceafd7aef39f1a5a5df893726f0b81648e352628cfc0142acca173da85120cdbb9e7cdeadd9c5e89d0ac67f7a4938657c88bb195d01b88b73072b0bc9e59adb5f3a66a5eb34c63d54bfdc3517553de58a73818cf6ffab8f1d3592de668517a074359ceb00653a6dead754dd7a0647052aee44803f5ea3fd64720c058c38fd22509928451c88fb2a6907f86e38f9febd47f6ca5610e5feb877aed1bfe50ff09c71f6f4306d4fc0ca524806cd51426fee9429e4caf610ea96d812438e2f086d9e29c7399b3b9901a5a5bbb0c41ea03c97cd47c03f4997460f3bb85456a70b58a081992a64a165d80310619695a9bffd4d0dac29fceb9300492b9740b32d9864cd7d150d0cb08111b5a45649ad6ab466b0b73d8fc670b2f38fe781b6e60e4f0b57e36e79c8639e0d60fa62be943648c888c1f2e63ba94f1a4f5f3a47d0ba9cb1b1287d21e8e887ecc4743eea4fd907d74169625260fec4157ed1d8893701044a6432d8a7d5d903af0b7bf30a50ecebb8af1dc4490a7cdb6853917b528851aa1c92315a2453b45429489cc69bf61ca54c85f680474deee0215ea9d9132f9f6a644dfde4e2813a9c33bf0463b42f57dde8114a8574d5f1f6c91c7a654322dea1d3a4a399dfab0348221db4969b13da66971a38ad3b7d6a12cd6b7731ca5aea72727a7a69cf4a8348d1656410b99220800000100c314000028100c08c5429148200fe464df0314000b85903a72549bcb836910a3200a32c0184208008000000800801087aaa60800c804c07847778a62af42045c3eefb1395633e29e034b5cdf2546325fbc9c514f576782761573019d19b546730653acc2cc978365d94273680b7ab0d6ba402a841a45626b2a7826cc0148433ad8c14b87bdb74671d85c2fc8e1cd5df4eff07fc2f3af20fe69ede617637829b6f1ac2acea81d7b731e23d615868225d8d30e4a2fd73a9ee00fdb3bb0379f2c623787d6ff0e60bde4e2429651c41054f1b8e5eb177d93487f1a85c27fffeb02c5ee47001de08d4d4e3da1a43b78d0cd36823dfcfd63ab1559e12971e51e16ad1d9aac40d1b8a1d5c36535fd152cdc1dd743a6d1e713dd022c657f81aa1b1ddbb6ffcae518ad2be036bceaf2921aa2705002f800c7774a43b0ce527d08b6496bed3e58b2761805597beb0c54416617d55f1b56af38d66223fbd0a748bfc1abad4d37eb1cb6c53d2572046012dbc8ad1bb71d254c3a3cb7c2e3dafb5f5a980f5c0bcf895fafada1b01c51473e5f878fa12557bf4d03819c452e6f12e2930139c8d49e428e443fa2428e924dc486bc24fa881878a5eb7e4cac4db4c33e138c559a01664d15c9de976c24c17b5706157cccb889fe8b74a68fdc3dc5d1e105186a265886585229a734336ddc1636a4af7ccaaca6ddc215b168432cb21f5cc0beff97ed10668377117741ea4e0f3bd6094655b7986652629084ecb289add5684280d798e06fe7c76f2dd87b0b80c6d881dede7223cf0df9aabd8beb883623568337f4eabd45978e1e8874268ec27d758f2532d17ba33b412f940e2c3aa0f4ddacc85d4d89299fdb772c99897e86f4d6737bd32d3df748eec647ee3edc273419315e634e5a6c633eb08a285b4d240cd05aab26cd03c1faa733e8e2ddf046250cc643a48e2f4b14500262a0095c55191d8b7e95851ca50daefed250b591c12e1f27cea70f3844a68eaa475d344ce97aef58357f5a422136c8d1fe0da71686bef4fecd995dd063a9faef2bda19214a5b53976c43765fec464fd51921955d1cb6be50b676617246b8478ca199345ed4fe1a90006ac71abdd7e67b3b1780ddfa7087d73d18fc125f72379742ea53aa3087501ed05505f77d03e141d17fdca58a194ae6d0c3040397839e50c2346a68db07d05c2dfa9d7f84c1cf3973f67a0f7a382238e01e438756988ad898382dec85da89415f9d93bad46179f09ec8ef6e037a8ac7145f753d6528ebfe58bb482feed01572e5f6413e4f7eb147d9e7570bc205523acbbe61a3cb0387a5b206768dc06055259084c297f7d579bb1dc111afb5defbbf1d15ee906a5e81033acca506cb4a6e9468ed0563273e2d88d221c33e56bdb1c7160e48a876ce33926373eaf2aa82588be50b23e6366c5704446ae843ca862cdfec095680aab539b47e3f445bdf47b92032a3487f5b76d1e2202c3282bc794d2f216d7b16bf4593d654868899018f277eae3f8faba52e7c31aeaae28fdd02f09d4032d3c3b7b30e9411460b3cab5d78d182d63484b6cb23b69315305a9d9a496b9899c4defdcca32ab49c06efa833c6e110e30e0e8acc1ba2ea4561595cef8a4bb8bc6a5520ef01a4ff440c6bfd79a6d175045316452ce210924922315e8d66b83a70966a8f2962788b7730a48439c0c57b50a3163d96adcaf00e8c58928bf5e783eee9f7b9533683224120d8c6fbce093b5cf4e17bd49cd8ec0dc92480420e5ea6fa84c455798b19cccaf848dce911cba782654134648acd0024ba76416ea60957934c9b2586926a42e08bcc6a3915093a968ed2eaf9561160d5b5a798c41377d03a62ebd9ea92c56e65eaa1e918bb67ad93c4d8c0b7e91209736a37da56e81c540a097037024cb036586efa74a4262ca2d657bba891bea062f9026b07ffcb72aaee241b57c1d563027cdfcda26064625d9adcb55825ca7cd8ab0de4595c62f72e236d5595f588094b439dd8b73177fafc8b8518787ffe52df8a56d030a1819e651fcdea99b504db92a225d1202c6c09f9a13c11231a443ff9b99efa069efd92979f4f702dc291b11f316fdc25965832d7605c31193a2947ff72dc8dd500b70802f66daea38bda1eb816f1d2eb0a28fc2411f77a7d5445cd181120e65663e9ec3d12b9b088f9a365f215c0ced12e609c478f0f8bd0fa58d2f5dfecff35d7e121921b90809b93198aa87ba951c80be1f90036629d5b15c67cebf3d496f120e9df73c60bb3b831906e7825194d91319d9ba2b65720eec3e7e3292168157621f2796903e8cb0cd5ad2b5f85f5a1fdcaa42772ee681b7c3a0209ed17218475fa3928b0401e2bad58841677bc944569126e3f1d148abec182d675042ec67a6f1be04dc4716bf184b398c903621717c280132166226e5f549901c16d963e80b4d295543bcb52fbd3e57f548ddfa59c460ddc5a1efce7908f3af2647087f57258507ea2661a66c937bb28509516795888da0695c34e116d433e305ec12cf9d6912e881c2f8a76b1e2e4ecf4742f3118656312552ce3da13a8f37b4e846503f51520ce9d76b10f2419ee8d88b5e4715bad2ddaf1ad1ae58436353cf49b1a719577186b4bd0c48403564039398b9bd6054b8bf13732349794951afcc2adc2265cc215a8e7c17a2516c9a325940469611fc5ea0aed01c03d8d035ddf7fde924dd6b32b8866256f35fca8886f5a4153c445366f61c0bb452a2ff470b0c4f1ed6dd9bd229998be2910743241261156fbb9feda06160c6201a02782ca826232e4f799922bec0f2794a16588fa930952dc4b3cde1ac6a174df06ff9eaf4e46c7caa56040a429ce1a79f15828970cf01a5c806fd636ead0e9df1953890bde4c8fa4639c05db3e0fc9bc0078c67e52683aba9d5528ec84864fbf4e42879d1ac22c28f0977d2c5cc565455db7c7a7fb5160ce50a67a58ad1e6ef4eae88de23df15a9043b9b5297179c238abd060b40798bca8731db0557b35b51c8304a9d7fb63017622a05e2b0ef967f29d1dda46d5b740d2b9f72b4b4f3ec7829eef860d9217b54045dce60b23d4bc8921e5f33caf44bbbe4c05b55fd24d3c00a7f2fff1bfc02e6c2ccd077a2cd8fffbdf1d54245da179e3a1269fee5b49c77123fea85bba483f55a507b8d50f512c4566c2787a59514b02af6c508fcb0082fed5dd2a26b356ba7ef692c9dd18b6e59500b7524c2cb0722b25d082593da662e301b0d6c39769bfaea7f3ab1244e95c9fce51f95c75b11e383cc210e55194832dc2c4b5bc9a977ff0e205beccea0a7bb4ebd82d1bc3a9fd819da5cd48f959ce2c7298ca44bfa02023e4de912c2ba61389de414c837c8ce7bd587017ae64da958b875084bb77c306b8a0c8520dfddce7e072cc0eed5e62364336c02a841efe26f0bfbc79ca321c8eb8ac8935678c599cc85c898673ca03fa93d052917a94364561bd32d3cefd43de8c5656b538d2e151e9bf018f170ebf7f2a8b5a31a79bf049a3fbc9b00e45dce6d81b8d9b4f45e4053b6c741a8a3e1bb364ec15b7718d488fc3c59449992aaf97a1e24d108166cc200a653607ba74325e1b8f0ec9cb25a87c15b4188aa8fd4673e0d68c56067ed2ea874065176af3ed165484b2fc8b0d2f78c80d7aba42ef9d66e3619d3da9d10bb6562604d5adc509b5dad6be2c0205cc915c8f099118c4d0b0bdec8dfda7d266da81ae89f8a6eb30c3e515b8bd846d985c463fad024185eef147a81aaa23b4b761955b027711d49655df8854ec2ccb62206a45f8763634ff0374b5e02737791f02bb47edfb037296d897de719e91d50b92b27ec7d44debbb2776cef3d762408c1466ccdd991d906cb0586f0947fab9987aba1ed5b7beb899f20401f53a79c084d4abeacd3777a60c137220248aeec16e8db96666627e3c4f1b8a5edaaac1535925a8b17ab9595336c800c368f078dd7c497526858b2f330c953a18d1f199550d5b13b709f66255c42c90748c4d884dd8934a82d7c832674990e2c530523a1d9b70f7dad0da3190be9e8486735baab7af0c6a39cbf1f98f67fecfd9fcd81dc4d9d396b7d5862fb0effae3052bb49f27c3c06e46b2bb92977d4be43a32d513c89ef0644447d1697a3ead73d57eaba307b29d884289744de4937cb65864650997a207f7ac5d11c3553e3ca3005e683cb74e79be7b787c31d6bc2bc6fe1025d57cb72623819ae8eca69232971d645c381841b72de279ee4e7bb7f543841c264b85501bc492424bf1ab2184b7d2f4fe1d48450bac1e91512e1e6e117069f66298d3debb42b8999a261bc135446fa74d27219a5fbe8f45696577c87f6f7096c059546712b1fe7156b03d4428cb242d2b56290caccd8927aa18ab3f7db81bd0a6efc765266b76b4090cb6420f68aab7c5db9a54d8c326602dc6a0c27aa7689e159a148f494829b82bd2ae450757536e95c89b6b4da68e897fa20d4e737aa559493b19ad5675a451d27c785f034d5f3151cba0305840c72ea9cf0c6c3369a9abc998f035696f40605549c942c8c26df2591d8eeaf429fffe619f4f9afb78a10e57cf8e12b4298b459565b6f2ab3f7d5c13cb07ca53ebecf431881471fd49b0a6e0371c7266f4bd1529b546855c5406713d2373ae3a294c82ec1034012c956c95889f36afe71b77ff65bb7c0b05071e5f398d55c13537015f0b2f2f4d3c72bdb337addd708a41eaed5b626d09b49ce479e5be7676bd72ecbaf154a11d448ce1571091657c91cf73dd66c58160b61510851c17d90a9ccd9ffc435888c1f364dba51b11f65404ec8bbcdc945cf7b3d3ca1e758a130681879002bd0332d1149fd31bc9d1f2611754cc59d788b2b4bb40215abd0ca033e579292ea907f6b0f30dcc8540fd0ac55475698fe9fc28286208ab0d0fa1bbf09493889ee0ab19d4f7902815334182359be35cce0ba28918a8dba75202a470349f3cf4a8d00b9b765ec78bbca9679179bc57008ad28b77119b8ae8f0889d582318fa6ac6df0924af10399ca8f6c8047a88b651e3892d22d76e0166603796fd69522a3cb88c59ce36312830d16a88e35a43aae866d58d4c78b4c6f116de4fde286a8d9f2d8eef7d70cf9b28633e3760f046d331d15cb4aaaa766d4ee566fd6e0b1c15b29a870f6c335b3de247e267cd5020fb3c365dab1309af01107d8d4d69ef6824ef12d4b3bc5aac83bef9c636a27113e5bd09531089f5e514c09cbe9d80f8544dcf7bb2fa0f4f65548c2a827812dd36c1f49f2a85ba0337b1e278c26358b274335a3f9e632a2f75accb2bff78f45d8f3c142d34746abde7fb592668e568db0224aa35522c4fe6291670f0e0c7ec1b4e829bda17edc6cca97df589ff45b0aea3e9a6f70bd088d6c0959741423f87f3ec210c7fb93f79e393337d8e59f68ac40b652ef684322aeaea11affcc733676310be2860d8aa0183562eb00b198ec628ecca18edc841e533fa1a31b7a98dae68d094a3396b319997e289ef389d09c251f23296332eb3750fbe3c0bc9ca3b30c10aac1af8a89140feb4560004e758844d71fc26693e5020a5d35da4648213283be0862223c3bacca945e27a0dbb90939ce7d91d5e2ce1e6d2097a28c9a3237d32580e3183187961661ff6771d74aecd538be3c0891d26f0bbd9693dabdb1ce18135f2d7db42833d7a390166c48968397abf9c058ce251b279ee9ffc4c7709ccb077c0d21d4c915ad87c9143300c3c36b277eac6c2929d903beb9d1ca6251aeee830fed76d63844d7b73805b8846ddc7be2d377dd8883064528f0d05219905a70b370113a01227cc8fd4463360be2c62f5093bd1f7881d51fa7b71c69fb1bb1f6ebf00504dd41594d168ee702d81b9c6441d31f97d587ed7abc3b70c382eda4e9d3f64e86010196b8613090d3ea64e7036fb9ab876d5390daada4c74c0dc4fe89e2ec0735d3cedc3754d7b922a6d40410e51a47022c6477f036564555ba430978216ea1681fcc3ea06efa7d32ce98ab73f4823771e2c9f022cc84143aa1bc3913f9b1867c4fd3a27c6eb1d9290cfd457907655d44addcd3ad2726dd351e98a0c2bc831745e3ddff79b9d228ad638ce92273290bdaf47eb54b2d457c6df2226981c5b41cddb291e14d48058de736720fa7151ea79a6bbdb90f3779159bbd577b011680c7a1fe3db4fb5bbcb2ca2b7008ea0d1c22970fd6bae591564070006488eb5cd0bdd12f3a477125293a1fc7515329b1c1be54f53999e874f522b4633c6276e5d27e02bcad0b75c2f4bca4529a44b8ad2231b608b280bb7d9d1771d1baffedc9dcd906edaa43dbf43bac2652cc1c78586e520da1574b3161e043e755680380a511748d8d6c7078d2cc0bb699d6b2ed7e2e4d6a2508536462894188f06f570c60f182a92882b643350c22b85eae96ec9cf1300cf7acb00e1aa2e29a3872975be0ad3f104a126e5d37e640e53bfe7aaae15e74393cbd9984c99b293e9ea4636423e811dcbc585e6a6be97db4f9e187887568ebe04ba8e47375044fe5fac39b7446dbb1b0b196530896fc00c0b012fdbe40d13e5f2172b2dc3af6cfbdd62acd0b13174e13cc126ca5b44c341cc43d9ffda0d0cafd4b8a54ca125724f653e4864d51a797b63037f48c261745311bf8d27c410b60228c13afb6c184965469f25ce033550bbb03ce70d5cf28a2f219f7a30ad2cd72ac3ee52dc9b56a0fb3e5d2bcd8120c1260649621968891b1f1de6a626e14d7bb2f8389faa8a9e97838cc2a67c23bdeb028f6862f1fde349915ac1e69d419e5740f5971f559a4be3a2da91338831fa8dda71568e160f06021dd75dcc772b6802c70aaa5588087ff01d5795baedace066395bf32e13dc7477559200d799c4c717f28a21b82945a1a4c4a077e89a149b94d891d2a377a66d6eeb3aa92dc1ecb576ed1241a96814a46da8ccafbcb081b5736d4e90a666c3368ba6792bef17b6e02e5840dafbbaef7df9df9d84fd950d4f0ab930e0d735d93dbc923f4f19341e447b2ce22fb27ceaeccc67dd6faba4550c78b61925cd7d2fd789e7e41fe6bdc7e451032a442f547a4e64ff4dcea090dfbe5eb2ed04f5427df0d8808ae5bb2e9fa79626f4e2c2a6ec2485e54f251424747308560e11a24343e02eff07ec4961221f799614b8713908596d8a400bf91e5d741db91bf1890c3856a7bba7876e1f6d3501850eaf899c0eb76b50fa51b65876989b8634215a663cc54439d5d27a4999d4fe8270a00a2b30046aec809dddd8368885324c129f3a3ecb8cec18db0f046de988643a34a533b52b5dd140b9df3897c8c0aea401f30ff0892df608b26c5e7eb3b17913026dbfe757a3e7fd88bef645700ce3947c6868e369e8e60eeaf4e746c6e3f409cd6fbb0beb404188aaa6f4c5eae5e8ff89312f6a7d8063c3e008fc34cd74f97e52431d42857dc5f7bf86e214f78389e2464658786b5889d33c91ab278d2add0122ec450ce51ec529e307fb46aa7f96e98f925119ef3bc7d836346338d18bf4859e9d67f907748675be65321a46f3312877a34ffeb3ae7500429de8bc423ea758a55279402155d106a446566f0d684d50b77cc97b4034ecfd11a059e4e91e5cd40a7a78276330ead47a3a32c7886b4e3ab94d2978ca64adde5453ebd46df2f766582a6ae39377da521439479d4e70f03bb70b101a21f8eaa988843762525236d4212d1d31785f3b7f16911f897a0b0d6c4acb63fe03feffdfbe821c0d908bdb83813558b9db6253abec82205adcfe71e401e0897f30f3fd192e534f26736359ab24a959baaee44c446eca96a1729e4b900624533c710c1a95314fb106f15729dd62b74b7345dc242ac46ad0114bff663fc011cc2c16dd093413758ad1246c435c796d434123d5798baff894a6c60626b60750c505840776e32000f44d18748ec3b38dc2a4ef87e1ee4f130e2768b38d45eee75eee9d7faa68889bf22295914c9b43eb80ca335ef4a09e841cb00b2ac34573b342d4722eaf837091d6b4220e1263290f006c2500534ca478a29504aecda8bc7e1090fd63d81a9815af311f338f49bffae5f206385e2bd2ab9d6ccea77fb1b1263ecb428dda194dd756b5fb13ce1ae19532d41669cca28711eb44c86718bfae91a382a1e8ca4e1e502e8686533c04aa4734cc189df5c12594c96eee18d21706858c350a3129a873f7c936755e8ab373a35d90b2ef271673359fba688050318a9644a66737e2b66603097e4005e783656d71eab1b5c79c7dd21453cecaf9e417534333e3c4953dec02c44da472495a0086e29a017e0c5fbdfaee1948d5e4ead29c78c52eaf14fafdf09ec09c7a53a6a8773cae0be90d2b75c3be34dc3c36b6df59dc148c0163f3480746fa027a32c953d04996d1c8beaa2fed6c835c9a56675d1f2a24e731d8cdfc809f20744cc0c56fb7e0139769bd057ae106d0487954bbe51579cc42b303df1f07e42ca58a41714cac5d669e43457c7c387d3fc83975088198b395fd3b4965c42c7b0d20e2d5b231d03ac30961d6b9fa1d12e73b7078799f8412ca3021b4045e3a6b34c976b281e327155f67e5d76489d4243eba0150d3fc9af300926debb7d394b6b121adcbc2a2351465ad06b497059430d46f970d0475cf3c8d605c16094316cdb2c6f7f6a6141f4d5ae01ea3f476502bb9fa2bc04fef37625d89614c480a3732cb6643b984549bf623d33b5ba9fd50a77e8f86edbbc83316810b0ce86f4cd2170aeb9d611ec3d8c718a83c7a20b79dde15dadb7530ded64d4b5dcf38fce242582329f9d6b2d70e7f78add99abfe700b3c0741d6ccd0cb5a8a5e913724fc3a89ba16547d2b3ee0c48e36c1a5dda14e740b8955e03debb344bfccb25c1888524de63c6804b546c419fecb95ea41ae7324275ace92a99695afd356abf5c32d432ae104654a61ceae1cb409c5ec0c6e13a513ea6e67de8101046365b749c25d29e4fb1e193c224dc03ec48d9aad7d9687478395062766f1964e7f75243c98db05269174f145149f816113382d8bf337d028b2ed03b98449e48f1c28f3dd3669d9c17a8a27fcd18d28abdbd3a5057f054a8746ee9fa172a9467a1e7941aefa9f38790b2878f2b85c4ac240b06d9019761ce37d8782db30fc6a6ed3094223a3b96efd290ccd85702a4ecb8dfe069aeee384281f705c1a1710fe9060439b05df3c3f7673f38b24fbb9cccb87ad2e5dc84a75dc77f2bb03a4bb700ffa82a017d09b48e6e1373015469c09357e0894aa22d3a0eb5ecfb078acb222d004e8e1b3f46652b419bedf149815b8cd143402f70220a76046c1c3cdfc8b28ed911a6ce754c718c20fed989007d07389648f2e4f85aad282f0ec26cfd5afdd8478320cdea21884a44e618d514eded2d18e5f51b7c7cbbbeac558751ea844d937d7f1c7664dd10408aa2b23bf87dc57f8f4cb64f04bb2108e6899ef2f7b2e449f3e429669dbd69c5ff705ec8d616781a512a602469f1880c6ac596237d602ce35fdc4d9021269a97fb8074c890fd117dbc587d5ac360906cc07f799d3f17abdd6496cc8a9ebaf5b8c2231097ceef6409e63f2a25cbe306f79b00ea9bab38bfaa9bae97b2a14f6a50fb2156c9331c6dfd2950524d1c0108dcf44aba1002ea6a1ea530eab216c731131b482f52ea75415fe8eabf4f11ada7036dd8081523a17b6010b5ea8ef0795bddeac1e7f42d710371f00d881eecc0d7e8e0900f02927764ca33070ebd12a841d6d3495b380a2fbc346423cc4b11a2fa1021bf567c3a8e3e9ac87f02cc16098bea6ede1074c527dadc29a80ed57ce3eb684659039462ceef603a9ec1dfcbc26832f5e3a27c3b7b8e02b297e8881d9a528a02d01fed9b3c1f8792a5f0570c8e261a5a29713bda9bc0fa4c9b5af332d0bc0988a883f9f1dd4b1e0238b7aa34aaee15f23a90849e7be6af8919888128abd00afa80e839852b88bfa0298f878e0a62a39af6140ea1cda4d37c8baabaf07498239a04deeeb59b8b7f8e43fac96c7e3e11ac7d6fb613d26643189e8c068cba9ac075ffbfd3ffefa4c8f26eb34bc914384b07aba06eb957a21a02402ec0b4ca122d3d00de6c16bd64b04d98ada1ca2c71bf3fda34c0cb603558337619a531f4e786224777da08fe0e54e083ea0f6a48c019fcce2edc4732b16aa203c2dc77eb7ff6b7047b524dd9bdb88b01fab841b4c3cad0ce22e7c8870d31bce93967f09583e635de14fbb8a10fe95aa6101dbc5c4c3b1a7a2722cee3b0e94bdf39ee8dd9608e6e22e92c6e2262907f72f0a5a2b7af8908ea9aa924e41d13cbba7cf1ed1841aec16051a2faed66cf96713f9b05e502dcc5722485ac11dbc3889a08112c5f6b8e72dd57905ecf8891d98ff4fa615b4fca0fe93f56a21e7831d0a3eb363d5ea20b2120942b434bf4c3586b3924d375442c59411d8f91790a16ba8f2bf8322b8631628b3cb201b08dc02eb2f54fc6721cb2beb47ff5f62d01ad019177dcf94bde373d58991cad74b0b37db45e97f03765eb7f06e8641305519801e46dd422aff8c65dd78ce8dc2ae80552e658876613ae24e1e082943fc6ec1f53ebf60bf8acde3e5f9ccba39f2e443c190286bb4b526e5fc38c69be2abd470f15e3505e9f9446dab80712ef019abbfdc16a3d5b818225313471c0b45314fd9706126bc85c3bae0b9f7aecfc80c7ab898fb4456797e9a748e94dd08ceff58f18441e8bd41df47a7022239ffacad6a79325f9421d08f4c81c621fdee3bd9185c78f1768aae42f9d6e74c5d4b33fe454a754ccbd5feae7efb687004e569d79af6cc538eda04341dcdc4c140e22ac17b5270cd37b712b6195f3c0c6c299984e78aaf3be5d8dbd785e18b0b58de4ec46038d545227615983a5ba217ab7a0d400b1624d9fa254b7d8e092f06646c9ea557de72b8dcecda8267603759dc58a7ee097566048cfa4580f790b0e9c2a8de1fc05b48ffaa20600a10388a77d44b4829569202fbe10e6e54dc0d4c39642b6b0f3253ef7dc764c896d897a0ac5ce077d7f2aecb22416ec44e3fcae0cf99f77ecf9e3aa85eb7a50a1bc5a279c40a45cd0732a99fa967fc870cde5cd9865a89362bd5af786349e737b70fe5c54f52bfdf90f829a78072007880b2df682906e611ddbab939104299c0bb78c182642f8909a93e58d3d56002a9242259e0a06a674af7854203f2ff8c55b8f3c911a140630ba6832e7ced3799fa6e2e4f7cb6b2efc26327e028aeb52865b8328bf2745145bd868b7b5fe7bb1c03bda037be8b13faaa9c9da5db6fcf055e7f0fd59ddf66006a1e2b33a815bfc0d1ee7df62545f359ae0a07e001ac56b59a019674b2e0edaf2567a35abb0b40c612f65c21af620c1a8783a09cda88c01c908f688c893cc0e90f70bafdb3a80c7dbfcc574e0a0eefa05637be9d9cfc8856527a829b0207d17ad486c881e0c8074e8e807734523d403e0c4e7dc0a0bb3eb84aaf01df34d2c00069a8d3646224a7f4bce96804ca74d02069812c538bc4772b01b63d1895fb7e7a0771ca22e6efa73d2516251da333310f095608b9b9c9a0f8d8b808268e3d710218289a1293df318ba543521561fb57ccd98eef73e7565c362319d07b57a5c67df699719a4ab70e51235c18d59dcc014df08b1d75653369dc16b7102f4cb534fa828efaef19eec0df9f10389cbb233c06631ff5eb4e25547b21bc5d25e09aefc978e98a5af91dd5a748431d774b0a1d38423d271da3a9e1731f858cfbb6286c152241a667b723024f135d2a9436a035c5633b420f38175f6ecffe8ad869a56c14c78f89712fd35c5357f71bfbdac3104642b5a62f134e7fc8e9f047dd925b4d0e9a709172ca6cd3c344dccf0b5d17f7f882e2b5c81c570bf855cda92d1d97bfe2f103ee264332660fa57807a58f9ab6530c7b6b984bbc3a7daacce621642513e85f37dbcd421e6adf5ae764b9b64c2fdc47187192c68f16f615ca7edf987d117739dc7920de9d2b7f917034b37444f488161133bcb609a6b586cb32d41817cb502f42c96ef625ef6ba7045cb4c8d1c10f8cee86805e63056848534e04b01889d5e7614c1c1d87f9194044906db2bbbcc96d88cbcac930bb02a56de121a107b51eb67c86d66db7b59879ed79faf887449a34573f4bd38525acd4a355852e0e6328aeff41343a7bd285a9515cccf7d27636e3a0f0e755eee1bf5d4c8a716e9212a7d36bd7250d6255d7868f75ceb2510d8a36a14d2abf7abc0bcf2e33585cc2bff3784b890f78fea584dbd84d886ceb93d8a1cf6829147f22daa19d85d5a006e01753f484a99461619a567f1729f286b51e8d3d4205be768d22ca07375905853e5cb8c5a4b3532625f0bb4a1563df02ad233fe0f1e3e88a791ec4e0c07e1d621a9f052c963aa6dde02df6722649340088c26c83ebeb53c49473d0822443767526aa422afce8852fac6080dd90ed81b0290d3f1d8b8e141ac258c26dc469736855636f63e0f251fa4184678bb039ce0aaf5247a5aec47d93d17344b0d701a1168df0926e7551c847111ea00f37ec79d0ab674b5c01a2762198df803b0998561ff956a8114ddeac273b53c808c5774c632e26a1344608e71cd720d901db9334eb6e36e4c27773d9cd0a40de072cf3bd71afef9b1bff0d01737a8dab3ef9dc8cfc89956dd50e5eea663b7e266ff042b22ce2add235ea5628469ad7b50eb988cf020d80fddf977df93f1031f74994cdb4d88c3310197406d0fc1379e3b91421596465040bd500e275b5ef1f77b4bb105afc2c4684e0c6d6abf6227f48e8057c184bf7313933141581e95489902f6f0ea86c643d9ba018febc98694093300f851d8ff52817f69c805c1fe3535bd9ddbbfc400e3c217543950a5c3bc99838e114fd55ea9200b228bac4e9d20d87c15564f00d143d1e00a45954053da15242bfdef7ca2cd69ff60fea8896ff9a4616d6ae84d9408ab8733a87a5e3d642e6bc92e16ef57243a359e83c51d5adf6cf5f51636c9fa8ecac699b2ce78669db8ebe04f43f38bb53adfece5ef666251bd4795bd689716086815ef76c0d8d228343c097efdd4632555de08c69e98e2f91738776ef88d203adf3552b503fd4381385f9f7e7340bc8381da712fb952709199bf570b45069903c77dcb608526e8882724dd1a459affae4d999a1267be00a1f2042ddd899a028be5331860f9b39d001ff509e243d9e636346ad4189c4fd9351d06d4ba128c1c4e91df7f96fd71859cd07c8d378927d0d1040a31184a141722cf8e96d72cadbec660c178944a7106cc3a1b97c824c741f33d137503c1e55575d538db5032717969c7fdcf89d5faa692fe9016f1c7b7477185a06ec6ed03a660b36ec115825628b22f8621fe895c04e36d6f97fb523295d70a2eb1ec2a1c5f8ec2fe9dcad70094b63b890b05bcd173407f092dc8373f25c1420ccde3bc5084c70d5eaf2b2b1995745e4cfec75fa6b882fd09c31e5d36e2a57056efa4d475bc18f007891f83337fd5a9cf262daefb57e56d830562622265b29067fcc42e64867fc71501cfad996841e60f10244b41ee01622fbdbc5615050b9be2b08b4fd7e4f6c06227f720a8d02c410595ffe8cc4aed707f022ecf917284a8d3e871c50cef30328f69c5c526138b14298070215994634fef1cc2213713e90bcda24767b84172513f066652b9074f34a0499a6cd0365769c1a3ff5fa10b3380c970adead64306c51cbfc2d4c44c8c6f2776028b49f11718466c3861e40cfc49a1860067629f36d52fcf1cb222958c8c65269a947067662bf24082adf5d0f3fd9d34938be4e22e1ecb8453df2dfdd0f81c3c52ef28270c1b08feada6f9ec6ddc177b2f24b515021dd74df595bcc35a7aa154bf6e770161d2969a54bee6c3dca42d7cc444372da1542581e840c4c729259e51fb6e753fea441b6c4197cc6d4d57d3b491d68e8e4ad2a8f4d10f99b930a28695aa80c109982d1540289cca45f7267699ac0ccdf594598d370b185a117ac3afa9764d9525b550d12dc6f2143d41137b21f1ed0b31fc430d934ea2a938fdd60ee85c742645f24c6770e3448b59e7344b6a561afc7a76fc1a2efa8da16fd3601b9a6843fc83990156f6746e81836fda5fadef4c52eb54da10792a8e6048592d0b5f7cabe31963ef48b4f0c68b0c4b6a729de7030fc0596904f1b0e25419a225f407768e8253a989fd4306fd71001408f80beb6d3e7bc30755cf9ed10f0a515cb8dac5d4a0939ae50fd5e7fbf71e0fff878bc88e040a46f7c47439f9dc6e9e8430572da2d4efebdbcb59a7df4473e9c6015a84b742c519a8b5b9933a58135dab4d0feea5f0efa413d57737b3dba0de981dfdf23aa51e08db8aa24a7d68ad2caa1283134045f444ce97473271c76572c90aff2b4f10d006149818047cf2a900ef0ef34a6406e1bb9f5e438040a6c27e2ec197c2357c8cec32e231abb48b20407994403688940c4b8d6a0f8470ad695d24aa00c083992857a00d5889d73265894a0307bbc546ba01bf17feb60e2dcf4123a721da5d59f5a47e0d08d949d19d20c5ac7d694b6417916501c89fb0518b0c5ab0be8b3d8f60f15eaadb2a65511273255853cce06e7616db6dc48e79c06e63492d5243690d52ec79a66a0f1ac65d5ea2737b324c0a8a1c72a12768e406d54f8fb5419387a270c6627bab9f3f1cb00774e639333d696ebf6666699a574a283270d17d05d68c8e2eca900eb5e72a7d0e6dbb5a7b23d4c2e99ed7808131b5b04cfa281fc0d38cc5677e55fa3eac13ba2b325f887698d59029138dd3afcec9f950c6578e573973e04e6302a33c7a0a0d10544ea43d5f7eb5b5badbeaf63d4fc2d636e19aecc54ec6a12048226a878b66ff7e35c114c438c0423982df94ada9f80506e3aafa4f90eee5220030d15b746d0ab45b37ed63bda7c0e7241a84a46d79a6c5bbffac788e9119845b83364aabd5e2c1fbb8909f23ffa56001db509ffa3ca57da35c7ccb14d1ad21f89c1735a016407d60518c5b6bbce4e79dadd126ae0f883d80723f9e2e8ccd75990b80529c04ff568059b527b5c82cbb2c14851e7b252464cde9531eddd886ba096c8a904b516ea01f06f80e682234a1071722cffcff7dfafefaa73b2431b31e88271bb7cb93a596e48470ef32d4bfa7486d904fdd7babd89e1756b850ba6318c68f05ef45c6b92ceb47833f2f81950a19ff39c0f1042be5a442ba6fdb738580713b0058618b4685e081a32c8208eed57d50fd0bf271d721eb5a0bec1e753066be82714a0b93aecbb729d0df8ea368741ebec11781c067a6d48821822b370186b2e1e6c20e7b2c4b0e55e7d6e1211a3fd6fa81adfd3a7fd114cee79fd75c7aea2dd2e2aa87632840b2c75d13a4631a87d73c7fed14ebc9f4f604d22c1408ff7d27504fe68e4d0e8860daff1b9cfcecb29a425a63e004b454deceaf5f6e94b49fc775400f1ae021c6b582774c18d280c7cf6732e455798c1f719a3c42e6adf011716a38deb6ca293a57a1d4a51f9412851db92d054f22edb91dfd62e6b52be3a64f92521238d6393565a46cf0fac72f38a3cdc2b60a6ebb3eff89b1b5db544941b46cf64d68ff555c8653cb86ba6753b3e3e87bfcf62aa140be722bc452cec56fc8647d8b01edc7071ba5a04aeb598a2eb5067dc4c375cc81f553a11d586f9bed470db9916d6431708ef4f3f162549b0d8192f36159f20235cad552e822d6f27fb9ca14b1bab94dee7cf81be67aaf913eff0e512b7904afe179466adf109bd74bc0c105251b91557f050b13ed4185b69292a9131b03298da5dfeffdd8be5dda7f65f838d0b6fec298c8c445dfe6421afffc3ab88207974af4deac3c9805243cbe41a85cecba93f8ab6d2ee0a7cf30b091fc739fb7850d190a7289a0c297207f87b159a0561d55400fc6de42bc18db19b0673d45d1fde93d6ad557011883186c0c99f01439e51a213fea6958e784b5d7a89b74a64910c758a38425b9b9e6891c32665313b825909ce7c5cc3498b061edb562b8da00b0d94e3112051023c63848087e41cb9e9d5ef30e6dc1c772c730e92c36a00fb5a76f3184113b3aa680459b4e4ecb6575644e27144f46c93e9c2bb359428dd05fac819963db353defac9ba605f6506ebdadf953895a60f23cf55c2ada02c20c16f4887385383602b23aa16a6b75940d19b404bbd5a2058905cc34b70dadc14519a119bbcabd533f47de6f4756944966a96aac379460aa55231692cbbbba35606d037561a4769f19962d01dd2de5ff4b17dfbc4a04cb20ec7e3ebcb0d8326e0d77e9bfb3ec0b17dec52dc2de60b47c1ec360e0162ddfc82018b50962a7a7efb97cc038347646b2ef453c6af4a9d3b7c9e0df8a5d9018dafeafae65d8b6022d0400ea512bb80f3a3dff9dbddd909c792d289fffd196ffcc3fdc3ed4597e182b1cacc9eeb6de434545066cbdb5fcfdf0b28205170da8032a104199b5451e6a65ea15c6898e681531dd096741dc3546f6fcac4cce11c429574065544d085c2bbc02807993f386b6060ee0b56caafba312fed026f524ceadfc55490d8f41160fb4ed3d702359b320c0ca62944a833e344a8d24079ade725b853b3ac979023504cda981810cfcd6b230540da59783ee0cd4cdd773c16b1aa68f52bccf825fc6bf701c73b73522efe067ae4762dfbaf4b6823c6cd7d522a9b84b597283cfe5ba748c42c1cf9227d8a28d37be1843c54e91e2a7fc5cbce2f689a1b79a4d88898f8afd619ee6883752eee43233b12570db76eb9fb8791bee3ff20c7b5e77fde9c64bd8e8c3146850cf0bb81d74f72ac26303a866811da35388113158a891cc2ed6746a5581aa163ba9c736031e37b54bfb6ff00b424e50102554109567aa16b814cb4ee905800c820287c1512bb241da79cb4f5eb276579a83746181a071f6215b3bcdc90cd057e17fb06e43e19d92ed01df4101d43306b60b4aabaf073e4d10a7844c0d47ccaa06b30fd437488921521de8d354da6d2af9ff5873e4ab190cdc9445e4d629b5bd56d866a7d292256d51804be07f35c336a2a53910aeb12ee3ca68221e00d3909a87a0cf76a55d4140d9c5701eaeb7d05c2475610268207ad0d674f56db3eb62d8e89d800655150174b8a4cf6c747124dc15c9798e5ba0dd62a04e8e67b82f7c950dbf8af6aa9197382704a911aff2e458ab934fd7fda4a5620dc02185e972c93cb2b8a8a8317fdef0a9e7ee03f1babaca7760a941114f43c96ea71bc00f532a6dd63fb497aa8a4421c63eac8ca722f636a27cbbf94ac3a81f000ceb3b97864acd43c2f07fcd2b55c30507b2ef1481f6387c33b5902175de5b8baca2be6f900af631c26ae1c93ad9404691d337188555b4aaf9a70820e53ee9caf1b2f284f62908287bc1abdb7d55357006326a6177890a79642b0a270e94cd629d65e0a1eed0e8d340e901c3d720bdc3a28f01a1876e26db30e0d0323111c7f8b0bfdda65e88a6da16b9af2938582a1a16e9f8eb054a01a50ecf05b2dc5a7619aa6249d865974dfa1f2c69c5524029eb3a05ff382b2573c30077485397975a9e6bfa649a4a8072b7ec42349991b30328d330c6e111aac471c664210ca01ca57db6ceb3b01d9c3196140459b42f70df7bd24d1bac7a753c014e3a407df854e157e11b833c19e613ab7e7da017e16c1ac9bb1df67236ab56c013f48504c3b2dd19931384cc055fddbe83351d074af7da1119538b4332be2ba8406ac94c7cbb2c7e98cb68f08265d17f94244c0dd88c957bc58cecd1313bc1e0f1e3b2361381f1b58920d6618890860db7a321c8724c41d826facc5c56f9705b41d3efda5fbe0f7ca3bdb93099250e495136d2f8211b88202684749f86f3d26820c6c7b0c367a1ca3dbfb31f80986b1c3389b4872593d31a84e6a25b131091974764e01452fb5270637e41bfcc5aaba6632e164e68a2811ec5d17f6b94cd79128a9512b3f2f7dff1f18132dc540b36f1f2ce798daff58ef873e1209e6e2ff9821f26101241e8f47ba58c52875d6ca85dcbd1b72238b540bc0a9c4c41ff33868aa0822c70a8870b08cb5d80914ef160a3378da4c388d1a7335363806bbaf1b0e9cd689327dddfdb327b894b091457748d3dc30e605d63abb2ab27606473b94801791e3d9d0692782546ec582337c490fe09943d3e99e46371c1f788810f32b94acdda5936304a585b3d8ebe64092d1b8f6249989785cf13c6ea70bbd94eba8f281e38bd796c424dbda62fd2f9801690e16b6ccd9b3ff9ae9897b245294ccb71f25effbe4f0256d3f514e532794e792e0fcd5b017a4a02e68b86b0d22a169a1d42b7e20da054ba4f8c4d65fb330f729231de9a258a44895c2f926e4b9b4d16befbb2ff1ae9147cafebbe32523788f504bdc1f95b9f0a5e87423867d9e2e3ee6a44211021d46db7cececa656ddf6338ea0c77719217f192881d7cec3f3a26eef04cf2b1937900bb38f79f45bc95777bbc11636232d8e198a9330be3ec4b4677afd2342b98a22d6285279b49b2f6372f259f84fc38536f3900a4d63351744894b4ae9825da6ef650e1dbde149230482f64abc1250aa1c51c4c304db7323de36c3e6a78de0a5fa496246ecefb657edd048221be03d15baec12e72e34a1727947a66acd961c35368361717ecf9177ea0f15bf4eeb7c60aa1a5baf6e1b4a2445842cc3f8b2078b62df2424e59ac2773075a749661cf085119f8a0d997c8ba1efd3b84aa090b4aeddc42c730500d295c1462e993c7c63b338402843a307e16b2409a6b4bab98505dcd4e53ce1b339ae071f938aacf44853dd9b66c17d5a85e81baead308543ee1e8a1b64badc316b1a0b250d36b5a8c992c45379fda70f3dac5382b687f470b91640b2aebc55cd681f9f24df4b9da3303a61fdb1fbfb1efb93f38193c6d08b48d0946a2a7190202d08b7f65eb7fdd1567da5541540bec7314a250e10a1b2d4b476cccb05764acd82858c857392f532b8eb1de95a82c9df013709342ece1a1371c210791ac5b45b70e0c3f0cd0ba6048e413dc458b593450e32c63edce10ac9ef21ad1b4dc825c1510c4aa3278bea3e8bf7b9893dc9fcdeb4a510b65679f608db85940bad7071b7f83bfcb61d08b211e05b255e8b022ee90812189a69873934cf8e94f469cfc7a7488d51132ab8ae55d90bff0d5f9370ea6dd847c4f2d4d54c8fb6825004dbc32d869a056e9cf3cf3d1e0967579520e3eac734eb2621576dd5ab1ceb9fbaedcb225468fd3df2b473abc7578dcc33786773c1da1ebfe2d2108018ae2f93d957f72016bd2fa30eb64c9b3b9df3046edb284a9e86523040beca619afbfa1fe873c786c0f4d1afbc71928f58fbd1bd3ee2f1ee929a5181c64fc24a351d58b5567b4cfbf0228ec373f0a15834f66191bb7ec622ba406f58ae6ce5637e79d7bc83c632350845dcb8fa14241ea1988f0c41ac20ed17d3ab20e5b721737417532e98891c164717f0ffa66b1f8473f8bb8a6d5d2933cf26b46420f794acdd8e904e22fa4cdb530d8c43be850c85ec143b21c3a6faa21bd41078a8e509e9fa09fc48d5747f37652790bc6f29b60adc9f0faebe7ccc21a69670c58023355c4da98852a2f9ed027db7ca2012bc6021ca6b6fea003423405a3c3330803e7f0debfdd0e7a568ea4e9ae51e930dda296f42ff213df04fd1a5420e567125d4f27dbe5adfc67f4e8544f78a1fc3c78507e5292bb970d1be158484d394607000656bcb5f27a830ff7cc4fdce386ab7ad67147cbc66fd57d56a909c3e850a4adbe8192bd1567310673579364780f5d7da7a17dd2ad247b35580961ee44d288d14f72c84167204f455490aa47a39f955b56be55068365f87bf1307b3fab238801fb59ca41283ad2c16f85ced17dec48590f925ee84396ce2ad70ac400381ca959ff1f4129840a932daab92112788bff75cbcf97abe50844bc4166fbe97b7922617b5dd81a91b9c0a8f46823cb168a75b5eb9aaac37e6260fc40fa3afaefaa5bce19e43d1575e4a0f9d332a29f123aab1a590182d7dc1c5624de05d4508acee1bf9ee36d6a33f3385f347f4a885be7b31e0d4a6bc8249a1641eb43f1c1e6994a2ea3b16eebdec04acde26a27d6aa9d7d0339968e7c8721729d85537e38118519fae8a41c965b23c0b7f5c65a38ef79dbef49cd8f975dc7f87759560ecbcc4026540d90a142e953e571f25927fffde1891b22e3c07c1861cf1f37759230586b499a8f4fe2fa31f5645eedf1841ba6527a492c42d6484b25b7e0c2dc4bea2a70e13459c34274e33def0535ff417a851539ee782ac5db3621073d814cb7acaadf1834c73bb36801f802b4b96acbfa4c5004fe59d2bd32a24576cda0f33b41fdce3ab5eeac4c47c8419b501e22c94532c1899f50743a4adb682ceced311479af705b43f718e08946112c486232f6de6ec16569d1da3ed57174bf973035b52fd4d780b3262a732c1001c35faedf21a1d691354bc3662ee43d9453e5d393070d32b7c62b9f14e0ffb845a6d0e1eb2618d2e1b91d20c36d601620d4c64fff067b9c54891e30824e5f77c1944e8eee66d624d69e5465d667c326a9a8ba9a2a3919ca3bcf9afa0d70300337a9a109c3b77e52e07420b161cb735f8a55dc7c695ac5cb8a2bcea0365560c74f8160b10e879693db13a131f9cb6094a16b4076ba397c4bdcbc011189a6dd73dc7e9483a42ddea5ac11a7b3c0aa9655150eba55a3ba56aba086b51585b9037283c6b65955417cc8a6b514fbce1da452ed2aa0a83fc019c85ce05de6900191589ed772623ad73283a92fbb9b07299c89dca21d452b6537a695a55b286fdfa2e37eb4dda913c7cdc96d8042659533c9707a72ddf0bdd652722739402817887bb96cd8ea77fedf9da12e51aeb58e837b41002d4b7f563ae1f07f355dca70179e36cc41e55f0fde64a823708ae088a69694d4cb5db5f756a26f21b99cd56fa04ed050dde746f06162869244a8605d766074d590e3b5d350f06b68d731be29532bd24979080d78aac31c207fe53614a9e74cb36e5f8b2e5a41bbfbe0783004c77a0a649e1ccdab450f1544688da6ddb016d10a819c2793158997924ecef636c34c36427c29f0648423a59aa0ba9860594c1629f69a5039731c898fda902e17f5ce62e541b56a84c6917cacf72cc67a137ff34927c58ff84b6c8584c68141b6d3fd3a7ab403ecbdd08dc1ca63d9ddb8c2de8e7a9fb6f625140dd123707110a326227ccbc7842bfd5aa9ed027fc155a2ef8da030a6386b3312270f548eefc6c88eb20f5c46663795dd9631dbfe45575d5fa9035af6546b47d679b02036cc01881feaaf032c5b23241b675b41801d8b37be5a8961849b9f56c79e0bda89d700f529926fa3eab15f7351ab8d67a5fae2f6a71630667ced0ad0f9d5a754ef5ec8045c8dc438aefe8c0795557b2db4a93ae0b410281acd793a37b0d66697e15c1ad2f244d537a554bf36d53e160aa8ee9a77eb2053a3ea6ef5d435bcaa77ea8c29e9a373862d17038b3f41b7e342cee7b9e180ce21ee0dbd5838e7e989153353138d77d4903631410ffb91777db7f5b9bebd5d66aec6594aae2ef16fbadf63cd0ed9af01dc57b02ed7f489ab03d5e2eba8caf5d04d511840dce48355be0da1c8c5f65caeb8149a6acb1b59b50a4d8903868729d148e8d4274d7e91cb16b59e82ab10c8c3d714f4f54926c3ac55eb97b72d4ba78b72815187fcebef017dbeeed57b062df85b7df8f28348760806fea24f8c1600b6e5c9ad7e17e32cff29d42cfcaec2457f856ab7f30cd1ae01d321914ab0733a8921566407954b9fba629c0448a6ccf3187fa62750b70c07d89db7702be336f1001b33360acc7a05f2cc374a4f70ad15ff34c52ded921bb5599e5b86ebbb1e41b3117c465946caabcd25666fd1d7edf75c0706e2ab513775f8c8a238c49eb3b0e9c6ae3aba42f60094b7c7cc8ec07834d272dd997f7f44183bab7f6f5178e20479b7216729750a18ebf5677e9a1319e0b0a1c22d9fe55adc259ffaaba8f34f3b131e006b8ffbf47e82d244a6804293a9139147953c8171e7b0be689c35ff633e54ac95e6719ea7ea5cfeaad03f31c63a8dfbe06578c1abc8569ce1f9a00d1ef61d165e0d3e3b347a0ce03d44f4335074d86ad6e6a40f692d9415a01dc76a88ebc9030d41118f33be2ac3146de9345858c0d06ae288f2a7b7ac75eef79d35c84d1a3fd42b5cb7faf16a2a9eab9dcb33059ab867ac513bca348e091af38ee6ed54de90dcac28c86168038b5f599e713240bda2c40402984ace1410fd9c8d4e8fc31570865c574290a98f5768761ed68258807aae184709bfa986db86fa690129e9305a74e884df6c5cd5b45bfdbcca3aa3dea13b9b8e982e1805d21044a969a370889cced618bb32ca38ee18efeab32246355104e21a51ade50290b3206c8cb419c844ea497bff09863dc0adb8f5c3a025dec5e51ea3eec5f3ab824a5955cf590db9a875f9dbb52e735b634d8cfe5f28615474c354f346a0bbc22ab5dae87da94742ea3e7bd28a3aa8f7b0cb9cba91401092aa77b608d3518b2b04a7f1e93e065be6ae931a0e7a6eceb93ea78e57788bfb59d3b2b45ddb286a513fd0a48103ce416d08dd331f771526473435079441ca0a060e40f393b4b3fe77e16b8d6bc1c94b0800756e852bc0ddffa73e8c897dfd3c332ba21c507aed811da3d0be39ff6597a0fe50135a74eddcb7c4f68c863cb1837e4a208af34d561e0d84a7a45fe854b961d0fb1492db9862ac80f5ee5eccac19583283524049dc578b0b8284daec6181e1d4b308b1500584e5ec0504563a1de8fc00ef25a685101547a2aff6ab62886ab5811f4fb00e2005dffd74a21dfbb8debd16188c701514c472d12ca0a93d5e1f575d02cbb6520077f997465f797a2527d4c6dd28f1d1e98419c45d76b78f2af5df5ed4ba5de31127e4f1056f075a72f716d77e2a8d3201b1dacb59d22a61ece1ffd3e3d8e1b87e08fc7890b850fadf1334853acd03b1feb2f824fbc771c050494fee381c7542730b597b5eca41af19d4f9b43c22cee22cb27ed6ae5e0606f772ff849918b7de30b08a57bf21a1b89dcaf66c999507d032610e45d5094d913e842a98144744ccfaca195bcf61b96f9a5b751ca651d0a4af77e835c3e09e0efaf6665efda482573842bfe235b0ea00437d914d93277f0d76c0c97a5fed00b014560b27cbaf94e8e2b343d8869d771c9baaa7963a4f769ec11ae617228b5fedf936d75b7fb9cfda1fd0228a066cddcfde670fef4749f7f3b5ca0133893db5839bb2f69c9cc42d0d9854259c05e179f466ca8f15d9ac52a0401c5d4dbfd1606268457b7af377bdef8524079ed339e267ae1a65478999821828a6fa57108ca3a780a382a55b0558c3136e3bd3fe451731eed7e855d713aab78e9ebec43e6310ea79c7a10ccd187c86d35e9deb96c5adb4826783d21840e3c36048b3f2f96ccef819d125ab0ea24d9807d0535f1c855753cfa7def4a2863a8be3d3d40cac6055abf27b738bdfdba734f729ddb8f894429c05ee3efe25bd1d030a8d5a8e78e0edabb5d667b9d2c5331727cf167c6d5058747388a59844f0157ca85c4730251dfe464324010691bf767e287b21337a01133f1230cfbbac12d43bb40967f3775306b13e6e4fd7097b8694287d46f1a8671fa20cf33d51665f89de15cd6b8d4362574c690ba57187839447c95f69f5120e51e4d95efb0760bfd2c4c8991e1a4a8c0b79d1d614ca444c928a56675ecdaa31aa90931451a4ea464984691d322041bd05ebc8635900f4447809e503a112068034374f2dd364e16a95b85ce610786446efa473d9a0e350be60208cce974a09bb991a6406227c92711c01c1106ceec16c7ef1b218d0285bfb34af34d7b8253083eecc5360a820daa4237e441f071fc464fefd4b27edb0f480ef192ae587fa99a805045574ad5a0dc5149808549b6bb7d73852040dce7cbf0ed5f0721bbdc5824f56bf3875bd756c258d104e1f36f94b1364aa26a2e49ce0a11c1fb9d5e8dcb886abe4f0f9d1f27c0716ed71b1342b3348a599e422f56858ed5a87093683a82fe03022a95e4aad209fcd48eb1059091fa0744853cd044e69ba6c29f460e9f4d8af0a10b7fafb4f5c9c70aaae6f688925618bbc27d09cad29bbd93c37dcf6a3c9ceb309efdd0dd21e25a83645fbbfb2b87a7e93407e31264f89f6af4ed3e4022f4640961cf9d098b56fcb129cf4971dc3b9b8fc70eccca2ef332252979afac760e20af6cf26c6d09b54b42290f17ff4a9eec01dab5aa5d463b5c55573b9c7290ee33b953cc781fe99c81154fbe121e5e66f1750d326827c263ea3a9681e5dc0256d2a52975d400fece0314c8335a32e59980537ba80cf416b433dd6b9df0314bebb17446d0646c629235c5c6393d1b0d157094a1cee84bb1aa393074dbe4f16852fbae8a09db910af7a3bd2adccfeaf013d98587c51c78e96653539b62d4c636398491758c1746ff273716d7e0d78e63aea748bf9a48237a589d9baffe2fb64244480a4014e341c969d8fe6077cff35c998149cea5bd1706c6cfc2de6531ad1aa84ecddc68ae1c37360b95ce355293b8295b9c804f72c761f3b57ef2471fc2e183e90d14afdc70c9ef4e98e0b46643596e995263606c3867f45daee83114e74e50c3149d69fc933c0ed990789aa0b71f54b1c5a01a8afca063e29f4152054595a21e101d395a491ee8c6d9bf1d91eae4913a09d90910ac0513b10b0fc6513e17f96584c7d9cfa6bc86ab1ed366df18e1b4abc30d0efe08f1d3680096bf9b376ff81c43d04dd4cc802601329b3b26b435d2ec04c12fe278c66d20979941b91ab1ac55ace047b55014c0ede4ee29d0d99086c2821556f4a91179d56cd5ec1feaf0054a9fc0ce0af4ec6c917f156a639d91629985b13542d569483751a3cc134e22d614e84dd2049aab3ec31297a79acfdd5b2e77fc3b287baccee5157fcdaa1a2cdd6614f9db583b6abd06e90d50885a23489e7a9857472fefc7a5c475352a3aa08ad5d2d18845d8f0cb2f8859f2d758f0f67af6116888b3c5ade1e2ee78ced3e5f0bfe25940940b99e7bf1e45fb87f843462f3fbb7dfd8eda26c8af81b4b4ac7160a2e7f887edaef3a81912b5cd859c81e11946c72f6ffd0168437131f03a5340e714066e67f66b2ab2f55ed676e84fb27ba9428905ced0a5f7a119ace3a840a9c9af0d903e399ca912505c12cc4f3e6cdaba7e11bbb3135b4b7e36677fd6859830084d8c4b0e74ccd02d03157c021dfd255ff3537e405e3f1d0fe836c2f1cb4deebb392cc02e6d4c05c2dd910d0ffd18e42f674647bf1440f5166266f5d52be6fd9539fc52d487b82459e73b5e0ca251216d76c69e0fb4c4019e9b4a501ccb53158a967955c81eeece059a5b0207fc28eda87de2470627f8af7185a818b9893a24c81f3920a25e196d668589492cc434bde85ba8926c5fcb23c57f3d301937d59731a0a3d2008c28a5324098b2bbd263c0f9a1074fdabe4a85121b72a74d0b1314385c757be1a86b50537998f3efb14999ab9c69afd4fad3badc61eed9499025a69283ac0bb55bdea481d09ad2569bde73c6f6927e76035ee3a7f80c49cde86b0cff1bdd4cd575391b271755e2eb54e6191c58bfec1961029f178aa74497bfdb42aa6e59bc16d58c95073ac95842b62b46fdcbc309d9ee4b424dcaa48bb17c5dc6240e5b6a2ef2c3dba724a79f4ef1490a8797fad94073897f428eb08cc76b86a6bfd465a694e3d7c33fd9efd540e9c72d2ca80743774e246d1c8d47e55be6853f5ae0029283a522f46f93fdac991513ff38b7471c802023738a479ebd9d38e638da04d557dfa8cd4573f8a3b2a13c19e6baf482e8d9bd0885a58cfbe4dc53c107ac456ecd7a087b3bea4276d52a1479441a00505c66377d6fe693682738494b3af989a84b6252287b2d78fccb585b410c90d849cfe568103712239c1704148bbda4b342fe07a46d7b0cec8046a04fa23ff468c009ca41c4565a93b432fe50b46ac56cfc0f4e1840c4894bd3357726ca7c36ac2625d48104b707e126dbac7bd7b49f8958d6af26797c3037fa6c6cc0346872f0c4ee63a8c76faf67cc06f3dc6800e99bd53c3bd80ab522e700c78b0b1e052220427042ab99e8d5920d0ba4a0789afab4ff41b19c0d004ede52fd750e36730b063c54713c06f4eefc3c7a3c396b6910303b55651867664b102a26208e9c012ec964a317dce1fb2c3685af643016533fbd608ac93ef43cd92bd09917b4b99524a018508f708df0842b10b283f09d80a7026ca1f6dd7c69db8c65251523dd29d47d943e3f628b548130a0df5cead0d2e413be2a33efa78247feaf4d5d928234d182baf073e507e2ab859430d7270a60d30747f770d48adeed3292b7b2c958783e3717b685c822653efb304b304b46ecfc9ee45001ea15f7a3cf855de4a51a5b75bca745945c0e3f13649f070910081b74942ebdcd3517785c242d5b059ceb033d592b5e373a64d15b43f643dff7cff1d9fa3faf8fd9ccaebaaeac358551feb78a3c171c1eb5ab8c9056b7596719c4179d75f1d44acf1679051bebfa68b4625dbcfa06d9a8cf125ef96338ceabe6bc7866dba76f917bb5ddd7c0119be7f9bbe84a9fd1d83c12a5f408698c43bcaca2085294354c608856d9763d1b58ed569669943c1e3510f87358ba2acedb185862e1e8623619eb03d98ba6a079d9373b04b3d974a3df34c116997b7b0b33b3fb118f34ef348275baf7cd8fc77cd196936b2f97d2e6a215ca8aed9a51b165073dd125ae01170707890e3f77fb6c7bcf1011097c054780447e22fdf3175d571aa70659bc756783539292635cfd6b8eb8a2fbd6e5df1d928321fc95a167fd398411d2ca9e5c0f11894443c90e190ede01c600237bcdf17ff8d07848448112335ad5a0384f11013edece710a09d1b1e37b5028e54dfd1ce06a3da72ac05b46b57b833f0c6e37df1db78abfdca95c7a0f6d5782ceacfab292f060d77e640abd6f6c531891e69da9176a41db5f66d47234790340fcfded2d89c9ae5ff3411ef91cfdf4d3be397489123acfa987635d4eab837f9cb27106b37d55022c94f2bf2973b41c39648d59be7a979f871b035a1fdcdbd65993bc0cbeadf1851485299b9b977c025008c9ab8b32c97eff29392bf5ddccccc1f9ddbdca4ca42757021703b46f624c68b538cc98a12121528474c1475868c19d4986804b7bbbbbbbbbbbb628290e0b3b31d37f1c8e9b0c8fa1387002f16e06aea6e29a39400309c6c3163e8b5acf164c46dddc8ed07caa072c3531151173750bac1e886a4309cfaece8b19391b9eba279970b2e0422caef33b2affb141b63d4589aa67dfbde55dfe2b66d31fa5744f66ee8d2dd37c61853bdec3451882863163251926127d34f9ca5b9dcd98948501c1276b056aecb65f4d9f136668ccdae038e893613edba4f868cc2704c945f3291f74800d8907af94154bf3d173fa005536c50f9db0f69514aed555f90f6b5e2fbf8cb83e4d78adf0882c8d7be2097bf3c09b87d12d027f555e9d00534e587bf9ab08419d22e873242640bf59fc925987224c3ca4e8c3136e9581500329a54f9a908c61893d0004510485f286d6962058c47c42f0c1369ac18c71655fe54c30555fe46b184991a0863c450e5cbb83748428818638cd10331c610c3a5c6d805e7504950e26a982c66d5c8a096a48005944c8c998c0d1d8ec814dd1023f38215349119a2c80260c82cc124ca0c2616706a22c3e5862f51646650a444068c1b88c898b1c102543c8911030e16f87284130401c058c2e40585872c4ba0b858da07644e4d929a8c692f64311b906c20c306a2346cf0c42436864912d4f2482d6a1e5f933146c9bc05b7756577a4fa737b715d712291cc51b8fd40184e2b706202bba1090c3064326a46d54c8ddf0bdb997aa46b0b488e7aa3c62f4c81f6c9ad9bf632ba103f5617e2e344fab1564434aca9fd610df46de07098a1494abb3f5a23bd758de1f60363c42c812d61d2637ca006514a9c94c8606d300e1cd0fd30d6b8ad4292daea1a6afc3589732fa979ae72724137c62825ff7e2dff50a0a9fd6d6533a4fc5ce88f31da3e8a7493730e628c51070be87ed27d77e516a594a1ac45bad8b030ae0eebf8816effec662835d3b959fb26d77df9752791dc5a382b92b342a3b78203da68a38d36826ccce58713638c5e39d2a7f281c7f766f6707804fed87275687751ca9e1da3267bf8a0b6ab1177706298b56be3152e80baa9fe3c9a67f7a34ea8bbbb7bb84cd9ddd7290046d058c3ae76cbfc3a64386e1fb79f31ca9f3f5b93114afafaf3c7285beef4246ec2f776c13f8747e09f3bf6c5dfa4b5bb7ffb87ee66f160f6ff68bbda8b3c61a41b54ddbbe3ae9432eeee8e4be3393f8489839cda6fcf693cc250ff9652fe87c03b48000c60445c84616262064668a0c90e34389343163b98e2454049f913ef24452bd0e287a41df84003154f8a4fc2d6eeee6167ecd27425873262ae8851014622ea01c64c51c516269808a20897c1961a30c1c1f184aaa0faf7f6e8da3121ae80820c8c95b07240f96ba5c0fe49aa2bb50bb4c0a28595ca7f02901061988490c2534480a2fddaf342c5ff218292b0bf886b5f0b3d2415db2e4998a95df73739b93a21fef869178ba084b6526817be166bc7c7441ab573e4b7eb5d3cdac5eb84a5490dd74989863e4d34b9be38e38aeedeb5cb9d992977c739a7cfe9cfc349a1daefcae572bc82c0bfc06da852deceeaf84f6efa967aeee9b19e3f8efbfdb39eb5e38fb48b3ea1fee1ec7cf8330bdc49c5384e0a0dbbba404fb3a62fbcd4dd528699caaabba50c3195ab91ff020afcdafb884976482843631bd53bc6ed07bc80925153a25423538dae3dafa426b58f5a8c5fa87d2e685c94518b316ada27bf7dc12b7f8575b1e3eaede12cd5df54fe4d6a1abf4ca5bcd5e9dac57146d6b6692b5e1dde38068573c518638c316ad23deeaed4b64ddb18ddbdbbbbb5f8b1bbffa8abd32cd69ccc3fdab5b4bb37d6c6d327b46b3f046ef7a04b4d4ac571aa94a66d8cabc9d9dc944a95da1c57f456673fceb66d4bb172ea4a7777df388c7126d95dc6a86952326fdba64929b79a252dae5edd7ca6acb15dedda326874cf9152935195d32eafc6e75d1fdecdae9bbda5e3563c68341937d50a38c6892915192783855e09796215d7b9bb0fe936329b18db2aa678a4562c16a5fe43687cb058354dc494a879bc86de44d58daaae8a53a53457a9542ae7544e44cf709b23e78ef628b56d5b75504043625c6e95d3a57efce5e3a3c2ea476a05e4bda254bb9a48232202f235eeab7e56af38a75d3f9b8771dac5dbca472af08fa69641798fea5543ed045381add68243bed6146e41d94588004155debc6264a4d2a2a6b9e611082866fdf191ca63236e83c2b33585e91e0f92a4b49556ea587be94d6b2828a82abfc734cf92a9f2f90c17b54bee88d1fed83ba8e03687cb21a3b3ca3258122c06c5240595bafdf6feb1d991aa9b31b5f1a91bd05aa183a13585e6190c62908bf852bb9b53e551cee11f23cb1145c366637b18ccbe787ba8b13c1d667b3aca5f1c420c34aace9fac0fbb29ab27ef612ff6c54fa64ce577352aff93f70ccabfb065bebd87937aefaf7e1a4ff5b1886b7afe2e5bf273dcf5d3190ab5b35379d67ef29eee992b6366820fd00f7ff1bc12e3591028a076f17fd0792cf3177f121495839bbb6b9aa6757c0f716aaf2ae020c7fd148750a2822d757e3fb51857c845705445fac5ef1fab7f44188a05f70a4490534a54881a6e540b322068b850a9c631228b585d1dd4c5e26949cb512c06ab61d80a29507e2d5f30ae82b4a6b0c0f717abc0055b53b45f9065441935dc99bb76a4e7e3af2b43b5178885ba92c52e3b10b4cb818a29a104ae288b448a18d515eb68294a110c3c298a13356428bb9483931a466e77a10122f01ef91de0bf998081e1ac6bc4909e86b1a268f547c2b0a2a51f70a8fe4466ad2f701b2ea31449f9236fc87e78cff4214d3274c5d03fb8e9a57e67474b7d3796d0709b90782472d73aaa8ad66906a2eee9fad5bfaa256e8041c3598dcc663bdb59ca7be6f7e41eeeb7c7c63daad4eaf403790ff7a9f8d5dffcd2bef0c6497ee107b53fb6b7363882c4c89c4dadbdccd45c3a6132543ba85ffd48bc8784f66242ec9e552d41bbf697e3c47b4eb0affe18a692271c90ac0e0fe12f88a328d155f79777797957d3de59ba74e9d2fd71982e6cc27c0cd61ad6aee62149049dc5269a18d2344dd38e905490180263831a59a4903dc1a4c687a218c8a198e44a524a29a5c6b47d318368484a29935c79028b35a6502286882e9c698a303da8be94012545b524a7abcbb483ac0cd72df93442b04147101d0307b34359ff392e1e06604206008b1504c8328500aaa05add25a62e71dd054b4ef0a07ddd25a62975abbbc474e44d96fa0a099ce1e405982b76f8618cff2b5b0f9a51d1126954839438bb03254ee567668900a4d0ad2e130f3708a3868d8728d5290ed30e666aaa2e130f5cd490fe5cc2b7d490f500335fa0aefcb0035292ebf0af7445ffd36c41f77763661cd89318f3182d95dd573e689e7bd54b6edd66bdd77d230e272326ef3d52aa714a793bb1976d979d9aa7bf9bff7d766a9efd7ed058a27fd050da11f6b9a95decd42eae5c436eaa1c26a9dbd51ecafbd347351f3d1b7e72eeb86df536de115f7d8d27a45d71e58366cacdf7cac7ea93acec0d47357925e811627737efcb18a5bbdc26f6d23c2958d38b81daba1f3fb1d2c7499c5457c5ad8c454e9abcb84daae3525d978ace3aeac14ac7a58a624d4c36f1db091bf911151db02751b5417e2e95e4b1cacf58f8dba9297e69721813466a29ed62362ac2b1030d39eae663682855ee1b8fc911af4ffc73fbc2f9313f0d214284610cf5ad80aabe85e6514d6905b9f1fbb552d829ad203537bf5f6b63128eea9e7801f2976cc98ebbc48393ea41e51e1ba39913a3213889872d356c2a957bd7c9b35476a2863c2602aea3024ab43d36080530dcfc0d0d409ce7e728efc1f982664802a200e7614801ced3c020ce1704821fc09b1b5f1008a2802b213734006f40d615fafc9579da5c000605fb2a0e28d4c5507352eecf63a80f7ca0fba9d4f37a5b395ef5735c149ff11a66039d1f721217e73782197dcad971a620c314685a6afbfdf678ac34cf7e0c2526da5c00f6648408d582862b2352a47b3e0097a3b8547e3ed3b05dda010a6e871f87057787a356879f9f78f093cbd5319bce4983f382168c17322881422c45d1f442b4040a0c1a018b2d305088c13c394206c65640abb7d0c0cc0cecd14314483118c845efe834b16558230b224e76ee05ad0da03043060a390d4ead15be209e728319a240a1550d33d03f4619b460e6040aad5e8004fac72f8865e0a48918284473c611e8df6eabb112276494c0f616148adc38f4182bc0785a2e509834711919506902395f42c31846a0d026a31b460a18249093ee04ca4a1328d49d39e268dab08596797483153230888198c129060af90e47a076460a1033b06938228a285003a306982890eb68342d6b1c29f10ed6698b2632e712f58453ff10454313c8f96ed114451228d400323d84891a0c6a98411328b4d383981844275c684004725ad79e27110326723003b9ad6b2f686540d4c00914ea34aa84818019c0cc402e654506284ea0d00e94334dc09c56407b4108c0818c245068aba0828015283102b9ae6b0f0135d8900472abaebd03b4414311c8d174ed711618cc10052e93922e512047bbf6dc8719c8000672365d7b41ae0519658a40a1950e4f14dd74ed79d10f349881dc8daebd20f985071a66a0d03a1501469caebd78650b2d92400e47d79e63a146124720373dd0d13550e982040a31bb1358cc38710472dcb5d76a982992815b85194dc840cebbf63887299e20818cc31145c8c0a03ea3062398b604092b92402166bec107eb7bfa98dc75cf751dcd8faf7e3e7b21d8597561f5f397b43871dead9ea5303333f3b6d1d4d43c57b3d56c5b951f4b69573f8dec35e856b93528576e0d2a2b173bd51a5a975aa1a0aadbef340f8b45a993eb3ff0e19b67fbfa1989f2cf9cb88455ad41b515ffae363ea2195237b903a49582fc14f091ea4377e276f2399dbc67c1e94a1ee522384143f752bbdfbdc8afb5fd5657bf54f28e6d338d9e3ef8f0114be1595dfdacabd5b3e72b2f7e77146bd84523e0da0a838645e2d6004abfbf95be161fb5cb632967d0bf12553525ba8572636957c76a33c5ca1ed7560ae189324ddf708184adc7713268e0fa4bbed7efde1159b1a0618b69a7586d294c5868377d1dfbacd0fdb063304683e3a464667ed9d139c4bfb065a21605f8b55f41fe7e30eccbd73e18aa04d967180cf2f96b2cabe3bffef2e7dcb3820ec94f0890cd0d686a5ffeae239c5e66476a0e7ad05251430e780085b62cd0007fb4a630ac700128b45d010350bef605ad164d4e4a80429b1210a0fcb61510f2a8fc49407f7fad207f7ba92465c497d60a9f267ffbb4179a33dc40e70e166d0591d383f6092e567801063cec2fe5e73c1454fbd9cd5782d5c310ef7922eeda1e5e0b44b6ede5a1fd7aa14fd55ee09e9db5d0fc0d51a29188d742bbfaa714dba30544fbf8da47262a667599a880aa05a8cb4485953a9b87a97e5da62f522a173b5f49b949606b94c0566d86b2b216b5fed82cab06a46b7f093c1026a9fd496a7f3f0da4df05ed350f4800aafcb65d75733c32e198be38a9367599bec0bedd1853af799b53a5b7af79aa8f5e00ea265dc6df3eae50e89f807cd9d5cda95acec6af43a22e4c76256e66228d33c47df3040d5766831ab342458a07b6c7cff668ee9f2d1e83a7d82f8f45fc34ff682e445aca5ad6b226d24da4894c2dd409112832994cf603886a319b1cbac848e7b0cd3245462277195f70cf0356cbf67cfbe2af41ab600b1d587186146210000a0454f61e95dd0b5e4f29a985763191fe3eb023f87f42768c2d4d2e040d295454932225d174515c03494a9467686790ea0f6dc30119cda4ddcc8e934f4c16ee0c8b152ad508926d9a91718f4b4c14ef88f11943c61828471e738fcd8cf88b998d301bd16464239a9c363a90329ba5543355145c1a45508c60b1b2b9fcc53db7d6a28be23aee3eea280dc4aa8b7240c39d4d1ab9e7169ba3a0b151236ca53a9bcd664a9369622370699f1e4858b8a3847009fc710c438cb00c5a40b9913468d8b00ebba836a9bc89bc67f51edf699a703cde11ba8eedf3dee6711e9fe20d2e768cf8f8f8e4482aa28b895ec0eb4eaed1ced9a60eb149678a494a95834f556a23cee1a870dd8d0edd0a071a32d18a0627079a9a9b266ae80d26166a08b5c942e30e269b9b3528efcd0d336ee044a191c331e273860f0e19d0f8fc383aefe5222226f282fafbac8f07396e80918395c5647573a3abe33f844be0cf6647b8f1c54cc756417dce9821c30a13319940820924101111f9fcf08f65e5d0f16388dbe112f8a70008b0d9016b87fba6053d77543bce83c71b5de2dc51ed481c208af8949a130d37c6f270a763b2e3a345dec0a0e12209194af59f1d1f7f2d2e0e1d380e070dcabbbbdddd3631a0fc5a7c5985f24edffdb40f8165357edddd0d44eceeeed8dd317677f4c254f566efd8da1641f9e3abee8dbadb366b65709b13c68e9ddca6cdeac4df1882d4be6c44b0994046d9695b472937d9cd8a6f33048d326a51eedd44d149bbfa7520456ece2dceda1efc5df9bb591d790304d555fbea7d495509ba3d761dcbdf5d6af365949299e76b9d9c40ac470da58ddae51f1849d13185c6a32e4654b99aee612727560b8d1144598c9115437efae019c939c9830f5929c7c7c8454382e2508d2f24d620699ed6c718a353c4e98f1f7fb6d8a02def9b7e1869887ff0852c1d5f485939be90e5c117521c319c2f9c4621f59b161b34ec66ee365f386521ad29f21dde3a79cb521f51f50ffac610aec99afb2c5839ff0eff863e55b00d4766662766e06ea2061f93962dae2899a8419558a65489455665134330f183ecc9a104462e4dfeb38625a6b8e2a5ba6a36c1344e57643a7fce7905cb15236a76708ff8f30aac4625246afcd6ca47a4341011e58b0e9e58d0448728b248d1344dfb20f5a44445893035be8e958f488bf83259719f60cae149e205498c5102a96a4890a9dae758f9d05c55601113638cd183950f197657e454eda3f6338caa3d3d628caa3dcb4a1355fb474287236ea8dae358f9d05c581c01e6468c31e270d951441a463451841155fb1b2b1f1aa54bc49002eae8090dba28e150182104b793ce8ede6c971a76496324c5a0c6b759f9883963681874e5438b677471b50668da965043126a6842d59629842a59aab021832664f005112d20a28baafd5639aadad3346591a166a90a518df157ed84937c4dd3344d8bb17972545966aea6254a142f4a4a645c3465c9c2c251e4da1e4ee5892acc1237a8a416638c93a67eb99c06d490c7135a96ac3040d57ed218aaf62ced9f49d5de25c41455fb9d1554ed7d8670aadaffb0a06a4b31b8220b6e4372a2c6d7b4170c41668d1fd6b8f2e505dec5883238b9f221674e036a5c42ea414c8d1fe94e943151d4700415596600e35c9460c0006003d452e3c777aa695786543896a65099223c99da26b7edc809903094acec6003a8288c80c8949682f8c208880d8a5831a4a52c456c6abc92e84ddda52c2f4882eea8bb9445869913b3073821bb517709872527742883f67b6fef2e37af7cf65ee8caa05dc39f6550aee1772d5710e83a3dafda1534741de99e4e721e924907d89ebef9f83bdbc3e6e9872b5b2f56b387f86b6adae86071f7ae0ebf8dd749de53a45ff1e36b5ff1a39918d3a871e38259286a7cd5174bc5b88076aa317e38239176bd14347424588def48716e7c1f6a16941a9dd438654c20f0c154606b05185ab45d314aeaaa3102404643972b27d2744a8b18a4356541d6ae363eefbbfcb5fc85eb64820f945251513c8a694d4949fc1c0d1b51c16691e324ef6125fca4f247a548c44944f10b3916e78cb18bab18e5aa536ddf7393316a1a5be917d7481cda3d8d6ba7fb58feab5fb0fb9045e4323281e8a7f37ef82b16c41be22f21575443463256412d05015b828192f61a0c90ea9a517749ca957f0a43bd1899c0af02f7778c8cdcc87dce55d1eaf0774882f00f274569cc7bf84914218ae0278e19ad0e3bed60b1b4546a4e6e7ee4542b1fa9e7150af3b7d7e616a3f69af69aa63113738e07e1bef3f9e13e97efa83e745556815540a010fe9091d888a1b831565081db17d16c38aa53b62f88c1d60adbcf9f1f0cf3b7af06d97efe377bfe825b7703e12f2e0b9847c130e03d2278012606b6806cbb0aa27dad0952d54fef705610ede517d49ab2e3affe7eed7b97bf3af60fa02b50fef953d4a10b19454c45f3e733c1b468991d390c894a0f35eca893665d44651cac0b0065a2f88b37f2392a95f21ae6af281c2ccfaefc56b24df4042b4b37015086527e9ab2d509829fae18b183862573445136b6879f6417557ed8f116dbc3515c832adb68abf69325a1629d7cf7f7afe352352b1fbefdc79523f1fc6ececa3554cb5a42495977547f61cf729e15030d218a2e5964e02165e84aaee44a4a3e49d876bee7a4dd51fb926f6482f4915be891595bfd496a557dd8505256fe9c50ac1c28c74aded34ff6253f4a1155be92944e6db43a12ca09a71ad20a15c4a0ea7f3809003464074d367e8c9fe302628107e530ed35951f877754aef1fd9f66a73efc9bac3e2028eddf811ea9d1a4055328b78ceeee21cc7c0758aabbbbcbb8d345530c607bf47b7f5d506e57bb583a50ce3bd512b4e917a309f1f311e10a6522a8b7093f3eed62614157c77b806145b1c58747102933dcb66a620201fafddc0e2fa85d60031318b8c30b0a126223643cc4c02af377bc083aa040b8974254ee93d4766a9e30cda3e359bff9c66a0f7ad6b0380fa85d1bf7add607df4c1e4ebb5aad0f85ae2dae6edf5468533bb598ed7b4cf77094eddb8a53dd24b87deb03ee08168668336a9eb0a554212a109ff1c2c40994ad35bb99460d0ef7d1766d34eaf01a69fbe875d20e19286c7b96c74349dbb3d3f639be90a56a1c8ff385cfbaf185ac8d05866e543620a5e6a1f9ed1b4bf3a8d0db37d3f6d07e93cf79e1576ec8eaa777f3d10b8b2c540739d2757e3fde4fa14fbd01e16d3cd5afbcd67caef341f05a292198d8526b1a69076b7b6e87b75d10310403819a67fe4685053210a680f03b3ca0e6f9eeb7e7be1049e57e7a21923a9fdd0be777c441f85af31764a15fdb775f6b7eacf313c2c445cd99f3fb8f80fcd5d378ad14668802a85d714f65e5f168d79684d4aeed456883ce57a179e647a45ddbeff0b81ccc62e0ae2ab06df377dab5bd099e4fbbb64fe5405fc906757e612bd52d6ca4bafdb46b73b6cf0587511568dd1fb076a054287f1bbfbd233ea5fb58e8178f61a24e89d95e917d4261bfdfbd10ec537561bf91585f682b050f859eb457a576f77bce20ede2ef3d3e94ca3ba5fbcd2bb24fb55d8b71a7985591e45af4199130fd19699e0d970cba83052fa85ffcdb218942fb1b22a45d5f147de2518195449178096a62183f50f3c8a3caaf42f3b0a050fe1fdc2d7911537b3581fd0e6218128e88902726e2e13a21baacccccac04c6520c5566662ade4cad1d12b444f146f9f8ec33d7354a7032071774ab5c41802b03d16a2b85cf058d9bdb24abbf0a5400474b5ebc8ce1a63db369cbd462ed3021a6ba6e897a73fc58bb1878ea2e49115565f4a8e6e100fd10a10dca83846ddad9d99e6caf71e1769ca191080b438eb4a66d5d1847f45059fb8aac530a5d18473cf199554e56c2bf9d26e8d6d8cf0e25bc20edeaa75cc352146264f4d6d7d6b25e987d90419a877f9be1e989334a0051c39e2d085150ee57cfadbc86f217b39ed0d404bb75da50298772198c363bcc7bbaafe53df39eedb74a46cbbff9dd07c30743151852afe27ece397ffe0043270aaa1224154ed91eb67d42f361f8d4b79cea22f53d09c181ea43156c1f02559db27d3020f1d747c45faa2e54dffa2bf53191bf70c044f3abe2dfbdea4356179d8fbf1884293445f35bcdb7622b7e53bad80c8a6ac84620704137075dc6a00106c230bf05443ff54940fa0555f1dfbe06a95d9cdac2cbb1652876aa4d5e08183014ab468a30602856a7c020b4c9c0d46f9fda425b260216da5fd84f7593810ba63ed82603b7afc5338ef217d450c38e70f217ffa6c9dfbe4e1ad3444c5a16726da6767592bfa648bad2a4863d6b275dc4db5a9750ba87953419542a379da91cbdc3e6d6b61766a6957ffb884af3eccf28109cbf12b4d5bfe0a6b630abfab0f40416232c2d20db02c2ad9fa2fa84546095fe24759ff793d424b50544abfdb552bfadedabac1b695af27342bf260155e3ee744ee8cece67478436e886446d6666d5582bc149521db3fc6c36cf458c9663a9e90cb7d8ab7f10edd931455f869cd849141f691d817e0489c2e15504ba87edd1411b336307513c81421cc59725c02afda0062435811f4ea369fd3fedfdf0819ef00389f70075511cd43024ebf403c9eab88deae7871bd5b56b7bdea889a30534649db0f2a1fdaea226a36c7d0ed777252809ed72c7a9db05091e6ac84426f8d4241dabbae05a40fd49407e76fe1610f3b7fe62b0fb19ec8fbef6fe24e0802e9fa04203b58be3d57e394ee809f6e5ef2f85913e3e8a3a8a95c1450551fb439b96c02a02ea360d9d51696da212e539417168a1336e67bc8b1815e18ef0c38ebf711ff990d632b1d2f7d43d4def8186ac1424a9306385cb11182454c3c3141780f4855a32a411036b825a01fe351e9256803390d404e47327c5867f0b5989abef121458aa4735cc4bf56f22265090a9feeda47b968b24180c16c32e4d61a63ae8cf6d6c85612dd7e2af6a8e1d1224341c551aa2ddb3d2d675bef2d1d1acc47450fcb44350f304bdabc29dca4afcd3dda7f23ba11060a15382ae334cb56a354143b6c25492c4a86ea00cc54fedf2ff9ad0a54fabe36fa3023a47c0f5e90857b9339f676ca679564b104c75fe74549a4154fde9a0e6d96fa2213fc57797a2fab399edb1b5bb9f6284220a29b5bf9c6f9783536cb49fb6f16a94bcd4137eac50212cc4232ebfbbe3f7d7fa515598693c01390e5de718e2a3d0709f7866c84c5c8e01161242991583630c50804eeddf1edb7136b553b59fb74788105f7d47646aeaa8f36dea4cd5f9d3c6f358db0b0ee0df55737bf7053c046739fd82a05c4f5804833e3319e23d3f29eb818474169403dcc37dfc211bd81ea98ff353de065627fe72805ff1e38c7b465c356a8c435627c6c8c31237cca0e14ecafa77ad53051888029302abd31598abc57eb1646a8e9ae372fa6643b5046d9edd1e110c6003fbf20f67eaeb6f00db83045c9989b9690af1971764684531c6d2594c7fe9981ce3588f0bac0e06f8e5afa37e21463885e4b0802ec736a6e5c0a996a04cc6b22064a1a87c44d096dde430146e98392965b11c025b7dceee2c88b4d0aed412f42b83f277165886b828d13c6100b8abe9ed129959939b9c526eb29b638c31ae4777d75ec02a6f5d3235b70c547fee8eac52b1b721d3344d8bd107ae6776a8a3ae4df476775f01e1182a4ebf2825c3a293d9bb70817243ebcfb5e3f3365f3379720ddbf0cd17b2ea46433a3f74edf8a4bc3db9bddbb67c21847803a75ffbad1415f46342d070ce36a2e669178f76c9ee62f7a87e555e0bed496de7495bfe9fde96507e2d885b80431950606090d08241620656892f5423464b1358a5bf0532644370a2a88b47f7a89e559a27ff0b1d0214a479b69701a6a8a8a42ef64435756646000000008314000020100a860462b1482c1a1236691e14000c7b9a427c56990a84519203310a21630c01000000000008608066c4120025949beb342a08da99c14bd40aed6e5ae059c18bece3e31c32031202481e301737c55a5b07996a4ef450ab8c2c9344c846b0b4ed9c39f2e3e8455d22c6257bcaade8167c40387c9bf12f38617fdf5e8a2ea2c7bab614bd54564dd3a17670f89de9fe9a112a22d00169d178fb531749f72dd5877d220f48ff91ab95c5b8268c8f25c178a7404d68694635be7478476851009fafed2039e96ab767e9e83ac46814d3d4ca2b497301e6338da96dea5e2729b3d48a6dc9a6d87dbc21cfb8d6f41ed4a8735b32a40039082e76c6040c40bc1a446ba2c5f59ca560a3c5451c72bd6bfb325a370f2c9d559ff0d6f893ab4fd1f12021e520069a0b14516b6e0305a22945032da57ff829965ede15325b2ab9320edd598e99f7b3e45d98eb05bf6f44c1bec7a7860ed09932b335007cc3d6f09de42cb657c656d9edf2369de1cd2eb564b865d9fe0321ce12d8312c8509375c4f2a62f0cdeeadbcbcc761d82f2f5b80e6ca90018b2a9e704d2c94fcd6a170ad44d4ffa4faf775322fa1579b46436aa18636555991c2c3a06467aa2d27554715e24a3d38b2aa1d61f45d7398236e5d57b35a4d3011bd71c6ca5d175f0d215d1be0ea7eb60eabdea5ebdff72ed14081ac7342bb16eb2a84f3643b2707906d419451504e2ab2460bd8267ba6abe7e26782a7d0550e3d1b826450ce28e53d3d325785889bb5f17648aaa8e011338940c4c03c2be6fa9427e5ada20d6ada7118b4f507f07d0990ce6c835a1d9ae8d8ad303a4cc1a7975b6ce2a9b378a1cab18bef6e11bd7db76dbecc43830b5d3737dee3e5f06013567dcda06c34d1438de4423fe6f985ea5ba9bcea516c58a060cd26846d2b9abe4bc1c98b08d69255f68221b9a75c9018be647c0e5de9f24a5ba6844ed731fba7e47ac6fdb12cd543dedbe1c3b10ff92d4219b935d7485d0b8bfc446c94d299a4f87b9af0a838b0978f3481cab9933c6cc36f4cb86d52f3c61d63d307c9b28680d1f364b9b1c8dab150383a7222ac62492a5c686a9835a0d513a08dda2463c144a490ed1dcd06dc77f16e3c9d0b94dcde994b0f13d5069433403a4834ae2c03f76ac35318415d3f208e3748e1ee9ff10a859fab119769aa2a63ba9a7823d9c72f4b1ef903730a7a46d9d2cdbd77ff8367e2e871594a6e75dd028f7b9086bad736b6247c859011fa399f401c43b8a742cd9ae6cdcac91ac54a1c9e7cd241bea75782ae3bc9e53f9b7ca7c8f0ad8edc40a297681b2d32f574b0ddd1eae80e0eedbdd61d69e3390e359ea48a393985c96d8397d570f74d7e6e9ed7872bdd8f299815a6d5721d05c17845597d8bf9e64c8ebc5d33126c9c35b5d7011ee389bb5e19b8808cfe9055e606eed1795297ca82966dc8ba3532dba779e7f91f14c0f25e8460c9a4ee4f238247a07f5d624d25548d8f0d15d6dfaa3bf4afced947f0ddad0d1d9ac8b9254dc21f769e44db7bba0f96354d7b45a7e0d89523a666da797c33b17572127faf0ff3ac99d0088b7e966765ee61590731521e813f07311b2251a5959c858f9601aa44d11f53fa49a8e62fa06a5efff21a07a2647b5bd85e31a465ec3b2be336f94ed4db158173fe8719450e01f99cdb3486540eb93edb6036baa5853fc5f8d312491a6cad9896d0271892f12dfa7b8f0388017beb01c3faaadfd32a4b189b72ab2daed67499490dd426de2a40d1f7ec5d56271ae571b680d48d341c3269ee8a96442280579123815fd02e97bddeabd35bb7257ea4ec1af879df3f74ae0c01c6f56c7076fe3ef7e05bf0d552ccd2c733c7a0cbe9546053c312a870ee00db5f3ee538a969fca0ce58e672824025a679ce5216e6eeacf3f505820175ed3be7a5bcba5460dbf1ebd60ef02d610f3c3f90cba9e7648b4187455f724c5f9c9f7aec75da96555116ecf3f3fdc218649260d406174def4f2e2fd267e81cfc8d7881d1c92dbf9ea3f5f154e81832cc46cf1cb0344b7962e31fbd5471f728928e93e69d692670a3c84259f220c66045f1cf5b75baaa7cb0e7ecd804c5afcdb5bfc7c4f8c8ed88d8eed88eda0a6629be786b8048da6d3a2a29f2aa7d965a1550cac60aa52bbbd72c665f54ca6a5d5b2a96f02790f000dea89a31fdf32a9091c6cff0c4eb1f6fc6781d349876f8e1ac2b23bdd70d43348a09c35e3b22bba9ac6486b4de48388afd0203ed374b0346ef63650a0c0dc33fbcd6e3d1f550526c252c313beee7f8815c809c0a0aa431bf552228eabf8864a558b2c1139bec6d35a49e2b7d9d3289cb40cb3832960e6ae2a903bbface4bb5a99fd5b1a8bb24e897e62cdaa3518a0424df95d2146611e29742899449e0927ee966cd051005f36cf42e0bbaf5ffab32b0e7b5e892a665351b2023232ae6f5e8a59da1ce9b54d67b4c6ff3b1810cae5b9a154514266a98cf040645efb3dfb63aa5270217648623142fec434f1bc7e85c4dff0c03f4375fc9cec82941e533bd73a889b932f829ce7b343e6be751fa1fc5b7e5f5bb99af0842199218465909134e78404490113e247b12328fd15b10e40b4a9e70ba961ca33ab1a8098923d29288f1ad6ae190497d2538c52748d3ca7c63276961871ed89e44d2d781b8272a8b89eb47f24007038b2fdac94fb49cd859854ac757cd192a8c4346a01c6f3f0e55f0bfaa33cc79463166a2e2d1fe7b623b52ef03e2560e813f1ee2f13c9f125a1f49f259abf95b4add92ed9b2ad2ec303c41789bb1cfb3c9cff949653f6f967f462b7175d4919e65f021189b2c40db4cb6012c6db9462b70690208ef7dd6070191f7d000dbb022c630093cd3257f81b2d2c2e9d2bac7194ea88f4228766636ca966cd997ec13b20c7fa672291090e7a370e3b5dbe826dbb9f546e4fbca1d20597c57c7ba254d4a3b5c6f625ec79561012156c77d581878412c89f76401ecf2e684804aaab91525138b7d42255a4069cdb5d2c4e54234282ea76b05d96b6772ce236068cef19c5b0438ac8c37895c8352cc0fda0bc3add46852950dc1164546f8b853f1099b9fa9ddd38cd922481aa35c12ea5e1bdca18b577f1567d391fc64b7d5d04361bfdaa5c6536772f6e5db87efc213fdca1b38f20bf81d2a7918c74a5e0c6d3fdd4dee3c6db28791d667823316ac4e01b4e407aaac34edc907d790cc1353c67accaa7380c9a937fb58ef3630662d90e7b39b38c0101b606183eb5d5da9267fcd6801bc20d79528a571e3cce59b3b2e8da18fdf55806a3cb16fea53e3728b07eebc2979e108f8d2d9b532423d231e506c0546b2a7793284d9a07aaea5d03ecd9446d6a27f8b551dc233b1d869e85f654fe929477169c237d13542ad3810ef416c39e0e4dec26d093682873d6d909dd8cee81c07f72add41da08950eb414712285b63d1fa6a0a99b1d2a80bcf4dcbd8f407f3492cb86b4901141ade321c9b7147c20ec43306cf6d8d72b2fb3c962a2e1b547d18e239f4b9c43d65fb58a181e09a0e43ecef88bf95cc7858a6ddc8c0e8c8a8d3b3fb407e37d2a17cc05832d064a4779f1d3f346b74ff69f0a5e88cc5b7a56d6113aa55ac246f96bae29a6cbd62131b56973b7bae3bf81fa0ccafbde42e1996212a081d6d47b6807ed3a5d9186d4afe6d31535b3a71dd6fc41cb83f1906ef84eb21afbc487d95f16dcbf9301f03dc10cd19ab81ad9be7bd9a68b4a4e5d70d8d7d77789302ac51db2bf43f53ca3647b4bf3064c51ec0f5f5dcd40dfdd28bc2d94d132cc829d13349c9e6a0571e3848b2b0228222491c2ac0d7b31e56c942f5ed68bea2724deac0271b0c2dd8677f707d4b0636408e0eb619c5e89def5ccec3304378e622ca32ea91fbbdeb97849da203b157a8951e1ea4bda398ff50195f042fa29a464c76126be7191fe2365e7bef6f5adc696f30c5ab448edc05019b47533efd2d1a8e9a98fe2d2f4f71da215cb33d6b5527132174d202ce7f46eed2682782c472200bc296330a43d6164f4a4c0e8841e907f3bae7b61a9f00deeda2385a7ee777a3bf1f67c65f55c81ccbec3e582f22f2897432d2dbd6e071d5bd68678bb4562d645c79950edd76dc7ce9a26712c3b788875f6b7daf8dee73f79737f314c2623a24acbdbb193958ffe8c3f834c659dad64901b437b736e823824d066221f9f296c4b07c961e8431238518c86e4a20ad19a8e14bd1b772234e0568b6c690bd31935380b6cc9f50b5ef9eed3af09fb477359a3f226e6087793d5e49ba0343cc5e6b8e41534c1a6015eeda31e3871306aa5075471542bd9b324417e10f09789e663bae8f6464847544fb56fde9f8810ece2aa1af6df348888352d3d2dcfc0c19efe4445b65721baa97730f665fe1e3dfdd9cae5c7281e553868b48c43cc863540a8eec6422cb36620147aa6a07e8df4865b62bf0336f71d99058aa51aa302fb2e3f26aa795233cce1adc34ea64029ba611fb03070bf8f5c3a619cf28f282efe9deed6ef22142bd61d9e15c360e4df2b90b3e25e590b9ba564e2944a4966e7e334c64c71ec10163ed9c9e45ebe08a3940066b96bb75cab2c3020cade315faa88964168ae278fe6ffece6898f92a4e4820a84fc04da65026092b58d522dda43c703d746f61f198300635f47dd5640f3d07c15de9c05c05bf87e463e11867141e6949cc905cd84de3175e64b8918fb9a60297b654e8828a4e5dc978c3208b52a14f34a234086da23ae5427fb012be53d3d5a260e45755659458c9624ad84813ef999b24c3c05a4f1ac88e0580b437f4f001f7df7c344cc226f07dd43cb0e8832b6a330c0d5b89160d968c8795b246065516f958c9faad7f64532ef0c20c72d393ca7a0a63afd936835c53836e3c4949708cca9bd452f24d2df384ad2fba0636872482b2c72ca73a8272d5dca3081bb49022b312925c717be76cd4b20ba54d6fde0ba3a34ec9f520e42634a59f83ece07341c16e490bd7cb0ee488705fce61b00ba28282f14fe49dada049e96d3ea7c5ee08bb984c8896d16a83aee3aeb7255296f21b01bc43a2dd4eb06ba5e208fc5c2b343b691b96aba210c06ce91439f09ad60ecdbb596d885999b6758b9ecdbf8f96e966fc3af177a1efd6fd9197f55050c0a93ae7f5a4a1c38e855b462930a7282f7008aeb9d694f6e4134dc359085fda9ca36c205db9fd3aebe8bbbd5bec90cdb3b005c809d8af3aa4c1024a2afaf5bddab8f94a50ae23b4b5bd00fb9edb99bcf1fb0f3deeb6cf16ee2cb310f4b679d9470be66b7b4fa11a845eb2acde4687d1e0b19128fdc2d8815c222041cf6282cc4f7e2dfeb3a3aa13890fe4c19e223788914aa52082578214a027dcd12ac9558d59bce8bb718ba3592a6df05eabe9e83256bcfe2a00c114f513ffa81d1064684b0c9f8d488f913d518c08a9634f09e6a0c8776bd68e7868808832a1300ed1db4ab2719cdc4c6e4f8e2bc55702d411c351c29d2643738ee67ed12ce5da1d099d57590eabf96ecea7ed375516973bc9234af21d3c0c197d0b45a662133912d18caafcb44a09a5a3cfff99748e07dd590c5fce43d50631be3ab47936ee1c4e43f171f2b865fab372a60d956cc4025d6c93bb8afc259439ed9cf38f7a7c95fa2d7f7e3afb63e43b904d6f2cd12090ba11dd3ab90d08eee8d49b8fee235238d8d3275a8ff44962c4d00eb1cd41d2dc54ab0a41d7d86d60c93152cfbef5c0ca3f803831d0f1c14100c64a9a260ed6fe43e2dd41788795cab5e373e1ade5f354f858ec509d2b6e59736cb4d25f37749703ce7f6664f47ba82c21f2386b55aa9b427218d1195ca79f8072e7f0dee8d09f0cf7b868d14061a2cfaeea8d67791bb916f7e8d2a87b05c112730a4b0a9c064ab8134c6cafebabf20156ca9840a38e7e53975f061c76e8335c021ef521f7554e8eadb7bc726ff20a4c4297221642e3a7c4923b854f6888a9985fe6e3f1ca48296385683c5b9372c609068569459c9d9ece3d452826ef245008c09814804fc097aa40a7e0db1dca621041bd08207ca708c3c03537af66d52f4608fbf49c0a56e5d87c734e39a451c7c975a07d5a65aaf63005548b6b82b9645db2d6e4133b46cec7067eb8bac33f2dcee9e22479ed878b866b68fc75e51439457c42aec219b0401884a6f8db72bcd0a662f4e275c520f663565a10610135f5f4924e9d54a0a03d2ffde49039c684b95741e0f7b35c173c8f915aff228137912fba39a768cd4575b788bff724102b929155b91d74001393cbe84af7d46552fb7a9c52b92d86500fd41e4d4cf7465372c8bad7d15db2043fc9b7ca2df98cbaa90d1df75b504a7b82a3705423a17d22acc5c8de3444edef7c41e47995091bbf47fa343924752e6e3b51e210f1248a5960a79c3590933410efcd04a52001a73ea87c9a461d9fb476859a4d64a28c651056b2c93ce99e7f422751d21738ad857212e2baea08158efd8f47ce8b74460d6c928136aba46bc3674d853601026ad5fdfb2d485780be8da5b0d82c98714b6cde9d72a2a747fe6eb29389c40fa75c127812ad3afd5a15622fb55022f8dd0bf5fdfb79adbf08ae2de79131d476b7216818d133fecb35f3bb4c5eab12efed4e125daf8f0ac0a48e6cec99c4225ae4d913c5730186a081236616611d094d3b7fea045756a779eeade5c2c38cf99e5555de212e3506614343492f7f178072b698d77c124eb990fa12f80143ea745a46ed73e8b667d6a9e072e3d5c99a821918b3615c7932c85874236e8590a7814a3b9d8f6fb2c018c5ed045f9c37885b5beab2e5b8cbb05c4af5965bbd6e21ffd0ee71dde62559804070273904a0699d07634d6f373743836c68a5c0d27fb5c9e87ee55f6408aac5ea7aecf5dd543218acee9ca3183041a7993b42da88179c1a657f3a43bb3ff16241b519e8aeee937e80d4b00f892ea09bdeb76f908cb0d81e3984e42e8dcc0df59bde0448e5e5bba48437c1cca8aa4cd69bfc138a9ae8ff0b3c352ed1a489dd720555deb55f05ad0bc8a62a5cde8a82746aca9da02342d18aafaa63fde1c98e7ac68a3f832adaa9ebc594f70aeafaac5157d6d9584ce11b5b130cc2dd872dbfff93d0977f91bebfab4ed7f92c13c8ea7b2bb6425a6e7a501c819b76864eb305b27e004786d71a2e9b0b85a64b2a2326c55e60a586adefaeb15aa6d9357b542755b45cb27431f53206ce52c11f46ee6f22641001d83e28b801e4bad813e7e83c6bbe1a6ab456384f19723c140319846d7353b6e4ca01ffb7e5aa88ce57bfb4af38781b2d388b03864df8bef400bff1517482428067bd2f285bf43ac8593f54788b404370c757647705a15ba1e5d940cdae74c7623905d6b5d3cb356ac38badeff3e899a34deb7dce17f9cfc173613dcc9b62b1cdb0672d64a954dd90370d22c6d65e89c77c72b462014370f9bd57b49938dd17e6c4464aea2d0e63124ab89c6ffe10468ce7b23c828bc51a3ba7cc28d1457fd50bddec21b738340b0b5e6b9c8ab8416d01f89e36b76a667160d9a0039dfd885d35ad84264c9525aa531ae66dcef1822f703e3e909141e29f26325a0830c7b4972be562788ac5e50af9dfb99749cb544e0b21c3ae1cb1cfb44254cb8b162973bc17279c94215d5f4479d86f94f8910b633d6797d8a16adeeff1b126d6707c99d073ce089ddf3c475fc2a328917169d7c614d649c541a659155f523e44858fac6a804a31bd1252950804e844a430f64e0a203890bab444fd84425209496c5fa43d5f5452064482029c6c6c018f069d30577d018ba006fb44e4a0635422efbca3586892c0e01d0b35bed3abf1d02d9e32d02c907e9e4f9dbed8e5bf60a28c955dbd51dafad029bbfb829da53a655c8c3673ded35b7901c98c56a904b8bc557acbc264f38771909f90b2a20f79d8f24e37e41fa898068686235cae85ba9fc32e243e234fc3995bfa3d27ff8520bb491c0f356193557716b353a0f65e5b46acbf4e4a14b7f341b9dcd6122f9aba0e6aea55d3db4def82787882021ed08665181b35084f519f86b4e720e159d04c9c9915281e135c3f2ac4a1a790a67f591b01779214bf9b7e6f9f2c0b6ed88c1c907eda0e9cf552c6448692ed9829c2dafa593b32e2f7f20f94c41ca6c8098ab43537adcb9637d4d2e3afbd12931e875882aabe67a1c034211b575c5725fbbe98e4ae1aaec5aa44f1a9bcdbae868366c43968525459198fe89d368482d8d6154c0314849580abc34bfed219f171d08173991342b2b9dd3ce1e298e3dd5c87f634ea72ac6ac5253062cc4418b5c03af73adf7319068415775135d19ca4dfaafcc4a1c5c81382f689458b144d2d5c857a4fe9a012ce69b6398cbb8979b4c177f6f4478bb92e09f033f032c1b817d99e0968ef9f95b8a3b600ff4850c4830c8f226cdb4ac9b8192a138a12d32453786e4be7eb4abe343063e6a6a64454aea7632ec613dc872b3858fa4b2d29b1de3b111d8c1f1121540970999c73821623ba927afb9abd090c65daf437e1760de8977cda703e043f7016a128c2b17c2fb5ae246408ab280827dbf5525565507bc02ca56089384d007cb9dc4e649c9a4f4870137dea2611fe7dc54f09e2d0e2e83b67ec873c64dfd44f7bd25bceb52d08662389cedba8f174db93c24b7a6721170eb804d6953da084a6454c61780a8c4cc9a0ec816d26c19f0cfd5edf20086e037fb3905d490114c107aa59a08a3e09642f8f8197b7a5f121a6c61a3f3d1a3aa7e1fb7dc2974485f021eb0064da66515b41b77e0b4503d6c6351629f55f7be94859f4620c6aef4d34e2730255574883f33b4de348e09fd3089be315e1e775506b75455c5acd7517256aceb4a9650d1a6990f713b44512526d3cfdb064df0b0ce8788ddb2a433a265c67a6bf5a5fac5a5ee633008cc4271d044ca40e994420cb1412dff0b2c65b479dd20679a6ea5268e8a0d33b48530ad1b822111b4f312a4322ae2e4b7b00c2b8c4f1094fa7dc55798f01141d6eed89f88089cdb288315db50f1477f64b48c6fa1819c38e03e52e5eb563ad94a729dcdb8916a4794fb78dfa9c28d182a0ea12e5a560bfd34be1c3c413f13c52106e604b814f23f18ad3826a0109fb14c1adce0345831d39700d6f4f9ef22e8ceb46855c3609b0cc5c34a5fc546a97b74c5f3bd84ebede1e798de74ad54d963625cc00fb45e7652481dceacbd7280c30786067bd7b7228a0cd0c3427087aad96ab11684e8b59865373102a628605471a476023c9e79ca1096d1ef01f9fbc1519c55e19286e4909ec13dc20318f434c047a6ed0887a1a973835ff0ac7b757ccadbf24febc63a53cf5add643da960a5ad78b12623637959940ca7185a5b314e5ca1a9e7d777cd5002f682e05d3fc21e1624774086d5451b9420ad8b3d042e38b90079d78565be5185a68c4b113da0abdf7a758c35cc636ee7dddd53e078d77b8cc54701536b979491600bab02832332def1bcd73b34a441b525ae77aae318aa2786532684dc5ee13f7fc52a0cade15b086cb861edbf21f0907b2e73f50fdd3509799bb117522d3a5cc643cff9d5b07ae50f188441bee420d8a2efc3bb013f582b22439a5d0464919944fd8816edd694d827541a5c8eaa3aa7bbadc27391b82d8f442b5487f7100a186818d67031ea3103db375899b1eba23b3c0f6d41fee2eb18cc795a8c2723a26145beb918792112cdc5f5dcb93e11f748f18cc525a3459f474c7744bd5ea5b1a251ca33f199bbc87ccef9c0a3a43e2b82dedd3102ab17f0dec9d281fe0969c5f175eb90dbeaf92e061b667cf5c3819e1e3d5a91eef7b6a4d5466fdf2091eed321ceb78bea8e395573066d8df8b650215771b9d3f2e4fcc354a4fda1bf4d7fe5901903be8bc7696fe436a72e2e8cdc1205e754af016edf187a8f2cd2a2da3b335ddb47bd83949000d9743dc04ad0621d42471455f2322331e744ce7246dcca279a5f9f30bfc72d4e7de8a02d24930240280457b0cf212f465b7ae951c7d7ba1a54bbd28aa579375d3f3161a89c3df9f1740356e578b1b364aba992121962e36f2b96d4178dc1142d1d4cc5174e9b11030f6fef82541da218bdc49b6b115f967ed929fb019f2df5f51ea9c0a59fc6beb7ea99c30d08093d31f49ea9856bdb17eb1a73fb953ce362203eb7d3cde1b8d1a55f81233dff4a54ca3f60bb856793a855571004363099abf71a092bf3ea3fbf7f5b7241379a3b1347c603b0489995f60fd279c973f6b9f9d8cace18f2b7991591bef90fdf5283c21e65ad024fabac0de9a61af0fb69f268e8d69b5b5e0ae4001907a8715eb5c7f5c2d8d95830bc02ac5bbf059791c0fd28d1d2e2f6ceae94de1767058dc86d82e7bad218af6ba59fb1dd951974c274a43de9236d7f5c60a4394104c85b024e99140ec6a5baaede28bfaa256336b4274ab905c6e8bee5e92c55e8dc917c966015c6315f07c1d8c822d5dd036180a5dd2db3f6508ed8ee32a6a0efb0a4d1b8c59f26981f70b811ed26818c7004fe0ccf44e758c8c28ef019448be469422514de3fe4603f0cbb8dcd405029c7a63ae74a886e4ed392f374f742d4cdaab4d326160d47d559b8ec21f725d1cb9827bbec15599ab7191ffd09b514b246b7b50404b68ae576ccf2fb6198ff0ea591a7f15355e43a11af4c61625ea302a805b39d7fd8fcd97ca915b53c989655b2ca666bce5d2fc384096bc3a55d4e0d9d79607e829a708e888f7960bd6ee06c2e4e11948c2d35afc04b88cd50ce4c06ebbd07bf54999018bf9b2fa092d1e9436b4c1f0947123b27e5137858ab55b1a9f8913f3667d1c588d362065cdeb656cb0dfc1b1fea4b7e2eefa2e38ef09126c9a39926a40db466bedf2df53dd0201e00c914bb4cefe797353039141de0269ab7719d9d477b184ff63bb7634274af6fc5169f78fc65d1aba9b8216107bbb67bdaa3bcda654722add7f2b32582e798f8d074c838ae7b8c34017e30c87f5764d82bd020bd72d544a4fb2170061c19ce40d10fe8d11d5892a4db3331f055a757e6b3a42ea973efdf1e1ea6436755027ee967a0499b762cba390a1923da2978706703f28e3fb01456f3d5352c18644f16ec55e38a3907a13f2f63ca41b75383a855119e5d22fd60bdf1809d7e30f4b62beb9cad5d2717d97d700fa14480cbb8d6053ed86b2b618c5b3f680a00518dfc4ad6236c9a4128c8187c71bfab5c0c7568b51b2aac54d62b9fd604dba152c7c2b27606c3f8091367de8d04b703d3c3a2800cbb8b3b1b0cc50561fe09059ab97233a827577a435192fe00e8f1917008723551ac688068fe5c75081e33c62a23786cfed5a5c10b1e9b763cf4b7f384e8a9f382882aaef4263e8d4ca32106eddab2f7c87f7f15ef114577e0f1e0e3d5aea118158415f930e6b0f3135a77016c65f3dc810d64d161a9bef1611b7308fa516d96747743f8bf9a60decb6dd4e9f7ad9df420c25084dad62257f963e49288ee0dd309c854f73cc34d4a53edf54d654c49047b23a59c6f13f65cedb3902a226b61bff68b3b6e64a6f8c5330da8e636e0b819c0e76fd426132dbb12436a4cd280afedaed90e772f3c5c46c76cdb6fe6e7b770a521270dd6eb1510e59713fedbaf874434bd78f297bdcc42cb4c2f4da9fa018003cbcdddfab79b432e77cb8972a130a8b4c689d23017dd1753f7dad7c48de8d85e3933f5c9d718897119891c38d4a3a93e7454abb7808c8e4fc4fa230ef4925d0339e92d1ada7b5cc5a482df4a054fd1603548234a7443bc9a2d7b664967c3b5b84b6d9825e27f0fe8f3626ac41985f54807237a95cb2a9af87b01a7462cf43556b902a33b335cd17153b08780528d56871402c5a3107b1f40f1d62548b38b53346b7434ac301d5218b2a5b1a02078a1dfd4d75a27499398d1584dae3a7627d2e53de5c3b6bbb8222ad23a904863d68982a4646b027e21f6a8d5574398bd6f30a6c913027faee3b880a4f5c038a0eb7d3af133f96273c64c408a4001870a93384474678001a88789795ef91a3f214823ddfe8b833f64f61344e9ef3c8923fa1d2ea35927e1030b0a70921842374fe8cfbe1379c0d23c06024954fd4b4b77c98c8ef93beb96c6b165aa1669d32a0897eb705a9031f9636fc9ed8e98610a24ea878b8349198945285005435485833e707b3a91bf6907506f119aa4da0503c5dc7baee6e855ba98c7cc33abae0e23dae57cb8654e1e90fcbaa1ca35697a25061d225ba2b626699b4802515fdf271f324a3af9dac3fceeab0339bfbbf00934912ab1c05f80cfbfae102cd39c978437c61b17d391788a0b0613b86b51d8f5079a32f8498224025e835077f57ff1f055af2846245427d4cb4fd97e6c526a9d7c417c0f6e051b2e1d3520a45b6865bf53b05dfacaddc4a39ab1ac06657ae2fa80ffd19003e2448de43cef866a44d59b0e211264338bca0bc480308fee2ff9950660a2813029a85836ebc758abecd548fc96c020eefd2ae1ef93435cd692b26b72e3ec803eefaa7b8a908bcfa74e939ba82f50a49a1c4e45ef0b6b610f37911f8d28d01f455cf17588d9db97c47780cefd4d32be7c9a4309d31fa2710d4d0bb3c847e8b43cbae682a2974c01ad8fc5563954ec75094bf3f4e55e28728bc425160f4e9621a3a41486dc4c641c08ba3bf170b715d486e355d5059277007297c7a8dda904aab666a411a92a8a88e7314819cda8089653c38ac789fa7e1dfa0bcae9a9aa3eacf03368ca0f59a47e94af0b09a564602734b3e768e48e6b05eaf7be03586a5fa03c73121085295397903994b3c0238fad942ffa17bca3a774abdddc278de187079b60e00a4c775409e3b61dbf5a59b02df8b77ed1fb2ed8041dbf88f9028e36c78b20037a5d2bf60b1d8f58638e3c743289260a5bfcae7946951cd640be6cb80612796f746cdd76977522c4126101d3f0bb0b1f94bcbc9728cab904397719205ed21ee1ac9409052ed54e3e46cf391009956f1884a89c7f0c2b4dce98a5419cb78067dd7a1822b46777dd07945d68d4d7d34698a1a3f7ca2c4998835212e4ad403fdae85a02c45e226b8318abcff5b791182b64c1c90b118d9c29d91dc147d5818b3681154d32d3b33ff8e56ac2192d4052527cf7e8566b489135c78286ae6ac87a295df5d6561a9eeb3038836b58f8a563643afa1680440925d5398883f10508741be320ac6c58d230f8eeaf63140d6c60ac900d24f5c6b5b60f6d813edb94c991f5676157320f3db6fd33e2a8ab87e92f7f7c7718fb507399a85adafe3942f8267216486910f6787ad1b455993c05fb075a0fb6f048d01a502d8b9da96b3752356a944339ceded31007ea022e0d4796471fe2661040587232758e46d6714f81fa53883bcf0ea5590a9ec9e7c4b94a6ec399aad3b81f60e8a2a07f924efc52701ac53eb8e25718c301e16a373b10cd997f7a2ecce6f270ec04dd4c27a2c5636b6b20889e4e6911108764a8e48c2739d83a4e8e66f95a9121899625efaba6cd348aba1fe5b60ec1d6bd2803cb5eac75efab6472a9bddfb6453bc98dadc6c246a1152484c31352cd7785baa371a8785cf59f64fb0e840acc2dd9de1a64d17903e5cc707fc986271dbdba632caf479829eeaa6708945d4003ced1017b4769ff7038f83f32d21b67a3ee778fa1a5d2ebd33776dd796da8466ee68d07f35ee4107b61f00bed982f94d8069451b9b042e78e5ac365fc4816c2c9923ed63c483c7d639aa16e7ad4fbcb8354f748bc3bb67a5bafe70cb48ac175ddd9f9c6d86d58e0911e9bb5f7e9d598012f3135733a0c8a9106358d429ad64603bbf6c57c5d0a3a391c591dda9168bd1feae8de23640a41f1d7d9cee48798e21d0fb4a8d7cd8d7652d991a246b6d57d338d7f965d7e4683f0ae48eccecf0a475245f9f33bad5ec4c688943553a0f94b496de195b5eea86adad1adc0afc65dda9f70e43d853a184df9eed774a322a97d8d1b82304263067b17a5d50928baa384fae2bc007929c22c05bb74e3c7435330aec68df4a40e9622462881a9fa160a6e9493aaf00fc665c2baf1eca494086ee05b59cc74e4cfd91dd0818fcea0f53dbb50b75965b402dbd1e82664a592af2089980799ac343ec0b70c36754b9f7c82d237008b46994e8be4c6a00b545bd1186e370dad3e398e61abf20f980dd4eedf53003f9d3307b30ea7105590a29d2fe55e02388339be7d0cb8a243fd572bb4197e5fc5f5b912a69f09b6a5294848bdd19a652da4fd5c638d9d7879c04bdd441cca7a25a80da135373b5febeaec2f36b2fa4a33d143679e4eac89f15e2cf2ef34550c590c40426a854a627bd9c5ffa2c85cd2e43423ae40c1764a6b07acfe6be5040cc3477250b9fa725f4382e57e0ee2babe440e36434ab19b0ec2f001cbcf821788c2a02e8b2a23b525a7a7f01cfced901640d87c3a48bac72169d92d2c178fbc4881c1d8e2a9f1f411bbb578950b6c5b34af6013cc16af496938044b983981a97ef8031371ae8f028eef7d649f4a0560e70153ff6ccbb72c807b1f39e473de24b6d27d7b6808376b7553223fdc63ed22eaaa595c26e9f3ecb30fc6c9e1586a14773bdeaf2da5c6003e783ec65dfc25bcf354afb64368e9c538d3708e5dc635e2b62ba7a9f62344331510b7652f60e04fa399fe5723935d44d6cb78be7ebe96b57e7dd384d61e2b3cbfcd739dd13ee46959a537091d60b373acef04a3889286789287b060ed76d189fee6708c5f8f1af9119ca241f6b934f0b9a78d487f72912d943ee74fdf7267f2670e6ecc8bee4c2ace3dc3d6f61e3de2e92d993450d10cd5e29ac7e91c0f47c4e6b142258e075356271a64b122348e072762ada3f11899d5b71c8c0680a6b61aa142de67025b57640e35c577d937953d1791e1d34ec1529e7ec01535b875f87417cf4194e36843f664d5c7f72aa31765900be30d760fdad5245d4696d35a6cc05ea853173e50fac202a4e2d903b3d876b34f6ba09ce6eda810b6e5e0d47a664072d09967c18f71f37917aeb9e47cd1c5d02f5da2c0618aeba171719a03ceac7f94aeac2f10363f09a5cb8a3240703d1b6afe0d3ea0d00a72bee9acff19cbac28be12012a37d55b79fba3705763b75d16aac8209bd164771411b3ec96c0e7bfaaf604074e71336e1b60d8bb53801ae258c2d9659d9780514b9319fb8eef09bae972ceed2bfe91f7ce41c861b3c186be4119306f94d9b0a183ab0e0a0c20dd5e217cdecac65113f8a7c320e177544e6eee8b3ff4eb76ca1196eec3b722bd0a16ebefe705ef777ff4e86ed751c6a7f80562f1419b9e7dd01f7f9c0f7b919407316d824964ee2c14663b3c3b0602565dae13059336dd9afcfd9042c4af74abf25b5ad9e4bd49795ef3e01371a5e84084494823b8f93191d14a1c2b9d059be8169866222ab532b233f7a0aaf1613517cada50f71057d6880e118bdca0d8f6265ba1f8582faf97b1d04d8d85e26b1b22f3a501ab930aca7129a05aa7e8f4ab2d68110d0f9684cdac3af5332c753213deaa98a7f079eefce267cb69f7c67634c74eada140b30b3ed83a0d2f8ccf65e73d3259942d1c3dd5de409608612869a198e21c51598cb5b07d674daceb0df4a58640716e2e139fcfedf43da2de3a64a4ce6e7a8f672f3590a1a2cda3c2c4edcb1a885b09343439a15c62b11b26b44f4ad05935722fb4c98cdae873c9be541c207437ffbe2b7a80227b064572552f878e39f838734fde73ba21d8466f8d62c83321d354a10c3a6dbc81ae53c57bf03dee816b7de38c4144b98983e71d9d05fec4c08c53622a70f80c9b268928b659aa6cfd95fe6fa451d021789a9630a71f7c2b1d20e73624d3e7892591d9cae0654052e5caa40aa689c66b111f6e70ef4d98382a0e3123192eedaeb23de64a2f22e68081e533cb840c42db55de4a3e5646c9e0d7b3fb842f7a640dca6fee3abcbedb40f6ab311494961ee2e823189bf6d8aa91f46c333d9f384d3cd43ffbabfadefdf7a7e9a6f7baebf73a483ce68c775af34e846ae009318e83646edb920a31ecd4ff3bb0eaa9badfe6a1d26c66b86ee69e23c67abe4eef8638a6abac625605dda1229853cfc2c31fcc562179a6078db6a82a38e22c3cab143ecf1866d9fc41d5ce8cfbf1cef0eb67d989dc40836246e5e88331b479959983f4ec91d24061c0c88cb7f03c78a1b76e4f9303a2690f0308542bef979959e06f78a4152eca5f9899424b086320ad320025b893310c4c36aa0be34532d9f025a88b5e11e2b4b986b90c4e0511aa0e2c65d06de49d85bd1d7823cc819504f80b6327913e2b8b5aaa491829ec66d9b75ba5ddf5a6bb6ababc950d1046004fb04ffca53a79d8673d695468bb6cee9eed83d101e0cabce36add74ba5730ed7f04876e862fd07a2998c14a0bfe2c9d6d25bc8bb8873707e97197eaf78390a080744790bd61d5dece8360f156da94a3a61d225f68c92e2b0da4a59817bf7f81ddcd16f3a20d649f82cbd38e18637da7306f8968aca7de2c16072626fed7cfff047607001a7bdafcdb52cda29897aea74c59402564d11e9ea0edae44872ee60a8011867cc53fa38401594a3d38a32e060d84c0329b53e1d9a8190a9358f4863544efc945a1d737cd77f5c6e598605956b7c9211e4d8f880b2d8053511601d6289cef348e1f9727598a21a57f99e830d030a2db74ca7e0d1cfc621fc81a05087dedc0b7a3f4a302b63e5799e50d3d9de36534afd1d5e209d6756923916010f94fa44f079fd8f6e6f27645bb0abecf1eec911e1204f529db990528d435293524904cc5362a533d278ca267085ddd44ffcc2a0a023425dd41764b719cbe5b5c8989b40def1417caf775738ae660db10b4982064272a70aadeb92dc7720ca6dddf8625ab312058ced1922592bacd722d17566d4d8ef5212210a86c8c5910aaa06b43de1ea53aa011450c12e84bc58a1acf17b2a014a92eee2b8505288b880fcf8ddbfe7da413467a0a29635d3a6e4a9141bffafa63cdfb21d124df06b2aa82585ee1f178722d4de0931e88aeb9b0823ef4a4ad0bc04c97d02358404c8075ccb9bb71364352ac4a9223a55e009f548037d98f8c6f0d53715938b3fc607983a7990531605ff47322a511a7b7507eecf4889acbe8f2957dd5a2a53137b4ef3e920a2769834e23931f45f70321dc7a425b6b131a229a8c4d7c1087699c1814d751c7c806eb22fa120e07a3f7a59fdbf51c00494d1d9cc947b14ef3103aacfd51a0268469c76a628182e6b170dd8ce36f52b329f89543d85cf0d0c5b87ea24384f2242bcc35317d53e0a91c15d1c921657c05e1098611d2fda349760ac815b7c61d2da030e67aacd20358f54c255add543f22e5afee28bbeb969a661934590ecc1c3d8cd66633ed6207c7d8605a2c9d72bbf8fd6cb6652f37b51120e634c0a438938dfd68ddc941519e8545fea41fbcb2e65e77158f7bbce6c561a951b4ee6bbab3cdbb36ff2ff8e1048422aad5099f9617438ede1979361032887ff2ce2c2f28aa01f692116994dc05450f572ab435778731277000f48ae60288cb01609ca1ca60dbb48e35c1a85824934c9a7b8eede5408e3714b88fead02c511c162502bc4d0c8f33da4166c16acc1884336e123675460d171c33f20955c894bf05737a68e3081b5882f7441a2625daeef79ec96b244423a4f992628be30db48815a9dae2eecdab262f1f9c54df3603a419ed5fd8dd7e0353181c3035dfda84a06270e3db5c4e2a3ee695b2bf5391bb2008094dc4bf417547e0785c17ea56c1e7abbf5cafb40f9c428f8cb35370ab5092b38bd2b33cc88dbe85f7cfaff06771e4fe664cd89bda8f8a9e4695f4fbaa5b0bf148c6373dc77cebc0d8009c05031722303e7ff6e4f49660202ec20d6d6924d8b716364cc8ff60c837ad1086f484fcf64cf71f747bd0a72ab48b7c8ff61fc403adde3253fe6b6f8a726b20c9ab2b8efe03e6a22dcfe30f004b2cf5219f79fb881931be2dd5a5236b3bb3413e02548b28ad729b6e263c444c41d42d4162cf4e3c83e1c6985e84939a8c4c9f5995613fb353ea5f49acb808fb872f2f5d2382f451541b6b0eaa46057fae52c9d7ad15b2d2034ca8f82444098cd644d68c4d5003622b41cff80eaba989a54f1d9ee1be8515cd41caa27c85bd30875ef752e2e27d934f9f74d2d4eb94637968ef5a420a63676c83c942fe03f1dbd1b2aa608faa1a1093d9fde94e3e835ae1c40e29251a85e4ed40c2f5b7a2fcf62768650af7634c8153203024dd6d63c3d085c0a8ab720b66f022d1b9674dfc98e0ea5fdd854d48270b796c935102cc32ed0a389e79cb3fe3261537e741cfcb5209c5d4725161d2f4cb199737debea6b7497d5883567c00c967fa432b1fd511822a907ab5019e2d0289ce79d25561c95064a3e73bd54470bae829825cf1278dee01931314fb3ab93e573807ed7df3f655050ad51597e4d494aa55c08775be61c6c62ac7336454bad1bc8b9dcef3f6b8a88107f8c87dd0ac3c0d5372513f5887335905743d39cd66f20abde60bc0b96623dfec7c1d6c1962fa06495d9723946b1318609cbb7c87c1e952fe8f61beb352ab1f0b4b6cb14e970f48a730b69ff7be4afd9000572c6a83bf45168cb9948bebece7098e2b02e89b0e00141ca8e9e7517c3804c3f1d2233509100b5fb01138f8e50cde1414cb5d33fdaff786acd6f64854ff0d635bacff49aee8a62db2afdd05736e4ccd26ead9b99086bb046250c0de2a53b12a29e81b8f8241c33f737b506823c7259e9eb37ce7c50783ec24d858cf5503b8b044c962af1be529c79f2e1de78d64a728d02ec4e9360d7d69d5bbdd9a13e2a865e9cb6f9bf2c09a5bf98e7345747df37c335dea394143d2fec9091d3d3d237fdabbb4d21429ac60a0b83fa9db382dda7f0d444ecd5ac1009aec380b5a7b6264b9c4ad50b67575dac4a28e96686004f2371267be1cac849ac5b900cc51370d5d6aa11aab3d73dc102d91f329a5e94a60cca5740a9e02531d35e7231558b28fc6455d11b6b3e88dd9b8a59160dd646746125cdae8f19ccb41a47d33083e93073e67ab23d4a04f5c2f76d4b02340183720f52439083195a2964f8b661b7fd05146c1c6f783896e3b5cbf64a24aca407f3791dcf76103bf47343720029dbe239d897bb2d66d766a8ab1710ce4a1ca111d11e17ab3f29168f315d41338845bbe3ed30f5c75a0406c1d3755155a9860511bb05e8b5410dbd6836f0429687d10ae29b2171d0c2b8be71c06322ddee6cad1b6b6b3593a77a724184499a59a00198b83e1ea944ae574f00753d64191bb00d69db13db487946f03d97a9aee4b3ec6e105a0c771062349ee4d32acb30cadac76d5c68b00b26e956528f2d474a188a03489aa88744fd630f0c889b512bc993046919a97480e96ba2dd6525bfd567f95c5588e14d5c2c540393d7e11c9438e07e00141700610229138e94f92980321ba75df15de13b17d10e4d5947122e217d488590ba802142d1069ba847f060bfd6de1bcf10cce23a22515b86c6ca7f037aae88cecb0e252c5d76c113b07eb5a463ca95028861a0890bc3bd228cb77efd30b68fc8b134492b42b5a16b0b292a3ba2247d457844ba775fcefb05bdd7771fb79f40bb60f48de308a953c093a5b3986c06e0f0ca3785e0060295a59529e69007c7a688d815deb50230adfc90c3ed1df4a9f65e27c05a7272ba58f5a9165cfdf07119fcc228e0f5a32eda758fc690ef6a65b7b887b584bfbf54e7fb559202c7a6fc628c82cba006ea866cf288c7d4990c4ad44b8242bbf80f3d13658f4f1f8397349b61ae4dd616dc24d0fb3fd0df0e267c2409806439d14807a0ac4ebfb84803c3cd0d92b0ba2495fa4f8489ce8057625be66fd40cc99539c98f7849193aab82767fd2b4badcdf70d57dd86b0f670d89c24a97da7eaf510b6a73a95592b287824eb7cf21563f0f8630f767e80c3b04c202ee36580fee19fa80abc78dd28017a134093722db8b5aa122173ed4d1eb0765605bda2e82347487c78e065a20c52862cab7d059dff69c5f01ee140330163df3fbc36f989f9dfb39868fa608cd0dcf17b9e3bbe44b4bb31b790bfdee40d8f62d1c6a9e8674ab6d0bf11c151d4dc4808ced08fc03094f0edf5f73123f877967921b0529148db8f49180049836fed2ff509ab28b58de46010ecd1603680f0a687626ff97a5ffc01f63081d6063f69385b5c8b40c0c68c1efd500410a4292106afc743ae5b054936ccab21c7946f899678d57c8ea324d84228c2ca0b24b7fbf3354bc8d5697571ce0c47744492a2e906aaefa9bd2ac2084e205dd58e25d5427ae63f65c905c2b7c3c6abe82e8380334204dbdb66c32722f645b8d0e054d27abcd5773280234feeb8c7fd907c568dedd247c2bed67d547dd6fc58faa6486f50f4da612bbd9cd68c4d0b24ce2df10a2eecd4183753131eb191859768ecef0c32a5f9e26c8e85b068c9a922bfa9e216304d37c2c49984d16450e0d2584440f91edaa6ae7e6e7abb661f436758f067a9f0f43ca9b82c4edb5583aef9da02a6d0f520c3655db6916cfd8a3d8de73cb6618ae56ffdc2e16de5e4b3c86881d48934b73db6b47f144e2d42659034479b1b398fa2ad90ecf6bdf22c923de9e304070793b4d32ba7f4f05535aeb3a0af4fe3c90442323b331a5416dcbea2edc590ea359fbaa8982e73e28485e86401edc51b72560afee7c4d79f87d7e6e1703d360707fc01f17f8a23bc24e98d19d9f37fe1d19ddc2754123e878ba26547402e9367359d40ef916bc289f36a5b63bdf336a63f4e23f211a3cd0afa6763ce09237066874ccc77ddcdde214e406275aec9eecc7c85ecd666d54ff08a508af33c958cb1086f0b63ad85815eab4617d34a71f486d835602504ab82ba172b559755e6a200fe09e25d55b12d49ab4db4f4f5d134688b22e755a7f75d6316f4edcd88f87dcb8a6bcc9eaddd31d9508289271d64c956743aff3f6666f80b7774d44f5d6b35f10c31f47f6a420450c0ba0f235c80f6b5078dc5ea1abce0cb0144cf60f5634e6c24cdea5f4140b581448b6c70950995fbb5d96499dec3c12dd1260f26c81e98ce7a6dcc40398b9d1efc62c46908768c6ce60b04be82c2510f5411ac9898bcc04f469e842ab79b2abfc0aa369e9d993f37457a2adaedfcd84f52a127b854b038b174e6fabd668ec33cd8540aff6c73381732d54b5bcb6ceb2ef10336390e0747247032da9db6b275f196bf65f80d0d1eb6d06713bbab48bcef4cf78011abaca99c0df1f237ddf34a5e09a75fe1563e09425e2e4291d50bfcf6b85dd25fdd9d0174f9e654440e991296c8c395ac6bdf03d54827a437cdf8e61b7995adb6f1d5cacfe2faeaa368be7d37036b4d40fd91900ad7ea668fc0e614417bb16e62111cf5be8ccfd95a17d877a6c900fae797fa3b6162824adedf38dd59cfa949e797cf8b8bc5369bd3ab017d2cbb95fd6637117c08582a0cbc17ffefb5307a512ac322d9888ba36bf4d8928241cf69e0f866146accac95aa59a7e06115f5db1c732e7198d5ee86186709000915a5c78030966bd95c6e53e079ebff20f65f7bdadae2348880900c1229433ec0c5ce1f22c7302a32cbe860549dbb89890f917ac4141fdcb2900eb0f840f1bc1d8e083e5d9dbd3b38a12338345581e53068a07be6a1bc605617b87986124952ced9c7d199b4eaccb366dd4338b70248db90936bada8a417ae357cf54bd41e70fdce12f05eb40b47bb8bb066a3e7b3bafe7aa82e1c32f14ec3876d9f5e0b54e66fc603929bafeaa0799547265e800859f28a0671ab0c8588b015dfc2a4149d09dc8ed197e74a58fbd597ce28b43d56d8a126e508db508dc28b8da17a6880368dd884c0d1276cb9fdfaa549ba4096d30b58c41ad1338387b3479b791627ffcd9c542a65ba49232d7f94ef54d18c3fc60c29d0187e228387598f15337b64bef3f3133e8bc367a01df3e65efd14c8373c245195380ac2cb2b8760b8278470c4b1bfb75fcd9e523f5a69d9d962b4695b5f659afe6d24dbb26a0b67b857083f7f0f718a0110eb879fff0c79a80e5f72c13b64e8e206a4b36259d14fa31cf01a80617e5deb4ef092503ee1a01ad90e7115ef89ba7a92c55196d0509b6c6c15918d738fc1326da169974f6b0da7b77da8ca462181b27d75157b757490457220a7c7e93d31862e1230c578f8f11538a54148ac72338db3c11c6e494345f7bbd3ba0a486f151bb72402f641e321251c08b82a2bccfbd4d8d93204752583dddc8fb36dea1a80fda6693d38b3489ac43440f55b8337d5b3d8412ea35480cab494dd2cc3a8bfd913492750a35ea78a25bae3221590a8d5a8e054e53c9e926120c63daed779ca354b86dbdd5d09ed5fcabdfce30c9a53329e8517715ffb89d3a89a331d37ce70673158cc04e5b75f43be68d78ddf9e63b8b35435bc3812844648c36b4a836601cbad9bd754cc0f875158a627e48f6f316538fb309532b7fbc1a3051b2bc7c9bad1889397415ebdf556be1c587513ad8b04aa66e5dccfb36bc57e03853f52e73bf1b4aeedafadf7756fe07ce66fcc8a14cd8af03069a0e5e6135045f0f8830b3cd3e2e5f23b91378af1b2a22c5b4340fac8db4456a1b165d76975caf3d4c263abeff92b6ee9dd9cc379e8321258ce4322bee5b7bee0ec05357568db17dbf4df684a239ad0b678c40147e73faf5ddf2440e92142682ba82b54297ff48f1a8839dc62cd524a62e932e39d0eb2cc7db4254f210a70f80640df0aabcea7beedc496baa5f87d2a3be20124f456a1262990e93d1e850e0568ed0354f4128424504d600334278146ffc052846424e6ebb233bdb1c561709b182ec7a67e371eaefc4a09c650d7785c588455d3e5c6f2f0cf70799278a685419eda16fa0adc03883ecb9d71f53d3a353038b29fb48fd6c4c804b09e8be4de9f0749e18fc80065694ec1662cf44de97f2fe4fa9fd0cc32b04279c3b1a3bbb18924c98b524a55a7c5f4053bb65713d07dbcf74182e8fa8a7cb1291209a58deb47997b5fb5f281f6028515af21904215cab456e90c004aceac3e2a1757b8a8963d9a271a80e1856533c3844253679cc8f495dda6addbc98d8d8f0b1903b797e6f8d6736902e535e5d7084de8b98a98a265d60f091a0ff8f0415c654877ded29c9eeb95b9aa26abb6c7a21076ac8db903f358e94ce38b0adbe7f61714fa8c028340bcf92b80126181f30a082d88ac0a07c42fa4435ee0dee6fc6917dc0a0ad0db573c024d1ab868d72e876b0e428624964c952ef79fef55f85355821db4fe1e1136dacd26825abeef7971ec42509da3370ea5ff31971d5590e03de35096ccad299602a55047c8ab3485d41563196c5b4d3880ab605c73eb5eb8165ff5ee5473820e93e46d42cc71f700982a32ecf64ff1a565e58ae1a4649dfab17096718e6ff5df6dc059e9c65c4538072336a6ee1ab29d9138d017da1971335e8a71a433f6d13781bea4c80279d2656c0fae2b011198cfa13b0989e228fe2a8c29061d85155d69b51814235b03427276ee228c79a9ff8a3005e3260a942392a9b62ac89181456c40e9665a906e6ea42ea99d4a632c67f6c7958b10662b01541fa53b2e865f3641515d2a685d408db977496d8a610f8977abfbdd27d941c2add24edb67d0d86a7304cdb9637b75f1a054d228c600ff5466a4df03cf5a2b53c367050e8e7179be9e76e8946b9c3917d146c43a78af0756011af07245aa3d74c84752f9b0d08024c532b077221ee8e695d66e572a66042e65d47a505dca98578c8adb02f7d1c2a94c83f459d58f5ed936a9274805dc4ec5d29788edb8b6818d3108f0b73f733a623b08c15e176e8a40cc179e8371f04a55418fc081e5a9e9d2dec90b69cfb0ede5be668284e337c7a6cbc3209698fa366c5c413e3bc0b199c31409c729abef337ee7073b9b1e529d3ae0418054371e9da6979973be3c56b36e12c85374fe238865cfcb55e5f30ba80ef00d2bfc2c79022c8fcc31cb137c6a878f9969921d604ae98899e9d519c75d95171073f12adf00a2ae17d0940407ba4cc0cee2bc82ef564a87d5b0701df35e417cb9a113ced67f0af049aa5953c3aaa3bd40a5a8d56c8280c0b12d4f0a3ffaafae3503637c889eda78e7faebe83bb2b7c353ac04ad8f36c3d4e587c5ec1e0d63c2484b0aab8b297ccddaa1987d96be1209facf100166d11a7d17203d6950cf1cb3bef2ef2d4039120464f7f828f3f7bb99bf82d170ffc320f5f5bda400fc6a7f300e446033bc5f7ebbd4217d853f379fe1b197a3e1b3e02166f4d54394fd3a014b1ef6f3eeaed1cf8a538051b192df38f161a1afda14768b792c609e0b612c2826995414413f65cd5c6f827e922e8b8d5cdf5016dc62db320a212aac3d02a4c3cbd86b6a4381d27be74b4b718502e89886aa485784f20d6eb07a878886e2ced02a9f34d42016b8114d55eb0ce2efe413d6f0336ad4294fa6683acfcdb79520688fd7a2db9fcb9f3e430a24b3ef359467b858b3ac4aae79ec836c62184c2d666d2b1dc3ccc21db6dfcf566689f87e5872e7938524312c563545c8c7c187124f3dd1d4bbcccb0cb654d32efe785d4afa96f5a6ff9879ad1f93bea579049e46edab020ab9e30a9e8e9be68f76e2bc02c51b24328eb2a6d0a3bcce9e4625b0a9bab52b203b50fceeb063d898cf3184f400447443eda016f1874631edd5311134d726608a4523e091b7b8b7eedb22d9826274cf4700aef3f177196bfc1f92e1042be2439c77b4bbc1dee8e5ca28a69448a9014a1a9fd2d5d14b3103bd7cee397fa9d4e34f097f3491a49d50a5fe66e00e92942740839994ff9a88db1c00b8e28102f61d2e0a37e228424a33e8cb64024d34a8de220cfd2c51ba0091c2a227dc84714a60f0f9e4a0fc04cc852542216c5a756e33c914e18e3a297556ac19ad2d796e633de97daa53ed0d66ebb8153f8cd2fd01b197f9c8f3bf89805c694edf192fd134eb942f37820971df1bf096eddbe609563e7637891ef1ffd8c1f557afbaadc7de1cf4545647ce41e51444591b072431c4ec6cf094c2716c365ed2498a81a44f4f10314b15927dfea8c012dc089549f11800b76680a4dd946003ade35ac309a4c420a7517cb1399d5f8cfd91ce8e8d92dcd680b66e6dde5902a4f86df016007d489413689c72c2d4088f5535aadd953295be45b2edf2282593b14f9e77cdd4af04c2d7473b1d3b02ddf76169695ba6320271a1e0d33a00f50c024622c7f6010ece88e03e0e238af667af9d72582dd973e84bc5477593c5611cde8d79285b41c7096313bdb1431520acdae2c1368a2fa5fc7e2012f1d23d355c2bbd319471b77a22844af1566f668bd54b52b4500bd8887a037a843b747bd46dc97689bb32b00fdc518add482328b30461954db7edd1b158a3a2d97caa11059a1bf397ff81b971bd24e8ad96067edffbfa3093d7e9f34639eb5551f3d6e9bc1743d8d6f98a66c9311a14a86f761171f931200ce45d62c34341ff4f677a24b563309b3441b799c2d66b755ed84c571ac1a1747a8e2d460ae239814c142b6ef7d0f1328e11b620b956b5c5996372b9305d001ff4bbf6197fbe40f62d19396234191ad09cd27dcfa0a1410ece3a7355b4e59a6ec71da467360f0a80b9a47fb212dda2a3b2823822511289a69f3a48f6b913207d55a2735a07333f283287b4177c55d1a309be5287b23b94b4f30597ae7a843797dfc04d9d32f3826138f772116631bcdc1f0f252214c0be07a269d9f92e09071221d12e2f4df8e846d6265f78dcf95a07a6a66b2e054cb1f401ad7abdf81bc410532741465850b06d00a282c81561daac2a7945894d2e9ddb80ec7e84dab9c9444ec9a48aeb0bee07eecfb87b305762aefe45e613a62b2e1b5cd2125b945bf346aedb4f1dcb01ff20a70b9d55d445464330a0365ca78ef64c280c25e0f2dfd1f90e9b3dfb8255db412b2793cf91a914d66ee574d8c5481275f2ffc0bd20907ed232e40cf572f650f3b9b20df3c1da05cc511eb1cafc01f3460520d59540608d83bee506c9d49c5dae91f91cd4bf6af4e7ba7ca1913e0e3d22f3fed6f9fba77096a4bfcd09b47f26f1ff64785634666c682e0a2113af42955a70d68095e9567d8d65a7af37ef9d705ad91428222b88276c01a82e425c044cceb2e4d950d3109d1d1f0497bfa5308782de55e93648329c06aa7c4d0216142a4371d0ca5e10e08afc4eda47d343c37b63dd056e5202e3d397c0d212e66a5f1d50406d8c0eb047a8727d7aa0a6f5c24f27e3be894a262862960318f1e3b8ba8a44433f13932945e0108d3ff43439f5743501b415c76d031acc351ca63299f8987ed37e74d7b5365d9057d7fc72a5f8baaaf0b107b854887b09cf2a1c25a684211017a66506fdcacdf4067505129a0c93cdf33143f0cc6615644217bf6c9e21138174877b948dfba3fbef1ab9e99d5a1fdd4493b98040f186aedfef02e1be02e6ee091b958ce8cb0165eaf629331622a5c9705475faced7e17483e220dfa71cf2ae6b04bcf9ba76ad7f2fd8a07d23a8dac6a99ba8bfc929b403f86c64991af57d8c68d8c820645189038691069634454286100704555d647db9828d0007fe4409c9c35e1cb0fa283519dbd3bed45025b60a8e83c487025fca91d8e3e3d828d9a182ff331ebf1bf5a7244fe1edbbe172ea0af4ffa4445a91d168896e42b9f108de91866970c31b6d4d496e00d6f321c933781981aab303bddd364c6ee14978f6dfb448cb4882843516417bbcb6abb0d800d812d2bbdea9d3863af8d9e6af982d0f4e95a1d53ea25534bf23080f6077ca0a2579a5a810dd8615d6ed065d0981a13d10e0215410049c4f949dfc343736edaad8a42d443d0b0ef5b0a342564da2959dcae5d02014c8de1b16f15c0c4db6129e6bc1c1ae74efc756d3d74aa2eadf1236ba95ac256450dcbd78111bc9e38c0aa0c989f6c0d11169ce76ad9f1603eda75de1a7994705748b07e2fe33f707576c80983a3ab82d9235e63dcee29eace753bd8d2e766cb8966be84d9350bc082cb4f71db2e50c1912acf8766193de087a5cb51ae7897a423429b350e314fb57d0fdf3b7419d72c257df3587c919b6dec98ef14278a6c0eecdaebf830875e591f4aa98d9b15c99aff07071b1a98938496a230159b9bcb091b064d44276ac168268246ec19bd67d56453181b12e453ce92666d5ec0b18008d0f925f8188354108fa508834cfec3f84fb836fd40b3b6ca1d7915beb26c88ea44fc9c7de840d8d65cb149ac6bc1655923cb48c449a36e94c256ab6baad13070991588e4e61aba968630df9cf5783a7a0991a8593f0fc7f9fa01769feebb28e8dcdcc77b10a795b0650b956a94597120825018daaab3023cf6091bf7a9cb98ac96078f008731512a3caacf61e5f764d8a57acf0ee61661527361ead048be13be85ad95733129ab05e4d521a1da81f08107e2bfe8bd92533c9d8a756ecdf5b6b9619430b0fb5e4684d0260048bba35ef5daf623938731956b3eacf37ddcd8e268e3bc5351aa6b85367d7da0c0cf37fef0cac309182b6571fdb08775d887e75489865b6380c599551204a0a21e51e7cec8299a31180f4158b573c5cb6d3a8a3bd61f668c36f7337443d231cc60778b249ed011f3c5ac38142cb3e46fc604d40d33084180a7e4b128d0b5c89e25c439179318c088359b3777d2c4443159057c6c5043471bdde99710b04c10999538670a290935cbabc7a2129441b426c046d33421a35e9168b8d4b1b1c6ced3adaecf038539611e5a92bb4657cb937e775325d2e0b5953b448e7b9cbe586af04d7c1aa4442e8813ee399c5fa3a9f58ba7fdc6865bd27df63692294d041a158343aa41f4686de6c3e92630fd906b4c3b16cceb174e2ea7e58ebd7447d3a3d11dc5cff66820d6a0c7364092aa0be5bf0f8d6923bab94fe830956799e47c1c430c9bb6b6dfc9a649ec90baa78b2b359b288af9334259a3bd8c3437ed7c884fb94a0e5eb7ac86471d57f865326cb15618681198e001ac8822833ea83b2095fea41c8af99be51ad2c97308e88facd973845286a98a592e86f5b93f2a6bbcb72ccbe03c7c21009ed392c86cb1f97b056bad56aede49fe6489ad9dff61d489f59571c88fb64a6d7c2019ae3e980265cb43a44e8da2111a8258140c31e93c5d14503913a705a1c1e81edfa348e06d9c28b4dda02d98185868145cc43d72187082c388ace7e19ee9e2b30f4f18f25c9545830235b46ed5838ed0b2b8193b8058cc0bf5abe9b3733a735e2e61ad6a11e435ea32f77530455aa65c06be6d1c74b9e9d385d4be1e41856f62df0cff507ef5f99717c8c98ee151f72a828472a17e73b66c303054cab0965aca620beb0a43996cd91427081d585376c4f04f58e5f74387aca95daaeb2130dbc08acb4d9b81a4f98eeeed61c050bbc0e59d0a18f9927bc5170e403da59663b295e98a1797e5434df7d8cc39f785b7492fb31a65b4ef8b791f26acc252111837b448fef81b9bf0e6d24b9fd718abc11dc1bbb103a5643d45d504b467364907c7186df40c585352abf72519e8c73abc055a8e44e4d0681cd89dcad907254e84ce83b718a1b643a6b564004b9eb36b7340e834a08ad0caecb1f2576c400be6034647b248d2bd3f8a7c6b037034edf8fbeff6c3bd04f3e952fa3d0bd93673bc332b1abaa31b2ac2792fb742fbfc6d477c02204423ea1dd55eb3a0e9acedbed6f182dc80747f488a4d9cccd280f6355226e6e0473f3805489e77c3ac8b832401011f97b6283996d347e97f98d8d639e1142626365a3f186eca5da5a39712ac1ff366e63f74e8f5c4e13f40915f3e1fee2add369bb880ea668564c2d1748035a13c14f42f65c88732a26859a8fcae53f0d84c593fc8d9754df6639d539cb612b90e9ebcb9b0909caf258aa70fccddcefdfb7e12bbe4d9662936922e31bb927817be35362efaafff252f493f97580725810213b3af048ed0b1089729f1705dc2c1572a9060e1314454b57bad6f3505cbbea99fd98490fb88dbaa56ff2221f6faee10835f6e4584e8152668d1715677ec9c70bac658c2dbae6a9a8f01e6ba59b85df1cc63c0c893e04faf53af156a76bf77c707478cc177b71e5f90f9cb422f1428467b6a7c812ac44e81a38c9cdf192c85cb74c727bb016f9e40385c5114e99bc48d5cc0fa09cd763a9d80b5647ba643b0d85d1e288954c35328f1647bae4a7915db438f2f8e3f6d2aa88718ffcaa62af6cad51d435dff35f2d98c61ed1ee68399e8afdbb60aec14c7baa63ef8c38b03110d8fb44b1c5e9b293cd10f5bb0c7c3973f7ca907fdbd72ff83ff35011d0d1029a63110d1702f9d7e61827adddc5b1068d4a3879c252e494d88c7c0e1a77a7df12934e405e00f4e2145990404c44e284bed917c65a083982e55463d73ee058051ed2b5260c501812236ed8137268cfddf25e5ec40b74cacb8c2684c0df7a19fdd7df1346813994b61fb52800d5cdc878571b448da9320894a8269813e96b19fd3ae5a76a4990fa817caff7b9521724243f889eaac25cc377d6df64c9e501d8d44b30f71aba685f85b1ae9857a23d133cb47196b807d4ebb8766936c28bd58d5acad1ceb31cdb59a6826f25cf866d430c3b34af96d174df7a6dfc252cbb0dcf5fc15946a3b3253380be242e3e40079cd26d11c9e730ed48043047f81bca6523027f88a364ecd66b2c083a3dc3139907225dc66263da3513e49bef5ae844c9e4ba4abd3a2ef82f0c31bbbdff516f9d879bba8a8ce59133464d5043e1b1101a552c9f29661e6cc3031b478bd4e1bf7fac6b79e6c09125ce59988a7d1c1b7c278ac13b79f275fe53c8675a7509e6bd4ab87674206cedde3e9af682da4c76bc8b33292013c4ec41e5cf0e71b5becfb528190b58832fdcdee718987cb6ab679bff3d31809e48628a7269abf99e4ec376cdba18aadb642c990f12d3a017e0d26c617db836d2d0296a36cd4fc189050713c7bd7963750bf1059b73b1614aafae80bd0c6e2552abbfb352a34f2639499d226177b22adfa30429c14dd992354800c4b451a374f55490bd6d02d03663f3f0da91fb52c671634ceafb7b7f3e7499c3699c130f75afedc1ddee42e5940f73ce5463f96b857f0073f31476dcc42929adeeacf672f9325ba53c189fe2ebc162efc1a8478fdcd805796455cedf8ed31e87b932b751b006a33246cb2beb94ebe44a9257a5983fd1a9d9f317d3dd10bda11438323283335bc746ed4bb74f4e3bb98d559d000d490ec843cfbdd7587557d462f09ed0620f52c5c6fff7c54e4d593c3341f68405791c1e9b01ca233f029263ce65ea386b180f1e7a9cc5eee69154479bd017484f03269e4cd7f60226e2801227012f713d6abea192cfa6089383c856b4ccbc3c3e3845a507a5051dd9be7c885138d7a1dee1dd6afda4d84aec3b7fbc31c6232af11234b3a0c95c4e4a4e5cad9b56b8b325d69c257a037f38d293feb77a59c1102504f5040612f6a96cd6bc5ea000a626249aa85d0b0fd854c68217e9db8b6e0b1fe833d13f5fbbd6b0788e64961dca7c6a9a362aaa58d508313cc5fb368dbb57f48d9921633a92256bf49a13e4c00369f7dc4566a66f46732945a49baa75198abc50e170373ce3a381cb3bad4b040285654572e6c8213e2d2b8b4407160d4c17695a5d5d9d653b16a12e39ba490ef49ca9c075f2e01c701f725a5198095803ef4601ec5432026c8ed9d33dc38ba021458c24323bfc2345e006b5b029b0434f28e04280ed1443058f53228aaa314cfd1918c2abe80c4aae3f5582bef8576e4ca7a78912fdf9be2ac9d690852cd504e07e59c8faf35bca2a785417339674292cb5a19659a8a98ce9641c48c35dc831bf61d24b09c78c2b67abfade50ef405c288b036d6e4cac9c1271411af959235f602dc3e4e7e882406110f4e47555b44d04e788956e33439d81f33e5c318e1c156e148c10e003d6008641c26d0593cee1282e5acf7dcc4eccee4ed4b36b48ab3ed1b274046a83a5f102f808aa17062dec3324a1e1e6d415ee1e2257f3f93400cb5a6efc1bcfc517024fef04e0a95b3da94d40aab810f24943854a10dfb89b9cf9d0d10fb68a61085800ba14581100af53c3c326fb103c3c9ff012d91db2d44c31579f29f522175a470c89e8adc62bc1a57113839711395c94188003edf748e477550387d636b3f04debf1d65c1974b39c75202235b2ce9e5c5743148224632df292b88bae45c77ccde542c9ca16864ebfae169e04da5a365b1714c72460d4d4203e9385ac86be04c99b090bd2a071e28570b2f71a360324c0357adbd7fcebc8d65c1693c74956ea85bf6e7d823c0268c84ce6a57c7b48e23205e15bd2a68f43606ef4e8d3fc2a1fce594df950c95717af73c00c845b10e3e2fd1d168bedd362759654d113902de61c25859292b152529cfa200dfdaf6161dd96585be5a6dd9ec6e5decfcbbc5f78b99b18b9481fc8db9327b1a384991109d01a804f38756aba10baa0324c6487e339fe2b0db6acdc1096c1051cb303af0c8278869ad1439702bca63293634ddac1856f0147b082fe6332ecad08829f34a72cbcf969c5a24cb1dca073bde74e0cfd183631dbdb1870c0ffaa61d82f80de0c6c4d616a3117574a82809c06a4883ca63e0314342550efc395355540dd8bb8e49c844818784274bfd86465350a8987586625e41bcff984f6edbb440f1ceb42edf8c759aa184f3ee9fd0f06fa6769f98062978f7438d36e72e55d9ab190ef09b27037e477193157ae7b8cf9e808c3bfab668ec1acaf224cf21a41ae7068a34c2bc22a518129f7794f966369dbaf0984c3b4f88d070ca32d4b988c3a18d7bef087233d6ec03cf20d768d41c5e1eca012e434aa1412d08026a5de46c24cc370f8d387030657ef46d4e6744f342070c77771be046210bda3e075d27a4626f16e6a87afb45c95c524aa536f00ae2782aa0915cf5e3bb038159038a58ba5d1824cb04a237d7a94a842d3bd075a37770c7edf0dd11b20a17fb12c05a8223278d57788109a721b8052b0d3cbed9eac8c48e4ffc83e97bff6a9b184118e2c09169e79e16881127df462fbb4f581099a6031e7d8380b9f5499640dbb1f1a445d02d3e9450f96f9c8a5b43eb4e3205527da9fbc799eb5597bd8c761808add79c2e59d0a2161e02f60bcb96d2013f78d97a519f5be94a7827ddae8f6af915671ae50716b64098a2d9b0e309d7158b269248dafcf3666c47cb08d52854803e6e20ad4173e1d7a683d63b9b487204bce22a2f18a2ab87f0a44fc846a3e4c728c2691caa226263b1fcd6a821e279269d0e38ffd30d852947008a180bd1ab7e431a8fc83585c8d8968ee6fb2d6daa3f695837eb558064ce81d3e4f23362c7a30b8def0e1cad6b527ada0df038ed347461d956e7fcd60a7c98bd36b3021513a403c8148f181c03d8fc3f04cdd2b8f8e4124121c408f9f8cc945ef9169c0c8bf072c6826823242e077f2325d6dd0f5de852dd2af1fcfe123cefd014de7237839ef4ae393752bfc43bc6ef06a2339046561c81a5026660293186a703631fe24a76b13323b1509dcd7be0ae1cf6c6922f8013756e8d71108f02ac42473f21f80d70e710e3953f5f1690e22bbb3b8594837409e3eb7519699ede13d5ca6b0aa33765c198d4c0180b2cfbd743afc36777a346276e9fd27c0fc58e10dbdadf3cd45540204d718cd52f8d87313d44fe9b6e9993e519127981878a32203437695f21a5d421d01a8a5c68de7c23042494758ef5d344ca8c3d063d4cb9a570e736ec3e824f575e5d59093de70b271359c5491b5997d2825a5af0112b89a96897d93dccb15ecfeb0488b83d0f731cda2e920e388728daea48b29a32b1adf517079f3345fb296e47365df70aa9598a735e317e060f8a9eeb5c2bdaa2372c3bf5bb2fec4059703a62a0185f0cfa94cf734cd26ed49c06f7a2149d0e7eaa628f588e5d5afc74815b24c04d3e0d306e0ee0a4954f2290d94e7163d5722252b18561f1f5c1476d6b1d75d000c0cf8de0aef25ba24d976d7a7d128d4e2f9b1b41e6d54d5d5bf9397ceef6c311ae8b3a08fad0dcb013a1424a22a65bf7de311aa487bbdd42e7d12797fb46016fd41de6effc87ea790049b86cd186e31c56c448b8554c416482c96d423bf7e449250ff8f1f35d52e07b3188ea7379df6ec42bf30129fdbe06e08eee3481d8888e43ee481bca4363aa6800f53f4877e2ead080625b3e0bd0c71f16640996830d79e5340e91ca80a94e565d94bf4c8134504c581b878cf64f3e87f979ebf9c6a8858786e028568580422ba525584c8b24ea4711b217034fbbe97b3aa73824ddb74df7baaea1fb54b7cc6659acd7c2df81ee5b88c1e7e6b93c82fb04af84b5d583e3e4cb2ebb9a32191397f13b648140cb165fd0803256269bfe1d4020e0c01cdab84f30c5d2b11d43a57c7c3ef5747814508607ec20ca4645a1a0a3e2445d96ef6dc5f7ec5c7c73a9caf42252370b34617b92974017f9924b66764b4e4ecd2fb5814afb2dd413a8a9b12183ec19fed07b5d3839a2643229051139805de8821b962e301161321b1823398e7c3ad40cf155bbbe254af605d610aa6b9f2635b5c065c443d515fd49594053ac6d54ef8875631679637d65f12cbd9542812b57722090da030df62683e30095887a65d7f4e28219f3ed27bfefdab21a31b1665fe59101154698ba9327ee232453eabc5cf943d7e14e33ef703e4b00f891b0e62579567ea8df6063588001fc8edadb34c76cc8f49fbbdaf152e5f60e11285d13358869156283211fb0dd002962200707290494e6a6d07fd22cf3888e40b44d7f8222df9eef51ad11a94117a706c241cbf573f44f6fd502fc345b68cd12ed202b8b89d1072dcd76855a410c889923400d1f68ab640c0ce55e27d8f1e5dee76480523b45abf216863e158ace23579ac4fdbf069fab8476a3afc580859d170050d2d39086df7be33bda9d9881e7a788de0dcf7bea40905a6bca36bc2d04dc81d692a5e11a7cc3a6a7777731c40651a2b5da7acbcd00680c5d476a74321c9fff4ff3085b7eb36103cb0bff1e9b38d0e398e1f9d67c22288c1257ceb910d0f5942b26aa0e31b08bff4c1aad1cf151a952ad91ac6889b2a79e076ac00df04183578208305475e7453d6abf66c03ede7bd3a9bcbe386ec8b3e56e5b3643d97164a06a4e1e04ea7c6b945071fd73c4e12275384f4fbee39271612add7343edcd19048e3dbc72cf78b90bb2927d989274122064d39691326317055439506bf101c5c763070f87fc5cd8233b1c229fdd02a64bbf2792b86d864eccddd7c21a133846ad737deacbba40342702d3ffc1e8b83dd2c273e0cd7bd2cc7691dd290118a64c3965e1615df8bda12fb1218f3b5a0c9f0f79153f4e4590436f6311a08ee66044bd8b8a534c674385771d0d513fcef14b43098489126497774c0ff59ddd5de28df632ccb1a353b40ffa1709a40a073b303ddb557532422aa850e31949a768b4e7603cde96d075c87e492d291418bed49b96cf69e3c651f33247a5e9400c698bfb665b3d5a4addecee71c0057c84847a65a4b0af2f42660d02ce4a8a8c907484038ac40e43092aa4d050bddcb00afbbd70b253bac8233b753990da2223c17f2f03354f4778c8fd6d8d8b1f43c2b7c02e809c0ad65822e5b1688c1f70f0685d0c31742c2886793bb122ab3cc047594d387930b10adfb88ce10d84703ddb75f393dfaa24bc903643b889611c7b03d9b88faf7060b5c43ff8464a42514c8237f1d8fb11bc6c44ddc952f56954350945a02a4fd537c9caad27f120bc8e3278f91bf2b68b998991b1955f374dfadf87a978aacf0fa253743b735678f36bf52b1ff1783f92589e1394568e5c7ea924048eb632facbb07aff3456fa96ce301c4904173ec82c728007a2e7b9402e3ae81b1fe51e980cd41b5b83e079181389507cff283a610c1d4b0aa06641344933bcadf5f6248acdba454bb054066aa3aac8f40a4701a20ad8594a6fdd14a6db03b961347f407058cce7c18cdb50661b842a081ccd7fdc0de7a0f4428b37fb09ff1da08e912d083ab20f3e53fbaad162aed0f8bbff263bcfd34978d2a78e08efca093ac15d8011337bd7617c001d4b5c45253fd83edf9151f59b9b280dd531b3a560b21d2aa37c77c9d25b83a343b993740bc5e568e49f22546a8599d622874e276aae802166b2583a5b5503cef5ae8f1af4c6e5e1314be8ffdaa512705cf7008f849a45cb4c473585e6f892c06f620954f76a077b3c92cc62ca368f2de1847a8b96d7eee47ec584bd8514a43f8ffab21bc07b41104daeb82995c30ccba9a0e251d2a102168e36481dce2c0d163f09bf18284e080c6488505424ba5c256ad2ca04381c9334548b60574387598b96fd7f1d76954156f4dfc445d8b88ed585ffc2104c7858e5cabde0b8da691496b7bb735b9a594322519d9095c094609453b64fad3484b9e93274b261268594af776caad6e49f5bc77ded7bfec7c4dcb4442510f0da81f5e8061ca0b9313198511605831005330a165cc172699116392e4f900f0e1872ac44801922d26808225082856386fd4336008c0194c9c0126d7c7e027063450810214966e505a413a0d71c39c73ce1e6f68ae7274ace8c24486972a5670c40a9acab0a18c33c4389921e50c1472fd1ede50ad2408a28647ec124446d1d413793e8f8e183c5480058a29a35c7f07930a78c091eb5f20450aa6b0c01baa1b971c1480494111b97eec36f9114956cc39e704bda109801e39cc914222d725505084822272fd1d6fa8ea3821b50206b9be0e6fa85a219ca0c97133986fc7c832c60a79be8e37346f5217343806500031a41003863c1fbf5e4c4a00c316585280c20a098510355b2b7e07bb5b8c10d4e4f99f373473c894084396411846e4fa9f4385eea28ee45adf460d91ebdff0866a17f50421b07202a65cdf863754b7271d308ec895e96b784375b3628a1f2576f80835608a57a2162f9074783145982861b6f862875cbf07e88b2e72fd1f4082e4784375bb225a10ee00a36403cc152610c204b55c1fe60d551d1474395dd472858a92ebbfc010e5fa34b0121266b821072eb40c219f20620515ce1417f8708145174170a143aeffa242b48af7755dafa09ba6e2eb17242e50f8c224d7b7f186aa1135d4787122cf7719d9e450e143835ca3960aab61b8d428c68a14acb0a28b5cffbeac3023d7ff2b9ac8f5412c72c8f57bb01893eb03d5b8e4fa3fb21022d707b294450a725dd2e2895c1f842d74c8f52d90812d98727d225e6878bd6aad482899894133edcaf34a9729baa45053830e1722b0105197b8c872ad5aaae821572c5e86115dbcfbbaae976b56c18214797ef73a620bee36518124cf5f7d3184956b9b9e92c8138bce9309b9d65a6badb5d65a6badb566b92087ae297c9862cb0979fedd52459e4f5f3f905133a3531679ce295158b5d65aeb4e9e45042db1f7058ba2498597164734a2c832bbe1128563b7486293b76d73bdb098820595d78d422a494d97292b6437d784992b8b231b1750a0a9c2a866861566b67072452992a935d14c79e1621135c3840b2d2e453258acc0d262ea66f922ca66191343b785118b89122e8b266f04ab2b689d9621544f9c653a62b718ad66d32ef1586cf2c4633187f7554bb2802a9cc25861015a142d69197364015434a9b0002b8ea4a02149920287a2055cf1248514240be082f4399a4c6dfdb295564aab8e272e8d1c9762b872e472392ec5c0c50a3184912403d113198a7ab85549d365d174b020430e3218b1e446278ea0e59d2519a05892a1cb7dffc273bd9c80b224d39f5132998c05195680a48b4ed470845ad0c2ad39c18223eeab091597660af874594a607155392e359122ff52932b4ca83a4930189d48622ad31c645cbb45cce8da1c979a7851c495c971a9c91832382162e2ae725c7292828e14eecc71c9c9121b392ecd30460fa782eef60ebcd0c20d2fc8eeeeeeee186eb6736d312846cfb12f6b73fe25c494835105827e3ac3c9184252b6a726b8ddb382843d665cebb13e2342dd07ab5f2b16c27a0e17693deb8b70dfbd11211ae74cfeb45e7ebc2dfce2aecc7bcf61ef693ecad8ccdf6dd24209667e437ab2bf63a97463c60d6d035ac8fb8ef5716db749eb240eb7928c612bb7f3be48eb65dec345baafc1600d4c9c47642d066daf3c49c8128757929125966164f9f3732207392ca5c615d785a5918cc999bf1f8258cc602040637ce0032d5c448a90d6771f65ec65f007baaeebbec320f88005eeb7f0c5edf4dd87f1098b16b64464cc7bcff3dec320f80057996e7fe8442d2ca50616f7bd0fbb080bcfc718dc772c25077fc95509799f0ad75f853530a5d3978bc3885b6bb88e8d3c87e0affaf9e1dce0aade5ff5dc6c99a3acfdc8300ee563c70e9dad8ba1edec6b2a064510f42efa916c21f9124a3a5296b827cfa2e8d2684a1c76b307190b5142f16f0ba7466beed3126ace6613ffc148618ca498299b4573891f492319258732cad1e752554297e240f553314b563df7f50bc17f72d063f2064b407dfa1c547f0e749e2a2712e33e2a1c420d679e218ce0b92ec0e248bf10fc371a427d7ffbd14b802a048b00bc7909880e4aa9fac54c9b62507de95f8b3c3f93c8634bc41f9985962b57644d8350d67cec78bd646454d39f1479d8978831f95934451ee9a59f6a2a2b8e43764327f269244a9e1ff3c4b52831b2d6535ee38c58ff1870f800e4a5c28d1ebee1fe7020b2d25aa794524a29a594725229250754f5616cfaad1be2c2ac94db263729e595745220b449bc575c07e2df06af136ef430941b46a60dbaa393d2de6c383d95f3757c839e5a5cfb405cdaeda3106c73f78a9baacb7be50c0264851b9970dd95ef6d822c4109b61792bace1b6a2d0e7abb3bf548bd9dce0c8c9cb41c4d1a35256530dbb918db5dab238f1affc3c01e5f7212931d6a7af48064d2069bff74cddbe994d14a14145d249ddd1435e53eb2a1c254713159bef5861c07894eb20271b3d70f241079e4ab96b81209cba317686e28f9e3a2f93c8aca55ad7c48f58ae72742a41f1a41c24074caa111a41cc2fce6def5c1fc6dde8810cd07f3a22330999f79966c72c1904fb27cf9c47d4287923469e4a054d17db9c47dbc972f84e65d2ff33580e65db2fcf120dfb12bf9fd9c8a741a6e3515717dcdfb27858585b8de0617b179d7d77c456a9ee6af1897bebf74288fa202459372b0dae028635d83a3941bcd93311eb974d93d329c51a8bfd0a964f93964dc501ee121aa57492877a92807a5fb181c675cff469247625c9d12ae7f9d5df9b11eb9f263457271686404528aecb8a37040b961877941b9f2b54c18910c4b40e71fc8c854ca96c1acb8321ca1e6f72f00cdcb775acf644c65853e31e2b8012021414145351d4509239dc8a14b1d4d41426a922f9f38b9d374b8f2639634ea94ce9671b21286caac84f2c88a54b7d42487b28b4c924c5dee510cdab23c8a729fbcf84f902c2e618162924eb24ba52d619dd454a46b4992f2a89564902ea16a209157dca71fe93aa883918ecff151e76b212ec77795acbd5729c7176b2c71e5bbcf741a89ce21c7e74daee40298b4d3f94c68a1300ecea8e9f704b7a1ba269b24b9928d25f746de799d0f811e082e4ebcc0c504a83184e6cbaf6b2e80f939e6ebf86291ceefe0225284e4f81cb888fc4d8a101daff345e4e7f8fb51fee8e022fe3a709431d6b33ec71765ecfe90b1f96ef3fae834609f63ff7e278536bce12b13c1f11dfed5f7aaff1b4f3f4aafc6b79e88fc71190e1cda0f87afc7e1dfc021986de01a3804ca3fe48f7b7197cdcfc1a187c9f36138b434f00b872e26cfbfc1e1cb06879228cfafc1e1bb300d0ec1192c83c39efc6ae110c89540599d5f2beee1121163f3ab402d912796dc4991677e9ddee4e0fc39abf48e945575be28e4440e4eaf1263f39700b1e449c5865d8989eb8e83882b8dde5949465d6ef8b1ff2a03c04028f7a7179e73832f8932461bc1eebb1122d87d47c4ebb82279f52dc580b2524e282eea6c51c6de5e4e1d392bddbe5b750772b95b712b1528bd0d883ba3cbfd2d3d9244942a35262644a13f29729f965522134d2abb90ea48460a555fc6c9eede906398fc2f7706024b0e5545d51bf24ee6606fde90e3d8e28676ca4e5931d6be5ecf7943931e1539d81d11d20d3b5957f41a81aabeaf2bea64f4ad980fcaf673fb8ec83b99fb548fe2b1d9b9937545ee13ab4a36e52086bae12dba45ee538418395415d12197dbff70ba750d1c2e4d8e5bb68c206ba0fd48ca7268a537646be470433f72cae176d4e3847b73e5aafeb574433b7593c4a54734949472d8c972ffd1a447b9df33923f5b918cf51fe5de68b93ff490727b49b99f4611eb4b0e3d2bb228748b84b8e1565494fbb7221bb7a2dc5fa162cc63ac50381c52a1324c7e14f2280e937f24e6fe94e4e1bae34e8a314a96c450b6ed3b6a2a777f03a6d086ed94dc8eecd496d4b84706e972016b2f2d6ba5a824d418e6452fd418d64d2e112b640a3586f513b95063291c44107463c08893a21cc470e1c315412c49117534a3a2d4b049c0095210484b556c01430b162b284e0006892701133932753ed0b8187f36b757c6a8c521fd4ae9eb65ed638b438c514afa93cef8515c8cd14b8eef4a3551b76337998a5a26b65de692f27cf9752698fd5eb38836ec47fa21e0d0beea6df521752d8675509f6e1d1ac9f439efbc0ebed0089df282b813417ff9e188608f18f3ffc99e43c38716f24608f3733831ac83cff53fd74975b044e83179e27f9593ab56d56e5ceb762c4f86c635f3381fbd211a35df3eab5d754e62065ef4688841d5d5e3a36bdd97f9f845dc87f5f17be623cdc7ef27add44c34c2d6f28adf556206eda57bd87cd84b3ff149e0c2cf0cc949f48f42d481e8df474070086c890ae81efea01733d8d13dfc77d830176af310503b281a384d5e5e37b89b6c70e85e831baa9110e0b9c6042e9a9e4599cd198f95813e12f552e6b6820c6179386c31de6db647013d3dcd75d4d7611c7429f7b73874a2ecbfe1b0c5647f2f76646fa8c8e30ffb0eaa280639ecbbbb63b7fc09c31d06aa4a4375e1331c9791eaf999423019ea8e0f2304514970a92d2185822c4d542c2b54289c4890274427011347d4b6265078188e7208eee8f172648261492da1d229636cc5e860fc5983129b5015a24253889b1dad1bcb0acc4686f5aacd93a492c478fc8ebaf225c7ee22c76f2f3108a78d9cdae829c6be32a5ca94a51cbf9562108d8f4528e49824472639badebf9fc87cd84744eda489688841351f9f7af6145bd64443c808139f480ceafe87178f31c6da1621518aece4b129c420c7171283bcf701a44190dc9175a2df9cebcaaf0ee7a3dfece2099c4fe626314a5d3c71c3062c49eaca97dcef44b98bdc8f63e3833dec6dfc8d1b30182ceac6d719e7eb6c03ca0b8d8f7ef3937949889c1a9fd3942a35a4a7a51a9f8dcdc33ee7b9ce1bcab1b1b1b992f375a6f175863129d1f862c6e9dc45ce247751e4a9f968cd376b3e995b0437af2f03dd4e723f95dc35af4fe665dee66f6e6464648e6ebece355f679b36ea2691a7ff8bd945a7ccb532c098a1f9641f74d110329a06d5b35a19341f8bc5adbca119160bcfc88821e27d747a9fcc718ad6931721518adc2fd55bb2773f554b019dbd272191a7df931ff50264a7ded413000d72a62a4f0146b2ca474f4b47d6513f2ff7fb8edcefad93376fa8d6ce5de4e98f3987e8f63bd9e818153b2bad95ee94e0cae4c8d40253d1f572646a61881c2a6801e97e0b32b8a0baafeb7a41c9335a700188abca91a98523605a20daa1dd9a239392339894444d2f48321633a53b3f5c1a53e8d002450e9fcab53932295122ef38a14409dc40491253922472f83d3d4877f98fcb63fd4aaab9b13c57c751a970707ab23eefeb56d64655b3727134b6bb92eb64951b9098251ec113d063c68c19d31eebb65a57a6a37165a51c9d3ca95743bd9a1ad652afa6c6b40102410bf6006d30326e38694c98bcd1572f8f4e2b99ed7dfda42c20dc6c09c620dfe13e1debc8838100727f3880ec1d1b87e6ffc69cb37538295dca28a594aa18a5a4966e73d6bad139638c314629e5cab9961de99cb3fb557fce3a65f458364c5c8cf12565534a698cf1467635b8bc26add1c53bf2dc2e8a09e46084d9703d3796db6fc3ed0cb3e1469b29433a5d730c4ae30ced628c118740e594b1975295ce6c49c97594524aa99452ca158beb28a59452cab1ba19b59aad6e46a9ec94895bcf50cab9bae8451c4ae0ca6ec76f88ce53eaea62e49194524a29a594524a6d7c8cbe2ddc7ef9b4bb439d1c256aa5b5ba1cc442d4248fe058667904efa93c82e777106f5b0e558ed5089ee387f5e72aabb6ca7539e2d75a6bac35c6aeb6fa7632b5d63a536ba5b17155cea69b33da5a9fbb795557df7412a0945219a3d71fa2e30dcdc773ce9b5965f64c719a4637a51ae6e4258fe0596a86f4e5770f05e7e33c75725e37382e53029d9bc5e95577f7a455ca2e5772b9b252dc45b3abb8928a2b7174b0b3a1a1abf1c2d0b470984564ae6cda9a6290dd2d251829c1483926090d5232f13061e84c1266d231b015b88a3b779e71b13dc33a885fe414814ead9bc579e5f8bde2b8ef5b71ddcb7d54acf8ed31c8fecafb4ef515c144ba2d534d42e4f1b7dd4d718f74d11204c82c71cfa43062510f37ad9b5e03a4d00a379216aeb3e96a3c8fe5b2400d04164d6bc6a99b76901ef03a9879cadc1cbf08526e26d6cacdd0b4726a60d5d874d14b6edc9187bacd0d2701073f887315243ae174919b9a9a542a2f9c4b94d3ac553d7d57c96d82e5745064efa6d844ec2cd97d065a4e8dce02c10a883c37861a366cdc90403d3d3df267c571dfcabfb8f118470caa407afac7693247cca0a3ba87bf183b56756b50345f254d164af4a7adc53baa33e825620f7f2f5d8c7237214613f7e1d078dde0ccc701135bc1cba8f20512313098925049929464ca28989220fd1b4d773a414b122c2fe2a27325994cb66405160a52b49294548832836486d73243c76ab574726b47abd5f25a1f8fc8ad9f2d26246372eb6f95dc6afdaf00456e3d8824b7be87052772eb8156c8ad99536e254912a3cf67cff33c4f0531d93be2c4a4c2112ecb9373652e27733f392e8527994ba18bcc7939ca036042a10935b273fb28c656837664ea23533a001aaf6090c3a8a50bcd804ad126f2c9611fc9b49768403f66c994bebd24a420d37f2165fa1f0231327db0042032fd9e12aec8f481984a1893e903214acaf48310a520d3ff000428993299e045a69f01fa44729089c08ba21e32653ae14ba64c011f975799f33af9a9b2cd5cab93df96b9dbc9af669ab9998e867375359d8d8b66a6e686151ac9b00e6e3a37f68091d92ea6a38e7bccb9e1c69f2a748c1d63eceed8dd91c6ee27dca42f99f8f4affc22209bd33d467713b8f84831469d3927954e1d989c2955b6fb72e5538a6d94f5ba125b1d816a256537b74fd2f9d58f66d5eab33182f4adfcb939f5bd5a6abf036bdf1b52596c75b0364c4509d9c4001de3733b5f1402bf22345000e3884c68e7b92e020267e14b91d0055f040400e0e2284a28f616c8b80fd991675ec6bf70479699f91fee9fcccc476f68c6a6c647cf068d4fc7fff0170a3766fcd6b5dd0be686b2c88fcc8e5a375afff26f73ffa68efc6cae09c1fdf79ca9c30d5f5eebe2e0d3d40d250ddf7cbbbe717c7b43aee75c2e3a66cc182c3947a5aea5399af33b1ff70dfe0e88fb776cb8709f06ced30f07e77f382725c5e108343ffd6cd89367ce174aa770484f479de04a263188fecb071d72c5a1acc1667b4614f9c4084bda279b3868cdb8345ff3f6551f4a1a77b3a972a85e7a43aa8ae36b8e987138def960f279fccecf0f0653bdfc0dfc602adc001ebff330893de081779ee7c1876dd8039e079f0783df12fcac8c71bf5357cfad6256e178ca89f023cf77bdcb254254ca12873ff2c421b85ec7ff70ff5cafc3e70f77bc4d11b6ed13c100746772b223e24ad294f6297d42975c7243b90ffdde41c096a964b1581deb6547955a0afcb61b9c100115b42652fe944e93d2734fda6c311996fb6553948a8a4c4952b925131130e4502a5dc9dd3fa52b2146892b5f02259a6c9c4d173d0e74bcebfd73fd8effe1396f9f03df88d0dc79f085ec3c08f3e78932362516b20373ac0121e0f3e0223fb003a2101019eb0fb283ed86a5c03e0af1f0ce87bcf20f5fcd6ff5f60b3fabb0857d21d4bff987b45f97f375dfe3e3b103fbf09837d46117eb69bca1d5bbbca1ee5d1feb7574cf7a6ef5dd2865afcd9a94a2ae92d39383c0fe90f31ba1f3dc70e0f78f7b9a83c3435e19c7db1c3d1cffc3c31c78484fc6f1f83f2917e7693c87f3859db717a5b1610cf4641d1adfde100dd8f7309d8fded01724e7619653b9761e076f3938b4301cde1756816190fcfaf971a0f3f7fd0f7f7da10e6e3d8daff307e5869396fb713c1ed293bf9f1dfe6cfe3e188eef93dfde3f27bbefc3f1850d73dc0135caf4bf8f59ca9c9e6a505153535150b5272737f223a424a5a626a524243f8a9a729917f98ce6349f4d452935393dd5a0a06a4f4e4d4a3d6b5a1bf511525212521fb551d37a166b112a2a6c595117b56c2aaa07282af5e0f1f100b953e6fe87b7de88833c3c7a7c3c3e1e108bb0e5b033067a3287c320f987e3577d18d85e7a43f8fffb42780cebc0cbfe5ef6778c819e4c7108ad973df95b2663fd4684769e87061c108544e0de0151087c1eb8481110708fdf791e1fde1e9fe411b66ce7a3d08e0f63144f186b418840bc835fd25a7eadf7aff53c1f2cc88e8771d8015168078e3226b4f3300f5a68e75b6807f74cc65a166b29b5fec6e79f94eb5fc36dd4a851a3468d1a356a7c9d6dd8f8ba461b19b9769ea3f9c2ceafb7f9866e705e62b007e855bf3dad6559a353df5ce539387cc1f04603d315b698c3385f3842cd37ef72798d95351f63cd4b0cf6b83e04ba71bd7f353fbff0e6dd1baab979aebb1b8c3d8cc76a5c51f2d1915f71b05fe67ede34d4c29ee47ddde746df852b4b9afc89427d5434739fb02ff87c890ba0df82afa75c00fdb12fba00e4e7448d50f392dcdfb289b4b966881b4db511c881bff33e80b2a4d5ecef3407dbc886eb4d6a9fdcf9bc04c42758c88edd6886f2481a6530cb270787b8b20a491483a2fc96e1953e2e254dd27c892bb32ca72021d9194c49c6fa618e3bc98ac4342d4edd4a2d3b9a328a6a1a54cf6a71aae73df6c41028dc3ae8595681d093e50f395718033d5985c320d27654023c3066cc18a7bce1213de0ce178566974b8b415dee974631a8c34cf9f7909e3ca427cfef28077b4ae9facff61026ee2929cfe17243497bb2f3f40bb0b3f30dfefc064123f7a118e66fb37fcfef89a11c543d216950547ac8b2cbeca79def0989bdd41cd4a1a5fcc7c378acbfa71c3bd40ac9fd9e42eb3092d6500ef63794910e9f8018545219e5766f6dec6926e647fc3b3c947712dd34504a95a69cd035ed0195e74feb2428e28c07a294522a71f28422cf773a060633c6488113279c7084a48288a93a43539e566a92ea4b511493932f96cc4878400b3424478c9824c961f60025494c3308a1e40149a03a147a3607bfdc558e4b4a4e782d2d51a2134ea5ec7ee3c5f599776f68e667e48cfc5519575a4e46e6391919ee7ee190b7ffc3657dcb71975e7ae9a5dc8febe387b688f8e3c231c65898091a6c25bbccf159cfd18ef51ce775ed7160bf22c929e57c6eca29e5949999796e662674654a714711417a1c788e52463e6c2e711f9783bd4d23ff79e43e51fce72c923f52d692fcc9bbc830eee332647feb0d61e9bc8aab57e6b8c8bd27399555197766d673fe0aba00c8d2822c71228e3f4e8b5f0c8ab97b80a4bfc45362a6a46c6aca5a6855c6ad1a90b28fa48f309e6864f23a80cd23da7d2865f3894c5a7bedfde849a4c8e3f208c71137743146fe75e6412ae54a2499342391ac375d262af5d1d7aa54ef17be72eb0b6d9e42ba1269db5e25b1f538f0fc23f2f8d323f789b2b5979e3295dda5cb29739f302acd22f70925929c9262e4530e27d13c92fdeb064c4d9eb237921e20b047de97b419fcc0b2a7c9ad1543a14940ac9227f6e13d34df061e73202986a43aef9da424e33a50dc6071c3e8247ffec445a450db42f2e9c4b097833b40f901f184b8501123424032d61d6b4e515f28c150c5dd9d521a630c928daf74f9caf366712b0e87b8f0063fb3e31fab14eee7f0a504be72f74b8544189d6adb10d50417471814700722bb39a7063a87ffeee3ca9f41087bfe3669ca91a949d2528e4c3248e56792c14bae3f9da4bcf1722703e2529ef89b4febcfa7923b32790cd8de4a0fa828e01404073fd672e22b7b7f54124d94a69c90bd8833b2632b59e847dbf445fbff8cecafe39fe3378cdb0f5edfc8c8b4bf2ed78d6e5b75291b241f5e3c76d92af5188a5290fd75bc658c42e215c52aa1d65aeb96335a485070220c192238b2811ba0c6a0a054c2962db63cbde045f17483122c6242d081394185e7cc781d95540ebfc722795b69ad7495040d546011a3c313a229b6b43013c5161c9a98f7755df7c6315a48b1f0c2d30c4e2c4df1658b628a3050ee65e264c3840b2624a0b92e262652324e8e4c4c96e43075d2ba59d58aeb58565e4111003916c522abba325f6c2266895c7e8b1cff00d18bfebb86a5e3a4322a6b9b71cd0a04fe903f351f7ffb2800426dc8c416a0ab4d8c4c36400ad16022644c7d2ec47577bfeebc8e2e57adcb39e9e0ae643890b96d0ab7a7cc8c4a2929a986a456cb1b627dcbe3c0b3ea672c919679e71ff88f17452872fc05b8064282282006a9b647723b9b4569b55adf2d3cbd157655dd6a48b547b2b328dbaccea2047902c2cd7433ded0f6331e079eebd7d86c1988535ea441d105397eacd66a5ddbac962405f84f276d76f65ed4b575c6acb596da95da14990fa88773b95c3536b55be5b420cad9500f4c1b1b9b9bd7ecc95ed4b5eee17f5f2f3abda8e633236a2de946fecc7cf8caf672f78b4232ab4c693fab85284ebfcc95f9eca521f18412391a2d9623b1fc6bb1d6760a0009285cbe2c51b3a20769c50f38d81e2b434c996925994c76245ec9f406a554504a63971fa464fa3f4801b104105053aabc10c409327d20415ec8f43f10022aceef07223b65afed0be48354cd0d510aa4207e90c2f85025d79ff186aa0f5b7c209190e75f6f68ced083095a35381080872ab2f42025d767d9d04981c20309b9bee70dd59a274c5290d0a8c9218a19b9d6e7bca16a8520aa5959e014254b14151130b02c8f284620c5084ac8f3b74b850b5b6badb56576108369075a9e4fef9c53096fce39e7bcb88acfa18f5cc15cebd10db91e89c9f51f9c814987292865e40a14047777a0687941060c0bbe9ed490d84cc9987df2850a355a9c6c9e4c417125d560c181862317682470410897a32892ccb658418896a3308a8e987020e30425990bd6056e8b5703926ddbb62a68b40aa3cbc311551551dc2b872232a5db539ae569cb52dbb2406d59a2b62c531edbc026b668a7bd82a6ea525d2f2b64326673f86205da258a902e53b61cca6032a5a02205d385b26426048b1294274a8438d5a008a144c8144d509a8e087932fa9e90b4525a2d78e1d21c979a5ce1056dc9c9931811b7cb71c9490f3b29383102cc618b135c1a26a42d3a90f0e1dae4b8e4c48a0b922c39215a7232c61126883c168fd825b7c9eb89c72213513c167308c68e80628cb991e3120d496a947d4efeb42f3fa39634c924c6ecab24cdeca2ea644e30fb8b946c5f1eb98c1283aa945f3b7ded25f2d8a75fd71cac12833afbdbb73548a61dd94a22b2f5215b1db27d570e97ba8e6e2869921655ef82726592fb6c336f5f1a49c9e1502a65fb5151dcaa5379181e1585fa03e2ceef1cca999cd99ff6673e6924690edadfbedc9837498b3cf6372eaea3107d5648a28f6a87f6993cb40fd744f4a150449feeedbb0939b81231a8b3fd767ab7eff5972cf1a7919a3a83bed26664db28c8b6bfc816894bb6d6beffd05092c49f0cb808da3e12d97eec22b2d521cfc0c47ef55c4f6e3f573d06d477e170ade522875deba4b559becc7be00f0b222303f3e02dcd5f07ed47a1d7a5c130efa310cd47a10bf360e6657e06c33c0ca5e6a390eb6530ac6228331f856476d4a18a4130dbaff97a64ccbeeb039231fb349f53b5efbd878138686393ccf7818cd9bfd5daaf6f0107ed4365fb8d74a53390b3ee61df43ba32abb556ce3bdac9aea37226674d29fdd6b4b3283db3cf51caf2b2773e27d79a3d25f79cf4eaacd6fca6b56fbf8f74068dd43da2676d876d1711b4bffa7a8b085a29eb1ef66d7b1163f64390eddb318c5a397497451e6b27b6ff61b92b4b439a6f71c8765af943b61c9e13e987a7dd502737533fe9eeee4e8105df9698d88beb3fbb890d377a584a13f7799913dc59420e6bf2fceb8a41dbcfe96097a6fe58002e72ecc902a4e2b1bc96c8516210e33fd83f7d45c7fc6b5051397ed857f48f3ba970c8c3e250878d1cf65383919d8bee6de4d067339f55e069979b504ca7f9e43ee184623a4d2db3cb84c2a7c824242f90b4f09991cf7cd61598451e7f3148536492ac85f19f98524c420b2d4d579cb2cba7ecd24b76da4e336fba22bbf8ac27a7450e25174dd28718f39759f4500162f9255e55b23b11f24be4e95a3fe1d87a67350e7f83fe69619914f2c861010ac0fa2ede4b4b77e3150ed491436fca8e7978412412f672c3396b2d1e94a0d2c4b484d64158ac8779e081f71b863540f5dcdb8779c0c1a4c530676158c51e744f571836b1072a98c450ba0dc31c43517d14da3ebef7592b1de3eb25f8515122bb0612c8c106fa2181787ceaea73082eea7034a600f7f11d3948ac9229b5d4e577c45de51824a3d71e02bc2c710da4d537fd0f885bbf33dd24f716e3d55bfc41883807e3aa704ffe0fe346196b9a8392ca98162eead4af8fe867420bb551c4c07f03e346198b42dcd32e6da3c8e3bf857139b74fffe949e23e52616b44b123893cfed1480a8beb53fe3886dbd88bdb180a7dbd7c4274dd1f470b37eca7a7277f9232e9754a8c31c87c70c782e848efc84d73374d36b1ed3845e48686eb665cffb069e0911b85a2500824611207110a6b8ac8589b715be68ec3042476c05e2491d2332497888346dce80de1143a02f57b9c6d447051e795dc28e3d634e36f3fed4f4e2623e2e07cee05c38d1edee186718a88aa7fc619e777b9b218b42306adf2acfba99247befba28cad6cb2dcf856c61f1b0011f20108ee038bf88bdb0f10199b1f8292a88b7e7090fac89c54c62ed9670e88423f646cc65509d1845b69fd721c9c8d03c98d320b064ce2da1c999e3ce5f056c182e40913201597e6c8f424891a25d01daed3ea3ae5d65deb477fabdfbd75779d34470259b9d19c6a5f39decf6ce395e3edd0696d9ea55c51fadaaebd76a3af1c6f874e6b524bbb4b819b52caa77f03f7e8f89c73ce493ff6f4a89aa136ede89cb3937246c5182395b56ba594527a2da5946e1b1254c61869ddac8a524a6b8c91d6788c2f393b2be5fcea54553a372e0a183060c0cc31130c183060c08099748699632818304761cee0a24ed8b5235b172d99cd365b8ba88848a9e46aca71f2f1a4211670d07f840c38e81fb59a12e03e90dd1054d77943abef9e257b8040ff97f781fe4a5468f2a7993ce6b6afb41114775292060e42791deba35252126773582a57917163ed86317078c891c9e315a6274732b54adc1085c6ebd4c2930d275ca61b54d0a5eaac40083a850d4972e4fa53e74a0d5276e4fa968a5ceb2da2883bbf062436d01ca851134b84bcd06484059329a3291784b880549bd87ccc923282b833487408716768b8e0890f31302e54d9e1b6725c72018a1d2ff7e6b8e4c21535725c9aa184292a318733c69c79811873ea2f7d6e34b640e44972fb093339790f3d8b41396250cc114714b3f944b325f46d8c71c39ecd9ad6b2af8b965c99d0d3f7f1f229a5b321b87e1a36b636b926bb66b24cbe99db3af9b532cbcb5de6b84e06a5d8a8248df745392ad19400000001004315000028100c088402814828cd835db10f14800c7292427c5c369708b324c661144286180288018400038801305354e3007682e79eee7040c7e23542471cab3e8a5010746afc592d0001442be9be613fda755f3bff06fc67ae8bc9ca997de96f1d991bfee11f78851670b5b4b95668efef23aa28d84cff32f9e73389ff847f590a00db04c296af65c491abe3239626006d70d79e1b50b2a8e531e915c542949b08c52a423b61c2fda524c05831051a9755895e1a838004331485d7366eea40ea1053646b76a6b3398ffa7bdf548259fbb75d0a2641fe4b53720ccb00a8e2a12e7265874ac9fe1c9598a0014341e72b10ad74c411e5592742c7d0d6560d6e2bbf532d6e3b93e5cf47dc076b5e091e66f6ebc986754743fd6af4aef6ee3a306529b3e65cb17656e46bdba337a7172f212c7dc14a71782b98036879eeceedf35b87e932858369f19ae72d42f10e70a33e4b73084ebd9d2541774ed8808b5d2434c37bda30b660efdfadf39df20e4b9134759d5195464f7fb9e90642fa50bcfeadd576406b94d4941d98490e3b8a18b89e601e2ed493990b7f6bffedf0b768360bc28de26d84ef4c2c1b9172118272f62bf53f842898cdd30bf1538cacebbc17da9c752b2af19a22e18b48ae093c0f38d82df097090c7ee2c06ebb421126d1c4266c9b584e3c7f902d10fe7e96eaa9665ede7e0556184ef9038e40695d5a9bee647059f5c304ffb41a0a73d6cc0dab4cf8e8360a629cfb0b172ee198e0fd13b5ec739f7986cba0f06980554f3cc2a3e4ce8d29a624ccef6d7e09c0c5ebbbe055339800b6ce564edabd1fe63ccffb6a773d2d6812470681e7aa4a32e34cbc41a0e63582ace32718f8bf1771e47685e7db024b98c5a8beaf54ac4ac370b5782cf42ce8c0c190ae653f34ad15f5188a6168b383498461a09652f82a8a1aa1f530afbd18776ff8850cc83b11315bd5bb196e62e3475875ea6f3047d5822bc946de632dd8cd6fca0e287071e47bd136ad41b10b8496a6ae3876c5d8e381bd6dbb1a94ced56d323cf21353bf045b32e525b0e326964b11344d8aeaf6cd8ef23836171e31b7f7181a9ba0ec6525215513c42d561f3f0827d887c190d0652c7d110d751f2ed6c67f011590d1e39c94531a937ed345eba0b992ce21a48d4ebb9d0de364478d859e0dbda75473f819729f61af4905bd86cabb1f27206f3be97e06d9d1a73187a168b7bd811eadd8953b27b00c5eaee05d885db156a8ef6c4a801525026cbac229c472aef2aaafce5ff7a8e79abd1b6ac827e7e458f481e0b791cdb5c7f5f950f5398db7a4ac5ee6ecd3bf9a2e4992188fc8ba142ef93b5a0a7c7e357dea81219fc92fad06e6605d5d4d80d86ad2b506e218a887b4fa9197525f82253494a9a1008b27403dd97f83a2a960c9c57308bcec5825f0250c6b865ee6cc2648aa84018432a72efcd72da302166301ea4f56e9f2fc80f0006b6507f724242c13f630a8cb1277843665c572e9161bda2a4a65e24c5e3a7ad532b5a171a3e3d87cbe23531d81d99a4173d6b28ff6c42bf6eac3fb72bb9e3f0af4d2a42c1e84a9a0d1d271a7a0a2820c7211ea675bb18b356681e60a28be3fcb0dd198b81c2c7e2e1a9237deb891f2eedf971cade6de3593c824b140b528b206f9efcba4738f2be492128ee5401d147f2401103782899c198323182f9deffd9181ebed7962c5bd5e3f9f34fbf6646fcde6b006d3cb342c8618f122078e02c77d27119ff066b4918c31fd57e2130e7400fee29e9010d144ddb296c9d0665c9e3de67ccd0f513362b26ab5abbdcfc31ce12a2a91e98b3314671fc95f9df63b3690ea1759b18b9beb2b84fac899206d6a80ff66d73863c79ea7f39021fc9c8496fc360d121079a2fedbe07955b2751aa6e1516b55bd982b283d37eb7f959c43c9ce3d7b94f494eec30ab16d60d0efbc2cffa157921afe127cfe10a146fa8484968cfa0eefe665107151aad7b6bff957f9f0ec3779c898698d4fca929b73074ea4faf5c8a6a7171bd3263cf97a169e1908adeac7b3d95cfe1802fa74270a3034694de89423dc548ff858e8f22388b915badc1b04d8457c32b64b9051960a5d4ad684b31c2d8543afa5672e85b633537b399a97485d4468f74985ad28b5b624dbe2c011bc1599cb1b98061bd21eec7b63b66924c3254f187d00232fafeee16f3ca502a79ae6c4fbb6e25592463f7ee7eb145dbaa4d7d32ebe4b4548db2520e3682c79a7ee34470ecbdfb4b6755443935f664bb4907bf10c5275e485cd70559b01953e147c477f91bd24db739898e47a03d6e9afa1cd9355437f3a3d2b083c3a44addb3f8613544a052804c2e75674fdbcf836c4d3755fcedc7f2488e7a9e99fe1cc82de48ad6ac17a0a9a2ad38b03d2c57bceb38484b1678aa278d574415bd3e6d355d12f02dca12677f77775506d0f844e3b3a0f4c3cce3ad081b09c3e394db22955bbe392310cc33bc5e76903c2c46541748969f6655e1986febd04cf474b1ea758dec9030fcecc1dc1a3e4d967545d7a00ef220e1a127b326d585dba2f30da5da00b4181abc7c1e3f67a9a30b085434fcca8d848267ba21a7b1a5b7634663975ce777d0892c1cd96672a6b1a646c7ab01d863c832c9b47fc7478a0b3f07bd39e11d8da08cbb2787c4e2e6f903d2721e5ef59a2811fa7065e19d64e69e1f9a1440147a8ee0650083dc729a4b0d3609e47f7f829fa1a9b59b9b06fb672f996d43780981c3e3e300d247841f76494956d40f9529c791dfac074537bf78efd8e017a8e05b986019ea70b521a24024e68b525b16986d6d2f8379c909608178633e390fea3617618e8ce5b833088c111f6d03156e9f1f4d3059f58598669a8eb0aa278eb07425e9348a17472bbe5c27f5a8051a853dc0ccfd5c3f685f13ccd4c7efd7c0d1bd3f30cfcd2c234fc5f4a29d24b5f0de189f6d67d5a7f0a77c48e1b9ca785d1c02c3ac557cd2538f11ac84f54fdc5c9644c2dd0dcec2dd598292c602d2549c45282c581096f4b4a00d08cdf503b70b33e37b54fbdaf4b987b72da3501da27b5656979f7f0420c11b354d47d34f86cb3aebf5aa992819b0fae6e8298e599d5534b776bb1fb69b56639ddd2f2aefee859ef4c5b631a772e7e416d168dd080816f4fd409a194df7faed7765a51b58218501608f5029f9714501e090a281889343c139186a783736bc48c699aa58307a278acbb86e5b1f66a05c6510922d111a0f480a0693a2251a2d0b98b75edf55113c98962c939d21cd9dd11df61ad460643519705930224aa3114acfee91f2d9232e9c8889f12ac2aee97fe262a7299b7c18236e7650740e0ae8889418646be6239c3650c8de14c6d503763dfffb5a45c0222af22c67151eb9627e2686df42edb06e6ed1ccd30c81ab17f10b428b7dc6eb066b5548744dbc33f10c037b9857b166eee6ed0cd9098c744cbb8398241b38253d37584776b7a5f479db366551e4b586f720b7d00ead598bb921c4548f49d9368273500ad01c914a8456dd005a4346cdb2057ce80cec123fbe5bb3d48520c69b367b8e6d864ab81193fa1555ccc08780686a0370be40cf22acc428360e24f018b180e2a27e9598631272b0bff68a6b02531fb6da3a61530c5aa804575c7573c8709557022f7456c2b33629a26c076c98a98a8452242558c13416ad78b4adb2bd2b972a4d3358abf44f088dbf9111c7ec6fc9212c729bc88767724d99ccae0891bde99eae80c2589ee2b3a033d301d63b22c5e209172ea6b02edfd11af599309227ee974f38d9b77ce70a4bd642ef160ffd76e853d9c1fc7a27858ccacb653af36aa478d9a6f9cbf904d437de6771b70b761da17701c138f5f1a1d2dd0930c9e5c7a4e0356f1bd0e429b2144dcc345511955bb471514771f22a53bb5ca1a178b9229cec2760c5cfe549c60fc54b0ddb8f484553cf035eafe0c508b0837671ee314c11f1dbef267fe402024b97b32b40b954080a3e7323f2eb8e58cb71652bab0c1b246bb912190ee56422e3831b6f3a6698d4051b311192485e565aa53f2eb292848669909823d1852be4142869a19752359346daf96e7e49f85f2e152cadd4259339af1658d6e457bf0fd7cced16c8e686434a2fc5c0f89c62d589da28a96b452660a78d87aa8711faae79d2732592b2bd0b3a560cad54c9a9b086b9046972729f17d53d04f9d32a9fbe5d9d0d908c1782bba71f3f0f9749abda0edb1edce33b191de79aeeeb9ab4f6b7c7dbbf1932d9ccca879c11ede04d96dfcc4994ac73d4d8f5e5701ed1038133feef02f4c01a2c8d3d5b4e767a9b512c33c16dd7dd359f04802fa2e9903f2486ce933a53a9b84348997508e10fa63c50dc57d0accadc6004dd37791cffe308d2ec05ce42c60dc60b4484ece62c7a24c495346cc8ddd71984877f02bf4c338d4ef0a2d4ce2746bfc5a212aea38545c1937008cd03e20b4e81887a20b2d0a6a8a96870bdef54f5e943587d33c9eeca1511962ec300aabdd9167a2c59048797045a4b002a626c624e4da50a2635de6b619b2af32e3e0351635b254d4870607f183290cecd3271a8835433d7d10d129b194928d6a5c06bd2e94e589e4ca8ae13c03d9183a63394e3cb220787b8289aad9c6236d35b496023a27e85ed60a14c75328b3ddb69c7cd0ee4c4def6f1b7289089795a029bafe62f7ce585bd3cd1814cfd090cb0c560f7d370d91184594cd06fbea69afccbf1f1c20c31e15c1783c4b3c144da257695487eb793d0041d88b8f60186bfc9b607f2a2446999673216bd2cc7060ac49b079ca05bbe83f83a41dce6fce9afc7b24af4d55481f59de189fff0535159aad329a44a05626ebdbf1d94d18b2657037dec74412c9c000fa5ade20e3e01f49376b6fda09b8b5de67fdf22bc3e040e1efca4a25ebf80f3d8e03da89ea107709c74a49082e39f7944784c2a29479a83c742be426d4c95779174ed814c5b3a66b151ae1a52ddb3355aa2a22cdbff22ae7695c30090c2ffb9b46e33a0b1bd500d69169831b46cb5b9673d57c01c481fef575d16dd22ff5b558de252620cd732e68421f18fbd2c2e9a3624a981e56fb684f0e296392b0ef94ef77a13cc5b4a2eac92d805d51f3011019024b492496b40ea0a2a8b318473fb9a6221970200b4311c39cdffe6832770f85569c58da0be34673e2388156f074aa42edd75f3e468273306993682db5767d3711a0d14ba75655a4c64b9061bb831b6e5c2d0c58872fdab87baed90ef512c184250ebafeae305c60f020fb802c6a169db66651ab9d677a2c971c9b71531809c1c1d0bb6b59a74c33f30e2b32200b2328513dba98e30a73f4d20c3c18193dc02cb46e52684d3f209ae0d801de4b70167bc98f5ddf0885aa2e28ab142cfbc01c99be142caf3b3bd8e8b5c202df8b1ed4fe24aeaefd6b0420ce74816c33135f48233dc4a011ab8dc4cc13fdf762bbedf24257875e2e2d15818a38cb6617283417a8aa6c6a6c36642298cc7f1a17b65ce4902eb64bf247cf84abe02942ec4cbeed68e0b9cdecb995a32ec72683c4029fe4b2d82e946d458721ffa229c887196ddddcce7e20f42f24fb7f012c40c24526581348d93aa66c09d96d1e908df8643270af9e9fd8fd613e256b8b7ff340eef5bdc6597eeaf034621d3137ce4684aeb817bc76a051dcebf2691a296732082ccb6120336e69948ee703f4c50062ec8392a5adb464c004ec477fb56f457a5ae661068608d04ecbb556b577da687a7e67c73627c4c47aae74210fea689cbca5da2f031b5e06d625c1430ac87df6533504dc9f0c5370b383172d82d93511b9d759c7bf935d5230c213962a52a49bcf748044e59be91433e2ee556ff6685ab8fe3b938be2d57db7285684e1e0eae17fb4a2d6ad272da5b729a7c2517ab1e0f550c977ae955c16cb20bbbbbc5a26a2798f5e302ead4183265370dfbeb229a4ecf2398fcdac89e9634f4c60978681e5712a064bb8e2b07a43ccacf7ee08b4af294d8f986bcb304ff82a3204a0a487b72a8f45bd8aba3f8a2333137420ecc71c2d1692e24dee4c63ffef09b67e1dfbcb222422bebb4e1009cb8e2d1463a353529bcdd97bcebd6da2f11c86089e3f20c4a63341ce245ce4ab3de841b081865ded615e8354b45a77bbd8704905a06d50c1c17bf0bb072fa79fa07617bdb52f5d68ae777e033d8ae8be6829de3a20183ee72485f73b921cd56b5a6a3ba036a1e1c4c89355856c4d4b69133ada1997ee7915b0e6e73677a1b48bbefb015595566054de1ea727e2d6513f74c7d52107fd901691a4019422bf1809938bfcf6ec19ab951431acb030ab3dc52a071498643d027a1d41adfdb31dec49acefd200401d66f30435fd0a757f15e7340f89aeee55f573e065bbffc9b3d275ee4ed7862f610d47edc0997caa88ef533a590fc52f082a472957c27cdc09651811966a5ae676a1250a11f912080ede317449c7c84ddfe6659d5d5933c22814764fce43e42cae02fdf3340f4d0a601843d8cae7219a9acf4314d4a5cb93c2eddba4f9bbc9bbcc7effb373a88f34a5bf444435f11eb76f3934a4f404add9282378e3dc15b06c008f766fed68d1e15bcb27a976bd9fa54ea0201d986bb5317f40c31cade6f4b4c203aa967d2871214ee5765e88c8b4b6401faba2b4b2302c0e529385d90b4430c657b15531b57f4c8e3373b01c225065c016998af985014e5c5e8fe99893b300b96ed219b6b5468e4ef9a1f0b960456e299e138695be55514f1e6ae3bc8ef5f525b9884aed0674da427341e50163e1da6e72a2a2674d8678cd01634473defc80520b38ba14dca438d0617ae9642afba9ad3834305f7fd74cacc9ebafac2f4c74d3bd68ee6e827179f52d9725e6599d2f0d79b5668c53cc76a502e1223c729f20893b2cbc85ed5c02ace565c9c6e2d7161e9c96a6eb1ecdb01c9f549f260cd4884599906444059193ea5b27495be531a64c60a375d1caf782b9c6e87deee7a2be5754434dfe11dd10ad41374608285e23ed834a44e2442f4e804e885b556e7a41a3684600e6bd80bc9a21aba2f51024dd1f6491dde1fa6f198ccd8f5ce8fc56d8af9c6fc798adcb52e96572e90a051e1bf94dd36e4bf40d851920eb87be3ed26ac19d32effd0d51947f1956cbfeb13b0fea0e74630915d1f79bb786363b3637f9f3709f894cc5513391db01cce56e827f34738c26fbb06043b3cfc08152ba04e20bc4bafcb94bcc791e7560f054820f3226c58715957015472b5aeaa6bd0fe0a0be6b328e14e95a1ec28928d1d3dea3d85b07d9f2a685333acfd0f4e4f9800a911d2b83accca03ee80bade8909cafa71bc4a738b05d56839f7681b365070707c19a06900a71851ab10e5454f3ca17c793b41cb38e1a70bd96a2c38d9d84c3172032436bf3e81030d26d35c2b1de54350d7f18f24fc4c70898a8ffebb14d378a256defdfec1fbfba3e5bc54472d344fc1ef7b392fda337fb208aa4acba81da0b9e03f9d71871cc1142e40f09f57a2afe442b077d05dd98921afa5289630a4f699afb81ac776a99dcdbb919728241c8b28701b5eddb2aef8e245b88407bea83b0ea893442c61370021a03e5048ea818f2e90f81ed46c7cd0e25827071b00f4a01223fa40171e1642ac94c184e212486ce299b2988515b187ca3ad1c9f45d85bea3fd4705443952de219720fac7e540a77ca02d466980e21dd140d2927d38979029180f10de1c8fed5d368ccc6f401978475de5e12091c62d0149f64c615150fd58f30407cc2bb3923d8da06da6a8b5f52d1529eb9929b32843ea11c5c8baeec819c8afc5848763657f84f6dfa63fbf050b3fd82485e49c30e29d917c29f5946fff9e6b80897fce88e0688ca7e2b878ca1e44fb4b4905502d0458041ce4cd42f948099266302ece1ed26d686d49881806021ce9e148021c6b21d0293424136d047df2ab35930927f1f41c73f42e9bea61765b54af114dde4b3e26234ebf42bd1108696765729a8927443bb53311537c043102fa466c854a4a4955ae5800c7d479abab134b365e59e9c72e83c8c8ab35a728248464794c4858ad14339ce8d4cc57b142dda3497775dca2c1ba3412840d623bf28f6448b4c2ea5d1f283948cb54924b2d423df8761c4f5782c3b12487582925c3a10153ea7bd91708e9196f57115ee4bb4f19708bd23d391a472b981285e486766209210e2c518ca341884bc22cc84267e004cdfe15cac0d7a1062ced52d1d44e1117ca4230a6fb57bc33300f36e9906b476c724b6c13c72ffaa5528c0b5b45764c747b46715688ca847a7153213de367f2b0c7eba016e299455b69d31e82bade1a677fbc3b702fae29a5689e2c90351cd1ce808b12257a0a81e02d7aa7e65f88953bf44ab5bdb6dff66251830ca3f25bf540dbac8dc7b78cdd7e29378071b040de96448ca954ebfe545842dc40a712887f35eb0cd32fdb6489e6cfe715c0e903e8b212b7ef048abd9d66053fe02d60f5ae16d1233a2cd5519050a92992ce98aefb72c163e35ba664a259f6c17ff66fe19891cfcf0254ce9853a8f6884c068031b553d9eb990ec723dc06ceb9419689a14aef187d74ef71675614fb9d753af8feec9d97c89abab3ef8f450f55e13ac0e480edd2f0fc0e53e256809d011cf6b854a84eed9c432a33f348725593419df15eb381a2e730121a881abf7b29e600d6419a008340565bd7c866e6e10a1ccb6a9049ee9a94d1fa35c5c19720b6e25f54c61821879078a03ceeaedc6362bcd0898557b693f587a70971581fd0518dd6a83d2bf9cf1f05f2b30a5f0ba290f83849587a3b795c2d1e350b25e273b5953d8ae5c8858743b5a09d0a2e9d5f772ec8f1226703dd47ef160622d3fd2b5787f17963393184a243ee6fe0861e8d48cd1f97505d6701512ef41da07bb277bf79bb973027fefc9c8a34ce27aa50a99e207e54bb40970cf63c88c8104068a675d515910d24b777619dac0b09b022b7f8e6caf8ef2c2d7c91f476f89de58c90d4c611143a7f772ca2955eb543ff0d7ef5bec1d40cd2b6d4ef97926ae1b98d709ae9a819389e26445727323fc7664bdb1c8f5bad2ef90f1dc82ce6a4682b7a9effaf169adbcc3db015b357fbfa8f9e773061c9d42eb22cd795192f4da985833de501e775f14cd58af70cd2a2c1e71849602cf265dc8a5c654411f73d1dce6997c1c6a02633edfb26da42c6a7006728ab1068f80141fa8d5f9630084026b2524186b34ccf9e3fea705dde4606a797e1c3435a1fbfe8764e4e5ee211e55cd368926eca0437fe0040fe3bf12861dc21bc736891b23112631099456512cba94706e56f50e175c1153d36ed93c85ce8ba194161efb5f9269bdf86ffa35e626f65655e0e1bbd9becbf1853d6bed5685f3fe1b2a08c3be820f2853b5c9e83c66f28530d135641343e3b06498fde7e68de70837ca49b801d8a91e9c8164e23f964979e99766431b86ab2fb4785dac439876afc34491ae029c7548401ee818d7bdda9e00a795c303b679c9ff49ea7d00f5587c8faa59e6744926d643ad35d84e2f8a67ade9c476dcc8c19d56ad6ed1456ae4f0284815427c49f2277f59fb1faa29c6500f1baeefe9cd0df345e1a66bacd6adfec55cf2a2d2383816b48b6efa53ba673f121e915511ebf3ce246deea71578581a904a5ebe1692a8b71ea8fd9635ac82084625148c9fe6698191dd9556b745f8214b257c41a3962df35058ee57b6dac70ad887e76b6dbb35515cdb62c320d3b8f284cf94ba2e53c8f8a6cb4ce4a49b607125112ae54105d918f8b80670e8ac050a41562c3c73436c8a928bba6694c99dd5d1bad666879203eaadd31cb33292999a40d16d26befc6fdb6bc931622ea8fa0333843a397118a10892309441a83b82f84ae0fca6987d56972dd553229fe2ae1e48a80b6370d030bb131fbdfdc1f63437bca62fd35c4790ed5ddf0f1b3079231a9926c135072c58740cefedf2ad536922e3012d7482fc2ed5ae6a401c11aebcb0dd28a0149095ca1f27a8c13722c782698351ae6f7819686799733555a5b22029891d732a365be636474ae59cd31dad8c2972e42c6ab1a04b0a43a9bdcf1e6f1edc8420f10b7c2b189e148c60f05ca77d8ab184e0ae977fc8ebca2c4ccd8d7d03e8f46a4f7797ade07877c1459732a0898f62f1dc0476fbbe4421c6e7dafd02c2b577a4753979b583072ac9739b79ee952aaa965e31bc34a7ce2441f7605457739c0ecf54e8cb11c9604679b01faa0191869a98bd942d11ab993285655dbe54e2e920ed5038c202d18cb7a9baba3dbe66e2dd025ec5685e2738e3c10001ee0f0f548a71e4b47890280b675a028de6f66a8082cdf4a539cbdcf2959c1a2dc148efd55218fce24d410d401b64e5a43a2bb4bb8876c75828931a1b86da756dc2ecae9c4c4d22396129a76274c92f0df2e88753bef5b4ab8fa9a8ced53e2352c16b9b005ee3b5dc14855520f2f59fe210e0b82351c0517c1d8d16927c681b2c7bd1d42a6962093373ae9294fb7cf176cc3eefca7eb2ada239f4609bbae5bbab837ef2156d40c830203efcd2f0e289aab0a138ff68540dc0712551149b4586f631ba07d6f85004ea84b28c1cda635af7c3b231733798faee680fb909cf1c6f9322da48d17072fe433b64c60b256950312dd51b932087bea40ff0ae20ea468cad25d79fc0ed37b3b9305f7864b111ba58cc4ff7d23fbab72170baa458590fde830231d98caf2be60abacfa9150243bb86c0db1504281c9fb9679f33eb505ed5204a9fb7e5c8b56b3dd3788d328e2a9d15b445f9ff60a133f4c6c319fc1dbf886d6c98a2097041870447cdd7738b7c81424106fa275153a68206ee7f21d92da4153cd83c944f827cb786538db0334224640a096050075a01044200eb135ea712472cf3fbf96a67d9180f6ef247f0f89939e90b159fe1f3786adfdd6aeca0e092c224b8006b17751584ee068b5f5695e2966b684aef5b11aeb8a8fd2044275161210d9acfc8998ee926e7ca16cf38eee05303f39a2bca2f8e081954032bb78bcc3ab956a5b2060648fff8b5177eabbb704f1b5555b5de8b80ab10947cf52c1909c7784b8239e60d25455f35de3fd4ad6f6614d6fad5e596c59cf48c98787491785c7725a7fae719da7e6bcf43499db4b14a3120bb794bd99f31404a139f93b8e9ffe6e401b7e200b3e4c9f6d32310473021aeb814e8916c3ada7a5e82ce39246b052eb9219e1ff4d5a00e37140b96dd9d18fde32874334ae6ce2a22db0e31318acf98f351ab5b0e4637282483576f3906ac7c90a40d2a7fac82d52e208be812eb4e05fbc939496122560a7be667e82763b7d60d6150c7f09e10c14ac9c62cf551c45ce322e62fade950371aef2f37cebcb8e1ff09d302d90e9b85ea974e892d929f383ade43a8d7738129fb5388e388ce8a429783d6602f1c1c6cdb7128cb14afaac30283d87f5745aae4a74e5a72feb93da962033d6f4f49400fbe0ecd5bb40adce3f8fe41abec029d5eedae924d0da2df33912e07e09f40d9f998872bb06deca410f84f025f99f02af712e86d3e5e800f7a80da30889e99d62f5887d0410931c807f70ae12790ff941721df1fc85f92a6917c4653c41dc00c7af993400209ecc4d0e8e3c3d743aa198220e45935903411f05e4a033024f88724ea8178fba0eda71999c2c00f2bf73c888875bf17f96c824604cc1688c602e13361145d9ddf11cd5f1edb9e71f5ee2def31510c1578593b75c86c6585b6ae3f2e89d822507a1f7af736133c8d1ff6c9f89d3a09cd2e1ad8dd53a91b9970430699a8da912a6efc3c19d82fa8091e28d703c37d8d2293a3d6baea2445212e8cb1dde0ca857e13257ea888071729b78a170b6b6a805335edc1fe09946f59fd029ef046003dcdf9b02b2272d82721cbecd40d0a8ac8ae0b4e9f4c71c63076a3faa191aafbf096b33ea760bc74d5d78f0826b7b03657aab5b67d9f2636ccfcfcea5d9fe9b312e155a8d0971a78288f78b6a0bbd1384f40366383733f59657cd3151592a4d7a6132cd704c3f025224170cb3172fd66f49efce417cac2ea4f2882d651d3824f9a0320b493df7b410a38ad2722032a9921d06c655cc0c2b5bc8a6476b27e132c237d22d5880275f6a48444be5584281c0d76afacbd2820a766209cdadbb523838535cc5f7f69be0f34e59ebc9bb6ef9327a24e79b60b0105382b5b7ba673ffe1251c2995cbef1ee3d6efe8251ca5e5deafb2c9df5904a401033dbd0f3fb4d09a24b9d5f28b29ae396b4fd7100b6eb4d50e62d1af29f018930e0f5809082b0abd17f1eb8baaa0211f2bbf6cf1e2a51528b71bcac670d6df28e3618c8419dc63d4d5bda899e4bd4c004f13e7bc6f6b88e287de654a32417a8e273a90c588404653687716d675302d3f58513fb6b293e9a9d0c6c868a99a129e3627bcf25cb531d7a79d5aab4413871c8a9ea543d5bd16dd3b811b6886772a65c3bebc39d4e59378f1d63a2f67d48c6dea1f6cfa455dd8fbc38e939e2406d4e7130095f732b307d98aa190a4a32a92510592198e817211b43bf640b8b795b262a9a6274227d840f7605b33181316faf3b49fab71ea06b846f07f4a95baa2ea6e24a28c514dfa730b88da2c698641005672ee338672213a88f17441710a9858c9c64d6c7755689596291866eaf2d7c09c76433cadc46400fc522429928a004ad3f748042adfd9513c207c0208048295808ce7992216693e00b57b8c4bd24614389fb2c86feeb60dff532bfdedb74da93f268e4dbffbe3c0db41aa8ac39399b7d3b0b60d8879bebff4edf4cbc52e84ff592521a3dfc3b01e98d42e1cb57d1fd6de43e9f77534d6b2922a8ca937a0a6bcfa4c70be0000da13c8dabd6777bcdc073e0e08b95f3a3efec6cef025145dd1fa7493930c8561e121457a653ff46c225f1a56edc767b0e61e236c3529f19beba65e2362ccd2715117aab698829e1af7ca222bc91bc101aa9b0a5012ad6c9d4a35dbb130eeadaab51978c5cd5b04f0a4494fc432b1e485fe3c4b8c0d42060d5018f79b3642e4bb017fa604a00a25016e62f72ad335a8ca08516f516201b7d116571092a6413bfd03e3b1714d83eb6f2aceff06eea44745634c4d44826fbef2f7f372da0ee6bbc34cea1033ff82d7fa2a831f0067ed02f0f1a0d79cb26b4d0521da7259f234b25b8b5532c12659de38f719861703dc9b4cd3083324e0478259e42483a61d6a44e84fc3285bfbeb65c2c8e4960dd0feb02655f7418d81ccc99288abf0486318c8fa04c0d82cdb46adc20600e841e4168baa44f5bdfe615c66395e141f76d45e31599ccd9189afb234564a1a787c5e1395ec8cdf4a3a863cd134f55e49409e6f4446d3c18058fafd1295b2e19d0666b4fa2a7268932d307ef6a9cc86e1ad21da32942b1593c2c3462f9d1ffb36b2d88683ba6d7bc918b34cd5f1d412cddcc2f8228b34e1d96933c6c39bea970472655c53f977348e1412f7d0e124b478cecbd58f4a5063ce2059e9536ea47b0e43172c2a7c76a8e7bfb3d84327cd7ca671c957cb5c8b8d14530791061faa95277e8410eb3bcbc6f38daf0d97399e6467bddaa5c55dbd309a32e6fce3ad2e8940f2360dfc8f77b194d2381e55a9a4823a2ea3238041a4238e8bba966123f548a933c8501b10bc6d8639be1053a756c20e46310cbd5bb056ac50edf1ec389f8ec1c041074590c964a2122ade63a3c7857efae06971a23a26a0b18a4cd9c2e94cb146434a928b73a7aa6f71001ae4f61d0884ab43299117bb1626ba9b503f81a302380eb1c9275de3002749e05f112d29f5d476e25b9f97b905787cb7c70ea14f39909099a739c17f7b532216e5dda5a7156e2d907722b66c54ab23a69e0506039a33a18a3abaf859e19e1609d7b3115a4a4bcb6e690e459150b416a587202d30bb5395677059bfda741626babedf072474110c7b8a843a17a216ac57a9925142b179f89e537d0cd57f3739f9ffd50a6f4c02ac5d8443fddc2aaf53bd9f0fdb018c53db11a97405ea87b44b59c85014fdf0a398caf91c670cdb0805bcdf1e741ec4d36ab77e48531bd6afffbb96e6218f79d358a84e313c7bf4805660c38f76e0aeb106fee5197976f2f9cb0e6dc90e1c35fd09f9c68e5f8e3f45d8115ec6f8fff05f772c8dbb5c0d44f4d90ce03336d579bd68d22101ef72a9805e9c7b152bfaac0ea15c8f3ac21b1420c820d9dd580163fdd743b0ebe9a26bd0d6d2197296225e7b18f3e55d8d26538c5728edc98dd85567797aef9298e00f13c8e9bb80a2d956c944b42de90731553906f8f41be77af8ad36e7cc4a58f165b262e9b30466e1f817175d3a1d9dfc36ff68a0da3121ebb820ef72888c0331d0f06b1326d76979d477ad0438ee97710cc1c86a03a21ec778c4bb7b18fa9e7c854df32ed9658c0734da9507b8e628f4f3aed61101d928b65bdcc84cd9c70ed896190f58bfb1be0d489a4b52dc003987a0b4689642758041032aa394fa7b8e04b3011a01c11d020944b2cdab16776f0de19e3fe91e0df5af3b067f760e97245383f4d217e44a815cac4131dc5fedd83e89302d9d38c2997ed03f868d997f3a0083ba568c8ec8cd91082f34175f74c9c9b37df2627262079eed831048b345613f15c2ba1b20bbe40549874805d1635716205574248f2001195a9bd87dde611de01cccf96932a3f51475930903145ed5a5c481416fce8ae0eeda25e966034254c8624db7a5376772e735476cbb0ae36264e044f4ff53f3870f21a7199cd1764918a0c628b054e915e41fdf34d9aec399197827917b2bba39c0471ee85ad4c56d1aad9f2aabf35eecf0240817f197d0ca5844acee815619c6afc4df5c393e8c306f5f26aad7528f80321577e391950e62d8679be6e43120e186df31a8091719dd077c32d82a2f7a90a2ee14eeacd131aa6dec47637b1ea59f52ec76e63a9cc1200f666325d107dcb41239f72f0ced12982d2b6cea4f16bb6f4fbd6aed01f661c877a80610ee1007f5e1cbc83c9507725a9068bd3f8136bfaef15da0f27966aa17cf9afe610771f678c5f70f79d0bb5bfb4b693b9ffd0928c0297ed92f60f06563a4fe924757622554f32d673493135cf3a9ca668e7ec46e23ab4b01735857d18a6bf06981958d7ce7829fe0c12c34c646732fa1ce96141a65e5b99ec6ac4b8c131eb053b459b91ab9b09cd1a223bd789ae6ed3e1cf17e0b2c76b90d34f1cbcd5a002d8354ef18c01cc9dec8dce67e989119841dc514f427ec2d505453fe1d4a932f46bb1f8e7d9a65cb9fecc307362a5d7eafc3e4605e7b2364673ba95043a78cce173ac38c710b2620e06c5370120c15869299bc88b48eefe98278a83d35c46168fbdea497c34c613b31d4e9cdedd4628c4639bf2526fe1121a1277b03dff90605746b735eaeaef73fe08ef8e7e333bdc8e9c00c245840db48aa3beadf5538b3ae43f098870d05125cea03bd43f6cd4c055eb8547bc74ca32d6378bd0af58a846ac0965870a49fc74a7ca9f4369cfad9c21870220d76c42fd4eab9ac029dd336c0b6afec4f3a48c58551d04c6d15b9bf18fb9c2c2890ada1f046b88fec1391e17c60ff0bb9bedc271d6fac69610f17e8ea37fe74a176c82702f4cbb1c1ed44071421a6e296a1945363ceff1ebdb3943e72452c35ccf7765f378c4103cca698204c23884cb6048f4576c7cfd9de75667441d29740018de41d9e68004915b9a715dcb142480469f44b298bcad5d7f50a110472c98efa412de53ca818e04066d57415b8ca81013cdb59a006c157ea511efbdfcdc58d86cd3b2d4b96b209a53591cc17e3cb00762d5f62255618be056110092945a09310fe890d789b2c106b8b6c8abd6b54066246732f7e5575628795bf7d4e503358f93505bf1c30f136a43740e99322ce49bf9fd6ab4181a0f1fbb2755d8d993d3827107d6d6f8cdfd7691de9d8ee7f96212cc5df2af3bf437a2663719d788c39bb36a1d9a59a7b5d7ed4428581c248863bc71c33f3f8a9064a504f41af47904373c583e04b971b4996907e236289a21265b375a329fd217223815c2182639a4c54c03f038f01f2cf302b8c1df57710c4e41e58a8dbc444ee2cdc05aa4c168ff056099f7d1a9626c762e9992b459eb28c3f7db9ef9d4b63160422801b711f0413e0bb555a08336aaccb2aeedd111bb913ca118319b0061ca2bde5d6bb509adcceeaf11d2ed69fc6874cf7fd4021d8d7c8d7da0c3db868499997379cb22c41b8a7a408e7e5c1c5450835fa9e10d3cfd2fc9740245f260d4a0d2bd85cf0292cc27e7fe4858cddebd82137957308175dc04811a349e7f85f2c1ca58dfb9e04cc843b66042e0b40443f01f505360e79d0c0d0d7493ce4b4e7c2e00192cfd717b0b39e5ac2e940012072d8772106cfa90d9bc180a1c6dbb3ee876a19952000e51dc6450c02caa5ca0a11a12014e4239947749683af4f55352056abb144395d9df4d8bf67d4526925451039bdda0d1c744da5e4575145989108407320efdfe34a1165aa3c4608c0bd1a333bc68ea071363b64f8e228a9734727b43eb87df8410c0c1b05ea3eaddbcd7f53bd179817454c7ad158667143be6f7c025410386475aa646816fe4d30e6510abe49c585a5e9fc61e6262a17ce2b153c192ed05ac637a34db89f52a7180f9e0022be561ff29bd901111b5f22a98fd0ffb698270bdc19718833fa847c615f253be815c2570c5c9f87fb061cc63cc50741a52160eb2f6154dd41a74658a480420a1ddcd214acfa5f54f2dc0f108c4753a11d7a9b8641e7c9fe3744ed3d183a6b888fda6417d1f27c53cac96ec2cbf62c6268b240234e4283f07323b1a7617f726151f2e16c668a78cfc22507b1a164f81e2b0746cdce2d0db3c63f04f82456189b9469d6768dd409c370526c6d3fbcaf1af7d8dc7675b6bd63e05444eea6d5e41628db2035349dc171e3af02be77f81a6822d85c29bd5dea00b6d9e45f2afcd18f0622fd179c0f05209da3435174bb789ccebe4a627f11a95692eda3d858310b7a036d4a0eef435c74a5c9fa344a9cb37983b97343b4d2f50a5a42bb88854055d14828fd99e672694f309aa168d63b7a1e5b8441d5f799ae4fafb4c7945d8cca24d40999fd8981889073c7dc3bbca9b060a0ca2ed5c130920b3b0c03893bb8fbd3b5b9a79d92bd31cff9a8fe53deca91d0f3f5c3f7deb1221d58a894e0ff93eeabe670a146b2f18ac119a02b2c74edce75900eb87b2573e091034eb1c8daf9c7a131de8429320e6d3e9116e789e4a53fc619cea1e670b48305bcfb27d8dc05452e1f851c3978d076bcee70dfb8620318b1fe7c9fe3ac3305d737f7f51a738a500a392e7c8e0f2fd2dd7930e5392daaae1ff131cdc3800f1bbd40978907523751d460f1df51c0e8c8b51d9a0dab61b7f580489eeeac852ff6e8ffc6f468e8f9d780a6a650e9ebc3abe5d797e021a0f7597f2c6346252b7e8861070c1159adf9955993bd1eeb7d0ac155864f4094f9ac658dc85f2f32cd9eea110fb58512ceb393c8ae90aa9e31012d08ece5927312938ad0669c0a1230408bf6bafc395ff6d965c0aa9b0687a66639093cba3c5e8251e5f1fce2502da951447c43d5d1acf9985260f7a092b7a5b3e929375e635bd6a8829a1a1ceb4768a912f6249f6ec062a0ba9c0334b4b0017de97017fd2a7c2de60ae95f3be46460010ee4bce35e1f7ef15fbc0ba6970f8dc202fc1c3937fa6771c8185e65fa062b7e1b7f9a2ba3e7cb829abe5f5c3a527f60fecd62d271dddf1b7ea1c4ff19dc0dce809f26476b9f37b55b8f74012f2352683abb408053415a89e912a48b399f0b7df5a15cfb7436c94ccbe6d7ea3c94e2464e51a7e438a5ddba6f93ad9369884be0034d000a401ecd5b43a68ae644e7761f535b081cc420ec76b50165d94ae85a8e6da6892538117e41ed23cd75272cd98b6ac35925b03a17492c23875231abfc0f16f7b90c3b58f4f92d8b361d31696d704bf459abd1cbbc3aea66baab105f7b49b8bc79e7193aa07e696442f3a327c5d097f0bfb2f6cec225cd137807a8f87cba1b9fedcc816e1a0bdd6e2948f07e2b6fb0b71e3cb8886f37e0bea940119a8208ebb4d06106338d04081291cdd753b1eecdaa2270c16b2093c71d53aeff1494867d0c4bd4606785e24b7ac400301901f4209c54a911466b494b4fb183f347178c0747ff1dc6432d6b1093dbb36e292cae9e35fdae32ca9486c3c0b15441ccc27a90c062c03766fd07d77ae2ba283ea85f829e46c0231099d836da229458d5c700d3aa092d510b2b4ceba1ec11650b985ba1709604ca8804c3d3c18a4811fc433980e7d99df65515c5790f10410b8052080ef208b145ab7ea40214168461ea56d93d45a8426c30cc2699e812ec620d71cddbf31eb3974c35025e7acde5dc0ef5ac054a6e30841c805986cc1a42d1a501c0abb8e6934e1b56a40d70d0499de386a66287de65e84962c86645ecad5379ea2882449035d512649a6f6c88a91085de595f4c3b2ebab803d7323aa00c0a7358a47d83f2bb9094955494fe6853495c4e52133840988ab8a72b948578afcb2b209c126a344d56cfe56b2ae33b5b89f96c7d6aa35b788bd06ef573fb71a06075868e4744af71a4aa0b665a4fe79576219e41f2ddb8c77c498ad2167f11aca8c9376a0a199cffa877ba059bf4f461a1373a93c450293c3300103d08642af2b8c1c1ccfbe3fe4442194786d3ce3ca22015bc59259a5ff83908dcbaf623efd2c0a908191fff58aa355d48b44b1a82dce31b36a3abf6837882daf5047d4f5d546677f894b956242799e3a525b7ec3e825f4e003055567cbcee91b265e040d82581a7c19f7952547b41f157e5752f88bf60c5fe62ba56d0de6f45d130007c4de68ffc66af75ea5e89235a5f4b90a00647e79a3acbc9a0e33691e4cb1f18d0a7810442b49bfba1eb77450bc27fca89d6c3f2e631e3c2d040cca1f5579891fe1752f493555ccfb29e675fb7d5ada7e02c6d026d049a3534b342196b075e135ddc6078dcfdf4422e62400d5830233ab6b24105ab164512913578343620f3713d31a6f014b4850811dbeaf506122feb2041250a77720f52e746a6002ff074c9ee00829ad2ddcfcb8abe4b1d6d9832f9427673e9b8bc63de25ceab83be3aae9778918a1b82d6549683187c900bbe43571c4dc3673c480a3de7c9a8fff20bb320c84f2d20ae2bd98dc9cbd220330e27a78bcb1ba279b35e39ee18806cdfe7d7f6e3af5bb03401362a78d994227fd90ae65a5d63996280d857a8999b02e215da8d623fa12f3444ac98a94831d38b07e90f2cef0ec21ef7edb0e575a855ebb0a5aa777093bcf435601b82bca2b22949166a751e5ff79b046b76d730b000dcc8d75f56e7380e810bee5775d244a05f5b8c9568504f39dedff95b53ce24e68fa1e98af8cb77e5631e6dbff8f3deb62620b04ae1561aec71c8df9ce27bb7b7d64bc0b8de3c2d8525778fcfa4252e51ee6f1f366889365494a0853aac874dcde127c9941911b8606f46a9cd6a85dc027b7bc9cda337d5bfb04fa7bc193d2da402cb1e9c3f3d053b6301360a3806692be13812076fb9bb56f519ce815cca7aad43300992772fbabc34a7c2a30dcb375080de4403dc5a109d0ecc2f8de6f19c8b263398b839708952cd354ce68dcf2c6561110235908d65ef5eb404f77f97fb948b8c82108e41d4337ca53dfbe15f31db3ad487386435507d773e59f90a5e5a1a3651cd45b02773d8f71e4f8064efa3a902e815e1a0fd98446a1491f84d8df6c8e384115fa6d1ffa3265a184d3b98ccb21e885831ecddd4ec90663367c0620ab7810a0dcb19f101baeec58e171e4713603e69ef69c946a041c7cc1ecf088ea2956b55c8a9ff8b619d52cd08da70aed2f47a20fdafcb5d4f8912fdb914b328670ad64da9b0d335190d3399de7d611b9c6256b3bb128914c3ce18b34abefb09db252f2107fcd82efc9bc30190853c512156166a03f52a45922e9892f7c017e58bb3e8a7f1f538840317ab8bd931dd20cf69c64b4ab6af0cc35c56e786ead61cc8ccc8bb3c3c6bca3a0808ac03b8822f4747141704e0a783b94aaf7b16327a9787226d4f90786a832c5f2ef2e6b64111d9015073f4c8d67687a45db95651307ee1df2974ca5ace5481e1f1ea2bd6e4e41cd4036b4097fc8ca3467e030e785833f87654bb53570c28f61d52ec5aa93e37dc9f72ad8a2a30058319f8abeee5846d454267c231c982bd9a760a09dfb7824dd7ffec872384b01efb78653f9d84baa3e492c22a741d1b90fce50e79a877a28b80eb23061ef2c3b623cfcc0cb8a13c819cfe049b0b0d376bd0ddc1b8d2df6f249735a5754b6d16b42607981549d74e0538d751a716192777bd23357bf88fc3141d404bae76ddf7fbea845d6c0ab2472301c1a6d12274f3d662d91f3cb94222f8cc10e161063d204f11cc2152ae5a6116fc294cf47811e202a8296874577f8aa65c7d838c4cd9169052fe26a6382fa9f4ce52e92a659d41b6312bd1b50d2860b1c7dee8375c43ec40900223d9e35bdae127c9b459ed75dbe4f9caba443d2926514b6440a11362a04ee0cf73e407280e6d607f3e5c0646148228296f92815124cfd761bf584c90ee09ba17ae0d0cb94f414a65808a15360559c408fd0cc8bbfce46765eaaed9a4e726de66914211222d901d6fe45ce9b8162f1f60d9a90b31dd0d49972673afb202fed1ef043630776898c49ef67adab577e509a0a1a6f28f7e3d12288332ed022898c6716206176d8c9a61024a9e442adc7adb34ec8046447b1d0af89e0853c9491e871012940af7d30558190e150fba91bafea662a298bc726de678e109046cdf7ca2beaa64534234650af2c3b3f5e39580f8c8288adc95090e0a7bcfb26a7f1d3cc0c815e96e2a1c24bb699ab000e8a08753cbb53d45367116130238a4daea5d766deab0cf1b75376b3e3790f21a524a274bd517334da91689bf73e3cdda52854175b0e0d9058bc99101266105f0a24f29c8ac91420af246a264fb2ba2e0ca20e361414424f9a4529d1b34ddb9ce7af76f6c977df90edf25fe1517815c1e021de8c90025f64a97a0722d9afad4ab8766758d2f4461bfd50c9a91f8ceb27952a466a7f01bf4aebb3ee739ec2fd7e2d0f1eab3336d22b5a8dbdbe802362b14496f6a6c747ac0ae1dd13c1d084eb33d7fc1acfb77623477a5acb3db14f36572abe60141fbd4b56ac608bd45462f44d28fbf432647e2bb105443529eeacc5fec45b665f5009d400c76dd6d24a99a7a4ff2fff533bf9e931085aa92fc5c9ed7875032bb3d523bf8851b154cd57fcedfe464e908c1e984fafeff246af3f8082094a92b8d10302f001af04abaea3892e90d9085a04ce7c686dc64c9bae933ebadf1c556abf84f6d6b9fbeed692fa12c6e906264ffce27a9be730150c76ac24735756313f79fd5693f399bf50c823d5fdd8d7230d389afa31f829183f079f5e68822f1d0e891fc03b0bc384550475718205b5f5b1ca2d4b5c15acb571dd3d99de1e346147958693b1945054ede4800f78c357974c4168ce71a78703d63ec9996fdd4e911cc6cf43a941c14e66f2fbd3c0433497b45546e9e244fe17ba133022791eac05f9ae3d27fbbac14e401ef94321f6e0129fd0b936f4f125978873d5d6931e6fb576a4cba0c7834c7a451d8baaa5eb7149cf7d86c43fd55e3a9d311ea3b297f54193d741dfa7b5d024499735d23e4ed0a810018b7437735935ab0d0162850f01b1be88f6f6124892448377a29ce8e4149cace7186409e9cf99d90090b3d007d3a83ff4b652280772fb9f3f4eb0ac5888c06fe1d7745769d312fdb9ed0186a13b03b13d2e241a1e91ff2371de143e982ca0b4d99b74296e47b9671e3b7d90bf9858be611a5f1d433e0fbb5be5000c03405ca82d8034ad2afe97b6ab6421cb6877e2a8000308f393007a7bf13403087068176e16a16ad61a4b5cd0546a601961a263f922adfda6950131b301632e0ea696c1921c32e654b658285a2c267501907930620641a2a028dac050e0d103627a2a6a154304de401721c3443c231988cecf63b6b853ab5ad4c32b4a1086b4c8b1e013ba52f26c96c8254ac201f85eb73d53a56eda2e4b2caf05ca4c90a9ba417e111b6d17c84082370b7e0a0951594cdc1e211d33c11eefd09653f4028ce32b8697b8837bd56e216e73d71669328718c2499b453ee458745c462c386b8f3f129382e3a4c54217bb2156cdc131286d9b68bcc7af32731a8f03d4bf602a4fbf9e14869f0aac5dbd281de986bcd23c2e00a91599f302ff39f6947aab6a329011f584191a58637c7a44f105d6b39b41947686418dd33a36df60e621c91e2ea9e0558073f7a20385eb359ee016a9c697a17ecb78ca760da0b1330a7de30694c6518311058a8502e3862e507779d14149c4409ae8b10be32d70ba038201102ba80e295da21db101ad32a508796f5e0597a49c03c04f33f9c108268c861275bbb0e12a9d57425832dc42b682d64e3c9af3e157aa4408cc48d4f16a59a7f4860950172077d2b4693275be446499064f113846ef55aa755b06cac23d2179e3a951b528866f2872a4b5b599367e0c9625babf1919b3891836321e74c30a7701b2cc68608ba2ffbd986139883a47e937ab0972719d0813605bde34bd62e2106baafa345c03d6a07b8c8181ec883bdea7d6e5aac78414ae6402e608477a9f3d5d2f190f817d8be1a80f78d2a304e01ec631400b92f577d585967ac41f03511ffc78bb54a33cfc21317011a46c1b076f84cc4f06b42fae1fcfaa409ad5b93e5f6be0f0a60b02ea5024162856e3c263758b91287a95b1ff7d6ef443b6df104560a3fcd454f76fa30a5bbf6aec7afed8950e5e899ceb586b5f7e4f112f5abb49705059d4f2339a709d226c4e8ecf57e300d060b4e753e7571a0a71d7262c974dbd14d16f4995241fa9b9f8abc9305b81022f8747fd0ebfd7d49c6700535909afa36bcdee8cf1f8f5e757f4fe51c4d37b2d23a58abe1027bf70c445d4fe540143f0dac1bdd65477e96679efb045119353e2d8bbb87ebef4ccba05460030dec2f105e4ec8cbdb6237badf6c66c009d4d9c048aad6cf8274b5789c809dc8bf4c5b48141010977d55ed8a5c1c6b1cef254fe8837b1d0617638e48474d70cf38724d6d5e7442e4cb0185415aa02b463002d052bd4ee3f92b7641c1a33e609262611486e3caa3a7671e275e84e803b408c408f922470b1129c58a563941e8d4eb1784a614805c5a56e547b2637062a1cda4e737ccc41f1487874e0309c8c081dabffb52347eef0de26a73abb341f976f82ea82297612051a4f8240150ea5fa0b07c6521572b4cb827e4657c57e9e8e08362e68612514b75efc86b805af9cff34cf78d74f7f808d471506be09d0488297c168917044de93c68c2776e578ca6b1108d79429a8ff8c721a520192bfd560ceca52390b16d06c8b649aca0bd9e9f3cce24d2bf1fc6353aca13f95b1a555740685bf7fd9400615e76b6f64221419a31d5f762466068051630aa8fb144f2f15024c36d239a2efba2522a7d8ab84d563805cb19ddcd91c716b476c0f1538cbd034ad0fa73f29ea898652f712d060cbc2717a50dee8a4f2fbb86a350796e8676ae1da0737a83736090944627c91775baffeeed78a33f8478fa68e3cb496e129e41da07bd6fb2c41f17c7c24f803372c542a0ad7e46a7e0d0c753d95e60e8913ce694113eec95d280ad4b55488db6b71350fc6de4d477aee70885b8a22964602ac0351fd7aaae7c3a0f414239d150aecb50c07754bdb8c7e566470a58f38ae4f6b4eab52ea4ee2b6cdf970e2ee609179338d5ff506d46a52621fd44f5cb1550d507443dd1044e7f22bfade45b97690906d19f6788ce2a4383b64a791dbf395d0b1cf9076b1cda679908ee4a7ddc45a14b725101b386b17e493b0ccfdf647a01f5f115273c2eb9eb9aff8e20c7c2708091262211eddc41434516ff09dbc28c36fc11b19c6e44181070af03b30f8bf4fe41e69b27bc186871d360d27b5775f93b7fe8721b28ac5d814875eade81f8b656c69e00ed7eb1819534d1248ab61d52f84f1e004a72caf5c8df0846beaa4e8e599823d78eb383bdaf47f70ff92ba6b4fe16b352a57b370777ddc87ae43f2b9f7b3a8f5c39a96f3ef7bc82ba51620f89561aeeb936e806e0f3b426a4ef0626813dcc52aaafa76fc47a5c2b943ae8594d04366b1672b3486073d7e298e802078e9464a56fe77548afa6d78f996245e623568888b98e57b1a016f3f15d1cb17a2bef10cc2089fbe5a2203f6c505886104be9b8c6c5e7b3c3b18404d5ee37d254851d495a77ab348f0ff4755e0a38081f1fb738cb5864735903ab0523cfe065236f1e6a206dd86effeb3c58565744e24d26f813aad283c6dca20283303724143ef81dbd25b625c47fb7cb478cbdbf53d6391e4a6d6df4cea26900b91062025a850b70b0feb918ff7d43dcd06e9ac72b0e7bcb43f5db7e8be0200c2df9156ef91f7aae0f97fb8463080cadce9d4507be5cad2ab54adc68bc22e64fb3e3ae2ba58f99e8639fad36371b889e7f899d214da0418dbc3c4470b87552d8508a14b7f7201090f522b13a64b765e2c42ba472b5e0395681c8fa2b272207738c4439377e2832a87bb14b6fbe7d90342e9dc69a3d8051c6fdd602dd5d1f197f862a4023426643e8f0b3361c5ec2fb76cf0e47838fed41ff25114f917fe9d96bcbee1dcb3ec976a9e2e55e8ab01abb7afb39eba50a9506845e5ae168b13b2f55f0d9347482ed87540484d3856f17b81f7bf0f1edbc3eef5b7723b06311d84617dd769000aff6c2305767740a4a44260d4408d5ee0fe6d867ac9080b378cf41c80f1cd3aaf18fe8fcd5b15f203da2df112c103d3c4083f9f184b6ad1f22425b1f188cdccc215f8e0311a8fa202ecef048fa9f97471db630271fa81ced5d8f4788d350c0f1d85832c9e7611a63ba30e31db103e2140cc352ee720e4d6201c01a96173a37e3478a59d6a046d5afa24b3473f368af8bf0dac0ed40cc1f7ce3c1647ae998804bb353ac7f65881feb846f681650c856ae1a2c6cbe71190bbe14c0a948ae24765407b168ed6f25d85c22b7f10051d8aead42d4d915b70599c57662a8d5fba727034a91af6d705d432a5c688a0389ff69164787251c00f9217ef9fa4cf00c2536ceb1c039db86ca2ba39f1b63921b941e1d42c7e98f0e290db381b42d7ada32fb6f4b07127ddcc992e2f178a610d5acc487934e0578d07d1b0a5d9bffb75972851df09b982e8340f18705f0af40143c3db451d0821600952638b05900c965a2ad665938d625123f9ac3673cdedc791d7704c3f318a08aed37e99f7eb27bafc2bc49f93446b28ebf749c27dd644f8fe245a0d21ad06b16b3f185ccabd017d1bfee04c4ca16d92a3c2f8fefe16819161cbdbbbde145c0dfd72491f31d25c6b9fc661a5904589eb84a1c48aff52a360ddb3ce0dcc9cf64740d1ea822a08b2145aae2a0119063906e4ecc3706571d595ebb63ea13034012f21fddadc68769823642b863f9d33bc241523b221ad37dd29893b8c5161422e46ffdbf0e78800eadb9da845c5d5321bf1534213fd2430dee2aaa31910b88427e2c9a42eba7ebc52dab082a09142ae109f9f5bc392a22e4574a880323c0132228988b0dcd98faf8eb8698bb6160aaf3c5322c010bf6e1688bca65d5f791f5236bc930cf7abe3dd00096f3efbcbf8ffab1f34b365ec1dabd2b1f859c7c9f062b49ed5ec2009bf90f50ea4db1f983d5fec538dc358a2ce630a96d0040a8af12bbc73b971fafe33d98a6985f5def9b6c8c9cff80b847f6e144d0ccc5f0073f57bec3432e0bc4d7de3c05290edbdec0777bd31e7a83466405a3dec93b3ca3f9a7ced23c5961574568d370293ffe64c7bf559302ea6bd0fc16c7f59d372fca6ef20e24d26a65efbf96721e4889cb4e610520543158643f43b18598499627fa4acc8632bf9e5317994a99918f8c654d4bec62411b76f68680ade5500215d49acb8331cf63018d51fee0f665d01bc3cec895d05bb6cf8f0603014c2a1eca7afa0a2e282b7fdc63b094f293a1b6ca80026526290d98197b521e2b5175ad6723ec71cece2e6f650dbeac74610154c7c1fce1f0dcc713a6a426afe73692097042f0c1b9b7bc4c4e61bbefe2818a979ceb0404fe60a730e8b8df770815465b5d7ec234ef8aeec66d14ad2f25102170c8efacccff3a93946f7350143368afa0bc65de027db0db117978c073317d4838c9a5b140abfd68d9f1b136937a1edd86604f095f1cb094709e6b6ff66212ff29c0ac106d161bccb4aa66f383263bc66c924793909b3ec7e6fca29f9fcbbb7ebedd1cd0d5102f4146359f1cc362905d0faf32a863bcfc016927c1f6486b31193dd5ca7f3de37561ed4e18f14f39343c148c978b5be431c4349b511fd220e726eee022de4cdddff171d956914f188382e5c83b2317ca21266bc761aae7648b2d7a3243c29d96e3df7bcfcee559dea2f52f31654561fbfccab1390e4c2c6a436a66f9a453f8c2af2e647b577788ee386ae6984d68ebb3227edc559781751ea0320e1626b75aff587289ce04a6a1fe04acd230ac85f9ab58b9994a8732bc22c2e25434e69f88b34f68cd3e09a383c228647930c903f88fa2ca6b8fc7f5ae2cf46b99da9e6efd7c3855406ecd3ab83abdf8edcfda9283ccd95d17b4e500ede81967fcd7798c2dc9759894ed46ae3ac9c1dbf228ad8f15742c2b2efbfdb287e574822c51140eae7983d9958e40ca8f689de5361685c20eff44f0cf0923d8f5b6de6d28c2e0239e15dc55ecd392dc68f1063679df00b8178502d7943780674dcf5dcfcabbcbb2853615c4130b5706aec20b074d2a6058565c0b7a12cd0e6880fcc221f9d6c184ad336ae21d71f187bb0014d600f2f7ae93a764722d60ed8c96e12105a8acc09ca0daa53269ab0da10827c5d8ba322b2943953bad198cbd714bd93c06f0e017104a2c08a56cf5121755040e68aafc11ca5d2e8308d62a4d761ff257a5119a63a485624ca89162a01abf7f0227d8ca07dac6e54fc06b236e672345f07a03828daf27705a77964ba15db181912376b9765ddf9a2a0f62ec03df15083d9931fed94972de4bbaf6ca45018aaeb49502ae6ab41461c6b50e512be7bb36d590068a55045c1753e0c41d92d11e096329a5642b273ea91f7042794928ba2d92398d9365aca43b40c99ce0b2868983fdbb5e3e4091bc516848fb6fa85f6f2700a287d93874069b8600d101529681fa52890e537f331a79ac32b1930a41f18503666875a87be7b3ffb93266f78c182cebde965b2b114b67b21ee8bfdc3486d1cf380d17c52788ddb06dce4c298834dd14a032f791edf2b096f83022fc0d5420b69ebb9d739239f424090556cc8139082419ac90fc030f91dcfef2df8cefae62c01c9631f1a7706681e1139c7d25a2e6541cad2507ceb3c088487e8de6a0d60124f07f32d518c14e9f35c322793b89c74d60b6ac7988061044eff19ab982d0070f5134d2e1d5176be0e189470c26bb94e465eaa37f5564fdd6f2b75fa8449b964adbf95efda31a0786ddbb54e6337fe6a6a0a8cd8b5ad81cdc83b9e42899eda552da5c0fbe43b7bd40430ca4db196904acd59fc9dd6d2d9a1fcd896416c28677ba6b741f49e36cce89abb946c8b658402d3e71673f0deda41bc7fb6b6487bf446e944f04d7cdd1fe35d8c11f1136e2238274e338fdb982477072d596b83f0a4c56bc1a294ab6c403d4e2c93beb83bb2afa51d50f603704486579fca1a56c6823972cf7886c78bd09b99251d1dfe0684bbc1415c309ff09a348ea73477cd24f1fd2a87a78f2cb1003fde4e3b9786600eb4976e066c3f6418e25dc6ee42f9e587de0d233524e872570d07162edfa3770a0049c1c792098b8807e3f23b04569b2e5383648e10fb93b0c8d790e1a0b5bd85a7ac29d15fc5d77ed867f1310cc37fe6e37ca687a518c88dd5535345fb8b4719f20719615a0db182cb74f9576b3b8f1f8a439c04172a293f0c4eb34759cf7cb302e6ad0ad0527e05891b90c3b5122cf06957b37846e12038225f01bf6dfbe94e31b675c36c9b0c65c9db294c0154cad324a36b72413ee68af50c5e8f7076af3628b2230fd72bf44a721bf4cf9cddf65c027d642a18db7dddc0dbf1212ee4327657149c8b0189933b3a83a5da495d9fd6cb0a1c2f4520419ba8a26d79528d715f10335a0d4101d4f6da509a3c2e3cd32c0efef852114644575a7ed150c66813c0b7511e9463182517aa608fbdcab48d152bb9ddcb0494171f5253c86f8ff85cb1c905e4780910708422172cacc0962edd2f9d7b08e56756ef9d0965228d5d7c128c278b456c403c5756467cf3764a5987e0fd0693cf5cc8e75bca9ad130782410cab4d51691bf04f3b4130cb514f4de8b4e1acfb85ef18c3e689b2bbc07cbdd64ccb88fab44ba4cb9d7d7312ae0d4ebf8015fa4f9842c83268f2444bac6cb057717048eda2907d68174555cfc1c5bdd6069ec460da69962490f44e20041f43c23ac04964388e08d1c2db030734c10e6e84b17ad86bda100f678967dc6a9c8523469f1f362c7c1def807f5914d1397b254a21ca9d98dab989fadad6fb853412dfc3f63168e9e253ba61b39b2e3ce0165c5fe2de1730d6028030b144c3b3db810f773ce4f98dcb3ee5084a62ad2d076a12515d790197eee0cb0dadcd55cad4614d7d13468234b78b4d29625faab6768dc806c139011752dd36d096c57a4d9e4086915bb9d019673286ae37b02506283f13f2e272a31dad8dfb9eec4e2b48541ab683441fd5854d498757c5e0b0d2b3e31dd9b58a129671243444a72bb75e92fe7f32779320b042e8e1a908fea768a3c292e09320b415865054d17da8168015f0721de685faae1564ee66900744c59ed23d2a839c6cbf582341f8eb41120661b5363188a5ee9431a8bc29caac697344b6abe260a5d304be621a9a00cf171446d6d1de576bbb97864c7792b05161c7c34f455397eba9c509cac949bf71399bdb9ea97fb38277204bd22964ed90a0c0612060c56f7106af32da9f387ffe107d9d099771e509554c1fde9c36a4b4ecd4358bcebae2e29ed99f953e910b5891b7269cc9067f2c06a842afd18d157f86eac1be24a29c9c436ded017fa41a3a4adf75d5bb1cd61b35b2545070086dfb96b246bee7424b3323f379e3e398a527611ea627fa2423ee559034f58e01c67c1573971e42675fb637bbd0f9490d8d8b0a05275651c73d0f6ef35656b58fcfc8a09ba244e2a820769e1316ef82788093076d1e342e595f2b663217e4d2f3260cc6b1c74d844440bba0e4feb2798c03a0a0d045af4a16f65038739c54ee840df2fb9561d32cc8ff709213191d6631e313b95fff0de876c058e23f9d1eba5ffc141640f445c520680c96e8bf7431062921c5a5eb5f402efa7db7efb7998497e7c0caabbc6bfa51e7311ef277185fec4d8e91e0c8d8438e9c651be58019da1fdcbe97d1a046b9c0114488dd928618f71202672c33234fce51cb15f4a6b2733eedc8590044a72aa83fe8a3f79eb1e1bbbdbeb00bab7b2282e801957a693a4f4f0e5954cf88326ca59fb19c002656c12b5dd22e710cc14e72a947050735a09f168582d782d459c5424944948151263cf65b3ae5271bf377e11f235e82966524b1e9aeed22dc3ae3544ae660aae370792182f07d7767b4cc861193b79adf7c8716ae767b1760f958be5290b6fd1b48b0a12bc79be3a6e2330038bf7268d478f83cfefa3baef16f279a6717e616d586e693846fc49462ee5936632e0aa65b05834ea6fa78e6d4a9384bb905a1fe1b5d4fb6788a109113d9be875cdb7e33350e15d038d44b485c55f637f4cf50b8b6ac9769e400d78ff8f8a3ae6c7eba2a4be0763fc70e6350c6a1a2a64a18fb9c64cdfbd52428255502e0858de1a04ee3297136e9c5e588b50cda1cdac118452315f3282082e86b0c4be17073913718bb8346dd8756a47e59845d9901718d53a7b58ded58f442d2a6f1fb1d35b07fdb0cc2cddc5f0e64eebfa18b0b5e6ce5fe687cf1f030bf7c5146dd74c83b1ab7e104cf47ec0bf01947660ca532a778c02772a1cf4d23827d2b9dd3c79e0b9409d0d91cadfaca5056eca7204ef42ed44eb1b1e9443d81d17aee5f0fcc95723961d6d5f6389e86a1ca0b1ee7a30f463e39954207a5049280d2beffa264e30192325a59caeb9573eae34741cc635bdd2322b11cf113ad00461732dfa3458d5bf81200a66f9f79779c844e20845b8a8b7b67a0e64edafa20858c3567d3fe433a7954287fee03240d983a7cb93b46c5fc80ea253a622866a994c01d4bdfc3e235e551392b064b4a434e40d45950006c38b448ab40ae852f9b52c45604235dec6145f6a66bcd709c71396c903a196258591d6cd9bcb7b7bf70b0240106b47b5949f02490c1450002bbf9e312d0fcda6ce5b27e8a6a82192700054155ccf1a118a0d84c91603ba9075492bc4d380275e43651228582481a1a7ea22c35f20be435a52b664b1bbfaa8bbc86861e077f48c84fc8df7b8eee2f522c5b0e756df61474d3e13a95c6fc96e92aaf5da158307abecace3ca29e5b7066b2db049290491380a641f3bf5a87f12ac448a27990676571ffc4a4d4d1446a7bc8f25f5c5bd609ddd79ac0120dfd0520f4f3382e28dfeab834166f4bb27af544399c117df06450565394917c364a517a7a10c310d18f3bf7ebec3605b704ca42242be2532b00166d6a3534d74dbf16085d493c6852eff9a4b002f6098923ea8707857e1f08087b0e69cab39090569ab59df8e5f1035e4e4d21673831592b446a05eba947988535ba06b3ba45bccd54845d26d36a4a8acf5b6782e12a55d90e55fede0fe5f443b57e811114263c668e91806da972a6f6d3b8e2c66237699a1c5a1ca4396a91b1531d458adee2592247d21fa938709647b1b428d7ec9e783b807ae1f835a8327aefc9f4b359364f9d6fcbcb1c4870c0034c53b7dbd09452e449bd224f1854843f7d54000a7d288613225a6c66381050261fa72a2f9d7912240201ac735ebf77e5210e5befb703ca4a6300d7c0424639ed8d09e06c41ea3a21a87b3950392a2f783faac210726220b492b38f90b45b46e671223379a6e606cbe98bf66488c5223c665151290838b8850524ef971ab8d918c70948a62be09aadea955b105ae642224c6762c520cfc44a86db2908536e7ac4782fd7d2f4c4a151aee54982a1b4485cf6b84f88ccc5ad420dba073aa574da294dcc89232b951f5cbcd89abb9eee2a80c5a3ed2114c21e24537f47c7e52b0555d7db24b300e5acb4375ed269673149d42a5e1e64bd778792ebe96445ed33c946f00b0b1182dfe1aa888605d893c398d48892d0fe6016fa3de9665a7422503d10995676f1b4991112141d832422f23ce245c08198b451c7bf9d64b9989e4b5575dc3f70d5b95746a1b8fb26c7ca9490670908d724c3708996af3b9d664b044f7e4950210a2896d13e54a8c1f7d0e6e72c37b53195b11f30d1a620462c02c65470738455bddff7ffb1cd289338ee9cf3bffd169a7650f9ee481544605c7ce19063a87777f180eb77f908f230738a822bfc675d2a4ec8efe0d24fe7fb14a0ea168af7e41bdc822cd1cb72f73e212f3f3d8cffa62197a2795d2857d0aad8af5771079c68935f2727d1930466652cce0dd947aac90e430b2a3bfcce0326298cd480b9a03f2f721eeb4506f5df67403669ae6c7688576cb9dc4aaa0f7f05a11db6c48af522f74a69b506f740c4ff115f2cea0c5d2a93d8d591175445daab3c24e17e0da5b6dda585683494afb3c7423874a235a89de6ea4dacc0a3c48d6e06d83687faff2fcea016a2822cc25d578c75ac9f9b68ebb531307f8ba97f3645361c921369f25656f9b38e547bae0685105b34d0d0e8cd3879973c2ee6f1d99e63a669534d9fa7baadbc97ff89fa9f161ae23dab1cf8e388d355433c816fd73404dcc6901c10c6cef13b9b5bd7fbd03dfab0c3100e6e3dd88187d78b3922eaa803e0ecbe3e6c374cb85dac51f0233f075337912e82e1f884e4018ae22005352d4d3d159d81bd40a5ae914f07f26926a2af720f373be19c36239f2e9b50cbda9dfc7533208a04eae2cc68431bfa0418a0b976d3a204f71c2820c4ce2db4dfa5f66d2e966114c59d143841bc7e82ce9a2347ba002a6d5777fa5f8588185503bd70362e15a845d103230c240cddcb4d0edada2be960d095aff43f20d68f34271cc69cb4d40260c368066823d03b837e0f44c57a602ca1b70703dfdc05bf56f0ef9acd46bad10b58aaae821f91b114da682e1ad3556f4c28875c2e5a3bee0378c12624825c4cc305d20b162ccf76502e3450747f0795210099606813a90863cea75133814bae7767698a76c016faa3ab21b61c5498633d55466f2d990760c00fe48e7d64417e3a431358598fc88f0b78fa200165b40e5ebd40e35decaee6fc696ba546c929c72813384f43923a4138742a443c24c5dbaaf6184b6badb6dd32450eb05ae99032bc5b1fd2204ee4f1d149f836b084573b97cfbba9bdf45b42f81f25223bd427fa528e3718e6baca056bc083b6f89994c838f910eb9b2ef60e2c3f59f9f05b1c6442df0844cb906aa7aff0291659954c1dd510d9dc99bcb9576e21e297124d771adeab31478adee2e34f27e5352d8a43e8499e2f3b07c51babeb5ae7491b4183a66bd84a1b6b4d124e87456ea612a4da147ad49b5fde21ac34ce49c8ac1e364fccf0bb099a75af914e1f60166008fdef134e3999488461f9bb227825df4ad75ca08ec27ed200cd0419131ba6335bbeb9bd623b4d9ee96dcb4126f427e8a458567b7d36f13487dc140bb2587fca48162adc67cd0769bcc09d309aa6d09b23650adcfe97652e00d1e24f1d8d2105e0523ce3deba68e3eab113c5aa633637cf7b1ee413dc288de57f0588ed27cf8ba0732e5a90bcfef8419f1acf73f6318f7610ab07902dddbd5bf84a4068f5249e6d5f6fb9fce44c661759ea3ccc76c031e814453d05cb71461b84aeb028b6143ea3e0dd4231e614650d452a423b5a601db29685d05370e369f4939aaef07c8c1f73f17a51b5e82296cd0299904a098a7c97025eee55d14b29519e75f12f5dc09f4baf55bb8e58c4e061c80dcee40b8cbdcb662a596e9daea7bb60c8ac2a08053ea37f2584c6a1ef5e2e02133d8abe25848e94e61af5381192c627f54a121934a703ca941684582056b24a07f26597e77e2fbd1946fd6760593e9d316307dd13c1cc5cd5030680fd9fbbe537813459e0f28c39d16d1f823a43ee45b6a9ce15d5c0480fb04a5f9178aeb1adec2563e0008b56d2882464efbdb7945bca94640a5b08c60743081ce4f6734b7e06e248c2862e84f373dbd21591dbec59f971fca3e6421e61422e452bdd18390e23bba217535a0907ee2d6e8112ba4e646253fc3b3ed9a64c791225174c1d9138454e59698ea223ae742e713764a6302ea9b9f1a8874b32b211cb9187e32db9f52475d1e32ff6f1e12e1ab1cb3f1d2c6b05298505a41591c202ca5e7e2133b9118c9175771e7e88a6cb11e7684493021bc62746401af40634583445baccf58f5a6a8818298b28ee91cb754cce90c4cbb821af31847361c368148d5ac8cb183e2c09643e3afed286a28ff69b0d3c1ae25127c05ffb0858edab6133faed63a282cff63dfda33de649d93e123009f2d15e7a524640030e358c407b157c827cb4eff12d763ea48c407b29db47821afa4be0c3dd8ff6d1107dbc411c7f09619deab2b1cdd67991b91c5efbacd142b14bc7a13a2939be3d1c1e7bebf4598eff7bc45f375eba14563e51617fc3a38149f439c206907fe3dba321faf41f21295d59fdfca8e58b928ba568f4c5a42f1e6151f2d45ccd7c03b13457ab29af944fa43c41698d2bbfe5af66e6929116a52ba59371e5473594ba7858572f041bc6a335a6b02c1fc6a75fcaaf76257db61928bffa9939e77cb2f20060836d8761793a9ebb16747c7b355ef4d1e1235e2935afc36379dad778ec53738477a6cc873a2e657b2b106ff968ec816032cf1e83fdcc175aec6bea65bc50478c97144384f1629037642615f6a9172f05ca97f259be7c50e25c2925273f8787c36b216fa1bc76d2445de3f5136fc9674d91bf153971e5ef68b234f453895d20d05f03096e3c0ed1e7c627a58c5db279e5c735983a87bbf27b4a3bd12f0008f5122d574a19a5a419364390cef1252197622e3197984bac8bcc6426a5c8921ca9df94321da386e79814cbe15c5bf7896126a0946294821527d6ba5a514a674b4ab9c8cc94524add23bf5ff756c3a5cce73c654f2ffce60c1bf39f93e38db367bd724e2b95e0eeee320773777777c7648c4ba639991963c6d8c3306c323327ed650c0929e7a44fe56cda22e5e7be45d62cab5ce4dc82512e1bc7338b8f25e900c93927bb2acd2a9d53c75f2f74ab29c0376d01be2264bac7cde71727e63b1a0431df3cbbaa7508114782b5f94a31ba84ad52003fd8d465242cb818c3dacb485850017e81050d70c02209ab821dfac1b7018b28f216635800d104c3304c054b2ea840cb009ec06207af08e78a0757b2ca6abff21855bca06b34acd8c1698d1b97a051458aada2835395266b650bee06537448582cc9ace4dcb0e6aa9e7170552a555293ab4ada82b7204aa56a6eea6d6eea53a92b7eb8a92bb0dcd40d82e801a8e57e0123515f918070516fad407151cf483c705284aa904191aae474485600a1564a6b2e0dc0a5b40a1f5c6a45511534e01e84418333785812f2c1aa38d21281d0c419480c61791c39515153ac649538f2e5eae80834260c1555ee2cc09d54a471670db09ce69c734e71842b6bffc089271ac663871459dcf9727e8d72e7dbd51457eefc0771a494524a1b3421a59472dae008cf0656a42062fe9cd3872f9cd8d003305a70848b31e79c938389020c1c512461f5824d8b5a6b553d11860f9688a2972788d0030a0a96d4134d442726cb830f9d1051504634e1a29c9218628342d4219d407b22c479e1244b41940d0a317aa84318695014e107fa6429b341070c668582a21ac50e3c4c178448a25140c941c2ac86bcc5350a24807061082d5140e1c4851428457185095c2062298a1cb8f0c4c82ba2310304cfb03097ab141145006a14e1c40e4554013a29420758952280e0a40726b4748b992a135dacd0ca8526869e0041145104af6856b1b10d10b12b82cd0013e19db8a2497d31c41bc6a42c66d56312bb20c03e31bfc5206ef99be0faf340c4bf566bc31587ffba212f6d552c0c6dc41bbb1e3f319f51c1b9acdb4b09e2e8a834f6d3b3bfb8b69f2aa5a1bef03b893e33fd98e4d2db3310fcae54e6d3a25beeed1ef81e84f8e27acc47c45b3ee3840d75e8e09d7ea2973e19b1c5dfee1a18c6d3088ecb1db8ae354ad17ee35a6b0ff6b1ead724d8ad0df382284563217e7dea15c9324ae9ccbe30d2c7aa473f529b6ed9e37b5c62536c395060b19f397eb0fcac060cc0a0728512582831f9c497b10138f2ebc962fd5b7d5f911217507ef09dd8b7d773a77f8fdb2db8ee188350843aa5ff832ffdb02f94f383b0aaed60bfb9234e82d54b3df6d9e2a89b882f1a88a3f3d957d4a9e3e637bfe370b4f9126befda59c72cd6e834de19b99b2f1fabe37f968b3407da4d2ccc6524a3265a2db0db6524a3237880646485910e6a58ae09891a24a108260840440d8a3080218bb5d16a52032d60036ab0342f23f5604526c5d32a0c16e2ad9dc9c308e01300b0e38fc9bef10a41ba57b4303100f35e6830e29c80009efbb12b7edff82c9b8bbf79949977ce9106e30bcd205906c18285b1f4910e8e4dc3505046df12843484b0d20eb604218d20323a16383b7078ecc03140eb1aa051a8020b50d244af95ab594dd178de110eb361aad1b204be846c327a1a42b00ca94228c01d19315b380de756ab93d0a3b16d31cae8b1b52933efda36d4dddd1b7377d7399239b20747c75fd8ecee7677f7d8e5deb1bbb7d34c0d5863c0c72efe2ea6408169107b49b1ae07f65e986d15fb9ec6b288bd0cd2458be130c7d2c9132a08bbf87980dc18262d8679956a84cc74b78de3ce89329f64661bba7f5766cccc9699ebe4103ca1583ff5a6645469c1a114aa4255e8052e7e55a494b40dda066dc3c6c7e8d3261b9f37ea853acc1fdfa56b524a29a594d99c2fa59452babb3b7f3dd2203fe52ed863d8d4a2820df2cca47429a5942ea59492c1adde60e3b39452b2942e8fe0a72da69c5272ec32659729bbc8c984c904c90425cc39e53bc628bb4da13ad8f82e6990a997ecc8c86808a329514d4d72367199b2a949ce262e534e2967939499724a2e7236bd787c9fcc2e19e717b99c3c2544e614fb28eb9cdfe3d393b3004e6cbc731ac166e34dfbacca4c6699967dd56483dbf7f4f6d17e9edf13fc8d7a717e766adadcb6d7e6c7e9fde0bb3df5f8c6d82c03468d0d573d9be296ffc74e0c7bbb1f7fe727d303ebcf752bf2965d8fbdf97d27869d9f7db6413965fd2f321fa6d3b25ff96fb3004e3c09bddac70273d3cdb0d9036773c28a0f4337895fcb567e58c18dac0323a594527ea1119c989898ee7e791a2ffcf9617eb5aa55af52a9bed008cecf4f4c4c0ceb5931ac2fe6865dad98c8bc3c8dca0b8de0e0dca059794c66647e62626038bbe2602a0eb7aa41b5adf2d26112192691292dd5b5bf2249e4f5b76ebdf0486b1d0a853a9d78e774ea3adee93a7669cff18ee4b4df78476e9ba6f18ea66519bbb4fad2abecd29ef2cee9b59fbcc33ba9d75ef24ecef61ec63c18df68df9ff23076f59f3cca3c926ffab5774fb24b7be94d76699f3ac9937fca637679cbe7387bfacedf7e117f2939a97a4ec5a99ef30fc9e993d244f5aa6fe6552a99998ff9f92b7fc1fcacdf9e6b2108baf6b9af5e9124f2da47755f68bf7e6ec69389f12c8cb73a72398ee338958ae35e5eec498a8d6deab680edd95b42a53e0eef43c5e3e4ec943f9fb9c8a30c9b7d6cba217d4d03927d614552e443fa49d9a40cbdcff695808a7c2acdaa57de8af960ed7d8006dd47639b5f04491874432925a0529fcab645ee7ab40ffaee7fd2b447c27d32d05cfb1aa7d99e6e6603c8741880dbf27b1c8dc3636e75fbf3553d9fbea755a7af764e11ecb3eeadfdf6c278bbdf5e3e36ddd46f1b6fcdc9ab39e7f9957d65fd4217279f7947fbd3b7abbf507b1b54a47941454d57e39a93af79bd3d12fae10cb79381e6729f7aee1e502f12fa2128a27df745b4efad3f24f44329f39bfc90d0f65cd62bf3fdf621e13e294d645ee6e96f350e78ee7e707c4fcb78d55b13c717840da07a99afab42ca7ccdcfaffeaac121f3f337d5b7b7a341557fdfa00ae6653c1c3532ab97f998d43ccd3391f91baa9f7926aabff1d65f333fbfc6b3dc52fdca5f377ebe8ca7fa1e87f1c2005cd557a47bbea87ff14223ac74431074bbdf842eb32bf585ddd7bf3d5feb6dcfbae1d1784c561f0dda0a8c98811041508183283e3cf3d9adfe0d1e68d8fadb56fd45c3126ea3c947e66f883e43d48749908fcca67dbff4b46f85fafd80faf2bd5b81ded63efec9930dbe62bee3623cebad292718563e8b85af97952ee60019e2cd3ed60af5d23742af0c7eabd2152e98ecfcdeb6ea890436f617951824109b7cba3de6965797df0af5fac73e415d82d82d3ce5d842561ffbfa19e6dc7caed7bbc9427dac3275006d6a6aea72b125d46fc1a873642b5df9d987f5a994a1f7d1be12b4920f957eacf3d15fdb9b9f451d77730ef3fafb01fd45b0fa2d18e9b863a0e2df6c1d4485c7d073e707f57cf71c9fe3f6e750419eece9f69f7430337318743d36ddeed9f568f693b3fcf9b2b080db31b05658c0ed5f0037dd3032bbead1c468cda6c635d87111d6294fb27315e8b8f543a2e3d69f3eec4512996e8d4b1c79e29491a79550ac6ea1fc0ef2e137ec5898efdc39a0629f8fed6bc7c2fc2abd96e26e5e91ecf96a5e91ec63617ddd0b5d855b3f242adc1a65bb3c85a0af0cd87d8a7d2cd82eb6f86c5314b1b4165d6a72f02407368842ce03d2164b785ec7288f36c6529c174497df89cbafbafc85245801d9380496b1ec807451dc391e6ebf6d918b6ddcf95d3ba7b2e06d08ddb0044aee6cbadb77b92f8caf6ddf378c9fbd772c047953d3edf19edee16a1e17a1cfb77a45e8a3629016fb045b3fdbcc96ed2dd333bd6a55c3744cdb4ef54bc746f5a9bba6f9d67a6bae6bd3c63a76bcf1fd95c586a561fec81ff957917f26c67ce10ae60b2dbfea88e872eab35fb862b141842516a13e2e62ed86c74ad1ebc7589c915fc6d58ddc754f0bd51f6859776466e62fbef65d2337333773a41f63a4941e493243e7546c6a571ac4d260fc8c1081e185af71aea051609b99397baf34f8168b62ccb237d2491dbb3b7667f1e5b6fdb66ddb16330f89c77844e784418f02db4410613771fd91ba09de6116e294ed88066583ee99d751fa8829ae431147252d0baef30be79c7372546c3f7fa1ac517af4b420ba31f7eb12b26460e33b958d19bb825d91f259baf7732fb179bb7bccbbbbe3f01a77f71befac777799777787797777777777777777f71fe2eeeeee38eeeed57d453383a326d3b04a3794e54e5d4a05f3728325a3847583663523a324060c18d58b92ee74ea524a2cead4714a68ad7453a26595e6f84e89ccd1a111d939e4155b70f4cc112d34976a15ba3e31724c3774a62e10601fd7c23e397450d9857dcbef3a439d4bdd85bbfb7b8f9618f6ad79365b11b91ec52676e4e3dec2cf7077af2291d8f86347b99b73dd845fb6871bb95c356a3e8c6b5cd6cfcbc894f8f2acfad4c7eae476dc5ba6d6128bbe88568b78426dcfcf21022e53c746452ef126bef64cde8a356b27f1e6f9c75f9e14562391c7cd8845456e9dc8976826262277125b49a9a8b97051f21797c8d35ac49bf85a70616a2791a7978837d1ad75a2d84eb468a9f9c215eb0bbfc64931ed84da3acebea852b3fb81dd1b344966acbea4994fe67333623a1b7c4b1a75094530315b542fee728975a225525cc7c4c4e44e2ceaa4c4a553e294b86c182dd2a2a8480bed732dcec4a5c1a8466f914b6e8cbec68d436057fcb05d0bbba2141cdfd960f943d7d2ccccccfdee9fbb96226c8cfc6125aae9192628fe4a713aa7c38814bbfe1b31c8db3caefcdd3d41b1f4b18fcffda6715bf65aad5ddb3bebd495479388669986f5b66958779fcd8dfbc2eefbe91cbed147c3beb0d2d45b54ca9e1e75d2ba8ab179f577e93289bd4831c62aabd58a777a48b768582b76d9558d0d965ab1974fc32f03649ad6f508b2e21dd495cf383f1f9006654c0f36642c38b458f98eb1a0c4963ddbb24cc5230a214123a5947ed635f6fe713e730dd2efe94a64bdf31174315a44f7a675cbe03124b1ec924f3d1aafa65bf235cfa65bf2376fff56ec929b7b2cff90afd86028aba1c807fdc21a8a2ef5519ffea84f7fe3066b8cb49212b726c1aefab1eafb576badf463cb4ddb5b6bc79ae6f0dad12d6eb5f8c64fa0975ff4f945bf1dddf2c7be70bb61bdf38b4166a928bd1d382028b9e8fdfd2bedea9d8d4de6784dc5309a2465e87d28f6a5d0a04c815d724a4c0576c95700dfc8df648f14748c6cc8615c39a473c2958e01704de45100dfe4e01d156e5060f0a5c32ef9f3637c92d1afaf7d3603a25f310cab7269cae0d421a961c69578d4f9e062d1d6f9d8ae4f28ba3f82f5892311b5eb9e4f1fa134189f28b1db0c341c894e78a76ff7f18412b2dda35efb6c72bc63bbbc46c0adce7e339c86387d7dfab1566befe9749ac15f40521f0ca8cf4883324a2cf758f730af7ad99dbeb3dffd0cfe02bdd57d4d755b87717d7aa95e4e24e097eaebbe1b724e9f0eb77b1450b7bae77ff18274ab63c1ef0a7cd371dc6f282f88f5f8caedfef49da058ec27e8af15b8d53d19b7ebba0f756ef7ec5de7eabea79fd8fef89143eef6e985ffe40569b0fb976e736ee66562663e2b1353ebdfeebbfb5902d3bdeafbf156f7b103d260f72727acfcd87d4c2452fda97b98d3cbab4e1f0bc54259d4dbd3a34ef6843afdca5bdd9f76b0a7eec3d5ed7e86ced1713becbbeed9a6fb8ac4a2d9a726b82d08cb0daa61c615ad8b51b4eb9f7591c84776fd6b17857cd4eb12099273b7813962a3bf69596c162bef64cca33d6fd66b40b7e847af278506290fbb280a7c43a906d84561f016cd00dfd06ae90b7449763d2aa5f4638c315a22fea2f1894486b0f6535f14fa58402fac98b7cf3e2f1fe423559f7ad5c7024ad1c09280ea6154413ed9cbcbfc4b4d7d2f1f7b2b4e919bac1cc795b7e8c378ff42e7d00869907e7b400d6adf4f8354c6041de01d19ec0698c79f3e911778277bfa30748ea66d377adacb08d9909788f00eeae96780794e4f5f039cd33dfd0e700ef7f48b70cef6f441c02f1d6fd19fd1e1ccaefa272ff24dfdce7306eb731e65b0fee66d0cd6d7f1178a932e7df6fada47fbeb117dbff42fdfd0fae9b08bfe098adddc7ba1418abaf523c22e4a9ffd6fb59f7ad587e32dfa2f40f653f56365f5b38f95696fa495b4ac6a35ab0f7a8bfe69079b42e760b9d90734a5c1faf1eb740e4d143af97067add507ea9e7cb0b171b0f10ec7925f5cb71ca65b1e39de9827e31b7faec1d6b2d8adb9ca2ed724d75ff4954817684a833cc5c636dd200873a3f6608b5f37cb7ad8254176a11a1004e3c0cee96f4757ae87777a08107f116100ef58805d3c14e01bf9d2524ecb62b51b72d6f9f00360b1f236734efdf60360b1f5fde7e9bbed39a0d367bb49e9c7a2d91bd9329a51a6239dd3577e922c56de21424e4416fb285bf7eb9efbd67efbee7b0e48fbad3f963b7d23ade44ddb9b8f72ecc43a23421ab1c929de6ae951640fb51091d785c9859c49cb1253176ff563515af2566b51aa4a995151d2512735166ff5917614c588e3a4d745de92d2eb28a827442dd44344714888a94b149ae1486bf196901f18881c252d49e9e9e000e9a95bcc13194e7a9168860501f601bde5ffdef228e4adf68e784b7a44bc253d21de92de4f0d273d203838e9f5e4e0a4a7a383931e8eb72427bdff38e9adbc253d6bc349af7a4b7a2c80959dc14a8f0501f6a9de72d90918f216c8498f05243f18bcd51fe4b371ebb4590103e946302e237df1837cc55c19666e7c18dee98f30ec8a32d4db57cb627d75e58d4fead7803d7d09843dfd3e1616560d45379c6fa495e6d785d2f53fed6081a68438389d1373c4079106fd67f7d3e0ec6910c7877f83cd4bb6f0c6652d872318bff0bbf1f9dbbfb0ef3718afea200aa101668c31c6c8aef81e5bf16f7c8c4a4a428ef4e0064764ecd84c8012853ef56cda61981c815235246717135309619c77c5894f66b0f20810b656cac4c474e4c81115143664a623a4e61df9354b42a6c1e460436682b2bd98c15c8cf47041709d92ee74c56e27948a092818daaa6cb02133f15175e94920423a8bd44b1362528aeb1893224f04d231e945b506962337f222214474705216757a51ca89b261d5ae62d738fdc2f9be756c5ec13d874d32738c07f0c2c2740eb78a1dc6b0c63f295629b4610392152cb0e189248c80c40dabd665a4329eb8a7cb483cc8610d185bd1e83064b208c21658ace08c2cbef0342a30450926306083871900c194c4134af8b0050d2c43284388223ce1c5510f16a5140013765eb69203130428b2f5b2951c90a04866108159c961e86e97ad3461a3d7702d6104932266a0841437306a1fb41449741835a805549a8cb6a8428910d478c2a70a2a866842858c13302142900c94a0820aa32fcc6062061a407409c3084e4c21c4141d7079820344d0c0062e6010c590920fc188043734f1448726286109679cc14df082cb900cb890b16404dfe25a18074c8c8104222ba46c9101ced72c965042b542c4e4486594016385480757bb6c85080b256af31bb513467d74e5092b188a201714c1a8054760b1c610429e250442f0392aa3890a9a60804614a38612436432a4022f24242424b4c589932daf4587036891afc396e82de4d19b9c6c1c5fd873830d13bfc8c3374c126f94427fbb51250ebd0f76e304966f84e996cb1b3e08aff2eb7e649c445edf912de994dd4a3d61e3cf2fda25fcf894131b8db055ece5639827035d210addfeab55e6d56fbb014e56b5c7b4ac22f19034d03863256cf62b599379d4c3784828babbbdddbbbbe79c18f6cec4564694324eb0c619647421c50bc808828c1b968ca0ba8cb434c6cc65a4a5a4251a2c4959ea610c36c640c21867c85c461ae34ad218518cf182315430c6500e4968a265088b09c4c8811855c49881182e10e30731726023e632d20fb460f9c11596062eb8302c38228816fc608805b76b821117264b142c2bc0a2888b824205b7a302e5724610714f3f5c19228b10f7e53252184a9edc4e8d2b29c88282ab9d11c44d8101c44565f1c3f51b5cf1214b0f17ab82e8a68a78b8f40527b8d5881d6e0784934b2f23f960071dae652387db3135b95996a1bb8561828bf10087bbe180c99d575c5992e5869b4971c5862c4aee160329743b175c69cad2c69546b071ab1057d6c8d2846b2f23f5a0c915266459c2dddab8d2258b126e97c695247041421635ee16c6e730c3fa11ae5f4602030c30ae706a051ea51777f2004540480bbe04549f521a238db45aa01f20ef6d9a4cc005e6f1e71d5a7fae1a9c5f6570b0212bfdb4108109580003fcd2a94a3ab5cef97702fdb06b2ae18029ac341f5c71346b28c5408ad534987d2c1ae96fb4522c64d51f33d6844666c4e6a45fa433624f2316e7b4737e2c6c3ef6b1300c9b139bd954025cd5ac583435362be660cc38b2e2332a3a02408d0d8b66b5a259ad6c914dcdaf56384c4c61f31d0489c5528079f855bf21f4931f0e24f586b4101d6cb8ba14c0b1d915c22effd313db92597eac39b1297f4a39a5bf5c213e46619198babbab7588191860977c29dc775ab86f488331604f7f089117302f89bd1660579d31e98b47f128c3c17182e3afa885192bf94f6ad147b153e2508348d90c6c4c3aba72a59d81893c1790e11d0cb08b89020cde0879626bad4cb1c8a4e9c0daa0209ffea98951b3ac9ed0f276340fb0f22bc3aa579dc60acb3ef533d495f5ce2484628c1c86b591235e926e1561977c0ff08d740ebc61977c29db73de06ee68d088c300faeb87d6c73a1fddfd61970c13ab02bf56de927f02efcc977fc32f06230ff6f209b023f2642fbf00bc739379ae1df6d26f1579b0afc53749270800c3b00108615c6a0a51877fe8b44efc8142ff40c17da0905d9f73fa9c523be9d0d9d9c8ee0737d8fdf8f958f7c39b92310c8b18d6da09094a763f5cca1fddfdf0ee47164baf96c56675326d6cc3c1649bc7d91b564d567bad734194514a83505c4249c02ef923881995523fab948619581d1d22f3669d8feee228b294526efab1d7e3587772c262f3d3316fd8d42b6a566b96d55a9bb677cd6ad0715cd771b33d1edbaf6aa66d9f0e8ef6e9e0b827b3a4db9e28875d591b1362dbb8b94d8fe0abc5add9fecdbf524952cfa32e62b14b3e0ddf14619eac12bb5babf5c3191b1c21754bbe94ec352fab300d629c7bf18838c58b4b744b7e0b8cc06a8c825df2e3137ce351a278378c464652fb19fcc57df1894fbe51f7b410e9c355dcd0fa0d1e6558aee2b27bad350bc08d4bd5ca0b53f10bfb6cb8cda673e263fcc2dea625a57c7e9e99e117b317bf5bf2d3b055e9a773429d2b5f2fa49342e7c897cf2e798212df0ba5d58a5d2ef4db2b1f8877fcca1701bf70b8255fca9608e057c8d3b15f401d060dbf70bcc53b4da441f92f6ec97fe1c89ffad4bb55e7006159527ab9e1eaa66eb7425fec5babdd0a7db5f7a858edb3a206811a940f34a48508b04bbe8f1f2a4a1e15cb376a58b20d468679b19734d2ce86108d146157e5f100dfd49ae5f0e82e07a7c1fa59e61969100625360ee1a0842c8f06eb6799bcfd5e03d8557f93c9c1cae01749bdfda83fbd4f57bff690c2699dd6f568b2213788a37b97a74e2bc2afd3179d74ab7efd803882f635e1d6df28277383fd10e7662feeebc7a1531c7a20feb2df0a25843d7ddfea22a95d8f9f25a8afbb38d460fd9313566bc2adfa462c0961939e685eb5ded250d93357351dd8556313bea99cb7e4861208f970acec51570af771675f1c8252638dde12ee71781feea3a269bf753d32da0509ba5aac7555ad91f9b1b28f393ef9e2a2938e43cd796c0486ce89482292be8dfad3671f89bacf501fe8adfaa78cfb2efb581a4bd37ed3b22dd3b2f756cdbefa30b06ecdadb1e885093892b20f549f94b08dc958794cff64171bdb586b51d7b24beee8168f6ea598a7a5e29d137ce686f64acb3b280cfb0dbba134bf08f65549c106a5ddd1a0b49db303a6b31b3b20a306ab538e51c29678c7256c138505ddd3a3b03f7d2c20fb27d59f9e7dec07f954d4a7b0477d2c20d4a73ef5d150c30bea837ca27d98b7dfc37c8ca1de3eca4e89ac18a3576f4d4c27059dce99f1e8d3c6e8a96e276f47b7e6470f64d7041d4ba1c1d9e3adf9123b256193db61d32d8f6fe6a901dd9acf7f9a73887d0cf08e0cf602cce33f5fc810dea13fbb1e18a555767d213abcd3fd7c149887fbf93cac02338073b297101d1b73dee63983d86b1e6510fbccdb18c45e88bf5013fbfeaa576de67c9fdf29609f0ebba6ce9d18b6f2d6c4308c6215a3d869074b3f7c1bfa014d9961a60e8abc314ab13232d94894813b40558a6f60bac6dadbcf2cff7eb4f6406fe1780b356def4aaded1c2d126141bc96659a963d517dd243dc72a795ba67350832ab35cbaa740fc889069f68f5b34dab9fd1da5d295b966173238e1b639aa6a27ca06e7fab2817a7ad88b26939750bbaa7362ccca79efd25e3431f27a394f60edaa53c3ab90da6e63198ba55ced2e7b1e3045a6586629847867a67784786d5b5abae5d8f0dcbbebb8a79dbad35dda230dd52a5f8867efd2cbbe8cbd86035201de407e41dfbf45bcc7302e7dc20e0456774548b3a751c10c60246ad2bede8f73418843a901d0e82dd0ad86b0ff3aa1798afaa52ffb2bd9156da364e92b9f483d0d73c5ee2a688b2aac926d53e97a27dd65e0c49e4bffc9e35bc722fa2b6a8546d58743de910d100000020004315000020100c884302a1503420c8b2de1b14000e7398486654170aa491280762140541c6186000200018628c31c814155501adb64ad121e68267de05dc66414b0192b2c99904d78b62811c63441cefdd086fa45d23c4ea2dfddcce1af0e3e2b9ddea3d2e65bba93f07f1c59389cdc74e88304acf3892d9bd7ed743c5fea13601808dedfdb5ee3a724f371604e9c3c41cdd767795a0debb90827bb90a030e1cc1dfd6227666abe0f2476168b85d92c8105e546b2f625141bc561002aaaf419ad79bce52906ea36db067f465719d7696e225044a49a1944f2cdfe99b65d335ff143e31df13fd157505f6c8d55161dfe7a963941dcf6f1784367056b8ac3af7f5e79d56a67d2f31b99adab1b456bcbb4c903d55af6a1b08d975cc1839f970c4cb19b8540775c70c497c8614df17d3c1c6d92d3c4d6a393e768969844f72dd9378672fe1767d6bacf8ad3187b109d62936792a4714f90f447c46d1e2d714ab200cd582025ecea6c41c44b72991bca4827d5eec13d91c86d14333279be7f2495e5cfe611bd1395a911d89829d4138382b157cd759061644b49a4e62f79a67d563ed009d4439372611ab04b89027da8ae8fc1ff9e26894452a0dbbe52e816fa154a9015b680808e3231f1967445fa0b6cade0e21f5af7a2a63c6f04da34723774ec0661b00eae40fa42b09e00adc0c738033ff5aa9da41b66d1628c3473c368cb861967c284e6ed6536b5c83e56f0afa8ef4f7cddc1a78472bb7cafdc0cd1ea4be39889d426dfec678ad6e36fe4714fb0764e72713109a3682a42799ba8702b186ad25858f05f5a92721cae4265b49c34890713baeea52c19ed0afc09751e5e6ef9a776299d9a453d27c28b6d2355b92920b120a556c08a7573e95b04b50e1768a299818d10d1b0c6c9094a98d6413960a1f7d66434bbaebd38f3365d2a4e280a540453a1f4223920964ead75e64f80f9c31489363b51bec9705cc492215a5821e42ff28aa40794a95ec20868a655ee4de7e1ef6aa196f3308396955d43125fe9c24ee23ccea8dc5fb48a9b02aa67ae73fc284c2609569dd126f259e793a6f130c229054df76fe23a5da284618ad2a2dff2317080f55553d41afaeaa8f31e88530de31d809fa84afaa4060733b7d1bbf0812ed7d9a4f721a0b43615996bf1a0bcc6c2c9be85ea43187100ad1db7a5cd23cf6d3b7237ea15d8920f8b6e0e4e44545daeab9aaa6a6ed7c4376879ae63d2a103dd7c5635ee02e00a1ba53d33c3261721a8cbb66166c7dabed3e7250b2a2a6ef6d24b56610f6ce4092ec34e08ea0665aa101b9c74981c65fd001986baac1f9dbe5571fee8f6c82a53d14e384038a60c7307d62d8b99de4e9fd119b6f92533eb4f10b70844a04b294dccdb7e86271bc4248c5c9bdc46104c2ad0a53bc49089f8ed65274b951adbe23bca97a17d3c55b2a3bfe96cea8ff886e2bdee6f48d59ac02cb337148f8447e94a314117c400081ef43b57446fd8ef046e1073101741cf7fbaa0f1a426cf4d4f6d3ce32478e9123d9a85d65b97488fb2b10ff4fb208463a19fef524dc6d95a7f517e1247a350b3c63ca17cc9225685af1777215bb009538521557088e156155fde15749a2fb1fda51ca913828e380c705dd90b1e1dfc973a3131b1fb72a9273e09dd2385222a2e495d4d3a53572a27eaa1238cd2ff51cac5ffcc2c02dee07d88becb4ad30ba7c4c04939deefa2503d75407a767ec217e38393a0d2ecf49581f5c84d31163c62fd866c581093f60b2fae4815d615ccc74ee1cab7492e25400f5556ac244f267636c9c4c5a21172f42a8d6d8eec256c4133059d5c53e682798c6086212a1c7d55774db521a0de1cbf3f0492386efe7d1829b3be6d50b1989f68af2264c77d78fb6a38d6a9c27006900141c17f7879b60a74e64ee56fdce02cd8d5c25722d2792c9aa1029170c2cc372bced2357f48696905c5b53955c7a659029067ae3531fc7d7743cfc82d23c48c4f99892ea7d184ebd733a70c2ddc33752b2931059f67e41a0075b4a3d17bd2ff0d11279a121c0060dfc30b1a449f612e33d5811f7c63a03f81b633dad03f9c8c43144ba623cf0f60698c9ffc860781e4b6f04f7cdb33ef6831aa06f4f6824798609e339957358a4ed42f6d777928c205fa88959fe9dec7f8532db336203713f4f21d6f6122f9eb016717b46bb9d8860ae03f2d212f52006a300f31944876959f48a5ee16d91baab620d705407c31ceb3a6b42f00c255d2f3e828625d3895b49675f695e8b66fb98b5d05afd4ad7ffb9888231160f42447bf7eff30819e938f6a52fb9c04363bf97f4c5414a13b208bacdc17ead0456093b7fb2369a244f849654e9cfc340c471993d1e3ab3f4915eb311a22d0cece2fa919be8a2597a4427954bb9076a0ae69b0469c6f4ba1c2e318db7fe076d243429592c90a360e2dc889d999d7816b9a63b122e48508aa84dddbc4d8426aeeca2bee81e187d8e2ec0b9b82e9a2c9180968426a6bfdc06cee6d17abac1dde03a9d6af0e0874d72e753e993263b2599500d08d944ab5564051a10a0e536a7a935297c985d2d09317920b5ea574b1c6c8bec7e6d7fdb98a68581de0e38630036c1409bae8df108a9707885b5cf0841d69cd09abf9996b5e475633d494e1796c700dcbd74d64ce9220c66c32b3318a4b1d38094d572a390f65320c24d4888c4645a8f34dc7b6a50fee352779e8d90e9d5cdac20e0515835cb03a819cd61724debd0383ec328abe6512ca6ca1d684b5b019cce4d17ce46e18a5a67051498bf3c42745579427c8cbd19fca8535393bf3c0c30d98f04bb8a1c99dc07da1bd04d135b11285200767ed4373f87768a660c91e51fdb1a3351b43537b613c1fb9897b4daf87bf6fa10f414709dd2eb20176c1b52428f071c987b563edd726f7db9bee413acee6ab92da64de4e204dcc74dee9719701c95c6e4a7dcfd5af771184a209a9e80673226882b136a71e609ded70c7293ad46c8ff9c02da7c12cb3a4bc537060ac28867731a823c30cb62de804a186026e937c80e8d8880ba0be97ba24805564deccf2c3189b4708e543a4c873322aff11800904e9f33992cb4e7d1ab04dd12cd9fdbedcad5d25ba2a1880fd567ea77c9c6fb1f2bd2f8e183f6e3661f61095d5e898811624806b71b7404e5dc74b1721e353576697b85e24374b78019f0682b25c779f32b527ad3ab93c88f129b3e91629438eb288db08a33af8d489679378eec838c52b407733003a8d84b998d7aefb39b1c8ff18a370f083f6387460aa09c7450bc0f9ccffad59784491efb640622727de772c5c53b83bfeee59482dfd0fe0fbd8df278a45fe95bbd3494825c2edcab25571d2ff195b756811c65ebd0ee80dd600de274489d0478a5be74fd39a8710e7f8883209e926300270effe1fec05672780d41612633cb6420debb15c6145af01e1cf8c6086f233dff5925dc97cd2a89d2737e74b0da8c0486db67243089c587de1bee42ec1977d1d457f9b75e6d988f6918108c7903495e191c21938a52ca0c9a9206bc47c4eba798be72f9126bea561470f21dcf76cae091812455de57f9ae30636ed4780e78c80b3a0b51ac20e4ff375b4ccc4461e33a8f9757b5442becc0ae8e6b5b44f0baa99036063b7a01449fc12f37561d1b4abffaf89c7c334fdb47ec4c8583da031303907e0c0ebf2822dd5a76606d278a1290fb30e62c38198f6223336d8d446d4a84f0bbd96ea93fc0d692e291744ef4a5abe72ecf1c701ae35ea15f7ce98282df4617edc6792a00342b698b3cdc68cc68811bb8c9834dbe9e0e80d0cd163845b9deb2f766c63b035027ca22d3f1f1dcfadadb8f41baf53035988f851911e8388cdf2e14ecd1836bc77cb2d42ec19e2fb8b618f8c753a78ba803d726d770eaed0d2abec200ae69c04841c15acf60477b213035f72181ab0df77b1950886d7508753c4fdb715f84de16f81a75e871e4c8eef68aa3afcbca306f854f66fea2058670914c556f22e4a605436e3c7711a6f46d5689f0857025c46f40d7dcb1690d86eb0ed6ef3fbe5e0f2e675455277bfe33b09faea9cdbd144a8ecc79184ae29cf48108699f0b4546079e4ae536a8ba24518ebd7590fff3d5acab0ee3fd99e4785c854bbc5fe7da3f5e51385d2e4cc359dc6c2b5c6007cfbf530e4f6badedda96bac0a8dc5e65acc9060fcd1e884af99d102840b2889fc592780161a9fc48c9ec92d1135bebaeab1e128bb0e2f5a2754031f01f99813b925df2c51916e2e5916f54534ad849b34878346003e9e8ccf0862e789ad4622e0f787d25ddaa0d32b567dccc81cebf28d4d01ceb64e80ff753e06a17c926f695e315c8eeb067b0aac0b1c17e279a274126463c1405028fd83ae65490e83d07acb41b2bba5c77791d0e30cac1fce9593115b6c3ab017f630febcbdeb4ab1c8f6d07a11303b00a2588377ca35d5e3249d05b7df5b3a7f4e9480bca93a37c5f3fedb1c13033cc30977e65451a3fe467460782ea48f33c064d1c205bea5ffa8c8062a29a2e099410c688dfa9a8bc4f5a2351bf3bef231a131067799eb8701bc28dc269b4d1ed39974520347759ec502f5f64da015297d3d44a061e3980b1382529b252f07a8a5250cea15aa665fb39567afdb3d572caf7f4dcaf12c9544ae24cfd6e58c1665ac9e7e1c2ad8d443dbc5bb11c29cccaeee8ac42ead27cad770c705b238b0194f77d1224f4ff98a3f5be76679f43e25fa36fac564ba76f7031e90c2e784569c8108926ff075cb8cf2724ae60015922a80725d5ca5e1643a09baad146efb7067e9775a7280d3018a3669f97a2df154d33d91e2ff7915a4a843e9101efc20aaa1a6aea475f8218eb02e890ad1c1cb35d3128ec83d9fc7bd90fe5a986c6357f2f5c8ca05a42dbfa326ab7abd56140040ee4b61880aeb680b262f26d892d77bb8201cb17fd3ea5f8856001f3fbc730d37871d2ce69bb586c81023668967136e0855989f92d9fe7e00fc4115af2a153ff6fc50dda5ef69cb5ca4449242229cdd65bea42e6b98b1fe19b5fd97e2cca219806b17e256a29dd21abf2c5a85aceca816ff5a0c047b9a61ac37eafb0745fed28bddf11e121364a1cb3bbbc25dbde661380e0e1b560fe8ac69bab1dfe152fd34065008520c9b786783b260a71f5cc0c2e2ea9181258ac2ed1a304f87482e43e6bbe10be1f62f503d9bbd6555b413880ac444e5950c55824043303e24b9a14e88e01f039367aa38829dc22721d41e9087c55132be348e63d79760ed90aea7594db9092374ff8bdffc17dfe3f900f918b50be49a3baeca61c67aa5ee82a44e4a4b03eaa10d417c9352f79377dc556e52fdab60fb7b7f6633243665d9cbc2a5241b08105b2cb54003d221bae9b613dc03494c0766d500442ded0e59de875a6c587ee0a74f2aa900d62133981d7054a017527e1f0f2a06c24b0d09d98d9974d8d5509d26f0d3c5afae09ac6de5252536db18b420ccc31649a76401ba124e338af97d71bed34ac4c443d3a945ce163c4a5626061cdccea9b9bc5257d612abdf0c8eee23157458c013cb859286cad6913651d62ac3d81248287f84b32b7467f315b964d1334658a30c4f14179e5026ec2c30e555529436899953c9042b8a196a30443e8928434c31bb0efa6f29c2f8be7108532c9c2b2b46c512afe309045c5c359a7787ef4fdc5a47e10f4a8b7cb995cef769a4287ea4bcc0cbf885d16451964d9a120287346f660b332662cc7b009228c2e8ea9f60038f4fe8609930924b6b08bd84ec56ef9cfc564d605c0cc00e265e7cfe14cae009cb6834f16d93eccdeacdac6e7badadff84e6c80581f20f8e006e0920d64bed46d7fae47a365d89ad71888580282957c19cc55afc347edefecd2522ea26e9562f84e8a0efec7f898c7f05b82743f8330556be75c0a83a32f0a3606e18b3ff9f21b94ee15ae92f9909564b4a664fa72d8ae413223ee34f5d5b3235e659f265839a27af614ffa38a62d4b7908e526cd4e302122709f68f32f74c44a55ba2c2801b66cb199a2515823dcc7436d4ffd033d5e1fa0cf433b2288df9a147dc9467920a29652fcdb29c7d717dc576943ea90ae9b5a393d6b8586d1fc1477b670ff67fd09b5be4022b7abac859474bd8f8f3d572cabb9051ca547247b43489d339d63655bc6d135e4da61ebabc149f9e78da150e02c0babf5038f0984c13ac07120f20ee09c6ddd03f0793a6e622558c994e7350bb00e8c2b5289e6e87320ffcfb3a3651291984486dca9ec8c2026bfbcb4d17507c28e255828ff01373c36d96c97cedecc0f218558981e52cc73e11745d12c98d7911f1c725cce10339321080c68a01c3e2e169e025ac61e52c0dd4e40ed7c9ebe735ef9c229d8ea7602e75bb6746c2147b735a8f6dab5a5cce634f36a737250853531be9023fd4d6412b2ea58c16d055735928677d97ef181c51d170a405c87bdbd0303e102542a9d9cc2bcf71b7ad1510d99a6e46652c3b8814a911bd16ed4a7d54e68bf20e3139ada9727a4e9a9c38266f73f25e37616b53baa7d3700fa4b8be61852ea87ac2866267e524a5f6ee3bb7850cf10c827bafc0e323f1eb2172333df3fac7db189cf11534610db4d6878f9488975c1f807ca7f89490b55688ee625de50dd8f723baa37d137d2cb7308955aef02a312aab1c241566c843bf93971b4a0c204b0368881ca0f66b5b9315e03b1c21b1b9099cc267dbae4c880810c1b6b327dcf250596c7ee5b245b8f5413cc7ec90a515690fb5ed3d481a4ae6f921227349940c5eab5055d3ee976c2df0f28f139d11f79e009ac4356c279cf2cea91f97cb5259350bf1d01e415490060ea217f56038b5eb9720beac1f0a1580333275cc8321a48f3f56cf51a4cde698c6ee246ea9720e33741ce4896eea7e1064be8e5f1af395463b9f5f6c897e46a50beffc65d9de65f9a2089e92ab7563fe32447a76dfdc327447f73c7768904d29ea15a8fb875b887ea37cd48faedccec84b239330201baba091952963db90c6b647284ff145022c78827f368bcfa805afcbd21dad9bb5038dd18a525d890d66dadc241f6f5f9db575cb72d7aadf9226ab13f21ebb5db2531b4ecd5c5128f533a23b6a0f316bcb43b1c1c9cc7f9942b4ca940591cb9e30593dba7c433808c4f8be55c8ae40d9d65665e5e62aad402a16067347e61199a6ea79cc66469f814d222d5ff2bb7c9b8c57abe74cfc6b28ec2ff16e5f25f651950b8030be98a3471a2526937a7861824678cf2ce12598826f90418662e0bc08f16b98634d494152d84ac034164541dbe4fb9cadbe4fee6b7cc78068379dc47ea9c254c405103b6043ec3fead90926710f3308ff84f1136f67c325046fb54929860fef5f78814810def2b736ecf4905408dfa1edd3b5de549bf3f1a4386ed588914572373e39f97624c928c7d70ce2163b877e832e42e37d95662462f1a151f92456c6b1376575d94a073e27e88926e56164c9e080932201e22870aec4f0c82d17db623ebae21e5737c31a6b9d2e8f7100f88439071749e7fe15ae9019e9ae42a0f1fde6566a86c70c401fa9ff88bc6c56ce0d1291dd59ca6af157694989109ab1135f55a0bf4a48848a84f530fd807566601257b5c69e65a6a604c1629fd592047719a9ad3a62a03329ed2cc44008eded2255f5788fe8b5aaee082198d2b9a1adeabb68c2653b8f97e223ff0504209cfcc68b282f391956f3119447462097f8f0ba3f829baa8a2e753b3998e04b19eb69768d834492eb6ab304eebc55f81fbbcb106dc30f6c73a519118cc5c727402f623799cddace960f82405c571733987fd652151a3e2dc672a6c82c8cf00fb5b90d120ea5bc07bf90052ded24bc664dc2ab48b011b8342029f269b27c25ad618cacd1bedbb6b08d15389380628fcca383855077c9f05240b84a5fe97bb1953970691b0e8d2390119a1fb73ea4e2a7ee2fc69a47de3be80ed10cd7bfdbdb14595a4812c1b73cb5d5a5ba1c22541ff01ddcc21637b4c16039b5ded8fc5898f0fb2a61f8d2f8d4e7997da2c1a87d294411c4841c895063d1b18d2bc9b0dc7622c8eb38e4b6eaf2ef320c2212517cb0355b225730078d29efd51fa19501929e8404d46243906e2e2130820ee00bd1d849608dbca4c3a9e8af6c44e94aa48cf5654545e9ad7dec1694481feb72680fdbdbf95c7b5ada979c17bafb3627669d6b9fd619aabf60454228a10a99049276403b5ef266d8eb8c6d72d4ec508fa46480706267d42db00390a997cb11ce81dbfc0f933c6269fce244e40d07b56c8f0044ec12f8a0b04f841f5bd44e0d78ca9ee0f15fbfe1f46c4afc03960b83c37d2f39cb0d72e32b1f8f4bf484e0860a68f0d8b3c41ddf909ac4d5b3d7c4efc8b43203dcf66091b18d51d5571c1cc96ad2d143b0af68bfab3e5d4ee37684807983a730fc53308f6defe48bb53c323e088b6577647305e24fef966301e90a8069f6baaa0b8f8dfc62ca6e549205fa498f73f5a072a63fb44b9a65c2c9671d4e2449d69e6152e8a55413f93f1d45fbe3807240fa2aeb6f1dd3b012952a8cdea136be7f017723423763dd2e089475f1cc07836e2257cfe8d6d87ecb14bd347803ad37dc04bd4b2373e530869e3987d28e0b90da2b6e8d11e6cd13e2624a0435dc5918eed3963801269036d9865a6fcf642e7879d00c41e3acd8c1eb2aec691f6f09175953d90e4ea56c95e12cf98f2223f98da000b884322cb57388bde3d7d37f2fbdb1546cc2a629a8fb19cba1c6e8096f901986ef2c866c542a7d55a36a6bc989714cebf4e431d9d4e22ff30446d7fbc0309b5031b1c02f9d325d223f547373c4ca349e14e85d4a1b3088d1c495b2f512eb8cd15c2b4ea598736625782d600b2ae7656bd9342e3daa030e11f6b920538960f0433605e6d4c429c58e5b5ed7bfb49d1eeeb626c5adb2a3bd190add64b6f9dc3ae55e2967fb870b36e23b7c15a9ef35868843a7040a0d0fe2c8c274b532400074cd3e7589ca6efe4e1090bb5e41edef21799337a627531afbcdf552abeac42877bbb8dc9d4676f5168d0bfaad782f8cfd708ac2ead7ad138cb686279f1fdf58c2adee4230e4922ff48ab7bc062f5c6918f6a5c6de614613e9845d2f0077fa24f9d1216161a06adcaff4a07ec9fe0bf3a2c5fedf35b591b95d4d828f73c0e7f59cb747b99dd3433e5e7ae32e0ade2052abe4e2f48e1b05c76d2499cfcef01427b00d67698b8c733dc2d75cd9d3409a95111c306b748692c9fc74350d6799be551adaa09f1ad6dcda31f5e460f67e06d768e40982e7c212b64c78ce98542022fd9d591bd1baf2af3a3509b3535fe31ec1534b6aed8d163f98515298ab2701dc47f1f8e09304514bca92271f6bc8602f2344424b6b410bb69a5d25195bb6d069b038e72e7dffe79f1a0291759d5af51869fbba2e0cb6557f1cfae22d5c38aed8f33da45e3d6bcd4fcd23789dd5f9f932c35b64723ef8a8190638c965735fcb400bbad5954a73a8c234c889bb84260ab13d138fc0263bedbbc479fc2d424ead3a735ec5b0801198ddb80f317dfbe84185ef5f797d5d72b4f71128e641cab789ee105cfb0926a4e1a3f9baaf81ef7204ac1ab00b46fbc9842e6bdd997b8a37df849ed4f629028c36dab6c4a0e27780273135f619abf468d8ea3a25814a8018c095d7dfc0a0172efb7c644cbbb14db5f8333d0a7bbd70a52ad448f0384cd77a3f94c31ec258a0ce37115eeaa6b62fed5c8123453809a1c9fc806db38a1541d04aecbd593c6b1e741868764c5a2be5aa9cced303dc9beeb68e7fc96a24806f4d2f280266d6441fc0224b8013194bd13672fc08ace2cb7e9d8980e600b48298cd5a82536396eeb9763cedf26a5ed9196bd43d8256c3b3137ce8115dc16aac3b3da26bc2ab9bda444d3e8dca5723d26fe0cfff259dfce3ed4e52f89d622c00da53bec616d0ebab2a57679a4a8b308dbaa51107220787df9fc594871e687d0bb91b84a09f0f852e130c5298ceeab1d2296b741008acc500d2860ce0915269fd47af8f2426765090f88f0aa002935b708a26a24d69c34cd679875cf46442ae65fb0b6b7e0c85f7784c7bb4f5a60ab94e4a36b8ec67b913e3d663357bc76370accd64011443fa9075a9ca02e057292f0212b3ca46c4623f62b16dae1310cca836227de1f3f0ba40ebb4eb0a4ab6602343222920b151e9c4011810a15a07891df8552bf72e6f46af2dfd45d79c959d1c436dc6b9462fb146636125915027582c380ebf73ff2b8b15c4b21f5b99c20f88ddb81a26f132129d80aa81fc55bf1f29a6f0e4e5c65bf4a1d4a0fa451f7ef43b950a357809a10c1ed1501fb92e447ab44f63d4280ad6a45825d233d36c5c670900d0800ae3e0a84454517d6bc1f0b675e01689ffc156ec75ddfda8eb165d41ec411210803abe492e5762f10b9a44d2105d304dbab22281535e5ff570c6d6c398506803328abbc52fbf5767e510813d60c0bd1d8880f6a88b0fe38c8435d6314374378d2ea937b345e32dec1a0cfb0c64638ff8ec2e832bc99eadfc949ec1a97b43bb51da987e301e73ffda8b2ecfb60ff0470255263069a9afefd7c75e269c8d53f0dd46d6ccbb46069ba65888e6badaac8978a31f6724276962195df087fc6bc8469a4ae1790419e2309b486b830f7fe6c287e366db1c398976a79c60c23b7c94ed66e92f54bb61a1a0ebf6a041d011c6cab0994fd80df84d6118e080f072715ee03402bdc9398f078c494144629728edfbc61e26d6c7beea2568c0fccba7712839197799683e359db3710b91ed5f2cbc9e562fc4e1a2d9bf82e69c065b3b29ac139f03ad191fabdb544755e606c59807b6ce4ad4051decf223d840e4f63b7d0a1a32a79de8f01280c02a9217855d88b7bfdcb0f8416432b3539da890547adf85fc5cd367d9db1189e6c79a1bddb73b701b47c89d971c8749d7ddfc708959b6991af53cf421c9d88a5ff1a8c041cc7e94b926f561a859dd417c8d7ce169ccd495447322793499b97668088d899696344ad964cd94c471f80f97ea5642249afbae784d8003a07ee8f588253271f1eed3bd520722eb4fa2eea55059ea65e34572c60fa52489a4e31c029a0f034a1d449eb87d3284abd77858bf7910ea8d0cc0b7827a976b3908cda0a84d632c7ee4a5397c9856ee019f8f93fc0dbf3f374b2434789796b181d99859d3e78045f0898bbabeac292c9a545a931df70e40d3cc108ae2ea732bde581ec67f91cd860cf94218ed63941b1bbbdc4fd443a5b62dee7ebbf1f00a35d1737488a096ca649d118a1b4d3b9498adf1a3d0a72afaa62821639eb7643eccb5934e8354cde6328608204703abd6c8d5c2d26e460381bfacfdea6ca2abd188b4f1a98a5e8431f228e4e82658cb6f2909c49ae72832e8ebd09c1c2a0a588bf490349614ba270fdb94388ce49c8ccca1e2b500804140f54409267342c105bea3c6c787eead27322e8ca09703b7413cdcf6296ffb438c6ae1a6c4620cefe2dd0b16dd6d017ddce8457134e2f89d58c06608bbe299be9dd91f77c028813b6cd53f6debf885c5d45685a42d035744bf77cb128c4b0fb85749000dd0ca4671b26a9648fd3c8fbfafc1bfab8be9ebaee200227bc4810dd6aacd9fb6609ca331e54b5303b58838f39784477c80e04aeb1a1fd63e7fa6568a434d675854cfc190e922ff5583d0e4cf56d8a50700a5b1f67300b8f8fef2acf72005a950f12153f194cbaaea80d5385e9236b0fc53260aa582e69b4c3f9f5b0e6a47e28e780b556eb6774b1f75697f0c45228a60bd608a3285395b9f5c9770e8a5c11c8afbf79196a854a92a0f3e007ca8bf14964495ec65eebdfe97c592c509f57fab9624a345ee8168e76a430992fc7925a4275aac61493bf12f7ced7cd3a14aa7c8dc29266086ad7aa7ab69713dd9fa418fd0e5a43525fb1f68ee103df4f52a6d29277d51feeeea86fa830366bf0b8c6fa5571d881a7e82d4b7f487a654152f6d6029d504dcd60514232d84fc790a245899364d944207c8cfad8b9481c41508e11432bbb4052abc4912e8c4fa20d487d1964bec0be49224d60508e5afc2ac903e6389431276c6cf4656be7cd5b53049747e5bfa2b47d50cf5b4756c87dc9af30459dab09fa931fc9c6362339ad3c729ae13189ad538c8097f53267fa52eea1cd7e6b8dc4427c36849da699955b4abd18d3b10ec3623922d495487fd38e540871a69719a77935618f66795442194fede38075d3600e6df797f38ff491e753bd31af6c0f3cacea884c0b62d2e308534f9baa53cf4f3e5230a1dc6ab00acf66bdc6a44760ee75fee62a1767184c852534d5f02e11faca1429800d2da226c42b82e5fac89218067bee9365942ca8f9693285aaa98290a7ccd55451b4b16d0fc17a9f6376cacda42282939115732d20dfdb177005c20e7852a4b161d6e606ae2f88288556c41dd19c2656b546a0e1af15f5368f63290e3f7530684c80f0a0d5cca8c20d0c64a6723f8375283355aecd0230444f6f2a3a5b156c0707b2635d948ffe2859410fcb05738007aa0e41cd060ff20c26e70688999e52c7b1dba716ccce36dea3d70b3e32e43a5cf047de3b057cdc1870e24f15294408c659de16cbd937e9fd7e20de08df9f7b9efa7f44dd9472a8b6a1fb9a3ad6e63eb28fa6d7a82af61245a5d9cc21d594eb670bc70a733c7edf70a37de7d861b0943417285ad6b0f7c87899235a892bcf25a37fbc12012fe0d24d2c019271832ab12589d0d0de81914ef9d89d32619862bd7cbc405bd0881e34267976cb18c62d7f6fe6a72609ee8c08d197f801e1b472b1b870ca0515125e4bc0247be43bb959925b41055db1eaa204d6529dcb4368c5d004ccf7a58a350d93a81a91b4f866489865e0e490cef54ffbecafd5e6ed8d3bcacc32a2c7d08c9bc9f98ce0ebc6b93518c87e66046b0290723c5047bc773cd7751259e35ece5c880b04f85d4ce873736e648efe11bc1b823a07a0ff8c6cbf15387b4dfa0fd0cd4568450cf468d17d01dd01b417d705b3ceb5d86f4809e7e944382e532d99288ebe023480bdc17ba8673146075ff3031ec93ee9516c2a5800de32ec7604bd311ff959320b7dd88de83bc20d850d3425eda31286533a02cd628228c8e90348bf749ec40b5d1716c7e6d63e3a0be77621d86bc3b3a3d3ac1620065ef4e25130215a49ea8312c4681a2c548d8d9bd093366d3c923ceb86320b56ef84d29fb1691ea673a311a883e16bab6cbc5d5005e5311fff76d61b68bb4a32bddd3f9fba7c509e04fba8aee29d02ba40ffbd2c4266443877cba9dc18c1a6324baca6a6b54cc41b42d11647d8917a260fdb911d5872dab819f72ae6f3576c00e3169ba0b7a2dd06765bb67df0ada8a1eb412527444d27d3f13f384249ed547b85a70d8e6f00196d3e19b44dce1f5b152463de577952cb1238023a973ea392ed895683a8ff364ca5ac37c06fe436ec91c7e91363dc4b0fa194012ccf166557804f9c12774e1dcb6907d2f262a4b0dbacb21e3d984bb725b3e48298103c97ead4dc958e5925bef578d69df4cc37b97869977947d3f49ba071cacc65159dc11837e50daf12e5969b9a52ea613769a65137ab11bc0d15eccc7d7eb98c71173f5cd548f06eb0e8507ae04d7993b34407d389b0a046f24e192c012306b9ab5b4c9497985185430ba511aada683db869352a37e93948b2a7d75a2bc72588d54f13ec7b886a558258208e4005e7a0317a721a57fd6d2c4ac6bae4f1ecb93fd1dfd72debb8f7da4b986d26aa055d2d9e9a1b15c89c62097166f2cca5b3a133abcdd152d69d1f13ca0b4708c264c5d7a298aa5cce299e281ec9c95755b33a1c3d7a046d78c7eaa14df33458b642a6636cdfc0f925561f68c1cc7e868d2d31b1e5d318e419e28d73ec1a206cd694ce0cd4637956caeb778630bee52691252958def97a88f4c3aad9b29133b3502c73de0622b8cfd9e6539decdb3f356040796bd8c9de8ce0360969877b531636694a48b063b8f350a503e60104c598f2d42d2b6ffefed4f83e4086a7fa7690b095f867784e99130efc98a39f8cd322714603e072f41cc2b49570722375e631a8691cb32fac31c84d7a81b490326246e5f6ee53ff2cd0cc62ccee594191a781e3d5336d7e62414c70fdce8119c04507bc741436c56eed8131be3bf04142815e29f05287bf5d88dd0c1ff041428adf0500f7145225d5811a6a8be261118c32af7549e53f4b10727da65702dc41aef0918cbc8d75e20ce7e46c2fd7e7145e2efba17a3fd5e865964119a2785102c1a4fba30e0cb8ba128ef9c7a3c105fac94e51d1a8c47c90c093a298eec3b4afea3144832ea58b6392476b2121a8ffa50ca8d8bb0670db8b8bf39f59d71c67b6c1908f997201f05d1dd5320ef3fa889c07809a87005eacd497a6beb9e40754d45411cece865cba7ce6cab1d0f5c2bcc6dd14e7cc8d0fbcda8f446893948924e8e4ada01d87f81fe586edeab3fc61c06a3fe889ed179c01d18d5d18d2ca2ebf4de4266f1487235dcbec04c382e67b7b96ef263cbcd0f5b8f5eee165a5e307294f35ca0b6a470ecb238880379efcd3e57479b64b06c7436450b6dd38353709ed7addc9e61dca4a69349350ec90158c7056d52fdcf2165e2cd8ea06c8159dc20fb1aab5358f186f8496864aa2fb7f005b0e68e5ea7d58ad596be96ddb6ffa8d03e968b2c599ff49bff40ffe4a100dab2e0a9af88c8853470c0b0be4eb3e44d03decaf0c2b3e77f3586658099743074546596ddaa0f76fda9c4576f061cfcaba691d20d34551d89838c9f287169bdf7a4bf6bab4d845f295b1759ddf06cb72e0349af25df4e61f83407bf273295f84e712fdcfad67d4700ee6cfba8da7c724e0280c0e8b0bd5e9d94ea5ebe3e17a537ba9c0e50d67bb3fb4e4c16f50ea8b649a343cbe72b77dbd59ec588bf915790250202dd10f3b3f5a50972bfce46145a844fce93e7fc3a39bb8f5f264b404e0ce551ca7db9000ac3d471939e1d78d29affeb6fccac41e45d7d96559b1735cae8cf7c7e32787ac64f1ee0e073c0db24b015ab3b2a040c13fb4192420401a03bd87f85ab8ccbf80c0f825328c1db39c105b8d2c7ddbefa6b69cd34d5e75f828f543c76043dab30ea62387e6e257fb20e6dcc2e0c8590fd3bc6422653a582abae4dd1377c1cd6bf4a2fbf9833b99e91e1780bda61e32a47450ddf17b06d347aff92d1002e27f177b97751520827491b639036dbb483a0260741e109786e48b8c1938126b895de91ee94f073103aac2a3a58a092880cb739c1c63990bbb160442a3ea3c62822c92e1bbcf5ca26b114a958abc8eb6545beb3e93e13811d69f52c6a3098a336a024881eda852369d6f069c73edb6142c63e1d67607007b6d37ba494603064488c2daea01d2b542de8252cc39514caad8a98241538c42d959de569c1d2043992ca71ed59de90b91da4276a68a9517336254b041d202d4f04a74d3667d95d91e101d0cacdd3355fe86abbe8f112a05b31216225b114f63ebc9204d051d2d54657c80e661124a008d546542d2876804db8da86114099b19ad230811972a196bbc76e1bff4075832ac084ce91367c1114663bdd207c10dcd7173d7a546cb41df09517639272d6252f3816b5df0fca75b5c4f20c91f11d27b70b9d6685b97b46dea073b67f373cef11aee84a07dfcfa75c162efce3b33b576f50e490717440fc336493572651ed9e11b947e382ba64f94f246d31b51f82b02feaa8bc90c19b6e19edf9263c972dca1a4cce2958cc9eb47ac4e5b1d3955b8509952b50c7206e1c58b711a3b1b681f110d3e6d5afe66c8b57ec6d4bdb581cb1e2d6a07081b6d004bc7562e5ae4ebd8b034407c6818a6f6dccf49d99c77e7b2e19908c76bcf3ec12b3376af1385165842115ca7d4162644fc0db3354e80124b5c291206763fa8c50a3a19a0ff051e0d46afdc5e8ce608ab25032d760a1582320769b439a3f958fa294c79177a4f57c85992e6f3436e23cb9017e935732528fa7661829afa83372d51a7c63e1e946cf4c9e62260743436ea102941ae82cba04a9ea739b550998cc385506db268a9455c911e0a1099f755b167af95ca0b468a8913e7f6bd90809aa429c7d68c7104577883f1c22743ef09ec7395f5b533a8095be91b7c9ab6884769131081dfbf48ef8bbc4076e824f3ec51b460b1911a8cf3eb1dbf14b696738203d5ce2bba17f45fe1b4f14d02061486500a292c46becc7a688168df040c9ab1ee8fc6d4030c3e6f06e851cc54c881c14a82fea782b1315b22c7e3f0a1da2e5db0942dd642a7c7c9940b4f47485651d9a6fbc6cb86ed4f5f1d8d99ff922828c2ea50270ee3ea4c864cecdda00a894bdebdcce2c3901ed0dc601830aa276ea90198204146accfa749a74ac40d5b82a7b648d8c36d9abfa31146cdb284a60288bfcaababadf938f67b1bb223a1bfa81f0fbcbebd24daf4ba41751fdb6e4265c5a50b07ce69c4f2f3d96c35863d4445e188acf83e5cf76bc841596f86ae16f47e8f8cb1f57579c38b976e7a7386e8e6850cf778585e553a3945e61e54050923ad7eea7639ef52f26ce0d6766887c1f62202a1b850ca3c3b44a77cb04563b30ba57dbaf2e29ff450ebf5d60f83379c5af0e7e4ac01de828db26c4c2f5c11d7591706c756f2b6d1098b852de236e555494090d706fc11a3f4f9a2d115702c72bf211b3cbd8f052044e28f4f2f71c27711916715ad96cf035d1ed588efd2d17b20b02ced01a87272c05cfc99b9fdae190b3b92357cd5e4051d5b2bec459428e4cb0ede41e848a0a31aa809208bb531e032aa797e4824596da07f99972ff6d6c1b72c911655b01530221bb10c2b953e7a1c9bc808e751fafb34db23cb349ca12bb9f2833ab1ab5b1a51c2da99cf600917e6264060e10d2db35099904f932622ed48360ac334c7788c3528e668b152e9262d802cacf413df119443727ec3effea4468662fa6c6994d02159159f0220b73496906732ec3c4ca4c9bac873dc90c479ace8f52b71ccb7e549494666658251ccf8556c7adfcfdb9655ee704514a262c1076cd70929dce25c836573b069cc26986c3c855824339d761e4357d482f5640c956a733fe214b6c0357bc247fd8e9246eb9a256a20d5a10bf7b0deab4b241a92618c793f688a1f36b0c2160ea2b2d41f66678881af4dd91058ef52f662ad0a49d75da4073814c7c601c230ea59d524fb8f911e0a6546af7d6c7288c3da58aeac4e1abfb894811e212a2f9f541864d249c4332429e0c0667e463279474f277789105b5ba03273f0f319c7185205b18b4f073144a1ed081c9162ef18d71c652f74befe7313d130941952cf4312338f095163075c91785af6c8229f2c609c6909e6bbf117892f9794b67514f2c8254fdfa5786d5bf042b9922ce2ede45dfb6bcc6e440e2aeed12788c349eaadbd22cefc184c663b1b99ede3681cc52ae0d81cb51eaa92636f966ccca8c7177fb0a21faf331ec03e29eb4c3edcdd5bc2e7db0f29f2972668872cddeb0ddc7983c7f851cb6636bc52acf62d283689e1e66284e079ee6d3604f18f8125ff92b0dad007677a43ae102dc6c21b4cc31599e26d03c5b9ca1f1596a631cc2d92c13d014259da4ad2e027bdd2cf25b72dd0dab9e41995cef1085c2e8e1846f3154a4943ce33f92ba6d1a07ce238606c1c4d415fa78a046d7e0df504df6fd099d112f6c0c1f19b231d8c628f450668f1e31a7f2c61646b6c63c3fdc39cd49cd21853a22d40c66a6e5378e82de2ab0b477d1bf8d50f0081e4bf415caf82e04b895aa342b9e4c0cdec3ebefca5da545faa162628888333b7e32c0bdde0c1343f62ec2f9a2fc23a456eea48de8cb3a9a2a4085821f2c7f749c27d2144afc426878d64829245fc75edaefaa2dd0ffb94809272fcb776b2568c433069df6a5c64d440f06516539e2630cd50282cfbd9151695da13950349541d22b29fbf936778ca390a27727165319701ea0b8b8418f0b4184b4725cd3eedafeaea12984cca61a44b77dde4195dfc8a2b3a9ab1133bc251204e8e5a24da39f82f2c64ec3ba41a7e5ec7bf828440be64073bfd17448438419f0b6399282f2a2d4f335f9ed7d9f748b379c2faf344abcbd42e3a08c648a4205fdb037ea9dc48708675685c539e9c2b48b9dbd38ddb1d9033fc41311c35b9ade22a86dfb93415cada5646b6c67c3aa017ab5942c744be4a33edc089d7bc163c224b0452f3b7dd495a10eb1d480b7428d6dbdca88a504d368f6125ac15350d6440b60f3a0a10450858ca80f93b44da4c27a10973cd323ef86199932585250a6a3e3ab408c035814190e76cf93da062b5f087c8bfc22fb91edddf66ead5450e2ef9130068d78f485c86be9802553e8e34a70ad2545eca03885633881b85507c65bca2ab747b2992bd03624b6a5933bde7fab0482b16282171c76f6fd013b2c3daa3e7d5174bf8726d2c3f402cae5f240f5be729d8b024a95fb49ae6245f6f02dd49fa511b5de45ea6b4f0d8ff5d402193912b3f96999b4a7fcbed572147e0b02f1d3a78f1cfdd6300f93b9c4b18a64efc1b649cdc0cf873692ab249306c87e27d39c136c828b94163fbed501f392989ddd99c817503616ba9c453b3b8e4326516407572dece4e4c04dbcfe9bd76face522671f1318415ad5cd38fc7c1b1972199ab7e90fdd6bab8d63d3613af408f6acc76b054415c06050726e684ada91064409aa081d145761ec27d891df81e195b0e08e8640b68e126acc23247508b79e7e9cadc730a3f451abd483f01cb6e938758216c0f6b1d6e7b67d8a99acd8c1ef1dab92d2d14c977ecec03b872a74f7632a5e1e318749d923b9f8ddf0c0ecee5b9f0b1774b7b81e91b5fb1d59fcc13c0f16993cdb78a2457e657edd9541476670d447538be79fefe945f0fedfaf24b79b030ebb1a16536bd66b87b23a3f4d763b8291db1cdbe4ba366ccaeeb324d91b78a2f533bed512a63ce35b5b9320b643214a6f2c69c45535c7fd1a3bd0b3495a59e1ae8b2f85bf3a39f3efac8e3503fe01ace8c5fd565ff5cf207620c2a1899148d9ed0816bcdbb2ab8574fd54c84d97cd4442c0ee14e3dffa83e65df4b17607ef01dddcbcac52c88363913dcc2efa57b03a6637ec44a8c1e252214e728d915c0a7184b632a3e4f1a92b520c34488a45b57a27c2e98c991743e0fd8f916ca874364abaee5744c61f4024966ccc69482af7d817c4cdd00369285a7debf869fe646fd0903753b1440995a3bab952f91393f1401d6392fc643e213146ba6cc79940cdbdb35310d4d85382a2c1205e528bb24263e850170540fc1d071c4223b441f1eb8d783220681329d1c25b35480311bfad007d8e9b85425956c67b19e2bb4e62b0f6ec495595b37a96aa053d0b7dc54a2ae2223e1227e8fc3a16229e4f9a904103e2376898c56a6f7a032fb17cce48c298faaf103b433e7edb7f4222a671a47800d25d0fe2f39f7db4aa0119b7166a9cbcf1abe35869c52f801b803a759266018ae3c349ca05756310a64203f48905146a81f8a3d9cd0091a8fa7cf378525e350aef1ee3876289b166c10187b9126d8af1ce95b482fc95d47b29b11bf70efaebc56ae3378d2f9b3160907c76f80ed08d5ae32628efbe6f216cab582a0f693e8bbed4236c06aea5d886c3d4f7195033546dfc22d731aa95b038fdcedc79f827b4567e0de2d7f86c79e4fd0d491165bef6b031f83e2078847b52d21fa6fa6975f37cd0e50f3e25e893f704b6f06cba4e4846580f8e70590cc30660c596bd291935361fa23f00803277514e5f585709a71b88be0d5a2b051cc41a5135402416e307d5081959e85af76050b453227611f6d9ddd028dad8805a3c286405eb92f010a8654a7c03183c1bee9173a43bafc2b0cd34f7018360fbec1d0bd46f215fff365159de3047f461830b66b6527eea31baa317a4b27b24ab6013d434467cef156bd3416aea227b5386c11a18768f51f45ef9fb511cfb92dc127a24336c6045d3788951c6fb2c2ea14a73206b7c69d03a2991f563a9eec1fae7d42f793b61295862a7028bafee1b9d5546ff60d8350e56ba071b7bec964b30f4e0008ec1849bd088e1cb28b17366a110a6b39796408140386ffb8d4603830dd32cb4117f70da94ddce89ff8cc0fe4ae0b3355fb90b1c620fa6d85c3b8416e8cad051825ba064cf27979bed061b0132ada91b9f19259f83e5f419918b2f05695e690b64343c382988ebc69bffae31fa57d1b713111465030aacea73174d4f0873094cbc96817ef66cd4eb81c1eae442ebe8792b388a68062c039d16071cf244c4f191a07aeb5a30019c2c8d6d40facb5538438d0c749e5dbfe78487138767b40b47f499790075b8a5dff57c70bd67f03dc5f73a18871de36aecfd292bcb0a89b6c0e33ddbe35e6a541904c0b8eea67145f9cdb4fa0a79d7a0ce50d812bf579259679360cff1398fce1dc06c4599a00332959a472a319a46141cd07805c010c58555e054c56ea14b6087e11af74c0f6b99c82e26cf88199b95e03e0cfb2cbb193dd27233fff25e5bbfe64f9863c1118beb13239fdbe3500f77e4e02606e9c7d3191b6cfef270462df39dedf9430cd093d0f85ed3d0f22b457186870c036cdf239b5d177d05d79dd4a761af2ea46b3a50409e56da306422f4b0162347a7e79e9468c192c3ef773fe6412629dd9b6b0ebb48335deeaccf31a17bbc45df14f123bbb6deb3dbf854a3fb0037f6c88f7c3482bf8b4da1b802ff1b8322f64cf455f2d0b10f9ed120f1c93dac20d0838de107eb9bd1318a61f2dfd8936104efbac5f6ffb673562329227240b2ad50f65441ee15ec930dc74b087552f254753f9ecebe2d63bd77ce6ba9a6cababa926796bdfd5652976455dc1da7ad0ff873349ef3dd10a1a5e0b92b3a6014df06d80dfa692cc8407d163b5c0c9a8ace50d1bcf164636ba49b92b7d49931405be577114347cf1c4d85c2609126025ce2080783f01737879249de7aeaad6f33e091d54feb4c45d2830f88efa01fb32f0fcf67dc66ba98b2060acf504d0ea5df09eecc5a501ca7ec9901871d044145cceec8277a8728ea34483fc480f6cca223019c9c7261427a4b418e1d0ac40dfa170603193598cea1359b3291ce8085318a7cb67548aa1f1243db75719da8f17135064b984b9e5fa96ff056f638837e169bc103dc360a284c06d3e68e79a91ee5dc14ca97ba4242d3b7e09ce531fef4582ef2383131ff5803a36ba05e260984eab61be17d5607d49ce5f6db11c50e90df29def9b44e9e48227c5a6c8a1dd37b5cd59da7f40644fbfe795e5712064042e259063b3b834aa169f3cecdbc2c2fe16ed9d588942a664f6d17a9869f6532680f3b09fb420a875c225fff1fa2e26754b84fb4cc1ae54e4101457dce3d4b2c9177c78f5caba7b5498756bd6345b18f2d3b9a9a7e56289a368f4893b362ea98799b4a76a89cf3261cc1909ba5470d3e078872f1b7287a36c986d2113a29a50964d5c80bf87f3ca481cd6748a91c31e94e8ee8cfda615ab9249eff787bc615e014a11e75cae53f9f9573e8604bff5b880ef8499703463db5a71b093adf60981bfbfd00fbaa8c6690d0fc27b2aa75d58570bf3c1fd77912aba09dbff32274e73c575865d5bc73f9f1cdce8066f925ccd31049c777be01dad7452349408566f4c2810c13b95805792513d0236b4ba7e142e5c8c7dc26e0a628396c45861b614c5f8d627587adb6574d276e68b0f25bf0e7f10e439e80db2c8370813b4d2b4a03c6a5817767d5b328d1092ce45033748f570d0ca32b19d25fb6ac12c6f8b484a8e2ea509f4a11b7996c9e6d3abfe2f1d89ce3a2705f2a1adfeef2ec141f1f894bd5d5fb9b605bec011c4753b25c4f2d4c5d35cecd409ca6d290eaa52e416cb0d601918098a9a9dfea46cbe6e4ef61e67094b313f04448a5e0706d49ecccaf23d083dc8f4c8eaaffb8451746ce85851894960e7df687bd24babc959bb71084e93a0b575db7897628675b9139ab6a9c31bddd58c014a860a64103053ed333214a4e8f928459e521ba4bc1f1936e7e2c0ad3a4f35f5545288345ed1072a302aaa4bb422d041ae5518deaf10d869d939ae43d6bde03b25085483e13311288a27ab6e211079d9141ef89664ce6289cb2dc18097b444a5268fc230a81bbf4e01aedd925ce2d1e73a809f5c1bc6a33e91b28059632d1c7feac77df453a38b5357cdffa0d234d2a86ed0fc34d019e7ded9d87cd6dbd537b474d448af3cb74aa45ec1c6bd7c59a7078820f11e8bd9e8e8edeacb24372e4dde9451ae6649bf4abd605cd6730703437a2f36916deb13b8e8d183bb4d7c32da9720e9f1c23850663bda3d8c268471de84d892fbc28e8ee22e3369c196fbda6124cfab2c54e15440c4e27e4429bce9c4297287f8ebe939dea95324eeb981a14897bece61e35701393eb3ab659a3159ecb19e6d09dc0d2a630a58bdbbac1e0c536f2b6e604eea740054e425bf34f8895719a7be3a4117580571087f49088652af6450ec20fd209020693e31fe58b18eacda49b3b245dfb0ae0aeb0d1c0f036de53c8096582b813db096f3df7aa38da5803fe591c14166bee99118e227b2c81149322489a56a696ae3e550e23f88224c8db8ca0f048d246c9507ce33f901b859235a225e26bd0f35a74a6a7d85d95745ff6d45ea5aae51ba42f5fafea5d64f7201f905155e743d561cab3f0dc8d03398852b8e24a1e27558266a7a868de044c72d3d1d064775bc9de806a8eb7d2d735d70e37f34c33e4c1a08828f333130106f69c60f5648d4f4419bd6cccd57188417521dbd1ea4f0ae2257032749fe196667ffecbb3497ed10ad35873c44db037ff18123195fe916afebf28a5fb90d91c91c702b4c4f23e4e9fc6badbf1b678fe76ebc11333a6db4340e9da126dc84c2ca815ebeda2f7ecb7654df3d1958a7778ee2678bf64578ccb93cca228d770494f0d59ec89f1b2800756690429060430e77c0b88fd7c2214c559d44474b39fe269ae6fee2ef6a35d70b219cea7f04bcd11706e2cd1f56fa8ba8d399c0cce55b18a3e995882733ff0c63c5cf184f3a38a91a902b433dd3c2d3a0dc4c50e30d5f3f9bfd7f60519ffeea0ac96d4ab84c8b8f329161f4622656f01f7252006f24ca1f23d8ce243c4d070a30183f5e4f1779703a65a48da90553b6e410738c84db270eb129c82a62c26f812d9628a15f45a8427f6f0c14100e398c488633d2ac86151388697db61992fc4948da136040ecbc0eb76aa7aa07908b72cb2bcb719a54414c3755229bed338306bd4b5491fa29ee3a16525db5c1007033db451d5cf0da302052fbb766de86f3569af6e46ff0945858f9dd52f2a72b2966d7c54ca7131aeab8b67266b94d3871c513d52e3b532b00feac4bc93fc6399237d0ee6d69665b3af154bfa646a3a578b9f98f7d858893507adf1221b073f2e4c50b274ab280e1b26a169886e2bcd4893c15548f11c7cd830505306779061add04b1e5734bc67df84980acd74574cc205543f991f118d7284ccb4c4d9f54d1871a00c6268a48f9b59dd4ddd4bf8f861a3cbbcb5748660a8182c6cb3e1d6bdecb54f56c6933817ba12afd74b8e8ff631203fa294aa2091f40e4914355da6c1f0bac085388d17345766808ae5abfa59218950dd369c6e256870bfb5789f5e1d4a97340f9a962e4937aeeb0defd980e20598bdd68425518b018d37067d1c365bbc247730db31695240b2e2883c58a8edf1d9e37108b7c5f576fa2ac269b26883f541a7dffb25146bbae4fe3900301fcadcb8bc0a00b0fa316ff8a04a6c55209fb127da406f9674009c2e20db614114fc5743383c2122c52f27daa70ead2d5e00ed2a50de4b67d8542b08675fb39539adaee2338e8fe19846d9a18e6d9682122c8a0a622a5b835db000d3d8318a87a76d769d872a8ccf01072f5f71058770d1b1dd5cfc5a5af457e62cd01da88297319668c5a8645c102158c5e7dfad2932711789a44979ad7e7337a69b7dcc29c8b1279b4704172adcadf81ae682647fc684351ae5da0ecf6ee9d900cf8d7d29e7ceda7385b98b2c05f069991061bfb00b1c7dc668886954bffb0ec5a198982e4ec77869fc79da7d95b92cd901d8c36f6771a64ed81afc8fd0c0d8d5f79d4f919ecb4506badedaf8fda5e3a9de7f8dea577def88c1e0f1b0e14e08a47ff85c2a928f95663d6e2f5d1e9dcff9f001c1306db2243ab0c34ceb26432c877597602bb2e8bf463d1f9afa8e39de67b3b43f54e80bae59896dfc7569b043319cb53e379215b0c8c8b8a7c67f2e6698d72a343f303e715db24d77ad2acfe83d3fa1a65d1f94d719c8d4516b021ae0a488cbc52b862b54cc3b2a858be86a9437856a978eeecc851c8b0c0845cb597ce6b093dcebe0c05b375a5c823aa03e0449c9772e35bcc145c1bd7e095afdec8396017017895285b31782509314209e3c79ad3a9d80b0f7f54390d54ba0699d23e53fc0eb266fa7d777fa244a7dd9dfd7d9313f8cd3980076e5ab1e62c6aefd049aecb887ddf2323fe90df38b2bf76a36f305ee2b430ceb0b2879c63800ca9dca94a5b0dd6ff768d516224ee7d9fbe04d42ae16f8967182d55155f0125526c8cbb1f651ff2867c8f301621f65dc3334fe96b5ef49f9d913c71420533ac86d769173c2e9401ab74c243696de8db47068b7e0de56b63be10cab5a6f09f7740021fb9be99a60307a90676a65c5578f9963a4cc256829811fb2b45cd13085e33c2390f21f9cce78ace23cf2788527024e1041bdc1a430f6cdc57f36b617c1a45267990addfc14380ddb3fff321b0eb61bae24af2ad33de982b0901cf760ec0bdc59cf2a1859e9f0e4269c3e82c06c354ac7552230d26f955da5df27b1957e9570eb687b724da2ea8a377bd068ab26ca9cad6d50abd1d87be51c5d15186b4e0c898c7db513b0bdaf74df89b18fc88770e32e0263df00adfa8afab90f160e7f96c33e89d3533fbe0dfbf6e1d1e0799638472e75e01a2dadec7ff6d3dabd5fa79f3d5d3d74f1ae2ed72793031290757dbbc7136a782db59ff8c85370b2c08025dfb768a8d487797c1fb3b0568af71ac2cf2ce91822fcbebae6b7b154747d6cc2855262fc201294547a187f5db21858930fc157c57f9721204018aeef0bd79ec5d93271325891eb61f06fb70e6d4618abc0efceae62c20310a9063371e5cac44185c78c67254f531638d9c61ee5fa2a4f02dd8b8327c40b271ecc475f411375ed945f2bce50803d41f84bba76bb3e48fa814d610b83136a0589682a57d852f601a757fc627000fbc4d2e2e9035492d7aef38f07aa0b2ab0e8d444d6ca9f2c5b1f92253de07ffb0a17c7990a05665d1cd97f57c089001d3e627598132e8b9e963589234398950076b0876b316eec96c8148b8a2ff3fabccc76c488cdd72a928f6a53ed3a95296833ab8fec8b77be7f10bb1c14ee36c17ebf3108468039b7fdb468bf2f0c6982bfa45e09d88356659a47e95f2fb4952ad29dbc046ad40ea9aed96027fd6243fa4b0d4e2927b06ef2cb77bbf1ca0d66055146aadf8c967858499ee6c85ec3cddf1155c4970cdad5eaedf0afd1c9e7425871e5a1f0d03aed0f042a96b81bb19772dff69f012dbe5d5ef90d2c5052b493fd9c2d0f032d9ebfbc648d5c887cf90b1fd5c93647b4628a8da8d0bfabfc0cb568faf2926d741670a2bb384b7884da13df42a20b91ba8da822951014bda239c58d4e16a4e7d046c7082e741a1510b98c376c596d71227b03550f4cc767bf3c414bc2219c7e2a1cf049bd21cab406de78576d9be0c7aa08b2c24dbc34c752a6466671dd05bb9d0ee2a35b3d5b5828f3e0ce7b48de3f4021657c997e98db2004a823b5e7f98e5fa7d010d6422a023d7850f982512a0248837c6eff2322fb7b77b6862b712b3d094eb835559d72a14689b014ad943bcd3d978942bd1c5059ead3f763339184e10a9303b6b117e2de1eb3003d7064ebabfe52136cb6c999065ecee0380588c7743c848580967695f6c8e67a51a1286565e2a01fb69b104d74be196c30e4270505ef4491e8c8d0103116093c84414cd8697484bb6357465350c180a4eefc6a8399ef384bb39ab2e52f8ac410f13741d8c52f0d467a5462e4852c208ec49f65635abc2251f3a9d7975774e076aaf7971790b78aa394f1587b377075ef14d6ef3acd7c03a7b805f3151bb09847b4045c0a2921ce4e448fc1e0dc2c7833bc5cd4b76de9187654f65e78de35e0c3db36a464fff70f295b04c0fa2c4b03f660a5e875f1acafe4333cc07706290abc7fbae88ae1fa8cd454ee1150b6833e437fc7cf59c28dcf99d07123860aebf1e36f1d25f5c7f4dc3fc578f615dc25340ec59fc27b0c0a5d2b229a3bc7446e313534d9156ccf28a2dd7366ce439da125c124aa8cce12f62f49b57b190c0efedee7d9f49ec8f91013f863474e2df0cd8782d978db791f84b9027bc804916bea310002f7ec933a7f00d10af21d1176aef1a78249ff41ca4603b67ded5bb46ecb6c3e084b465e733cba989d90a92c839bedf72b51108d7bba586494b95c5d41906474ca12e87a9f88e102f0ec7d9ccb23027534bd71218b224a5fa1f21b21679bbaae9094339aa79ca68e9ce5e4cfe626d7cc98484f35db7ba5bc1409044b5e3f6888dce2e8709efd2283327ea95f037c4cfb2082e526e713fb8ebda8417eeab48e9d8c78fbfe74e0bc08129ddc3895533a2926798009eb2f19c884e2256983882467d876d1f6ac0bbf5027e24ef0dacc9967895c09a803e858799a4ad6e5a458b7cc3f73d68527f36ef3f2659d96c945ab4137241ae4280559092d502d739c30917b3263e7321b53bc2e15fe5fa9065ddc2b81d2bc11822fff8ff012c384fdbef667b7a3e8aa276126810f2237fa9a7b1c96c32b20937963729eb527ea326982b5017234ce330a12ebd1c93d05fc06decd5abc694d046e0a517950fe005ef86dc66238610be6716b67e54d9f427c845c9965a3146553794fc236b2b050d7aecc440a00c3d46c8a5149dbd902256a1b4b8073bf68521e6bc186c392b6864311d21bbbd72684415e21f0428891655e96eeae276a1958c8fa7ee031e2bba2d75ab525ae2d3100a20741823747d892c46835803c1dd38ef419474d9d0573d303239b10fd6c104891cb98554459d2ba41382241d86db4049638b1e9eef17edc3332e9038f51b15123f95134c575d2ac7e8921a48d38f7e5a9ad6d25c0793498c25347c80cc92f6de08be9c5092506dbc9a1c9a1e1646cbf7d21f9bba110741ecf26d9ede1aba247b7fb02fb8e80f773c6af5dd4807de848e57fa0fef3595cdf71010152e67c0b4988a28d431faf8a0ba01dd423e3e3d1030f29643fe17bb72f29a1dd80324a5f92ae414d907655e1d97f688b0140738afa5d1ea610b5a8d7348a508cfaeaf2bb8924e462b6516cf15a5f882b1df813803c4cbc0f1b821ff974364e3e312ee21c2a6d3fd0854d8e2e086979292a2dbf43d3ae08a016cfc384680f66cb64c97e8fef67917d2d3e34228ee539f47ce0595825361a264187504b2920c1182013784d06d418b89dafc1469d6f4fab5d8386e940ef11e46fe685cce87337929155f15006687de0d1b1bfd9f9487c4e77d623bbcb9c96da745ffe477dd362e7f6bbb7f37690c388529cab9ccbc577ef96cd0d32ec9981730cf47b2753fcc26407e27aa5b32a229ca3613c02806859097b75cc96c46655fbfa208aa902232821c2402e780cf933608b98f0f3c52f79b15ec196283f481d006bd43fce075e5618be101a717c810b2e073914c711549fe22c39bcf25f1d0b4c636ef92c2fc97651011b59f99c6a05157c7f7aab0fa1645cba9e7395dff61e13e6552bfbb2ef415f46d5ae71f0f7cfe65f3c5b39b5d34db04c5f166702b2fdce5f04c487ed3028accbf3da510c05cf104bcf5633ab4a44ea32df7814acbd72fefaa3f1da9fff62974d833c0b732ba563634168a75cbabd4b4785c762393ca75efeff7bf5150478509df5f8709fd330df110ca2198b5e4ce92fc5c613e4d41c21e79029de18c01ba130972557153d76b21c04b50d6e1a013158f5c47e8a1c77b0aa5ed18ecedc4a523a5d6b0aee7ea0a75572a0b54ad2cf96489577c86edf4e906b1913dea529cd7e9dda1310019c6dc51b4641a038ffd7924b80b9af6a12dc7f8ab09770612152004efca8a65395dd248c96aa4845452b884ae48ac74d7eca51b40e9bbca282e14a265938706d6b101dd7f5337d4b725c61e1a79e46a29303b78d512adfefeb0b1e7540df32f7e513b51def3dc7ce29176722ebd991ab5acf9d7617403c6985cef3601ee5496c7582ae4ec98593a5b746586fde02aac4a2065be599adbe20edb135e9d8ab13ac00add4fa50eb592d5630bfc4c2d325598bb97ee38956233d1eda5602601bdeeaaa8d2904764322021c3a64a144cdc2e8b76ca3b9825144e9ef8a5b9cb2ad281db08caad09dc51d19670b975128d5832d4a44ecab2dd4e392396eadfd322f9479c637e4fc10b55b2afc66e6de7ea30ddfb56bfed8d3e05e0a4d2751f49310c4419262269bc9a2ebf4f8bba0bb94d452fc60a2ed6bb0e578e4916ebbe0ff95ce265a115d4be44b6bf650b5a4eb94fa77341969b3c0ebcb07bb0c7821bc0709a93b4f2478454b741087a35cd07a13a685675ccc2eabc25d8ad3c7f5af234e7241f623b133afa9489f8453227b0a7a259d43721100714d2857f9615cb846ba363ef789bdee76bd438c40c7fea960a8bd55513033348699b7961cad63f6f42d66cc431e660e48bfc1adf43aa0dccd01e4b1ef20decfb4d5cd47021fdceea9e16328a0de9e33187ba04011bf12683b2ff85e8602730da0eeb96e5e1f2ce64112bcf9258ca1ec60093077f690daf1f0b4e95233a4d87c092d8bb758a856be895afdedce82e5148085f7e1aa71e279b8609cc1f89c8f2dfe7201868e754b57eca92a074b05ec878cd5db88ef975ce4906255b3199ffd16caf0fdd4c9cab70ae3872cd807b8bf4a364babb7acd6a989c89eeec35172fc47e398a85490a272b8feb983e69ba725f66a4d10556db21599d82805f6b186bec99ae06d8c068dcca64cd4d34b339b5e9076ab1a4fbd8d93d0a378cece535fb0b6fa581f66561d14b99b374f18c63063e1e7b7d1bb02ce395772621fda74625592a1509bfb7476f1089809039b7c2a26c2d64c320a5252f91e4414ba9061109f2860c2df52966398af4e4c210b6b269869702a909a19ba3482e066d50f8860673122de6304c1e9f974482078faffbbd3a24d040a2498cde2b5b943e20e430a95b7f307517543042edd7efaf93c99abbf5337fa85bcb7c53361c8bcf45bd1df499767c06e6b73ca5a7d4e06d6ccd0628eb48813e87d6a4cf519c1022b4dc4c5bb33bd6f5ca7d45509dffd2e6e76fcc88325810881ff9d11147fbc6a2a1b97fcfb9b5426003eae371b2992d95c0190ebc3bd6ef09e792f93e061a6ea994b32d401febadd90de2a5a6af4658c78c6298fb31bdcce8148b972d98b7cf0d3731e6d91f840062b4afac78bfb64b56e65b2de22231bbb313dfa49704fbf7cb077598514e25e43409fbc3b4e65db3b0c0ce5b835402e66bb09dd55a86ea57be1e582366d67ea58d365943c4cca29101ce70af911d6cc3cc204eb2fbcd07079a098c6fb99d632db6ed099b602002ae7000c4231c70832c87bf8b916facb0016104069e0297a228e4f24b1ffc9e37e8e604403ce491e4469cd0592bce9a5fcd2c1a960d2a96176952e6e6c7c53c950e2cc3efe17f4f8c0215f7f4c9d81c615e7c112cf37234325a9c6a041abee4f2e1483525ed7ec6f864c25985ac92e2b098f6ae42ee429a451305383627096bcf95018cb22cd9a1140ec78319de29c8b5211874042a1a2b6b2005f714d3c5688ad364ebf96c59216c32d325bd66b4149e8acf9817483c9f7264dee0ee6c17252fab5f1afb4e41f5d513ba4cd18c818c557174f6142668932111ae8886235d28355e8ffca886470becade505fa99472c03cdb64e9c76d20e2fcf77d002c69b653060cc13920b703afa67b36190193abfae2edea2b75ba65a8a94f147cb10c388915d2e620c813591476b40526bb0487d18e28d882d588be8c1b6c01b8c92b4423a80279b31e14408086d27f1795adaaee62273c4a2c09674948107482445230d40c2d51200613e4a7801e2d09158503d1c63e1913580a22056a8523239fd8b5eb7ad4d6688b65dd946c63c5e0343e9e207b744fe28390bebe69ab19bce1671db644f5c4d706294bfcf23b1045c0dae24a1b548f832d704c1171d668e046a29055765f64cd122c83349280207f713ec6d7e3362320df75136a5305a49103ba72b27f5820cc524a0783fcc92540a636b56512542300ffde127ca48755ca33eaeb4a3fcad21b18b0a6c2379d0b877c96e7020070fc623abe006cc2c593f76252b6698f12d8255385b3bfd9974e5664117d49c8de9b6cb9a59429c914a5072608a0075147ff084d17cc60fd434a95744317dfa1ba4da541305656914d0db60632d8fed8b42a74adb921786955adb4caab6ed3afb7386cf5c686d996964d76f5943d656b3626b253d844a20f1a8cb25bf64b6db5fa6d255772255752ca8f88a505d7552ec431eea87bee91b6ef65b41db17e7ba46e7941a26cc8532ecf3661d3d8909f7eeb51b2e1dfe84d0d4697c2cac299764c0505b9504389c5a729a62b2f08cce0cb7f8a89415f6a3042b9fd852745cd08fb5d7563172737642a9f6bacff1642080db28ff94db5213e710038c4184fa85f0485a4bb7aeaf54fda0c6020d611c6ebd3f3b95556bd12b3f14fe85437723e47b1bbbbbbbbd65a8f1c8b952ae254413535e54d0daa9973e7fa57ab34cd397343ae9a33e6c63f8a4b110a0b8ff598d8138cd8eb62c0b08e68e958869283e5862e0575e39c7136cd836f9443e7862e0525e78d508cc5f735cdc3d930778cd1ca78dd3b07c1e7cf8f0760c6cacfe353f3a8f5b6b1215715b1d61381244b57d8ec271b3b35c8c15b8a032d754d70f76fd67529068acfba633da64de9857c879f7a46664f0492d0a9725d2a2e71749a6efca3168a11ca56e5912d2dc50bf0e0cebe6106ee3c2a21def9448e14462b979fbb1d57b08f69d085441063fc1a638c532ed56044622b2c53c9e8a166fcb1f62012740130c813b99c6c1524a61b72951179b9cf6c56242bc29572d42ff71e0d36acd9d31d2bb0f46764ee699023a458f71d21c5584158314887f41212e359ffa21f9f6ebf75de11ffa3d677cff25e42fc488c7b7aa4f5befa55fcd949a0e76dacdcd3a220dd778414eb7ea394fb20acef28c6aac32207e97edba851358af1acefa8f531e867b46d528f04fc59efe560c31a3bef48f7f6e30a859e6fcdcf0bdfc5747ef835fb59c429cdb49f45fcd2af48ac5f18b38c9f64025a5e52cf97a5df7e475adfbd7722b4be9751f7bd84b0de9f553b8ff5ac2772cab1e91971cecac609e89eebd35823042e223e25d80b7024d4d0831bbff5b343aa1cbd19bdd67b566c5867b6630536fbf8f463adcd7dfb4e57146375a4cbfacc7b01b14fff957df721d2cd4ca81fae3e3ef55e402a53ddd667b1fbb1fafac9ee595ef79af44224ae5ab95c3dd0faa61835b0c165282aae5031755b9f3da07b1a56f6da15458f487f186fd6fd583deb930dfa0784fb2cf3340ef403fceb7b25b02dd66bdd1722c9d80de95fc9d0e76637a3f4b3c9f28a6419fd2c760ff0cb755e9178b9af886f5fe85e96d198d4d967c46fbf5c791ce8d73e1328fd5e3fb2ef05a4be7f28bb1fdc60483fd2be6d3912f4110cc443ce1e0dcaafe39c2cb93010f7fb833d5b4ae9359b93cae539e753c96c3ca8536c910a7318b558f0d6a128050a150af9cd11055888d983f8c523ba5ce9e5f613c1e011285879310822bedcfe1f18944188b90d24defa452706923f2c61bd58a575c569ea7664ea9aaa24314c5a2614eedc6e40ebfa73d26d8fdedefe8d0245d29a52b1796cee98bd6d904e4a4106ca420a6d308f301eea98655946a3dc3e6e7960b0ee7e10e0fa34ccdd12b1be8492a684ceb9a3259973ceb92599f354c164712749994e09d5a6821455ca93357162369973cec9056dd2e4882964d07ab0a658a1e7888642082396cc07ffe75da87ea54049ec3084154ed04045455e6102059dc354032b3141f650dcb007d4144e6ec84f5041526fc258a3a429a19452eaa50a2a6a157652a8e20e8d32e79c93ce4922abd0228a0a3ee78a2e0d0b090834349440872aaeb0e2c9ffbc79f39455b14605695dd6c75a3071bca0f64d1a1c59bd0cf5a6cc0dedcb293e9b72ce3748ace077ce39c12ede68916f787012f98d0d4b2cc8374a5254ddd0be1072e4c869aefad81aaed8a4e887941bebf0c840459fc6a0c76e16f16def039d75d7009f1d9dd545fc4a59f4fa31affc79e52743eeba0fa25e451ded2c078a28a5947215853806350f8dbff0bbfc610ed7c7618ccb40714d0458a8c793c043f139c64e36f3478e1cb24ce33ea7fb941d7cffaa6a610ed787c1ed8ed1234ecd28986fb33a76374f9613d162aea05b30a160cc2c2920e924e0d0691ea2d0c18e17477899410c1776a40e52bc10a5704297b0cc610799e509a5945236db9299a539e79c5bb42496ce6001536689cc218a3c38bab80e021531a490c50b7660708a414561270a298c608edededebc00792363e08a715737ba96ed0410f884c8df3c98b71a3dc6e81ee8910ad12a1f4941fbd51fadbe97907aa4fdeab35d100d29cb8b525b836050a305954be3b21a2d92ee2ae9fa1663e762a0d690b02110b96e8df8ae27567e3abc8ce66fd33b92c27cfa47d9d3ef25847efdfa1dc93efb65e1587741e6571d5614841dd6e06dfe6929a557ab11af3134e2d78b8df1ce90b261e50924ebd95aedd71e840e8df8cdbe703aa5596c31db7caef40b91aee6bd7bfdd4cbe873d461f4fba83639cce46fad785d57fb8d3fb0d9d3cf3edba4c746b0f68528d4a75e18847ff5fcbd7ec4d79efbedbbedbd82dbb7f7fc85aeeab18725dc50fe10d7d5e86fabee39efc8eb47f6abd5e7dacb8c662f1becbc7e5f7df4b28f9da669da735e1d12224529dfdcec7fd0cf68a5af7df54ca0affd466b5d7daf9708fedd1bf5733f02c7942cf15fd2cf7decf50be97f202265b4289b6a98628c5c966dcd5572488332f3a10648a2c776231e1b729c127c931cc75b59997dd7de40a615bc3be7ee3e65dcb05e4966862eab276a758f05451837c6b8c48a4be365f3e07169bcab2ba2e1fa0bba22d7054b66b817bc055dd10516fc8caec88219cf65ee8cf7e98a66f8bc8caec8a70032a264e0b83264bc1d7365442941726524a9f2fc262a05237ccca9f3863560d77d6d73bb28244144252d0d453279b258cde76a3f57d3c0b8e26a618401834c0e28305c30c01765044085c31ad7816ef8737de8ba7781e7ba175dae7fd142175baeef7822e9e9d28832521084d405cf972f7440a2a44649621142c4192584b83367694af6f0f1adab8b2b61b8cda52fe9db00ac10b9aa252e7d59b9b8e1d26f592323ae9ca9a109942e26903c68b182e32ba59492c55dea83c10d6ba4d962882a2855415cfaac05144f292d028719435c5ca5d1428d16488ae48f1a353c74c31a2b9872a91aa815dc61228b2f97feaa2ba23f7628163c0061a1e6d2d7ba22ea0202aa8b3ca410183c58e0e1e14a2d5f5ab42b9a63ae78e2ca9f49288f2b942e6582cea5575cb9d44529a55d0d3b6899e109a8289cc81d82684329a551892b5fca6a0410ad035cd9a4c7d2a89366050bc2dc71d59942698618a70bead059c167aaca823a5eac20038cd30c3b4e96629471f2b1d342cf1a19766e7062a798a69a62d82c515a524f3d45b0c022f364c57462a85a76d840e1ca24b1ecc459dac254e9ecdc5995e1ec78a18599daa044a960a856539c645254da0d4fe8d454f58185d9942121472853459960c14798da32a558a14700c324a1acc013471853e577902cb198a5762d599f79394a69e986f63fa62a9d3063c4065466c2c0943063260c336f02800233564099c90206eec1ff018518d3303ec3e6cd0842d0f17721fb50679e38d9accbfab8a82e33c1b441155b5f0077b03d337050c6763ec048605746dc60b2aea81e2c8dcb5067daa839a3050726b0f632141a261c0461e56528343074b0049a18b8a0a90216292555283478aebc1cc504889a467ec040524af9335e4687c085667cd149c6e7b4f919b9c5c9bfa393537b216850dadb5e062cd8beaf7e19fff28ff148b157ff8c7ff97794c5fd339a71e413a570623346082f8208f326d6de14368c4edcd5b2854bf3882f1166bc8c1732e37bcd8f21e435bf2016bccf736c8637c5f09a5ef367fc8bbecf7c19ffa2f463cc789fef288606868d5ba2d30caf4e4f460c3b592d5bc442710aa7f0001cc56418cd16da5aadebbb19a760a16efa81857aca9d78a8e7ed6f5fc86fb80d0b0d198e3ac00da35310cc73f226060aa3d395641836e5423e45471b100d19eb4d55ea80815efdc2a7e14dd2c730907ce9f235505ae2d132a9f565c78ac54ce632475d3e40fd3008e65da6c15a7da21030900bc52c0c237ac3fa421d6d40344f66fb42171f400b0a75c0408f7e215755d9f8452c0cd4ffa6898d71a83450a2ba336b20e221edca0fe10703a434c240f2a50c6a50d24062c3a0fb2fc45588104519a5121a141465f5a15f0d5783f25d4f49e23031c5263c24b770b95a8c04312221c85410128884800d150fc9205808749893161ee2d897332e3251472465494957f8d2cf3e99cd7eea713569529b94b4c4431dbf081ceefcfe0a033592fe72fb6392152b6e686fff2775a1a2a27092ee002e434901c3dd5afc2359eeeeeeee524af9bc926337aebb197d38fd6ea8a8a44b68b0692029212669a064e9ef7822e9f6c73b3db8ef8f639a47f7fd318a7d90109f044025e9c647bafd118a87b4efef383da85a055f6e57cf9eabb56ea1e5cbf52f32d1225abb4da5795471a1d677670083657deb8dcc4b89da87442974f3c85483a51f910f095bff82276c1899989a877c252c12b21ecd636e3884c7c8cee306394f3210fdc85402fd76b99828c779803ffd6a784149f0af3f6bade4b1f2c75c60bb3c86f5bdfc39e641c3fae5d743d362ff06d1e686b1ca8e226c18745b4f36fed956726cebb21cdf1144801e02906c226080201ed22efd1e3c945d4a298d94ee6890067957580b368fb075e9d3a78fa379bc44982fdf68befc5e6e24ffe534c8ebe16058f0fb06a9f7848dcf2d1ef2a74fdfe5424eb15d8bd78fecf267998f23ff1b82af20b1cb135c945f12ebb098041737ecf1ce64c3ef6e23b6b8758d06df07b38e0b6e5cc1b11cfbf0501d29b788237f4c6a9fd78fbeb7430f5caf21bb600561c35aabec8af43512af4767f9137511bffef93050af56b4bfc6cbe95397b3bbb3ec9d66efd9f7aaaf5a332dabd98a66b4468334fb5c0c445d48946905875da8d6af66f139ca961d65ec2867d3bebe84c423d947a4d8ecd7be5f46dad14b483752cc575fbfc8c45ffdfce339992753c97d822596b0239528a5948af912a6c91c33658b3995d426d9145a9d25b78261c527fa7cf4c1ee75d655cd42326c3ef5a6cf902bbbd3b4164d8a1224a14a73ce3971e0acc89aa024c86850a299d214154c4f64c8d8d089939da1d366632e5aa89374450c5f5889f23fadcb5074cad0792242894f09cbb97a5c574baa25852403fbf23753f431bf768add020ff9d7276020f95576fe0555ac43d9f935d34ba766f26abd9db8a71c2669d59572cb93ed0fa55383528bbbbbbb3bcd6a56b39a65533a15f13c72cb4c63a58cc997f225a5d3952fe593749a5912175bcfc8ec2ebf38c34b66f6cf04feea00bcd8500ea08b0debe54827ba942edb3f966ea4bb7b411a74b11c638f066316fbf10586e5675d6139cbc62a39dde8aa11b5029b1b81e460f969ac60f9392e2c3f8d26cbcf6961f95759587ece0acbbfb962b0fff1fb698d952fbf23f225443b32ebcbef6524e94bffa691cf77ed031d16654357c631d72ae9e9c95aaa6ecf9e3cd2aae8c3bf6ea25df050fc3a06a9c36dd9e4591303655318d6df2c75f0ac04d3091bcaa62a4a45a9eacd92a20fff4ec2427d03c3fc5b0505ddb8a18106afd70d69950c1937a455d90978c83f1b32a7e8237e5d259eccb2afc618a43583e5a0fb65d7bf12c2c84005eb3f4370ffa0c12f96a5571692672453ca9c65f2e7c7ccccccb17f91735e13e6471dfe8573ce497d4e77671f43bf3f32f3836c57aeeceb17cea79ff48d29fff44c4aeab37e6c3e98b579d0d9834ab13dc3fa4dfef931d07c39db063bb3e7ccb3f7ccdd9d4ee7a4b75fa553122e9862fde51070052a3fd9a046e99c93524a69651b73596599ad2bfbfa8594d2f7d64aa394e9277f8d95ae7243db1c1c1b3fb4769b2fa5d632217b7bf9f20db3eff5c35efa56725b99e1b2496da756b3596687317fdfd1b9fb0423d90581afc6401c65fdd8ddac3e0b8a3f4ebf6d153ffa8842d661433e30069b7d17f13c79eefc000085ed9e7b18a5dfd4697baf9e3b23f1fec8600525a8103e2019aca084822bb39a7943828e6ef6e4759ed39698c47dafd5b31e2976f45a716fd4fd5196d567c4fd113bac23f24ceb07540e04095a7536a66672c505b9da576ff34222770baa755bfde6199137669ccc3167556b500fb0d6ef2a653a72b7a05b3b22dfbc217eb72d7ba2ae73ca5b2f145d8146e1c6f899c07a9e3c4c3d3f645580e3b2428aa156d13452cb45a105153e3b1df87abe0adc215fec351b27a06a14a87d6b833de4a7860fb6bdea853528e0dae28324c5dee1c6d42b77a2a0246c2a777efce2d2276a10dc3afdab3744bbd37f2efd420adc213f57360f198ba2b4dca960cbe001f0fc2e1a6c28a5dc7e0078ff1978367078ee0ee53ad3f5a5eb9ffca8e5d29f1edc3bd32bc2527e7d27a7ef1efd1e4abf5b2cfa1db73dffdd322e7695b6d25418a8df29d392cfa5c914e91a4ad5604f6c2a3ce4b7eb58b9dc2da0bbf30e03f5bf1abae6f627e08654eaf6bf7ec40fe79ddb318e8cef5a2d8871e89ae843aa79b00b517bc5e9125df2f61933aef4e61b0094b131261719a582944be372940a4e57724f13cac6383ca4cddbf3126183e7e085800febe46f4a80beefe037f080707836dec3f130cffb0e827cde14d1ffcf87fce4c6c67fff46dfff3ced5efe7d363ea39feffb8838ac8170bcc77770bcf737bc0e1ec8037a22df9aa311c777248b3f8ef77e03ef7bd0bbf1409e8defc0fbe7c0037a98d7c17bde919f07df3da39fcf3e0eef083b6c83204a6c7c47b218d978d81fa9ff1db1f1c19ea877b8c1f13fdfbfc38d8dff0fedcbdfc8c6e378a3ff9fcfba977f22e0781b6f64e38d7ebeff1d58d8ef6f3c070ffbf083e6f7e1f81b9ff73bb0b040dfc16ff0e0051f4409d077f03fde911bfff33872bc87c3fb1c38be1b379e633f39fe7ba31bfff3396e7c471e8eef891c47920601781168e0e1d0e003f044fd9a36bca58ed9f8f794304140ccc62f75ec7f7a416cbcf446e0988d774f88c7fe6f784772fc8d07f28eb218e9f80ebe23254c121003faa518ebe0757c2fff7f8e1d2dc518d0bfdc3b92c528c7f7f2cff11cbbf1475efebd44d0f138de28c7df781ddf5116a3ff1cdf5107ff7f04f4369e63389ec8813c8ebd67c36387edc0638b7018d703485787d7081cc3f147fc1413c18d17018ee7588c85910948098eefc637af743061de5cf22607de065e00bc977c8efd7cf18af7925facf27dec30cfe398e6c5022eaf25468cbf315c3786651692317e2e7dffe4c243f6dbc7e743ebf3af2f74f97cf8e0f599532e7480eaae9fe2823a6e28a76e5be0c509b91bd2a513a40c2fdc71c3b914ce23b8c1cc7e1e3050f6d26b58f63b82b2677d2178b3ef6453b67d2bd9247d4269b02753f6cd2522ac9cefcde33d8e6d36bc23b6a28a229262287448a05012cb223f86b2c98697c1bf07802506eaa5a8a464632ff150d75ab2f1ff05a87e8a4f36bc230e63878ba6180a1d12306462598808804ad27d01f1f94929cdc3bf5794ba52f70ca5734d1c7fd31fa9629517e3c8407a75065a9c4e4ae716d65e8e6a52e786d6095ba700f3d849b54113343770b074095818bb41c1e21811c3096ec860b5cb514d7668e2060ea6d8ec7254132a514d5450a1d1ac71185882a8264b3026b65e8e6a920456452d2dddb0c5883d3fa0952c00781d830e9391d3b48ca39ccb67b2b495c7ae39f8f3355c96ebef18db0220ba6ece07ce4fad2e17e8deeede2e996bdb6e29a59452cae972d5eadeeedeedeeee1e4677daddddddddddddddddddddcc1e6b5633dbdd168c891cbf7ee2cb96d3a7f469a5f4e8eeee3eddbdcacccd63dbb81827c7e1abde9e3be7b43d5c10e7838dcfb1e9996105671f9d9eb4eecc2d2959b29bdc26b4d2404f4ae9eeeeb23a33334797524aca75c58172cb28375db2b816e703c7c6f7766666890597c5c697cccc2c8362581968dce5486d4a275a9b34b35c6b955a46679eace59e676a92524a3549bfd612cb0d4a2d8fbb9c4efb0b7c95ae71ee56f6d0430f31e41472dce568529335a3938ed46a46670f313030e2c96378d0608f1e2c7b7a68cca0b2517a936696de217aa259cbcdb362b78a110c8a42b109cf2acb326e3e07a475b32b5b4a6c7ca2bea2803b1bb7e23a6d55a90780411804ae7b0ccee7ba1ff2d20910b9b856d6b2140ef89e8bc613cfa6553775537f37b5f74c4bd497c8870ab7327cba46833e3e5dd17c9f0e8478b7ef91351109e103063491e8e3b2eb2a8081180f118765868c18b56948d597b2baaafd2a41f974b716c72f906881a10446125c1e2a3d75f2741191b461a590c38c242e2851a25e71dac2c275289f0f5d6acc8d5467dec42dc6a51b5f32c9a6282638dc5056915602bf3557b533ae1aff036a727eae5ae7ac9e03f31712b9fcee69316ace9e11d97dabf5acdf9cc7cf5baf46c36495a42b5c4cda925445ca690b978e49639a4774aae1b6969854030f456b5f0cd419e5be90b3cd43d3be6d83fe5af36885ace7063b20ddb79e763f58dffac2ee877faf1fff99c07d7d13581ff7b1731edfcd0bebbdb776f6e372fd5416c45663cb6f296b6b5ab665f57385f2fac88fb7b0fd527619374565839d7d3472552e06a795317e1f2748ea10db25730d1777f4b136f2701e91fb6958fbc795a53733478dc50c245faae988d4d1842de4fa823556b0ab973cf4bacc1139ac351937cdbb526a14b8f5d3a17b2217b2600e192f49b1fe10788abb27c81bdaebc3403e633608d1076d14f2164720982fe70bf22708f65a2f7b0c9ad81882526280c5ceefad2b821297ec09f2b6d2f471811c59f6072d4eb23069c70d665d7da275355ab027c8195d51f3990f78a559502de058210a777b9f79675cfeee4090cf711fe76d2bad66744abf1bc771ab8de32c6bd3a17ba1eeb3b55ad75f30eb16602f064d408860ac50b9fe12c808ee7c39878c97a41b9f8ee04a2963dd04bf61816a4900b458d762fde517065961c0c4fafbac33ae3ee7e0baea75f66f48d06dd939a0748d6ec6bba29ef7aec8468dfb0171ece8211bbcdb13750cbff5930de82be3eb0848323821c1020625b2a071136386ad7ef3647c2b2f94778be1c5cd0b8feecb7ecfc70ddacf6708775b9f0d3595c16deb3ce95b08384f3e6fab2f3cbad525b38e63441a6563fa723e9dd3887b992165432458fce49c1f90ee8847bb62e3e253c5f6f21b48bf34322f064454adc72552c788878a14173684b1248822b0a041bac10b2a52f0c49aa617a03883a51ef1a5890b4b5bd4489971e20e9427636a70a18a34f38212197c50228a175e52e044132c984c0933450a253985a5bb301126490e5cac5461d102945c420e1a178620c10ada784953c606364ddacc3047094c1d32588a50f183152956cc14d1650c92152469b2f06108252b5188711c783042c30209dc2041c39a3261d88b28787e386229890c45c43c0a9326f06899414e5211601070c50a2a2559e84c8142aa480f767218020571b248e14b12121f78c0f24445c78d12333411461b20f0a8417243145216195878d374c4164c501af00186285190c0f3660a05484c48be809344c40b4c5978d12189282827a0a0c9d243122e28beb800c60b4b9078e1e9d5449326a29ea2c890844422ce1b26a0f2cc80e4c9982e3d749b33499871a24c0a787e807204961f5334c8f004551518d0482e0ee7688a9492921b92ec70c5882fb248c1c1ce114f9c48228bb02c3081a50c1e20bcd040440d5257527053039425e4c82db379b0001c12acd0c59c1380791a824a082849ba8c31c2890c42fccc2084933a2e8019834706b1c110456c21e70b0c4b3c915a6201e29c3b1828488cfd183dba7497d2047fcbf9b894f2db06e0c532736c61e39fed7fdbd1e08423e55a96a282caf5e8cd0a8faef605c99ecfb4af861b1b1a91d7ff33af2bb259966599b31cab7d4fd8ec897ca57d6683953fe78330f61101d6317b0cdd98414d82c3e6134008cb178479ab77b780ecb320acfab357f6b5c197f6f433ff97f63103655fa5fac2cc5282534018b22ccb3249b92b9f5969a35936e79c349392f9d9a564965688bc0cfb6ab55a497777e9e2ea5dc732b3e5724193f53959f21429a59430068a43eee04f8d70ce39259d35f32624b394cccc524a29a5945232b313ac2ab6c15d00fed81579db62a27f5a16fb63fdceb2efe88f8ef891621c97fa6d2cfb5ef30ba284484c7298fc1c6e2cc7b4cf92e52542f6ab37ca7e95511965af7d47447e81a7ec439ee25864a21aeadb58babeb1bde56ba706e57b66ac7f3f295929656520fe4660398a853b37690496a35a48ba1a0f55285151b4cc1fb0a85158f82259e79c73ce6f86a72c2988422a4e9dd804e2ce29ef9c73ce282876aeac320aedb3d5afe25ca0bbbb2b9a9991797d6a9a94ac56967d8b2f708d4c6f0f071b5dabff5c1f1176f55eb4aa736a9aa6d1baad3c49572bc642c677df3801514c4e709e3ace5add7cf7b1ddd9fc699817ff6a6dfe8b962459b65e45f5301c193b4607a37750501838e4f72ef97e411359d34829074d3a77528e41ae17eefc1a3be6fc1a656c92940bc5e8c7675895b65d9f552297d218437214644727e8e4b084ba5952479ea15382254f986e08e2887af2e4facf1326782403eaeac78316cab6678ea8041ea25f2debf983feae42ba27ee7cae85826a341577c670fd2368d3f0100b2399f0e1fa00b4a13b9fd9cb9d5db24b3f01309003a8940278c0f5e79e3ba9f3263b20ad1f0a6c2baa553a23730deb276aae824be0a1eab12bd2ec4ac4bc91835d71e1c1f6e02786e2a9356ce2dc59410855f47294933437fca02dcc45d5103e709102869730f2045b7871030c29b0810c1236fc8025c825c50a2edc2c718309a27832473acd3373a20cf15b94133237e427285e739dd9506597a39c28f1840b0c6a2995a9e4b201e76e6c86c0c00e65bab860064e17282658f26632b1444e0d486aa05343d2172820020b1f9e9023656e804a933356a058b303179985c9d28a09262d38993f9f6813a529b436dbea7294132e576cccee85ae382465fff42bd2baac8fb54feca65d8e7202844cc1c7449b945ad5b41ab34863f4e862ea8dee4aaec7a406659426868ac2f247270957be7b174cb1fc52724bf68e6eb60c54b0fc910121307f80d4b1973cc39841068a3f35a4e5cf66d44bb03def60201b622cf3d768707a4b2943ee5a8e6777fc9658304ba794ba9c9e0e4456b2c75ab2f27988cf8df23959e98c69985d0ce4534379e7bd7bd8f8e7b3cc595096654fc199516a5d73d6ca94259d328b9369fc9a3d675ea0eda8044e974435e9d0d4000000404000e314000020100a07042281301c1ed2e55dee14000d7992466e5c9ccc635190c32808216388210410000801060898a9a1210e00686d76025bc4ea29c66fddb6a2625ef32650a17d51929d62e56f757534b5b9e7b69a46ae0504606dd7041902b0ed88c9166c7edae2f917205454e88abc5ac38df461653ceac3153de72f6d48708df4bdc8f5494b6e6f0e589b8d05e1a32f599c0eb4703af805f5e7ba19063c3213553b890b1e3413f8104878e29d927f86f3302866c12b308e9971a5f0e562c1f5372fe18aaf8e2e40d0dbf772add0bb868d736f6fa555237dfae0d3b0d09a62de1bf91eb8496d23c5ea087cc370f1123ee8c4dd3f680660bf4b8ff0ee53012625341a6c7d3a262ef00e23d8b42b83ae8125ec0359d869361d26e8998536ce15c471e4ef808d8917825f6f8af2787bd0dea78a4cc528c7fa1ccb668f297dba1a2587c1d45e900e0e1a3020ba5062eee10cb5761b9ebed3d6a6d0672b8cf10a10ad077cb63340cf968c0f51a2c04565806f62d25b8e8db508a6099e4a810b9d053c74d86a66cec8875fddde71ff2ec76a5123d2803790b430886eec3d83b57e1dd3db0386ad98029a74eef4ad1fd92abbf0e77a4fb6aa8ac22bc1126c21b4315717640458ab3492e4d0679644ed8d371a52efccc24715b1d5a908ab0ba4bd7c94ae6032833ea11ccf7efa2c004ba4fe30ee2a1138e6c6868bb86721ae3d088a38a0e8c49e63b24a275a614fa83877dba3a46d7bc2abe565ac528244b10bee5e192a90248b978777b897ca8af3bc37313b80edf8696418bf91832ddad7aa5055822c6eacdf814f3f9f9f701fc49ee5b94b70642832cb9b883f994f2a0050fd767cb1d6a7618dd35b7f49a74726bc5303fdda29631a435b091c33f32810c0cdf439f1d1c71e520e206f798ef2713ae4502fd133463345999c6d44b54c9753a4b943d2c1c56e8cd6e248e01d70bb887a2bb224da2517a133f668b32dddd59f4438ffe030bc036072a3eba9ab97fe5a621d31ff371be3ff8f3cda7b6446c797ae9ff1a3c0dd77c0202b91eb36f6048e8b42033d1e9ec14d211a7d430a44e1c9956731f50156ea6d7b70d1140bd850de7c9e4e5409e4519fcfad5aa90cf606e1bd68e2c8f49a2db2e94a3cd7ed0a1e52790bca95181efaab69a04bc593b91ce64548bd3343ebb108e882c57cf4be98d06fcdce6aba45422417e8d4c8a8cd9fd09dc95ac0e273bbf35c6a92830b3daf1e96bd7448a6a9691c5377bf6d35ab30331450c9926b19468c9792ef4b554f52349020eb109f6ea8c8540457cf80334706f511ab5419a891c01b27f28bac0e8878f851d49142885f64ffbe325bffc7080865f3c8d1ae5391de18526bce8b43d3422a994182d73656537c21ed39cf4a578c435568bd8dcd4d1d0d4b8aa0c8cab19efc2c352b5157125c28f771c5eb7b36f7ac89eb8ae6fd25f3b9b8feb4ce8c9861a7ca6a4a6121de7eb91ffc4aceb45df6e58e185c55316ff4898059a018caed30ba1dff6e5e5d2bc7489a09d323a2cee41a363990e621e5ae9236bf1e9d04c747c1cf713ba3a6c59d23ccee2e929b1fcefdb7445e8f12382c344a3bc2bb8fd904dc6bf3d66c6924d4c746884e0300298393c069081b51d59ed0efa673ae13ac788742757d91846fffe312d22b0b064a9269be70c4f592c0c6c0de52d8ced45ef73c43a5beb5b2ea119b026b36a1effb37f5a12692677ca2c5541421b230b03791102770890cdc7c70a662f1e82aaadd35591c2b36d7013a371e49e4de7d03423f2dae50a3b90e9179cef4ccac2c355af7b1e8bda115ff7f73a32137f52a4f59eb8c6910e1a1193ef2084d82c9d356319c934e1fdc96888d0be44f164cb03cc48e12cf2cb060b0a25a9e54b91e06353967ab779355891c3dc4d5630fd838d8540f72d9e2e0a637784e68821893b91e26c441476473e3c5608e067d75ecc04043e73b800a7dec75044d97c73f0e933ce1b83430b4a851104e11e1a6b2069e4a1c1a262103ca332bca7b0e252e9c66c4dfdf2ae04954229363bea1f88c183b70ecc34a2b2302d3ba0bb9a111c06ec5a2dca278802217fb0855a3ec8070b1176240771efdec29d0b4880d4371392a170cdf200faa9804599f96c90c7dc90260072255633517ff0fb5095a771c7c1dc51dc867956f830a24485d39f37050231e5fcb01e0449c1a2c49b1e4c41481a94a700c177649de02bde910e27fa12161399dfc84bcac462f9142f5b20ac5b29a50cfa442a04ba756843f75e50becbe095baebe4b075a1e2dedb473f07352a268f32e322013f4ac1caec9e9796843fbf014747798d3180c661dd459b583abc0c72ffa2403f583034288187c83ee4262581cfb45a1e6eff1c18333dc76a1b123518cfc9219879c90dc6413baf3b142cd965ecc06d11508dbb941cdb956c18dc67141a85ae572d5e9e7e78289497fb28733aa74b38115f72453178729f537c645adc677481290f448b568a5e7734bc7e30ed2fdb19473908e08ef89f0e4f7324e7f1c9dbb86166edac1bf0225e2ce3801a1ff61ed21c025793a38f267ec88b38328e908ff60cdcb51542c8d424ef440efdbd7eab13d3dd9c8ee930882250051dc5fdeeb70b2ac9eb2ef950bd21cbd697f544aa562c5bdc5e1dba3a89c20da006ab3577c6090257b5ab837564a2eb0faf6063c6d3c8dc238054fabd9e346ee26029deb5232d786cbcb997eb9f237288aab003a534f990912bad043603937329654630adb6c17aa52727e42b9e776e716128f077f3333990953b6d51d344adff581943dceb93a3455d0b577294bc41bec01d431489a681f505c165eef26738811023a4f2a564dd7112fdff8d491e168d15e8f3501af80b5d1e19542ba325cbacb755d2b8af9717eca17680e11b6e5bbbc04bd11a8ab82dda34442adbcf5f4268d1d0adccebda03c2d016e87eb329efce80ba8b5c80addcc018a646550acf33944ce4a8c0a3a99038ccccaa050f73980bb598976e5920d47792da8ba2a7cd5e94a0c0a583de184e8f67ebe25b5d8c4c9c1daa61cc4dd4a2529296f5dcf99226e21b98ab904fe9253dc4fe610150284f374145d57f6d9334f104b7a92c2783915f6d230b1f5a9a1bed31536ed668cea09d414b6979e37ad5864ea0e712b725889622f2d616fe61611e03ea358e44e46af0981ccf3b25a462220d52f354dedcecd5d3f03a2169f892820944e343bb2c9455ae24665a05401e39b6f7867f7a178a544c27a1c51888c447f7710021309b1bfd87fcac3a9ce0babc7d84fde08063f1a8fec58de1666fe375287476adf88e8d03a83309f524de2674618c0ed83842607ac8ddd1bdc5326ee942db97e00be6c4d95042884d9363ab6d3128274de8e879d587779b72e62bb96127844eaa1f1eabfd65292cdf32cd2ab58d76b040ed1b39d738a4f154fdd0ff34a2dc6a1cc05a078814b075386993addf53cb2284628838aead645c15605ada73443d9b002004d930980570187ca6d7f22dcdac215541e626cb6888e985c4e0487d8e2286a221a3dba8987177bbb57b52163203ce118a1ba0a546844fa000b36aace6391cc654bbc9427b45ab9a484d59b48a9a16ceb9f3a1f81479393486371a2e6703064575dce91a832a90fdd323954a60820eca2fa9b24497b94651915cdc36438809cec30630136b7514f7169a381f181c4f93c2dd854806273e69b96b74da636c1af04d7539f3d9206487aba6d63eb9d8ae9f70d8499cba1750f5d4e032e3b8593eb412a36e382715c64f066c874f6970150148bf9182abd3ad5ae846b1ed687ccda5cd9b96800ab09087052ed6927c1c444a5fbf287ace9521cf27cd561892629188c58425f26258a2ef57351e5cb1955a56500e640e0e2c6df48a5dcd24d071482bef17d1899fd9f5630b3c036568aec8d0f6169bab3b695ee320130083f9f82d6ef2201de83da92fff4a66424813cdb2031f3da18ae33a7dae28d83102a9a3f3ee06c46f1d44f83b7824e2201297183c742391ef22a5b2de7472c4a7d4c6f47b809f68723112eea94036ea46fa763d8dcde494fc82f2e0606f80e583749591a2012c1d5a04f4b26d545a89ef91098c5cc8c3ca2dc80be25d28405725a46c9d1aca405028b1073ef6d2f94ce8a7368451c9490afecad731e617e682989c002f0d1d848f63340ee072ceb5767fb370596f4c182cf6a836d63757370a8320ee74cddd8ad0cc46927e8f9e64fa6c1c4587df416f96d9798aae74089c3ec5f0d5075c18c3763dd516627cfbc9b2e8df870996cc7a99d3e5631b9c90b64589ed12f1cae303f8d52ca09da7c26865a5a6f66fff17bd45245a315f5df46e7aea209535e5e30643bbf9388dd477456aa2a00ad4951520f4d115801f0be5e62670a9ae6fbece34b9f6cf672ee60ee0ead44152a26a3fae43e9f4588daa9010d97c7e7b0bf3c0551b55029443d6c9c00135f0d7830a266522780fa1cb1b79ae7ea0e8b14f0b3592de045824b857558e5cff771bfc9ed31ffa6b159fdee0964a6b7a534b6b0b159793b1d0c9a9c9c24bce3e66eb8cfc4bce036ecec3fa88b40706299a6698f4bcda375b51ad53854a0fa537ef266b37e9f2a718818cb075aa6d7703b5cbd5458bb027c58d54ef05f187c1799177ba2f7ad38c5de65bb4ab96a161f6e46dba55fa8d2908fdecb5a622ec76821400a23fd771735cee9dbae0df327f24846f426b84857d027f7b4ce3b6d9994f29466127cf1f4eb852f1509791d416975cc5576f68b2eb0813cc6cf025f77fca11b365f6688aee19c7e856055fa5202cd01dd05447bd22088f42d0d06e808588bd35394526e023e74e1eaea32b282610c7bf4fa4fde324a374b4cde461b7709ce3560abd8592aee09afd0d3fb4efb25bd9ed5a9e2a1fbf9fdba4bef0af6d5f74db0c0dad119684f9471bb57e5bc0df284472af5bffdb98623d08c45fb29d7b905ec28e353409cd8fbfc3c5e4fede3a8f2af098c073ffe6156539dd7c65fcf1e12c3410a7b69a5a24bb20af60b6c707b49b1d4bbb89c50eaa91c077ebf3c5b5db1ddfeb78d4e993807f1a34f62e6d9a8f2e9a5307703304f2787700504a8f61b467cafe2e9c80e10e9e351242c40da681931aa1736100cb7196ee7802ed58efcdb7dd41c76a54f01c2d94b38880324dd9686a0e087aa8efc647adfb53b21cbe24ea0a625ee140d4762277ba9f4affdadb938622f6a288f3206ed7b1c76ca34e9a1bd000a2dd53a424b96558974dd1eb75991e051dcccbfa83e81a22bc40a3bbd472c7f623d0d60b2bac20c48e9aa52720def41c406505c0cd064784d4ad74339ecb926ae4736ac33d5bb5c0b0bf833f0eb4feed4409b5793b879ac4f9da3938101215fc84739c4a78e81adb26dcd612959e08f19fa68928b55d36a715156f08e084733ce8c686fef5281e8d9f97fd146aa81a5a45aee9a72c426c479f4820db4dfb9a3122447dd75a4c7f69679991247902e80793237fb42853bad956f94c7ae5f2edb4638fd7b510c6b962026e3567eb26543b35f9789aaa5002ae5f311cb1a5798d42ce1f1e9d7a4d77da7705bcced0fb37af384891a470d2a63b2886cafd7d0930148e77e1200c207264232e931cc4935afe50e61bc5f8d38c09d1d85ef1994803de4517fd0601e40dfbf91030bc49e8fb818036d03ea29bd0135d20b90ca8d39077286dc7f90851a79b598cd5d5dc711f95496864ba1418173c3f0eb74a8cc7e130896e7b99b03f705f4ff8b4fdeb26c31f59806cdcc4f09d3084044554d1b052c0691e9ba0c6c1b4bfef820ce8d3f4e81411cf9d89e47575a8247302fde9bd864c628585df5918f07b270d202a938c58d1c4f70591d6887ed0c4b07916c66c71019e86dfd08831d08593892a41db5499d17345dde2f39aacf439213882ae02dbef297dc7fbb94ccda52a80ad4d5ddccf2dc73997545b6c39b1f29a105e5e10cccc06d5ff79954632ac034cc395db74b5271e0f1184209474cb4155c88a8d409082df84124cb5ce45af10656ef70b8cd49a67c5ce923ad14983ac3ab530a710638c66434738e03299f25be424011d4e2dfa94a71b63270f9bc0db460325e402953f1517ab84eb0ab628451eb0a474d4bd8d755298d670e14699611b4ae1454a0485728536c6ee46aae4bdc678e7c2f59e23fed3a6c86f87874045d571135e438fc35bbe4a12504ac96ed4309d64ddc2d021b80eca9b2c676bf9de37164d61ed2ff9ba205fb23d285d59ad5d0632b8e0c45942c17430e20559ffa7d8d69a9a5a46ab4506d0ac0147fb1e53431788e8dbbb44e9df60394c6e47c624a28ed0c1da21f97549357b16d4c998457170f0e82628b4f1733696143322f196f7d3f978825f3a0b14478e1c94de9cd0d961e82478223a705421a3a5d47ed49a0e8a57025e6c765bbd8d2cb193acd09492e6042cde10aaa1b6024e226123908891b69ac0f24205934c1bcac72d011103744640462dac81c4ab7404de9170d629d1edd2a95801a962c4d7934f10c127abcd8c21c91217ecf666a99f4f8b9bc421d4a8832618a7a1b503557f67add8e4edb6f8155d174ae0848c8800d4e411d847ab6b084f4b49daffeb5f9c7af769a887245ea40b55bb2e3be2c643771686fc5f6ca27f4a5af6b7a091f7890d36c0ac8761e8bc047864dd09cc59abf21c638f29ee78088d75de7979b759d3800363d57fd7a01a939974a912d71d2834f8c9ca82b942d0fca5d849fa3e9cf23c12611a355fa22ff3397a44a9e86b1ca6d5281d6a67f4a7bd706a7939c17cfa753910e99b730ea713ae976c5701ecabcf3323d01c0a6fc1e85e60fc82fcd4d9b4779eb4464e9f33914289d3dc5521d1f331025d7f3d1ab5f88c2ee2ec72b274583d01fa3dadbf73d4fcbd3beb3e38fa7b962748761c07d104a4a62ac9fb2442fb691d83ada38153f65164de908146378ac870ed904ab8aa0f6f6608293dea14f68a10288766f70accaaa3d66ad4ec19ff266b4427a240bca7d17b27e74f9bbd272930f9ac40aa9ddf0ad088fe16fc3f41196c10d4563c53991a27453b4aea9d1eeb37d52fe06ac2328e6382daf5c60610638b0032159d5caec79adf47e861cb439d60c8231cf0ea05edc48382dc62a836c49c24b34a285cc5783613501101f630d5b545d679918955cec7aa40cafccf208044306029cbf03bbeb2c9b7dfe2aa3102a9dde11859c45a5daea4f2c6cfc506b9e63319cb90c4c90c58de303071196b7d363f9bc82e6c4c5882bc661b472fce6c836c3cb6952192a3fe74aa3f0e516aa24655c579e052b9b45dc74da903df117e411ce32b2c94cfcfb11249990ba9e4312fd84f8e114ba6c184ac91ea88b692d09c01401802ced0c6b2718d83c95ca4f38fd4f00c08f8b8e30f019c2799df2b953995fd32a314d6ae03395ddbc732c60c7d66f428297e5d802dec21a26d4e9881016d34ab9b28ed0012e3302c10a9902f2d70d0e6b4ff4ff8beaace306931d093f5cad0cd0a1657a5331484e71fda0bf5d5287b0424f25187a7b0514ecdf6699ec5f85147475e4993eadf79ffb8016c9e1ef16362041c063dd373590d14caf8fb191410e62c23bc4b6cb04f3105d4c70858b2a41cfa0128b3c5899822cb79d047b1687834dd053a242d1687d2854f8e9273f8ced39e82d10d9c0d10399ff4d7aaa75d8f08a38c108a80731d31b792108ac21e09396d5dcf1f9606bff035113687fcbc695ba3d19110d3e9fe9307600126e0e0000e166ce053633df90058e0337d882c84ce184cfc8b9b1c6385556258e1de4031c653609f20949136ec5d639b1da4d100bb5c48d4f42b17be11bd3559aa1146c16cd2e160d42354eb84c150c1839708c646b064e06e04d085e3261e23c9fd105ac363000bc60441416a299970701609e3b541b6e5045b107b3619c4f496a725b6112e0cd5c4d6ea1fd55f9585981df823acf7939d974c98097d9608abefff5d29fe1c88519b20c652780b632683313624f1f36ef5575cdf6dab54cf6afa1b1301d5c0f8c0bc2196142b04c115748494c20d2c702d84c80d51c4560d0aa5f7b1bb10deba8833a06540f547582209eb3643c356a914661d811667cf2aef048b29ddb45a09a8eb2e6e12968b9bcf40d8bae67d89df437d41a0d063d912dafd639fb7c393fbb0bbcaf1720c09821c9316f36a36d0f0a663bb546dd98e51dd4ed65cbf240064737662d252742b994fcef1bac11060e9b1c290b8570915c623ffd5506cbf02b1e8671323ee0d7b0764d906442a240c7e6bbc147dde0c391e2147dc9cc9a916006d545daa04109458fd3e39803a85ef6ecb8b98982f7d3cd52cb34b0aafea94c562ce26e04b53006a7ad2f0c1937eb7d065eef0fcad34ff6ae80dce7c0e72cf471d69565fe5eebe158a1e95d8d6160abc4782328e5e9ca8438aaf43843dd70690b44f02e5bc894cecdcf8c4dcaffd72dc6287285aba75e03adccd9ccfea2782c302f64be619a09af731410f72614b85407f9bc9f1ac5728f8a38c8fa30bc1f33bc05476c1d4355e235f9b198760f0174120c32e400ea5a9bae1044194e79f42bc9dc6282d2e8e503f4afff1d222d027d2e80386c9521a4737d9f19d5857926cd8f45f99b84e3d5ac10883664f674bfd5d3bed864f302e2e503f3b038ae0974c2707c42fef15a8ac56aaef21b2cbcfa5658b2aef3ba27c27df53e9742cefbc741f42f3c644318201bae7540184792af7e95e87f6b27a94631543330ffb8cc2a062a366a7ade77c166490ae2e0206073933d7ec2a9dbf4e15be9e5a5a9ccab5d2c6cccfdc984f89970ed49577a9669247fde50920b31aaea972d25a7849b893cf16a0ef41b7c8977a2aa18e7588bb5242327db9008d12f5a2a593e14616c0a634fe6731741c04ace10c3754acdc06faa11d3a61381fcfdca365e41eaa37f516550589e91fb1fa34a7f591fc42270d1f85d3e69f297d5a76795436ecf6e9a8968ec686b575e703e3ea8fe10164f7bf587dc96c6f50a4a71efc3c9e070b0e98ef6a8c512edc3071ebe8d16fd89781ae45e010cf24eee2958dc61fa5c30699af775235f552f849676978bfbee5c752242596185527380f1cd40e18c5bef5e599aa335ee04d6c77ddaec1fdc1217c5c127374b3b69c139fa19b83c7998edb182dda062e757e5be1a2b6cb6d36b44968cf1dca232a3658fd299216342724067762365cff57e14bc4d6470e5cc75ec7a09fbaa49be4dd76f3f9626dd92b3755a2d11eae9089a8733630ab481a9e3d461943bbf1bcb5dc750292c1b5ec0f6113bfc9baa8d258a6c03bd29536f62691fa4a200ca6e24767e9dd9b1ad5aca15a251c57f7d615d0ebc2a8943d256095a28835d16ad3d6e60f26411beadd4ce208558abdd529f4c7ef29ac36f8e0726e5050ab329d6b041ee84db56cd83b9b95191aeaeb2a3592edf681721d5c45f001295e8a89e9446e78ddbe23f5f15bfbfdf1df1bf63dbeeb431c906279563457126b2a7481c6b7ba0fb4f8d8e30ebeb88e82f2b350d47f1bd243cb4bbe416b786ef6d193922cd16c5548f5b3b2d5acb3245019201f383d3047974cbc8f3160fa7e107f0cd0177ea67a79ffffd0e25e42138a03e0924702976104cca260c79f78c17a53800b2ca0c6603f2841649a411ceb6adabd228ea97622a2c3ad195cf9e8c029d218e3f3731bd741c018807db42b59a44885b40772b0488cfb0f3712803bec5f1afee58f6237aa9904c4abfdd09d24c22889d924754512b6e435b4ec22753ac1fbcbc8692001bbf386268436ba12afc57710584f4420e4dbc04a2716c5adf49a14a1dbe201107b737c88a07b91b0ceabdcdd53676d5104c3203a71eb93ba402e26a8764bb4bf069d957c795c62751614a91dd2003fc8f5b0af86174049e2867867c0ac114f1e40ade705abcdfc8e4e1458719d1e199c2f184527313cb0da23ec01afa53a2006f37ccaa3d50acfd8e8416aa705d49e058b2b82da0d7bcb3e0420c824d8136306e6b0301e501f00987c21c1b9173ab946e931638c9d56a794cc0b3351da7a604ef8d4b9bc3e8c2a1b004cbd60f58f72b0163e43b16a9b00e6eef7b3c3fd2b886ace554ee41ed11e31fb3816d1ab1a07165855057baaf5aa13f38d629f88b5d0901eca002e4d80357814a437357e0468924dae70ac5293bc734790a18c7910041f99a8efd39f673198574bed10744bfbc0ea5471f8c1efffb4556db3fb618d91d041eeb057f1d436456205315eb75469365dcfe1fdc22009c15d8515306c4fa61db008c9ffd70aebe56504c6c38c8414f00ff9a0084ae8f928824609b259beda0bad48d33b408adc1a10a51175919fbcbbe057ce4041c7be2c0bcaf9d772ae05b48523a962cb06070210d94a0b2822f3fb030e21a08226702a2870a9cc637f8c2b26660cb699dec04f55ae4b8814a27825f01dd3e8134d082f07371799664842184afea0442f00927eb038ad4018e760c4f0b746e19d21ef70a8400d8fc89230e151207de4e0c7d5b32fee2f775c3063238dc205b52c208a860d296bbcc2d67d03eab7e588b25f271c87e37a6bea86e05026acee08d694655c62f6c043209a868c632602271383ea3e6952b08c0ce28ec010f2d62ea4de050004451e460cd4c4121cc09caf28ecffb41d74ff0bd3ee48ea25d71748504112df0bc35a1d6e9d9382c069e63c60d88289e837638596f57fa22da3e7ce906fb85202efef24356e0ad964f6b0be5b2452678af4e6fea45d28b4f8e138c77d1f4321ede96b3bd0f21223a1520d7b74b1ac60c4d1cdde05d054e47f8e01d2e44d0e8ce86ae1bcc030af4d554eafca9cb325fb2af676aa3d125db7b4c5571daf742bc7f80be3b8ca1474324332a4db35cbf6ccc7bfef3a1a7c51a38bd02f494230ff4baf7a6693f9aedb5bd1ea6f80d9ce8a17b35f51b079bff1e76291297ac81bd95aa39a708940fd4ec86f2afbb97b4dcb4baecb008dfeabf9ca52f1b4dda758fe388ac68f6589f2b6dad34554db2b74860ec5526bed67b95dbdf3e8ff87a953c324a24efa7870cf3d73025020270207bf7c4be9bc8a09e3130473def0563ea4353304c5d4c3383458a0100fddca114f74e378fa06145f5faec7c772e9fb9c1e78cef7cfff22e844b02ae7457b07691fa49cd106e03b1b42956ffb91929e3d0f00f2e2dd6ed5db63974e18ae3cd459d9268d4e20bc9edfa95124920783bb1d06c5d35c8191e6d455ff0a3a90d309c7c25c9af449d5ba745fa10609621a9817393973bf4b4ea75958faa39eb0715c35a2a3de6d6976a82a841ef45afdc1f24b06a6003e0b3a7da9e5c1b87f054aa09efbad578037dbdfdd7d2133abe925be59f9e7fd683be7362ca46e37923f00bc8768110ad85c387c76ecebd8875e97ec47421e6093631d09ba734fecceb8af3614fe20eb778d06cf6bf4ec1bc1f02dd391ce80fa96cd7877c0f501bdccfe8487ebc87788acc1445bd3204323f871f3fe0a28f130fdce254200b3f1a36d71a1b6147143f4a430517688f82f7f4e40fbe7bd29f864964b3017ffed1bf2f69766afc7aa372dea1c021aacb624dc06b12227e0cf1600802573ffa4e113e27fc16569bce7616da9da64681e4acf896d45eab051c55cb3de88c2cb8e9d6282e5dee3fd25c88caf75eaaed1ffe75a7682d4b3558aad1a1520d7dea7ec1c1b879e099c0e5a185628abdc28035ff34867a855011efba09fe7ec1e080aa2ffd5d9f194a16552e7c57a9605076d50b79975130283755b4f25df301368536e93dd48669b2aa2e80a990952cf42a7ac1366c946a5bdd02671a495eda21a8dd1bd54c67042028a4ffdd5ec2e3a2d943d0cc8cf2cda0d608090898c939a828457a5ca01774cb12a1963c265f8777caa62b39042c8176de6d63ca2903c0b6982f45375a44f85fd942077aaa7564584259b29ace640b53609d67e0306e757699d01b30ddc2cbb6323427db79cd07c9dd2fa0ec56820e00d1012a2953ebaa1e751ddd44e378c2ce97cc6281162e64a8be58d6c59ff8bb73e862a6be3165103c5c1a845825b93f1921ab432ab021d3e095307f8b836ae77ad62e8d090134a48f2b3edb08f891c2c8ac3ce18492006d6e84cb725f1164b5827fd6caace9b0150f1462d25939f0e075439df17b71f53bb5036e0ff77547a3ac59f3da65ebfb673b8662e88a2722511fbcca8e191896f5804562b1b7e383952858b71ade6407b42ff952f9d7cc09411e84b1f2ab9a3c634cfd258366146ef8a883208307b6091aa6de65c7f5607b780093bb429429694e34ee6fb8bf64b1ab45274a1bd1b5a11615b20a4b3c175cdec3a376f4c04bdd7213b9ebfd14192fa8cdcbd7a3ea0d67c706d650558684fbc0284ad0d5566a75b2b315ade236f36c82709b561e59ffa63f6808137d157fa0ae4754ad479947fd47510a4972bbc5523b73c47b8f505d24eb84e77856cade1702aef08b9d115b99f1246d271f1a2abcfc6c0af38b51a7779c500d74353352c98e825e3c710b8c5ec26940e8ed90de734da7abe28fd5b132874c107198c9decee9626a57083a30d822218f9b781c3395761b903be931b19132748c4372b1ccdc487d56629fd775baa481a7aed10005d5b1f63a023854295e80dbeee46a99df02216fb6a22142622d363292953843f5c5eaca138205bd5a4654c37bac8865ba2584b4b79ecbcf456b6132120425d71650c2324532b982a988e5361c95e114dbf0a8897a312296fbdbd10762e466e0c8f45623356ee903c45420ba12d390ab8780f76c14b08c64839bdf1f9caea41756170bc82022834ffff51cb1fda1e00942a8dce1ecabfb7bfb4785795e5c05f4f1d1a9a144e404683d85653b758200b984a151aa9339324d48b5c3dd1588bab30e335e9cc0bddc6f27f226ec17aa8e9b92cf9ce5999b46ed016f40124f8ae8aef2b85f93ba8cccefb7b1e7742967abc134e5774fff2477b997b570d74571037b490ccdd4d1110ff28401731b2ffb680d28f80a0606ad8aee68c7d3c60ae79a015d5a1aae5df92c74f45211a1fc8070411e78df07ed0e9c27f4e98f9833df9d906d7f2f150391639cef8a3994f0e968096395d9f00e1a2f4f358bbe7ed22d657ea8fd9da9c5edf21440480b606840ded9be0b0d5a4db98dd9d650381c2f1147fffda3d51e13c124e4dae72d5b59c9f4456148e026157f4516f95dc1261488878fabfe755a65c644f11b78018bbbc429fe1a79d861051fe5aa38fd7fec7e653e1728cf6ce847b8643c770853f19741fca90de277315a9a23ea1843d6c44aba2b46d67de4fa5ec1b037fe37d59586305b1835ec0414f4f1c867778154831f8311806b2b0d29923e96a77d19284fbf5518976084b19f54ffde40508fc8a4ea41f27d6817defe93125e9d3035ad687f8f460b5b9dd0616885a4eb540f02792f93b2cf547646315633c8427b9b80401b807da220a671fe0aa22da59bac88d90a7e8a513c9f809e80c84338faeb66b52ea111be9daea01168c49af7fd67691055f1ab15c384d0104715430f32ba7ce080f5bfa05509a95d0328738b1dfd9dc6771515c19a3f4db04f9a24911081e722198753a510133a3c05ce5e30ba983ea7585905940c8202938bc0c6835127306b394f6fcf1e5836b1d9d6eba112badd6f08db63c7a706932098e312f4f200695bad3751011e8b5dfe0053fae8ff2d5c354f62ddae0bfe2158813dfbd4029c2f5fceb959824e2c7ab18f513e7f293a023c162119761e2cc1520075b2f7be552faaa70c1bd6318f3bd6cb6fa64224671ed6acdcb2e996661373b44f065350d0a2e56fea12bc6cfe326ccc1b64513f117fa47e0ca8c583eb25bac76dd47801cafa753f195dd99408ea4dc582564b228fc505ad6cadb22bd22fb5ad412163039aaafebbf07b00c8798034aa81159635892c737653b082b9d2a723dcd4b5260f0d43c41df65bef89c507b2fc3764eaae98e0bf6000cb36f503c8a584d84b7d8c42eff3d05d8331a3126846b390f198d249139448f86f07e50dba15ca7ea3a86622f4408d191cf0dc4f854fd1b52c50c5128c41ee071e37f714eddd1450a64ff8fc6988dbc3b848c4a8684d02b12d87e0d0d3185344e053c89f974a4694c47d54d76cc352d74f86502ff871974f3bbfc27cfc3aa303161ee9a84880953b275a7a759cb7ec9155ede82be28c3f182dd9cdbdf315969d52b6eeefa3a27336c255951e4ebf8556a04b4322eb431a3afc0ccd49508551c24d43a7e1f3895aa0d8e9d72c15da0f8103dfa1f9fb1bedf88d1bc1d7d3f34bcda9f41e26d44b864cc0c2895525f362cd7257d11aae8680d33f56cc98adc548b47bba82430f7d01bd0a50ad9bc9d586eee2ef3620f95f3f37d7e8cac4ccdc17f7e91486dd5018ad3f2257dc475594eb5e8dce5c5408195d052b4d92fae966fafd851e458143ba19bc4e5d9cd3ccd3fc89894b62d96e0de96b8000b760de82d7733c8bfa25f4f5ae0373cf9f8eb2fea7903be204931727066744d0f933e2b24a5e48c262367379d49000b65c968560233a851a4208bf2f89a2d7e11a37478f588c26a1072a446c2d7f46e6700aed9cb9a358505b60d88963dced832ef99dd989eb43cb4a5a69b00b49e02e00a618c63349140041a62acdea08c18c214f9dce10243d69850e5eacefeeca4e4f87396b6aac652b748d72064faa23c275d668c018a685b44e12637cc7f90c2ad8f3236c70ade26fc4310d4435d7514bc708e1e54757325d7d8aca953c0b6a7b2eac1e91152480bd3d3943dd95f636483302483fa22c4e691279907af25fcc833e1688731da7a85a8bcf2c7090a236b755cbb7085f446214e6b36c02d1b9a228769ec69b85f69f8a525e90a7f9b94aeb73d7834b2cf0f84fcc801078361d228138ac043d59e54c240c269d16584950f60a08ff46ccbb1b128cce20305d8a334d2a4a65b3e6d6e646a90c0d4c4abd7f5516f0c0f5fbd7caf1435063799d972904554fff0f5c7aa9ea8127fae8be4b3c112bd8a34ffb2547310d00466f706e10f1cc4b050260d320d1bda3f8124149a0a96243261a6cf78196aab0f7b08b536c16eb65ae7c7f97ada7c938aedd41a0d52b06224b348f505606e53179a1dd9bc540fecff4032f7d7102ec0fe345bcd7229611654587ed28f09bbb5dc032aa1526347af6eae3d9aeac9835213132ea3e3e71b86100aab66b817c7cf7b921560b354514aab3ebed138d8180fc004557a97be9015ac00ed49c3437f9019daef2e95cdc99133519b845ddaec5a02cc0d6e994ad5b33d50d393f0fe505808140852e8a6af9ebfd1db2d8f074ad29180305f87083f9e3fb77ededa663ae9531ce44074bea3e3bdf23d635a0102a5df6415caf4e1d780e3f2f2c0c1ff3494a1102458aa9c58979b554c3a71471f4d0d80c408468633d02c7f645716aa5d1959f1aaa51fbd377584f0daa34f2c6415bae4c2ed42a748b78f5ebc08a905a8a6ea5d1d7d791f236dd456cf52c8d30bd136435afa94df4689094ba1663622d6f737c1ac5202b315143175a7b26ba1dcdbb276246d10659255848206d10a3d87664fb557821a1515b368efe5b2263500d8895464677d7864e4d51b0d3a26821d214f94fb027aad69b1b35159600de727f7846f7a2ff3993d9386b56fa38a8cda76d909d45686216d8535e544bb8d3e6e9597d12ea42db5481b8b925e322359f1717dd83a2d6cebe47a94ad52e3ad9d322c69487e8b444020eb8f3a96e1daddef17b5ae478711141b3120c994371518ac72f9dd302e1395c5ee75b641eb05ec81b84e74d0829c202616050fe320d6f31d53b0fc9969912cc9c205a17fbb0c502e1245f91bfa91d0b0a1b0b042a2049240c6f0356f88fdf56efd83f4b32155d66943f92a8f5d1383a159f3ee1482232f1da96e83c77deec7e7947fa771f9596029c118dde3f9f76b3fe12ff52c5adca917cd59a9b95577e3e132750644803baa609ed0cc25122a20c14531d5cae26ff8c9c0b2a0772401563d5218725924e8b27a081814dc80bbc88a414d2546cd768fc937e857224e3b7d00fb22d82c788bd8998b8f79877c225ee130bcd6fd231fe95bdb48c883332a4284ae83483b2f83299de1780da1abcf41c8884d21667371b046eb4e98109b23eaf2715fbc2d206ff444fa664609340c28131627d622484d4f2824d9eb0534f63706043f1f03f66969121c524c269ee5c3f1970a86f61cec1e69d561c6add3182a252d333924c06ed692e22988e9adc9253e19f8ccc2ac2416c1f0fc947435083d20bb44f3572388405a1335403245a03249bf73b5419100278f563b92674a81462ee0251e942f60671bcf733733064733606070cd9b69ce6f35b785c480c341841251c33e2379a75c6e4fda1d91964c53fb1a1b68bddccee1f266e2c5394385d9c4baf082a281d304863410c3ef16cdcd45f20a6479df3f1bac6024e42a10316e1e58f14fdf56c274fb99fc20f0c1129d9a8670534cb271cadfc0fef4a7ee4eb1fe46e50ff4730050d689249336a46572156d09922a23b5fc30d2f1469d9dd9d33080455665b330d9a9f71bc7a7eb21bb191aa3f3eefd0537754dcde2d0ec4a33fdc75cbefedac2190d502db251bdcdc9b73dcbf9e76a54fe4cbfb707691c92bb2681fd5ef0c22899335eff3d41b0c0463f25dbd0faadfc31258de222b7d70570bc73bf828aee036cb2ac783ad29076daeabfe694ae8812aaa522db506ce3da6debb5b78ddcf357cb72ac488b47b517edb7acc80f8cce616d4c4c25b125e6a21ee741d2ade25fe7837e475a32669b30400c560c3f0cc412a78052f5f82d1d1eb1bdf5eda234ab55f26c7ec586f93c95154039e4eb248f0d8e3b72e1cf5ba208d01a4305c929d8b6b420a9a64542a39442cbb2990856e9e8b61669bf196eb528f9cb8ceb1a7d17021dd34eb15b9541e290609c44e436b99faa512b6cf606f6225e6ffab94db941818f03106f200d426ded1512a1d6b8d814337e8c636ed341e813e5507de6fb8e7508c9e2f8290de405734b3af784298853d9b07cbf098f32f153c70db2a14464c4854aadc45641c744f322d0a4d02bf608d7469fc88e0585acd1b3691f2887cc02840f8217016e9cc87bc205c113a689cbc641b8ce797c32153de0ec7ad75a0e89f251fbd415fcebf7e88a39166ed834e141c34dc861e06c3447484f8f51fbe67cf46a80a6e73a4301504aa7872ab32139d9536a9c1f8e0c584121b2e200f30ea6739813f008904035976f26bbad8af4e090258cdc7aefdf712b207ad71de30c07d127bdc8dd9cf713ef009834628a1c57d510209122f0e2f41199e01a6543e58d6416e8c49f768ffe8223ec834de81bfab71db97c01922025bcb7e1cd3fea60e752d25b4879cbbbe5a6dfa00e32adbc043da5c62a6c1e810f7072d7b42877a81e354b6b4bf13e65ecc804e507b41eacfce632feab55774216b9bdd40445eaf151c457a924e5449daa02640dcff2699c13bf9a96901c0c84c82e1224976475619999179a253f823a06ca6b8fe11f9445d4c995a7e82bede49db3422cbe2bf9c7e81154f07767b193a41908f079432d409f8ccfc964a49d9fda188de07244aa9627807332a6b99bce9e95f700553fbabb053a09f11ab3c1d073c6ae4f4b2da197e6a1754d0381bd9a5d4d4fe45b3d555d4012089aeb442fcf05b978c65f8aeddc6d82a879cc2cd24a7d5aa45742f635be976874fcfb62aa4ede08b7a27b75a3b1768bca69e8fcb1683fb083e069a48e369840199025b737408d365c45645e40f6bf8a3f39d09c758743ba5dc4916431758d7b468d451686402d9a81a6a602c18911732069ab8b2f22d0988bf0ba9a33f728cf3112abff76aea06e2ec4b744bed12d1e50b099583c89aa5941c38c4203a54ff10903cec4f18aa84884402f82cdb473283f91bc5e60f7bd319db11ffc74c4bd3e31b6149be4d12309420e967814f4fb3f5daab019a58ecf89244bbdc20b131c2468e7dfa2a33f7436eff65a8ca751bd3dad736ff04ad0e9fd8ca6dcce3d46c9ac185d7cf8cfb397c4888f043f2cc48260a190bf1e5a6d75ecf5b3f5a31e5b67a34fde1297a8ea7a3a9644d79b98ba856e2d9a9f3e9f62a1f144b022f995242a243cea676e0c1e20553c7af32bc847d0a4376811d688a8effd19f15deef6473dad972884460fab3a1454f4bc82305e80261fb92752baf4971dd19b49de6082e32602183902ad2975d63885a2a40010114a8c999fcc934724e7ec68117872c18c60e60d9e3a15911537a47b5520ea6a1915d0f2b73583ec805bbff55a38422c85d2c1f70951cb793b76412a8390f1478c98e24ee1861bbe9a48907143972a0a85f48e6ce0bb729f4647429869ff33003f649638e700988613419a95e5f48f4e553e8b811e79001c0bbc73c2d291beef22c48a3ba271c604d55a5874fc1b98ebc42f029c04f3d6b6a7223bd2a50bcbb470c6c7541f50c69de850413ba77a7fcd6cf539371277d38b4ef6374aac44e46658e3aca566394ff7e2733e6b6656cb025b71bd694d236a0516e7b3dc12dcde00696df3eca092fbbe3123b9747c93eb179d925bf536ae37c009f6e021281fb2303a168d2115686ee366004201ed70e2405af6d00ee2b0c1065118684297e24119130ae6df79671a71e5e101264114c407e157f3a7e30bfe2a89b4ae619ca3e10de20c843316111df289d508400d1d550fcaf2501da33bb43adce25c7154c4a843c582c29b0dbb9a863c35a94651acb822edd59b1f8bd09ed5778880374cf56999150e204ad46f7f4ee903d6f548dbe00177afbaacb8255664bfcf4c9d12148bbe14fd65070bfde24b1f1434facc9fae1593faccf316e5d3f75693fe51620a249db1f6e191ef0eef924980e94fecbc6a32f9f66f3cf510d66d8b0e531876799eda5d40eca97e9858e2637d258805de1e43b69f0ada8d46a29eccf0da07e517624705d085cc35da3a92d6d489d04af97e2d748793a19fead36dc40124763b8469520b36e9c6d8fd9186d24e6acc52bb791c25e8be971989ba84408437b0a2f0577fd27ecfdb38af4e67f9144e27e6f9aff7f9e282f0b5188014976934a655a1e2307b2edc3d81e74a8fcfe617279e6bb0a3e8526856270efbf2a781b0dd96e3a1d471bc4519c60109e5d216250dbf9905e7537eaf9acf8f71afa86535e08e511179f3ba0024e2305921d855d9295dd3df55ea384f678a55da3779a00d31d262434f2f4b682a33d0c319472dbf56e9abebfedcffb06233e689f9d0f014f0e885bb767a2b8536fb5d85a92b924e01c2cfca852212bd0bddb1ae3528b3c4175a02c590e484bbc8467e500cf6051a0bcbb315f0902cca3f4942a33e2a026a522234fbe3ca064d539d617da5ff1f1a59f15ea18a386159ea1d4668c93d58f45a4185b57ac191adea4ccd4f4378516276c84ecc59395461e0b740e6fb4325384a04a50f48fcd15dd1f69e0244851dba66db4120e14a3b3089281b13433f57abe60137e0c9fc6c52043f9bd76b90086aeb4dbe7b850b750b6bb53ba8e1e71a052b7f08da5b816b91d9122a9abbf2da561d5f3e6209f26915f60c1190af367478b2fe33735775bace397878f302c3742ace2dcee927901cb09d1123cb7372f419ba7970809ea25c6979607d37d402e64485843c47cbf5c952185f33be943d0701a9d7a0a1b3a665bf9570d3d4c7d372f5bc9443714dec33ecc847a1da94dd54bfb9ca4be2c1f195122d0cc042de68739e9fcfe2d045770800829006da2e1041b09f8fb11f577f10297a6bf55584f48dc777ce34a415d1d01f0e264499a0880634f5b1aee639208017038c4765b6a0da523c217d4a6b3bc59fb99818dc5d73f576583fe6fd6300af76371115c07fe007808732198eab5e66190cfcebb22340e64d1bcb555bb2ad0305a2e6f97535d1b7bc2149895044d2332714a09289a698849876ef651b181bd3a42b7b20c7a4c72fb65a8b933458822c85b141234d1f591489a4a7ab9e3c0b4312257ac29eec498ced58894ed8b4e11fabfd5339dfd9e952de5e7de42234654438f3a2830ae8a3029db266f50420b654a228e1c4bbf38fe863bc6d98402760eda3f949441387086947b15b05aecd7a24c33bf2d24e57e5db58bf89c1820ba54cd73919c28fa28246ce977178f818903f31c585e8490f09c483fcb962dd9c4c7203472f7664b1e6892e3b12b7ee06cc9bb18e0b53ad2ece96bbb9eab356cb3dfcf8ac35aed3b0dad5ade14a34c80c53218ccffb769bae236dd8d1199c7ec7511a493615f3e6b4d68f9aea112b5f97e437714dc555f03b084070c744a9b8316e3f2c1d5615de61a7f219559cc7fe17d58206ed80ea9b57340be35960c2c50b25d6ac1c35f579fb2b506c91a8acf130b7db23626b4c2a3ef959e7d691d5c9eba7de5ba65bf4f0cac19165365ea57e921e6ac30fb1fa381667f2d526a38667cdc94155b1f994fdc6662b2a4f3e11b1a4a4e256e819e3afa069824df70a003e161ea426e868b7f49fff155ef02531b13d39907bc000dbc72806374a8ee4e6838baeb39f4aeab34645e838cbef0189c5f97c6f68305b59f2dc7f4e126141ee36b9eee6da581df7a7f840b92b9c171c6e11747f8f2ec31799debd884c75856865c5ef7f75cfd9eb31ec7af0df66a1857eaea47f80973c025c4e456a0845bb004e87370bd76dc530b8de7dd85c862bccd7e730aa94ee87f5c825e93faf2b1d9a4e1dda35079a6b6a763748735cfa19057d94b664c46438d3c5899520d9c6d3e17734840724376ec7809e4270fe686803ea53b143789f9f21fe6aada26aaa3e136c23167a7027a04a9daafc6e7b62a1b07d2bc08d12781e5f83f6cda491f5aa31c23dba13f6273a97b6f780955dcc43d8dc6d23279c337d3562bd250d61b168969b1ecd1f2f90add949a96345c541bcdf1ed967a3b381dcbdfbc336d8dd35f14aed206300f5eb50e65be2a77c1c38b791e6e0839452ba0805231b5df19166df0b99e2e5f8ed5857429e433864ddb549f1725e9110ab1e0737a62ab476d153bf3174d3d2505f1567d2b5b0ea20dfa332893dec5881655ebe236b199de5ab4f412f92d192b0e352a4c211b0f23c2a17a8300c947fb2404fd65db5fd68173b43c99077bfe45bed7c7736b3943017a314369fe560322ec9210c1ee710fb3f8bc597b6819a64858b49f503174eb37065ee96a684ab58dcb7ce4562c80d2c20c78ec256431fa245c30981911ffeb78d659312172e73d17f4397c0a83167f1ee526cd5d12d2d48ffde9d94803956597a386eac8d66c4389797f7f93c53f5d9f4c7c71dc2693aba15d5b95d0ad4816e4304f1c6439b59cadca720fa365ca897bd73a4e133653d93332a110ca1d68b58dc98d26561b362c11dbcec605de270e6889f95b4efa159b8b1edf8982c2661eb11c77a2d1a0380a85fe761d053c8a894028374fe60094cd18a869d87f9903b81824596ef0f920871bb9e1a60c7ee860beb741a1d36b189dadc644bd0a0f95cde7c45e51023580f81e4245cbfc28b23968da0937696ba007e8cf7e404173e6882d027af461ce72092301dc90e71123dc5c70d24299de0cf7b26d3b38a36c6adf0a165f0653bea90f8ceb27e02a43cd541ca43371cf6308bc1b9d8bcc0b2f7889aabeaf138a610a6833a8245fdffdd656418d0327f5c322a3425b6a5c2e1fe1a8189e90e3f02ebf9a77f348b03a6235eef5443e8942df8d3f63ea33afc37d2784167613abea4fb1465e8aaab51f71c51d35cd726a5d5117784c314695aebccf967bd185747154ce9557b457969f490deca1a805117365c47f75a3e4cab87bd1b00064ce7c2d975f9768a545bf77691152c4aa4bb6bfee0eddd98e790d7ba913738e99726cf2f09b8495e3531cded6d96d5119f5da971579f4dda5102c27e563d174d0f12a098139f7c469a135f4b790f350c4036fb67e6224cdcccfb2e9ff1a41f939cc68dc4c0b24070c92a7b8c42a2ab216107c94e621a2c74d3059e3409403ca2c737acbb0d29fefc69c9ff99e06a12ad9888928e4a2a353855d4e171f0b3816973812edbc6f7cceb6ec2a63628dcb2409d5fc385c40e9e01dd1ba6b10935222e416665957baa754260ded401f2c0e11b69cd174799a2c5090358413fdc14ddc0bac60219001fa97322f5b1bbad5a7e1caa8220fec2b222bcf1aefd143e52ed8b353152322891fd153c8d8b6bc8d7e45e49dc89f0df7de23110fcd8b99b28f4edd1497b5d679c769f0fa8bd3b8cb92a035d6788d1dc8ff7f72454812fa6219192a4818b7420e2361e0caf4493aa2bc1b751847d818a08e3f59b29940cbba99a4e6fce60252c70bed88724dee30da6ebd9c4795076d17fc94e8f61a62d300867ef4a76cfe433c1087e66a4a1bbb81d1ad209b579c442db285c11a1e89c66604012f0f6e7e142b5942a5b40909b38805d1b3c762327012a89444cfd78000201753625c86f108f5d180218827ccc1e6bdd402fa96e80a32bfb2b720b85ddb168384d38b36bdb31044fc9119dda0482bf2a575878d8989e3f4a1dc93b693c754cc5b27e065bdca5deb7c254f83b21fcbe680f0e817bd9a800c7c150603f59acbf7aa5c76a3c7f0053cc7889868dc4aa5d54fa216067671fefc11fa04511ab513d65f2db00605248b974250806022f163d1e3bf385a80ed5d2c60a8f2814662a1300adf85ca39dfbba1771ab2b3e534fee7e51129b90cf92fce697219cb7e20a06371a09d1f64e21f54c2537a3c5a0f3ed5398429db3d1591f8c7a66a4a2502bf4d0702a24db41e9df2deeda38fc0101d5eb3654f75c474afab54e8296b60087598967c1bb8a0db4056163b4403ce6dfb68e721f6db933e606d9effba251e7d056513117031906aa79307857c029ccd87c719b32a80afe7c8e2ecf5e2f60a729f28e230cd75a377a641bacbc2221f397c9fb62b9632806e7e33db7a5e5e3ef7d96ba2b3980dca335ff4a50cf36b51c8c90ab8208ae8359d18a71e7bb190ea7fb40594252ffc1c2b1d6b86840725eeaef5da16ec0cc337888ec628e9bf8f1f5c5b381bf2a267c11bf6c634f1be8115b8d6be52ca1618e5906942fbb6a682e4171d849af3c973d414a26ea3d87c23999bb80619bed6c4d5a2906b80fbe8a41af1127b2034ca403e109b7453c73ab467fc7e472f2f6aed293c96f3f1b32fd713b298e060781d8b564541be9c5c79d1475835006174b448490bd65fd0fb1ae60d0ae4633d5cd17a4d0d44d5492394478b9bc9c9650cf4698cf795f64fd40f035d1b72d30762f245c1c8be85f76981758001a1b160a56a812d5df257a297b7e12d084440a8f111bdfbd081a1e320a2e3f0a9f099d032304f77f58d13991b8c6b8ff822977b20c66910606412b7c5d5e8978b2d5b75f7295524db7679b4331c5827f0f81692cb710e20c7f3e20e754b34118defe53fbea9c3a0a65134387bf3cc9810a6ac7b3b3b15f1c8e88e1d068cbcba076324d732c9f6ecfdedf19214f41f85a78039f63da2447e1a9e35a0d8cbd2819cb6ac6b46ad11fe87aeb2641e9814386dde74ee5667180c26371843b569c1103c13b119397a269318f1502761a5ae0c3fc89299a914d3faae9fc95c38e1a5b7ce9083a4fa5bffb963b2770b900e90d587096a9d33981b179291c9af15036b1f9d385c6c85980cf9580c7b898534d88c16ecf5ea245ca0587959c2fc791376a38abb575a8b9b05cc155ce4572d474d35e323942ed289403210f45396309878a57b54065877f3aaa59758924373b47ee929b0830ab75c6fd1fd855e3a4321afbce4b84d15e2f71f01ec0c1610b53b7b3460988a6d9534da8c385188d57c88973db4d5eb8e2007b00f53ca35ecf0ad19cf7bdad3b2aa68cd03fe58737a6cfd2c8b370a70e9d4b433953ad1040def61a9caf1367f02698c694b840df28e078a0a4f581886121f860fbef28995036edc9e4b9cb8ea3f83811f9c8ec5a28252cb80d7e3cced9c82389e410c36002ecf25842cc523a58f7d046c2092c63006d8205537e3aeda22ecf2f4c479226cadd47aadd4985ebcc11e1129845d53fa3023800b358ce38257275ab3c8095ad61f6aefd20ce441560804247c8a7d73591cb1412b22952a29bd18485a0058b9742f6feccfb86de074f1d19f498a16b65ed6cba8210787ca0b3a9201cf8de5541bd599a62d519a33c32c0f7fbf329578322c6ecbde3b92d1be8241e2146d691d29e970832189746f968ac3bd79ce43a5c5558b3a42a743f3127a329c90389fcfc22d60777210f3ab6be73ef8cdfc5ee4b8a90f026b4d2b462446ab5dcf9aa05e37f1e24b47aecb77b63541228f78115283a615d40c8ac4a9d51dcfb567945cb12bd9ce47d1a0e05781d9d351ce863065d12f7096ba11d5133a0f119f930d5f15ed14fbc152a24f5e9b10ec10a1165e3b747c7ff5de5c21603d24676c3f9ffed4048ebd002e5f5427f5e40912f5799587348ca28512a2e4ab012dc66d7ab9221a55a8aa12c977ede77e96f7c0a01aa21c0c9c937e13d8e4466adccc35b4525ff01ac17669a98e31cecf9df6bc825b68b86a27c922104f28c0cbf1a6fd312577ba7f27aa1e1ed80621cfae46ec385706a295bde402cdcb8c0c6247b6f81b1d4cf222908218cf8fa275b4ce29dce03e956052bd1b3a4ae1061095015079d2fbb5c8cbc3be96041e4b60a5a019c8d71a2402301e86d0f5c67190bbd013ea90d94dc4e297211be06015864ba75001be8cfbe78bdc2f73a12279798fd56f9ee918e2f3e65b8cf84208160cd3c113446f2a6894c5136dc033353e9a87b38fd160392cf724a5b591ce8818824415d6bc50b40643d2f61541ecdff9e62e46085683614d9849c3aa6b02ead05f8b7c001b409d1da3ca7954f1acb8281bf3f53f62e7e2ea324247941a97063a2588578ede81f46ce3f59e7e44ce42ee6dad846c8216d000ba5c0a711828bad9fd60ed399fd703c8e9f2f1cf7380807fa462187080b71658cd17c653b152c22cc1bda870e98d68996194894d0867fc7f57a9abcd496cd36b19fe0dd1c54300a55d5451d5b67ea969735de4fd6fc3f06ad07a307ece0074b0d80bcbfafb30a8656a9965ab4c2d8f911430b11472ee5ef1b3a2c053e48db3e82f1d4139aff10d56d5a8d46de6f3e960a806d7202375a272ee99114935c15bbf286a875a28eff62b3f3bec83b765d03e5c8d01eaf1853a380226b842dd18c453ee0720c9a70b1b53a681200b232bea0d2d4db5a1ffe3c65ab15200c8d6bd963422bca72f4541397575b8d61c291b2e4ea95c3784e75c663ecbc073720efd1e8556112c3a9ab2871c408d1df32ed653cd58411034800b897666ab7dc16846defaa9026eae1811785025a33f9d3b33b6b0181335194a82b99a6bc4dfbe0fa3bc6788df9cf8e9c7d1f8098d14c5f365ce355561bb4e0820aa3cd2d786cc44688d226a715456ddbf4e483a555b1683af231b32662d7a1061ad6ef1bd4a8e9024522d9534eb88ae5863afc33461eb6f0696a74762e4b8fb742536ea0a1e01d1ebdf94165c2547d8342d67bd8ca87191f7f071ea412153557fbf0580a723b420121e101b24cedfe97b6d897b9be8de8779c482538a8f4cb047b5fcca13aff715880e1211e7119d2457d3484440eaf0830b6f845363f5e63fe4abd50b301bd0bf746eec90ea03a36ae540b6cc5c125732c9b7daa7878a72ffb84113aa157cae408c9e9928600db33d91108e465e146488c95636715048b86a9155a5dc4328a990c6cadd4d01bd30427de3b4deaf3ae5f659689b5240209b61523210d0576b4362804a554ba98c9f31b39bdc19e56e8cf1611006df99f7edc9ca8e94c54c40e235710a42bc200249d377c3b183f596c60fc03ddbdae6bb4af1480eae36c59c0d11b3d6e5a8e5ae7506ccbb7f884eb6b1e72d84f9695e96d5b481a5d9a4991ea5976db9a926527121e96ad9afd23210350286a5b32cecb68870ff1b9d56a2acf6d8a71c610234ffae297d84125b182a4809f7c6ca9b00821d46ec0a2d6a59a1a805b9aaf57b72544f40d54cfaf36ab7d55d295bef11044015d675abcb831a4046d00d29e109405c01527528f0f5bc821b0722875e8664976d7deaee2ba2b58d6b70f8320895593a5673944e3fbd7e4040b5811df184e069928ff23e309d149381e5b25440902429a8ce5ea394eec643549fc0512a8477ba48df6d564af9ada1fea8155507ce1e45d30be966853cc2b0806c5a76ec92d7e710ba1c0b9f855a59cc094699788e2a747a0971d2ed9a321fc63a22ff5dd6c29bbe21901344665628c55001f998806dab99d9efa692bdd340de9e19c814f4577ae7745bf441344d92320a4dcf8494061470d25e1e26612336677018e4e439c870022214ab11d9e71a9b1491ac1d0b376f0e30218c7e48d497af6c62cf4e7722e56030c11d26b1ca51434e4fe8003cba95407ce1c6438a6b2c99cd33bfde34b2682b3d4c2f8e60dd11e6881b8414182ec2bc9bbd06e579158f0ffcafef3610ecf0d59d4ada78161b2d5fc99548d3a236436b2e38d47f21fe34dd9d240ac85a9ec54d1c4890ced77124d27ae3555cf47cd5dfb1f0a4b83984cfe51d53a56f2a6210620ac6f9c9923950f70bc4c5738d2e4e9767a37a91714d969ad66b7bcf52c9012f15dc602ca95798c59159cba69cd29b6a4232cabeafc8c87be24dd1f02842204cc1332b8afa3f9e09e6cd160704e1ca7d1332b890a2151a94387b675564c52bf662bd6119952be8150c8c4a245ccb22e6db18a05fa121b2b577a94c71b08d7d021a5e64d54438cefc5d2ac4867c10868d68d6d0a4cb48a43084a06f19fdcde7472a84d23abe8bcdd37980eeba6b9ec6a356949159187d4b2b9d8a874312b3b9983514787b102add531e6a640e8b8fd240484b80f3d1ccc95b649b6c2750f4a6397d33c59367685e5cd2530310aa9bbec18ecab748f7545c5ed8c746b34b0102780954347e5650c6c1bfc23e66d7d9f438c32d4903e8326b927d26088b1317c2e3c1aa6db9f1673bab5fd7fbc9d45425b9e413f24b886105edbd1d23ae511b45384614a5ee7710e7b784205456d861425a6382390a0f73fe10770c4f3cf23f8206b410e9639784e632d79fe439db98e46193784ff8ba338d434ced3382bbd790a85ea122dbb1dfc9d474a566b455f2be3fd6d2f9bc6d3d6432eb437accbb4db6dd83271f07828e0857b9fdc64eb88cdf2faccb7dc00be0a17353eb1b0bb6c0d7695e6037abdfca41b88004fd3ad06f7f07ee9c8a1b602c7d306d4e9cb8f75fbbc24c9da340f406ef9b1647524a88c356a14e78072535bcabca2006086b852c903032639bc6a285982625f0b36d3440798101c8d4c531be35db8e9311c43509c378999a52c6415d666c69ca58f6ad912ccf5d91a80a9dbd3c92988222965ae047f8982a412fa9f5aab07407419dd5d0d4fe961da0b013b9fd9f08eea991e4fca2edcca85d4f43bbdf6c3fdbd478cdc2e7f9f9f177569d536f94b7f36dcae2b9a7e59164259bb533f59cc22f97604e83cc41da5c4ec16e15cc6940177352f08fb5345e4da6ac2e2f695bbe694587aed814e117d3737154e07f59bbae3080afc2ff6beac6988862d3700b57b5bce295d8d46816cda8808c4855c1da25c99a205d0a9cc344b69165b1fa50c2d32803cffb399f0b0e94806dedc32864bf1b10178c014815f553a6be84a2953e57de36fbdd9989fec704ec9c0895c4472f90b501d35e95092af866019481205e60e4cddfb82880e5448b9afe51e9d2dd4099be398fb7fda61ddae8610542c683fb0d3750e97e37c055e629b47e4d4e1f268a5700f133d81aec8acd014280bada48a6371d198b3a5920a1b5b2b88ebdb9d4e0f1b972045c21d34e1b542a1923c0a2102d3321ecafee4494e0960c4d798602ae3af5ed1c681fb8e85d4a26fb8119acf774214e61517eabc537fbec3ddf5613264e5ea3f8413ba8d24f8482b94c0109c662e50f42da86da41a2ac34e38bb7991bd6b0038180802e4f9b709feab5b9c444d09473d458e5ac30a6f9bb0cc4b00c24d32f79910ab8e9b36826b73102aa83a331f7d1c0ca8433602ebf935c217dfd29aca8f8e9ef1e3e9f5acf9bc7fa003cb1fe4548b54ace0a4b619b2c3f207c7428ab1803ca56613098d859c885a93dbb01d7a222607e27e71a15a34a7d1d1efe0655b818f044ddd4ec4c91ab49ab18d58a7970021f0c92e99c3a95705a797a99a8b536baa6f6d31d2a74c4c3db08217a00bf8026555a6f869285d79d8a7f8db9a67db608dc994a3d21e07bb7b333c2f423a5b31471a39506bc823b6310441d9f6448c9c7c8aae1bc391fff94419da3e5a6b4c5edd87df9c3fa25b8d4a7da86c44c2406099bbe6e80789c942450d325a5c147f1961ca74b7e688be7ff322616cf4706a9149c9fa114dc80d704df620c8a9b4ce483085fdf63d3154c533b645729c3783c6f4a95537a230c492068e61091298b3cc70e90431e26b516ac8ae634910e1c17f9ee734165f88f366a0552890a246f7abefbbe6438d93b8896be71db18e964ae0f49e9adfbc1e1b987f212148ee5e2ea746909d8f857a17580237b41957ddb2c0036b0db4741cebe30e42b5983f4ffb452ffa9797b00d55fd33f52b3100a274c909c30c60c0907a2ae864b145ca41031eed91fe779141baf7d871b6bf351d82c415f00c7c848f937b98eef20768782670cc962bc4f51bea2c23e3539c6377464acd12eeb6ebbc623713a3b4a22cdaf4c312dc9b3c90d4c1603d54f738e6f889b445d8001709ab1db64c24f057a4350e9de7772d559638ce3456a3852f8bd5f94d6cef422c1f2c40c6892cddff12edbd983d4475fa59bae4dc9b9b1ab9d6b6c3fed4046559700c194b81bd7ddfbb16e7ca2b620a2541c1a7e946a6b6c86036862804e4596db4e4fa6619def02764163512278680fbf40bd6f44485f771cac100d379773d58d2a4d2f21f2eafbd306f26ad52ef2a8abc0ba39fbb9b985f4d9415ce937181466a0a24070956e562bb9d00549fff52aeaa4f4c6cab434d017588775cd0737b551d7b7979ee32c12361a7512a60083907a5e6b97f061a2920a210a89b35750fcbe355fd58d7211e986633101849b9ebf53912cab3d50a61069fd3f58aa08d9f9c5ed69e3ba31563316d25d7ee6c62b7b629cd0fcac976e9bb0dcef4f69eda5992f81ecc1d90af0b5189bb99072377c2239560b8a9df3ac9f9b36f86fd2dc9e7c7b2f2ba82b671d29f11f15777b30544fadb41b1620f2787975a02f0e64d208d2bd3f9e54a00dcff19ba6fd77963227c654c7e358042a7ab72c24c1db6842ab300091f3cd7b37611b94d732b2bca6703fa096d23540b5b1a80f80cf3a2af3b8530e85e388093464a98c3e30fccd48a5fec468d683dea74504f645446f0ce029575e879d855acc68cba782af074fd4ea41169c71cc488d861d31ff20918374900346222eb083da3251ba1ae7cac6d26ca1fdb3d0ad3595f4acb0679855f6535f8d0d954e898eaed73f65b62367857daa729c5d315f223cc7502bc90fd1ef70f73df55cdc04875b5f8e387f2c1efa73f090e4320f31d8fb62833c5a052b2405ae5886314b98d6330309c621862634a6879816a73d67302b6a1a57f10c2f3e627b5f1790dc3f8b9f4025aeb03fdc30744dfdc8c979985b3a6096abd32cde191087125e03b9878f431e0be8e353a1804bbeebaa6723c9832fd53ae5ae10cecf0356a1c8d0ab70e720a280e69bbdfae01b3c7cea347ae04cafbac9e9b3cd707c8c099db3526c06803063b427eb46e4116df13d33c5e5b7df524dd1cc5bba20036f02bae5968a1d00223444030a4c06795c308c4a4e07ebde33e77b6cb6e745b30d681b967e9329debee0e531201ed67bba102646712e7e606ab87e4441a758046cea5487196e5741601cfe3abe995ee48e7f0323a400aabed1d0da27f5fa5fe013c84b437bef2de5de524a99648107790758079c16a78b2a85b0f3c8e5cf38ca2f7f06da5b083b77dbb87da305b88dcbc8a8ac0a5917f7e5df3e201db41657dbebffbe22a128d15db509dd44af71dfe2083df76a5b6de30664e7f56bedb5cd3dfe4dd4e2aa4389a8d7dfbdd6bf3312d14f645656b9fdff1117f7ab1cb0a4b24e54517295d359c1c5aa11f7b54bfdc27dad155685dcc57d0ed8c9ca70dffda4a437e6ef5125395ddf5330ff97c7215403221ceead444037f7f641b8f17762a9f735da9bce155815a2cfbd0c532028a80c51c392132f5078808b65220e15158cac0eb54aeb1b870c93710a0e51462459776b3dccca00be6bb50fde71eeaa268a289568ca2043e9044c7630aa72c3ab7a400a6a0b0bb3872311966a40410a3166ec127084c8709ac4c0e9616b72ffde264b708a6e1370cf599631683a24db15b20fa626094e042e2099b1d6daad5a6badb5731583765d7d5a6badfbd0a696035f83b6f427cec974f1fbad368b4319e816ad2fede27764cb0798810e43ab83e723c4ee2da7d4bed16f92506a69a594d25aeba6744b736da873debb8f6cee63d4086142d209a6086302154bdb96ac80e3c25695d4eee33d6729a4a0046f06b950a7547fc8fa4cb3b9c0dee5d68bb6e2eec6070469427f34a7fc6e361d757e90afd91e382be34a0a5338144ba8084848418924610ab25d6a319ef7939294d2df58bb8f783badd537a5d4d234de0c0ab95615f10cb5487e145a47657150b9f8a8ac2898fc69d3a10a0524f404184e609182e504242758151698105984329e0866a0c061db4184378345c90e3fc867cfd995dad67bceaed076f9addbe4cd2077771daba78d49c5785f46e57cb9daa8ffc97914ea518f42bda638b647a13e9c17d8478d2c3aa0bbf2c7fb9cf113cb0af6b8f77239f851a811bf833be8bfcec9f970dac8c911eb8362f7d543594a5a1232c7b536c77dfe8d02ad9cb9f74422211cc0078feda3e7a1bca172d2bcd74151169871460fbcf780fbcd1bcb567671bb973a3cfe39cf23aa763e14e83df765ab14c29c88f23c07776c5fb6bccf62d9da1985aae1799e8ee7fdac42a9f7428a73c0ef537af4b1cccfe5926c096d9abbce3fbbe7ef44cf9efd3f686d6fed2ebb73b873efbed6aecb9ca69e145df7572cf5f61e7ff77df5ca233beba0ddb443ecee9ede21768805a1fe160b945f1ed9de885fecaed3e9c65201a58f7d7f47cc93e59f12bd8ca78dd6f6cfdb7bd4c7b2b5bbf7442c96be87f6edf0f85d52dcf7eaec1bdebf5fe78bed63a980dd3d1ecb17bb0c3bdf41b737e269c3bfe5e9d04a74d93a7bceb064b1f9f1db2be2cf6287e9d74477ffc1d0eebec478d30f81288e0f8676ee511ed95dfe8ea81b4bdf2b6f042102dc1fd9734f8ae37ee7fd043b2f7fedf2571022b0d2c037f7aded818fdfc6dbe650acc59b41d7ea11989a78282929d19413b4ee50a95695c4de9d1aee96f2b2713470b809e71d1472973aa1f376da789f0a4a8de15ad55dea91ecd35a6bad8558d1a5cea1b5d69a47cd51a291e8d1aed11aad512154d786425dd34f18292d618b794c25944e3da1b98d1b794c24362567d2e1b123839214159817dcafafb5d6dcf64c2237ea0a88d8b4b6422f09b1bf94a209d5e05dc0021e67d3771e4f56a1eef36fba89498f2e94e8efc1e71e7feec6559d5f470ede1357f37356b31b577304d2fd4804cc1c8178d77bfcf9c16f9c235e79231ea70e71c5adca32a2f46ef14e4812d3d528436b0d7df1f07ff93fd1878e203527a774070999a6e1121065f99626d910c9a3d5c444294d559175a94ee5a42555268fd28406793c86643e688e011d7f4c0bea73a38e334c581d4ce872d264474d827ca1d168349a75ef8046a3d160f8da2062f48deb94db6a8e011d7fbc0478db797344db82bfd0b8cd5ad5082dcaf2a12f5e130a4aa275afa3e6ea893e74705059ee44e3acdd39a20c1502a29ee45bba7c411ae2369ad39ce65b10ee3dc80c528530a69c2b15d3e5c442640d12547a6cca7854a19a0f893e5e95cea882873a1fb0d3940db496506daacb4d95b99baacac6a9d1e5d090fa55001d0cd19a001c03cbd1cd2fde412229cbbfa85abaf9cd2938960e0d6dcc6d37af5cf0b056edd85a6bb5b539ebcf596da57a1363551fa86a65532b1b7777fbc6f278a0aa950ab55aadbb6d7776956c7a7baaa93d675962b14dc986440f499cc95e9c6c4f9b52957befbd4b565051955c41432ec10d97b3e72ccb122c66294e94ba74c512ccea50abb4ae5966594698a5304496a5166dbd211363062b495274dd73666506194cac1c69d174cf99953056b47c332b55665644d8df9eb32a5434616940228592cb4847820c461449153ac45c46e69922a6334ad018d1118aaa78b9280e3218417379db5745fa324a62e48a4384acc8e5e1af8a5e53f2a6e69a66a0bc70e49aa1612a7279de57c5a12e5d602eeffb2a1123b336c399970bcc2a83fbfef78f6c22463e22a4bc5c608c7c415489b9c05491087d45b1c2e4226284f322cb4d60ae490990f355b1e55290400a25971114938ba404407d55f41f8c2863e49a52e0f08393cbd349d1265f684c2e3752692da2d29f9545691554da747111f13756b481e332429b2081ebfaa82ce79264cb152e231e1043dbd048e4c2c0260531b88818018f704d4a00235b9926ae23faf572557105a4522a684f4cae3af2c0d92908168ea8d72cad793ffc0961387fb8ede2546a47ab5cf5f1f418d90955433c61cf900ff5f1ab52a97878787a7a7a7c7cc8300c7f7e7e58b0f0e6d461dcddddbda7c72728fcf9f1f171afeed5abbb7f2b5fcdfaf3e3eeeed5ddddab7b75afeeeeeeeeee5eddbdba7b75777777afeed5bdba577777afeed5bdba57f7ea5edd3f16fad361dc5bad5640aa162e5c00bd78f17a6f8aa238864463059ffbd00d5518a4220ad23060c4881123045285402a55a8dd9d060d1a34aead9486cf1f202090a885aa45a802c2d385d3161f510b17ef22c8d2f0c58b202272877beeeeeea5915dc7175f75afeed5ff5f14c571ac00dce1a001e80763c6a0313cc6ada0023003bb71108c1ad467850123460c13741d65c890419224eb63b166cca001127d90bf0d90c823d23a8cc78821838824594d44feac1b435313a9767cee2e43860c70877b24a88106f60542f0489205ee98f139050dc07aaf5e4a396c67ad7a33543342151d67801af82e8f78343e1d46d7a861e3d361b407000058f059e0b5565b1ed1416b7c356cd898b5d6fadd7bef68810ab444d7b1fc2cb0a0d50a400004f0b93b6500166f660e7fafa7503771b890677f8229b29543984f341fde5459f47ba2f0d74b6cc0e7a0748608c1ba298bfe8092f43bf0b40e4a27b5a3fa3014150f17180c96278b3a9329e44dee4293afd0e42838acc98f9a7a1e1084e6c3000e9aa8177ede6b308339f3260e03e283a99bacdc8180f766479f362e3562594c1e3e9f0f48843f04ba3836ae088802a9a031ac49112c870687866cfe0949f029d96afd7879ebfce416b4d431720702721890e7e2d36174e9b04d5f7c1fc5e10e03896aeac54e8a851a6db1c65edb1eea032a17a4d8710383f765091e304ca0dc69fbb73d136cefadf758079dc55830fbc2011833177c8863a685daaeb5ed3997466b6f81661b102bc10f3398d1326506850d67542d8011807d6dad6a5b6badfdbcf4b0b7b75b0b5af63685fdba98f12245972b33b60d2a427f1097372c8badb5d642e00a9b1fc8a0a04413319765b32d0d4cb605b7b5d65a1e2ab6657078c2d21d2c1026b3ade58264db143e0a13ac5b4f5b1888bb1ca7a5cc931f2d4c2b2d67aa84492cb4b489f93cfdac80e449cf9657b80294253c1a2835f9ac80258a2a4bad6705189cec24f18c8192d2f2a4224267470450b42a5401565973b6c2d3955814a31e578e909e662724151183cdb68c800ac7719c15d9374978928d9a8c683a0a941e28247179d223cbd1079cf4b0525466498f2ab42094f44821294d093db6182561420f2cb4190b5158e9b102d3cc032454e9a1a5287c41abc228ed527f8c1b748bb6ae93302a5d62c0f4cf9e332a60643ce99e3d6754ccb06054d4e87f2209a8c3708a538f2935fbafa9b899f23df941a414a51e5298286b4609fa1b018906f79cc59eecf2ff63574cc83ad4aab047512c07bde365c60934169344e33232aed0dc120d11f477264691d64acc2881b652b068d076cf59ec0d0d2ab11390446164333054d4b0e2031812642976d0b7948ef5edb5e38a7a083cfa57a4b4eead1ce9d7bab2550b92962d974b7ed15a90c8bea5f6eface307dfdefeaa7c071bceecb2b563fe1ded9ca356fd4e52b2cb9615cb1a45cf2abc2c6b183da8bf71c97fb9e0af2217d8afa8fbaa66289a7ab922dbbe46cbf1533ceaa840dba8af0dda69fb07e5f83a6b503a526ecf1e7c28da75fc1e47d125de5bbc567d774cb75b3b3e25e78341344d8ee20ff4898b34321ab52719ada98b15b4bb01dccb297b3bd60d1b59a35aa62a55d9b441ff0160740794a4a2149a8cae5f6270532af49e6002aecb7e1551dbc50aba3ed2a6235cd7e746d7b1ac62362d67354252b3983a2255a34b0be26a9a5fface1914a4edbbd47b622d82d1f769e6fe0eb97b08e5fe7e1db978701f73f17e13571b16b9747fc5cde954d11757746cdda0acfa198abea3f883067b80e01e95687f6a995021216be7914db4da7e22816117fefc9c08c32e0e7f16930cb9b8bbda461fdccf0d44bb70b9fd2a836e6fa305501b8b738a46b4bf6f969d214b0b33a8acb2cd3daed3fb184a5c6d5a4727f53535a6bec8ac20155d525965794f521cde7bdeac792c3fe86077bf4d173dd8368f8ea56ff781c49f925ee78d1d45a1b4fe3a16015344f54f3425b7c75f71fd0d2afd6a77c6983e904a47226030ad78abb58ea8ed270af5aa71857fe7552b20faf10864e755757b4fa5bf52fa459efa984c4ab45e478b7efd22740c2264d2666d7374e5fddc65def26800bab3d7a2c4609bfbec5b6d0aea8d1ae0711b29121f4850afc1efc421746fafc5557efdd3851257795cfda88ffa0df4d3478df48ba86812c87d8e48912829345ddcf66da8df60e77f679c95f58d1ba08ae8df54e26ad33c4f44f528a1e9caafebeac77dd42fc03ecfeba0358f3c63113dae7ed457fd4a35aeb6a7f4f5d3d7aa5f6d23eaf5afc447893b44c00051bd1e8910019380e94252040c109ed73e5f84877bfde1ab463faa2cee9fe7555ffa9112e1e845af373ea31efd6f8c3b5f448f2efbd28d94e019551c87f281a24828c93d4adc3eab258a46a2421388990a6b36f7f3a94c61082ecf8ccaecb27ed9dc6fff65af29c198ae3421e18c36f72ba751927beead134fec922e39b14b8a4604b235bba46b36d7c42e699bcd716d7e3a5192e3e85115a2322aa301ac292481356ad4a041b3b4942307498ae2cf8f8e4ecebb3651b2be57b10826b7a8b188116ac4930bcc7d9e31b210f5f55fe7f5a833fea847b2b2eadb2094bcf2c54214bc2ad3022bd22b94b4f6099daf104ab5a314d54a631b17c58a2e6952514c6f493c288eaac63e07f382fcf6194071786f3ffb0a98187c6fed27828077fe3eca1431d91215eab9625fe9cbb67eb4ad97d93607fc666390b66d6d4b8bbc310a25adee9cf407d3c6486563280ed302fb58a462a60dfb3489929ead688f8d3ea6d1db584e37442440c59a2362bf0405169c5ca8c713d079fb7a5ec0bd5d0179d44864b5414fd24b8f458cd027925ea8df19573bf8f572a5c6490980f5d7d4eb4f8d9f7ad4a7c62761db79c5b6938a6d8b448f4d1bd60a5dd2188d6d238dd531a6472221bc5e2e8d1a894840e9ebebe5d2634fd2ebf5727914ec803783c017a818b9ea1f71856f74fd20eb33463086852663a490418c511263f6449ec9e10972891c9a72600aeb9a5aaf67d4bd2426c98e729abb2426c9ae6e1a773767eec720d22fbe24263bbf2426c9eeba777edd2f8949aeb31f750ca85be0bac6fdf246d0b3f6c67dfe1af7c6102b070d1af7ea7b6fd4a8d1ba31c4dafe9406ae5af537b4bb6edbb75ddfdbdc1bf723508c8d7b8da449920b8a111463cb01b7a018338ef03c4a291d29c514079d94529fb5d66ae9acb5d64a29a553b4d6d25a6bad94529aad78d85acedacd5a6b6dadd6564c71688a81b51587b556bfd65a7b496b2dbec1db2e75ce568a73cf58abae14d56590a8d2c7b456d4fb9cf306aa06fd26a5d6526b29ad1445524aa9a6d4da9be7eb18dce1b8c3718763afbd1b9c6b3738ee70e824b79fdbfb06b67de72ab7d56b647b1b277beddde05cbbc1b1d7ed7577270be78b3783ca5a349d668a2f77b76ddb360e5f8e26f99005ee84dcc959f4e79eef50388efa255d12b2d0a95dbad3c59f76af7b3e750caee52cb89f1d03ee7a1b77fa28491fc438dbcf74733a42a716751aa24eee64371da12c74faeea7efbb7dcffc29fbbee918383939dd29a84a958b76d80ce99625f56b991b1a74dcd480044de726072414103ee47ce3061876fe9c6f78da59cc6c671c9eec8c030876fe560e5e86f4dc81e382366739ae862336670396cdd9a0c4e666376ca9810a8ebc51f30cf7c3be4928ec7b3f6c61df59d2153430792fc4c84c21d3b219b57e20cdae3448997de0f5b29d3d53c133d46070c0aa66681234c311a784b540481c60010509e90ad212dbb2b132c8b6d5019b5073c0a187292430d44ba749939cbfaf634cc0129a5031acf03169ca89e18b134f098cc106269d2a6452f4cd4ac830981743104b7009455d0c4d4eb825b21c83131dd8f2f8c304d92c8627273f9c182529e18712a6274b7e3429aa5204f362c20f136a33193eb0e4078c89862f84474d1586155186d8fa67519678c2e950ab42a638af598d8928706a90d2c40629566238d1a83d67525c8891468a0d240b5278505531d3ba33cfb7879a73ce7b875c107cd3af7f64eba074ee0952588ba3dbddb62be309adb3e70c863755681f353386d03c3ccc4881fe6ea061a4c13036c070820a309040bed1e0130d3ae584084767bc1cf900e648461e25bda0b4cb6f591454c7b9f33e300744695027b5a38259dae313feb05801b570e1e2c58b6305412f60c49041b266d0a86103002ffcf9eb59a4d37c727ad51cf6e44a5e3accab05ad00086000376edc20000ea19616d779a13e2f7f1d06bd5287404f7a2890e8e6b80e41206d732caaea8fe2708da4718a32a13f4a5677b7d65a6badb5d65a6bb3b515d7ca430577ef56ebc5b5561e2ab4bfb5d6da5ab3b5d65a8bc9b7d65e5aa9b5d65a7b456badb5d65a6b3d6bedb7b364684fafcb5e9775a8ebfa4368fffb41d0aef5a2eabdf782f7de7befe675f7d68c3bee5e226cadde7be79c2048543f08c869bdb7de5a6bad76c35d9e5d9e9823efbd97db26b74d8a290e71daa8b372b7dedfeebd3af587ca9c73cea04aaf7fd0aedb7b76ab7bb31f04590fdf6badddbe8fdb3008125dfbd9fba6cd41699dd40eea9b14476a47c5d393da51599e7b6f8f4f55f1f4f46c9e9d3d3fb5afcfadd5faf60189eefb7cb527fc6ab5f75a8b83c577efd517e8de163e55bc66399cbb0704614363f2e14ad5d6cefb7418c0c109b0b64934101f600eaa2a1929c551ca41699d8fc90829d0493d06a6902b391425264a2328cd5a5532528aa394da5165150f16cab55aad2664b2e867803a95f089762578c6d9743472a7692384c27b7cd8b051a9787eec1552850c096148085a488e1a9bb0874d0f143d60abd5f3c3a27ab9f3bacc429c17bc601ab88ac87fd808a9d56a405688650394f2daaa039ebbbb7b0577d8bb836ece49575d5544f6c694bd70baaefcd448b5f6de6de3a6e83ababbb5eeeeeeeebe7118679c73477d568cf3e79e73eebacef3be1a443be89c9808ccc9b19bee54965277cff33ecfc999f6fb9ca26a543b83b46a72dbe501411d46a374ddf56da523ce41a1505a6b9d21a21c3b7cdf0853a9d40e49546307dd2110dd2a1e90082889ae63a9bdd65a6badb5d65a6badb5d65a6bad7574f7fc5d1e8968c085abd6eed5bdbabb57f7eaaea3a3934aa5767676542a1511518f4f1895554e1e4a1d746010310407b90811471fde7f4e854dfa449f6a94299bc640a21ac3a2c1112f81c928c42177538de85315aa5b6a14c7a0569916d047a2f5cba68fc2941acb5386e90b3124697dfea49422a184e6508ae4068922d66ceab02f94525a4413344529a54eec1d4a2b0e4478e0043e74608511580e6cfaaaaeebba9f212165776fbbaeeb2a120f605ea0bd5cf9f1e440a2bce9edc3ed081cf6f6b38aed618448a20ad9cb8531fe376208a618bbac6184525396a61a0864455071c66e7f99d43cc984465f950f46cc0050c43d7387b8730aa625fbbebd976969df2150d8778811ec4b44d2bedf3af364df1fd27106cdbecf030d07f645d3b46f1a12f64dd3b46f115e5eba0a5da6cb2484d3ae2f5ebabc340dc6defd68632c44d3bb80440a2e4079c900b36d70e802b6865440a4400041800922ceb68fdbdcb80104961940f0b06d8e7d69afd76bcacd21892b650389364bc6daa06d6bb0586d34e55463ade5b66ff9d93f6ccb83990e98e101cbb64ff200c5b6df1a62b2edeb506261dbe721dbf67d70a01467db0fd203d3b6df8107b69df960c5b61fc20f4c2f2db0a23073b634c3b63320901cddb9b570a40993315a509064042c2cd828a4783c4ba66bdb33d8be3b3051e605ca832e75ebc60e4580a006aa103ec6586bad7fcc9a272d2c86e2e9c598374d80546113b226949a6072118e8953b452d3444d143557d46c69f1b22725b050856e62405816bbf23302a3950ce94aa8e689858ca9c86789a61f198d4acf12b05076022a3c163bb1e4041a27d4f8844e4e5025e1d44326cbbdf75e36489435c59459fac1060d0f48c491648e8a52aa906644eb423ba2acb9552119d9131dda0e19214cd03fa0e89222a326ca1346984ccd494e125d34193831d06210348180a6759862429b241d567852c44b87189ca461d241c624044cf88726b8a933a20967c491114959ebd044d1e606ebf006ca9dd8e2354a3fd630717a87244eac2a8c628a1f51584159739758ef1025ca089c76e80213c10e47454d453bc8a614a114bef036806025a9e0c3942f58b4dd7336850c8c351aef399b3233010833539a76a99f86294db08af4ef399b52c5192a55c2300446e0cffc16e4a19c4b3ff3d72f73e89f96b2c0df11756c624bac6298432bea5c50ce337bea7c39d3f89773884e447d8ef8bd8b3954ec6ce888e564431ff42270a744d47ba2fe1cd17b1d31e7679b0d72306d80ef2dd9d068a858ce1e76a9cb09c410bbd47ba6d99a875c3b3aa2426a9ee7c9947833a8cc479b7b8ffb12dc9c36f7a56a03c1dd9a6cee37cf0a4a72db6b73af83b2d8a5dd774baa4275ac5b52cd3f9f6e4dd306f7155ca14b2e4efe42716cb5fbdc7369e615b93493c5855c9c79c1b46eaa0836c7856073f768ab2579e8ddc7cf81fd5c57f46f5265713f5dd8c73d72e12d147719b11125b91f63dac77293825ff7a80ae1e7c27ba4b967818db82ed8c7d78fce09bd7db9955b93d1e67eab71b5cd4dee5dd034978f9ce2ce6883ef91b5770a4121d0f2a042add6745971ba3c178f0e9a73ce39e755b5bf01f9f4ed58040c10f2e9577b7f0392fc4b7e9109ae7ed8afbf01f9f48afef68fb8aab8ca9ff30884becd39e7d7599c5b67189651127c2ae2a2ca0261c4788d9594d0df971c6c73471c8c039f2ba238ba073f3f75af0d76b00d96dd1a8a83e7c1cf4e1487eac1cf6d288e9d073fd7288ed4839fc1cf4854686baa2c30a8829e9f3497d96036aa4239ccb1581592f1e0734719e87111e61e7c0c23638ce573628ee12d542897a12cf08fd0a08131967329682c679959a682b1cc91e3c68d5d724fe44ff0c7aae7ef1f71ad7a80d4bf2311303d4f64044289904fff882bc7385865818fc7b1c45a1c4b1dfe58862fc6f2398e5659e0932ec6926c31963fae46163fe1586a9fb1958f36f89e7f3c222e8395f01266c26828093ef9d365c5551e2fe043c6a7385815c2f8e9712dc44e1ad3b00c912345fcaa2cf0afc81d7134b0e48eb8a3a31cd59554361a734c2cf118d3250e017e55a1eda8b2c0bf9ffa9e3127bf6e4ca1679b0cb41da58e8e36f8d3458ad3c523e63594045f2566274a82bf23e636b94649f075c43c0525c1e76855686be26038d35e18a68b3608eb621b7cd8069f3baa421989b2c0cf6a9ad0fc52584667311b3cda60ce98c71fd4254397d1dc13f75431e69e9e36f89efd5a059817cc0d3e90fcd2d9bfecdeeb44164c3ae7c11e3963d206df836ec494fcde52f27b1dd43352ceb10450d71d73acb2c0ef9cd0b3ccb1a6a6a6a60d7eab0a3960b2c007a3d8e0e7b8a0e94ec494ec5e8b9692dda3c41c31487e308319043b2316748506fc493e08165901195b3f8b606e95df77fe551ea7cbe6d873a2714adbe8a82c1087f7f33d2cd6dfc0f74c4524603cbfe713ecc6361c5416f818fc2c967a83ba07fd3d8ce679b40dbe3375e07b97fc20cd20cd20cd60fef2c17798831a0cc1f73e1104c1ff3eaf29e1cda0922bbaad2ae4faf7a2269f2d5ed0dd779e534ed90f77d1a78f7a1df451e07ba223117d54163864dbdfe8304a823f4170aa9ad0df7bcfbd40eeb5c1df68756eb40dfe26ab186fb20dbe0b9a2e72a3ca025d1ca1ed57071feb2ab43df8215985eea3c632c49b2be262568d7eeb256cfa7acd765a43700cfc0625c329f404260620e00d42d8c107f3821bf49dd20c5c844d472edbdb0ea60dbf42039feca8c48c9a6962e9d08c00004010002316002020100a87c442911c87f24c14dd0314800d65823e665c401f88435192c34008628c21c4004280310418620c62caaa0ce82a27d81a1ac6c4d01061b5d9edcef91f453135597b57896b298b5c3fed1607cb8201197f8f5f0b436057a8d99f0bf9df4b0982c9d8a91fb0b66e869a4f43fa1ddd75ec69db3caf7c264881990dc6a65fcde8b6eb2fd58728b77d373a70d78b6ab8db30c1258e45eaa8e9bf1378e20dd0f39bce6dc57d9ee720f1fd0bf4ec72b18683abeb89f8ecbbde6857bfd033626cd43430c9911a89c73558d1ade1cc59ecade19f68984a28de704b00c86e1033c924462667a000997ad9e6656fff6e3aa9e4c3221c21951df47598f989bfb3a747f91fdfadc6fb8a6910d74f90fdc7cb2040a317413834047e19b9d105e45e54c75b7a7c8de76a4a3cb8b2b970206acbcca66c79930d6e481c8615bfce2dec4810865abf6c19f6a0c1c2a98c2f23c3bacbca2dcf7f44cf630c6ec94b5026011c8f8f599dfcac8e5c73b2d5dd7c505ab97fb19c9ee6409494fa22491750fde0fd9467f535c948f53a394e0a3fb5a51439a474a43ffe0da2f0fb6225beeae4d2bf425289b3d7d24d1411cd89049d38e1905cb5c1f58c5a7282a2aebb4b01a7f3cfcf1e91334257e1c78ba5ef2af4f6591ce6e3da3c315dd24bef1d6226f74ba6169fd7c5f7107c2e78bd0bcdf326af270dc269fa5692034440a399b139884ea4386b2e6f1818c921af102f9a4d4776d6e1dacb78121feb114e2da06156ece0b1294d364a6f9a945644790f06d10b189394897602912627164d109876a7d104b94a02d963b04c98adfe403829338635d40289c7bf7ec49b1885578e65310819420c4340b0223899f025f244a068b79d492266c9fcff60100068c9cf3b66322c9a0e59f6c641e7810e1f95b821c769ddc56ee1acf5984945cb94453feeafde9aceda418d5068c6ddd8431a9b2431330886538fb0b5f29e2cfb904739c92981f50ba5a8a09900e1cbf7ce45b53a3412d2199c79f3d73279ce7bf8c76555c28536b3115e28e1cf42d426884fdfa5657aa83f4506f8f2c20a335085c96c98e8c841e481cdc5f8e789bce4f5fed95e47bb40a8f0d98c2bb2805ee504f04ea7f02d6119965c5fda975c2f4f89a25a6b61b4201ea3aaa0a9a9f0ebb8dcf3e12b9c10b0b18ed82b3cbd7e99bd98d85ffcc22551f2a8ddb51e579d1c2839cee2b99994c7ff68af798c5550d55ebe53f2fd9c7cd7fafe95ef8b372883c2fd75f738a45ae9d2835979ec2e8472f86e2e9dc91b3027eb487aaf74b275c9bb45fd7fb93b914fc20c0163e2556e6820d3e87f737c6b4167f9950cd460687eaeefb159345275223f0d26ebc0348d4feccdb3c955e74251d4956a1198835f2549e842e1725db120c2984255cb18ae10a0ab8cacc0ecdedfc96e82e6954497942952d6861f9b4d9927fbbd06b62bae756af2d71b39b2891f5676abde8076e883d075b9e6df8bcb3856d6e558bfae510b77bbe76aba13ebbe1c8ea3c63af59f36c84d316917d238164dedec159226a108c8ba947c9c62a0c03014eb9c8d509ffa5bc05e41607c6c1dd535b1a1aefd55f6db9f96d7a3b4266d2eb39f69bc491c6705170635417c76f54703283816e6819c50faf07ce909da0355d728e3cee04d1a8a8918516c52d4e508ab22291c139e499378066fa2098da96ecea3b20d632bee696da3f856c7114bc00c7c795d6c50aea4a5f77c8e8668e51dd5269c2ef02b1c48dd6419f6cbbce964b8875173e27282c87f2e351353154d91a39bf7f803d6d3d51d912d98309669d2010b52e5839308a2069b02be7d3627223b48889c6cb50d6cfe56f7bba647e6faeee75aa747dae0c3b80d82a1b20897a22569a1aee7de6dec850fe285470d4650fd7ed5bff265cf56e3e0b7aa584af25fbca77bc576d8f8e320a50a595325a07b4a7cc9fe66bbf2e1dfc3683c6a8f769f2645cf49f1961276f04f5a9bc0cb9380747d1075107a28e7a5b6d071242cab1cb1e0ea9e3f70a6e4bbeba8fce40b75d97b52356f987ab24fa187d438aa92fc23803271ffe4d658dd7f9d4b4fdc4ccb8e39ef21b13ac9f116ea22bf08059a1029821fbea8ce91694f634fe3e20670d28b1805ac5bad5c52de942e18e92dd1950384b594cf9ee9db5fda8399a017dc3204bd8ca37c8f9393edebcd9fd887d35a00d06b25c7481116d85c65c45232f6495ad569fb53fd2efa112bea7534b0cb2d399b4caee03b8d601c6618d213f97632f991829a2d23a0e988a01184ada7b7d6a20ab748f24b75b7acc32c90d74eae950073858dab1ae2a47d95f83d02dd2e42ba19c89c073e8394857ff25a567ae5be5d255ebee0367125100a3d1895a5c4794c0679555eaf33c0f7fc85480836be9d8a913631f4c1fcd008e5d0d9df2ebc2d77959d0417b9eb31165b5bea1430194084028e88c6f2955dc9b9bf641c3a6a718d1c0ae442a5378d747440564a055d57a5e6b40dbd02471728212291f7bcaf2a6ccce8b9c4c47b03330427cdf29f9263bdc8cf8021c8cce00b28bdbf0af40bd2d25e6b0476e5eb662ee0ef846570cfd469589934a62d5cb8eb8ead95dd475c37147410608873b81452f58247c2bf07c80f241789faf967072df51961e9fc4b31927fa71ce272e5c8be516e761f8754f05d74b9bc8074ff31ac4eaf56e8985bdbd9640b994fef6feba9e81defd161185527321d3ea19f3cf19275ea7135a42da5955200b2a990c4d2e6625f8cb513ba220f1d67fd3fe90b28eda00bdba9fa161133374b3d69d9663794b9d60e423bb4f92222e3c011f1367d71055c4f9e49f007cd48ef0c6b59517c8d34f9cc2895bae29adca51953b4d75035c7e7b437a5619d2e369b310823f197b2411afae924ac4111d40eeab670594b05482e41417a181c89128e3595a6d09c0db69a6f9d5693a3fa789ae4c1265e64768fb4ba30d5f41b7e896c5857a57722bb24c2dc61bd42f36f78b5d972703c07644f72c2b0486cdbf9042177938bd4194fc2564851ce6342ad4d864df71fcc4d3c6755480c9a1ae1eff08b461d3404ab9a28d109975dd48eb47b23a73dc73228d20e0d6981777c23b0be501a1084d4f5cca8b54773e8c85fd612003940f499e88862c0e7e0b4bcf235a960fc5e925701b59e568474ba462a9ba301524dfe5bfea76bf83959878a77611885cb2265578cefea64cc9a2456ff9436bb136ffc38633df479f5325a5bd0587342436c0a3ac35da5f019d2f536810e36076dc04219cfb3ba8fd1fb3987d7c4782e7440bdb1b8eff106a67e52948bba04147c8cf4191cee1aec8d2df88cefc3ff8cbf5b353e4566453b35be6d199fbe7d073f90b35a7833bebb682133595a7c04631904f560f184f8b8240c30dd5a0e449e2db9cb5127c5a7ac0f1a70f57da79237af81cd81088e977d097e61138949481a1137b4de92edf28df8bb787c9102ce7befce19e382e5b761ad2d03d3b71b0887c05029fc4f7497ee6aef1cfb1ec7172feb4fa09eff82041ef92c32ec5de519defc68c2736e9c3edeee16bd2f794eebb42bf65dea99b110037f29017aafdcd7d91d036569bf32e00d8b7da8925ad6fe4eb49316fed23372c42983cae806b82a4f7cc58597a8f01fa84db9aefd1e48f975b892764ad8b1c3a7a9fcc9a27aece21e5883c40bbe52821d8c02b40981d6b65fefd7922ab78966632ebbfa852d3d724b27093a6b47824b0a6b0d88d2fadff1f5901abc0959139faf3fe39d1873be45e5fcb7ed134990172dedcad8e998158b2bbb5327e5a404d95953e1f51ecac2561767e18e45e2d0aae56a8c471d7aa7140f8086112591d9cce551e38f0ebd2fcee6ca0cf604986e4d1e98527302a132d103a27077a31d525c88986c67cf4a82ebf0bec430cc8f1ffe8f92a88c69672cc364ea2b5ae017ba9d74188f0c75acf22dea894fc5a1cdbcfd916491f296fa19c755201e98a3a190cc5ea987afaf44006da82152eac9ca23e503729f814944a78a604891d0a8d0729dcfafb6d58e98603c8bc8eed584fe24c37ad1b7c09658f982fb7cc585f4f8e7e637b3be7c20dc89702920252588efbbbb8bc3537f892a44958ce2719a15f1a5ee67e5b42068d8efbc16bebe6dc98000aeeb5cc297310fb464df60bbd49aa5265cc59c4134a009487c1c97aa94f53c318170df2f91110acbc96d6a289bf75376ea0ec600c3e52c5cab8c0743e878335034ec53ea79c790e617ed5ca5c6066885f1ec090f3255dee96b256f02e8f7d36fb9c1a476cdbfc23e3d68d2a31ab99fd207e6c60e5a74b6cadfcee3072e9a551c4118f3e28faf0be5bdebae8015ca5a829732ad95e599a9348ed881a992c356381a5b5f0df5aab843e029e0f24ef7e5b5861a0291b8d5a124feabd0d0c8591c19ccf10578d813cd4809e55b18f625f879221052f514ea5629ae02e7e80ff21f9e0f680cfd0aff1d1b821be69b73d9a34fc08cf43260813c0ff7a646541136e4394c5c584ee40feb07ec8f1def6592c9b624f73e47dd7542ead4b43b22b47c40ec1b8243ce508114229a823c2d9a8c8679a0c7133a05be73b83765153decb64c12d25895ede4ff315904d593edac9a0bfe41d5d958ca9c999c775e619e2af9d6acb34d2c7f521e11ebef4dcae296c28e304d14410664e6cc9ae8edd0bc5eb6d2c68114745428a0c29a69a5f0546b2d3d2caff559c304e074f7e5c6727ec59edd35216adbd139e32bb199d5837ed303550a2b5cb739ba2f977aeb92a304c20481c9b834e5a2bfc25bb7d9e547f640821ba1a82c5526abb8014898b0b5d051d9cb5f28a445e19b3a2dd950c01204f1c7669ae2131694420c44ed42cbfae04978fa5c57548acc773861715ab9c7f047d72420fa8adde633d1156d5497a5329dd6da7c2b340897cc030917c67c974e145fe28b04d0fa082f6270fcd7443a1dc948c9c297f66b40da218fa736c8e30d469189d5fc6675bfc5f9b5aeb027c3dfbe5df94c11f74226ba670c628ede39fc2e76321503e2a61a02915b098f6ed2584d053360e58a7a6a9c24cf8b7704ed642d943cbc97d07a992a1f5ecaebc20401e7a0203729716ba6b31ce7ebe75c618f998644fd9a890b581586d596240c7626a19eaf11c116e401875c37f5c7d880b10f92237f941e269710c77c07a5acc91de1e4728c5c4d89f07a235634e62f219f18efb3616102006a12c9f03cfadba5514d5a52ee8ef3e3bbde7c2c413a2146ce87e934659d4504f71b763f423ae176e6af2a606a6170f834f82346d632448cda60f28f874541cce70ef4136548a07498c113394e64a250902b398edb23d3a2aa588830149328f483ac398405be16f72fa02b0c214c2d2564c4f7338899174d7277ab2e2e81cb2aabb7624ba23bcdccb7adf73836718044eb753229d835c634dd9e04f1ed9c1623006d9359f2159fcffe3e4f5acbfe2e0f4a815a080ad0735a752a6b2d7b378c6ebbb439450d7d475eaba29ee3a90314475edc8248f814f88aa0701cfda12108a323942e5ffc5446ab97457776a384d1e7073b85d9e8717a91b9ddc1298f4221817d378a7f2f0ab05d3dfafdcacf15462448839c82a2020495d44204bc49a8fbff7f19000d8393c670b92f5e28f051a6fd336f00651d094e25b23325c330a803c32d013db104b79129954d725bc94b807d5cac913c61b84696ce5574615d9d5f6b5ee87ef037c3ae921a7477cfffb77e9e76986055f79479bf16e1c572bc58ae94842233149a8845a279ba6c67a8e6fd357c1cc47baa6257fbf55bea7c025873bdc1ab3374a3ec4df449837d3e841ce9a90055f78f5c05e9f5217e1437c09c05135d35e3da575ea1c2ff3f90c11cd0816a01ef9790c159b19dad415457589181ae8b42bc4c9235981e0d61ef29e3ddd9747826c5f97aecb0063e132857176e11030b302c1d3ffde49776f3082c3d30f3ebb62ffa322861e5a88b238c78d965621ce69d1dbfb1a5462893067f99452f2cebb646401be4550fca1e268fac4efdc31b1f9876d55bb6f6dbfdeefa8428cd0bf3a71e3ec12ca2d341395aff4e314650fe31dae67184ed130bf2417a7a1cd577cd71b3c796e75a0fd6ce479565d2f24cc1ce97aa276b2b9381644c5bbba4168c7f81147e60d5f0874ad8409424d7db856373147d036c6b2eba5c3a042d146cfacb30ec4663701fd2a437f2f8d155f3b8cac7947f0b226d3e25891cc7a5f2f7815e9ee68245c23ab9ad76683f27532dfef21efeb63b4288e29d8365011a4335e2385aebb80d558e598400ab5be7e43cd49d1be351aff492523477b50c6fcfedc8092456d50e2aa89245a932feef1c48415f849ec4259165bcb2e7e2b3ad2782b9998ad1a8f4eb0e1f2a959a2634d643e5ab4b0bf48136b810d205173c7799a3ce7b4319821926feb0c756d78f8486ffc99c46ee5756e4fdbbf15c894814c98a1d5346e95875166b22bfc010c74936f74dee4a177b3fe1402f60540d13c36700a8b44f7fab7e4106601b1f9e70a03c7b262ad83a9f9516a01e7c7f9b4ea9b7871970f8c35e35f08387770dc88e32bd6ec2ba924b91af8830fd36a33d0837be85db1113766167f79bbe241969656f848299f88218757d2509141648a25db98bab04bfe79e5543ffaa492f823fdf4adff6369b37bb93f6a607d73e534dea327536dbd957edbf298da5d7d9afedd156f54f96fe52eea7e407536053cf7129c1e982b8eab2538206b3e0ee40f81582cd970efd5644fad9cf72a46be60c6f537f10a6f688ff4a152227c333d81bda8244522ed828692b3362e1e1489da72b95e14c402478922307856c981ce5cca51772a8712f661d441d5126f9d7cc9b5d821395c7cccc1774633c06a7874c1227bbe284b8a0695582c685817749f7219364335e07068e9264512fb0ea7156be482e076aab1ff85b059a16a99732343d0c3f01b56b381724dbde96093f716025f098329b9dd7449f02d00a1cbaf7582f49e89a2c6347477834cc210947bc3bf93adf364c0e52b95a1c2fad7d8d46345a87b2d83e0b0c2ac7e6817bf6befb6f4552c4356f3ce9bab94e91c835c372da1abdb896fd86e1587567f86506b25ff1cc8137402724630d1a6e6795f0b0b5fd4c94b9a31a81a40913a5d5251b0a82f77c0dbf6f4375eed7e34d7499c9d65fc75fcdd5f839678c6918b015882cb57c4c74d030fd5e06c60661c23b3cb3e30aaee8bb3c66948f776a705da9db7dc6c89728a0716a7058c7ae127dee1c63d95b75324ab28f68ade867531222cbfbbbc7426fa6b245e29c0ce08c05cac466e8d6067f216604e72bc4d0319c93c69282a8325ee3973d5a56ff521dc0a62e33379ed36108e069c38247f2b42b3a1edae6fc9bb45f2648a3557e4280ff6b9209ab9c60821233e81312b5f372fe9de589d205a773c8043d33c16f8ef38462ed9c434624c213aacda13eaf674c20d75d712b4e992fbb2b3188ec9776a319147de8dae1b8140c1f7dad7d16869105dad409a0dd0ca53244bcccb90a397302fd208d6fc4decf8a9c147a7fa33800f5de467238164ea008916557b9a8f8a19c00bddf4aea3cb139f5f0743a83641b764917b8fa07d047d83609ebcbcc9ccdd4f3a4677b7e7acc37467afab81def59a09b8960694d199d0b62debb85613acab89ee5859b41e6f6eb891fce67b290289c0b33419841a70978da9e711a023a9da6941d5122660c84844ebb2b087c8eef95eb0e0ce5a8a1b9494f16e57d05c1e3a0fe13730f9c9fd6e026d1e4e5b68870ad0f832f5b8d42a691bc36db8f459f36c84bcbade7d19d89fecda504c6e64ca2ab9c710456130918899ea439ffdfd4728e2593fc80354cac56a517c6d0010ac764dd6818380ced161d6a89fee0ed9afe545dd749b6006ca89220c69f8f39daf7bde019eac750a129e1612de1c5b8b6da9e438898d667645789e12cfffb0dc5a1759c98fc5b02b387f833aa559310f062db5adc550a04de4ad05e7ebe46c7544d822dafdfb8782273c8a95c0a707babe9438f76fe5ab46335979328f0bf3cae9b4757b7f3ae364efcfd06a83122f0e81bd71a0cc22ed62e9614124125af12cefaef02cd343550b8860c38fb8ad0cd2d5f7029c1180c00007cec855a43d54abddb4ca207660846fcbf12cc9834c97b2318e2706fbb8e75c9efd4a124f2608bce7b5172e7dee2b8dd24d9cf1717a505051eb659b7ac3b0ec6990e77e3411ec89a86d7c106b21373d4f5ec6aa034e24f29f9b40a1575932636256fe1c2007a5a95db515d54f2351db807713e3ab3e81f29a723348b6e0a1cbd158d36a676f3c5da9f85ba3094f7bd70dbc2f824f4640bf6f4b210923f64c061ff99447daa9fab2d10cc8eac73d8cb2a398670bda1eee8234f747aa1650d2657da29ff8ddbb3ab44ca2bf9097bcfa1138391a68482f49b86cf19f345f81916a819ca3b504ecf853840ae4d56599edd4cfe61095c9bd03b69c75921c6f4009d04da38357c226212467928af5885c8aae60b3ee956e7f96046165fcc7196b2667d276980c4f8c51c143685fac6243829f00e7c90e63a59fd993261a1ded0b202e58c4c076be600272a364d229c1c9139b3426271fa5d889eb6522142d589a0420323c1e53559b27e161d9fe67d28ca80855b3debc04c6c906072584ff6c051f6c4e997c74838fb9d157e725d55ef61ab258aac8a18d1042a5cce74833b4e1151d3ecb39d9aa6db8a0dea13bda5854d8196b41c2b61cb0ab2c1d673d280615e84e2311557d97e542a09b359beabbfc494d45b810e82761c3c049487cfc648b7a9bc27c42e68fe89f2e23f3e28e4958e3083356b54fc04d1e5d70fbf034e0db313192aa688bf60defaa81a0889793e68daad33179b924af7ede6fb6f97d6a51b018e3af64dd9aff4879816b728e800f3acd2f2ef99feb4fa6f9860be97b95e9b9efed7ca5d0d3594ff73ae50f64bff6036a92be0c1097e08c24d1525bb1f1047e60946366840bc0c559c0c47716a2c79dfd095fbb35f54a57d0f2822777bc5a6e6783981ce89a55999814a31b83a05ef1f54f99910a78fdb2202088a369fecf5209531f46acb16648c03232b24b9e0245266e3d9d08268a59c394f58acc75b5a47fb5088b5f8b3d5a9602b4d6040a999a29864a860a4cdf84b6c3d26499c6ed6e2f71a74d1a7370a1057a65d25fa7bd34120cce69e954d4f7ed3b510d6c3f6e99fd4d24509bc9ab57929b742609acf035507574b0f619a431dba47ab9b9a61cb8b09f79c694c392ea1bc3271042e2c835aaf211dc2798c58ebbe15fc4b66be58859b631949c78cb0c22b42aba140d28a50dc5c55a8fa56705028dc1c044576a9546c45ecae16666dd4a4c8d28937d66d89d54a5fe386d07c18e924ddf712dd59f4563434b14bc406def999c7e9358ffdba647b9420f4e3191ca553327665ee7f68b0f6525774899f1fc840a49f5de96a5775990a1f9c7caca4e6c55e4af053c36b95a597f5c6a262c44ba364a551f21dc6d275ec05cf7a7b95d24ee44a02627283ed6e3e28202bb5c70d86bc83a7d8f32e2d15be65d53926eb1ac2031b0ff2c860f0ed23dce8ef2dbdcc3d588fade57de8514b067c257800fd300fd1e51f3ad58691b4d0bcbb549600970cdddda5acbbecdcc277e8f01bdf693869a4234b9a6cd216812172d705c8b69a73dc97c31ba0635f8d0811ba7b118b9aa855dc0584a02d2e5941ec1b749750255b3a9bd418ac5117c0c2f4e3b52f4092dc915ecf99af53d2aaddb94ba2d41b3bacf405d47bf7886fd3b5b0c5cc0582700453fb0a906e214cd35247840a12f344939f78e7ca5adc366d90088c01223f66be5110460cbe00f1e84a9128436c299f045500ecfee680d69fe14b27cd3418b273001b43cb65b4daac709bd4b4aea000e3ca8aaf95e8f60fae327734d716aa8d80930aec558bcc424bd2030cbfbd3707de433ba43c6122c014d63e052c08d0118f14980b576591afbc204ba19b80dc5d047fa7f7a3ff92e8df5da75460d85d2282bc0840f9b9f78f043fffcd317206558920c8f5295d520d88779f75436c0171eab692af86fa0ddc7744fe435522cca42fbfd081fea78fd7f6b984222219da97851bcec481d7845ce2f55f1c33bf6ed7517d949e698221c74a21d3728b3748a1f7ef0a3bb1cc917e656b37b2f55bf3061983a21d25889e4ec34a8784f6af12ae3d909eec0d58f730c2ce008d5d5f72eb9427eebd4423c0c8b61a725bc0f9a121b5b3a73c551f71bc72fa0cc90d2a21713052277e2eba3b5c253a8eda809aa671964a7426afdcad8e76005b37d918ab051f27dd13faf625cc0686923c385d7fc1408aec4fb0edd4a9db18c3d1b1023c818597fe145a0be2fc2d1e77be16de7bdd51e1ec88d2b0d859f3a9a1b85d51cbeb90c3961dfdba70952ba444ffaec9c233604976c382678fdadae4a0bae5b971fd85f5c6c6f38a1d56db282ebe3ef698c7e44b47212b48159a00c102e4165aca1e4ba0f781068753f56835cf8eeb97c578a92d0c020c2df5d9d29c532814b7e66c02d5d5ed9201c07843c4d8df519ccebcf0fa05b091b7b60a1884bebc103bae7a71251632517c153f982862cd855b904f70108efc6951b7cac2855c2bdf3a0658552a3f4336f473ca5bd12088fa3becbc8f05c1189d01012faafe0471c262f1af7382ed398134c41ea6a16958a786677efeddc8b77240862b30d07af6ade53dad1adb8d01fac102ae29db97e1d6357926311ee8a712f5b7f23d4353790a988357e13dcc119c5d9eae3d6696cf81ae24748401fa270dc3d6c5280c4403eec3d35125e8a8d3154f84a04d6751e00788a62af714ce12244a61ae16e8779fb231a014c71005fb58df9fb37cd0a916f49fae00ba77641461967b2b493babbf9a9d544d66dea97c3015f2cf04cc93c8dc239557d8503a3824878c788da8f7196781894b402fd8e2d89d30dee8a13eea62bc8bc843193e519dd70627b56a10a09372ca7a609d589c31e755900b0e11033a624200deacd23d58d5a261dcd289b5fec7714cc15907b986fffc9f48ffdc041b6707fe507a3b91d9cd15d74176e6963e2caaf777e0555c0f22c0747b275eadcc03560548278bb0d6ffdf9ed91624299f8f070246267dfc85eb161c05f6cbd9445c2fe0e17e2bf3425050ef35ba3cc0d74a55fe8d73b52feac3fe2106c18b0a8bbf02b207170cc541b61b829e8b958897ec7caab7dc79a17a886b5c2d0b27f69c29f458c6c64e24fc87ed1eeddee1b0ad699ae2fb6f79c664fbd7fa7bc9fe62077c1b5a20289650fb9fb9427790cb786720234980d00a435a097d1b7709e40f0e33b6180f2c56fd3eee3c7ab131772f57afc39e9d735edef9b9ee0d1ab9519fff1e7f6b24f685f8ed0ac01f199585590588babfcf077a6dd39c7b37ed85da08c9a4d42ec6112877cd25e793a398b763075bd44cd6aeb8a6d040612a5c7197da9221a1fad69d837c84b8d08fffecd18799f13383375fad267803dccdc461f53c2f255b9bfa28227f182224abdb06c128548006d3ccb0442d88ee898f13fa88fff7edbced3f26c8ccd568afdb6e481c9af5b21e4e11f79abe4da39c4ccb8961891f5492e5fbf0fb81f8df23f9fb3709714e4fe55b218cd035e6cd0371a5344e4ceba07f3404e20f6a0dfcfd232202345a7bc185cda2331b4e6f38f18db2b1ee94514234d9a4a412911a383a3149400793b7d64e4d511ceec5899d5b7d483185f47ffab1f1ccc61f70165c0f68e842e60eef4014fcb0bcaf6870bb9258804b4a2179b8f372818e90ea2cc5dd208031583ae89fe3e012c818ccc361b26e7a6a67643d47d7a06788f04383c665a7d71e424d22f206a7ddbe0e06169dd0e9c900ac2edb6fd67da36ace681ad3dedaa02b6f7306816e22891715ef946ca29b12a6a2f6a43b1684697740a0ca56261073dc15941cf8cefcd7760ed44d676eb9f6590d52a7a6ee80814fc43593a492d39cc9e6e80e9f85fba34751ab426703d33dc85ec262c751ca69326e6d4104192fd9280a1982f32af1bc0f43201a773374788be3f39d12ec031e11b7008733521a20f15f38e87b0be1ce2394f568bac2b3aef576a8b641267ac81f41506af7bdadb5a75ff18dd1c801b84128f733284e4ab2b7db5601eeb191b98ce5a23107cd0c7a01500f194df7c2627fc52a2b73fc2d955d66e14e6560e88447587054b8ba6dcea50fd3ecfd184388f7c6a960569cd2a4da0be53568e41a72a6e7645e1e6c435ff92804e925857e9f06622170510743db2afde1a329dd0953f27f5344afcde33181b6f010adc49c8710961b92dfa7cd2f55f2b0781808638e209c4c5cf5aaf94166fb1824bdb3c956444aa32379b79743b438972941aa811a527378863190c31bad24304fa9e4059e8afc284b980587683a8f14628b0711709aba9cc5af570870e22e45d3e84beddbcfc382d864324d9eb0409558bd437ca2219ade60228ba6b4b4ad13bc4de71568e492f8ee78f4b8ed1e2228bfb20e17920d8924446efa5e1a4f5c343ea31e9c0db8870fad37e4a1dc86f1618aac9a91a103d4f17bf8baf70a1ee61672b3faca04535b49093a155e38361cf78521dac54b70575ea75915667da5acf9e7ed2a453b015125aad9a8679137e696c4c947beaef0e6e84fab616ef04d8b0549e835aac8ebd9c76dd01dcf1024f2fe320a171f318be32efbda1c7582048bff3ec7196799b5b76d8f97df001e7cf9ab8bd33bd45bcfb73e6ec840bff3abb3979e4d2012ee291e3bca107030f2ad2043f0601ab1ff1be9c7654bca225b14730f6792d315542c73aead0c78bbe4433758363a00dbb3418007195bc1707b0bbb00861909a70cf1d5f3e51ae57bd428e793b071c1bb1240a7e3999ca9e9f57eec805de1c6d99dccf0a345e8c1e26780f32be0f849a3390619a89222be8219617044219a5e20f6c99c2024ace24db1f5b1c10a597fe6583de0d2657ad9024e0c14a2757f0ccda2329a077313a60aa4707da658e6abaed10fe94b078c625cac3c0e75b1be553179c424866f6e8a86e924f6c365abb22f4bd80d0b52d774ad757c231fe5e461f46312f310a37534873ba75162f6febf89aeb0ee1304bcd2f63284d9ed04417df1f9b5785b39b317c565096a7318abb0da0e264e77f2a28b41093569cea1a7b6001e557a9f540a3f26b094a110d0b94687924fd1dda3ab59a3c0138beb014a2ce2a39e3a88d69d98ac567cf394e31205bbfd3a7b406a928adcc6a604f19a30c1b06dc464d514f68029c2c7296a10ad178e9caa7e3202f2b58e3488a643e67b505017df4d11b81647b126653963c701e645cc112bf9015789b4ad71e6eb450e30a957ca0bc1cc935682503a6d51c79a933fc8c11ba37a1ee0941236093081394ad414e3bac384846cb7a72cf9b92e4e102b6fcaa288de03ca052c1aba40282f219f41b43b95d643d2cf0c2c5cfdb704b3208408f3fa3b50332e06d16ef4444bda02edb258ccf0cb8577824c6537389b8c4d0db14aec3299b84ba2165ff4bc4a6b98efe2ea6a38bc84b31f6b3194816b6bf93fa543a2711504cfc41b1c165641e77a718f310967289107bc231444d3c921540aee9b7ac8e3e9aca9b72ec18c266812a69834141bb9c70b4c99d911961e7ad05e7f28085839026f4f780f808fa168b9e236638584209aaecdb0eda555debbf4253825fb0bc3cc63502dc854cbf4341b9fcde7f2faf67070ea4b4b017ff10a8f45052e50d416b58a5303fc6c74556936682952093f31f119ae24bc07e7ddf3a6c2d591a85d96a0fee331cf5ab0ee6e9a30abbd45814d9790cca5fda88f586fa09568205a7ce078d04c46356dc96865a1a15c3cc279794593521988d615582e0c3940a9dc9f37927f0fcdd1aaae0960d36df722dfcfb02ee7ae0485231ae9af86801ce4a7e35f3d3c6b668218668eea9419d2581f1e52a3bab9aa42680e4b8ce2f06a98d7cd92b42ab46410e58d2443064c60e81200b38016aac53148acfc9ba6a9272c8fec5d3e852d0af0eacd018582a007be737edf271901d1ae642f903e319af0ac8eaab754e55de24a56b338ef2fea6a20af7e808ada3c78eff9ab3193f11a4a138078c66531b0053b691b7c939357bf3bd7373e9bb316934e6f00d13a0e488ff57a4a3f68da70e302ac956ab45028908d716a3b98bde9e9444f4a13241afdc42a457cfaf7e4f00be92e45b5864d6785102881d525fcef67c7d7348eb8a0da8e415f6f22cfa9e398cff8780d12af751a08ea5ffb8dfe8e747f2d6b883347f8fc3c7a29b2d9b66a59f3d3a8cb3649456bc206d5e301c822b7be430de4ae6a7ff214d937cef4718477e252fe6bab45aac5d1872ba20a0aa53f171158852e3e1959f2ad3bc7ba5b63cb3622a8f996dc66b5b71cac32dff3c363e1c8ec309d586e5e6e8144894325ea63836d56882ba1553a1c13e153ada13b49d0adc49accecaa10370d5bb43357c8bfb8045d6c4d105b339f76746d297d07ad8f177dad9f23596a6ec75b74f6fd692bc95a29fb3139bb200c550b553a8e371f7cd1b06b33f5e308729eee3f417dd08d3129c9bfab52d32af84e57d117aac2d839422a813d7f68471cd87d322d69c729aeab06aa3f56512d8e11d093bb84ac765670b94740e85e656168a3a6717d3aa9d2d818bcab86abc2f3869f0c563b12b048054d8369f16a08ead9d75ca920c769cc6a7315bbb4094ec817622497ff1865c86224f04f6646f899a13e8da9717133ee6c6bf830c3ab4fa90f328f55d8faf5f445620a9ec07261c74b05d34e11b4ba631f5cc14d5b1d5def056d978ac35a7500be44962c22d25fb878d333f6c13c3c9c5c811c220b83fe976fb2deaa8d980535cbdb565aadcc907ace4f6121fd6075cacbe7ced5bcac5c7cdf1b6f1230ad67771ff749bb994dd91c1f19eec37d33355ecf6ca51b49ca0baa9906de2c4f2dab6704f74d9e1965f9aac5ac4659a5792b1cf6ca60e818e8d23de1a43e95e88c41a995c90ecc0ec22a676548ca6ee6230a153217bda9dabc71c1ccdf6aa967b62d7a8753cb0dc78c835b3df663ce7d700be6d90b55f4037eee03ea2a1a910f2e511c1da3342727af92d5fd7ad83b86fb6d5119d2eb6c9358ac765528273b18d831c370f8c3e626966a13679469b41d4ae427b930049649565a17186a368ea679e37c0fd74c5f55eac3641ca1f2a0c125c37cdd22f12dd3639b7674cca3d0edcef440ee648690a69e96c9018d3c41f19ee6e6ef68dd1fa71c7fa7fd6a99cd04e8e66172e282bcf9245f4a6a8c0d5840b26c57bb6e243b95e9395c79be7bbb75c47571bb83f8fec0de3e0b37c33db2188838ab67a4d388893d58b061aba9f0721ec167f248cb02873e77d59752682733eb3436343aff1a76ae51487accb15b2c6220a157b3538bf8048c8b1834e9ed50a6415ef771dfeca20241815efb838331e59f63fbd2efc3dcf618755b0e8b6d4d0d6441c5dbfd969bc3adf3e1b8bfbf5fd4e212fefb093474ae278e7e9e46eee3231394815e0e962ddc696417a9503d8399310c459f1b1eda2485717544a50418b3174954d5b3c0c30284bd8ec86055be15949821fa22fdf7bf1db0f24ab324ff74b4c9e92b623f8f7ccf1759a534ed63a88a8595530909456e571d3bd3d7c6505b28203ed41b341a5ac67fc4fd82c184ad88bf903cc33adc8d0339008c1f2b8121d908195d2569e4a81f601a0d123f1b215905a717c9d23cb568716b0b7761acb2603fc95d4eb45543e14b4c295836cb48ec97b15f2411d5e1e285dd057e3636def3bc225ad757c23362aeceae3f7e819172297f8073457bf8c4996e52790c0ae5e361b6d563b74efb22c153de1cf75cc075856c86c40ce71bdc9a3211d19654fb457f06c64bd34d5e0cf30ce0b224a22328d74505ef2590199fa8e894d3b285f81384428932b30a644411ee61626db9ca111369c172c6fb3cfd09372b6c884a8e8cdf6c3c8b6c80800a9c42cfd4600bc0817079b61f72312d5cb32ab465a5dd8df1e6f13e8455eea31992ea86041188507e92cc6e7ac90383a25c88741d19fee86760804340439e724b4736e90879cc26e3117b1b96bc78a91e9ee562a0c48012c6f38f0950f8f2797ae61acd7cadbfbda4dd651b4fcf2ba87fab9b751353ad772027847e67d033c4aaaccc05daf4f8a736776bd890c8e123ef3905d055d072426127a717829ea04fd0b62204f25e12d48c57b13a8e7463309c83cc3c6d620413e95dae059ce24692a98291891b102fe314f1352e0fd91499aa48f2e8b444edcdaf736277db2d09f80c98adae794d534de325101097ba05d739bd7cd2899583b67977487a7a6f3e0f13e25682760dfba50295dd0fc82ad3095a76292056a66a8c6cfc802019a73819e0407164aa60b27603e3653a45c93407c691a982c9da0d40902a3d0f683523fa6cffe235e56b3f2f92d4c7f77860437d64e30a09f490a5d7c70f39db4b23c8c5a7f37ad5935b9913e2e755e8dc18477a03ffdb931bea03535e136922b1e514bb0bc97bcc948a79c063e274a61cdd7cf4dc9bc1431de207f08d7cf75c938148886c47e9e988c100232ada72d6928c3530ffd85eb6eb0aeadf4a9d7be23039a88d5eb8aa6707c88b0f5a9067ce15cc62905875cf42859b1ba74c2ddb36dad5c8622c229ddd1e4174f239031037e9a7a4e0a3947d359292c092387804039cad6321936c9a8cdc17d993634817d95c1a5e4bea3bb728e60da7da5ad46f1f1f5986542c84eb55328971250cbd649408bfe9896314693c84d021bc7da0666b257ca0b1cfa811478e5948dfd1703746c30cf117f848b40f01682bbf7f3fc7b5c5c16919a57aeea9e124a45418e148ed721ba7c582ca6d14d560a9c7c71ef32f8e4f9c9b7bc9fb99d9174c45bb238a879c75bbc79fc76704ceba7258af664177211ebfd66e11c8dfd2953b87e01181a17658ed3576d572ae5b42b7df366755637108d4b431dafba62aacddb46efa2bf82d4c48559c9347305823cd5b8324c56c923b13bca18b6e1ba1ad6347ae221035b9a83a5f27b9bc1ba2c65b8aca4d36d644d22381c4baaa8d0c462439652df63e96449b96923e70c15e43254aa89baa549e784beaab4e9061671b648d24efe7b3b5ea49d21ef40eb5e5059612b820e0b89730db20ac5825057eefb25b86cd2792eb3275ccce960f8b10b0bec0020dcc93610bcba5a0acb5e135c67f3978570fdd739a0fa6343b5a43a14d26e4ebbae0ad93553dc7a6695cade9f034fa3b15ebc838b587f78217897327d8124da372bad0143fc0f063d3a02f4548343df13c5240ad4f0e0a6ccf2875f2b7a18a4c90c333aade36b3596af339b287e90f6ec749941718019b0b6dc4ae7ac0b968741d6783ecafec30b8a87e042a6ea9f53630606a1a3c4d6a9bee5aaf1d4a979f41f68c40efb236b0fcb172ba10fde5289ddff7ed50b763c35e5075ecf6449414a15bd606d616024ddbab3454f940a64b958acd85684b40cda3b6c5245b38422d7a3a54a08083271120e65fcacf4b782415c2d21ff034011a120db5783589acc177dc67694864bed5713e4516db58264657a600d2bea9fecce391912439e7b2fc3c3161094e8639b46214edfac6aa178b49550007611456b0a17bee73c1b55cdb1ef155fb7b5d9ed605964c543d286719ca4438f2071480450bc0c1cd44bad920313ecd3f70f6f8d78f2a1c5b7bf2c4b23bffcc05828a5489ea0a4b66e8190eec9120e7381d824cefdf9792495bf7fe1a6ebeef23c6af070b56b5e54cde44b7e507115a8e4f8d1d4197fd2ab7b8795bf701831f1bf0df21b3beddd2f470765a529a23aa8c4a755cb3d898d39031065f6e2bdc9c60894f12d1b79e4f13d23e79502775abfd15d54a42c59c48306ef79fec4ba76b191f4f43733fca00514d6267367a057c42e5578bffff741ffcd0c79201cbc0ce3045baa9f4241fd14809d5f32bc178bc709b6c7949394efce4b93448408059bd4204ed5cd48608d7287e692e5e8488765302fa504ccd65269f541c01086142f4a715b6a1f2fb1bbac3366d91a6900b3750e681f015256201e1173a2e6dce48bb83c114cef2bfff8c564b3836dbb0b59148274c24a041edbbfc4ce336e9eb51391ab30afa1e62d47dc11db58aca8e872fa126d13eba3729681e5cbc4b10e5faf32a7e84b8362d155a7a16f27af5750e5d223e4a004870409f02d4255973875c533f4a78b0795e813334319a7a3a4f36880a86d46f99b76b1814803f3279343814d8f980e5513038ea0f0461d9ad718afa5dfe8554e34711a00621ea67414797fc1898a3979297a8bf04e502e119fcc73353deacb4c6e910ef8ae2826767fdac30b9f4fb26fe93cc1512a0c1c0f055335f1d1026ab425e80edbe137c42759755ccde3a703fa7f8fcffe0eb0f9219bb9d2d571f9219b23711231eb48d6421e3e6cd57e0b1fb99473c415df767618026d58a73cf1f90054c681c31988ba9d4a013e1a1cf8eb9015a32c62d117db16f3e3c7424b52d8f75bd54b99d9ffe024772c10125658e814bd67e8db2075b7f663cbeca7b8b96573eec67ecc241c2c5c7f3c1e837c2685b5e733cb21201712ed0d9be67702c5f56371444fb792f10d8df6cf3b28b55b950f98123055f97c039883102a48760b25efe6c70c94ec7a020b252fe3ef5e5ceed9877973f1d7d17ae1e301dcd44822c72128ebcf149f57163901081c0e5a47a23556d88ebf51fec757f38b87fe6f68cc34bd29bb36fdc77c19e01feb061c8437bba8ff1c1c9cb8fe77d6b0bf9dd933064f2804c4635e6c7f36afa1aad138523e0e8c15d5bdeabcd68ece9fe4c06a4c98df6918dfd657e030838eca2e080443832b0703d44e454e09d0acf0d0df27e43e3f9df0a003f05d7b49102d63849de00803d2527b285d1f4f9f87d2000647663846af838b513824ab2576ec9d8e7100f1745eb3ed4fb4796142245105818d008429ce040c2376fada931a490f225c7297c58f0076a00f88a2f8c422eab67fa75cbfc4ca40be363d91df1f92d49b7bef20114aab86d3fb4c91852e833d73e64c489e44fd2bbe810f1d534e20bd7cf9fb276d0a896f5f3ae506a84d5f2831ed0c9de277079d310440f9cc564430e97a541df9188fe49b9abd8a203561e77022b6dd1ff96ed6aa883aee6f3ee00e132fdbc8c4c4ed23b5e7a1f052a803b2f052106d87bcd8940e7e687cbef6e19a72005b30762d84e37c17fe121a8846dd9325ee8df5e60bd5f30c0d3225306ff6227f06d84218debe3ee16fee70f24a9d0097a4913ac309f619832ded87858522f90ae60a84e01f592455ca38ae13ffcedcf3f14b3868fed29e7c405264d143e6704477c6824aeb2cee72d7cb8a22f56c9922b4552777833fe331cd917635534929f51768dd055ba8ab053eaff83c8265dcae994cf52b99478b471e97ebafad6f13ee52f677925e28c1742cd36baf0e61d85658e080cf5c26b86363a5002a4d535da0407a29ac5b5f8b4d730632f76e0c8bdaaa4873602bf275606813f53a82552aa44eac4bfd1a17d4639dfd3b8189643f464803781640a224b54bb3f0cecc8acca6c7196be52cd47bba012201af5a42e40d19e6f014617aa3f6306d8673ffcf12ecfc3146676103c33d41775b420e199f9a5ccd9e69f56532ad56d2f09da9dea36f59206d65e1277618b9f978836342374875ff278439766893276c298271612655093bf8df19fa8f5bea42e94337b39792813be84f84f6d91a7c22f52a86eb9605871f3d1a975ea41f42895826568bc51c4233e059792c3e9daabd2439c0e343543bef3afec0dada24458e63e369c6e42d14784b4d3d0f1a801d9c1f4d8d9d0f1ef112d1e96f5a188f9be74c55f00b8cc31466bbc07405d72e5f0907bf6f05a29c7c60e0f497f7ab2342f9d14dd82bfd0a7ac63767b27d7cada2f29aa7c7afd3e4a0745bc14863c4e973e39679bb1d016d8fabe7fcc47665810663c078ba646cd126b2824b5f2448f194d765f22bf6116073980a984ee51966eca29ddebdeb045b9d09198af0f4b427180960e4429870cbff7054e9ac0118d663e7d352393243698f55b613d8e00965438fafb4e9f2756e80caf90243a48d2b064ad4d9c443c77e05024cf84a68bbbf69ef0241d7f04fa566cb719ec6c99005a9ee89032eaa23d93f105b0c1f4ef96d83d85998efe6918bec970e158f387015843fcbe7d9fea1e748ed5ca08c06868f6649363d86100074b6865f508af811c94247fd87462fca1b5c35763e21410c705c4b1e07cb081d632943e7c1e3d4f2edc22d698e85ac112592740e03c469a7fa8875be4d92dfdd008549ff60408f9866ec4c6e9128ba031c18285686c5ef6fe31f3ae5dba8b746b7d0ad7b2f7c629fa0312155759620ef37f4bc6c349c6b46087f91fa31a4fe51583f1138c7908a344b937d0c900f058aac9b2dcc12c4e90c093b5630f462fd1a8ba4cd5251a9d91f5982a24bb3f9e8b42a6f1d01401625535d7afd9938fd84ad2701a86c81ad463c49b869012f2c9655a7a22d8a786a2c1748dd295fc289ca0ab2fc0f80e16956048c51de8d5e1720a6b4c2ed0a824b55c7f1bdae0cf278d490f0abdb350c848b88992a9e456793c853bec5d0b3623d693f65e9d12d0af4fc2519e90b5e978d3c9ef0a28649c16f6609169ddcc044bc877e50e9102a04c0570080bc99533890e0c2fee72fc7bd9f1d2ab90d079dec7fb12b43a4a0579c7cd26be8cc968659dec653c3132d026391bd139d9f9efb71baeda2200c77539cacafd6dbc9d3ff142a6940b87212ed03900e936498010f77580b62b52e6e22a2b74dbdf09535fc7e1b2c33d454b61f7fbbe9254b46b58924427e0e36464f0985de1b153adb9ecaf06b007a8dc6c5ab86e7d76c266a428b10ca65faaa1127bfbbb8566f29a697fc3178a3a9938d202ae7480f78dc79fd8eea2d261a56c17d609baf4fd5195443db72a9902abd4f488354b660eb722ceee9cd58ad881485ede5314cf48955a82d44392d8b511dcd5d7a6726b4bcae7825808c704e8a880c892f607543711c43a360c747b486899a22a731940e582c34c6746e476873e5d6ff2463a1ad7a4d266b7bc4a63843701e25d4ba789ec91073fc655f2e08ecbb91f32aeb746a37e1eefa5347ec160912223fcedeee3175728a220d08b2c314cf54569da8cf3f39032f5eb6b275c849b066f0e0ca120c41da2907fdaef2ef627e0d12834cd2756422aff55bd4bb116f5f0dca276489db87550b1e38ed98bdafecd72869bfab0eaf7d114eef5e337bd7c10b9f2c69d6d764bc7906e421edb6ed9c9ff06f58e34bc004901784489a71e00ec1d90b72819d3b55f47922a8ccd7d4c9d4f7f36fa2a196cae1998c99a9d123743b57110d0e3fb0c744bf894287a627230b00a5cce0c0db8add0b690d1862101b07e219f1e4ea5eaf4ce0cc89f65f88f078c974926819cd36804291b8cf62b69056c365ddf717f0864b46f3c7ed7e614af9f295c9fc7fe0eb3e2101412cd38fd9ebf95a7750c7ad60683a26f876e69e33a03ee7887e4e2e6e54dd5d048244aba73607e410c7ec899cd2b7b50d51f75672e90c143c63f575e644b1d6913df6930e21529778b35ec01743d95c099c76f14ed0ee6ba80b127a25697f5fc885cf6f260d299884730fdbe5e7f4b51c270b9699b5dc5f4f8eedbc5fc85d33cd343d455c3daf0300a6dced162ca522b157b3d90c82354a9778ba9445d89ded62704278e52beef9f95e7ddd25aba3afc7e47f3ee878de91ee837acab605771f4f5b72c1afa40b76bb8635310d56a2bb07a99820bf686a34e5c8ccc230c8b4a9b91e46a096dd85e16ecc40e97e19f0959846e621a4b72e5dd38104012da037c10d8c2993a94960c2d60b3bc8d8a09de0ea1e7d2b40b26c2f1446e7bfe41aaaa1878e1da14fee86d9fe51b35c719e1de5215d78868229575f5fa3b849c6fea86a2a04f9acfe106ef670e0459d044fb510424b82d3fa3ed586a052cba5356949103e945cb4da21c0ccb16b6542c2c382ef5a2935a1e9fda1451ad5907f10486f4060afca2aa4aa0aff201a49b403454d7cb4d29802884986baa3ab96280878763b51e197ca4c659d704327dd8a966c10e969ce02af81411f5968e58490de59e730ef83dab89a3a123fec9cfc8af360e27a961345dbcba8f78e322cee41079ea15628b1c382e9f765e17aade169694353d0956d87b0387bf9494f6c3881d58ca9b9558f0bbb18082d4539b747675368823213d496807fcce060c3efaa8b5b5f6abd204ff9edff6c6a1c0584908cf483165a98865e785fd0a96556c27d708bda67b057929d6d4d6913e9d64e90838b92a86addd526a9fc0135d4c162f216c22c0779bf7b0e0086e014258185b8c2ca2ddbc29af6c7bb2f3f39fc5e77680c6e0604467256477245307b8367ea480bec0c23b86a35c937717f30d3dee87a46283e75f5f3f78c74071ea2764f53f66b893f123412be41b12f8efcbfab0f48dbbbdfbba5f899d0a27ba656ab3d4f16532372806f5197b9ee60dc89b69711f88997c8c745e08fc4d9eccef93a7cc31576ea9cd0670e2e017ac84a8b2aff23b3b240e54e27046cd469a25cd2128838e8bb54a218ab7a09e0aa409576d8de1d68c30228151b86ae33a215674cd059defcde2abd498c71fb1a9a2a1319776391cbe97163fa15232e47d7c6508756b25eb81563c12b17a3477eed186bba073571c1e766e54bc79ec58a23b7a7f8d611ff75b081659b59a4486ff4518f571a8123c6545d17b200b75618a7948a703b46f375837b33f3c810b53758ccb5a76b2859709bc23da25cac032c4153fe1f3e1643018b41e733f285adfd6c392ae62d444f98100b6d22b8c3240d21ace2d1f71870eaad62cc139208548d7739920055a703de7bb49818faac984b8874c9dad3e61bd2414f95ea476e43a4f2236a65511b430345e4eefca155816af2bc16229f41cef905677a5e786d64a790bd1b2ed61698b4f92a040fcd5a896a2de9349739d5b6ad2595e028ab0ea570b21ca1c754ce95a6dab084b4a70bf18f2c1953d698770708b1c42def9becdcca4c06f3357205460bfcd901a1a0979c4dab3bebed5bdf4bfb2b7dbc968adfa08879f3cbfc96a285521c4d8fc553eb31dd84afdf382b4f5a86dcb40db0adf644affa4387b996601d657647be5d384801ef5d841da65c3fc065cec94b333d5ad8a03b89db3ec1c4c21232bfa899cc079ff59df4fd78d7216386b88e462fa40c8ba7b4a30e61f0f0fc8cc4b73cca3fa4eda9a84de6444ec58e6e338ec1747fb15ec40afe8643d325e3fd6073017f0855ab0d994ff728fe242112aff087133da467a437f29c96d0f5a9c7d5f3e158a7c9384d10f4900f015dfb1096500586da61f10183b49188971c27e24aa620a564c2128c828653981fc5e9f034e52d9cfe6c6e222d50c0cde28d2206d19f723a42f88524e7b98b9832c45bc4194ee070df3d9081639f5b91a6811e9cd52025d2d259720de8c4529f311111d911e2c0a6bf4d5294e2841b819e5d11b043d4b0844f5d6d4708901197b5804f4756917588b02d97a18616f43e1f88658cfd8a863a01ec49bc3c8c833290876039da4b4f7d48d68bfa6140a6e4c22f6503d5b15a03bde980d07cf2cf6a4c6cff391abbd24d494e7201feac8c3c4668fcf9565a18622682be0884ad162589b06f95c288ed290569cc527e5425e31d2a5bec58b53cd7ef507840a4162e3be4184a3f496f75049f55a3491cf8b1a1ca017699fa910125cf93eeba2bddde17604152f9d368bd8cc334130f94bbc70f88b8ef9178332b48f8a4c38f60c21778c08efaafc4850e0b819e68b84106e6b1fb2c99cecf181dad6aadc3db7ee7a84efe5d0c9d42dfcd1696a2b7d0eab3ebb258768ed961395576d5e84c958ca2112623875b7d998185b5844901b6a1609297410753f91e9a94361cb1617ed19b9f8e272710a1ed0981102a04540872ce7765c9db290be76af87d0efb2b0aa928e85a54be50626b6181b9ba7526a7d45e4ae8aa828d91dd917ad32c319e5a0850abff1c4c8f2d690a0fa46d98c299e91131279ad55fefd96a6031b728589c8df4c3e097568eddf76c40a6cad256d3447942e4b0218958d2363b3c38ce5b6020df4ccfa1ce2fae3892b343aa84251d1ac480f4d218dc481b3d23c49036408269c428eb5c399b8696bc079662b0c143bc562840b41829860738d3ecaa3092aa46b772cbebcf497101da277be91c042174423a6a7538368083044c27453a7469c5b2ca7c35001de8b6ad60d2abc0b3015a1a2aea46b71af2f0509e61201830f9aad4919e75c0e9aebefbf58ebb7605e942c6b210cf9129b434b999f4074a66249975cab3c23e9dfb30db820d7e629bd8d14adfb0c32bb144d2d2fa066ba613c699c469b90ace8e6d38040815ea69e575b1bc4de766023fe3b2b842a1a7f77532539d992f880725cf03767168c21ce1f4698e0cf5888c255a297731c9756795c5cdea96f97e6f28a741e310dd0e841eeef5debeabc71081ae5cc119b4484b5ac841bf61cb3da27b553f1db4968ef64379c46149ae56159243910f9c30b14c9cd1e7196a4d5bf18950333e8c7622a82d9293bb4e615aed431b40d7cb30132fcc8b4fbc68b361599fdadd228fd701ca3f44a8538487150b9a543190867d0c3b91bceedfb0156f4b020500a02125aa8a7766662a41a32fcdd3bb21a58d94fb69470f37f7e43585b0bb5450c0c9da0b88e40df21e3a12e1b11f17e223ef98829854290c5eb9fe265f860c3ec79ebf78a6921dd743daebbaf151922babb4d43fb80164937be03651dad2692c996fb81bd8ef6cc7b697d9dc47b3282a07dccc95014ebd3749bc4fbdeef468de8a5e9653a27464b4fdade2cf8daa1acf0df840266549205902376a65f295018b5aab2637a5362076aff10cedfd0d788b503bd09413b83b476e06510b422d825c62c3870dfa33aa514e4e791f3b64e293f3451900e98c5807c9f844cd57607d89eebbe71a70b85197b206ee6c68f243809488281bd00ee03758334d8d1736c74e7de7c46c1227f22c4273ab16a1f4423211550f8e96b7265af7359478ac550937c86cd2ef4f3cc2cd6618451f9c5e8c01a879223118ff7e499711cdd1f0731b70fdd7b5739cd9897498f716738b038863a50b25f5c6150c27c5536615ebf828578a29d23adcf516f836e14772f93ee6bc694f69a845f91b3bd81333c97c907c8c885822518e06fd035ac09e05a7a3706d62aad3466c923578a5746c79041029f45f99f04d4fbd003bb6805de9a8576002867b3d4d4a6af0197734baadd2ea3e903aa492167acc9259f227f84116e45d529279031316f794cd529db374cb3e59917da750ead0f3b8e9b68de0436595ec1a214ff3d37dafcf1789c65998c049722c5a4d95db7065f97b69a7e5dd57ad1e6ab7f8af21d4d5f5dc042aee323187c7e4f1d82ce11472fbdb1e2f978027dbd16940dbdff59a1eca22147fc79cf57a163a8e877d8723423e27e6c6eb13fd7f68a5ecd9902e38d0b9e6866cb01c3bf9ab89bf3160ad2dff4e9bace58f929a8676e509a2bdaf6ff9fc27a8b78971447af602f70e669ac636e62d715b541017ae194964ae5c2dd7157a8572f2417c9b89af50bb9370e87986bdf96b52e2ad0469dd3db86bea32df576404da3f3c1f5a085f7be512b24e0045e11393be74f98d2660cd5c94589360f152af1cd8a46e4320d1e885204200994515879d601eaaac3fe8c518bbad5b893b481fa43261a3cf89e14b9e3b8a928055b8b53ceb6b5e86bbb20c76ae3d9cdd002078952ecd4b89acfc3465320d6c75a7cf815dde49c3cab279fe8624f8253f98cee7519998059f6d514bac27adba9fa7b99ea998a6ee41b3631b906c532d7be0fac9f2ed29c31c99e9f30edd132f8df2650e7aaa162d21e2c1e4e2e9ea81c6c085394f7b9d706adbf5d3f99a0147e9ac7e4070624bc6add5b9f21eea422ecc48ae550b27ff94322d174b4e4ece165ba3c02b75775a746ebd046b870be555d1b636acf23b9ba4a5d7fcc7c297fb6c17356de085ba43c28208d1aef0af1488cbf65337715646a7f23381f54024f8bb9bfe387c422362ce92af0e2e2de5ac1a84f75137cd7c66ea4052981ce51a173afa843b8e7dae610622f50c51756e567645d189fdf3a5fe0fafe1d2c56b11d531d62679f10c61df024814dfd95524789ccb24469ce1280760a9bd26fc0754a477b498fee0a81c53ff1cb26ecb5f33ddc867d0fd12a41c787a5c5919e0f25cbf221d67be6a12a4b05adf4dd869befa1ea453723a4dde1e3796f2aeb3418d33208b9e8923bbd6a925ae94504a98c48b7d6127a84cc4141a5161136bc54d0dc5069af5416d383a8d12783016990d706720707f85e8b4aa178a726328e9d5b003468caf3b8cce020dae4c7196cea937cd1170f3a4049db8906257e6808ea8e0cf16051474aa6d5db0df643121c991cbfa7c0291f5591b572f3e8fe36df0a72cb4b6d9e69db7f32a1b5fa21f30a99e7742c2ccb4a1c850e19cea7e11c394c178d5055d15c0b490cd15980b97082a1026438dfdf962f14b49edc5654f3ab2a972dbbd0125e324691b711cc37b9de1445ae644f02e4a618a70248b3232afbb332b58508b7068734f523b6cf7a49211e2be59f1cf3bb2ca8b77f891d76fd3fdd820ff9193a7a5bf067e39d55814119920b19a3a8fdf5434fcd901b8565257e60e404ee02617f3d3565bfcd90f0f16a3538d20c002a7ca78580f645b75514ea4bb5076de34a1a8e05657a89830f40db422bf921d38aa287489a3250d922ec8e620ab0414d48cafa09e28f54a24ba82ba30d37174ef53ab0249f3759cf608797cc14c1ff88be8ab0580b8639eb8fccbe8d4eb298cdd5c6bb4d6473024af389685110280d6b3a39d3395132b120482b223f1040bef91ba9a51f9fa287a49ba5a13fd591f6bd88d3743d68a7b9300a984e0790aba471a66ffec552c23ca2fa118d6a5b987910706cbdd69a486b7bef9da44c291e06fc063b06313186d8f7c3c418d42cc5c499c730f39eb731a9598a7df8b11e91490d8d09bc813071e63dec391613675ecff33c94f0610fbbd94c883d6ce6f5d82eb4338fc77e3e3fdf6d5e4e9d836a937312a15ce0d7796b42a75befe89cec797cd338f43c7e3d3c1ff2bc4b64eaacef1199d42cf57c90c8d43dbdc5d455624f55cf5550f3fc27edecdbce46a82f7fe622942acefc9bc6d7ff0c0afd7e2ace5c54caf23df84da38b09161969e8c811f5f36a967c9e3ea55f7f897ecd51a7599b4f4599a37f934e3427f4d3c9c70728eba7be8f8f9028b77ca65396a8a5c71f91871db25ece66f5f4fc4d6e053d8f413e7f9b4eb29757b336e98ea74e198c424bf5652293ecd52c057d159964a20cba62da216ae95148ac0a9fc7f06fb24d28cebc6f1a6515cff3c823ce3c51290b4379393983d223fd1ee9f37cd0579149cd6c845a79ceaf6d96ea07894c136ad6dae480ac07e5e5666dbe8fe5599b50558c21776380bd3883127bd8c744263533a5fa24f0584528de5698383ef85085c33139c9317c2cf561e26c89be12cfc77efe9134421ee7135c94d8628b34c418b05411479f8a33a5faf4617f9b57777a5ea278ac17b35ed4accdf7aceefc9b0775a78735e57856334bd2e64472f3363c6b722a7cc970b3a6356b73fe043cdea69607d4e452480bca44dff305439995eefa7759647fdf161b0c7b4e203ae1ad714ddabf67ffd30fdcb3b945ee98bbb9bbe7c82f94f6b21074a3adf0e94d2ef2e9799ed771e9ba07ddbdfb29eeaed5f99c1eceb1621bd778e6bc5c18267415f8caddf89c2012286bfce0d75b1d7716ecba06251465a2972ebf2392329162cfe3b96e0c24047ad51b912bff25b6d0f7babeeff55d650c14bfd7ab07cc049e87895866507a847d8f30d1a98e3cefe46316d86b01c76623dfc76c8f12b6e4238fa885069e11f672f45eeffa91d9c8f779ef1788e27e1df0142d6575326d3623231aedffc5535cd77b52dcaf0396a222287a9ec8ffe01b0aca2370ceb682f2211441d99c70b6d4bd7f27fe84e2d177bd7bcfe36ce9fbee3fb1136750bed777e26ca96302bffb9ec7506acddad6ace1a6d3cc9a35cb238fa0e5f1850b5fce3557de239226614888898a614191b34979141027e02c9146e825411b13fe98f902ce0e428408792e4fc1c9f2469d2ac8d00a3206196974e9fe66f7c7f31596a5e5237b240a595a0d4ac1acae7a66387966e5b1362734ab2bf975df045dcaf3bcc5988d62939a6058c3d8e7dff74d30ac611873a344886be8c8a51131024274a402b18542624262f2f2e87a8c6c7f934878a40271a344488c687e3c5fa50dbdfcbe3b12abc2fa0eecb67d31900868439a529425b3d6555859ddf7816f5b625465456330269ecb23ea9ec2604cbaeffbe6ac9d6d09cdf57d0814ab955e61dda615c6e4fbe073a254ad8dbec21ac6c21a86b1ef0b6b18c65c9fb4739c9ddd765fbbbb7bceecce53745dd775b3737767263289a6cfe95ee7eeecd2dd6b6766767707634176a89c6efb6e1d13cda75fdfc9cc36afbbdfbbe77ddff7cd2c14fa73b9ac9cd057adeefef96466ee84be3dcf33a27d9e47f43d337722d3917ddeed664fe8e3c3fc7d9e6ce3fbf9ece705d5cfab554be8d03c2be4791e1da5363e77f7aeebdc3bae1a92a9999e85c215d84c347b78320fbdea6ee544ea935851bcd9f9fc1f9d4fcff33c22666666cff33c8fdff3c0392de3d073cff33c9b91e7defff03acf3dac28d2ea63a2d9c373cf9bdecf6fafba572d71b7799eb7867d8ee7795ef53c4fc9ddf0bcce465d22129d7be7eedea921474ee75e97eb72eee5d49023a7f39c1a72e47877c7ebbcceddddbdf3ee4cfa72a7b6ad7aec2a3933cac509b5867b97f32ea7061b723af7ba5c97732fa7061b723acfa9c1861cefeef8634de66e29bdf33ed0f5a2151663201893ee8576b88e3aa52831295a563c55575655c5b2b4d0482c8915e4f323036b4ce94a46c124546df3925268e41ab906284868c7acc7078ba1c059ce63636267cd28a50965a5d34be2244e268331f15e36c4c34b7229f06f422e3f90ee8582e72098211e5d7dd14a5f3c916a1f4d79db73b7aa575de5790ece32b14e43d2e4c7608d85c5a4f301c164566214b22614d4cccd35726b4b6e493bb8de9a503cb69f62ed641b06eba86e2385a6d7f41a1e3c66104827336a424da85966464da8324ab3165454b7fcc5a5eda6bae53797f65475cb5e5cda54d52d63e0d2aea2bae52e2eedaaea96ad2e6d2baa5be6e2d2bea2bae52d2e6d2caa5bd6e2d2cea2bae52c2e6d2daa5bc6e2d2dea2bae52b2e6d2eacb8b4adaa5baebab4bba86eb98a4b1b03d52d535dda5e54b73c7569bfa96ed9cda5fd4575cb545cda6054b77c814b3b8cea96a7b8b4afaa5b96bab4c5a86e33b8b4c7a86e31b8b4c9a86e2fb8b4e154b74a2eed32aadb2497b619d56dd2a57d46756bc1a58d55ddda2eed0c54b74897361ad5edd1a5ad81ea16c9a59d46757be4d2de40755bc1a5cd81ea96824b5b8dead6c8a59d55ddc62eed35aa5bd8a5cd46c7e90ed4366ad835f937effa34d28fa113dbd857fe38f2a3714d5862608241022f5c89800b10b0f28016aa5071000b2ba830e5968294063060010a4800020e8082010a70020106608200a204000a00964a20610425114200e183271e74e08483264c36d060bc4b32c0e0022549922cb0211d2139520105468cbabbbbf316a28798b28d36ba8d968ab9bfd79d06f6647ef77d84950662281379defcbee979a02cc2b48e91684bb848ca2ff2dde9b74adfb93fb826ff06263523c9bc2542cf13c3252c9b4598d6305b9df0f6dd298e725ab594cdb2a54b9fddec9cbae1b9d7759f473f57d7d53bdfc117ece592b4a63d5c84f2e7cd8a48dd6354bee47c5a292c84d5a758aca7613c62b33b9f863c3ddf13fa589f9e1ff1c7072802a09f20a2202021219a50d08e1db51d42329904b21d43434386643c7810e131349b15cd78f4e831418f990f1f457cf480000223087cfcf861e40704ff14fc0f6b2bb02f8a47441b410448221089888e88220002040908519020b620408408b14048101a2d8926a4564b52a349208112096a43865c304402cfc31e17ea2194ef44ba6ebe5b90c890a20c8a883c4d2075e70445628a4c51640228a30b1815f912ca77235d47c59d6f04340a75a0c0df29303255010554472aa802c991aa232456201d5d6143c2c2025b164916689124690b2549b8b8408915061774910106185892811777c99bf17ea1c108c6061a84c16483ab264cc4e0a0c9184e3820a30327703ce8a08c271e98f1c1933340f8002b041032204208682889a0811194d22061840d94400207964a5003004b595000b04600a0b0112500710410458e0902e8c0004c6883000370e304026815e084370c5000385030c09c03a0400701078823010890430109f0c00214b0c58005ccd10006d491d2003a529062e796c207a6dcea50610adf5941051c0b2bc8e0001666a0e2001aaa50c9d242951a1ed082162b0fd802012b4e2e40c08608b870c395087079e14a1709bce0050609e0c004430e31306189e1cb9d4f9bb0e8806bda41061c981964e0818619c264a1414c0d59a0b4d4d0c3162d639cb690b1c1a9cc0d36f8c0e506335db8fce0a54b140e5e80c80187209e7210e2cbd3103a7c39b3830e4480d9010d0f6072617828424c1823a0c41cd103549a313da82133660265c820e1439935667c48e207331488faa1024044b1090208258408c2024308b1c49921da107186093444349143e34411b9278c28028a238c88224de50976e823604de6953db8b0af144770cb59344111232314547004c91192cd82a4244a2ec020832577d46003264d3870d281074f3e00210411944620a18425004009401401983000029c500003a07000042440010b604003a4a4709ba2c20a2c38804a95161e6005022e44e0ca0b12808129062c4d8d6b197a86a6a1b3740dada5b7b453dbd0373497eed25e1a87cea19ffa4bebd03b3498e6a1c3b49886ea1e7a4c93e932ed439be91f3aaa81e8205a881ea2cf34118da6735d441bd147749a58db400748e5f3db7e5df03ba5ff7bf01381aed755a8dc29ddbbc0fefe16865ba1fc39e9eb6fe1ed34d7fbee4b6cc1656f54ae07de1c67b3d96c369b4d4a29a59457fe7dd3a960a482d17c158c5430f2e20ca83be4f294196c5ccae35364bfdedfacb838658a3f089dc856faca96b31bec9fe24dca7ee3ae053333b333b3333333773a5871d74d766766eebae7ae73f7ced9d9b99b13092396396d8eff9c57d8b123478e159e2a030e381f04255127250fa919d58f35c5c645b174834428aba494a449299e73cea9a933da14c599a2443690352997b8a8c2284a92f644d82f9334d9a0d1cb967064514654196ba8c018e2f6b84cf585d4a53c332597f7f425fffb66dfd366cd639a3f452699d734be8d041ea9c87493f9df6cddc86307fe1229edecfb28b3e67dd3e8b2b34f6c81041e95442627de38e37f5d89b3251c55c3429b20fff54bf3ebf357f92f91a9aac55907dff77f4fc599d2eb674bae7fbdabc78631f95cbf444a291ceed266ee1903c5f073d92bb3e671d67cba220c2eb25c0f4a1e71185b9491483d4e0e430b2fcd9def69bdfc4a61dfd4afc6c5a57e98c8c4d474a52a8a94ad2b27597c05fb259e87f53ccf7be21253d087df34de786be61f7b1ea59db9381b413eeca1f407f7d6746e3fe801d9858aa83454fcd0c595623b95e924ae1cc2ef6f2bb6e02e1839a693cc4ef2b054124d029f3c37d9fd6e61d79bb027733a7d2394ba231df71ae7b3ace5f4cefbc029394059baba653d20112e92df31618532d1cda9cbf61dea896aa8eb346957234e727d4f100c4399ec1f8b6fe7248df62f9365792e97cb45eb64e2ca2a9db0b0b0b01a8b7e7d02c13094b9aa8bbe948934f4f2a97c1e6b2f264549ffc65b208c8a4d66ee36f5584fd3794a79c8befc59a59bef6f9c45c5f924aba4d4d02f99435686269c35d9535de22cf62c4536e2a409e34002512130e8a78707f6469c247b17dd41bbbf4929100c4399ec9f8a4de49589b3cdcb29ec2c4993bf43149508c127e992469c249b8986aae20a893723d7f5af9732834420d187b3c450bcc9c498787b176751795d4fad1117b9c4998b612eb1c9bc3723f7c65946f6d64d5cf9df7472dd1b67dd8c48ad6bc4d7ec90426d384141444e6611071c4f23953858a08ed448679d39b8913a9809e046da51255181a7917a5a4f1880a30b2a46262c3ed6185922c122d4441d4ae046ea22838e0dcc31d217951719a81a29f522aa0aa79156aa3590801a29ac8a306f9c461a0be3ccd59b3284c88d3484b2c269a43d555a5983971bda8cd4a74a2b6f0033d566a43f55daf6a2c50a6aa440555a2639068e54165b231316a6630c183a46352278995822c1c27696787a625433457653278bab910a0505350b0d6141868badb1a9b1b49c5274de2a7ffe94190265a2e99dc8d414b03343d588e5cb1a418944ca0b8437f082b3366b73fe1499769041c3184ea38bb44e62ea08ac91ce9601ea8ca7715a1e5b76179f349ff2146fde688db4e1ecf0343a9189026a6434b49863cdc85ea8c1658db4ab9de53034f084d548bdda5926b6620b17592396971458d3ca664b1dcf160b6c8d1dd8d9231ba6781afdfb0ec8a00b041f9c48af07c16f4e127611f9a078035d6e6f4d13c6840a757710743d88f4025dfcb2409dc4254e9d51c575f166b3048d3450c1e1264c8479b1dcef13dfd288e6cb3a21e850d0f1191599e01675443ca1cb433e095c354a8309e1711ba4c72d901fb7444111ec1087ecec7dfcf80181f511410f2033213c6a6e8786b89515b9dd51c4ad9091ea3688e5d1e74a96cc9a5ba0fb592512897c7aa4bafd71eb73ddf6b80def74cb93d434a9b36c01172555b754092f91ed16bc605a2592e6d6ddce25d52de0a20d24136fe2512d0e40f75707b5031813ff396b375b22bcd96c4be4b741a844d6a407b127e107defd810f08402108892053e2e1a30704ffc3462012050122a4469380c890a22213185160a40224478e6c481624495282c10519dc25e3061a30e1a089130f3a7802c207212889304209242c410140000410c504020ce0040314a08b1214aaad8b94038049c182ae262500cc6d0164a634a08c0af24b8187156a2b619ab48069720aacc9df166d6156a86efb884b59a86edb884b1d50dd76119752a96e3b776995eab6d15cda4275db445cfa80eab6cf5c6aa5baed212e854075db425cea4275db415c1a81eab681b8f44a75db5197be50ddf60f974aa0ba6d3397c250ddb60f973255b75de6d218aadb26732996eab6c75cda54dd760f9736aeba6da84b5b86eab6c55cda3354b71de6d2a6a1ba6d1e2eed2cd56d83b9b46ba86e7b874b5b4b75db3a5cda5baadbfe72693b55b7fd7469db50dd760e97f60dd56de3706973a96edbcba5dda5baed2e97b697eab6b95cda3854b77dc3a59d4375db365cda4fd56d3b5dda5faadbde7269eb50ddb6964b7b87eab66bb8b4c154b79de5d2e6a1ba6d1a2eed30d56dcf70698ba96e5b864b1baaba6ddca5dd4375cb772eed31d52dd7716993a96ef903977699ea96ed5cda3e54b74cc7a56da6bae53a97f60fd52dcf71694755b7bc75690351ddb2072eed20aa5b96e3d216a2bae5382eed21aa5ba673699fa96e79cea54d4475cb705cda68aa5b7ee3d2ce55b7ac75691751ddb21b97b611d52db771691f51dd72072eed34d52dcbb9b4d554b71ce7d29e4075cb6c5cda4854b7bcc6a5bda6bae5ac4b3b89ea96d5b8b42950dd32072eed0a54b7bc814b9b4d75cb695cda4a54b7ac814bdb02d52da371692f51dd72062eed36d52d635dda4c54b77cc6a5dd4475cb665cda4e54b75cc6a5fd4475cb702e6d28aa5b26e3d28ea2bae5312e6d29aa5b16e3d296aa6ef9ead29ea2bae5302eed0b54b70cc6a51d51964a18810a19618a4c0385301207ccc1085c94c025acac586a49c90acb8c0fe8a787ddfaf77137fbfddc7d7252c7493ccfef7152ecf93f4e82d5245a39097c7e588c87cebff99ddd9dde9ddf9dae3befbcd13b937c7ebe9c9c14fefc0ae3a4eee7f78909bfefc702f9d820a11db2211eb31e3e20f8c145f6878be48716c8060971917c98ddc145f2ab95d9211e5c24df6567b6870f2e82808b7e70513847e8a325046f70ef7c6e3e746df5d33365403f3d56640f40a1191158055cffd9e38621c82008be3b7844d63e6ce3e9b072b281067707ef6024215c936f346b42180909c968225da6f20682408420d8b91e04c5255cc27ea3b9c112ab1c6067c8807e7abeef6fb0ef6f3c975fc9f79c844e9077f0e922f2db0235e9a16fc810280c7f5ad18822b04cb907082491be9f3d74a875228f1c4ff4ef867bcfa7dff7cdceeb3a6f4eb71fd0f7c942300462d9114a248df6830c30743a5dd7d3764f2783a083a1ac7560083e4f36e19ca1ec3c91e6855606e8b5e7303dd91e967a267c61044366c92c999925b364e6efe9f1f1f9f9010262212166e6f7616666c92c9965b62b0b7213e4e60aefce095f96b649abc08856011f3d61da74226936224fe4d195bec2e16433f7a4311294ae4d6f9c6d2d1c15a564b9137410fcc0cfddfd7ae715d40ae378c3f7e5ba9e573d8f525707e5d2939dfce4d7e5acec48a4be0265cf798393389136f4ec2b27b0cec2ae14abb4b4aa566b85a083134b6ba2f181adf9665e4d2bf06fd3ca65acca772b086a69b95c1de875567ee12227a943c3d045dac0a38cc2418eb9f3a5d60eac500d23358e6bf3d34469219bbaa184e2ee88229c5ef0d1f7f367183c31506792c1488d9bb54e72a58b4c99a881099b6df9ba6ed7755d57ab7ce224d95ab62f5b29054b279836ff4a27e9ac2e327f5291c4c69dbce5e562d157f24a369cc6126d17e1f7b7bee24ec6401cce09c411e1fe65681cae03c12f8cf5f490c964412ebfd86f8f6060da1ce2a32b9236e9874a12063e4ac2b4f936499b4fc5bebaca5aea22f33bf1939fccc9237927903b5f5ec934241288c5664a4581bbb2ea4e09e7ce29dddcf99fd7754f9ba58c755d8fd2446aad247cd44eb491b469158b6959595ddd97e5d13949def93fd2ba6e8f9ab07b29a5c4698562ccb0e6b66d20b8288876828fca740f4c9bdf4e5dc79d6dc4937438a14c82651a4e5ac349f226739cd473e7576935919eb8487e895ae2f670e7d49a35d87d59394eaab0b55aab2b623271678e8be6b3010317c544ea37e600e74f2d31c29bcce5e60cf3ebb84eebce032ef4c8c3febeaf6777cb29a79c5e85334ff7e979288fe6f7ed5726692e0508ca99c691c031c8e5a90e7820ce9bb0839801e68d335371d87ce1431d5f48b065482e6808301b000347ca0d38fe812b26cd89ef14bc9812238e1c3b5e9cb1f3459d26a82b78b89ac30cac2b92703a6850307903079c115b561c78c2ebd47e38a18a34e83c3103163f40f91cb20f813a1abaaeebba2ba400e38501e99b31a8cc5c5dd7652a33767290800b194d703953070b0d0c31856f8121c7240386902cd8042142881c5f1c088c1a0f12c82185a03d5138b2b429734689ab37ee8120564c2068524b1a566feea06163ab8c91d11b2a36535084e0652a3650c83ca1cb546cd0b02923ed0ce94172d18137f241f9619b3754312e6f5c5bc891f2b6bc19837e3d2e538959e2d2cb5462d208d1c1853b45a46933831923d6c861f485c75185c5962f6e8ce12285865167bbec4ebb42cb04be2d5f842133a9c4784165460dd7130d5da6d222cc7d5da6d202897b7ba32e0ca3967d6c847e792a8e5518278b2f0edbcae16a035dd6b0628d71d28983c5957d5371a6be08c3e9727dd3f5bee9d20f7ceec1e3846f078931263227c021ecdb5dec56e13bb7fb5825e2c19e4c97074d2d806fe5bb9ee73df1264d37d9c47bffe941d3ad12abdd7f6114c19dc738893b30dbdd93bedb2332a4e63fad4c3e99dfc4e7ac83babcce05ba3b0cfcacf5afeb5ca0f884ef5cff2a3ca5bb4feb60cba9efbf8f69a44d63bf27ce963cef9bc6177fb5209bf0065e998f2542975113e1105c1c2da5a858d162bab245e3d0927ac24a85b3a54fc9c927ce48e0d1d634468d4c3d766ed6feb6992e428544514d59dd7e6e28cf93603a2bd75abdb50667e6de9a4e63cdb937994576612c293d89bcac89227a0a2594952c2325a3726fa454d59a36b989c4e3d8df663a89ac52a68bf4f3982ed233694796813374fb659544aa61f5d41c3797cdcdb1e6c471a9141d32edb3c7749996c2658143ceed3702967ca8040c1d28a95388460000002011400083160000200c0a070422911c04e248177d0714000f65783a64523215082391381848510c84530c432110c22086102619320ca2191b5000fe119b2f79ec20e98583a718e11691fa39be5f84663d1e7d5568c5c2e90c46c61fc91f537bfc407132d6e8571cb37ea078d81a85a8fe08d532b182b5998d69013d9c71789771c5122b9c17527f21b526f5b4e6b465ac4ddf395a537c3198b9296b86b57126b6c05b3df56f9fd74c84817f9900714e928259802658f5a4ff6f9541ad2b84f154e13037523a695e9f2754a5deec90a13f335a901a3be75bd0f54a724f810c69536d4d04212658b9171312f131e35024d4432e96ee8ad01704a843c4b94703ab55e0588fa1e8a38756627c2b9605bb7c5a310961b7cc6c6e78494891b7c06226f1df093469c07ccec0b16e634ce30f42f44c659e1ddcde70e4e3946f8b495d6ca37752f85236b222e838da1db0239b2390acde82ba8f431446d84acb558c7d2889d50fa64209075818a604537fd45d2ebd4fb748845fe54049309847ada679d79a6003ddfe933419ef93dae1aa41667381343252e3fb92ce76e1491e6b7bacc8b7393f999f6259c70412ffe03d4f3515225a586a51d6feedcf6c36189bee722c5839181998b5a1102b6e411e603f7f0e7af2cdada2827c62e90268448bf44887012665473272323d353ad65e207044bff3b76984da6a5584a625ec990dad613f477212189e01be8c37d06c80662b6d2ae4e82c990bd296c850da2f6ebd3ee6b3229660410843389ce78222091d201fcbce0ae0a4e77d2e330c15cbf9babef1e4c62485c2818e097c08ae0b90b40c5a02d552c262f557b55b37ad1f6289b77dcc81ec3d8c4dabfe0870de6b9757b0a7cf1ad492d8001ea9ce71494afe960758a8ec0a4d180334b21db9e049b1be4dac63e01bac7ad1c96e2154ca89d4d63d0775dad7ea88266ddd9921ff03235860c870cf35dc2553f666e9f5a1a359c544021f178fb55e4fb2af9a357fb5049bfd32d771df33cf334f0fed50f2e765404037058271620e6312754c01d375a736cb9e2d193ea3fb8e1776a82a5f2f9bc248828593022cb73661060810d2b1ed123fe001af3340a59303ba729ce3c9ca6ae97c74964e9d87fbcdeedaf805999ba5a38d6a3507d9a0d81f747e7a67b1cb4309e6bd7709f4b39176940d24ef7bb9c43cc8c240fcab1be31190c652caecbd84b3ef5f8c071c7f4338422e878481383f05dc987019d0232ac0856628df9a39c756dfa50b696367b4da19a9c14636c2d64af06ef86828383b3810c10601af19e6e9e9a52e632edd6502a897339fe73e4ff7f794598e517d9a8980d3bbf67959b26054563f2c0a90fd2e6caba6eeac97811db36c4df002c306c30c50868753b90adf445a62d87c71b5df90056e6506f3fa22b9642237c7920c04f2ad66a1dd70e87efc0e440b2b37748938e61686ebcf81f62ec716c6a449420785d09109fcab8211d1932523a47fb19207ff60e606f1f0ab329a71b13a23da45c5472f9a860dc1320e71daa154cae3e1ed0c26f0cb99c080e91e746fa3f99c4781d35c713a4b8270a16b712e3424e1e4ac969caf2951c96bac26d3189f5db3db9b95d41d4b0d9c465954e5a5a369035256e239b3153a57238a030dd19885b3501f60d45fa02b4cf21e2c4079808dbb9515fd47f4894987c633d1ad70c06862f42decc13071533f2c63a1c685854cfd38493f6bc9e5349b871eedb7e815f5e1520a3f31f6edce2280bc18f5922a8a170b152a5624664c55f1bd2a8c718590d154b03d03371f2f34837e1a926e954fb68cdeddc55ad52aea3d67062c13655f44d7978a1483347967030a30163b86a010ec9a4da939b3b4cf66d5ccd293de65caf3be3f1e2343a37fe7fdf05125815cac8b55ce2a431633010d481c5df059d2be3de0718ec276d215b6321612428f87c7c4617cf6b56222519974dfbee97734e52b1a3890401833e45f2f9fe06046cf8b81ddf2be892545fb65119d7887e95a257dc4ea2640d781c59ffa767c1e10be6fe4da8f33798c84612ffcfe3548d501d4bf576bbdefdbf43fe1f77dd4c718c8f755b895c6365ed1dbad8381a20db3ce8325f4787dae2bd8298b08eeeb50c4b25ad4e44e2d97f771f81680b6ff3c468f26eb627d0fba81bf34dff129a0facfccf1b71d8bf35acc5a75775c4a98692999208fb8f27e6ac4337016a171b90f9466e4f8e9ff72e6f9e3907eb2f29c5b817f8c01a5e11bfd238ffda9aaf282e86f70e87f6e79fb036d285b5403785e019e3e9f019ece2a6c0e59f8bb2016025be3ee1e29940560bead25b3b381d3919cdd91103a32f03a6a3546e8bcd9115f960cbfe89ac14a099df3f3e7f5f3a005f4808a3d26d2c1cdb5f0fe3a6122687a55a1293b2d72b80ba563dd0b9bbd481d32572cff070b09c5adb6e4aa242db23c49688e19cf0c895b090a37c160276410ed5723ad5afa835b9f3d1eb8a3df8691871fe4795b9968dbbb4decd698d8726e398ddb84634e415354c3b88d2d3459d23bf03de538685e1807535aaf341b2fb70919a90adfd36d82e1a88a089da0996d824e814511a080054bb6b2d4ac78215e90654433bbb1b1fe4ee9c2064ade943968e0d4cb630296560a2c4fca83eab3560828230277eac1d61224ac55eee676f298149e6772b53266bf0d6b47359bd004df6a2c534fccafec66bb18a65988d6ac84f566e2a40121162c11310c0cdfdd645f69d3c7701947a82116e12a8a94ffb363bf0e9b30fef63c3acdd4615e567fc8c6939fd8e3950f0fcb341080306317c8d97d6a716da352a5de4ff3794eaac4a0343a42fd5db26c349bcd80bd0124260d57952ddad545980fea828cc28f0265267960d8f89ae3bcde8151e1812e8c5510a2c4fd21aecd6e6dd6234482f83a5c1c0cd7ba0f6d7a506b8a4ce3a609b4628c9e8a2383943995df99fef146ce42a104db1061e4677dcda006328ee7bc2771d29ece5f8971c56300fdf1d3242868c845974b551ff2151a8cb280f0cc2075b8dc2d8a5806c370d115a20a9c546f3e9d998b7c90fe3306f558c6454d207ae51b0a514e579f05553c259a83217f068c3997434c6a307195a4ec17f5dcf04f137ce10297df7114f4bfbea3baf0b8f25042b84670a94d90bce6dafec1318c481d7d25e4b155181af2aab2b8ffb1f4551544d5fa5e16c8141d30cb5fb5a9dd00fab5f9bb37bc658009b62bdc7b0bfaf34b4a4e8fe239ead0968fab92a73050ab64d521b0e0a4656ec944bb35138a1a3e33f369f70980feb26c69a124c407824ebc035965d114ef6258d81175d3ba17756db5d69e5a0b427f039c7640f8541b8e12123d012837a8ad7e6955bc2bc56bc5826a4538bdbeb66114e104cdb134b3e0e15d7f58978b85f5c5d556515b54b7f52c67b338e3ae502e93c3257c0babe0b1ddd8e2bf86fe995403c4d8de3a876b92a8821afe47f812138cf3252ec9be34b3e0a6e07f48f6d1b881316c97b81dab215dacac75b80e40552c586dd1f2ef997adccf62932415d645de4ae27ee25beddad8a34e707de5948c2d559fd4b974a0644da4b64b54da45f6288aac52d9704c38cd364e5a46a25252cba012a1c8c4fae605165e241df31d11be60f7f0d71e3c981f2298dde1ae60495fb5b2165a7580d8f3b32f35a587e4034709081a38ce88be45f34b31dd85232b2b3059b10b22f9c2c50468882d0ae025615cc473d4a14a61076380000701b35e8285fa602dea80850502bef6ae6535645d258bae7d3e269941d8126a813bd24bac76d018d041a6b7258413a49f6b8a6c3611da3c7547a575a2825942539373cf188d96a5e0852e9db4acf01b52c2c29ced8f9f71bd360416c4fdfa9d4b3c152d0fb220e2b12f1bc376a197a4093fd558d2adc3e839fd6050f637276a83d7758df9a3b784cf2492ed6b2eea56d769ecf287db1c2e9a0f453c65c746985501fd6ce3498275c2bd57d2aa3ed12517af954593c6169b794c812c29515866aa7465b4569bdf04a80da439d6e9f6ba7a21edaf63a7130b0474ad4a822d850f8d1553549038f049aec7ad9245b456e8abf8c9eed2c716794a016348594e0fd48f3bbfcfe84bf82f65e1503cecb947d8cc1654278ffa7237b67d4aa97853d04bc9a736629e693d794f6e43711d02220c05c2dcd5fafa8640181540f2f49453ce9419b8dee6cfa512466834ee9e7bb937d3102a504ea62803561aa2084f4ce4ce52010c1a6ccca3b54f5015773f530ea1537f5060370584242c1d845e41a20c709e905e87bde3b6935e3b32c46a951ae533ec7a01a1e2873ab8550d5801471ed35795dced4aaa8c1c312a83251d4a84f276d6a94b9aae182e0b7ea72b184eddd286488663154d12e1697b806e8b8a90ea150c392164183e32afc05733ed8292420c87173ce394774ecd0b032405bdb7abe30529106f868e3a146836661cf7615241912fd2b9f5bfa5c1852cb3aaee8b87023f1555d2832d6db526a460982282923be9d598603f25cf6acb03a71b0d9387f1d655a223b3c47261a677d1808fff37cfb8ff39e7734d73ac7a073a90060cf4447920822c10f4e11f196de8277df9d4e20e761508747cf8d63e62461a431135d9fc4f2770f54674b64a7b003c8bab13812d367173c5d96d8395aab241f10efa96109f9d3cf674dc3474556c461dac57f00cdce6678bc99d842ceea7fb5dc89a35e9a0570ff5d9c91d7b64213f6dc672c1656100a7b3223516add60839287c3e6946b40535ae799aaf0df1a5d88dd611a8e4f5684458be52b6e15917d98e8cd1eccc47ba0cba9f1972d348dfb07c5fe0646e88685b68868bfbf3dc36b261c560ff497e5ddc71ba757a1d060dd0d3e9d8b3c9bbfbf354c460cb8c33d1571b47f97c1de8a959e53f3410a8836d7de7b6d3ddc6aee4d57c1df53fac4ba00de3bbd88ebdd9909f36636db9ec74b7f90abb9935e7296dc51834185b663a0cedf69ad03d0540a951c639bff9f224b25f876a4cb6a7964acab129a6853a2b54eb72897925b5844f589a897fafa40238d3708ab591f467fa2b952bc0e56f31b07efa71851e80e2b47ba2ad49a63265faf344b014756878b8f5fc3c91c0a09954687a2d7896c4eef4bd18951a43253d6cd1dc3ddc8a0c764afa8b65a00087fe415b70926a3a995b113ade89b8a59aaa42fbab28302538926a648caaafff58e306434092d04c233c7ebe0e789a0f1d6d9eae46dd8a6325f383d36e48ef4a6131a4fa204ccefaab8b87d7bb1c91ea08ae86229f364a39b68a58729e0ff2d352393412a547b192f4a14cb6668ae8f380e12d19325182a15add83bab2099e09f3e2dd5275f26c8bda0baa5a28b81721463728ef7cf2c90101d540b12c276948413f0474905d5e7206a8215c8d8e559865a082bbadf0d2bb8500fcba31f8d5139548bff03045de92c42236892de493e813caccb82980b031dfde3ae48ef6bc49c5c6332468f4b2105689c55132e645c9c88ea186d809a54b5742d11c32ae7fc6a0d3038ed887ab7be650817ca585c100af89aa50e25fdd3b7765e7cdde70ff32ed3c32330504e7cf6f983f369018d24fe3f7013cef17c845e5d8dacfa9ba812a72b5d9a0fbe3d9a5d47bcbf67b43d2065587e33508108bd4c53a00046b63ba709e6ecaa32afe5b6c87e03aec259f716a45406bfe56adc8f5e329f1a4b80701c4e9f6ac2a881d6dc126891634a0b77ddc18f214b347d6c692916b5dfbc8414e7c64399628e65162618b1e88169e013c8815d6ba21f9790854513cbe568f00ae14ec9bfa2442ceca8c6ad5c8824ace7f569bdcf5f2abe63745ab65149bc3fbe6c0b5785c116ef208ef017d02f5aff1636cecaa05e09e5bb522e0dbe0d1ed924dcbd121262bc9ad2a6a8748165ba15c542cbea0d5fe81d826c4d536879d1f5fe8584108012736c235bdf7927a704d5bb62d9d947a81058701ae58433890a51cf8e217fc312e5b169ded94efe6b6481aae83cd820cba68c406d27f90f8891cf5c13594748a56c67e26d5fde3d11f37910e98890daa9d4306f84d6c0e983429a0a2f5819c677b2e65b0c472a7778341b25f7f42443d38e0736f00be78c809c5e7c8ef3de915ad25268be88e178ae67bb5da915005260b9c24daaa28b84b517ca3a55d0cba7e0b3c546451c25141474f0aecfc3d10c0b37087c20f050ef77ea44268241aefcbbed82ed3d95871d01df3ac4ac58122415b720ca3115305c31c708ee8df707fa8422a20e721eb102814b232586ed9b606b307e9beb259e7f1aee62038e737fe2b99697c188aa42a7620bbe2fb6c04501a51b233698b5e5f515f191774ec0e9c028ccf5562ee5c4a4fa0f64fa85c186e9867beadd179e8124f63f7b85212459b236622de6454180ac475b59109057721a0826b8d51adaf8866fc028aff5616480418e4ed45694462b7eb5e963666ca921b3e5418354a3bd6ea488282b83b5826c9bdd8e2848176d789afd4f7e62b9fd17e2375203e3fd26366b70fca43bacac9b7d0560c57d5fb5fa7314162be7fb4ec70386974d8e037d22f7fa70e2a7df0b885d1e9a9233508e3012c0d230dae0d2baceaa3be857f4576969d40e57daf6240f54d9f37bdbab66bed2300e6574b711c8b703c6856961c039da44a855cddd5c8e8a7bffc5deddf1c7523400ecca6bfb749aa214f524530011cc91e4422f1e8584c75967d9ef5b8f416ef16af03942ada14299c3d4d70a59bcb40b460a92ad0b239513e7d48f4ebd974a4e5479d1eeb4178d41ee417c1d011242c4e17eacd8249955a4aa9f7540bc5b8e430fa8d8974bd850cd48171e1f5024996bebff4f338f0a024d13262ab3fe56807bfa9d4d5979a5f0b42ab3b04d4f19c474e80cf1fb2e8d9e612723babb57e66d6a326a0db6a6e18b48351d6fa394a71ca174aab190c94ae5d589f04c8f2d9a12b18f53150f2899e89ccb27ff91ddce3345afded292707144e392b581e4849ed43bfc051babf6463d175bc994434edaf9c6789fb9850028b8d74f431a033c8e6763268b4027f1c9108f6f218414ae1e1b1b762841fb748891b8adb421803f11c2bdb12c6a0d42e5d5de84396f6cf6d407092c3bf5c406b1bc1cecd31dd990da0f075eca171d3f0ada99d16145efc1e7da565301de1ed10a915d77e2ac5efe45430a240046b0145f58b75ccb4d98960109ad9263cfbdb598f08694c1011ac47e0fe1290b203faef9bab3fe1f9f82f6461ae1304f4bdaf6e6f949130f250fa0123b910dee0e62d0d3e0dc91a2cbae33d0efcf4c053d16d1233bc2091764ca7b85f29e8e14d2747d8564829f99310d28009c8836e39e3af0ce757ce1bf85c7238f822f29137dbcc37d3f0342207516ba79c607eb919e97a013257772e171eb84da9212a364dd1bf54d7d4e106c0c0be94eed395d5aa682a1f8b8b3421c6b59bfbf11b8bc8536d05bd80da44fd002548db3d605608e0bd664241cf19744d676dc5125effb00810ef332c47d0cb9484fd46715e40f9eb6817686a3084a3a40070ef204b109a11b326294815931e4c86c9ffaf6d0a314788d92c93b3bfca1a1e1733270a194cd274a336678e8833e0bc51b77e792cf4c0e9dc5cf643f2f5d03a93b9dc4025ef80f9443763b77f8ca09c5c5d2380f3f05d29c8c0df2d5a6e49ca33613865edc8527eaed125f0af8cd92d5c0556385ee6ee189796afb64d8190dc57d4f4b8b5f93dfe4d7c07231967daf15e21827c85cac6a977058356ecce0d1cd92f60b0700bac30df9b560702215826dd6d86f07270bdccfd475a912f6e0ec02a5935dfefb1138efd8a745008b223b1886fc8015d5718d48b860db05955b1074d92bafe5823d51344650853d86f4ff773c46aab645fae90714743de5361d050e21d11f9c4a5509ef60eabb162a204d1fb1243bfabd86b7ea3a1da566e56ae370b83acbed768aed29b3b18a45d1a87e23fcf0c550dbd663f581f34b0fd8c8c6e296c0ead83186c62840143cfb76bf7d3f2a70b7cf35b61311760ca9bd6311befacd0281ea17d8a0a6aedccf4d9255c4f529685987d37918fb2e5504d174c4ab5ed72a75d7e4a2d60677214b58f653f960525addcd7a4eb9603793e4305f5cbf7bf91e8b57e498a70834cfde331112142f6e9e51101fae817b11ae2584a8f8aa06213fb39b55027257e7c801c87fee64ba66701d1cef47e7840ece7d085265252be04958ecefe3dce6e3c29c0147d778536f985b43f85f163575d5e2b783386b5e0aea373ad7d5e83c5202e9562ef0a4de9107aa50f164eacba26e784292ea16198d270a14ed60506af0566f77b3cbb48a4f75689ec8861823376d0f7873af70016988a536dec84c6533fa55ef49e277c01b5982332113856c41fb8cba8b2817ea879a3d2f34aafcf73255dcac858771cd324fe60458d3e60f29dccdac5c9a0753a2f7a60238f6bd422853cb438690c929191a91a85529bf1992bf750c630517988ef7400ab85d6d35119be8259496369008aaa0a8af84db509b1c38abdc6d813bfba97c3f62237dc27d85030c719cce17a9fe97eea3e0a3824c159450c19dab45b7a648ebafc68328c6ac55149e703da4d0bad530f4ba28ce731296b5182553000642a9bac31709a7e39f3580e94fecafee734d05d04e2982173771a08f99ddd85506839e176ab380135759333bd4fdbb9ecccfcb4a15d5aca3ded4ee24cc6d3362e39ff3a6d688796644ebb9238a3e0b48dcbceb04d1bda2d2366d3964d99b4f4e4ed42e0554a73bb584008c9ec0a8610080903287e10718edbe0c30e7e045be3af47a372784dfba8f18ac8f5ab2491b254386530108451d5902513953d47bdc9bbed179cc8e0f0b93fef2a6ff01183a8691e15096efba1fd99295c12ef159145b4420cad3f90943d4b5aa76a425496a63f05041667c8d9a59e89d50b24ccd53d85d4c6fbc03fe4575584f00aef5b9ca2362312ef0a3858e5a32d9d23bc5971596cdf3864e1c5fcd43d2fdff24eb2b3b63fe861cc75f2240de001c5ca56e1321ca4e467f3c2197100ab2592dcdb213e71cb2a0d177842ff98c70ad309d398573abf47994866c8007b79d2fbc1dd24da88b7d24ce411dc6a516bf169c8103d6429ebe638fd4cd24905bae87a8c3d743dc87d753ec43e5d1e635f1d1f739faec7b04ff783b85fc763eca1e321f7aafb31f6e9f210fb75fb98fb743d847dba1fe2de3a1e631f1d8fb9a7ae8fb14797c7d8afeb63eea1e321ecd1f918f7eab21f232b13018c0ff4d0eb1bd5488fd614f64951d4babec83d3f222295dd63765f9518d14ec01105716d40ace3659687e27f6b6aea84a772aac0727bec47100faecf492ea65d926ed5d5d34ed6a6db3c7d1035a16591f08f09cedf0abad4fcaf5086b546781f4dbd3278d4fcde8f531596b3b992efc5a7113a7369a14bdb26a8f1080b1e13a7e803776389eb96edb7a8d40177bc3be0f28bf2a64bb690ae190fba85b10021f482bfbb1c7d1e955c6cd6ee17caeb2e8b397c6e45c76cf8528db8f74cbde4d551cb164c9c1eba3d480f7058fc597998bdcc36f5b5f1f04a179d2a70f1edae5dcf3dd3bea8c0868f66d3e7be57de0ac46f77715ef7cd07d4c5a3c988b398862534689d06408389a270b6369a66a5378745b9f2aa86fcb9c6db3b3eed580dc6552f875da3ed11740af332fffbe3fe7098a4f219cb45c93561d1fea806100b8dcc67ba65413e3b905b9899ffa7e3eb495d46ce782831787da586aa8c3b5cc5bf58b77ddff7d039818060df2198e9858cd961199b31124b978dcf4f11589bf4a3888301a14e342ca4bbd5e6b89611b5c353753fc7ae0c413d416477c31805bc64138fb9247c8502644c0224b99c80fdea37828e1253b892658b9591fe010fed1f42e8a28b7ac14301b45b7d7e246cf046cd92c6084827360b411402a22547a78ea63a80962b2fc60c69c3391d7c0b2429b534e48206b9f2581d805544da231ba2a4d9f702a23627c70210f08e0af8dc9214ae9063c5afa96249cd14f94fcbe6325049f256da42e24c9220fb7be600219ee5c06c7ee57f59463b657e9bd50cb1cfdab457ce9eb3cc12995a887d5980bc65c1ac51f99810909ce80ae88e04da59d6476b8a644bab221aa502734575203b12c05e791f6d33322dac0ac1140b981b5581ec8800fbe5f5e00dc869655544575a682e7405304702ed2deb83352045ab7513ad7201b9d04ba01d0b69b7bc8ed600292dd795e80a85e48056817e50a8bdf23a9a2624b6b86a82291422075a07da4321f6cacac19a23b3e5ba88a6a490dc2835d0e3802d6459650171444955fa3a7d1615d18eac89c0f4447185740ade65d4e17a8c4c2ff015ca101846e8a6819660be959e65af6038f59e7f1b01ac2ce956fdd8ea06471127a6a88b80edcdda7bbef0027f71ebb0925024c4dbaad36cba3b58df4f1dcb06cb06c2b15063d00a0e8403cb450380767aebe024c6ab4735241ed1e28059b191ef07077961cc4c64c9f79389b8103023b0e6fbcb212b08981735f3fd71909503990bac5c7e58c49623662246fe7f7964a50033b131f71f0e9942cca8a895eb0f87bc34664860c8f5cb455c123122b273fde5212b88988bdaf97e701095838c05a67cfe78648b11732256fe9f3cb2420823b135f71f839452c8acd8caf78781b830642432e4f5cf445e0a1811b373fde79095440c8b9af97e18888aa3cc45562ebf49b42937212762e4ff9747560a30131b73ffe19029c48c8a5ab9fe70c84b638604865cbf5cc4251123223bd75f1eb28288f9132767dd46b245882d72780e6efd77aad265eedb4b84e8dda55723b69d2280c62ef2fab0e90942f5fa2557039b7e24a8c62d71f158ec2d0274dcd2ab8f854e2040eb2ebc7258e83501b5de920b876d6f13a2e12cb89e58761221747ec9cbc34e4f11aae9975c2c58f42b813ab384abc566bf08d131165e5c2c7a81107a73e9d560a1af09a8774bae1c167b35b1349fe414cff74d81cc5ed8c9b94cd2f00a3a4efecc826e73f4b7dcd74007c30dc37521bb78fc429fa85e2f633221b82062d220578d080a74e9963b505627145cd330a94ac03ae50f81a5bb055c3cc7f0b5c399ad7d31c3518ea81363acd0ed1e6b43403afc3caae17c382a1f4ea7127f8c7c2a6de48e7f54e9e96341feebf8a60ea3573054867aa4f0838be382b17bbaa49258a856e19b386677426b161529f0c858dd04997d417a6d471caf11dc094e0e72d8f18dc4f5e67a3449ecfbf89cc04939b7397ae1565534992f018fa675bacaa3d3d2102954e6eeee8a6f6b21f9a4738aee0c796ce9bf98727e73ffc772758621db8d34bc8db42bbda0567ace869a44dc142b048c7ee9a8bb6834880fc80796165d6822176e2acc124224701275b491160458d4917ba6ff1137ef0cab2a2bc6d013b84e7e1e29c3163c961da8921edd5784d84a0f9efa0432db155379f1266fbadb50cb7e0e083f878aed2f9b14cc17c782585087ad4d33457711e1729f6084f1887d42fd4644d6a7ec756a363654d63c8ecd94676ce0e4bd1067169fa6fab643d27d224cdef44a908f2715c3ebbe35adc470a532f021433382ccf21f2968b384f46825ef81aaa80c2d3339b5efbf6b44ccb07a596eb14670e09adfe230569e9b5a8d16b1b8357d966c8ca6dfdf7baaab977e4397553c99d4c5650c0c6f1ba113e1723ec0853073d33113d9e97cae7e9b6e11d093bf854f3f6391702bb13ed261bd4bb968eb0a9b736cfc69b186dd11f2d60d349226e25112ec0d278f5bdcebfedc39492420eb69c221d67ae5c5bc2ec8de209df94549534b745f9531d56441e1926e3709cf2e87dd61356f6a6027539e119df563d19f63a28ba7af9e9ec79e3ecda97d00bb694adaa66a9f55ec31705632f84ca7c7d07dc6075e7754df867dafb4e8b095372e3c6db295cab859bda9df10201f79f6120107b8adc98be26969ffeb21611dfd7e7794a884ba5c9fc7fa3c75734a6104cfb910fe75c748e1fb1e59569a5267d9e96f054bb3cbab20b38882825bde1df1f4f62d6f61710444c18716487014c517a058f70fefe79e0be48034f5524de6989b7d50b7b65457bde92ff58026f60c18a9cd8c3a11390f64eaca5db8f721d5e137403cd7adb664bf5833941d4e73023ec694802400d29c5603c065d92ba5176ec2eceeca28c5c4265f84007eac27c14eb80a1d4acf4bca5e6cb240b16510cec5a468e64b4f7b7cbca46b7656195eb83658d7b25f7f179e6122c3fcf88ded36270086c9e67c47caf7c66c96e84799d51f25747716da79bb600eef7539d6c8fc9bcf4ea7c8d40c7a06b40d20390144f8f8f54b65f1306b07341e2b935b88592092318267b14509eebb573b5bb439a0cff63a25feae606a0096c4761b67a0cc28dc5d77043b944e5aa279045c217a39c17f93e0b6eb2481ca89c2e5c2b9794690fe9a8e821ed412200eb7684de10e8fc44de51706fdfd8a75b326ba1b32c53e083709700719baf45b5f30a653bfb002002cf8b21310db1773bc429442bee053a8a2dcf438768002021315fe01d43c7b50f26d4ada50a2e5d066ba12e27712f076df2f15685955586f194189b8c005c5a22e0eaf5a114677c1052c12b9c9d752666d3b99418d5ed2972214a32eb4991308974b6e11085db7873389a8598a6bb5d72bd447fee13dceeaa09a8a96a51326abb4d9e9c608217e9e9b8e2c6915c2445f4df18f6b43452faa6b3d77596e34caa3cf92ae04fbfc0227c91e9c087611612e5541b93943b1d3cac13819d66aa9b0130b8a7c55b2bced8f63134cb6076c2443a918f5dbd52f16b1958410250f56b4a0639b2e7ff484ebbf8f41d76058c20f76d70a1469d3165c51da10067bcf77c485ced737cb06d4f94d8eac408d2235c2ad2cdd3f4526fb7cf44d6a0090ed7bfca0319af4b2144ba13806a764a4e1fe907bb8193a4b4e1cfb13e0c5b85013683ddc08bac6748345c19d04a1d515e11d55e39d4b6f0b79e6077f73823bd2a506e076b73a60728409d622d6b775db016fbfb6fbe56ea58e550a825323d8b552dce3e70657add147d15b1fd00159a2967242fefa37d8022d17a0112e373a3201fc1dd32cca4edd3dd9dd8eb20037d12545596dc1818bbf09b676abcc273ca9b8a08d7761a8515add6582f9c7249f8a3145a461ec83d8ea4aec5adbb7035d8e66e8e619e20b379d7ae071d4838234b7362f981f389dc18afb8299f4ccd6b4c5f308149f2fcd89e888f64d6bd44adad62f3c7a5eac3ace4a7521248976edcb06a44083741105aff0f7c2a6fa71f3dea9287372a81f17e29991e3bfde777668a19ec16648c4fb21c21853aca3c045f3ede91acf102aaabcfff8c6a8ad62c1e612f9e741c102f8ebf1c02ad66fc5af60902877f279094e312954adc70c1ef7d12b855c2333d14c68732ab73a8c93e00c94565b1e755ccf4113c88e8d47c432f6006c326d8359a3093b6ebeb9512414233e72f400a1934174c2d82b1cbb4df7cc3315e84a12b50de71b60605ba6ff780859cd2fe063eb05afcb6e383537566752976787a58db6be0eb88d1884912e51950740c1bdc948cdc01bc7da5a585398132b9dbccc96ecad097c85656481f4042b28b8cbb2013d07064bc1199c3f74968034c15a14781f8086f0df43389122ab3ece51ae1b83874d59c99802d05b34d4fd06e9ad58cbf487a4e78d23d6ed0b4427e1ff0f47c2247cb90eca4688ccc5629b10dc830dc255a72dd65c0f40a508b24deb0e5008183dc1061ca6d06408b30208cc00e0bb5978aa5ef7adb862434e8f2d5fe71aa0a57063f4333408ec128244d7c05d253c78de38249c12bec676f1ed2aa71e350178565646338fbe1e155fe52679d282a68e05be44483e463701174012fcce3af8e56e49b4697ffa7df43feb06ee718a7d6e1615169634f3b109599fd70fe49dc9d55215585d637297b086ca92faebf359e77dea47703bbecb70a607f44332795f64e5c267f33db1e44420ec4703d661692d9b6e88d0e4f4ab55d9c0631885b1f44f36a2be002ffc01c89524ce544486aa82e15ac9884a7ef6ce2e5640d3bd334eac12905426b2b771418a99d924c55859504643e828a40d69de91d013a2d62c61fd5a958ee518aba792ffe79813305fab11fe67f9cd2250efaeddd98264b3064ab3db1873b13a0d124fb6a171b504f9b624cac3c6082ade878d6632a4f07950cd4ba95366c1bd42bf96ffd2d1b56b5483c140c0187a37edb84df314d538f61d0b4433a04b174b8390f5b641b25aa159e81835f8832ab19d3e679fc4914938ab897158bc2e2b309112ce6f69a542c36bf861df3c06ebdc296a5189fc2dc2eb5748110a7e749a4622e8018020d22aeac517a42912d56471d784d669a7c4386cf52d072fe5113700806d77b59cb2089786d9439212740acc868e6f7a02fe30c8f6ebe06bf52fa5328503819945d970a0f970e4355dbd9464a31f90a689e7cb3c7c3435ab31656d667077cc1060f53a6aae3a6d2a767cd050bd367b101b0041ad2fe2bb23f76944051e6df5a0c49a329167b4728f7ac7ab2f0386e825b3381c4ac68048d0410f03c65b004cd2b8395cc66472fd3513a05913f8e3ad944c8d45fc5ab1d12fc6b94c6ee8234cb56ea083fd6e7142793418a253a032874bc0a2efbd0753e6345a4b53d95403c4d40a9ac3c034d539e661ed2ff06688789e6ac1b8e76a4a3af3b940cde3c5fcc5f41526002e6975a8123c3647b0e1475a5390bbfa7c6899056bccb9ea0695e38366003dc56a62c658b1b4d990cd95554f7b33c472aed4437dc505eda3487b4e57dc61f3d7e9dfac66715275fbb41a68f965a002f3c221442f3864d53cf10a1bf5318344b1decb3999b39e1c06a8738a439429f5d83f966573582f050754ca212366ddb19e5784ab41ce278c187a92bb2a24b2bef50dfd582f2624fa81ec1206bc7fa2cebe5f5b1b66b1a3c424dc7228d77ed211341f90d6a7932a5c5f0b1330eec3d2f1ebdc1d78e9f3f69c31f0b268bc545794a4a76b19df37ef42d987c74a19752915a78aa64fc5c016a683ae0ca14d1a4e6582ba079f862e9df0d5635283e04e7c839350b4c1c60978050ae78f13b08fdf37e155b36fc2cf5c7e5d72eaecf2c7573e56ada22f4fa491fcf05ec4f820ceb571cb817193dbc07ca75871439caf688fc846a10be9cc03620a1525b233e2282f2598dc616370626d4b537b7e9d43de831acecff895768e953f037fe2432ac3b1bfdfd55d2d476ee491ec11d6b6c7b8e9e5276b0cc7df4758636ec743d2abeeb797a8b46682a7a4c9a1d8e9393c1185e84ed1686f9e385ce4c0ed0ff7cb518917906229432f61dd48563cf3e3b94cad4487eb08b1758e3660e914b6f351479e57199c33b1bfe4b56248e89240455c245eaae2554a54de941bb958890d4af51c8986951950fcd12254d4ba78b5ee41c400327ec2eb37701c591987d91333942a980f327a70055954f220f4b826cc79c099550309816de8e3c814557d16a1a73f0f8e8139a2135f69f187f25cd5bdc971a912ed28d0af229bd207792310f7bc3a9732f70c187d0cc55ce5545ca726d53fc00a9f3aec619bfba0ec4b4852776d589cd315b43e07c13311258379308c11b975a438e38cc50a814edb18ecec3f8f33119ec7c0bcce20074ea60cc3750b05683cea2f486ee98eb26d17515525c3ed37cbb10a12411dd1566cf6eb8f3681a0e351b388dd8303ac60f8931fb3ca71f561e695113b976156e7e9dc11311c9c118734e5b92341199d10957bf496ad371c10fa947cb48fe2d6436828a5581dca2c7668a4afe42c3c1bb23fcb258ae3ec47ebb44ef07a73a6f4f24cb9289648162ea6b51bd85329006461e921428c310cde6125d914906222753416601d453c686078cc6a5237b78736811659927c43a2b245e5374f0a0788eb5d63541644471a390e4e62b27812b3e2b84f2f05b049b358b0546aa1c30d68854227a2343ee3293d8deaa9019047d9f5d50b172545d6e8000cbc29a6d1cde162c1f4b91429e394314a4a3f54fd87744ee82304bcdd1d55e0310c830602c74fc92e094a8b27fdea2b286b8b09ddd78a89012d66c01c6a84545727629ebd5cd96c950c8548ed4fa73981c7039064185e4f28f72fb60e115b93835df6938d8040194dc447dd971a76f5d1a85ccbfb05b2d60a38a8fb47f22d95821d1b53d82c79402b872db5984a55352a181ab3a5e38e45e8091fb015db034184637304b5eac0b1e9c0386be3cad265ca6c67d25209915c6bb0dddc0893cdddad9027c4178527ba705b8abe9a95a4138a84a7487e95651db272e7b8dc344966e58530362580f8061088174a6ebafb67646227f9a89ad0f82ab479fd8afcf063536e726451460561b635465adc3835a05cf612ea1e97e9f91fbf8be34d6b264de119ea7d1dafa4b3a76062c1643e22ec7ea17bb374a24a893d0018b73c1a0d42aa9721b42912532ec8fca885b59014935543f0149716dc72f86014e2bb74badceaa228d5537bf72b538a27e52bed4351a8a06ea638797869c25b065924344010b811eec5cd9f60455e81943189e473542b34d9cb2ffb05c5097dc2cca887be639ed42a2f8634f496d784d2020eca218bf249298b9a82f3e69355ca54a44b0ad3f4cae5eab74e4900ff7c8261c38be5d2d3e7a44b79512daf3acd599e7779522c0087711be71959aaa4ef741648cf059c60800bdb9b96001bca50c8d385a9b02cd46485c3958f5bc850d3491d61927b524424114be8660ee055559ee32c4a6e488c37272409b062c52059cfec17f6356ecdc22eed08112cd85772c0e469c1683b673d84eff8c8a82db8c3faec2e5c6b62726044c6a8deb90dae917dde4d0b93311a13ee12bd904f179c601b49e826b0070dde35039fbfce2d4976b4018a876641e1f348c50ced53db4216c4895dbc05ffbbb54cc74222b11514a533676ed04b2b50315eb5fb75c139c84e5301a0aa9f21666ced5b425a0008fbfe21a3b60957fb1779e379e8d00ee2a85a8dbc45422ab71def9b92a992bc638932ee17a85f0f425f3ffdebeb9fd607501f3d35233f9e40f1c6fea8c56491babf5a2a0f8c08d2f54a98eaeff37dd8fc574a9a60212bb74ee8c4650a9e8b7e14d2040becdddf171b23c5ba7189aebb14ea0d7bdc5496e316b2c74a9dbc235d0706bd92a2e0ea0bdf48c5e33ea053a9e283e4c67f94722cc757b392ae2123124f03c88a52a8dab26b38cbac4347e1f70a5ff08e892f397159e57cbdd15d86cdb918dae75630d32c6724bf9c2af8563dae3b8325dd138534984194867173e7d9e8b906ac9e97667cefea387ae4a997dda43f1afb857c220128fdd3f12bbaaeb7fe0d1bad7ceb4b532f1456ccd5d7af3b8d66a74706cfebe986c183f3ee77fc7e3730f487a51cd8770d4ce115ef8ab2156baa07912d3ea1822d6ba0c5be7777bbff161ec792de29f2940e10ca0ca162708978450a12f97c1c5f5a631841d3e05991e4dedb4eec91a8aa641ced9f0d01ca8c4f991b117541c19fb4e8c141d9c30f37c0378f03f10a26adb849eeb21a16e2c6576954490c41ab7a6363461e7396a5439d208e1b099c1f9850383a402f010eb8a8fa566df863af97f8301e8359466eafb6df57c36a0e3ff90f9b214b6d4ff2879eda8058e7b6b3ed7c88c636c7e588b0a72948fe10a84f23923d37f1c172e21f138f3d23e34391fe85bf90bd22742bcb86a7d20767d161acc8db632593d707745b7e38be07e745ddcd3ebcc1563b587f208930f5bfa0e641ca8939052eb71e6fd74be343818e68a311b680a058c6bc4af003fe3a21d5082b94a5b6bf4edcd6eef6de7ca35cdbc4bd7f88da7bd1efcac9c223e6ebaf08d17d883cff270c85c95b6060c6e66cc99f99e2f92ae58f5a9e798a8e6f3eeafb0c6a06f9f86c7c009d53c6585e1e8e9de8edcd4efd4128a7f046077e353afe90489ff612808368f4e0200f6334b8f38309d2a4df7541515a9d65e727e8acbfb85b02942e15c2578bfbccf676820298cb6901b4b7e03b8322fc15ab49a73f19d7361d84ae9a9947637aea0dd511ad61c1040d2297b8b0a9ea71c7803adecd828267e2ea43451b878d47578444b3faf416521de3205af9788d1bf64bc580e0b9c16c165608bc544e9b16e6a13225b4d8ea58f2425772497ccb8f8a8871de6d405fd746fc0b15af0dd4bf8042b8b07bc8c5e1a656456bd1646676fe0c560a6e067c5413a9459a88919765fd2dd0f58b50a48e958381130d881243bb127fceabea4fa5a112aa0fc5ed6bbfd638da931efb6a48e439191f010489cd32b0b31e6b4553b5a41471a85a90ed1355afa5b41c2dbd525bf1ff945dbfc3b205467a2b602fb65ebf4c09d254b2eb2ea694e2f3b5b239550db193ea969d62659e1a015f258e648189217544a0af6422b02f0e5db2a1af4aaef5c0287f9cca3ae03ddb46d3f402c52f36a4ad3675affcfa6bfff69b7689f181aca4acbb683e19facc9fee7ceb50e32536e9db29495a41c93e668ad33101dbf7cca098506f49db7c2e4f928212cef2ac534c7bf489166ba002ba738997489e770c76dc963667b8e5524e33039abb17a6bb6980a6948faa82fd97bf5cb25cd1004f3525ccefd5cb046f2396e3557c24e83e7e1c664042d3762bbfc3fb39778568fc67363696be9c8a01b580b35cb6be6b46ab4ac055f09de5ceaad881403b310c8d3d7104356d3cb109e1e141c1f75de703225d15801fa85784251b785996ca76f9884c5d5602ecb3568499fa1ce9b54dc7ec49f8482d41755fc20d89175a1b06d362265af4b2c3d07ea82c5657c6da0b660b37a66ebe6536dcd66297cc53f8d3fa6bff62fdadcbb21be3c3e63654eaa9a0e2b8977b80bdbcfe23cdf57ab952d2cb61635e5e445ebe31c9efbd1b6ae534ce7ed0e5043b13d9b6d1a09e8e81080624ba036aa12e8657205816a703f9819b4295991c26757055b82fe96e34a8125773129a97aae8feecfc88f8292ae25cdefbe214013b2845fc8cebfcfdeea32ed63c334d3907afbb01306e789d8d7d99b24cbe15ff2bbfeddd6d6fb9a54c29c922086e0865082017ca6ae58c133178d272c44b5dedfbda311848f6b6b9e8125d97df43cf35227288f41bf8da1645475da04ffddbd8e28c81c7d54298955b468e2e548531dcee55c49121da6ac86cf9a967385538ddf13744fac97bfe86742f3f9def71ba0325b889def3a61722f15e14f6d7bd28947e9aad71b26eab3e1d3827f4f20bc2395f28fa49863cd024647660284412ea54daea36385220eaba3d65a06f9bb5a512eb8e0c2473d49caee99205c9648e92393eb329cf992e3f7533500ef3d93fc99d2e3a1dd4665ab7ac0134c61d1b48ca88707bf410c5ff52a97dac3505e99c6dcbc2ca58266d247f75570d1b48025d1cdf8080569427e59658e0b4180d9b3c67cf6666e7d9546b588b81c3375ad82fa6f1d7f193180c44a5c59101004c654db7f5b7b8591c1e1ef73baa9b099259fd97959033a328ac0fec77646f2628b9dc023e71109c059e71e743105272cf6d5828ec868db01899359b713f577ef2d04f959f9c59fc04abafc819ff8ebb7f60c7ee59875325671c0b3b36ecfaab9fbfe3286baab46f335a0c3ef9cb8eb25a8d9e4dae61ad86ac51000e173e15a6f1279bb66d75ab9ad69a0264cd08a4e8353e388a59de023ef97b9be13280f5ccdc09ee197425e48cff085c49cfef1cff10d8391d14f65ce7409390d073cf21098544fce47f822acb09f17777fbc08259cc624d5517c0bf87a0ea1a1ffd4f9a7d66c82c1f66b399aac539552db6142ca9e73d3df349a93837666656a3a767f5faeb703269d1574eae4f958e97f5c6d2413d9be9c857b779dce3038f0f9d5729c4853aaf65daa9e3083c422b5ba7aaaa565df3829c4e2693d99a52266b56e17d1e15b57893999987300a08d6b13f1208f6c9019fbc99d5203fe1f2fdf82e87cc52c352a7325953256d4ed77fae1ce5393e5bf7f975706135cdfd4315d871aaa6ca513d5389c4b0e3545def3b551368aaa66aaaa6aa67ce6da31bb349811030856f0b4b58774ee6fa6fe0f51bd747d77face17a3f7b8ab9953842278f23ece839d5bac123f449b0e9d0e8f4ecc63147b58f4f0c34f5079a5af4dda110d193422426ed4dfd2ea24762d24c1d9a82c43ce987c0c03c2914220a212ea19531df27c91975ab5b2d92625e5efbef9afadaf3e81aefb570f469d15bda57d1ad1730941ee2f8c94d3a2f6fd2417b17d29b86700ee9d954811717d2cbfc8d1f42fad18f42215ec2286a68caa17ef8fda4d01444e66fc8fc0dd390d29bbef4a6973931a149fb984f920313923ee685903e26441285f7484cdb9be8c39042d37c93f6a497425e3ee6634224a62d3c22c4e5edeb2073621ee687c43c4c28441442460ff342d810498fd7109851a803f3129a667844079963d2483fc4e54921920f63432c29dc2149ce2881483ff6c851bf7f7c1c2f735ec2284c397491ae2b187a1238fa17b0e56340978701edf367d291791d648ecc0fd1f1a5979f497b2dd4b9f144748451f49b1e3469323a748451d452e961625e8852e803cccbbc8ea7610c1da9949f489fe36352314ffa0ac07c8ed0a443223de7bcbc3ce7bc3c4ecb8032c7049ab4cff149726e80262d34e9c8bce97384486ebc298c22f4329f244706bc11de78ebf242dc08b9c7becc9b9e86314ca1f493cce871b8fce82b601f47f89cd3d2f29cd3f2387d03e49c12c8399a4f8b3e5068ab35764a04b64fa7564f7c7c644099d34f80a200d150ca74b956ab2d86543d9897ce051c81cfa305b4626f5d3d11d82993cbf694b15351ba46037275cd4be698f875790a50bf1cc6b039e9cb6f6e73e3d015767e3868e12849a861b87f90a7d9da7713460b6b9bfbed473b3f7485cd71a917b344429d16a77dd5f4cf470d400a3712a5e327be3182056d237cc32d1d1fdc299bc0ac27ecc3eaf7a4117e59f97cc8a7d35ba485f245ca481716d3cc9f22c847c822a4b803d37012f2347f9271e78f3d1ee0ce25eeec959c9993888ef7ccea03e4e0ceaa317d7af9f3a1b3d385d2756ee897433f9d6c61097bea17bdefee169658ed76ef3d0c98384211d6c8d9e9de7b9c762f7485e52b67a3be97ec83c7117647e97f5c61fb8e7f04071df7fb0d748267cbf9befff3c01e72fee6be7121ffe6fcdb3770bbf2f3b0449234fd149469112cc2f38aebea4427794c665b62666ecf9ba51668a9856935d6261565a0a1659a69bb49e9943c7b761bc8540bafe4824ffe2bbed144e7825132f0c99fae98891517ec6ae6e94dea69301918f5834f387ef2e7e71f16c73217923022d2a6df9f0212c55b704e7cbfc3944bc4515f58e78f8460bf173dbb80ac0bc552fae431d72808b67e5faddf8878bbd435dbc4a1022bafbf1372060792c69f03d9899c59b1941d587eb1ab45b0087694414138ea4706f19c6c92a1243a57ddfec0b130af3b719557349860a7044836ed47190433b9baf910c1ecc82eaf0bbb4edc6a8f90a6813a57ce0401ca1901c10601393b87a378b99968326bd7684f1f8a7f09641037461c91156ce415ec46a97ad4b585dd4b0bb6c4795de53927c79e0ca2f785447d4690489035da1741daf0bfdc201cf585bcf213dfd090a3ac3e1b561ca579c5e148a3c8c031af021c05ff6eca633013a4e8cf51f846879de0c5758e12057649200e9233fe0b0082c32e094479355d937aa6eb32744d498afe23e82251a23cf977d334a78b5f1cb4c353a4b08cb3308ccbb8feb39999081fcfeb28a5f46f13e19b10c81f8c92fce272fd475748189d1f84a36cc84d644b18fa7f3fd7bfaf8bdfe56217bbd8c5ae3943eda3075a3061c5cc54f479feb651f6fd4bd226f4fe37a4cdf6fe38a44d871a8938d8e1f99c96fa2210880d087be97b20101d18430f3f75600c9a46e4b26844e48d9bdaf61cd76d60e8a927fe10c029009e486f0b08d3b6c5dace748a42e16f5b0edb739b6df916b0879f5a207ecd012116fc8af9916bc2d5d1adbe725fad56950833f4959cf1245656fae48f659b59ec0e55729d2e99939d15faaa45f00b3b83e6939e554c2833ca9472a9346ba5ea993b9fa584e5ba159d1d3b76ece0e131c82f06bfcbb5a5a48445c2bebcab40def0c034cea335e5206f8b47e70bd8c20bec894b811debeb4ea9b0ae196b8a47977074165c7f1b8ead44e32ee4c95f148ec2d191f08523b331ba8baf3c9fc1556a25c562b158ab98d02530c0207a278fc11a8bd5181bb24530df43dc2b4080c2ef7243d85a46d78c9a6ac7f5ea4a0339e33f5facd335db0e5d511fcaa230a02ea067d02b940daaa24be40c738b4271d8dce9e9a427939e4b945cd8b65381da448df18d16ce17d3f8aa36c1286f62f60c8b15292f17eb89931d9e949c093a03c69b985de79bc9869c50b88217d765333bd2d89c6958b4202d0b4c8bf1c8ae6b53d0903447980212aebf26a5fb32b42854a05c9fd2555c2368506c21699e7869ab96e6735de3b9ae29b9fed40945ec481b2b8d6a257d2a83ca681579d343481a9736fc4e1c713d4b2fc1727dd450705f9e3f1fa2679791fd3c1a8b89c0c6f202f6cc0ae87ac26a273c2939e33d5de32a12f4895f66c1b2934509ffe84abc65ee0d83bdfa497743e928a95e754ff3901e86926667812618e562f009039589baf2267cc531ee420c867115f6829df00a8fa031a9318e8ba9982891418679475f854db0a34f1e7fcf022ba36bb66f959f8266cfa56ec50b7b498b211556b6d84b666490e1f341bf82a14bc123950b6d95ebc2b179683e1f737acc614e65fa18de041a8e9cc5570e7398c31cf672a2b19ea8a2ac9c256de83777c48b515e78043bb24f87a3c68489b4610e7d481cb7558d4ea64e288b52262d1ee0009f8fc91d61433ffa6ade2e015e48861df9d53b3cf3b7679f57cd91fa24e02c65748ac935808d4dad5cb7558e7d6e6cb0e1f3d1cf60e83678849f04dba55ac8307eb53845cef85356829ca1f1974a47da009133fea1efbbafe1aef13e0583c1d8a04f5a6451271e48574c542d32fbb89c30100d704f550d527ca3853a4c235f2e2951666c763161976f6695e9c574c2f5b7a3d077b2fbae3f1fdef77cbde62be8e52beaba14e8d21e47558daa6e89f278c979781a7e871c666147a157ddc27aa5c4dd2d5fc7fda4c6449e0ffbb0cf086c292dbaa2c819568b4e562219aba6344aa74ffc332c32cd8a56c67509a3726da0311a1b698c871dd887ad982f15d3b0158caa66f049759965be1f5f35c363aa9894ec302fac1599a73ca3adaf1665420f43c684529ea6640f71abb9ea15afa68cb957afd7063a00368103e0ed517d98c6a7aebc89ce039eccf181d1993741653e7c43af5032a81a3b88d1181176ac31719e214ffe57aecbe810b329a3311aa3311ae338cef27a7158633e6ef8814303aa1a5418d000a64ce88aaae6773978dc11b6bf7b80c7da75bec2af51aa0deb5fd018743fa9938f862b592e8d491b1ac81b5781f32b083fe0178d4925211b96569127ff3358577c281badeb7fe3a331baa24ca84aced02554e52df77196af441d27ac5ee97c10f87c841e02524640488e1ca51b5336653077984781bbc9f5ff42dcf6a4d23a6b5795c7311a9bb18eb1b792334e5a6445e91a1f2c77a4b11834a0eb515cca48633cd2980d3406d3112224021190c01026d2a9dee12cda0426c051e056c023810e9b389ccae7c3615b583956f61013581ad53c74e77a96399b5168e3fa0d6341d2f8944dd99465e954ef4cd994f5aa9d28d19e684e3426da121aa3313a2de09140a7d2a73e8d5ec6f5d74020b64b4326c8197f0f43d290402718748247cc810981d880b0b75f03df8f8fa76400039f8fee31f0ddb0018ffd4a8df5180e8b8df575bdc586e52b55aa8c5f2e98273dcfd7ab6b48307436d3d9dc5635caa0e4b6aad10d6c60871d38e03d619a1a30aa82c1277fd235382f83f5761ff018e4eb32391306182f47f912a220850f460002860e63c18e1bcff5a70cf3ce2fef48205865ac52ae7fcdb8ed5c4f8244d529d2865bd5e77edafbe58f866167bd4348328fd33e4c41609ef442609e04c3313f6d57c381a53b20cf40e9a798977921312f43fa98f015f348603e26e4d776dd95c819ff900d4b5fe6c0901e66474ad79048a153d0838298af098b79524cccf277463ce92812cc8f3a607e748143be9937240260ba2ff423bfbecfebfb422b612a1cc4b3dee1d8e78542a1d00dc58032679e2167a0c034565a9cf5c412d4a27720df18b0731a7422e649608967cb89d1d1d35f82b5e854a695299b58ee94855d8c5336d615533932cb9036f35b546034467ec2f577987b8cbbce578b77d553e627ff6db3b6549a29e01ac92214182599f674cd9436678c1a112b285122ac15661ad7a76cca2efdd219b6ca3629d3989cf1a755349e23ddad29e9896d9bb53446631cce57d7cc0f78fd810f7c3ebaff0008b63bd61583b5ae384bd7b077936a4cfab6d2a8988ff99898d077b2602983ca94981f3b8acc8f0de5c7aea2144a5f72653cc6619ef4386dfa422a2c87ac228135e6ef1cbf58d5ea9ab1c6ba46dac4aa8c8eded47aae2fb9ae299133313b8a77a43123357684e51fc7cf47f70cf62df2dd50c4ab31695345a2b88b578d711581c7af66d2356c480e43b6206da412d71b09cdab73951d6458fa9d5281b45102f341ec63054b96eb3b17e554a6a07ca5a451358a85c25ec51226d743273cb4f892332e73c6ba3a5dfe422a2c83da75f7771813541aa5b1ac8cdaeafa87bae60b8d05b70b79a3a940d2bca4cdca511e863cf97b95cdcfb0ee57aeaf1c267ac1fac52f7ef14b5ba2f5809a1239c32f7eb1e6d335a394dd2a5efe74898c56719d3eb94e9d30b91e8edb168ed6964ae1f8e27c98077bf849bcb1f58cbaaf28a3933ca476e58b573edf78299924470ea1bf0d39f1c8b2a7743e4ee3a07dd3d9abee884d5dfed20a6c897e0ed96db914a56346b340234643166854817aa92ca44003caf4b59ef4e7d26f4f874f87d0735fa2ff8e12620b815be8eb735ad564619c115bc28b007a72cf7a8d6ec198c67f0d46f50b5a72bb7e5ec0285e83595bc88d615a14e1ba37aed4a28858aa0a3da0d73369d3fd836eadd1335e836f7a0bd93ef056b7357dfe7068118ce7dc601c8644f54f0bd6b29dc8db31c5f2fd5ad2a1fcc896b562abd5f37d3bb2f48c3e9d1d20477d2f7afe0ffcbee58788de8abee5452fc4e8bf9739dd8e2c9665bfc586a6feef47a3377528e4fb914988e85d5ef42eef1f1250f67d53421381f502dbf2f6e76b2d282dba11972ca4729065f4489b225ad883f66ec4ebb612d2a6732cb7815aa444b032685821a2d05e88ef5de67743cb14e47b9717f2bd4b28fa51c8834c14dbe2359d2eda5e04f6cc82b3949cf1300d5b05599386b4b902ab5544af5a37c802a87bbaa7c56ab15a2c16abd25ab5aa51510bfd91f7e295a4b063b77ab688b69e9033cc05df74e7a0597d65d69f8f1fb88dbba3063c76ec96dc9e55351cdefc6ef88122081895180cc6400ea4c308931968c643b8fe332698cf476b74bb188fc1d1ed969cf1d98807505c219bceeb7ab7ba55c33d55cd62188bc1e2a1e26ddbea562577b3b85b0d04ee1e69b3d5c7e91fee14ec15109ab48089eb9d7cb990009231c38e3d6b713ff6ec093264d81b6652ecd82d192b36f41df5b6ba79ceef1f04fa869e65ac60719ac17ab7cab354dd52a92d354b5134ecd8d3f3b41edbefd0c28e3debe16184e597a965a5164b5857de915d2ef04fcb823bb0520b1400ddb17be413aa80a15185185c91c62c0cfd58ed8edde380f9a67e4a04cb2e25ac2bfc1ce0e0c777013812a8d25ab5aad11d3cbc17c460144bc127e7b055cd7a0106183e1f4e7c36f5fdbd9bda0146f5ef868f8bf09c3de72401bd4070923b9fc386d2270ffdd8be5ad8add08f1d9f79ce2a8a9e288aa11fcbac198fc18f492ff9e9663e853fa1fd58d3ed5b9aa2ea9e16bbc759b552cd6d9b556301847e66251dd81e703c27ed66f0669861003bccf218740bc851a29fc29ff0264e5d57a4fdd0431f6286c4aa67c794596805c85d9a6f6bd1bb87fc6c8b53c219084083c720b37eece4ae8659dda2fe85507ce11276ec1fc80e82acf1810c2e1612d53d7e6af10dab68705d1ae1fa11d28b9022b73c099574150d96b8feceac59f7a8ba877e17a56f6e5bd54416acc106cf0862b00bd6b317fc4967712cdee34e3c359b990bc04a7413492345b7a07b4a4cd8b17b1a4b8fae449485f60fcf6aba35a78d0d37ddd35724d713fab17ec7eeb99f0fde69d19f49cfd89028265d17bd121689eb3e32024b9b204775cb51cd6275688a95e1c28eacba11b116728a79b6b023a7e61dbbe7867eec773f1aae4c19836312d5e5b05946a4ecb64f11be100a1b2eb1b31f79f2174217b36ecdba35eb59cf7a26fa3c26020b3ac943898f3bc2f695339bc82bde829f90db4adab470b7d9b6711777fb99e26e1c645bdc9e4118d16b71631e39b33dc8861d79b56d630d77abd94a77fbcd13a174cdc8473090a3a436f211d2a6eff63ad28657726607470591286e224fdb6fa328fe7669a8847b98270b368a104a88f9de6f510899efbd10f4bfefb1bdd8b1778fd4d46db0de6e52272d6e4fc1f9dd90e476340c7b2ba8a5aefcb8c7e96a5ff44346159ca9db51cf3f5310d1b7bc10d1b788420f85881ec9e845db57b053d7c74fdb083485defbd07f4f2916ebdf813ca5c50dc84f9beb075310fadf5760bef740d8a62267b66ddbde51dfaac4f2d3c6ab16472e0bc4767ff8812ffdf938ed59c9630b8b74ac4ba2abf527581fecbb759ad77d921c1387eefe48bad1e6749bc71d6119a4b7d3bc0ddcee9cb7e3a8e6b579e336916cb1d6e77a61a0b0f46768da3c3485deb4bdbff726eefd33857ed382ccef7e0385ccef42d3f6dd8f9e0385f87ba12948f7a3376da1902eecbcff5ef4a370fb91fd9d963762cbe9de9a3621ecbf84435c46ef12f6f0537d99d37d3ff0252267ea87be203307c74ff5394e081cc562fb6b0f03dcfadf016e7d2f01b77e07ce204ff5bb8fd19033f537905e6945ced4dfea16f2acc7ad0f7e613bc525994db36cb381b828a52efaaa556693728a5344e80c01c2375ae801a619c2a81e7a60141bc127244b64194b649650a74d168d603c70fe188e3c2ecc8f31301004b8b29329996acd41b59eb5ced704a3ebac569133dd61bd22692a19ddcdfa457d7d95bd9a70f7578b3ea5c50a83e88458adfca38913153ed58c1a0b408d5533d86755cd60546da2731f39d344cef8aa09f729a4374ffdd1573d66a852a4c8eaac56a932bea957244d25430d2e083003d50463be6898af09868a7d26188c622b9a99d90ae62a92b9260a3ca078e01b2ddc767adbe19bfe5965dbb92f03f0d03656f8f020a170fdbb1b7e3118b4c6180c465533eacb06ea041a03e3c5d5a736a9aceb7c53a7a837a0c20b16cdae691e3ab52f477ea1c346702af472f118a42f35764375428dd526eaca058f5f3870119caa818a01f38bf9622bd8278619fa01105080d73140cbc1ab2b087835562adda82bd3fc15f00df73b3ca61ce8f37328ef568382aecc342cece8ab1b8fe3475f1d69aab3b3c353430e0ce9635ee6774a6fc4d672e5e72af015308defdcb1797aa7ee58122467585602f92567f097e12401466e8294338c3c46e9d3a173605ee60b327360428ee1c0696cbea84aa632d5ef4c5e0eda646575683ab0cf310e9236f20acb292642cef8f3b4c3fce46d04c16032495f3eda89e89dee22a88d2b4197675c85c388d9b1c48ef5a519616b65b1c871b26d8992a674a76738b1a88650ad27a7d888909f18c128ae019f425a830a86a4f1e7d3c8fb42cc4f5aa34104fde08cb0e394c9ac2cc112648481b094167de4d55abf90370a7d558a0bdef49955cc2753c9ec993c2e782eb80045cef877313b5b09247a882a9a54859a8a6735a4c08ebe72e2acda64861cca7c144a94ebaf49b9fe74caf59f54ae3f3fe153fc78f3256dba90289ec267be7cec8ab26c8b673eee4dc63aab41be92994f9f41981f6f9002cd8af9005680966fced9f2cd1781324703e54d329bdc80697ef46a0d3b726c7a9f17e220bb90354d24123128354dd3b417cfda29f31fc9b1561704cbdd915bab21f00d18d5293fadf8462c2dc17a5184920fec0e3855468b31366c6ae58a327250e81322fa165260b9cf851f3b3207f14de8fd17206d385104248ab330031802b286539eead405e2a798eb0bb30089b95cb1de36b07b408514bd7bb87fd08276c92460b1ead70e4f6a95e2d20784526d631ad157fac28c574cd3346db52af57da1a93c69979306ea29edd350ba74c36b1c8dc3d3869033fefefd4835c159f886959044a4ba2e08b6cec08e9d1a6bf87e882834f177434c1ceae488582b53bfe84dfcdef726ee5ec8f748a2e0f09174ff0185dcead5f51a1a8003878ef21c85833cf9ab80ac56da6ab5ea55af7ab581dceda4b6da9ac80a3aefade94f10887ae5bb12dfaa26411a825b928d598cae6bee2b5be48ceceeb691524ae914083ca4db946529a73085124be1eeeed259d0f168517a73523a5dca3927d542295896c297668b7336a1352de81514d4461b1bc7416183823627f51707390703776f7ab3062efca6014cf3af1467111dd53b6c481aef56725bbc020705695b6d1804050505f115244a6706a5256c12b7bb881a9d3a8ac2fae63d68b70abe9fa2605f73e3e88b9d080a92335e586e5f6d35c00112ae48238b531a965e99451a4069b45cceec20290976f4eef5cbec2c6ab123971718528c4ce9060e530e1d4e9d3a75da820b0f863b78bc000018c453000410c38c0c343d661800010840001a0a50538029e058a0aee1ff12cbd2a88ae5a17d3eaae631ee75faf4a6f7c3b4ed98eba5522db164395e776e9bd329f55992a5f652e9fea88a9d2df29c1e02b960f657540186cb3ba70004ab8424765cb67405c8c25cb1c11ded43a1c42500645141b1f5ca2cae2821801558d29559ccb2fcc006f998cd9e30843bafcc62b6e2e1413453b18d84f4765c39df0339e5a720fc14b33cc5101205c44fd2467bd92c2e1cbb899c2133909f805c40409bc370470682e18ed2757b8a3b843c39c75c3e72c683662d863c48693ea8da7cbe99a10d4c23830d4b48a147e6d3cd559b3229ccf09946c65260d40fed7b34ad292067e6ff8fc7a1b3bfc5b0a39c89dc125b2fdc125b2d51e41b8e314d174186c859bca6c05e8e6225e4c9bfc9f5af1a9dda8e97956bedda2c999bf358c6329ea99c30162bcdd34c38884e4da353abb5d6aac1180ce697f6fe42b6d0c21f39e392335d7bda5639308084f00d6f2199e594e6275866999fa858af07d6c42f734cdc3304ebfe1c964a1206b20556dc1948eee11bae82892ae48c5bd2466b438a5790a2f3c05d84ecc1c24d8265adbac00e445af487f5c81997c9aa4c26639946a7cffb5081b35ccc1823fdedfa04d1f0c0eda79e3592e3f617a1e198e3f673620c321cfb678edb438e5b84fb51738c392e573b346cff37c71c957eec815b84fe46bb508f8ee98eb6695be5667353db26a5734ef6baca6bfc748e5230bff3b6e91d0d694d84bc9fa0474790e4b24ff6c071bb73765c881d8aba6d200e9cba53dbd8351f5b05c769396e2b21ca7130fd83eb01e616e1e877dd2247e9e4be4e2e1c616e11e66ab8bd12db3f964a258fe3429a86c4a6bd168e20c96d0e9caef1164a19c3fc19a2da6fcf6d1bf5aa51285da8fbdf363727d7bdfde0c3f5bed2ed470edf9b55b0fdf6e78fde9bda08641bb7e547ee47a1f6ddc0859e0da9e8bb341cf9ebe7c3d6c295e511f9beef3322de5ac11ec4aebdee46b1866a35f1f620de16517d7912fdf72ddf712e2010f5ba70b3d62de4e2f238ce85b4d9e32a47f2c7df1ada14d87e17d3a86954f3b4f9385dc11be4cb4b696302222fcc0f09b8f56b38822417e6c7fa9b3743a12d7c6f9cb743a18f646248a4972191b649c281e36ffca8fd86e36571bcf613c717d1b690391ce1d6a27ded06285bb45f02678bf66540af45fb31a06dd13e098469d13e0c7843c660ff05c42167ecbb8039a468df04be3cd93b0a4b72c6fe078ee2b5e13712b945be1789be2e044405e5d4b45a1fc7ab374bfd31999365287466cdb7ca3c9d8990962307ad959b93a3b4bb2bad5b87638e4b7bc871399ee198e3ceafcf6d2d8eea9e8e39c2ea8190b675b4f98bd4ef2ced7e4ab939394a9fd66deba17427b7fde55c58027d4f9b97f26079b845ea87359cda15d80bb9c06e4f9dfbfe0a8a5db3859b87251c7fed37edfdb5d7e86b4184ec7f2ccb6d93deee4e6bad557bd7b4f75a04a2c2890bc356e3d1a8d0f7e9c51e4fe4008a99b1c68a7ed7021a36d40ae3fa8f1d3709a280ca7db9520b1410e176ecd51848d44b1bd2a54eff8926977ee743c8438bf4c31e5827240ac74fb46b76685e42d67440320c24cad61f590797527f7b433c7440d6701252a4cf3cb4fbcd93903552d2e71ea72cc5fab47eedf1a894751e929b03aaecd60f8542a15028140a8542a1901129bb45ea6f1f0ad5e7ed4bb11a7a9ee7799ef71d0e1ad0b045425fc48894dd51f4f53d6feb72e87efb31f4a328d4ea9cd5d6fa3538aabf3e10593726ecb6ddf0fd180a4729bba1914d41ec8f7e88fd91e887b4bc281c43ef3d8e87a311ed3d2058bedbcb9003badf3c50fb7e6cf543a1ef2ae87de759db6243ef51d0dfe285a31129bb5d38562e046ecff9d85aae7492c7e851b8270301511a82bdeaaeba542cb93917171c857211046b11fa11f56496140c73314dbfc0056b6182143016b77beaca0b1825d987909d0617c172cf546a5404172957a5b56a55a3dcd4298bc1cac10c542e18524f868911330315b74bd9e1e8c92ad30c54936a52a47a443c29508af5973255b03258ac8c1a56260d2bb386959fcb75f1662335048f8c10ecd8ad1d50b07c7e72c0aa3c907881b504005708828b284be041446bc62d2fe9a0c2cadbf24f856d9d524604c1375aad35a238a8540dd65639eb140d0800000008a3140000200c088703e2e18040a20782a83b14800b7c8c407462381608932887619451c6186310218400010118819991ea00be231d829503017ee523f858000fe934e6bd14408e7c2f48bddc293628b7668cfbd82a34b80ff58d0de48ee977fe6222a58955f1a59754c38eebc5b7c60c4662a7a14ad62fdf93d6308ca6c658645293f2e1525337a4beddceaa9adb35a60284b6dc3cd149aea25d23877de2a0634845399c9e97f975dc4b0745e765e4350209449ec12970fff12ca61edf472a8dec65c563683a5663114f67f615ea3374324360f9b59f8b8f8e925bc3c78bb47c48c70514cb1321624a297052b30ab69e8bf9920771daf3e3d61741ddc2d6721ec3530dd113343ddcdc17c4418420bbc44d145f2d1019cdca430c21da2ec409670f01d9bc8f5e1862ff62a1efcde85604ccf36a60e41eefb1c51bd54c743670b1d093a14bab127131ae1be6bfe2fbe49c2faeb40d62dc18686b29292347407b046573618310d36dbf6379cc9a889a3e08101d814272c68e62035168fbea7086647677a35f461cc0120f0aaa2516b38a08a94216a1af50e83a53f990341392ac1eeb63824c8f11cb85e3a110dd3b03e72638ff878994f9a18c49c83dbd9980321eb172f4b02603f81b42c38679fd8607d6870fe780c11b9d4f18fc5b767d9abc51d2150cb39f0e3c8242a02517254c14a400a37b0614c03a2fb04499aff770a17c9e0235fc2a6df0f0525d6e2803c56774c8da1946e0a8bfe47899b04b441d37e92841480725543b64421e69531b6530a15ff2762652e0cb33294d41af9495978a48187c25bdc248fb47135de44f3435d4c227b7496bfb3b49f8b91b530f7f94fa60080fa9f42be88c1634fda7345939f0c5e0d08937a205b297654355e34881e49438c8c72b0fed9ab6c76bbb98742a8f925367419bd2a3db73d43cf59b8e80988191b498d19aa73082fbe54b07f2f9e0e32917a77b9631facc2bb9a24b39108ec8df7f477e994587acb973b682fa487aecc635b9b891811f41140f41867221a81c41df8aa24bb1c86e2ba8055bfdcc9293845829ffba1d3fecc63b2256d91b01e1abb6f6483807e418ec476c25f1b2571906044e2e19d8f54dba583a52724682627cb320d59133c676da2aa6b3a56eb81983768bdcb47e34a4f07f9fc972a2cf1906dd924583587851246d84704f0e746604fb1ad19ea2f9db8a4bf2dd6689d9b0ad1320be5b037fe0e4bbc09d7a659b83cdf2c05c770c60ffe2f8509e77cdef3e6564aad574f657199f75f8f4d06bb80f0f7d322f778591aff05e6fa7cae2503f6b22a3a0e979cd644c86443a9e245a7946d7f13731c82a53c04771922436f8a7fb7e591329f7d456afe2dc3d73b92412e8c614fbde13a0a3ad0a82a6b2733b21132876a7effe14027633bb54b183c822787f1eff91a9628b3c1faf5ab44a14c909145a0ed886ea90d8e2d7e89577a32bd6c2b3dcd543749d839a898fc15cd9b29f1c933a0a48f5b354977e01ff0d5d021e76991e152b354721490af95754fba1039494ce715b34f82e2bf47ca3fafbf11f202f481d7dd457547616e1d373975cdc5a018ba09234bc86dc91d1da138f0752cdc0e5bf83d1397266496b774657556d41b2362e86ead366b7783911866bb7c7f441133891c063be15ca86d977bf421fdb963426a2d7bc8179ce6a77eaa924c3ccef1ca2dce60a6d93915a0ed72ef1efddd737d6e1ff3527a644940c341db1af6fa9ab93676ec97a9f2038332ab555336888815155d9dba9a1b8e03f3dc4e68fa81003aea8afb2fdb9608e48f8454736078d0debbc0be16a9e7101c7e6559e1e2f6b9793794426c564a0ac2612d2842ab453974678475b2391e0b8d9aaa26e1cd46f96de73e3a94bc92ae5d0f17d7e18528db4d444f2837f33d93a3f75e2e7796ed4b8031ee25d3c8f84bdc11a8298c821630fec94e76e46821cb1f975c3351414a4f9c48379ae87d2088ebe8b9116f9bc358cf770e9d8b61a4248a16d2909acad65c2b08a6c0dc60d53d09c59e838c4f0f1c716aab44e1dfd114f2294f033cb843edc6006664473d262991c1be3f261817066fab854d64570e23cdb817da47be1f9dac4394f8aa231d200dfacfd960e0f5cdcf96ae9efb0b07f703ca50b2d3b8b3d3cc0cdf194e84c676f3eca501150378d63684fcf4613bb32837a106310c9ef63e227e5c33872ca6ac421cb9e7c7d864352192a90a146e0314e5bd9f84ef527678a609848592d523c7d2c612ed23509920303599cd4a0ed794b0a5064d734c39e33bcebf80baa1be98636d1787e1b678d532253523036f182587722876f240745b41c385634f575ba32efc70fc162a1ef248ae23f403f5aa0a443145a76b9bdeaea3f92279f9c57af7c0d39d169f7a40b7159b1f0fb7108cee3fde500e851f6e57f394bcf7520c3da842c7755b8fa9b7d9b31eb787d7a2f5867d0b87433608c164c8ae5edf1c00de6db67c984ee2c6ff6c1a894f2230f96bc71ff709924c2d2ad0fd001e2c9ccdc735697168cb56063b188b193380ff0411ec4b4d77385895e7253b2371e3e4bb17667bdc9f226647bf9d0a59b1ea296860d9fe57fd70f696998460a8220749f2b08d28599825099efc44266468207b8df5eb5591dd4a00ec167e27ae536ae8e337931542554fac246b6f5120e0fc359051c998840edc109fdd81e56ce4ebbf9eacdfab1947f453bc034b3d2c27dc0928bd417a07cf5c79e225639a2f792a028de6885f75fccf8f71f70cc0b46c5f1d539a30d5e812f34afd09510aaff8fa4dddc0822549cab8e74126dea9a89aaeefb4f0307a083617586a0e2c508ddcd706162e22daf2ab62b13ce3d99d1fcd678e058821f5ac770d208c5078fefed67aabb894323331d3fbeb9b07ecba64aeea4e47918f6afd2256903897277bb1dc319c9cbc8e2ff6b700a2f99a7a9e10e4664525d6a821b690f3b41afca6d4b504aef3129a3e020a499760829b0086651e0fc8262e80ad8d7e8bd501481a2599666bc9a91f7e84f5184216793e54f10c1ac7e7214092632e603a6fa6c75c77fca21451731fcea82a5eacf26a0e2a06731e8f74cbdb7a77ba73a716ccdbd220cee1d266657f9aa96d9d1b9ab6b9a63ca7e56faacd8a19900d2edaec3fd32bab0fdc2280a4613f46d66ee7fe8523be24189fc9a1569d4d3db08e9d673e4eec35e862586773f2126ed00cfa240926df00a4e6471533fce91396e116474c3c0106f85291e72f4dc2f28163a115bac0c8f82dac4d72b96ef6141bc6c84a0271cc8ff009c38bb6affb12ae4df714c8d16bd880ff41bdb235e4f4f3679ed375198ca3c417d03bca9d68a9dcc8721a004479092519428c9942a80cf2d1fea4bf1359a0abcce9bc687105667e614333327cdc79f46d82a75f2b106c65700bc7c2fe13e1b3db829f677692fe3bb93ec59d188c96b38ab8cb27be0098ae03ba17e35966e73ff587cb7872717d8423254ed537054f98497aec21238c0916e15568d0a4bb2d348f4bebff37cd0254a121ba9dc099f059d1e26e829567f7ba94892b2e6bec56ed354a7c435f4dda696e12322fe789508ff8d59abae962ddfdc1f6d07a0a792b6c2498e492f0fc2a9c0e071981da593743964466e5aa8519997f355ff202c5b53f53c0df38af0b94cbf1f5f1c0a490c4e1e82d56c69188a612045c218c8ffbaa50dec70d26672b44ffe742e19a7f6fdd2663c58256965e953d367e1b1feec4362c335c1f6f5be3f2e2678df5accab67144e5f1d8c7316aa5637ef939915997b923018a2dafbf67e4c00f1ee5b0052d57454b90c204eea5b349de4f8658b1529760d362f51cbbac81f275aa24258a1f6119395a41b059d16923bb865a86be5d9b31bab77e9b0319eed1e88ee80cad6f9eccec4e0450aab8881e3b932d2cec9bab0c695fb0ddba3a238df61978543e1bd7e8555dbc85163f63d42f58e2762dcf7d05fc1e48984130eb09c6df7aa6f8547901942b079802dd79efc0763aa40d4a1290a9061effaa39b6b0db3d38ba62b4ed2e24677e4b5995d588bdaa417a52dba137a069e954c0bf9d893e5887a87478da10a3e5b2d238343c8975932ef3149c139ce0e52d704ed288279dd4108868e0c5fd4d408541643466f25bc4307535ed3bd21224e36b2f0a9c44563e81765c487668acf99733ca5eeafbd6ec2f03f4a4ab0da67e70c0404928832843367f7dd8dfa3e798cc300f067e646e4cb039be3f0b9ee84e2253a2689b4b233434dc537e5285d1385b58062363ae6ec809231eff1f258cf4839f6488b753c6b796ba50da9ee60ea084c936764dca09493d382871997036073667513e39f7cf84daa19e440b18cec99959a85441435bfd08fdf62e51b895b0c17d91247681ca79ede288f1590a41a93f991ad39882360c5ae27cdba6aaf167260429c5793cd5ccd9b1adef5016e64d981bbc6e58f84290e594de303dd5c45f7fe94c4ec6308e1ebc34decc3c5bc0fb7047d05e465b311ead1066bef6f532394d299dbb8d7171522e1b5cb450bfc9b4f0b8cf4afe402cf115c2c3d56f9f7dcb4428341ef1760e68a058af0e807bf42a18adef36a6c8dd3f63957416e08f4fba4f40351eaf5a009c1f3d50d7f9ed3e95fbae627b30ee21495855a4e512850cd9360bdd803bdf8449ddb9ec73db4f2c283f2ca602cb662438f6a1e227c3c135bcda7d698db6360d4bce879e81e189dcbdbd124c04d95ca03d5dc274f0f5011c68a5791f7a6c817d95d2bef506274f3b788c44a69464f2cb0000f3d7139b9ade9ef87659cf299f977d66c2ff1009b7178054ca3fa7db27ab62178c73cb38639ad5ac4b4f9c90c09897a5626750823fa3cc05a2537fd78f294db47a1f3a4a3f0df45231440ddcda4061a57807b421ec3c018ed87cd8fe48770cffb8825350f0ad8861e7a64cfcad27a32ea9370d26e0182db8f2db2c6582717a315adb60f48c00d2a966becb3e52383ca89c8012698d30286eae20cc8eea74f4011bb78e021da309366e49d047d1418eb580ae9b41bf260f1017d2f3f22b04061a0890d9da9683f7cf16a23336ad5010d61563857c890b1e267f11647ab93899b5f62151757564adcbc5202809efb6230cd1d19947b9fe6c38af442929805a357f3c9a3603cfbb996721101a13c5b2617512b416e7dec41999db32e8088502681933ed74ca29c75fe1f1352b4b94ff99731e1e4e79938ecda428228f29f160430a8aafdd90692c71b0bb976d944d1653f3c5dc8e567154bd8b756253e0e7580c0e6fed26b12003a7a8a13ac5ad76f45f67413fa26671ed93c0385c6df61b39bd6faaa109a0829e07143604f4801e5720458188e52959a5d7d6baca94f853d4c7065bb0bc0a789b60892ef01a8d6a6651be0a5803cbb28b5f3c3891b107ee99424b8d6069313d2ebbbc35e62c5805f0c60992c5e090ba1c32fd17e4d684ba47b851809d60113180c84adafaa24b7ec3bf08299051f3980117945db3ce2fd775d91574b1e4b3cb607a488eadcb387fc028e347cedd67e5163b6a34ab912383e3726ee01b85dd017e3ff17a06040f0357e474958ff9aa76a7c45c289b5ff245589b4f06287be8aaec2ca4b1c9c8f18a68c1954cb195a915f8b4cd8edde3fec5a9ec30fbb5496450144f8e2479b68e1e1ddc156405f45e28d0581eb1779d25c272096a1e856c190bc399c0e0c0d4d1bab1388b8fcc69e1997b1397ee0080afac909e7c045b2b181e6c6591e4ad92ca8fb6ca2e1602ba3d5881090048e7cf52791a5acf6ef22e49d18cca55ed2041ce1844555aeedc6334ab92b724d2f8962b5c264750a3c11f111af0fa4befeca44aa2c441726bdd51de875962ee9d673d212577e0494b893dc9f1f2076ea2b3af4555c2499f13d27d358884d030984c59edb302313af9b802246fc50a9664702315ed2c0bed99864461bae88b3e40d254a131a20a7e6ec4cb8081b6282a7be3a6e64c3ad1a0134be3d5dbed2c6932403b629b448fb656fc6562b68bd51bb069e162e09cd03dab434501b48d40481cdfcb9daabd62da73ce2a8a3bf20fc615f166713a7fc8be1ff082195134ae95c96247a4ba918f19fac2c26c4d5523ac3691aa5a5c132a133ad894579c03b3da7628cb296ffbc0f1c02b26d2fc7944e67915601c2d5184b79ab07f6119d595eea475d0968bb2046fa9fd0275a0c9cea1a1ca459abd0e309f723f2d4fcda9fcbf35c0287cd846c27b58bfb2df41090a7cdda70a508c473e33e733ede6fe8faaf627228bffe8085ed2f40a6936244ab85aef85c5bca5d02e0240350d4b564f65d6f27d3b9097fdd8260179d0eb1fbf33905a9dd9b693abe278436450cd299399907fe14032b97261eeffdbb20785798ad16774c7499abc7565588beeab1d9b62c6090d89c31a437633e82b1c861d126619c0d2537069f7f30edc2a2b3f89c9c0ef202035d7fda3d44ed9e65cb9b50b349766205ea63e24fc764c26ee49702dc836b427413b6eab33a95f77e0eba02375da3544ec7ec50997d46808267f42baa67ab44ed7d28aed67c781f8849c86f18abe4ef4c52612525d225ad2aa9335e7a7b6da33585bf1d6f4b592e853ea40d67ac1eb14af71ddc3b482026049b7146318c5b95051158bdcbe53a46329280273f235588625a9e726ff33c793ee859d082bd3151694f0f89028945fed400869570905306138e668650bff26a7f1b02a89654f0db86780258bce146720f3a14a80fc0cc503cfd3265ed272291cba4651cbdc0e4c3d54233b67a994ec94071e17b10fc9ad959c6863de13a3509103ece71197154f810b28aac71b810b982c5f69555cb0561bb3ea3b50ab5ddde324b7c4c19d2fb1972fa2a3b74e4a10354df1de5d49e35a79a8291ebf9b2fee7f6c67fa79058ee19ce729c060131d2af4fd861b510db865088016e153b3050a407f3d8754ac56616cd460a130f98c50babc8e9d575658cbed9d1d221d8b00651cc208058afa0b8310e85cc80a7ddb6eefaffc2e24f7a9bb7d886f1ab89ea8cd108b770f5b4ae139cacdff4a5753a2cb2a986ecd8d1a5672ae03184621fe4e08e210fdd2d3253ed57408c8d9f8a1727bf22ff4810deba87d44c8ebb40a3d7b1bb00640567909b401aeef254306c34412933e4a83fa0f614c494617c85cbd19061f9e8f1cc778453a8f749ef7c45c2ed2a362a8a9333c781fde05dcffc47a7280b957c8e75dec96b78878f9f2eafd40486538f0dc8a9e39944c785d1369c52172e5804b865177c45ed0e44dccb5bf2f1a5a6ab004e73e5fea79926bd0cea3a79e3a93618e933c9c954fa3a37395753930b1fafda91895b12c721f475c1efdab64105c874c130a5755b2daa8d82f4e39a095d9fa488902783718ef9ad1bb24e65ad1c63d9567cce61e87f6b5065a3e2929eb8d70e20aafa251934ecdde984a68e2f1955c91ea19b4e72a7d97c4eb0f85d5cf6bedbd8b12760395cc5b1f55fd90b9dc34f130ea2afad88ab917a9e66a61aaeffbb2946c79c6b23b161440637c1e1b19d7a79d489f52d347dad34711c3dcbc2a220054907b8b7999ef97cd1fc1b298a2a2864974c11208252cfa11882f9bb40d678d9d05956cf293512ee9c19b81fd6469576f48e8d1ef735fa2adda5059227711937d7cd7fd1b4f82fbc2b83b198759043ed751758359cd6da4fe4951974b66fa6156f5200164607de9b8bfb0fcbb561625f65e683200c49793afb5cd2f7ae0a9d5b3e1aea03873e66b1d68336534a935e6574c593d589b44017bd5c1442d51c1377a1cf7a020e84c220038eb037822945d2066bf8bf94383348cafd4ec43f15f965eba005dfa38c5f94d79dd0173418361c2652c4ca4014c6d0cda75a790a6c3b6c77e75d0c172c28e944d97def7b9bf023278373652ac0cd40851f94470874ec892d80e4205840504dd402b76863e2303983f840333d48bfc470146927868b43caa0f9fbe56af036f96627567684cf90fb8bc94437a27313e898aaf71de55d93720a664894be15e04e6b0692eaf21fa7ed279f8ecee91c9fd8f467f3010c54e58b8c3ee3cf83588622120379851b4cd506724796c55f7c137466dd480ac5c23d954a913415362dc572455ae454a2fa6092439b9c6a5632d84ad71aa96dc8574cd10d4143870757fda0eaa43bb958222f8d4f7e664e680ee7214a31420da84cb1fb486fae544dce469705c01619d9c14777395b892979c8276f4e656f3ff1923bf5f7da8e5dc75a76dd63ed1027b3b2512661992528744afbcb8384dc81d9c1addc6d13ca3b4ac70f33e04fb5c09c61d7fd5514c3109453fba7f7376747d22356e95a6c70e4c2d30e43d4f8e52825e83f163451592793fb1c708164ec42cd069b40ef372faab79aeb5d08e02aba14c8f89772c5c113ad6d4c635d718db58157d815a4a540b357f6686b29886a4aea35b1704d1423e1ea514af9eff46b03f7e40c146befa2e4a082743a62e493c56a59388c72c3b78637e7bbff6969afd01bf16b2fd8b8e0759dd5281b2d690b5445a2600ba3bb5515997af3c8d70a40faa3c1b6087b63274a8b953c88c22225637f931d6317e1d340abfe51dce3c8dd119ce2ef6c68a957288fa3e424ecdeaf16857b9189727cf047ea352b7e922e6735381b1a3e739024cfd994f4354e97e3df4227f08e61f1badbf04485f10658a42871c8f3df3f360f35c3990876a23d97f40af8fcad37b82ed38419ad85d44b05b3792b69bbf0a538118210d56c9d4333fa67820e086f4e56ad466e606c84f4cc9462702153efcefb9738e769f499ead904a0a266ec8e39eae699386d3d1a7e69c01345e61371e855938d850fdc6fedb294ae41bac012c82413f3f2f3488bda9195b8929b8c64d9e603e042d3824816f551d775c02bd3e042014622d81ea1455d3d8680b9020ef09b143345d436b4465d050acaa6bdd20dce09b342c0965c976d7e9c25811478f4935897280789c09e052abb292513246b7292ef97b1517837dbf7584d7826355e0c4c82ef4bd88cbedad2485170a98539c7c72774d0b2540bc64188c89bd4c05e44be5e66bfae8ca6d1844ac0aa6cfafcdb2c7ea527bb247fb12b663922516994b220407241a8c15df45c6b444b39f42ed0caa334e947ecea01e0e01b4999fc8495211cd2f03f5342e3dfdb1f314381cdca1b12f74ff7cb04bc79ed7b1a33347745c388eb07c760114d84ca408d1c2c6eeeb920ddd7f4b89be9c9c319e6a46769846ad9ba88646391849671d1224732c42b43c5fb2675a15156caf76d5d332685408b87f92245e70db3c999e57f8ee08e4cb6b836568e291631cb5a634c018363113cd8b3cfb6bdb6e542bc74b74e574cbcda6fea681b1b81da88f32a6412873c804b49388c3e136da31179c529214288ebf626180cbb29ada0c0e92697a3809ee35d5750a928a5a01ea0ac8e4f3cb615b002bca4909acedda0f333409b6a8ed4e3245f99bef2a4fc43a73bef2fb7e0e3d0e7347db10beec567b7f6d1a0d95bd9ca1bec8a6c67c091fb30685e80d6f9df290de3f41b8bbb3a4fec9566cea4745b2db16939930ce3bd515f85d5b054d88475c0bda4162217b89aeb02acd805495fae0b5827ca969cdcf76bd89480d51970bf68df2eea4a53d0febb1f76761c0d640586cc66e62c8f85144b571096a6c834dd44bbf2ecbc343ceee97e0570b77cbd8194313241a8d6f814684919f26ca4f9db682b84a6d591f1627ef86871d79e8a3d83d84f62c0c77325bb241a1b45e6580df458b80d45efcf3ea95b322aa279a250de6927100e51c66d354e259170e4afd76033d9977cdc7e34b2846d1e69aa3ba28c12c3b476e67b7b868fa73353a5cb2b228ed5ef9e33f85869e1186e487e5fe96b16a9b5a272eeee82fbc525cd913baa6052c51345e9e75a87570bb2189594c749b3f56d395d3c95392e2f4efd9452f95615b5ba1cb1786d18f655fb0ce96ea9525d003f817b37329f8c345bf4eddef37d1cf1bd719028622cfc2b325821db19f7ba70ca00ae07fb9e6ff0df889c884134f86796a680473bc76ad9a9b8b79ee7ce6b782d652615554de6fed04bc06d4c5f3bce8afa8171c83943ccd4938b296410aa9cd437b9fe052d0d9d2c078716da054ad7b430e9c20ed62cc48f3b0b12857949afb94e68e2d7779447ce82038a4fa8b21276704162160b5ae71a9a9836070e28496a2df502a8c404f5a13b6880185794ac46de8f1e1450c069cef43213e6d7d067b47545ed0a2bfaff20f11d798c13cb7c0ea1a29814602aca5d6a7d1116f97fb6bb8928afe32fc443b7e03448dcf4874939d2fb9dc54ec2be43f11509cc9bfc49e80cf4552cee392d321a1e4529a54b904ac7a88097663d64d949d90f9df9482f866cd027d9394816b029639427f392b781a70971c310ae20e3591e59c76dec0c768c1d3184d78891321861333118b865d15b14f08487316573211b30fe70bd8cf11715acd5278cf10a3bea32221d5269fefbda5878ebf5e45ddf2f580b1e0a1f19e3bceae34a3ca7159d3fbb125fee3b28405df5d8e0ae8d9c9cb25ea15711ee33faed09b2d18771b047053ee9db5f54c8451d475a20e5a17b14d88d1c8301264099a13358d0639bf96f600c542a812424348cb027f0497f9fe7b13e5732f99a389429bc1f11394787aa361f293dab8b93b52a80a819256b34183c7a99e35c1017c6de0eeb1da011d07809e703394b3ec37148ca8fea80e8692c19ee376cb7c6ed665c16753b526e43c32ef1f1bd40ace941cb2aaa1e66452427fea2ba8a75fab091612876a1e38d134474b6dc3ad6b9fd9b3f94809387112a9dd0ef897e794285fb66e4ac3eb393087579e873d8b645ca62407f0ab9d8095e7d9cbf75008d63c3906ddd2f69b194ad87279b308c445cc5a508f8a2c0227180d483053a945664be07935c3d10bb00bf16f8b34444fb3566a0bf04d0df57bae4bdaedb39497190f3556c51d19fd7353f41971cd0b7ecf145af628b364c841c6d99ded3d8eeaf3d12bedc1288585c112809dad72c3a1141ef622ca7cdcec4ad074305f961212c497d5e3da196ea4fa4b87e00ba79012d0914e225461a172ebf1c89cb49baccb27c31383893d463c2ebecbc5d4e4a2c3b54d0e38ac21b90c5e8a7dfd820d074d70f2dbc5bb1c3cf01c3d8153fcfe8e7768d801dc8904f5f2a77d0b8c91dea52209aec560413126372ae7adc81f0b6ae0d884be4298a59524ff80cadf0ed8b13804f9ee9a5376cc241d093b5d04bb43d01633c1c95082b975a7bd22f27878793d7bb7ab12a665cfc727bb4b30bceff3740683a3d17ab1a07db3e5aafece1ade5cbd1c6506e4cadcbf36a82c44c8714899b699bf1a88637b66f2c9a529b2c3875bf03b4d20e451bb4411dabaca8a03dc5cf031028da62da85bc971de37975f9f4d787cf24de9f236f7fa543f59c33b8da97a6c43227be219de69717263d78605fd8a262ef3bd56c1264c03834d1dff8cfd00fa49b9a18e46bb76633bb89227e609978cd67678572dcfab9405093fc8f92fa9fe8ea643ea110080bd1bb65c107c6d1516b5d8ec820abfb7948d8a91bccedd2219c6f6cb123ae369adf1cdaf44512d425acb64e3229acd232da5259af0681efec2a944ffefeb1cf80774e41f96e488f2f4e3d9e5252364cbefc358e8a3603e4eab29bb9362d37c3a0d0da615934e2d83c51fcd725a16454ca4389ea59efefb34277ab9ec678acd1c8bb2bd39db618a5816098fa94d3fd1207fd74b547ba1db55ae113048074599f588083968a4b4b85c0c84248c2454c3560c5910b455b2a3c58afd21f71767247e475082ab953b7e45f5524e9582465da05eebfbd6997e4f992b2f4f5a4db881c06c0a4d7d67bc1f89ea57781af84669ae280a0b005cb07592305274b13c07cd274cf89909791e90d7ba5677cbc703b3ac5b6a2bbc0458d9430b0f4e52040706b70b28e0a3d51edd8c334f4659e53ce3ea64c5818b52cf7a23b852634ae66282fed75974027c7a2fde03d72f33a0405017752da647fcc6586db16759d20ccd138705eb10887dc2b1bb9f8dde688f2e05b4cb06bf1f171dbcbd80c00711a3b064f5078878e0587d35072b00350cc416c71bffc577ba38cfe9e967af0126ee13f70d83e033a285c5d09334bca6c27ba71d4e8e328cd8a3824c3210daa9f7d9a57878468ee0bca12ef01190798b1a3ffc4228f214d449333111590d5e9085957caec71d2b76522de183abce98e283929bb0c3c32741a628e284cf4c42337dd292702d28e9a063b65193aa756b9b1eda37e107a249721fa66e0f566e236b042477cc24498bb8e115ecfca982a118f28991640a8502217a6d9af9a44964c5451f78eacb86ed401f0986db768170609ef253a239de19d036e78063c364888868eb4f10c2f1111fc102250502d7c51fe605aa6002b85e909bc48b02bcf7759d2e4d8c4208ae03b9c850bfd5234c496ca985aaeb6efd7a03c2916e8861f80379887ace64a8e5f198dbcc28c6d44d9c4ce7e529bc0ca89ee8dd376702f2b4a8b5c505884c3894d7888b7a620bd351be945398c0a85df348a5c5e1e617938bd8799b804ffcfcfb56b9466af445b4c140b745c255f69a6c79349a4c32a1cfd44aca486ff290fa160413f9fd7e16088e5304ff2428d209c36ddd3b0f651d636f423e24e3320371b8b03a12f390054ccade002aae2422e84fda2f007b3e7f929ceb911de67daabeded28e3995939401ab1ccfed6cb211e1fb8e9c187518c63524fef89127360f20a76c851bd7c98df9980922f9671015a120dd825ce4a4e670314d0d5923d733f64cbb2ae0954e47e2e5c9cdcb05f3f7a0d2b91111e5c1d641c654b3f12f543fbef36f58b32b80270d27e48eeb3d44c9987368972cd5938890eee583a4528daa243cb9d2ade01318176a1ec83400551d746c05257377d79dafe0e0d5e1dd9e0445983c947223a40ab65dcef9f373b469a651da47e74725e73d077ec8e6965439cee220736b156d504384abb47256734552ea69b7e3c8c6aefdfd39f684beedfd5addfe6a32c8563234d1c9b611a3e59682de7efc3f9cc9523a56e9d1beae5aacf5140103989c2fc295ad7a234caef29f09fb5e6c8108683702b27debefec2078b7bd8f30d63b3dbbe5a09ecc83aeafad2f8a7bca3eb97811049ac0b7062fe9bcfa42ee160a889461a9d8480ec0e816c7beda7fd032f960be7adeab3b9c8caec1322938b0c99fb87ac18e95726d3589a2bd914ee15614c8e2878ced75a91ccc2b267971f6206033d6c40842659c1870f6f464d0c4966ca4eafb8389cc5c979f83f208c3a8d5ea6a5ecf2902a3fdbdbe7d55b5e16bcd1e2d120c58a4eed2ff44c5a23af32a21eafe5274ae675bd202b1781b7b977bc458f31341cfd3a5fcdcfe4d16fcb1bbc71d36bb536bc0b41860ddba15cd57ac95a4ee5a80c2ac4628206b616561048263bf22c5ff297ba0589ea6596df46e90b8098d1606bbc0d8cb5c1374f38568380e479d9d93ec01835b218b3ed3b6017d4f146672bb16272407914d98677cbb03d85075738d915435a98ee133ada2bb344c77f03229762d1a06247fe9559683c493fbb80a63901fd4f30c9ced26ef9665f39a0cb7827a2303ac124d2cab1ad8efc8815588641a00990e4063113cc3ce6e48e056335808fff5bb61c61091b26e2409cc591941127f89df7548fd6e0a8fdbe87e72265b03bff47b848ce41d279cb386505bcb86e1512a82682e08f039accaaa3f1d821f8b8ea833e666c2bedb0bb1f41ce8b1f41b25704321c5dc7982e73cbee39979d9c8b07a7c1ad72f2455de5e6079621ff4397c95624d4c4e44f563a56fb85ce99d6693187bfcdbc7ef206e312f597f6ff9b4fd2ff8f234c3c873d66afc7ed2093f3fa336ca02f5d93830634cf50cec09e5dd3433f6de986aeab6a67dc425b6d72fd445846b305e31347b76ec7c9e7d26b0228e50bc1cadbb018cc32c211f40973cdf5a195e6c31f891c26d75cca0749b4b01b4d02309819702979098e58cca06f06ab16a7ed5b09ad5a57cb942e6bcc010428c38bce727881b00cfeeee52de91335e62b9b31c3c7e1ecf528d05502ba54c4e92d902da8bf41db79a83351565998c25dbedd2dd05c068fb2d08325c480095ba9da00320370fe0cdbbd9672169adeb1c5c013deb0c6f7824e796a75f927ba961c7da8c82de7a2a60552eb63cc2e7273ae33caa3e8c15def0aa967dab2c1235d47204ef60e8ac14ec39c36dc66272a09d368693d64e1a7d8548b501b9d64436f578cc0a91d7fa1ebcc0711a8a396a00f098dfc105b8fc4450ffc77fc1bcbfba6881b0130f04f78fa6199ca3e2ade1e464b2d54c3d9d36f3eb5f6c814e08db0ccc8a76b8fe0aeb85e22cc28c6b76c48ebc9e4c936c8cbfde80d50067932a70488eda1cc5418fdf0c1161aaa3389b47e3568e02efc6a8476a7a1c95edaf4cebc1693e46c57d739fe01def2e988f36c9a2d86089a8205a8f66a017ecd373c5fcf5c9a7677e5bb2564ef4187fd61ef230d5bedb92d1f3376296d6a1f738f8037c5425e256e2d54a054a277593a3698de56f2b439364c9a8ff736e74e993a741df893fd7fe589cb2097de9d4892cd0fcc27df14b77d7d6e2a4e32781d442a1e5c3ff5a487038e8bfe5c3aa06a8b44c53c4e3fea58e58c9d6d59e43f9c455e633b397bf3547d611b6e0fe3263b90fd73e503155d8135139d6ea9bceb09dfcbee6b68d518ef4833102308f05bdd9bf5e872d11da13a5cea7090c6215cac0eab1a07306d73731c952939fe242c1b2898b4ddc289d37ffe24a95ae4157389dc9d49c8b8f6244b2b55150a2b45e149a04005988b148c8820015d5117284e9fb3b9a927bfdbf90611f5345564eac866f1a22c833b2af3582bdbd6aaab9d683f4e45240ff7aa142329b53ecbe889501b987441c07c799f30585fe236fa0e10660b4c4bb9bc5daef4d047f0d4b9e69de3d5198d97343f303090520fd3832df14cf52cb583e3a6ae78a3e313981ecd011150c5f52546ab710b1d5064abd2d6e1a250b1e325e8dfd948e9b4635af7f821fb0f95e8ae8cdfdedee00b85f71430b41905ce800b2fd544a4f19afa98c9ba5019845343640308146e5ba8ddbe8db12775e516ccfaeef95d2ca8eb6b4b7c1fac90cb34a6f0fe87649fc06acc9d45139c4b2d0f4447a9589fca8468efd3465d36e01ae6c6a556df1417ce4da61366f3b4359e082c43f7832b46c64f52da8cd249d3c30163eb948199532daba5c35013f8ff5c0f246adba9cf79a03f0ef80038feb71211ee59bab93d725d11efff58b77a708bca225c6b40eb87bd468051d9028d183025c67c6b9b8e7c1907b8ed204326cc6ec953a385a8b13b532a145d74d78808b17360117c68bef7be55f3d8f9f1910d46be16b86c3f8d810235cee45e0945e111541407b8f3898c8ba21d73806765582bb4928b726323381f05acceeb205f0f65fe50fc70b016cdee488d459c4f6215b24f8e0f5e5dd2c0bab0a1b2adadddafbf6e10a9ff2ecb0478da5d73ee4691874bbf53af8dad139cfb2a97cc9f3b5be8edaf5602ae0f6b76fb911353420ad352d76693d5b827e4604f7d6ccc57556f35a9dd8a62b4676f062a3f9e7f764ca50b4bf98e15c5fed57208ec61e6aa52410365a32a65e31a3e93dede1b81ac0b921612618fcaf5137f0316924cb2ba91afbc3f088b3412f0ee0ea5c0f05c771a6c00f637c2ff4dae3abb303c073a4860960403e720c204e7f1fad39709caa27d337b3d8f572b435cdfee518661a1fab8e64c7aac1586b56135d92d04ee9f4d63bd516c0b8373841cfbc5d25c7e808eab1e4561e0a7a93ef7c731dabbdf6405a5f8b1bdd92a736942ca38c1eb349ee051910a27e10fa7b91ca5c36b8823aabef1560e81e719523f575bd954e8e7339e6bf5a9e8898e34a5044c255519bda83282a5114250c62d9ee4ddffa2f527888c3ac010c8901da01f4464842662a07192298eb8ceac5f06aacd9ec8c095d83b6fc4750ef763061710191f7f4c928bf0213922e17cfd6536e7b33fff955b81169749f1a24fb6bf3cfdf9b97156092585e526e78c3784ac03e78a6972122ec1f555c61ad2cff36ba604390421589e045a8d8b84c40507adf6880059aa38f437afa672929e0c3aab215180009ec6aac1beee8a69193f681eba3c8191780fbf1e20b8c2f1f62a7a115b98eb1e41499b8844d362555183c6e3eb8d203c692595ddf8712006d00f7be4fdc6b42c90b9d761a1a391f2804a517fbd31befa9c2c66238b965d8aaa705457bf331492926aa7fa412072f2a55d43b667c3572a716943480992cfbab67cba785ec4599643d24691e11e90ef8a5fe71226254ed1578e01342370d100a0b908d55a319bd39f03b5e4f8ef37e08f08132b861af9d7cfa2472173224eb4b1c38bccc3248538457c8053a530a8ae51adb598f00526cbfccc4c04a98af0306c82a11749d0aabbd1b6b792271985770eca1f4997a4ab0c6d8d5338515f50a40d8c8459d845a8b275a4ef9042e753d4f5db404563f4b071639572520742d9187634997d189dbfa0de42ef91b8763fbbb5b317d6ae28543b9d112125506f9e16be829ca297f0c1b3df5090a63763051609219350dfcf5fa94678abcd5be8ca8339671a17cd1c03ff374ae99f51bd1603a2fd9322b3e8e4cec10fa41f4cc756bc46f3a1ec5d0f301ed8ec67339f317579e66fb081581c28588ae07f415013c91c18fba3b17708d838dd92f789e061d1356d24ac02c0dd0d8d3ba44f89d5c8068e967ceb94889e3e9e87064450dbffd478324cf4270e13f38285755964f537853fc403042b298e68b8368f58259cd3b0ce9bb468b55bbcde7aa59f90b3b4629364c128dd11106a82541c474d47441defa9a7f9938c439b8a2997211d64c9642a607c27432e8c6ad1ddf089a30ec1f4509bc0c26b922cb9fd16c68cfa4cb6db18fe540352eaead0413a5469fbe9a65cda02c7ee85ee788a1fce28727b3a4e2eb0d320e1b568b040b8c4bb85aec536a096d12d0c2ad7adb391190e3a986aad53d1cc8640f6f42e1375e6a17673b6af547e90ec9d18f0c7e447f8109d111f3446b3c72ffbd4e54d4e95daf9d7b1e57a0a8dd50c5395789b2d50e58a0598d51e682997d6239a6235c1e3c1d0415cd0226df024a5d5a4c27010ab4488784b62d83eb2d66ff073183d34b028a101ff8c223eac5731199bdbb907c02cb845d3abd65008a26b44b5ac8195b12931140e5271483179949ecdbc3599abef333c0305bd4f7005d9bf6366428191c05f3399ce9027f82b5f5812e71a14ed8a77c4d0cd7a8b9084e4b95c997e3b4041a64a454ff9410ceba45f069ec5ac8f434dc2172e147c05c3c255a094cd68609be864c7187b95aa7c5ea772e295581975fdc8624c10bbb847b41986a1a18f6385930a229bda2ed9acf82e043662dbfb357c6bdc288f4f2f1dd9f77ca03ff6f0466b223fdd86e13ab702b56faa9a1d2124363f76b2e59dd73d62910cf27ca79b55bcd0c179e0a1eaaee09b3109ccfa525ba8aeb28c5c329b5aa16eb2b00aed95bf1f3d53ea74f7af1c0e152137d7690c7e59a8a7442e6505a20e556a8c6d0361215711ab69d2ecf02ca479a40cf4479b4d47c8e082d13e4a78a1fb250f3a00e2a7b03470a6e790f266847048d73c388ebbe0576a69a21867b50a56d3240b84eeb1050ce30f51df03facf240fc2737b75f20f8c1f0bfc00d985ab818a44c9c3b43c214af8684d774b88823520d9986bdc0030b48be3261e90744ad06447b62123fca6aea5c073ab635d7de7d15104668d5471c72af988a05676de3d14e2ff8b2dc947626ce4987912034bb469ec6d324f24d2e0ee99d784db08fe7cecf5b1a85aa0558531a752b115c0f2902828ced5ae075662a5715beb97eccaeccc5e2204c011cfcd0ff0a73b3f79512a78d65da5f2c448e41ef030c2b793a33b53233d8ca4caaed376b14f1439904c84c6a534eae9459c69948654fbe1094bcd09848c0bacbcc65289c9ad8b5acb7253c9e63d59047c13374ef98a28495f3dda9c917b0174726125df9f9242e258eb46ea183b467b38bd29773a06ae66edc46e5d3ebb7c30b5c5ba2d6980648505cb5769e37af64ecffc9c9fba4c3e1236ca722b7740ceb09f8f65017f45029694eb6ee9366a50715d36e507b1462eee151948956beb9ab25824611e58059e24bb288efb57bc0ae052fc7184c732962b75c3f599088e0b2145dee4b46cd260e50296eb3c4334b7da4757d30da6ee8a3dc04250ca2ec6158c4b39c02e002a30545b021a93569152362e60a4fce5e2b0adc835fb1fbc91e79c5d32e3bc4491944abefef218349fefe0615bb64ec6086ed398ab56074b98787878650a5d84ad8688c1a4815f5be54c546513d381a6e744de59086391621abc8062f4523f4a3ffe4e9355cbc8ba6455ca6044d64691c215b44bef2ef281d8a2074969fc411ce749b3f8de30346694764432f467d152053262610252f89f67f47204e3152dffa2b00d39b122377c1906cc63ad40f38e4bc1dc49a3606060517cb5453d0434545ea8e02870b859d2797991933fa02d40022fb9f4ec487b812a6b2c551656b6162d6ab1759e212cf2d42872a43e0e3ec372d24f37d0f7a0e6daf41a95c80bbff0e0bebe6488af7971bdee534414e3251f37d3ac799860e7f5fd4b5a093b152940a8b4bfd6a2994eeefb0eba935904531738fa657f40969208c7f7f2994a5ea02f7a8ec9d0dbc9c4cc21d7bdef28a753fb66440fd19f869495b0254b3fb5a76310280719d06aa424d6c656db36ca44013692b63648166333396415c843628f94abe19067717ddea9b9b8f4ec0f5173844da8ddb29f5b633229295c741d50b64d846a6919f798061e383158e3dcb2dc83e171a1a0db89c16000c92fb1e903789fbd75ee15ffdf1e465c35fff4364afbfaf0d82d4a5c1b64c31369c2a72b2f484b7b5c773234f066760f09a3b486519ec1804b14f5ff005528f40c6c140be2b27d7a87ad1cefee41d59701c540c5fe38cb3f408a28ae60fb1f489c352821e5f3eec3b983c7e84f070902cf36a5f0329dea20ce823e7d52cdd341faf293e4d3fca3b6b4f59a01d18d4fb3f0c8ecfbf3645782d48937c02f98d68dc1790220554f573186f890bc1748a4597574a351d09bbe7cbbb13eea38a36032b7520b900d31dfa05b628ef1c880c30eede5673e8ef87be4c7e1eb3e55b866e0a79dadd40aabd51e921593fb3eabad598bcd788bf04b2c9ab31c7e75d4e10c224a11e9cd4ba65f08e3cc21b01dc220f0f6f5ed04294f40b74bd946c45b5dcd841042204f87a0c9717aed7ab3d2a7e0cebbb3119690915dc9aca0012a7ef3130b7536e2e6ee58f192d5b0ac29fc0b596748b9540714395b971c789a720136ea2fa8be33155595b6d6d6772a90b580180f8ce6841253249b30222e85683a470d91e0ab8f4b70bc75d09e98134fd31aea8ec283a8f09de0a7ee68f366764003e7f76ae6ad0b9d7cd06599c9ef408b1dc97c52c82833c5317b09a78b3d65a375e56e4778f4b56e9b52f5b88efcba089e1a16d9a76bde1d1a2b4cbd5482a26a1aad7a7c7943776cf268f8881cfb7c09d80e120685fea9d962354b9b78278d0108992b8e7119a2e11882358e84b6a0ca60e7f372ea5a4288b6d51fb7484338e9e5e9c055cb4eb64f71eb6fa4a97ac46df0ef065bfd2fbc7191135d0314496d5d6f6d0d4ea9d15c51499160462b4659f6f67cb5066df22d47c1472e245c4bd146381a3aa6a735dc80d6eb96ee6631cd488ef3e049664085221c92cc74329a41edc4081a040d95204220ac4b61e7d42aa30e2a0e28860eaada6da05f6e0679e86e822c548782637334fabbe432b30e7dc3e7d8b0a66d3f8fb007fb6a188bf7533a02dd17b61429582066fa45ea122d13a8f2f60397ee7b184d750926b9fc28f8c4a6d27e452aa3b6a52f239f446ba9eb5507c1f3ab679afd9efd684f1845eed7a9da8cfdbba0bb96f3a2cac22efcad9e5dbf43cd628cc9c14b5ca529d572770bdd9507b3de01fc7ce78bd3b4d4fb9ca8ef177eb6d58ddd0812329467668e28bf966fb571cf5643c7faea3059afbc912ced37bbf97b964c2784b009f2928709a1825dcaf8ec062162887248ccd996342aec5b84102d5a6808ab3063cc6f35572a62e1d60575b5670401b140f2f797353a151969c240abcb8a20228c53ecca4065eb49f49c471e4955b3b000f70e0a4be99a45fd90938b70e66deacf1b407386deaf54dc40afcda5a6fc1ef220c125caa2b68bf22ebd66b1a2b164d048e9b91f043cd90b1eed4d2ef84b7a24dd553fc67acccf4008e53f6b294901957b12f5931b6ac2b113317a808dd0198c10ac51bce247a1bc793b8d6e96222823580bcf9d5e2f028cf6116b88784fa1477a281d1478ca9f35bcc636c3f7c5a281d85d147496f99f93eb35db15e8cc61b4d7efc7d2b79128fb569443dc1a64dc61fff640d6f6c766e0d12215657bfc6f62c4750b4505f78ac4e10a64a32fe686b7615b69c5c0ae92c0a22700f846c218276444d40cbb17ee03299ae07c477e25768117b896aad7714be3377a34a3d45c14dcb5d9f716d6a977369d902cbfce970841ffc4f5af51501f3884f3e731b1a35867322e79ccf47e886d3b51a4a280833144b7a5474a04d89b0964cd6901b48ca40eb973c2bf394752dc574425f982846bd873182f02d91024493f710e6c14c9b46c4a1c08594c76df400da0f2e01a8857bfb5a898582d00ae02214b6e6ef2c3454378e5c21489225b05db4ca9352e449d2f2eee4e5bdda8cc85296e8ac50286eae97a543630f61db0d222c49d757cc95473c494f063da26680a3b9513ee5830a644aad338cc6feaa84f8582bed672ae3304b0598e44f3afd3aeafce0814fcebc18e38b4355e87072329138114b99cbc3556467c8619dc2c96843a39cf5423fe3c5f823b705ca74b8e12b46a32ad5bb82ab18456b044bd7f221badef67c1bfa5b8b8486a96f32ee7604e8136b2d3b1463569c224fd5081938db3842338759a1e62d8975b51cb035efb896ecd57b988c9f98db4b8094598883b5774d6e1dee0ee66a529f4138a8b523d5f4f2ba775c60f15d6eae4df50db6d3903c251f32f6e456bfd982dc5d33e1a89a497b79b9dd5ab2b5bd0f546a63e5fd02c775124834fc825ca88a773ed052fb968884c1f2d86d71db0831c29fb86fc8f1f562babc4a55819fdc726bb5025497ff15f070fa2081c07cc1041d733fc712687c483b40499f62515e8b23b4cd6136430e1f8114dcf68937942d3cd88069bf34d093fd49edb71ae438b505b0db13f7fa85fc3c3f90a70a73c1eca04b893d396d5ce548a608b56aac3f461f3e1883c6b6145afade6c7d47486aff547ffa1e309464cd51cc8abe60b32dee670bf29f10c5d6c2f7b35403ccd563006149c698f491129f04805ab7acbd67fab2c9f2bbfe74ad21faa31db21bd92974f4ce58600d52c957c734a6dcfc42d1043c1063cac03d47808d022b21bcae03ecc905fab4299a5ea87db60ca89c75f3d0d2d6df536c884a9d4de24943b1010180183ff4cdecfdd7ff9241fd9a3f61ec02c3eec0649c7ef1927276019a35cc76d40bbae92d2ad134498f5c8291206df0f47f7da3b3add1c53e52f71b8b45a48358c0baf7b80207945d21a5a14b029f29c6c70cd2d0a7bdd7701ae00ec42ed4f9ca8876dbd0f5a4a714e3329225f7429d8bef3b3c99d5804b695a2a18d2241c265321802d0ca5994d49fd05fbc192997f3035d7f355d2f03040518916a39b8fff20832233f1f635301b10479b9428b107196bd671ad3c79b062bfbda63a66242763aea0c90d8f195331cf40e55788661425ecec129d767ab7dc62db50a9c6a84f3822163ddd55f8aaa6f9070b4fe71b9257f7fe4279840e06a57414ff1fd6f14506cb6ae73d3e82b04d2cf3a883b16f3c95d1a56046b6b242048c0990072ed398f2284a98ede53d156c3e4e8476ff4f68ec85cde5dca377c65af6f9b4c88d5821179841d8db743c4295d300d020c28541c4b5389179e5c20b3989843763fcc75f9d857f5e82e163c75dfe661eb532b4e5578532764f12ea9714994ca643d5d1029edc9baaf7000df0d511dac74852796846c1140f720ba2c0b81615470467bc1fbf81af2b421fa74f0a7b3097fa2f612a1c85999e5e965986774b936cebb08a1615d8f89af748212b572a633b99807a0de10d7eed93b476685f9203cf8e17dee0cb5b3badecc917645e8fe4fa6bfb31a59689760ed39378155441df9f182902d7939b78b6dc8a6cd193829dd2599a328ae56e931a69485f2420d909b73d9da4d84d927462421efa566b53b5228a0107d2fced97c73bee3e59fa9b712dd61f4a8197531e9224f95e5559bad5b270a3965b34fdf71ee4cb7b007dddc6e84e02a8610ddba6924b7a9ea6912e07d494c8523827b1894ba6bdb50e17f14c40cf92aa43dbd6a21fd9b01eb48157b87166b72d1c59ca7d6eb591d4bdb5a9bf96433a7e90c7bd05156dbb39fe5f73f3cea106fa4e084904765faae98450b1e28076b2fe166cc501fc13e348389cc00953170fb69d8d17ba463bd930a6b2fac7a720f214b0c0800f6303326f030a2d08d7d76b8d8702a916a057510362d2b2dd0484e9762b938f4b76836b77c4f7883706d9d1414ecff5c94e9cb8cf875985b3e26fa47e7ca633fcd83da2d6d97df527ce3ebc575aa9aef2890aa645f1ee506b4524da4930761a56a4536f3090473a97474df0f1ad17b5b879d385ba251ac5f9a5c64b7d929518719896832d579e0052db554ab831fff57550b9c2cbb03ab063933e05b1c0293f43044f9dbb623825ac23e12b1fc4e674f0098f1acfab472b1594b522515a416917106e990b8ce03c3c34091291c4e8a813ae3685f0ef1a98500f94308a1d756dae7cb2a5f6e658950c04e4c85a4be542a479914dc310bf9975cf4f322f5b52379eb11037ed306ebfa3655f630ce5fcc01e9f9c98166db4f5fe5e623db08419def7fa089d080841320d70aefef0bf2a4509736d4381f81925b79554291f956d85ec7962c30373e2240361958d5fedfd12f05eff5a5d06742e4678de72102b8c0549b00b2d21fe392bad65083ecc6012a327240bbb1ea6a11677274c30acb7e1e5af74a3b836b195388c735e1b1f97a0a6962a55d1d9f5c813896a7c0b1d1625af4bad9f9cd41c8c43d778dd373bf0f9e755d83c2a86a444a63c997ad0447c40f54632fd0670905deb60dba56a574d4649c51f7a314de94b19759f6c873753408ae49f71aa7b714329dc36bc202a6e976d140ff3529492b5c87246bd7fdf145c56e8408bde28f1f9d5e9feda39d0c5b952a073dff8627b7ea805f9d57b96121966ae616751879a27bc0cd9e7671d894aeb0148c3f6c6b94d53e09bc35b44753a9e4d775fb5eec89dd14b66cce6f65938355d55c0c5a3ed5d2d6ba02bf1defc8d29f330871c5200e9ff49c4ab5f0ff17159cc96ecfbca8520672cf8c917a4b5f3c632e862609f695b774ef219ff7bda407a08f15b0f75b81247dba1dc1dec5cb247d8dfebc17f17520556ada65aa06d490ad59fb8e6c49a7d9f7736fa4af0afe222686b141fa0158aea41ad1e8706f3fc1692bc32e2157c2a4e200350c06651d176e9f0fa304d0cc72a4931a550b6587f31c7c54ed61ce0e010efa047a58c50b2a57fe8b70e481b387e37da9976ac9850bf24c901ffce85072cf5865dac2a908f8b882047795b8bb78919c2607f8dc3c5a718887f70d08520dec343fb623efcb9f448c1550f07c1ba075b910c8f916b65450faaec150dc6c2d3cfa2b56776082c831e885cdcb5b69235d4497e65fab67bf4ff8839ed58004fdae901f09aa887e147bdfbf5d65c65b1999084ba0f27058139ea1e60c0d37cd68948194b7c0e8be9f585c64bc6c5100ce9b7a840c1b06714eb6f7f4e2cacf54326a33a49d6480b3742d5e3554f47ff171e187c51eb6aa0976ed496cdd87ed71aa3598c8c8691360e181bb2247e3dc863f5cad877b7a5eb97135b53dd7176d7dd3006d41744b58b861f889eb4468750ee73278ef2b21ca1d5950073c1e926da58250b7aa7d6478c4c784f0bed51d158f0ebe6fe26330b56c7d07eb6809de72265587175e8cb17e3ce22a05ba4500e543f3fce8e5685017928857d4793454fc5166c922f2cc327b8faa5825437b6d4addab460aee7116537f3cc418d47155f41b21132578edc403ea7f62d4e58567f007ff4c61ab1cb90d0cfaa09a8277a97e45a8b6e78b162a717221af6fb13d515431aaa4a5f1ae85296165a6ac3363d61740b3ab36e1edc34e8e73a022d724bdcd3a980ffab6e1de5e3e8e1ffa931f263d5af061e1e56a83d503791bfaf1c2711d1ee8280d94c371afa5baff85b174653d6c875dc2a9e609f4484b3cb16bf99bf630eea650ee31b31e9bac271579a3eca32efc7bf5eb044ea4589c080ec20ee8aadb75cb7bceb7cc6890a3647ab4a28236f08a7d7fd1d82f40b82a5c231143f6261e17307c6095423d57bbca23bcbc7c95d3a72d2c35b76f67af91ebff4e8dba568c277e13978078944ba48ed8b8e1758a17236f7c5dc809936a37e564f8c8d9d732019dc49802a5ac3cc039dc8c795acf1523141dce6eb6e9aa19fb4f72852806595ad795a88ecb557e5e7d3ec0b85d692981b1d577d4c1668b822efd5c3fadd2f4e82fa47e701e65100a11d776c292f27656edecd981e89245be302bfa8e5e81f18bdd1cba40a5ec19656b02e502fc18be709cc1699f1afc16ca9353829eadd98066242e4f88382bb879148e9da0dbe338fb7d083b6fbe66f3c816a3b20ff266889bec9a43932dc638867f85e655f1384c923de503f4c1af2afc68a0a1fbc2297da0d7ea3587929a9d2838c8e09a535aa4b76d6a054e267e3d9132bbcc60447606306dd0ced5c386721e0403edb3d8dc2b64d09f9a239d512b04e0d6b3814196502292a118021364eced07821573f4cf3da7d6ba94ac75a1087c6e7a78dcfe2f8b87741cf156de9d449a58e552df95b6cef5716428b37354f98f62023887376ab4824a55d27b8852181028d24bb517f0fcf60695aa465575b7d16adc11954f650496f52d24cdc67593ac11ca4291676f48fd23a342dd4b685da3332ab5f4991be8a6c6e89c9df6ca31467d49139b90cce8c4d4c06ea3b35ce854f143f1c03bb7596f7462b25f9c8262d49ad7dec663ab49a4ae0fb0991d0a35b6dccb1f2e7dd5be6ab4a33f302331fc2b2798cdde9b71596d2319478362bf842471b7ff025bed5e28ee6c509cf363e15bc4a846241b62a212c39e895aa38478592d1516ee7b5b95c1be73ec1f5a1285a9a09879cfb76eada1f960f23f59eafc89555a69a21babf1e5978eec61b5c6a626d0c22865e14d30c6b72ccfb049de909c0e5893bad84ba0b090495ebb2e3f0ab28a5ca4d2ca62b40030a16c9dffa9136916adea9c9d1ceae7038e2ad8d831a2734b87f0d7b2f6f4358785e9723027b308fb39385b8e4763e6e67fc9b5c8ab22928039684407cc98a5e7d37803e6f4c1acfed4bb1c37e5cc1963d3bf4c743c06633c68fc7dec89c9e05e61073b5a5d4e9398ddc8c7e0bddd5f35923c931b3d44372de55da5b9506a1ce657a24645f93b58010077e1688aa9cd85404b7c4c61971ca5ed1277364535c2de422f01e082a74240677c8e6289f66e51350561b86de8f2aa2441913567d00c7aefb18b124e53ebf057edda8d7f188b653e06ca313c8ad9d723b8fb32f1a27bedf5cbfb6e1186c367a54d0fd303d7bf0f4898164f49652ac71e0b38f0052b202d93218592a76a96372ce426b1b60e2959267c59fb08dacb26e24d24627e63b7bcf632f6d3d75a08efa32707fb9a2bced37b22e81c2d9d97434ce3a6a7b0ba434fcfd6017949e25e117db16365681a49d5cc4fd7363f520380e6a37518eed6759fa062b811c833dda6528674c0ab7c2be5159a62e8c2abede6270149c965c03a01456f581803ae4773547491a52c8b295504bf3492ffe5bb220a43e1cb6001fbc75948f49ce875b3e055c1fa2d496914d7f49e98e7a1678b2351aba528ff14a2252a6c753bffb0a044f76e813b84b0538fa1060144887e98976c9eebc5f89bd03a3b4e04b68088230889623d728f9096b1b29f97066974153456f15f5fe6aa040ad4df7ea73fe6d737acb29cd20e0b87b158d053836670a23b31c6fb27fc13df39f906dc7a7289fba17c1e3b5d0de847b97058c6799d409d78a93f441be76eb8d0719639cbe3bedabba07ed830c55704aa1054e6c08b7b26be44bf6345fd8bac2094d383bb1863e625c538050df18cdc375352f06e6665313d6789299d5692393fd1d468ffa8889a866fe5a6f9aab8288b89f68889896e146a7427c5037697a0eda944a3f4cdd0d98c308fa41d397ca52fbc7d653a02f5b2ea7f08e00831546719dd025a379c9fb87fa5f1eada8d64c1c3c8e43ff861a331588160a9cbf19027d095e9d4d98f8aa358f23ddd8b598aa89ae982d8431b853aaeb7ef0d859cc9d62c7e864e4f771efb18144d34e1df75abc740e2f84820a387f1042f21e3985d25cf354388f0b6632d949cb9caac901631b9318d1e92e7f5caa3842bb485ad398d724f584b910b4b985c743128953147c49ed9329acf4cecbc7ce2673590d9263be40a25f640c19d3df2d27d04cf07aa2b37eeb8a6cc08ca470b6385b85ba5be7a19702608ea502acddcd01486ffbfdfcd5de33045c3656954c9ba1be85f4f2060fc9faf57042bebbb7633e167f2eb34e47e293714a51a3341ad46252a6f6cd9ef476d3de991030cee72ddffcbc953b68367e287373fcdd7cbf2ac1b5a22d8847abe6ff29a7577d09f8bb8c0becb6fbb2220726fdf21c3deafb67ff65fe7ce1f4e3867c8a6a818eadc00965592df155e94bac654d4c3acc5cc779abc3dfac21c553dab8f72515a061bfde0dc546817b04189831efcb38affc2f17725eb6c234247ef9672a1e89c83791321e0852e196ae8094afa8fa1f5152949fa0f8bf1f9f465ca3a994d33f7b8c825872c8dd086d3b38f8d1f7874ed0166668a903049ff5ae76fd66343d5d093fbeea8e8036d7688ea6cac5bef200826943caed7f0811df30412c11fbca212782af7d19c8e913e45b6b7b46888e1997ba42e8592dc03d6e03f03078b42aedffd213bfcb1f592e4dc31725c33e68b7bf48057fea93548e7bc4f65debc9936efbbdd883e40e025bf7441c6e357b4669b008202f61ded44344c7d0c8b5e9c9068beb6537a4c2b2c3a7b49ecaed3414fc8d9a9609b38f623b0d14f1533e27264ffacff7aeea69e0339abde8a9b82e4564bcb9d90a9251a35217dce79afea1f3f4a311340915afa33a7d4a5bcf6d2feb0c2437cfe7d4a5f0d938dc078240d38a3d9b7fed5124ccac30d29f4b09ee7c0ce7ca317a3e1cc91623755f112d9fb510669083a3f20341fa0ea2ba4857485f7ebc7069755aa734ca69c14360906d9164d84c5fa62029db6416cf029f0e7862ad9d025cfb6d1f3f11bdda828310ce5ac863dadcb615b3b931a364b0d34d101d9134bababbbb8273b3e7f2b9d411f2702aa5b6208df89b9317e2d9541c42e2ee339f4988a49fd10310c3b3562775608f19145b66cb5c34046054878edecde7831cf2d8ba59f37467a525620f6c728434484dbbb32cca6cca8fafcda45078c2d3f2b3f27bba120fecf4dc575610d4229a0acc7f9498cca52a28105ceb7817f358f7db4582d9aaccddd98f5638ec5b1e45cbc7a86ba177178e9ac0d22d536df35e5d983e29fd24e654a1e196ccb60ea7c46efc720b94b39d0f86a0fe526a68b8cfb085d8c81f5f8dca0aa9efadc06858e3460e8e18cfd92f6dd29037017899a9b7cbe2a88f623383910d92d5c36579180ebdc884f95ca153a81e0e27eebfdb6315bf4bd2e9fe78202ef21cd02d1de1283fa8b8f07b76dd4012f09cf85d141978e4c8b0ca5c1ed57f5097f1e580b71a9ebd63bc907d847a4f233f60ec5aebfb7d059840d6d6ce2ecda2975ae2459ad4765098b69d0040c23db036a54ec11ff7cd04a06d50dfa6fe90dba93b3a37e47d1dc3feaa34047122dc329d769ecd06878ae8c1e912f0b9773e1fd2138ae19c70d3dcae23265eb67005e042d415aac143326cdb63c3e510632e5ad72a23fc46b8806810b02c1ae90c6054cd09b4521e61b7a606ce456d8ecea77d1064835b88ae6eeba25c81c943a21b365d98dcf830b070d590f74d5c20098ad43b984220bba0196bdfb4bc9beeaa1088ba4d307ce6675b8f1f3b96d9b940b82cab9f7f592673bcd4b6b4291ad1269c710d9b8b0abc45ec7ac7a85a2560a8f3494432a8bcec00a73fd92046ec9f35b22df7c065b2843bd1a0c3284c47b7c1d47437a70136e0f2040bfcb761f6f148eca114cc925b37fac8ec002a44d804f0c470fa8498ade2733158ab1b913c037c87cfb4b92292f790281b4609bdadee8b8c8fc23ebc390870bc157e6cf8e17303148b2a60591679d76f096378cba85a15f07bf357a085b825fae9737804adf64e537426242861c1eafc08e3eac8380ea3898d9d0a553f53374270a4be45a7fdc088d3f8923ff4f1779cdd4c2ee98926b855985b13d1fbcbfa42eacbfefcbdefcc464e88f56aefafd74314526e408b3b23a434c741706dbd60926e0db4f99b7d8b4b8b5e2e2e2757c9bef8567be60500096dd4ddd53b0be674f347f5d224f569a8cfeccaf9e80dc5125931b737873682cfc43a62704b91964ed8096c527255283c3fd0a6b8f6bcb963380a91e2b085e8dacb81e6577e54288f8be899b6b1f0c2a21672ff176dd1574d6bdc98ecc1848b122994e7a6edfc8d29d62d160c796fd3e5fe91772476be0d676724f5a27007862e0764ef792c8e8598a79ff4157d57e80756e57e29a5cd64ddc68ebd43255e6f8c3cdf60f548f9f98c719bd7d8cace6fb37df13903322e538eb19a8ce3e30a37e7341e7e8c511a27ef302814aff8ff40aa7f1e8d96f10cb4991815fa9ab3bcf7cfda0c03adb5e878ecd99f3f40d31812b34e8034a5bdb6dddee350734a9f6ca88648f63aea30722ba410a8cac1f7a58ba319b1c3b04887e23a85c2ac01397ccab62f1102c705ee10bb878d86b66198d5550233ff8f2ed7f3e22d48bd800cbb600a97fd1bc4adc162050330f7b1942b6f0b499d8e423d03e8faba442407dd8ee0872cdcbfc15e2733dbdbcfcf033160c6c9f6384915ddbc2284d5aa34f025ec1658b221e9efb94240b3ab170e633ad0eae39ee9ea6cc1898fc0ddc662319c322ef2d0cd714b8131420ea6c8cbb1c94a49cc877d88913a9a084567aec4fdc0138b601a8a7db0d8190964b867da451ad845da51ff99fa41493aa927000cd5132b0ab93bad88a3e68cb42430c0e3d5ec93bd91f43b260f983936f65cd1a9b4fb1a87ec8ba3b1ca85166cb9e1a4ed2a5d15690d94e798e1ed24f433dcae76d72e5537f6563046e38ce709ee8b2f600e89583feb492df6f42aa17bc3cebb99cf97832c72602813a5067296e434506ae063fd5fb0b731de2e367a8d130615c9ae86f884fc60e89ec8c8191740f861cbe56dcdf5c21256106951f651ecf6f1f34b57ff1171b763c1ad4ce936068752926c5a4845f048c35a9ed64adce76fb7a34ecb35d5a12c05987cb24718576a6f9b78268f6090c4e1a85c954b504bef34f75227f67b8b06801201887692945936b93851fae203fd54d1e11f7c24334c5c50e5f79cbc3d0dfe7a8be447031e4d00526ee6903409191faaf0e4cabe006547bb64060fb846a21a120d926fe4744941e8034d318e46087b91129173cd765d36917878797c0d15837fbda0fe2206d8ec71b265ddbe4bce9bbbe9387475ec2f2ef1302f060c48db264394fa80ef270c2c09da2d995d8a00a4a500051ecfd06d6ee59f16cf4c4308562021ba1091b63506e4d2496cb0f8274e49b96b4d120e815bd508efe975fc988638e3717a514a238e8b95ec5d6ccf0abeaaac310015763c49e49f7b1886b81f46e1534fbc28ca796b233e820b4fc0e6724bd8614055de6ec312dcd1760164ba2690ff3b61785755c603f7e8b15ccc2d7a5585124895786f69755437f6aa29c3eac0666c1699376c0b467a1e1ebf175c07b3ba2a2cd3f78910b9bb6a564d84c1e2782c73a5422c980b68d1e6dc016f0ef1156bcb41782453e4ca77b2ad3bc2bcc1596fd3ed4b0d6f16243c14186b4ca764df572edf2c3115afb12f730666ca0071549d07a2ca3ad2c20a1fbcccfdd1a6641edb2d294d819b292a5e40404a11af20b65ef638eefe150b9bf5bcc73317a6d60e410417724113a629a94f231d15aff1caf940f023b08456a3b4e3f780d08d93bdc58144dd3a3afc3df3f96adaa68d6a4a0005c1ccdf1163fceed06346eb3e622bf2d62388561442d825d597af6ae31726c156a8121ac9dd171d390e74603481852042909bf596f7187b76cf6666da36c899ba466965e802722751f6997754bc2f372a032a09985ad2428bbaaf6d81ee99c9bef3019c6582974926721629d4321dd289fa89383fca693a5d4bdc60555225f11566392ee259c6546bef9ac1e26b8241ad9846efcda391e160b99160b5157407f2e396e97bd163c9baf30a45c887cd6a4e1735a675374dcfd364c1ad5eb078d511a9bf493f7d54089d36915d74a6bbeda99c742e9f034b5e5340b4f5e791b6c987368b5ca6c019733258df6f9f3159d2a298a5c1f76e51f152558c1e5da90deaa4de403f20ee8f1ca479ad61394fc810817d97f9cf52eff06c2e037e3cf147f783ca5a20399350d48e50ed349958706f4bebe6b9db4175efa1016997b88b4b7f7628c6a42c62377d4ebe88c84f9401474afd9b490b760096ecd5d7fe9038a3eaebe08913395cad829669cfe05120df11339d3ffa978aed2e7ef211f05e2d26e420b6ca07f01322c7052a441b5aa24f1223c9a8def535b541e61b3f160ad3494afcd2f092de90a929eb9bab47a078009c9a53da0a2bd6527e73d000cc5431020f15bdd85ad5f5d2129ced3eb02d40804faa7fe885720d8dda80369d5edb5b5d45d553aaab4b34857914e13829bf64654c9261966fd63dbb691494363e6017c3f98d1b15e5edeece10d8040b4c6778c09a43c6e7e1fa7aaf3628aceed347072e5ff219fbbdb6eb9a59432a514520879087c087eea9ce0a6a445c1f6adb646981096deed8f665b8584a6f16f42d5c27611b2c216bab86d04f903a6f17fa24a61a908314125b1e812854419e1524b1eeca0422f5819fe42dc702e41d29420694f6cc892c6b54ad2685325699226699246a9a449daf4991e52b22e30d8d662adb6baaaab6d1549b5aeeacae3f16c9e0d06f33cdf30b7f1ab43678b1677f7f73c1b0cb66d2fe29a2f01a102583a37ae7a3e9dd753708dff9848645c86f4b819bddcb8eaf974de83de4396fd96224c11c66d2d13c6a50dd63a302947f877115edf3dabd94aa705defbafc0fb929c9070c9f361618844d28dccdec85a4bc39ab061c3fca3542ad7612b58d36030180834ea713344448406079124e9d30e188c460d56b163e4c8dd1d6bbfdad5adae358abcdbad180a75b1eb6a5723778cddeeeddddeddd55ae337e21a04704d02c09273e7849db9e1c80bda3a49b86646847b708c3f68731eaef3628c317a3778191ff24e172758c162dc7edfaaac51322b4c74efdf5418879947f7856cc5ed77cf732d2e75ad2e4d66e0df4bbe3086cbc46d3c149740385c86351249b09d4885091eb3f6cecfc8097b03267e2d73900ad3f803a15e71e71a7f990c060624ea71e3a00f0789d0502409d79c741c1cc956359e2063c7c8919bdb4837ec155dd1155d5b6f5bdc6273fb8e91232a2837d880ea27351d39778cd0aeebbaaeebba91c7e3f178b66ddb647c61258dab854bdbfb47572475ef1f5f481cece872c51797e8f58f57b024e152bf0cf3d35cb3b9a060d9c4262cae7b32f8ddbe245ce31f4534ae112e11e13ddce0bad7180cd40ea9ee1fe216a554a107a771b1ca42e5613c798d0ab1f5459683f20b232b028169fca7a822ba8ca476767676768cec189131992756d7bf8b1c63c766ee1be6888a27af79cd6bb4298d3436b7cf9b799363e40608f8602f1836e98d83a20c6d05d7f8b37f77715d8beb322e39b3d74d298d1ea5f1f361c2865e9bdfb2ce433d2c8049fe8ab02b768aeb2fb6c277380be622ca261e06cc654e664544b93e856bfc5532559b1ec60e5f1897d81f423737b55a4be9cd4d942b1959a462c7c8919b6541aaa591d28d6e3146598c41e246dbb62348dc464712f7bfa1530da54f5bfd12e0b569c252080a2023043861fb23c035fe096892049ac63f2626f46cdbb66dfd59c28a98b01bd3606ef464700ae4806b74b0b0bdd1205ce347e2916d6b7074c453b64ac739d8bde5e06f44b02d16f58a25c44a2592a12896bd8f8a0de5f57fc5183932f39c71048140111465deea6696f10517da8936ce628db19014f6891a0fae472aaec71d5c7fa70293dce5059760de7594f418a62937de755037a61b0f836ab2f12124ec0daedb8087507c018b715d72b1509c146b4770c913ad78ace2510947177089bebf6bd13b3c0b2ecd8f43c47c1881b81e6770ddb770ddade05db8ac068daf26f385f5da982fb432be6abfb0ba565416492d93c9643299cc23eaaed55c2a95ab93459290982c268bc9e88b40a2be5d8ba423227655aaa642f5e3944512334f8f3fd0c1ee641de8eab8fe11159b38a97a17772e1e995cef40d1e7274e0ff42010687aa0771b50bc71e32b358dff87f6c37ae383e223c2f5f7a468015ce3ae831376def0e6d33961411fde88e6ed3e9f8e54804471072deb9aace6aa2e14eabaeafdb82e8b3c445a27eae1b2b26871e102e3bafb8c2d01d11ea66c4597c4e80ed7fdc3d053d09c5630cdcca24e2eba2f9c604c19d7b84c76c5f0316ebb7a4b1458b82d8bb55a3b57aa4872ad56b550c85a97aa5659fc0212c51d8822155ce36f43f4825445528c4552e401d3f8fbcb5452ace213abf88395cb7cc7bd10f90b2e85eb1f12312b20f4285c7f1b9ec5f597ae488a3d341598c61f89d00d6c105f1012030b4078e0b0b0f2146b2e03c92c16f1e43b91343defef5dfc0ac699a0f7772bb8e4bdbf37a171b870a973a7fde876f7bc9bb255e34857f3983299ecce952c9298a5cb5ee9f23dd0099826922ec034fdcc85aac4f60c9a50b5e84700d3e8d802f37e25ca0f3d97305f38616044de4724eb47048e6630c10e07db656e13c618ae749ec700f7f1c3217ec4c1f3ce3870384cc1c1f3f1dbc46e33677ab673eff992f873df4cbf2989e7e337d3df8d0ea6d932a1feacc5c19f7b8f28b6e787c4f73cfdbcfe84e8bc203ee7118db866bec80b2b67cd01ba27830eb74e2bec0cf1bbc7803ff73fba796edbf462bf0621edcdccd03df743bae7be2453b64f12bffbb66f48f749fcbbfeda0bae9913fc4449b0f2c38f1f2b9d5e417e118b11c6cccc393723b0935fd8f62f5ac618ed17b20893524ae78c94ce58a3284e4ae383925239279d944e49279d91718a413f4797e3e278afdeff289fd2b05e19075d7a3f64cbfa23ca3869d41fffe3231cf4c3f81f3f5aa07f04618eebf30bdf47a5df0d78e76de639a2dfccfce49df7521d599df6ec4bb7377d79530e02731888d1859629877e620b2b35d394fe20b46ea279bcac38d8d14fc77d3892405ef711b5c79670a471fc8340f3f8b07c68cbba4262e66a24a46be63595758544fdbad63ce23b124dea4d6e5bddaa9452d64e344d94f2bb691e1df7e99eb0db13f673fd76338a10c491f02f1d9d73e33e54e4ad22b8a6ce561e0e085f5a1f99f191c6e9562f41eb86c25a1de9a6c56677773a2708052466abd5aa6e72dbea5665e5a4a7fb745252295b5cd33070358e5361239ae8ee09dbdd2f36ec58fdd04f9d0bf11c36b0f2b7207caba856d8a6545ee148ec0bf300c24a2965f7f1401f2ae26e75a249bd45d3cc20d038fd650b246efcb8ea2f34004dac5fd07b272f3fb8c2085b708107ea09d1017da148131f86289c564ed8b0b31076166ef4d30d0f3c73c2e9e674bae9d837ce830eeab43ac9205c43e794f5930506b0894dde924dfc1fef47e5368e9bdcdc56ab6debb62ae20b12b1e9745af9670389dce48992f4922378809972682780f083cbe4bd114d0a1203a05bb17d4d206f2b92dc89477151e1aaa8135127073f2cb6d5dddd5d02a6f130da861162300e8ee6e18fa3c7781fdedf112c4edb5fc138f1f4a526340ec7a5fa56d5ee9faead8a8d53a479f8cf4e32ff22dea73fedf1ccd37d8ee3681c1dccc37b1cf7f3f7e339734d7b4baea91f19e3c74353b85d889c71ce3a6b8c31566baddbb6515a2bf56a6dea0109bdc0355d802fd4c5ce90f7fe5c158d22c9fb66601a7fcf62b409a92159d56ab55a0d0912d0ac999bc66ddbe846bbd25ab7ba511a29cb91ec1163947d7a60437f5db7c1e23630b7992f63acb53d5c953b2cf921e435aeb919f5b8d9116385abc562514ae98e118ee3385624b1228d1cc755aed46bdc7024bf27d890778cd8e047dc66bee7312e51fdb07212259627386d8a5415ab2b602f07a5d36f918dcb5e047c87209114a35061d5eae08dcc11b13833839b629e394cc0e502b00da54c30e81fe2b83ce399bf654cb04d2bc1b4e536cd9af14516ad3355eb9c73ce39e79cb3d2a9044bb94b348dcf280cfafb84c2ce26521e45b3454534b138a8258c1dfdd01a2362138d11b949cbf5a781f58471e6a6985f718ca84d51c4b7e3e68d11b1a9ebbacf526b4c5f4f65c399e2b2d5ba6d33156933254f0e42e192d499a82e369c29192a6eb7613bd89952c2b2f763a6666a862849dc828242cd9443ecc1cb04c31022e86d94810fa8dbac3a6d0dd1ad6198a908fa60449115599755ad0dd56835b06e54b061b35256dcc609931a9592af48aaf1345054505166111316036b2ad11525b27c60c39992f9195fe39b9941e667fc4c7f43647ec6cc901a1ffa1a1f7adaece74b13c988362eac77e04c7f8dee97097d8d2f898d0f7d1200fc8cd7c144433403fa175e07538c6806d4d9108db62b581a2ff322206c979a9981c6c73c0664bec6338e1a49313ff318a8f1386afcccd7781c684c1c1f80c7c0cc0be0c3c1c6cf7c003e1ca60cb1f1338f03001e47674394d3e30a36e66bfcccf63a98a82bd208d1a1b0176d459a0304615de69dc6e733f38ee3dd0380884d315f030038be991a2aba7d3313a8bfbd0e37462aa22219dfa21bfccaf8220d9a074c970bf3531416b9309f13ad28aa60bef8648698bff14362fe068d2fe6852f89ccc739d113cd10b1292462538c884d34ba4fc83fb1a794dbd0bcc45a9c39e82e51110cae710114d7b7309b8856e825b8cf0d67ab8b619022184cd3c91b9b0a60cc142db2680e7adcf9c2ced4e748e31aff2e78c76bf1e435afc51d445664c52cd189e8839899a2551c74eaa22d185e60e387d479e0128dc6cac2c1808511f31d644b58a99adb8491b5a2d1bca4669309e54b644d9f59668a6be60ff38a0c08312ab028b10897933ba33839588b446898a9ae61a68a843cf3d08ce7be980f41b7f14d554ccb66b4ac4616685441e6eb2d66cc142d66be68b3fbccd46471892fbde16c4d29b11559363eb40c7470816052a4451a6f9cc8126388248e60d03fb23c92a612ce510c26aebfcf17d7b87baa65911669916524922218318c9d1d76967891365373ced93595a231900ed663f0325fccddd2255b9265ebd6db16b7d8deea768264b15eac178b158233078d98e233b789dff216d7fa28e30b1bca4e4996cb6da4152e6ddf754ad45056556ca122595226a7b82eafc89914b11536942c09939fef1e1472ff96b0de9a7ddff711c99688e5921549202859208b6e3af849bb9ab41acb8a8291d4bd7f1a003bb0d3e2d545f4642092011aa41467c9c0dac2b66ab74593ec275b9dcf7c06f35d0085b2e059afc2071cc00de34b1ee086f1d53188e8729b3006115bad2a31462b3706c125d6d7856507a713969382d6ba6ddbb67d88648049f1ca4bf67ac5dc30be5876854932ca8bf5928105856be2a64567b9fe339e39a145b9e2379e20595c23a3482b5255a0a638edc82bb8c6d5b1497f506cc8b4193edf3d0628066ca481c27e8e965198c65f52b92e81705d5eb9ee4956a758ed4a75aa53f3c5f9278339e7fc7c3e1f4ae9a4f3f3f97c4694d239e7ece1a4fe7c54bbd13c1ff77d2fc4396984b1c562b0d7bffe35e7dce6165f91f4953a6ccb84f29a5aaecf2ad79fce6df3bef9482a116167fdaad5da5048b6c00b03171b53602b08cbde6784f55fd5d5b6dabef0b7554c4d1516b799b05904977670295eff199bb348ea3e04044ba5ce28dcdc895ed8ed89839b152cf7d2003eb0fe7c398a2e5c6edb4c96db843175533eb5d84272d30da7943bc60d638b4b7de327aa41c77d463f380f88df6d9b1f3d2022bf51e55dc04d311505dbf160d914bf7faedc26fecbd5625d8f22d912c9c086b235a16cb096dbc46f7be2a02792810db727dbebfa6f5ad8fe30a656a9a852a55253958ae08d2c95c72a5315695cc776cb411f4f1c74c6e2e9e964aa3a6f015c5322029ddee7864bf1e30f31753363aa4a3855b2255b32bb4d617bf27d7860a76a968a3207a74a0a9646beb6205d30933298491acca4103369c42c0bd7bfb1c0a54d62c134fe52b8fef10bab85eb63cc18bcfcfb0726c9163321f55442515a45b43ad58598daa61853750256019b80d4af2879c0530906e36a3261c4759eaf364210aea726cc55515485a52fd9ba79d5dc6e5dffbe3245ac8a9815b1d754bda66aaaa6aad61d23071dec566db1cd0710a5b3810c0b103b302b56acc41839b2a7eb4fcf6284c42e0a892410308dff16d73fecb8e6b8c8c58e8989d93eb5562bf7484ecdea2aad9344d55250fe95db6a48786b8ee3aac771713aa83f346a95c2073a34a4c43123140242103233ac9db36eb14306084a2b5be48832bc1ff17bda281f3737a36ddb1a4581202f24923acfbbbf17b93d90cc2259d56a35e7768c7adcf8e01a121b4ab89b993bb2562a5b69531a69ec6ddbb656b54a35a9bb8d0eb791e2a43afb32bdcc39e7a451b66d71dba20cafb25d6bed6d1b218049120c491bdd91478475976b676727ba7622084a54241941a15ed2a6132e3d719b3058d2e4f6a11f782e993b4c9611ff18bf21dc9784fbeee3fe685c03055b7feb264dd6228df770e9c541ff3837340f5f010b1a87abbf936af73e2f22918424d2344e4ef390bdbc20c9f97c5efdf9ee4f0dc9e713424237d16914922f36d08a741922631ef5c8018670442dbad3f5e8d445b1d026940965512b5445a15c7f19455068c1429fd0551435a1729b761b896a169394db44978c0f36152342511445538d436920002736a42852acd5da900f360036a0a9bafa01846f7f32e5a05314d7f8e7bdbfd1c38b75d74db90dcb1bb24c004278661e4fcd43f3ec7864d7ff46b760352600950d23ab968a39d8b558cc16c164c5c513a594527631bbdd2dd7bbbb259df3935d8ff1c186a35b639ea73dd03be84b9283a9c240a02f097d2501fde73b8f6846f4cdf4c3b4e787783c0ff32561df9e7b1d4c3a7414f5c2151b6759708d47591436ac308a1a82dbc022699afc6bcc6d7a78b15dbb2145f96f35b7691a4579fd627ab0ed9a32a47bef4bd2d5ee874d53c21ed7e455589483ce84a6fa0b47280d8091c635f091c68d60c31fc51b273676fdab169817fb1702312708174ba5891bd22154081136e458ade2fa6701a3281445511445515477b2f1454a29fd7a924b112ee1b8fe466c9032923a402bc235fe9f13a4eccd49acc52d77cdbc702b553ce65dfcc51b07c1d2206ce855dc4626817a13ccfc2458c42e65e35227650ae34423e020ecd410d7047d4dd0b7e33c34f85e60e9875e60528ed320e0728ebf935a44a790d0a42291c697e8aecfbd39952291e44b348d37e1f3854e8450fd4287c29bb88d045fa2b71697a870a9fa1242446887a2bd096fb9b7ea87a2703b91edf5b75a9ed06f7b68ba66eb7113631e16e8c8c7a787a5dc9467e592dba8597fbe7ce504c4650f24b3fe39ce0c84d58a15e3220506e5b5a56585ae56d5bd7635eec9c27237fcee6bad5f3f97bc15144d226244af1a87da20d48496218108a126ec12fe7586374d7bd5ae48530433c7107694ebcf79b7f2e0fa3f5769d9f951fa021bb26c031cd000976ef00126f5e949f3f0f76853f801130c869a4707e1c67558f33084ebb18fad0ed6533fa127da4f6606b80083880cb65aad56abd5a8c7ca52e166a64c9bfb65737222a94a493b0f1555aef9175a96d69430c963c34858d029cf97e3a371b88f86e6e1fffcf2726898afd7e49253abf461060cfad3d0384780602b705919610818d7662c2b66f3dde3e68185a5b55a6ddbba3098f993827262f22122ab44b8f35c91b922658e48294084694d7a1b69dc00d37dc05988d0b972743dcb105ea24966334f3794b957ec8d1bb24cd6cfe7f3f9eae0e73381e8f92244081b322dbc61de22fd34f847e446caf20d43a0474185d828fecc32c4ebe9d787610a1b72145f50b9a22996fbeebbb9f9978076846bfc6d11d6238a5ef480f483b040c041e7bbcd7c60e10d13029e28fc206ed3d94c520cb20797e4fb1549001def36954b5bc771de8fcd0697526069304e7f29a856864b9e77cf15eb5d7bdd779fcfc7fbe1f916c944e172f6084bbb1f1a8d46a31df1bc1d0e8e1cece1e08d833e722010c4c16e193e88a1dc85a5091396955635143a1a7532274f9cf8cb10979c8e4236c0208dfafb236bbdf4a768042d9271ce398af3e5acdf10a7e98cb07ec31010d6e6c086f6ba7c8fef38fe7cc51084d840dfbdb0262463e380c1216c689c3e1de1022f5a57282143316d368aa25028140ab56d3b463d3a06338399ebe82736d4d0a76ec234fe12a3f333ccbbfba5773d3cb0a31b3f9e15e1d9ac8894348cb98a2d66d7218bb1d825bea2966d4606ef3ed7f87ff7c726f3b76ddbb61a5aafadc6c5a5261ac7f3fd963eccc370c586b1359f348ee7065c62e2243fbd22c953339aacc99d4892f1fe13758a246fd249999c359ba44bb6e42aca28cc87f2142a854aa15cb3fae578626722107ffe8caf03150eb5d08a40709a4e49711b260e63058b5a82e212f3c7631173f6d5da50282e81b9f10718d10cb771116f5785f58f556e186bddb6214245d87645527479a660ebfdcfe1a4c8c4458c1397300c5c8a33f8dcf0bdcb7db515bae185b0d8b88469fc7fa84e228a6bfc0a1bc6566c6d1f7a1f4ed38f058b6bf12d22601c04308f7e5983112e4d0528a0fbc25a3d5f6cc556addba8c7cd5cd950d2ac6256ad184abf9eb4165655a9ba9b9b1bbad55a6b7d52f7616dff4251b061e84a5291a8c4f5ef2118076c33df85e052ff4da4691ccf97a479f88d77439e7d5f6e92d4f79bfa85355f6e68641e709266c25f51dc2074872269014ce37fa35255954aa552a938ae0a19e260ff27369bcd763ce54fbc7054173f7913dae46a4339399184a4f6e068462c51a06c9f56713991e43f1a816008d6b39e71e9b3a104dbb7ebba6e3ebd61ab26f390e17ddfcfcb077d731e902888839e811d52a20474615b3509543525e9314cec31f48f4056a6e9ff421b2af7db047a04bd6d40e87b6c60d8ddb8efeeba0f924e61b1b5630efabb604abf0ecc2b895d389950e0f6e57432a14226985702533b9d4e261d1d2502a05513cc2b71c02c6482791d253cc6eb64820168827918dc98604c30cfbd6c900f0b9d05d3cc9eb0ef1a62d6a63b3d836189ddbe9909f408e6f7ac023341d0c2ed1cffe00e6f5a8f59382a41dfee5974d9f006c4041bf6cc51b3d96c369b51ea33cf65b67065d65167a08f500eeaddb6b1043b9271123b5713e7873bc21cd7a9cff67ecc2f6caddb3665a432da3ccd79d2d15698344b67d55552934fa78e0c82b147a3ffda5f47c4dddddd3d09e80bd8ca94c388c90f5ca629fd21c36230fcc0facf990830974b9fb95cea3a7dca9175ea0607a9128be408f5e00006232ceda0b0f23671d053ad1c2acb7c3a9d4ea73ef58952a7755268ec34b7711dfec5dfbdb8bbec642097fc9238ee03eb573b8f16d6efec9e5e3708d391449fa6c37d7d5a2455ee0b400dac90d98d1e5e2ccfaea36ed846c0208495c16934da9c3b68758c8e99274f99acc7b03559173f317a778a42987843d75b75606a24b5153bd7df690d6cd8ad4869b2586beb38a9487f09781e611fa0f72f82821b9c04fa680d0025712793ebfcc53b2b1e366458ea156b1efe138b834da4b0adfa9ed8119020ec1d9c808df81371555c9062080c3ad83c8a500029322292a06a8566383ed40aa5a4a452527630d26ad156abd5ad6e756bdb463d5a33a4b85caf1d2f586656645ec0b65497ab7dfb500f48e7e1b863c3762921c68a3cd142609c2456669f54deccfbf629926efa497ca2a2a208dd4a4214ed84b80385b5723da9f2ba5cd5e572b95c9c886fbb869cbc2e4e2754cb0a1396ceaa4aeac9e90482b20623c95a2e798de35dfffac562c1e5367d1b50b047e889f66f4fe9f6fdfddbf71ba91ff24e7df0648db06d65db28a5b6e3a55e05180a77fb3ed5ea606f2fbd1f5b1525c1bd0d6ea4af9123f2044b7d4ed70dc0a300bc8502c0c6a9410938416aa05d6c100c02c1a07f0d8d83a479f8105d584a382276da69d970c4c869e774629d4ea7d3e964b1405f1cc7c5da49b264151df9454a296badb5e6c076dfc1a049978508dafdcc5924e6863cabaf5a3b092781b5849429490e323b28d3e773e8b8b032791f6b50dc660726758d4b325c9a21272c7f58721d06256c18dbd5318fc13f0dfaa6312fd61cac5dc023c39b6133d948e30f33051b46d749a2b8a71c05b2dd30db33dff3f15c1679a28f8863acd185edbeb0b7442ef10a26512298c67fca941ef4e086edb272c376f911b1567bc2054ec80df4d6e73e569cc6fbebedaf5d4caccb89d30d252ad6ac58ff6e2bc27d72885061b91bf2dca813d7db65a579b812d207066740ad4820e42a0a39c42bd624ab5dd48adfd4502b4822a96b55d45c35b9721bae166bb1166b1e0fc735421c1c4284be6c00c1ccb4b1d056df4412dfb0870a0b739d524aa9bb870a6b61c842686665b15a739cac5c00b3b826a6c541eeebd4ed20ae7f6c988f9c9b90671fe010b007901841e33414be02140ac54385a5fc04834180328486122b024bc6daa4d06655615d094d11aa227473c38ab1582cd6b6c154890921726242c310a6e9f08cbd906ebd05724c08970470fd89c0000a283d19a6582a73847dfba9967e433ad50d79f6b2b6880d2152b013f0dcfe402e794c888cc562b198903977787c60e6a992826a321b89b551eff40cc1728c49705f9f37fa7f798aafe2adc899354a7b38cde69b1662778cb17687c57a37ac4dba35c6186387c5c65e71e9bbee8574b0729b9614a61ff6eae670e5d50e3329a3bf6f6d6795811d503db0d36bf0067430dbbd6ff5e7ffe5571539ab55575fb93c7592a97f90684929e58e9119dcc43dc3803e1eee330203f27ca4b0ec604ebfbe70f46166f678a011e89b3ba31d5be5975365fde837bf1d233b3827288b52564bd6a54aac61cda55d946bb53614fad128ca6ab5f61f0447a366f58b4b7c7d764faf1fc6a5fe1fa6b84634a10b76b3a351a86d121c3721063d58c294433c220c93e7bf54ca32d7d8c993274f8ff60cfdbef4abe7a39f1107dddfeebcad4bd8203b18b1b65a6b592c101073ceba4dea24c65837fff13f291d758a76c157b0204290e80ca100eff0175917589066120d1a39187683cfc042b854e3bae806e192288bdb42827cda415110075d6efd520bcc11fc4990ab7ad2c38b95715e95759356791031ec7c820dc1eba22dd81094f2fb85041171b121087acb240c0683c182ccb9c3888e8fd8100e7f99c383cc9e93ba8e3adbe9597be9d8a45cf274308d3febe8f1ea973c5dff26820d2e729a5ffdc9d51722ac97cb59a8e8b2e58637314358ce4205961bde64a982c9cd424595cb598ef0e2e2f6eb543f1e1ffad55bc4729dbda4384bac4a9f9acb7c65893de186bdf369653942ec86ed456e348c56a415aef7f0aa3199ae80df5aad0d85fe417074634508eb1e1020ec20783ae108c5d3cb4ece628425d8bc765eafd7ebf5dab6978582e338e68e7c24e9288119638c2e9c4c32af2383fb9890679f1f8c2304300ed83cdc13b14946c4261939ce711cc7fd370e17f27e8608f41f519bac884d9c2889cfc788941c9111c910292972fa3cd853dc225cffb000d73f2236f5dd3e326e9a216a1367454a723899bcef603ec7258ce805bb9e08072433433eefbdf7f97038d271a2d6e260f7f2e19182c5e16a975feed362a5b07cbd9f7201192118a3c56f00e8fd6788580121cef5070bc0a53700f3601b4a039530e8df34d063b8eeee340ee42ddce71f49344ce38f802b15641a0eee73d41d5686877624e420d708f1b8c5c2e3f1b0876745aac7e3f1783ea311087ebe1a699cd890675abc97306157b9eede00506fb9a410278512383f9847949b273a52ba05e5ca78fb31f67432b90c5ba4eb72201084020e1ab1e1c80d48224d7f441ee794d45ab972ed20b88542455253a9b5d6eac9c0846b9c874a7bd23a6e93059372228d8eeb6134185c0069d9c0862db3f12f12699630e908e7c3c3033bcaf3f5ab50a47893277de7dcaa7c00036193152690538324f4175360d05fcdc3fb0a4c03c5f5530db8d0b296a3aab85a2d6ba18e5454ff509d340a2593c96432d9a8879021a998d5687453c4061f3db8cb4b0b6d853a236c7f884bdea871280fd73fe4031b8eee0cb9949147233a1a8d46a3d1b66db5be55881c63c766668e0fc3e50771649a077fbfa00a1760a61c1820c41096609acf1f120117f1174435780cccd26336123bc03cfcdd3fac2132820d6faed74abf9b1b29c1ea25c618afb07d638c3146902cd65a6bad52f610620e7d90213970dcf80c001036e4fd33925e003f2f2b43d7fd8642a15088527a84945246dbfaadeb6eb08a31c65879d63ff817e19201a4c93ecc69f3d8a8a24b72e474bad1830ad606f0869f3f810e97fb7e90c83ad8fd9d534f06f91fbbddf2f35d07deeed37d1495a0c7f8bce78a9d119f8b9df48000e10bfaf682f0057d33de8f803ee8bdaf0718a0a75f1f06e663be1a693ceed99bc17ecc10fb319ff7cd44a0043dc609e483bee71aef0b6d002fa7c3fd70a28f8c7ad9ebb82f6c6c62fd7360439ef12039e06bf57ca1e8f6733f2bc27156c435d25899108ee71a219e837630a71466446415a6d66a63ce39a70e9348245598da9da304db452f3a78716fcf8b2ad61491b8754422e97dbc17aee99085ebefa8482272d2c18103a777a8748db805356534020000000033150000200c0a860322e17848260a728d0f14800c70884082583e1a48b32088611ca4903106210300060404400433ac14053a62ebe1d49c3cda53fe7eae98e3e643a94226bf60417b85a91e3e638bffe5089ea7e763537b32c3cd1b038c3ed906023efb9e63f336e064a8a5567cffe1dbc0c094a9331d30f3b69c1fa8766c68dcdec5dbf79c271ee0c74b7ffa91677432bea0e7760a7cc1827b9935fc8bf91b5072e98c86924426c89c405bcd9ec76dbe2abf8b798d697cbdc896c50bd10f35b60d3eeee2e3a9e6833d8c3fdc9099ab586de87e245e3dd53f5a77d96d350a46ee4a20ec638faca8d75b58722f4c9d5bf121a79728d4dbff51812d25abf793941e0c2a3e16ace0527081eb1399986398e835311a9d843c292e10d193638c719ef7cfd7f6db872fb15176b6af55a7c41259774b743e7789008dcd33fdb5aeee4c0821d8133f78bf84d086cce763fd2975d25249e98900256f18c87ec3ec65ba3796c66396f3d40cb3e4c9235fbd9c386e20e511a6494adf8484fec0ac303cc9fd5ce9de955f3b8c3d890d84fc9f49851918390692050b930aa0579e9ad95d4f67abac99a2156e8e93dccbeff8726d19dbe1f738d5e6ed7c81f89e00e560d99582d3511fd4eae2c29a878ccd80c50717ab9af4827868365075a7a67f4f981646cae036e7d550443e5b091d11a8dbb68935740a15ed5daab9b4b9c564bedf47b8c41e2b26eb9f4fab1ebcac8d42f858421a2789cb6abeafa8603cd6dc3ae1df90ee6c4ac2ee834dd2688f12d65da7c2fb57c0b5720d571f17e5b89b64333557a8403c519ee0153a81296d23cb115ec9cc8faeced45f44fabdbb023c37503c1bda0f244dd058a0871e98bbd4e82b4b63b49ad2739856ea36ca901473eb0b125b68361cadce9e8fb4c36654b25830f9d53498c9060d377291e29da82124d2f2f2b081a8f4863e004b0616448ec2caf20752c8ec48e23aab98bd5aa73d6e853ece07c8aa7c44562ff00118bb279768075bf267ac182846526963eace6824eb4f159bc13b95b78e24f09182615f1f6c8da9cedecf86c906d856644d1778143198c830b1d57beb955188d86a275864d4f5fa80a4c72e30164540087cc42ff9727c32262ea5fc500adfbdc04c1a29e92a9f226a78adad762c357c9c06f4635c001d00ce22e486d8041ce8b8063ddf6937531de8bfaac2e6631107123727a9dd72003ae25618b18b7d7895e442553a98a6bd0b626aaf43ebaf5713efdde821caccb1fc37d15039cdd5081133f990cdbd4572ba8199fae0fd85cb60836e247223e06cd98b1a2fa39f743322774f5ba60978abce004c9f467bb2bcd1b1d1f16bc0c0b1bfe4380594a60e8c292610bca3f87e4b333474b25b02f8c15541743895ff0d42318117dd915595f6cdc4236582b62383e04de142221d853474510a33e68c682aba033e40b58ebc76bbc6290c30773b02cf76fcd1178b3a2ee7f8e8f0fc7390ab6e209eaf8cda14e00a8d0df50fb9901c367b8d55d2671cb402de4f72c2e7e2287ffdb07044884a22902a1b17269804e76d75db1de3187ae9943411682352fa2fa673fb0b818702808f2048681c5785b1afda62f312e6b7b34085bd8e3327cd5a3516ea969af67a2efb5fa660a86b841d56073fecdc848b0ab4820cf1ba6a0518546fddc3aa69f5022edec05965ae0c3dc47e8a6e85fc73ac1b4e09da48ed3d53a56bc3688a032d9ee5c8a52c55e44998a3c187ff1d5957efcbe0dd644cfb9adfe76930432b7015d19ac16f64b416e9a0b4d35431c27fa157c4d6546b94469717208a9ef5e444089ea933e9cc3c016aa0d2d87cbb90bd340d5d291e694d283f019bfc4f428959d736f5b64ec3e26bd24ca3c5f47394d134b17a824e181534f7c4c7016c5975e862d77f7c17203f98155dc4bc3dde2706189ca8a0f30cdb6ff0b7ac52c187aacf776aca195613a013c29d387817a57895e7df921f24384c56e9bc980f61534848c2bf683912224128644ca8599eb24a5afe099b80a72846470d30e9667628615161a4e182ca3ecc012a5f463a792f424412cb371d5289c610963613d2c89bf147ab590b596ad168d09f959ef77dcd554a927e8557c038723f857f9a6fdf08cc80a557b738a86981da9b15998db868ef528d12ae19edf7e79192f43b0030a00dd35ae6422ac970c657fc6f05d16c62d285b56ae153ba5ebf61882126f6416070e6d6caafc044f1410cafcbec35560083280eb41ae14101b312a1f906d0803adeca8d56e3b55c8b7d59082f6f0967ca9c3013ee94d68236909f3592ba01c801f3066d9e8f53db3ee9917d641c7afabdcef143a35918ff1e0b3ee042966632845372cb10a3d3d7165f0781b38fc300e1a6855ff14a54d9e0b85da30c8e917554f58acb08ce62b3b4d0f0cc431fe514351b0dcfa6ee4a38048fa8e95ccc4ac6a6bf7187a708329cbbfb45a20400f766dc5fbe3f8bd6c01c11363d1d5403eb63a2e564ca500c7c5ab9e57b63b842a4ca541f88c27623994bd1223e82a53e4ffd467bf353255ec48c98d1304d65a0bc0bdafa55e8ddf9fa060cf497c2745543ad5855612761105814e8c8dfe28965282f1121502ac17f9e23fd242b5509870abb0cfc3cea02d28700aae84c53c47ee4bf9f48611b8c30af99f36881d16a91b2626e9fa5b2fe34126128a51b518ff14868931744b9cefdf7d89295be661474a494f2305e25a17568dc11611278160b9ea49ac7aa0c4f9d1ae59aebd032ee838a6fe11299727b97cb6f0f3891bbd69cb48535d2f68b8e28d7a7e27b8cf0b8d8e20ffa142478c06d8a0bc22ac0d7ba8b3afc491980c809b0eceec3fd899fc402108979470025ebe8a8a222f98194fb3e9316e16ef9effd147df3da0eba96289ae5ca9e362b9fedc74695958a965df4dad6b72fff8d05d4a4be04e5dcb923077c745526bae38d9d6774e4e7e63afd4922fb947b0572807c84f889a32f90fb771a682a9bdf5155a31e0986d469b0e69181d19624d0ae384985681c49ffcb2e141ea8440f30b0d206d9815d7ce97d04067f1001a7f9aa4cea198e2bc91f6310bbd35cb4658d06a1e560182dce36c20e3ce539d4aae733fbc4a2edb6a1cbf13e18d3c52332047fd2f87bb177025aaecd21a3d78b3f859ad953338f7d710c9944a348921805137c51cee1ebf3b7abd2bd2eff48bc6c2f9f3bdd0b32de2ab613313936e325cbe3749ffa7a6377a01532bf8303a282701f102eb21647f7cd7fe6703cf7f5a0f1fbb0a0ecce432b02d948eb1bb99f850eca8db8a04a2a42df4ae6d8640c26f51a6926fbbd67a5c6f408c0d2229665949e909f22b189f71e1b69207e6e162b7c085dc9b1f84c419878845a611d6c7e1500a61d132064056bb5329615241cd71aa62ee67df9700cf1ef5725fad2508f59f41c94cb7ab6362247b7d9b9914c94602e602c81e1e69b2746dfbcfa05eecd43af1277f83745bf2a2bcc8105fbea6e82656bc44a25427fafd91311c34049fbd38599b17536390b654c662572d6180f070a5e1a5eefbaee7b186f2a51d0f94eb86423f7049c09867a6ba0e8e831c963a0895b087d0a72382505fd857e22f100695c6b2db6d500b563e0d93810de15bc602be8ef3e3e953f6f5071a3cca42145469abf001a1d644e6470733ab6f9153268eb85fcedc950150ede8e01fb480db574c85a9a2f49e5df326c5bedc8ad6e28ef29d8fcf5f0188892809f680888a9dc93c51f3f66d98713b45e19ce33b05c4f11d7bb1fb015bdaf0629ffc8633b1a0fa3aa3800fa003a7f72e4a60588ec8743207b6f21cf9f96b0fe20cb33d3e5361989f0cfa05ee8ac497760cb21e4e253876ea6c690fc812946908b79e8a354dd2665f3415cfa23420fee8b62d1743447aab966c99df93050992cda4ba6125f713a111191a8220b177f575b71426f3c25aea18ea52b6e24be13f90de47ccec53fc53d328697ffa8601d6f804022c3cb68e97f047cc83abf819d3218d36ec4d9951ade708a04561c7eba07edc995ed404c7c1b7f174246a2509c5aaf325d3ab9e467377de354d7d1802a79804da159f2fbe2af88adc376df0046f15a7c6bd109475608da1dabd3043aaa2d9fd190f62e131fabe00eb1487c39431aff841da124546423ce668fe09ee5500fd5abaacf527d06e5dcc128e77c0f530ccaed2e9c9d4b0461c8cf5af3bbefff458dc6aa2eefefc4f69e522bff8463d0343815021966c9f5aee60e025d943f37baeca3e0421500081742a80c14d9ee95d07f88d88e62e88640803219da46a860f1d8aa905b12356aa842f2d8e4bd536e625d3488720018d8663186c8cad165e9f3e44f6b6ca5da5329850b9332056d0ecc5c1cc7eb691c14d2aa9344cae648988be775ed2a5a3767493f9ceebfb7e54268d5c133addbd8c83e93504f758d452df4d7387168061150bf8d1c2e562078c4ceb31eb453672d4e18c45a5c7d50b3d76f27351715f300aa0507b2293d88489abbce4edea2a30b1295927f983f4e9fce7b547a49b1f2dfa38a0bb207e1a3a12d89eac800d15deb523fcc44cedc16afbc04e160e26662b2da8f05d238d6b81fb8abbc5c40e5c7ab175242d54e20fa0f839d54b32ec3ba87ef63e407746998e6536fa169dfd074af8a30a42996c6bc6f155698dfcc3ae14b76c556f0c2e790ac3ed65101d3f746a62e45d12556afc0841c038a7f9226ac88bff4c9e13eaa2ced3719a7a6cdf72771d3d1420cf79af1945e6299988d9630305f06c1643bc4a42d090e97a460602988dd09d55a0f3338ddb4f6898290dc1bb69701dd35b1457e1a0170b56380b06e82a7e86aa2ad2dae523308089e49c7b499da12a5826b44556984b4ce04b44412848a638881b2275c58d00ccce7200eeb5771b9895f0ed4d841fda00b28ca8cfc8f9a3249ecdfc74398fcabc720b8d458b3557ed9fc641f213abf5a7856f49394d2cb8b1a3196f55fba01a9691fe8d5c96701cb0d89f20245c76fc4ca49b6467f2a233142aad5ea75b0b4599ee4a86bf48f546db9f59396f73a836c176ea6544b4a640d6c5cb5135c29dc7ca942ef01f1e9c93e1520c961a1333f1625e04057ffdd71386530417f1a5d7a7b6c91ca2c870f23eb8cce3d44f66492c44a8f353f836afba51fc3d64c372c2e8b473a721254bcf0bfceff3867966954f999a814c4a5d0b0b154b6a3aa5f83770e14de14c8e9c390d8410ff361d5f0838dfe306959254f62e48adc6112ae26527bb0062c12ba5efc99a6fa8fd341bca9730ca51a730e8086ddc4bfda344f91bda9685e9867f16964af46d2da38c4fa40aa814aa99d13749bf4ee6d155d113be71763d80333ccbe9ad85090b228552db7b5bc3851cca82e80705b740df45251a2b3321d7c5ddb0fc8e064c802ad6907067bfb6d3ea8bef3726c87bdbef9f5cca7f30d644a1f5919df5ca9f58d02dc53007b2c001b05008efd46bff4948e11197283f84526e333ea5949f592a79c9a487ee2deaf5aa02d976a25db0710717faf8b196f6df9939ea8f409defac02ec7e97fad3500286567a1fb4178d785b85619f23ef685ce551434af46516459e00fb1c5a1b88c1ed57300fec042a57f70f91aed1093ecc1f4af0d1e34f494adc503a0a40739769cc95808f2f1e88520ae5b3f5940995ba243a12576a917d1beee263657207e7ad94cd2b06b3068c011c60fb1ff9dfd2c07386d0aa3c7f461d382f9ebb705aa3db04f03b651ff004c518e63f7c65baeb517b0845d83523f57feb52cd31a0dc803ebcc521b34e3c58081033b77650efa62e9187d2291b314604b21df488e64f4990c8990a6f36a9367bc838d6719d2ac2d1a2d7d667b56511b37271a94e6ca3caac8e3a1d013a16037807ebcdfe18bfb9e0f6521cb87e4fe087283a3875e9e60b550a0ddaf9eb2b672bc3d3a708b915eb573e4eb36cd2b7774b21f8a3504794586673342f0e8b13d13e46855f2aae9493fb020bdb826bdef0cb42a5aba683fbe60c48146a1a77da9187a73b833550c8bb91060d970ac3d2ec5757b6353eac66c81f4a8e488fe6eb69e88086ca3633b5b3655a07c8dd2ce7cbbb26589b4b8f21d43d723f80f0c7cd8535217eae639934e30caa982bfab4a6c3b38fdf4e163110414168ae9a527a6605160885dc7199f8cc3d9afdf7a369f26742ea7df8486badc6bd765a2fccd717800d46c36962271b06b00dbcfe576984c58b5e7deebc5b0d24608a87799298632ef373118d225bed47cd647e298643587c0a430b01ca837c52e0788df4030c6b52b278bf8b5847f8498a9496aac1b54815cfb50c33e5f0581ddda77c7d95c38ef4d9ff0268aecdd02d7b164bdd7267b66e91f0cb5533ba3cd65840ae0303838d390668185f0f8b6235ce490134eaa7e76ee062c82999e666f30ad251cde26fa4aa5746ea2acbbc55ea1dd785d357099c6b0ee1686d9d4942aa96081d29bab3545acbbd2430500971ed86b1f0b2482530ed2ebf14f7bd4ad3ff94eca7831fb70062ce6731ed80454ba3bdf7475c4b919f13c491ccdf5669416d170e0c5793df76518e18c3772e91b5ef1c6095fd9d4bfe0d973b3666042df85b6e02f1973eceead3aa3502498508f7c082d764a1926122900a2c20f4366592563d1b55f972488987214d63853a6036339fc387248f9da2ef7214e7991c23a16b04431468d795e3c7b2da99257f678d231d8b89cc96a26a68c4b8d82a451fd8ad2c79546402bfa365c58f8bc31137fafdd03be7cef9c594a4b7f1ab17fbe233a665c1f071e5a3073c1cfea76b9b1252409bfb16ace86d006880d08f0a035f26e689f27a77e7430d41b77785b56d31c307d2c0b4b1a33d8ad0dcb042a2330947513140aae52d98ca629b717ece28ebc04fcd85c549ec66f63ed8dd952f1aeffc971f5469330250336e774f057afb15b224c07716bc2b2b33d1c490a67ac82f84147c54181cccd0fa76f5d3cac128c8dd781158ffb581ad87c8e0abda6771283d6995cc2e27a78bc28f9dcb6ab478a95f01047a55321a3ba8b0eda1db2d9e8eeef611a0e0501a83cd3b17567dd1888a1f0aa7b72c48fcdde05c13e5c909eed65b6fdaf0ffe305445d988476f4d518566aa03dde53ea7c45cd1d63cc700457b20e7999b1b04eec640c4e03c0250477b4c24b34513fbd7a58eae9ca9daa3edc905d52069886e01351a9d344404b52337532253dc27b3685bd71cc26f8626e9b1b1f8f0d51fcc83cabc8592099d3241cb7af063e03d71f688938becf2efc9160494a2ac78d5074e928382d6412719339a6e7b07234699d5147c1b8a985495bed6f49926844b2300a0c9f7aab8445de6796253f54205e446f2cd0113a8c3ebb7d3e57361f03cd71a21c2319efa7da6c0f3c417ae916fbacef331ca8196f8d104e594b55be7f9a9a11cea92feb65579f2faeb5d2d2895044ca2979969e3b65d790a0d0765aeeaf254e1370465677b6a2366602d1328664f23693de008ff39441030f1150733fadfeb6c38cff28519b02a145a4c6364d05a756b1e78670fbddedc947e13b6ae175fefd15a10564252275495f6b34c996bcfbb743644cbf89a69b252966f9f4047f5f21011a90f85bab824dd2974a5125f8bc6ad6b4a80090105e6f8342649fcd4da0d86ecd4984e72783f6d1e657e3da8b9cb43194c59fc429709e4e5023e8bb5c50e864d27a11a6d3742b5acadc4f23ae21456b9020ee694b7de5663a7a86670f34d5217f72564eae30bc40f510c2c60b081230adc5c2aacafd1e66f9568b5703f6446c504756ee69733fad92e9aa689823d0a23b97d46b99612182c95bafc9819ff1067cb41922db2dbd3ad5bfe33b48767a688aa33f393abf4933263989a7a564345d7c83d0548487b0e09649f112201c3ed470109dec0296820e1505b76575d44d4befff5dfd01c1fc49b010593470761108bde3a441c7c4fdaafb190b564170de9ab9df051e7bb0adcd9908e391dbad9f0ebf0477675ecdf311799e69b02ee8318bbd2a025c3c0edaac4a3592b79f62a4837314c11201a84f31e9e6d958e57866040a5f2bb06982cce859b06162e7a040a5b33f2be5623200756880a328c9a19ae924fb7c896f5496a60340abc3125eec47590fb92dd03ca44299e8716bea1d047d06acc421483b6e41fc46a4a745e158aca19797068758b833d51a5179d53d42138f6381735cbbb0c2b54426754a62afaa6fa98d0cb4625e0b03ca0a3d371cd2e6dd1ad6da6d2bdb297f5f13f597d5b7077ad97108d0e98ef8ad3321cb4370e8bf374dc67e3e00030171ee1601d2f95086414f8c70209dc1716a50219e35992df51d221d1fdc9a18a45e8b29ac81e5ca93d30d428216c45440b8cd926add5519b0054bb86a61b307c22323015401c2eb728da840ef047d4c2abf2996a60944aae607e0e052139c6d12df55f4e28505a16c0e7ac134af49afc298e4f7493270bfa502282bf2bd621c1aa9383e0a92d5858523b198cc46a14067c1e00d8de0b32ed1c531761786b9e130d373eb9fccc6eeb2eda5d8f75785b906bc1c05906e0097234487c6f9c1bce308eafa1615256edc6b6b87a2e70d81c86576634bba14ef1b2daf37a283a5f4cb18ae78e59b20362d53afe4710bcf560be1b0bd378dc7d95accdcd00d0c880634eeca564b4796895b914c7c506238ae3c395a68137a2195e000451995588909204a746379ac80f54b7755f9c430f7dac534b39ad28c28b99e8015638307e72450b8cb6d56c68b791582e86757a7cb90d014c783178b14b8bfe53fb1abf2a9111591961bd0131261c82275d1628a06024cd36e44e0e4fd5204894b6e17086a8c8ba36d0aeab3737556bc35642082d351adfa2270bc1faf0ba09d661ab6cf443caca1997093d787a97413cae68482221254ca6ad099580274db1646fc195b4b105cfed9fba2d3ccabff992b2e05a6aebb5d6ebc7ae40ddde56c86763007485ae72adbc1375be6925b889e3e02259bf24b1aa12b76f9ad65306490fd405938953c69b71f50d702e82e4c451a537200e3cc349f342daf3a4c3c7ccb0f15e616383124ba5de8ac5d60764c040ff56c415220aea6222300c018f7ca2ea3dc57d508c677b779fdfb326d77d64b7cc573d08f2d8d48e79cecd62f080f04b96580534a807432fe4e4f0433472e7030652a9a146b463f83bc36b46c41ffb48b1325c11a3538a845639d98c970e1445658a013a8d9a72ba36aedb034b621b89d757e460e32d771eb95782d6fc4a726c9accb93774b04d23430bc9e39b9c2181878fe1ceb632adbbf2df4fb39fc55a73fe78cf0a50a40791b2f94d84e756b7c29e6f7f8f0f4c44a87775423b21980066231bd3cb306d03a86ebfc70a38aeecec325b7f9e8c4ac1494ee261f2a283ad8a2970450d2cd5254dbefa371d98ba1b27bfa0284bfb1e4d069735b724225ad6d93912466c1a74f07cd9843d4c411fc05540c4777283635a8ba7322b10910fba436c84809e0c1cb773f53860351cace24da7f26f1aa05ae4c0a1a1cfca141c8d28c05a16ac2cb83404860d2702cccb54f37aa462a600e70546cd3e426f7ced4f257c8a47f406b1a0696ba3af516dd6e02e3d55df110ee1ad2539d3d21f7fdda85ead65d32d69791e0ab6b3be2b2a3f158a38b2d8a25ca8ceae350e29a573a3e1c28310a92bfd4cccaa5ca4602788c2936196e7b694c31001029e7e9131abc4e57be60c8d60d90dcb73f084ff7735d9b6b40e357f528f934cc700c8d873c4018b91b15293e280528bc1abbc95f361e5aa23a6df9f4c3ff3a5c6be857892e384b0889b5c844f5f0c63a698c04bff8571655ba39c0d6d98bb355d39b4eb75e43715665d93f7d840aac0539d367298834087915c53105f9282082bf20629b93072355d70ab1772d0069349560a1901db72e359a81848280cbeaaa41a6255212fc3c9d2c035f185fb6c3f0a95daac8bbc6b7baeb449199ca6e671f1116ff046fc4d2fe77d654cba12b157f2f217f4c9d24de8e021b95fc4bfea89bda26b27e2dff3956947cd72fe88c34e647c24f80a64f157eb3537a44f4560089e81ce39f4814c7c921a1fa0c5e85666555bdb78b272bde309a023bb70ec66ca3005144d6de55edea7ec7394d1b2eed9087eff331eb5bc5f92efde73c5b1449dfb5f8d822094a12624a6de71d2efba4699d16baf0571eeab785a885669f7a64c1b5cd34d43a2e27d12e5a726b993525c633796837224003c7b3602469c8e509304f21905f743b88340ed228bb1ec4ad54bbeb07e8216d08e31b295c69dc52ef205e8ebf038bed90be5f70c60bcda7782207f3db2650d2154d7d774ee622bc31ae36232a1563d1067b4eb0e46aac2fb174a39c0b066de4ad7ff88b6625f7d7db6823b3c919769223f5971942dd7d3c9ccfa912f38f6096ada99838387799883324dbd265f55e9f8b5d34c3cdc699464517ac47a02a9939a82aa997f3a37696f4fbd9555e4189f787f9f71d3d290864aac4a1cb046a33875f5b32e9096f6969c4948b49ef670d0c8add2dd394568bd98985bc772598d18c3eed9327befa440387134671d89c27a04348f8210988b37304870ae817268ea4b03bdab17d757423f12a239eab66631fbc9b8ab917fb0d2dc0b89ea17114f80e01006014f1608233a4fb1f11c9d1b76e4ec072518d98848fb2373b034060fe6b635dd7af23d1188d3873b11a5eefbc847ab0c8308b41eee2d3bcc5fb3cfffef4a800eeb7a48b65d6991fb630aa53788d49e3abe8faa94d78b2c4f9bbb2769d9501e8c5303be66cb20046b5d82acbe1496501670b2e05439b70e57ac25fb3f018d4d37cb45bf7c42f62aa5e1e221c6b9b28407e6e79f9cf6d1b358d8461b9ffa992b0d40fbb18c5de6aab2aa76e3808fac00e780342c661ad75270afe59adf06d299d15461ef5e081cd4e32aff98ba8f7e200124bb361f1b0116e66e6595819e6eafba9af907db765da91d61dda9a00156948626da81e037589f371aab04fdbe959b4f1fe0d267b311dc59689b8ced5e175a1906f194b21712770f3b31c4ef87a826ae9515df000e88dac119c99004266fc18303621595fb33b04b0b8f6306103924ff5d26540b087aa20df6d04adbb9c265cfb6d23e1de3c43204f48533b6d6a5e7df1827e8f8152c75f7bad60c0c4d3deb6b3de5392798ba377cf6824017190849b04e2e14d774ad42480797d80817db91e36a5f26841809fc418d6695c867d0464db2d4390f4260b4f1bbdde97802cae777bb192f8b093b3ebba2a865458d262b1a1ee4d826bed829bfb52369e4a13307990bffc22269b41963c938b646935bf28b90ab1cb32fb8f5ea03229c8ec4610733b4b09eb5997aecad42b68e119bff37536d9c6fc0e240892d488c8f4e26757f640d94d07fd7a4b49c7042f7aef009a8495af1b9aabaa32b69bfb611c4bb6f53958ee3812cf95c091b0f55220cd3c6d8539e3682de051b36c348243bb6f5636ccb51d3bb4ffdf525e36ca342d7c45c5e112ac40b02af57da520e2ff87c06fd575aa8bd9a2128b9902fe131d9c39a9c7fbf496b50aed58cea85b516ecd60996ff961d073b42bb01ea4b14b6b8608ed3901a7f29302bd825fb74f5054bd1660ecc654e76eaffa4cd88fa28caf6256ec32ceb2c60bc50c1544c0fad34d0a51effc2ed614630e777d126164013d729701fffa0cebd3f6ae55644dc6a0798e2a068fae33840a03f6468b5b22b60db75c6faf8c97e0f069fcbf8fdb60c9cc5879357f0188b68376da4d6a0cdeca36e729a63e0ce5f59de37e3017deb575791c57e9620fb2de96a7d8f21be0a7301939ec61819b8ae52abc17fac40ad022fb06304f71a40d20d7784408a4cfb10636de17817ff6fd862d7ac0bb7c2b640b5751078c4885f4fc28b803ce31b20267443e1251f6f248e0607299a9d912e73a18dda523911c36f840c7fd7064a245478505d30994d090c2291627f44552ce0bbc8a37008de2a018a38a4c55ec085301b5dd89d51ef0981523d72f9697d8158d3d030a48ca9aae08ac397545e443675925f75cf51cda139d3cf07e73397fb8dc520884214c292b43d343258314c471f4068682a463877f12c8581681ae56116198752e6735176004d5934c99e2f84f9072ee8d9d153a340fe80906c95d55a8c9cecc6d502255823e95d971ded6d2d6a1cea2f0496c37e609195b0d0e500bc15228be89f59c4d5599a4c5bc19e816d4f6067aff643c5edafa203832bb50cb18980704e560ad70f57546496b34f2d535c779a2a51596e8c7855c05c0e7cd9a8b916782abc2430131ffc773837f1e26bb650ebb846377daa39324f298956c08ff5cf0fbdf2a3307ce0213b375696e2be34ded5835a6a2d73825f79c92e74add5ab94393066d3821526a7c720e83f80a44abf231a3222f0dc7a4538d2f9141f7d5ca2be517827943108279dbb22385046c5ee71ddf7ea41e16b077016028f0a18f000f821cd83ed20afb1638be0296efb9865c816e6292d4416e165d8f4974c87aa9a1f9a49620494694ffb742a5ebe486c5777243355ccc4d942ee5aa512d80452427b384eb5f8dbfb2a53814d19ac1bb310b8e47e243e615d68d9bb18f33b6c59e6ea4cc0bc61a2a24672f23d9b1430d8953f7be0dba7805714cd2984c13f8a7f55932ecfb1ba008738515170f305854a408f2f25b2780a915575309959024221d8d7d3501a3538b604d2472a73282228eb7ab5f1f23ace15cd85aa28581173b847650731cad92b488f04a9cf1bddb329051e1c411aaca34467f5d1c6105c5cfc059d78f33ea865c8f3787b531c0603bb1c6374ef72adc8106690f86de4806481b7a199ffff55106eadf8e6a2349d3025e9c4d6bacad5b6c569f519f2e85771d601c0014c28c570c17659b9ea605010700789703e086fa669d91ca726bab52998ec221c9bc9bdb144304adff22d3738dd7328b4c19f76c16be3f76833fab29afaa5b0c0b15135d7c3a80421a150c48fa4f65792d9ecd9e14e7cbf07530f0122f58a1159e1bebabdc375861850d77449bbeb5ecdcad20f2b6b6619bb9e57520d187f8eee371a0f01c784d1491746686ce3415520922ff10b49cc4aeabb185a9f41c449129b99c51658acd5ee7418027c7feb40ba420ebbfa2e759895257576d9adae4119ca5949d7ca113a18a1e7aaa4a0f5574b0128fda51ac7f91453424e4550ec0b7f3643fc4f7ac1900803085384deb4e016f21bccc1007cebd66d52ecd217adfcc785d857f162877d41016f197b4e2f916689bc7d4b205625c4d348165d917dbb981816180bc59afeca4184c842973693b6568f36e477a801a8b4e6d6c81710167cffd5929afb761c2101acbf754f7a08583f33dde4978af1af3510046f0db6756684ebe3e1401d81c00e20f3185a863e216efba675453b202d28c62e3491d6eb63c7d37aa10035c85a7517795a330b154c7cb2f6083c136e9fbb25c36e6fb192d8fd5d55c7d99f40dcc6518a419cbe4e4bc143aa301705733a263402ace984fb039ceae69907c022f7219788edf9c91d30281ea6026149c39e2c208d6337e76289c62d7febb1dd5101338d03c489b6adc2a94708a556c6b3558e49ac548eb29cb428c0b584cdbb554f3473f478d86a071e67cb2857ac6331c1a99c89537623d4ade22a5b8bb3f58c066cdd5a78245b54d1b46c5508e9aa5ca480d6c0197c30ef4ec078d86c7c72b9b43f53201f6bcd62b5ab8d7b67b01078e005fb58ddf52c091c11fc63a04db4d4f5ba97598ba81cf668ca917e6bf1b021779ac9acc81b2ff42fb06b2cefd491adb7d616bac7d314212a8966e5c79f95bedf02304968fbb040c76d423533b96402fe7407535ae831ae5cc893342eaa58a7052658c43ccdf677c5b9a0a729ce66716d1e0e7138fd5713c02d3d3aaeaaf2430543c12610caceb5e667c3eefb43e9c4dde55220ecaf3df2d3e55fc660fdfd6d8f4a399394c1fad386e3a30945c406ddb95cce3faa541aa011b4fceddf4f4fa5eb8491ad0598cb7f35dab27f8f5d0179ee5c3ff96d970c92dbd67826be8b92a1012207fd80ccf0df4f7663abe11e21717fb20a130a4b7cc3f2a4ad0909836f3255c844d43e9eec2e91a5683962f89c0b8c5c7818e376b0dad205bdcce83f18f207c2e7fc4ea3f106ce02ae803ceefd8611c8b708b335f8c2fd637200cb678123037135e0f237e6f9f18b04e30947bc0f0ca60895407b3e65dbb7720ab08f8fa46332dfd0e26a4dece89b67229f4a44ce1228e404b179442133e9a6cf90af982dc87c8495ed2f55e69c3641533bb5c644f8dcb161f8373a87f8d90984853151252daf616c94e0576a0df0d7d8c8ac2c6df90eb70af87ba0ce497d35a1b0b9941beaa5f6f17d3866a3bf3eba92347c56e7195483a6b66dc4d504da7b010674f4197340b44bb4cdd871c20502fd1caea9b820fcd47c828afa01021027e538b49239b9a990ef86730c82a4a9372e956c04a92b2ad355c1b423d487c3cbc112a990e72f755b721057880a4690a8d1f92099757ba174d7d887f1d9db36756d051ededd04c9b5b5020f15eeda6e02d5bea38696fc277177cca7dafadfb0184ed207ad0f840e0d27bfa626e48ade790a78f6f0cd5a127e283a93ada77af832706bc1a66d87f73f7f70b26859093f05ca7baa3432cafd5a42981ace66c4bcec1404856bef94b15c0af397bada8e30fed61f41681050cf98c9bafc5f6b94d8e9adc8551a31608eb43ff9754dc6d389eb6c7eec194c6234735aaeb5ed3740c5712e0a2c4d550bbbd3820d8353c12a80089856d01d1f559bb8c3d85d141e571c7351aa84001f802004f06e06e0ee4f9418253194a80cae1f68f3ea80186a35893eac6c32be4dfe0731b4322014d8b666a58081ba67c209afea223cf0750746672e268094cb4b1464c4b47a6b3c00476b645250105717a8ed7b42809e35ba86b264e7fac5d704493bde4bcbea7f7852649929b2eb3f1633f1f9fbf8d7a208dbc9b3ab3dfb9b2449c00afcd03ba03660811f9b6bda0d92003dcef87b32e1856e5d1cabe9568beb8ca647d0d32f9e2b040f0c30ae8ba71c02914658d6b47e31d620e7e85d4c6e32acaa125dce72acaf3c3af1770bf1ec9a2c88cf20ab6be60c9a44b0a092432f54e4d31abd701d5515cad6f30f9a62f20a5b983f39fbc3aa7997b9e60fae514f7ca3a18312f396ea699b7362b25e2132c34c1657adbeed960afbb0aac991742f204aaccd7f80432ad058ac4062b68d5d16e8ff8953dc32adc059dc572baa1b7495a0cab58ea3eb14e0103ad282a56b91ed5d43d6e0f18f175c98e02eda980b864ce32368c60381a9912a4494a376d874de059734e878e1fe264c61451161c4d303c98339c530d2a5329245ee2f081964883cfe76601fecbd0e9cb22033e74cadb3dc5857a37c64b5e01c1d6f809f810563b38d000c7557613475297eafb8e2587192fb9cb048869ac64a48737d4971363c80a054806df784cc5ef30e083e9ac6eb8c1701b08495a832340270a4b604a9f2deedeadbf50a16f12aa03dfb2d8009028f3e9baf68732fc24a95b65feb3fd0c25e7b52b021a50c85c02830f7dd0729bfcb440fa7a471cd5ed864487bd068a0a766b5e15305934d2ab6caa449ebee6c51869a064dcdd418306763257289ca32e0a7be9a93e866f388cbd85fadb7d68f55e346b6f7fd5837b3a71ac6e245b39c1f1bf48b0937dcc0045d32a2c8f9ce0f4a29deccffb65d35af6994a24b743137efda26c028afc0482ca9034ef0005a8ba5ec7c450f486b84c65deab89d00acbcaffd3191026b01017b56bad2969c35f79fab7d7e6e555015ea9872557a551cf7636fa579cbaa67e1867991c93db45ec52ed559819a3701d6daf9c104dff2647451dcbdd51ce1f837a86dce8a7e6708abce62b85fd8d8130c8834c7446c571139b3df6a375b97a55556f7199c74787303703de9ceafc1880179a61a075f80761836b98ae5502c1dc9d860a244328d1a33229c3fd7b61fea92b8bac334cd36b971071f38aa87670dd178417e4ef29bab34e4a22685f57e1cb12c899605bef84494cb058b1458fd683b86517dfc6e142c603e48c6cdfe3813a65b8c8fec9fe86a0a80a20deeca116178e5ae71cbf4226aadb7098aa9a87265437d62583528de7ffb1d26c887e855de6befacfa6ad6ba55b26dd1dd27c238589144a2080d8a1dfb3979aa1c8e0e1691ad3911ba66b1ff705856e0d451f8720d2b05964fc6a11509ca76b3060107cadcb45fa3785c418348321caf7470dd5e52f756d89ef4642e241e06cc8626af2d0db5da0d76ba074c59a0f8496f45a1d55b61dc9abe52e250a34f6ec79cf353998e712c62e9e7782c24b68676813113269263bd5a36801f88c39dde6f7dfef6ea4c3972c4958fb0fe63b268dff39f7b3df2481ea4acf1bb5db76fba5c4981b7eab77db2c23de2b11e2ead7db601cd1b6f5e86b5e6c37418763555bc8458eff7e802b18335c72ce12fff68ee581e3e230af6a56bc0c9dc99921ebcbd1674bee1b902237f9ab70edb6b655e82cd7cba88b8e2849e2f15dd206721ad142984f65ef13f3f143e5d95c25e37c4672f3efd4393c0f26b5280dc330e74a704fd6421505f65a923a4b21c40668102f7b15dcd0f82938fad13c4747c591a3ee84725634186877219dbdebbbcb83b98118a4640fe93305b571e7e912112e63e1aab96867e438c612ff5b32b828b09dddc147db76cffe1dc8b6542d388948222c1956118a203456e37ba6178300fbf711de38c758531b004a227f1fdab53a826adf3e6329dbc6cc72f3ae57e566c502fce9aaaf7d8bd42dcbbcded253e6478429bf846db7c0185e590d391a1da5eadfcadfec21aabbbe9c8e97706c6eced6f8b1960f41e6086baa9c2f4385195b124df5825dab2e57afce2ce59628ef6df2f5c644de08071db5a1ffb3d061c3ba083c4c874bd9d040c87dce6d7039550afd7ba98da24f7fd96a07f44040639452d1e8407272a137c28233a650706cd554d37c5f44466dde0777c801ca7af1b3e29d38e2b32321cdf8f26d269a8c1dac3f5e95f0393d2fd0c1911bfcce84da0112d556df7b06748c14c66b1b2910840991a75184b669b0a83bbd65a4267d8cd65400e4adc7ec4c706a478ed4d5a7923e8bf68941a6f397b914aa2137daaf543c73f8415329a5277aa90f6a632d1bb41ba4a9da1bf741b9e640dea902c906f510af80843fb59f17dc8dee8f27c4b255c0338ae75918311eab7100518f536fc3f1927bb56e3b186a9526748c61f7cd768fa1ad1e4376c6dd8ae91391a8a7671ec8b446b47d8d92a27ae5aca8d20368870a731aa411a2ae493fd133ddfb2a2ddcee5a8b6ac3665a6fee0c64124f07339b24d4ae9ddcf0f46ec28c636ac7fa209fa4c8cfac5c4c0171df05e4d8c69b8516816b0df2b05862471f65a7612da344c479270a78cbbb54d6f496a2333024b818b0d21910658e6aad69ab2c816a8d9af76b2576b1447f613039a5757d131b2bfc37351642e3ae6a2b8b262d4160225f937190bc7bd8b774edd8eb2d12108597a1e5cb05213fdb0cf0e1458f16c74c71105ad80a14ed1680dad776e6213c2f7a65cfdca20cb7517802c7d30d25ced7df83a0dbce5212f427989db4edad7622bd731d9d74b62a318858e0586547395d772f5f7d0ab376bd6839b9c15be9c111181d0d5c3cd460a0d1273bbb818d07bb0ed2a9dba9cb59b0e839bc040bed2809aca1b069a83dfe6da1b1b800bce62747dd66a3d270991335d0a80f1fc555a6c52b458369db029071183296ed98990f888ae7240bf8ba8609111b01f64dd4c7f9df805b81a58abb8ef49be421820093b54efa715f42dafa6e71a205c7252caf80b72d2ae51c0c017d40218932150304b2bd548e4bb009aa5a968d182f9d4702f4141208695d230ae6f4c8f7cc28698d3b0672acf0df2c36e57500429b460cc89eec2846e14ccbb2c2d569121dbd8fac0f8f291c02e152e0f51889b02e88b8469ef16e37354f3f63fd038696aeb8ae5574334845ee23ab2ec48cd066aabfee7ba85ce307242abf794d2128608510bc60a4774da2c9074ca44623460ed6da1c958aa10a88deba50b1df4d783618d2a30186845edb4d44a24cf3aee719f0db34cba41ad30430db935c2f30d276f11e35b47b6315e82534d07f8b1696cfba72581e5fa3cba2e7befa8c8350d68315c7f758d87da0ca0bae484b910babc2f0153255da145dee6546d8431fe487998facc1e8a18b4b2017c0f8a993b08a4f853d645cfdd7f12294f6e65057717656971d2bdaf8846350e6a7b4db5565bb91a6f13eb8421f1bcdaba61f5da99fef77b9b8b6ab5dfdb0a1fcbd2624146e6c54efe99002a7d1ccef3a6a049aa79b3ac7a082c9a9f6702df0e8f1fd1c3e0907d13ca2d5c9943be4b5465b0a5922f69f1514bea4cee8368afdb09264d20a92b4b0d54a917b8554efaf87062f2e7c1441b8a6a44eea9fb61fa5d28b2dd1fd20e88281f1ff52d248786693efa499ebd7464b3738aced1f499222af7caebdc4e21c5893461621afffc7edb52f2f928747875a10593dc114836349b219122553d8acb0eccbb105349265538ff196a8cfe7293dfa81ba58ca7b7979ee3300d90c5393ac4a82c6276c57f930346deb7721b993b1bae09e47cd6272867e5b236920ac3efb9b9d6b41e29ec033a586071e5e53cb11e1a8293e1b49673624225597c49a63755c2f53f95e7c5d0857afb31ad163362c4f8535e2b2df837d64c0b6adaf017851baa167877f63733b6715993e3105d6b1a8f6202388fd5d6ce021ed6bd30f7c2145977e786dbbe968e032ee5929e65bb5f1f17ecbba8a587c29d69fe430780d30434db3f205e13022675a75dbb582c0ec8bbf00b37e157934cd16864af98080ce4789b24c40dde2d45692e51726f42e5f5c406deaf27b33c2e268dc7ae03f8d466f5a6043b814fe0a23cc85b7f6e69b6750e3b9352b34e78471f628ddee680be61a2cb8b371d324451181e54b00468eb1edd149feb6bd8b65621b6908b9d90dd1d76da5d212bb6afd6094a2398b2c54f41650082aa65511cbc89f8dd6b8ba81a187e288d75f120fc6581583d6502bb0b22bfa99cd1ee45bf31a3b9039587081cf28a91496152901d9fc965c954cc71dba5c4dafee67f7c0b2b62a1d7616999b27bcea58bcbbd55d7956ba41793854e4d2c59b51d6a9114142edcbe9a6e95cd0a1733b4c2ef80dfbe56c5e92d516edc68646f89ef271cd3667e0c3278c8f968e8020bbe7f51b65fe82cba0ba3104c48716d03d3975ff1046e6644b528c89f10624c1f99b4cc2936f268cb7defa05d49f15d5a82946c98a634d9004a315d50364ad980915a758b35ce3e91962e96199ede91d2cddd212f2f3b1efa465e0ed0626a5fea7652b4fd2aaec1183db649ee9504d5cc415a0f92aeac9a4afc1288341d8cf74a711984beb31a82fb605cbe77599fac0c08ab17929c6367ac54c9c547385518ce3a20ac03f700d97b38167f15e8011d92526a9d6391a57e297222d6a00a9be584d59ee91449bea91c52a80087942c66ef1ac5bbe7f38a3060b444552cc58a6b92fe4dc679542f84061ab27264ede6aafbfcbfc1d506da2b43970db032dafc7056419c003e7614b154482077ca1ddc54a0a446a8116a70f0c6c241ce2b43567bd8416fd29471544ae581740ce95a52983ec0963aa073755b73f0e5c12d796b05c6a7542d9e4e4d092fb8ee0dc89a8f5465a43e0a92c927bb32d16bd6055d2e2abe860eb4bbd652ef090dfbc52ec9fb3e86a069ea63fe3390ffba165177cb3768e7bc5e45d0b86b0aac27d6660426af993da77b80794d1532c1e20d8c52e89fdc370f8f1b087ab689df216285ad668f1a39985fdb8dae8e226e54ad723ac96f8fc4a731e0c4116c93fb97c2857ee985a8f60bfadbd82bf2b1d97387c0cfaba64beac91e72d525beff87935115581ab9afd1812f1e61b46d99ab2e9016caa2b6b11a6959d1212e80aadc5c1210bfca8c00d8ed767012d4cf8101a2b4654e10cac7342fee7fd869e8fc83ab7ae0caf55189511f266375b4aa66227e9f3b1f0fd37cf9ec2cd64d27e4c4eab78490d0cbd03ff96237872959b6b64e2893c80512470794b1855e63b1077d40bf835c93b11675d24ad6396a5327321c19543d11bec71cbb802aabfc55b9d09c7d83fca03ee3af414fda88a4eeaccbb3f4717e9aa7d07a0d19f38bfb3ef445c3f6a5d0c1d9d89a2caf7812fc36d1b1e10bc5a3a5435af6e2087c34e770c1d73fabc8f0fa908aa4e487a8d2f11887952008622ab8b8d9db8d981829c54e1a5de3eb3766d0f5955ab74aa2486067cc75f11b74d400329c23c07210bb1bbc39204f83c445de9875f9dcf94e0415271b5925eb32813035122555500f78f0c4a108d7e0a1b119c278920e1f3ba344c1fe250e56a577710377b4d08dbd7dc3491f4c3414bb9fc995fcbd34d7b408f36f3b10d8af513d3a85a62428f949586babd70c15b0630afe1db956d05ec8fd7ee4d4250df08ddf254e1c27ca192accafad9bf4c9301b62b7fab614d0667351334c92a6bf8d2db855b9d2031d371059e1569b4e3df408b4e5613642a6ed26e65c7aec3c2a86cb2533e29e80c08a3d03d0e653c927032393602a02797119d62c678870a7a13b6420bda1889804c73e930f65179fe8023f3fb1661e64d9e5479e247f7c123e9666c19e89b634c322d4d1b3fcc45c61c4b4574822967601096a83d870ac9ef98e82cec79c008de2e0b9c7afc95e86b226dfa81932ba175268f0d7e390951e40af0b057beeeb756f7983496ddf18228bef4b3a8549f943e88946ba49381da02aa1bd67d0d737194cf1dfbb93e36b5ed54887dbf908a353b65092afdda2d4021b575f5ea1d4f9cdd6e968a96ac3dd6a442343d90b1a28ba19d955bcfe338f40f59e53812f5be502e2a082e501c3a356200b47e7f0575bea4bdae77a9e385ea9c2b71817fb942971cd659b2c3b0f80b0342c7c3bba06adc8d75caa5d44209a8bfc55000053d43868041d71065ca61939017c00d16f9398704c052eba7e4869d72369334f29fc432587c5608ddb47bf5e6c08c3ce58bb1257b12973a89b3640110accf4f26a7fd0ae57ac5db02b7709a27f811117bb33669f1139e2043ac9d1286f288cae9696a75522e8f4177ff0b7456f6170892c7865a92ad49af6a85d11ad4bf6cee3d3e80c7388eb3666ebf4997812fe14e795f7515979f5f1ba3de1592c79288408862985bfe5c33a360dc963397a809ebdb21c20d9b681dec0590f8d98ed8d959ef9fc95de6adf19ba01d9049128f3f9f0e087f4c68d201c36b8aea07a3c7577aab068f79f64cd709551beb2165a277d2a2dbf89e653e512f0f4d905c6a5ec25242e220cf838f5382262481b6b298e32438221effd31cf0a49688e574e65e8c081fbf6a680fef754cd9135a16a4f932d23b9524dcb4831b9fc01e52c454a451759a82e776faea3a6f81cfb8373859bef442ed3dea358bafea3501b6b72a0b2886ab83a210b5378bd9563a7908d4d33622dc3e026d79e58f16a22bb7d46adc2a982cede2fcf5ce5ce7c5ab567c4b60d646a762345473296141bf0833e797eefe35713af32669b1cf9617f75c785300e2a092645bb62f23887c13089796a40954bae6426e1898b3c683bf623cc6ccc138445c5c52d1166cec802b0b6b51d52163e6d0c55989cda9f8d68270f9924a52e197cd38c0bba515d2a69a2002162dafbb5258a61581e9cda65163040cf55f7aff0117d6c2587ff5b3fa9660d53cd50b8880ee0ed3153ae91c591e6cc5d16243c1e06d45e827b377e988ad5d617af7f3f2686acf249ec0e5790615d0079dec7f9339106d3fc44463cac394108fc86c83fba3b85fa4c78aadf5b4d9ac8eb78d0c7291239e65a784091e77eaae8ba450a0a17daebee902daf1d1e460c992f8e5624583c8c651a130c14c188c585cdde36c00442dc29f50e5d3e0e88f6729336366920956986592ffc25e42d4fdd76fa9ef2b8475ec56471226869ee8be0e6bb0624db6c7808647cc9bd88740bfa19d9cd43c061ba4139afb3e22d814d546f9e82d338119bb9875a19294c034e881ab51a072e2888fe8f7a911a0f5dcd830bf4d6edec76b0ae66524519092b7ebda85eb38b432247f3874bfc0023f6c0876dd1476d29ea17c71804a813e80544fd0a3755dc2e37e8d951c8cf95d65192fc80bd21441814bfc249bc4d8802e361d36b86184da2697f3856c3dc4f0b86f1fcb8a33ba285d0a3acef6a23b7074aed1ed15a967d94339086ecdf896a7318f7fb32b864631b18d5761e2fdb6c79a1fb5c043d70de28d5f0e69dd533afac3b3d2d9cfbcdebb309b46388d649d0ed32d30d3dfc918eda088fa39f84c5694b88b6655ece8980b654ec3d47c0a0ec7b7797c0614011f7cf57f9c46cd8d2e90d9125404f92b9dbb36bd3d9f6a3abe9edd2a62e0e595a2fee7a2f67a344090120056b3023ae6a6991d7d20ffb0f38d673498f4cc5cf022c7565a716845f054fbf1573a6a733f54c324376112fc507c329feb837c31f671bd5ef970c28519b66b9fe09e83a5110bc14c031cdfbd4e39ff38a552342f369220e9cdd623f4143b3a4caa2b2a87c499f3fee9744d5d2cc85171825d988dc5bac0b26d8e7f43c25378d8827053bd88b5f06efa3854b530963439d108d92d87088a853e0a63b9743b495fa2d4ef797dd9f2cc3e6c1641d40f112b2d020c13da59676a122afbf6b6d50355ba901ed3384d743202178c557deca94dd20aa16223155b93e7a59eaa0416b6dc4ac8cacb3d39d1c28bb6b73638628266a42753149e3c30ab7f33b01daecd2bbf566f8aaf5a35892c3c9de83f20b90284115ad4f46b9913d9985f1e76757abd9e9c288cf1e45364a6d2623c0a04d031ed2017393f9f34923d5e79a5714ad7cb7404e3fa0a90ac881ee08f39967e6a5da7866d6b2075c293eebe7f4fcfc80c22599d12bc15f574955d7f49e75234d2dfc0536849a19d6f419eafd336789679cb96fb57ac9a6698539694b197593403daae72c162baad2a1988df8343b146402ae6e2a5f30e544cf5511fcf075ae834741acf68cd298ab3a07e7d8b53f040c677542af1221e261de84cd62d6c0956db0d92ef7830ccdc7928124ed20488644300a99f7f0cbf41513a84178b710afd5b47102ca794a3b267a01623308ae01d60f124d88c8af3695bbe8637250fe46f53ebf359ab6e501aaea643d3d597babc437b243ad1bed7c096d2b6e2f0308708c0dead206f7d6092974f0c23d10966b4dc501a03dfe0e842dd0d2418d35dfa0b7237ec43698110e8329a6b2777711535eb53dc2677383c901724df564a01939bdd9456b93aca7c0eb6117341533a9335a10e2d587c11d9916d7c4890c1537a439fbee851b0824b1f523bbff93b580126677f6ce02a9a5034fc9341b8fdc790f9ca2a24820ecffdd7f6036e4cb06bc0301a0f264d46e84621649ad65c7f24917f6dfead5d9385362a829d54c14829e16d559547f288d5a59081f4c9c6f511af5e94804f10d1fa4690ace573b82f10726297953db7c7d38523658c9d4546548acea36cc8d11238a88d8501462131b9d0010cbe488048b0f83d203107bad3d777c587608b6173c8c1d58dc88dcd2069ecd981f87cd9498196c40940ddb85601c467ddda3cd0ff014c7f779cdea0f039f28d37821ce4db6669195cf5f28741341cfdd84718b4d573dc633c42030ff1e7b8d11837ef70b52fa62d2b3c6a8df64e0cbddd91e5ca87173c32a531eb6a6215f9ac61d48bfda398750ff039ce903561f62ee75e1410b8c884f889bfd4ead3621b1dca902637cf3b4d54800eb58155c8869b7a5e1ac823c3ac40a191c894253979d0a7140ecde4f6c412ae707fdf858ca6c6de95db80205d1946460242d8887b9219c6eb1d67d94589dae015e2cf4f4709b5c0317b9513044a389110d22f70840aca7c8c6fbc81846e2a486b27304e1504033e9805bc41194f1b04a56cddaac691f9d612a6c95ab3d2b7dc4f25df31f5aa9349fae70ce2226f121556872a1c1c6333ce81a47de02dc5adf68e2973399991810d14dbdc2fbc6c022921383df5c03ad371fbd00b9bbb7e1ca6c63bb9205aef492813f654c2fe128c1e98c3c2843400218efcd34adce90f64614e752556d77eca1c9232ed2d7cadb928e330c1ed0f88ed9834ad62de3bd00eadc32e5b79aae44e1331b15ac08356eedf0e8e6132e820b28990b837246cb03f54e073feeda6a8ec4f9ecdbdae07921c6f0b06edb399f77895b33c843d434a86d538e0e8e37700c88110fd54dc2fc82c686866cd1c29ed427050751b2966acb056818da3e79b84c4e37b1779da3a101f812af231f792285c789f087941431225bfaa2512af3f4121791b987a83c5c46d2cea321ae16813056e4ffb6c9c6c346d79f5e9b9a7fa70368af5a7ce4f6b3feafa57d1e61684634e07221012e4a03787c7f8f3da6d3107bcf03eeaa47fa8d6b09b2e7549644bad5974b374dcc1da11221f81c22f9343cd58e43f229a3b188163606d4a0fe1826f6eb66d1b6d8a3ecb8e3c434d419af53681c842521ef7f29a3660489543b66d80a1370600e2ad5316f7d3278d376ed39d377a076e01b9e95d65da55c2cadd3a2b6a3ff7d40a3cb14545d08550b967e48da7a903fa632b90b5667ce5048ecf42957ab53b8b712f0dd68004351e2c5d89bbfc2261bbf76795d1388b034514ad4a335a4dfbc17b9f736232a470d5d6b9957d9f7abafc020523c74bdafa9b82f2d48554d56db16a04d0b26938d2d3d13e34cc3a1dcd9a0f138f7471973e050bbe26f6813ae1fed03ef91daea1cab3490a37d432f1af4dfa4304d1131ff4f30cacf53153142f348f7b51a2fc83e58279f0e8603393321d86b97ffa7cf21c8a1d198bb9a8ee3b51c65e4a4e0ab69b62f084410f38d9039920556751fe3391bf6560e2acb3151700311331b02fa54e44bfd4ba103afe621cf8fd1fa83d8c30184a15f6ad48fa9c29059867fbd5caf143a40282e1d819bf3c371bf31f62df1d80d9fbbc5cfb4a18db255c51b6d55a912bb0465a175c291ab13f838789ba549214caca259192ea38bffe43c84a87e4cd5d6498ae94ee3a0f99656d3a825b7172e9bb28150ccedecb77aca715c441b45772fe2ce4e7635ee4c8258274f3eaa94c482d097232a66b7611c8efbd80e5fa315218ab857ff5ae03ff33976c4ff92f1c4cebe43e993f7a5a9b8b2a132e1fda7dec957b3b9e98d36911829909a284ed6ef1240e02171b323e922212c78cf84ccd2453b092e928c9a04c22f7825300f5345c3dce8e5967856b084b851921a6257a68d933b101b9cd135fb12a159c190dd0a212960e72c8a0fe64bbe958025dd845d296a509a760702692575aad8f0bcdcd9eeb7c83c5557b134d999cf01ad47c7c1cd1ac7406af0a44d5fbdb9eda198aef9c41e77061506445e4813239e41dee6cadccaa221fc66f6ad87cb421821fbc9172d04793c0c2e044ec1be5d81e006e2016ce7339022b7d52b37b36e1e648a02f4400902e9d8ca229565d9d3ac04c1dd6a8d4368f302b789915aef24b76d1859947b9a551be34ef9b4386ecd5cba2712d3f4a9afb50bd2f2575a8d98e75b065c8e29328d89a1fc74f2aa6174ddb527eeed80e5fe83475d9a78fa525901e582a05e88f8ffb558438e32d68a341de6e95e6944d10ff97abf89cc5c4d4abb6a55898b09a9c46810b54548556ba7296aa31a80e5c4e2cec5dc58b0c8edfef3e41541b79bb844ca925901533c7c0ab303f9ca371879cb68ed6fb9b900476dfc175be1803f14848ebd88d6af69bfe28be8d0da3896e07d8b8d89f40f348b5ebf010698e92d44d4c2976962d9b576fe6f9bff1ba382a4a3da6a85c7f087df812800e3939c26b4c44de3a2bb4d0d5e5e6c5c09d76524318c2116037afd338985499b26049e7eab7853d6a773c3ebea01f73d010159351c9a303e11c1d5bb9970f18c4ff54d4b690a7107664d73770cf628b8606d13ebd6e4fe80549fd336e5c6808033a8e60d693f5dd3a339fb38a39654188a539d984cbee76662ed9d5d28a5f1a9a28120487f888d825f7c3541851a6220a2eb06c7380cb6733978ea5e41f0ce2abed93029a376c66fa7eeefa056d6f2fa00f025be678e5b4a25ecbbbef826495f01101205991592a7fb231f5e0726c00a4b618206d9a14843f4c30473b4e091d92f41f05ecc1269559fe17fa2bd7b881791513551312b058be92b229f7454c153776162f92579b10ca2b35ef37dc95e504b6d92a235462325f312a95953174a3c4bf8a9599ed4f44f49dafb0fbc49a3b2330b5db7588a792ec00d68677addcebf77680831cc74302c40f2b0268144b5f1970ab4839cf8d466ab3ab00547a45e1eb272519ce0a613866863b69f3e5d9f7b79eb9313dc04a09462c354c2845fad52e5406cc412b77a32c071caac2c8032840a4dac3fd01a6b4133c16e438ecf28812432b51ba02a939892bc84c087b54be99705e77a344021f2b70964c2dd445120323c85b4a2a16334c682fcb0869ac0503cced5b51fbc5abba6b528ab0bf139685f9b09f313c0e9db38cb3955d49eb5d72885d694d8ddd9e42531c6f11297e1f15ee92be370a967092a09eac04759681d0eae33e5043cdf7d2d4129f9c9fbb227270c4ad69db3b3e119002a3726783f273bc1abbcd469278ada4c7ff8acbc5a839aa52a33a8223b3a03bc80eda648a6584a220e504d435a9c014f2ec135faf78cbc7f488b532a868baa0f4536bd4323e7e27c1951e72c65d2b8563c311b676f7c2f7c6eeefa176085a6f08b62e03a23c131739e87bb9dac05c0448093a22dcb491b8389fe4c6c962ff8843ad0e76baa23a6ad6aaf29048e3174d3f71b5e1109852cce9b92a8c0b34a065d5b761f9baa13b335e95e8927483c95c7ae2baf97f59dd50d397c6714e0a5f1e3cfe4544057ede6ee94bd4e21b483ea50917d1aee469701008d0baade1b55e707eab23e929cce9f730ec14831c9be24dc7e071839714ea095568881caa5dadd42a370032a289fc0094731ba4946d479788ecfaaee55b4cd3466a5b4b2446b04a30d5a5503e8523d958afbd1ed1fc70f487a579d11d0816073daeaeddcf85cdb54b54f177e1bf3ddec7075ca8ef32f9735cf82658a145586bcda4a06ae26d61b84a59a0baec5ac897dc5663c81b3b54976e73c92d07f26fc98c78c733eabafb9559e843598ad76cbea68336c70786cdd7e7e0d680e7d9e73caaabc8b4f8d35dc60a19dff7373017d59b4106c569a2839c7b7b274308711c58e658dfc9ca9756cdff2c1716010df4eca49e2d1c2c7fa17ea110a979d7d046788ff86f57ee164fa2491787dd64a5e13a745eb60638c20017e013e04f3572fcd247b0d71abbea295dc36134b4c238b963a08b00e1a690a8374bb12be81ab11026a27aaeb533d117223a35d4ae0467a7dbbd0d5830d6917290a5870e6dc46f9d9398b5173b4dfecaef3bf7ec096ac4253961925d592ed75d9579b574a129bd1025492c08aa835ae96cbb461271215ad48cf5b208816e7cd0c847473feecf1d419cb6bba5d77fa4225a20288f5274846ae94f5987bdced38d756bd8c396fb245a9e2b4c1326cce16a120d8c30d3b086a2e8e6245a358ffdee715c32baff63fa01a32659414a0e24f2db0e2afaec89365f7c45e22b662f31240e7969723e008876479cada45ae650743cb4411d80eb74ade9202275304e624eb0fb12fa93f1586f671633907e4b713d4c05b5f2b1e70454916e45d71ac2cd380764527240489901ee3a00cea6088faa2c5c9a799ba93adc666e41ca3ab4839a53321a33195854748128a1596b4982f223b41f48d514b6a484eb9bfa92d5af769bed16671f625442b5b0f289d39e8449cbd8bf01226429c5f4407af4a07e75618135067374d6e9a2b37e064ed786e6c7dc20cd45bb73b95b794250689e8a557168277439e4bba35c842c1ede78a1f7944d74f4ff9ae151b6051612891b53a3d9f224672f8744d6d09f81d5308fb7ff59654588084f44158214eca054eedab94d6930b15f1452c727c5868f14e02c9d9945ac048439c393af89ab61a422ba67e4d8c07326c463a2c5712ed5caf34420296a3749ebc694fbff47f69753195e2489d98261da76dab7831f5e660aa1e21961104954140165593b5b0b6ec9525a337ac3d0722d0252cb0b1c9b958a0edff3471ef0d509ecb6ad68d5e602a17b00ea99fc3057236d18676cbb8bc5b6988a8d213d1d31634f94a952510fb1ee1b66ea3117cf65463fa42b44b96d303829acfb26b791b30780b87c1c7cbca3afcb97f18adf3176b79f003933d671d5cd48d57f049d8759b5475a5805373d53b003bd004790bb1a6403ac35a6fde7e9aa250ac2dcbb0f9872181ca2f6f1c11070a53a37cb88e2c4ceef08b0dcead10ed26f90dc3594b7d2b1089c4caad77fb6c505c9a141dabac72e3ca17234e5567c08f86138fd871a104dcc34b0bbae329470851487f16acba4b6da1f4f19e239a6d68dd7c7819706a2aa67e77a778db72d911a77801c443b404e6e7a1e91664bbfce3ea8562cc8ffce7700244308983df19e007043dd66d86cebf257cba8227f0993ae355c4c6a1e0dbd5552350495524e5bf594fa653073ca5a38b4ad6e20d5e1f4a4b44d54b95582d74da101c06625259ffc2020662b1738dde95a6c67301f8d887c32793101f74408a92a83c199492de55223dfca478067406586faf309c2ac2a15c450fb60265b27fb83df96ced792663d61f8eebfbf827e939bd0031499083c8e5274b13e6ace4991cf85dbd35621fe46219477bc996b6dd7163c85cd84c8f05ae9c7604e19a8b2cda641afe1a20db79a5b2d2c937c9f31686694670805cbd7f4251f99568fc1b9afbc6ca0fa401221a5e3bab4365240d75b84f64be0195a44da6d2e0ffb1cfa8416ddd994e15307f86225c777d8257ae54bde5e6c5ff8b89ef5e225f4b6c7f4c3bb50851cda62154d0081d34b91344ac33d72b0faeb2605e8bcb69a9135cafade1a1c540ac9e10193e95de6d58591a202918c7044bbf0bc59186736553289cb3397189df8a1b09dcab9378de24dd0434d46b2261e76f2173112026dde1483186ef7268294e8b91e649b36e83f3c92570b334e662233c272b9284a6c9a0da34bb670ab02f519863a3b50f874eb95648b740397870bcdce75b0d66e7d9ab763b04ef06cf5434c1b02c54c51e83a41d7cb7a9d1d3c516cff1fd371737211128e46a2a865fe1ed3aac76d689d9cf22711e879b8d76f1b889f61aa1df3b03dae0c35d49ce3027182ed32ff993444c8f63e4f4a8065c91ab8b0944e24e74cd1f1a244a05068b5a43b1497df6ebcc3a59956cd10b903fa5620b949dae2a184dd589154ac609f1aa9ef3fd0c3e687556e6c943a1975df244d8786240c59a2e490d50aa785bb0eb33545ad00aef11fc1db7b1a674874b5f89dda6e307a6eaac1cbe471c6dc279745dba6e8a982ad94f5e26ee00542ac96cded164bbc9b3539e4343606f84d19a41f76a3ad681a343eb32f7a67978a496b15f4914625ae662a173d536ee6f8a7ba9347d1131d7b96a3caa44533f5038ea67e9993a874bae31966bfbc794a1f35b9efc249a6076e639bee04c1ae6234f66cc53342b9c5983ec68e165797054adb636ae71c6ab6973b63997012747b504ee426a46230710528245f271b0b4b120e46e731cb9f978abf4be721391d0915c46a302ddf7b61c23fe34e0b7988840c8b06b22ae586443a2623be6a79cc08940bf004ef733a5b7a559fc8128986c71170c5e461334b812d87dc9ef3c46bc0bd667a144bc0dd7c26522db94ff204d942a10408f3107b8d3478f51018d0d7afad1116121361260f785beb5185b3a4a91fccd66e982ca89b438865f0d60913eeeb9df77b36e3e79738ea1517c888b6dd26d8fdecd1153dc527187c9a98d984552102752e695bc2c5ae18fe5ce73dc0f050e0d2db7c69fcc8c30b878821a3d9df2a4214869075ad75c1f6e5e0ba980e07f91e53ce10a5e8c18fba754a722f28a369765561456974c0dca108bb1f420f833acf526301b018564d33e1aede0335b000757192324e7df4f80e4c11e5b8d806e2040425b926c26f9be6b1e0ef5dfc70bd993339bd39033abca0237dbe6b2073d7327f314883b052f3831052c2626ed92561d50b1be74c5c423ea7df697540c6ed4e6f2737870ad91d6c80554a17ad37ab9727dbdb821662bfd23a2f8c3e8687e299c8a7125a75db559d9b4518a2b1003186fcd9838303260186ab2b0e44b35de5c8354a68ea69d174b7c33c10f3879709c5fee8c1dc22f6b0c07527011b0cbf6c4352b391f162e8c7424be55e855b27d3ff613db56e85525af830dd0233d1312a63e52be6f8946afc365f9e6903b23bee323535c3321f4124a404496318f6a1f94230d8452456d53ed43f322d04928d02ac565e5d5481699de8b7efa0ff5d9718a89e64d14f05716e3819b67ff4bc5548cd8d8ea9a94c313cd733c803f15c1bd8ae962af3a4d5c491f590f2b3b0ba26c925e48b1b9bcf8e7ca281091b0f5d9ad2f45b655a2e5ae9739edb51658c7a7cc788fda2c833646811f9fcb2792b89de42156a3c34c07a808879bf0abbd9b08343b8e08adee2a3795d5f24196e719dee7b220dca49eac557a741b2bf9512b46e665ae7061f0c9f4421f8ef5d9ffd215ccbd837fa15ac05195b6ee4699740a5d835912aa1d8ca58246b465e9462ba9de0f9fd555288f8e61863697560a366fb0d694f1a03c78ec79130a2bed12f4deb851f40d4eb27fd5c69b885484e8243138a5279c4547084ca1d9f1c6b56ca0d4e4e3c0b616485bd8fe6e5b8c237f8676ece24102ae885238456e04d8cf72437ec20acc47b707dd0d1c8338ddccce6a67f1e135eacb57e4515deac7cb2705b1352a16bbc9de10c2a059f1ac7a2156ae841cb41f6f0ed1af73cd166daba0c5d5fa8382a8fc877b746478b2564aae4ce2a998b097bc527b44b00ee4bbfa0a14c7b807adb4d31f070594c494fe1404537c644290abf3174fae852800e6502c6c04a0d00cdecb84e291c225fa40ddffd53f28bbf2bde3015acc0a03b4e6636ade4f4ed88909ec9f51b2c1028dc74fe40664a989f69b774ed56388b6cf2d7a3f5b657dc816b08766bc3c21724284400931a7a1d8fce0fb947cb0935985338ae90ba30e407b2bd5f5e85751f6be259a01b3fe64bfac0f2f96e92898496c51af5580638e45890a8b17957278a474adaf90835c9bbca65c39d6a5677425eebcbbf7d32c5eecc1c495440f1abaf550ab0347431c9b24ddf0f50ce24a2af3d964039cf782a7bb6ab822b85d6587576596251728953d92f6843d14c207528386942df47109f9d0a405a392e427f1f34b93d83490048db38bb81766e35cdccf2fb903cef71e4c9228559dd1aac0de7fd7658409169c468d382eeb512f87d7bd8e50015f74f207b1044e89644b87db3e0097a7ce2436cf90de5f043157bc942683d0f66c535623ff162b3ce598af3943f27d1114f9e67a466c5a08bea0d97c2cda582aee94e3f8a7f7c4802b7df4fa798b0da5b8378b03cb1927acd376467d1e78b860096683a713e7d3cd8416fda734ece30b6dd376644f1ce3f4dc0f77eba11b913ffcd52335d61891c368ce76e63454937007b63a96ac57e330687ddd2ae8a9fcaa6014849e70e9c0f981447e28d08eebac4c3b5838af4a321a535911f6c539b109705c06b051edcf5c4203a3c628891e741ec7285163099f07f471b47588adf898aba0b7189b80f525841d9f3e1093824b4372fbe738316a43342829b7b1e7374978610bba6d961b3ba4a74d5a504284693bc9d1ebb69854370d0b3a8771bc71dc02077982b8bb03338f600a3d2ebd98a57bf3d46ee7225c618f7ca9071c6a958740202610ea0282252df5d2c62a6651640c83fa8e37691165b359a786ec6858480678b62b8ab6f3431fe743cb023d9b1b8223acd86b169a164e2a62e3626b780cefe4a4088432df4a244be431ae422f1d586faa7bd221433e88866ce7aa7b42a71cb263301a120691d4b5ffcdfb6b8e3209762b3edff6286823aebc5451670792aaff5247b313378042c427aa37816efa2a06a0424f27254ac85ce13a04cc641403b8da44ae5ffabd82e7dcc195fc8ca23cb085482957119db777e14c787d28f82d83b243518e0dbe28ffa5eef745405e37b4973bb43c563124854b494ef58af000c2c182fd76f58a8743bd360478432d4b0ee97ce1c581aa660ff111e65f390cdee20420d8fa4d8d692076167e6b2ef772695fde4c3fd420b94ec04761bb129598ee707397da0518c054ce178a646ce0e9db289fc301f2b432acf1e1cc9ef5eb56448f598c72586fbf008c08e07fb047bb70de34e85f5dfa9706bdace478a0be5a82420becef94a4f1d94263de507bd21dac40648c4a681dbf628f8707d2d2c6dd401904ee01680ebe823304928cb0239193076fcb5efca1cdeb12fb1e109e8491e5207322adf4a044037f4a02818fe52abd813bb931c2509640afb1aa2cbbf218b08f98472092cf7fe0842444e10df19061e34523bfb51038ef29cf29ed18fac3f290d2f211e228a219e0ac384d3ee01cb628b6e210a684ec13b15ee16ff5c9e7c51e1d83657f30d74951381a2cf0f8e4bc458290ba4b340d7e659294937c05ab75c5ae3aa4a38f4a4a0fe0dd2497877a83702c83724a24de7152d459239ef472afe5ea9ef091b66012237412cca772d7b391d89aa8512c21ae54d3dd91f2bcb46691dd9d955ab171df60d861860137a5a5dfdd6fb4780b2ded6017d0b663bd29a4010cab8fd38eec114a37d01aff11406605486fb8ebd155f275015e2e3d893ad5220178f97addcfe06dad81826225888604530d0a16571cceb45635fb0c69f60ede6ff091074722a3d749cc99eaf08c522433b2a75c12532437de9053666968d66dbbfa4d54683e1635404e12b975997243d9012bf523ae51be174232da9065ed45c4886165434e87a136943bd4ccfb9c5e62aa2bb41ec270296fb4a2eb76f1091f79a2af8729b675caea7774babeef5a5b1dcbd7b88c31d83847edadc280a4e02942a1419c4f8511fff75e4ea13d3d40737ed88ba62cde622fab19725a98e018b5d51281eae636a7b547ea4941c26a8b7b5916fb648e274040c6ae13c35fdddd2f49874ddacda922a0ea56eaccaf1a2170c18847246c8feece050b4a1511da13d151f30e331880db300d0d58cef821c90d02df1e817e68b9bad148242f4e016169eedcf00ed03054e24275de95f013ff591ceafbc27a9beadda8c0f1adc4106a00e37b9f8c1d2334f097b9ef6602cfcb10f8c1347717a563c92f2ccc44654df86a0a25d84d364da7ec0d2008f677fdce7e454211244da0baedf597cd5a641a82bab1e959d71eee131a0b05682917d427db195b42fe63b504cd1fa98b8011218f17891852788d1763abc05c7980a2d88f1102a42db1d38804c9d72d7bf4f909c1ff1dd4cb3d2aa543927385e0c89624aaa6c6399ac10f597c568b01612dcc3a981d10989c4042141611b5fa3c7fa67b70b2e8e8016c41abe720ab3eef1ce57c2723de3a48133ab87f047f894f2cec8b5e4c5ad1d0dca54a8315f4e82f1f2d0d7c0c5f60f5c51ebd70bc013ff448b596848de3c2bbe96a42941b86466d643978dfc82667b19df25b491ac04907d66cb73498242f88c54871da6d1c592eb8b9d6d7b3f83276cc4df65ea541ce82d7b84cf2bd3e6a62a8631e3c9c48488183639399ff3e7a980ca4a0fa9f41b6ab69923725f33603a0d8bde144a4a5cd10b8ac21344be6d7045e58b680fa5fe684f3199361f5dec7ac2a331327f4fca6615ec591818d8a8af43a8c92436c72d8a884f11b9e414298e191192cfb6641ba2cf6f1a5f494e5207e14137e16e6d1f78a2c38e6d1cb44e36a214e0587323816448068b04e22538c1f68e712c42311904426712dc5c50e89908ca93707a60126b6e79cc09ece4992cab2288e226656ee3c83530971ea6196aab06bf41e4ba08ab1423559e22b009c4238335a44f64e6f68281d98762c296c8f5c0813904243140db2a3db11d191432cefd034dfd335ece71dde2303a9446d9339ac36e59e4a31af53722e19a8ea36dc0c34dee2912a276f1658ad823e42aab286e1273a11d804aaaa3c4be2dc36e8e5fc2378c64d1edd9f26d245bb1c0f7d9e3413eabdd20e0b171275eb208c94307a5320a9604b32af250a38ed1c1c7d103ab0051d1c0e781888440b77d1af5d99c32ae8ff3418677b3dc7ce24327f9a934f8c02843181090123229bf2c463f75bfba55dbad63689cba2df98a203017c8a03da1e8a6e5920da15a903d9abd27f458ea7769d7c31cc9d5666cf13012eb38c2e4c5d4817d6c9680af1660be02e4427ed4b600ce01cd600702076e890f040c95d081e7d3bf0c3b271a7f28ca15b4b4222a8027941dc6def2df79652a69402d206c606e40623d80c9ff1a2a4c1f519df2f6962824dedc334da87b3ff89c940c791b8153e3e05916bc120a89953cbb7a09e6680a26779fb32c0202da8a7960641508752097452ac766fa01f10380871b9efa47077e53d6fcd65480a19af7d3cf849c67becf96466762dd44df0c5869e665c0e5c79ef3ab9a28cf244ca64109d4376219bc005394a981692149200512e71fd59240d98013d246cf4b22ed64a47739f59f8f33ea3588b5d064856404f829dadc9f290460c262b7c6245d7d171978e8e8e8eebb8cee7222a78d286bbf58a80cd521bb2da9881804bfb47a24e6e57e089629ea8999aadc93387cc23e66a2a993ab3c8f567c9cc2559ab944f230a2b6a2f47d0abdbf990f1392f36a26f7911588410a719dff2334432b04922e1924abed8885a5ef4a3a9e049c97ae6e34d230adbaf434a6004bdba3db4a6bd4c617d786eeb5e0629e78712b0f839c958dd9ea823660be965c6b7bc10a7fa5dc04f31bc88f46223fad18fc09cd18b5e889337832451a4171b979ff12ea0444998ef67bccb8f482f3632fce8650047e0cbf72ee0cb07eee0a7183e0650aa402dbeaf3526634cac48592c2f56fba25d79d65c8245a14260ec86fe7850b7012d131b70bf1f31d109a0d440766a29419354e28f188f9458f16f0bed6f037de8d5953ab012a808c5668809569ca86e990c2543c9503294f6a22b64133551ddd665c4609c73b240f30769fae91af3266886283373ce39c70bf8a987840192127203c60f128689cd2143eee0c8144c4345e7a84cb3bd8c0178200b143b234d4088f18425cc9546bba7e3eaf63d5bf50dc8f340b16dd53d9de366b58a75cf0dc8f34031ae9331f2c701f246de0887e7755cdd34dab205700956b67a70fdb9d3a4681c5ce7182acc47f86ebbb7043e5066451ec23210827f304d739288fbd2f4da4cb60a010f9c59955158bf75369bcd4a386a6c6a7b3ee0669e3c7bc6672882952fbeb681de337366d4629dacaaaaca65bcc2ea936aa44875d59eca9acdcc524ad7bb58ba4206b7608204ec94f36385253b7dffa3a508d7a90ba7e0aa629a15d3f068095724565c7f1a46ac58955c7f8fe723c2ca97cbc31e3f1a94b0defb007def49972b7485ae303564479d02099575fd4d9508c35415c7f8fb083d28641a661a174161e57bccdc1dc182eefc5e76083de879f0fefbd16b94a88d22286c836255d2a2bf4d3925d80357afd65ac11e82a017879b99b99b6a929341d0db83f6cdddddac854421c0fecaea09b2bb3b6ba129d65b591eb6a42bc4a7461df1fe03736a4769d2885362eb0adc99a982179a68e11a4f7ceac0235e71a4492e61187f9974a9aaaabe5c66565555555555555555516aa393cd5062eceeeeeeeed6f89b58e107a8536c14628930c1211858d0dbcf612134b9623ce5fc5881bdd7e81c7c7f4e3188abf56f93f492c3143bf6583640840cecce2311f1ea71fa00baa28b2802907f38a658973bc198115eb8fee5e072ebd7e47ae86dec5b30476af1841e273be37802fdcafb57635f23d9db24d6a20848ee46e26b493d4a18d44702033b03c2bb01283361cc4c99b97182157bdcb6d0460947e720293839729c1c512c2547ac82a8e48e4c713299176b99ccca703c742c2cd3f8f4004e8f3aa43be372de6b37a42947ee69a0426a82650f6d3cacf2c2c94cb0672f9cf721ef43a00fd07f209f38506c1f4e0621c994370ab115e4389415e528479709e226ef1df40a446f973f0938b39d1ed6670f3be5212705ecc2b287b54acb98a60ad370d30939cae44892a3cf26784029bc4e89b1e29b92a6940c076d6a9694dfbd1ca24dede27ed324101c348c1ca261bc03454985acc17599c4f5e7da88ebae2a71a049204cb3bd8c42d6402661c428a1f0d6624c09639b9aa6699a8d871330409bb86bdc03304cb3b111b817390b1a5101e3c8710d23a76a46cf8a56b5f95a47032b8eae7f7f5add3628e69cf5980e12616549ca4d1bd5c0e66c369b513aa56bdba9b5568ed5e7176ddc7e1a2014d45bc11078c8347c2587ce128707d3c80bfad58e0ef49fa67d0fea3ad07fdd7b3c80fe034b2e60f0f1bdb7f2a1d7b4eaf2848f2336a15f791f1ece4bf7f7d2200f3cac84409046c312b603bd9f76d48034baf1e7750ffa1c30c7035d442f6cdb01345260020e6ffa060bbc600710217ba53886022c2b712dc9706432994c86c3712b51586245a68a8c47c8d76b072baca7dfe1322f9d16e395e08b7cbef385f8a10b560c7b8734596b297592cbf526055d602d8bc5816dd1c76d5bc48ae10da5290786f187b2e347cef9e3bd58dddddddddd1a7b5d8963dc9320b650ec77c55207c57a572c953a87a39690da154ba551693492a65269541a953a28d6eb4ab26989c545370fdac443c2748eaac48b0a2f287891058d2456641f70fc72a84f3f1de697c3b6b5ab3492168a5de176668b039bf5846b48eba458012feb3ad0c7f7476c3e90c197e9e3653e9f3a1a9858ffce8ea4cdc20633f3a862c16ed788d1bf09863bd6a5f88ffb78e83eac924fc9673e33b39dfea261fcfb5be7755ee7e7013257ecd8f6bd063e17ae30852f2440ab44f791b9a2ab1e7045dfe19058d9627948c308cb2c27e22a1f1f1f1f1f1ff7711f4a6d4891125aef689675234ddc7332c90bdd3a311cfaded8a0092638874ddff0da840a7ce246e65070a64acfbc9182cc27e8262716ab321addc4b46dfb949873f2e439e79c737a366a74873d86f4a9128bb2a3720c6c62e53fc7bc5eafd78e275c2e53b581654c8561fc64fa77b519b6b05f415ec781c03ae76c1b7c474cc704c3f7d0e50b567ee7c0ea70b95c2e970e4aeb1394520aaba1d2b44d6a018ef1f7c14d1501579ab6efda4198ca6d928d4d0678e81bfe1b49233961abc84710d989059886effb0e1ce3308e03d65ca06fd8d0bef1b0524d2bdda0d6ca951bd6b18e553b44126fc4f51ddf683d0449221c7e2ccb1c33478d7eece29a63099f1d2178bda28462b59b03e9a43ca5e372d2446b088b476584fbf9f9690ee4714c41946a744a3baf683d10685d292edab4c30b870215ae37ca9398be8567e1499c0aeb38a85e83ebaf5d61454fc16029580af6ad48b1e00fc8c30e8aedeed63fb27fec0f140bbaa2ad3d52a0fee43df7d607568914de37cc0391c026e714c7b1384f718caf9048adb8221e4ed95cf4aa6157a86c20e7ac54ba87e826ae607d0304a5e01c70e863150ce33f04ac75450f58a9fe7151e9d98fa760ab15acbbbb57a9558a4ba552a91df8467f123ce5291c351c637323831593b55ab162517660465e4f5a3c158562d91512984a07ba4e225defe2fa92eb9d27a58c59e9ee4ce34b347163b8ac8512ad4b3f20fa5623ddd717a7914bf295af6444f9bada532b5c7f2986bb46a3781595693a762cbc07bee21831d8e453ace46bc54dae285f2bf95ac9d7eab5922ff95ac917378bccd554ada68a065484693628388764b24295a039c6ef064a9ed56baa54413c0832e64260182f5e1edb9cc0e4ab5153c5327649596513922402c748270ee3bc85273df5b56af1fe0b710faa3e05c738e854348c7fa0e85c5c7f0f149b0572c370a0e85b340c2849c334283a0fdab7a002e63275c534dd73a01b218b24207f5fc95713962fa6e1a0742191c079b5d65a3f0f9c71988ee11bfd376a709027c31352c662b5645294c47660515cee84a65229562ae52c16d3905a002f4bbf45f9e4fab33c6c79d87ec475e2933e85c19a34119dd5800684218924830c2c2e1343abc2ca9f48b0288be52c96a649901315a534caf86476840f95580c0683c118c635d2d4fd5fea1fd9339773f68c695cab2995c23006a3c104eb75a100ae07be50d0f75ee841341861c546813c1fdc7befa37bd088abb31aefb9f701faee6342b0f48680e831ee4b9022bce7be08d0772fc4f51c071cc3c2728e7020112f35dd5f804f1df8e5d0af69df0d25d0c097991a5e72a0e2cbe6328515676ca0c252c043972aacc8aff9e9606f835270df2869b2a222b1a8158c4a4d0b6d947c73fb82f6b17b44b1941cb10aa2da49496696b254b2121c8114c0915c9f0234763e722b608f122c7dad6218e92cf447a91ab97188d82ad625aed1d5ae518ee338fae823a5212d1c368e23eb937294b526b19386173616a341c48aeca2410496b5d6da0edc073dfa4ec3a0513b49ac5fb1562c7058859dcee1afd1b7f01d18a094b8cb437f6f314d7771794a8c0a1c240a2bfe0522040926c6518bc562b1586cdb3e2ade88840ee69c7394b2f267a9d65a6b85511a6cdbc61bd76aed68c4b6246bb576349261e871746adbb66ddbf64d60bd84c3a07e7ad997b5f6e572d2398cd008b8b3c72f9670fd6b04b1628ffd55607b50cc61db8090638cb1c9a003db9f433f10728c31a64b5932bd2c2c10d26bb05d1c9345dff0b79e85f5cb333995ed970b4c59970b4c76066b2da53c76e0ece0d14ce03009ad262499210a2bfdc331eebed2beab2e97cbe572b96a0d39c6c56264345d78508494ac9e73cab64cf30dd1a6b053a206334918c169bf7de55eba7b0725a4d324edfad73f728cff04c230cc800d06d75f93c1047bfc306261457e694e58314442b550c21da3fc5ae7f49e98218e391acdf9df7279083db0e28ae17c71e4ee4d50a9ab80cbaf99948a85bd21f4c51c51ea9a0b344dd364386de29ee7f34fa93d3f0b81c9cbe7f423e485cf890bfb86bf0c0654775a9a0d8648c2ad9f6ea00d35e4b421e461406986366861e89a0c67db4036d05ad358638fc5ae7c2c76f43e3e432c7b48a5f4402bf60b5970a5863e10e775dad4b8bad53ac339f7858e8eead553c4e52d77e22bdf9931f7c9e33293089b5a8700d7df554ce357c63b274df64587d1a0c28aae13facb2eaacb50a699b22575244a0a6153cb078d04c380c050b6c9681e92249d837bef76fffcb5a3d572994f6b47abf59ab5c637411554bf1aad1608342b28b45102b9cb16bc6913d464f496d3371c023e30d23afdf07666a7ae93d137ea26b51a7434806fbce66888c6d1c98139559b54677c05418d3a3339335f135e33a3191d978e8e8e8e8e4e8dcc93f2ffebd7afd555841498b0a95500609af6de821558e50425d07859b15dcde3aa6258da4bbade76e530720414a0508da7feee9fbd916c38a6c64350b4a06c5718566cd503c18aed5251c0c3510f8ef177cd5c3eee72b98b028d637bed29f8737e3e33357da8cc586c4ad122d31fa4053327d1a3eaa9e5f9991f80aa27d18bdc1589fa7b5e791d4e830afb329faf769a3e3486478931c72933442d2d2f12b55811c9cef8185e943f35325e87c7a0b049fec8dbf2f2478c1faf63a377f99834b98c4a2ea492e85b5e9425ede98f07913f31d0ad21c00d3d2af46a446143ef695cb062c76231f9c3f2f67fa68c22653226757cfa0649362161a18fa7482c6154521cc6bf3562ad5432406fa9b10473c728188bc5c299b2ce82c5c62ed7eba78a131f1e1915d893e972b19cbd8df64cd65a0150afb02f3bf8a9e7edd7130b4a62e9e778ffe4e9974b1cf5cbc562695f353027477bef2b98537fe33ee783e11f0eccd940ff32419b8fbe8fe7fec5c67b1f9587dcbe971db4dfde4603e76bcf270ada54f0067c9960cd69eeb4abd52c579425ede258bb38d62e8eb58b631c6b17c75c1c3332d359348c7f17302e9a6ce18a6d00ca288cd194d18d6bb2d060cea94d6dd643f6e8a17953aea1693dfc9b2013baa78734795f396f7605fb89c350c07b7c13ecd8f53138c6ff658c9bf3a20f35c6e8e798264c2ae8f9cb53430d38703ccf66cfdc0d4f9a617e369bcdb6ad34da18a8b657cfd66a6d206f03792330ece5d3886f9de252ba09d7bf91f413b197741324987a4818d8900b6040032c532a75c9927a48538d8419d239980e81d5004b804bd3488ee6aa860a27c45aadd3adadd699d010b899678ca5bcb8675afbb57e53fa4cf3b9e8c0b287b7bf1b660d13895aad9da174d5755dd76a750e6460f7ce816a6534912c79691ed8ad195d8189bb89d40b266559135a2a9f222b554b65237beac669df01fe0007543d5a35fba902a98fc044e0d0f6105d9715d184b1a5ea3a15f75dabd59ab1999921f96db53c2fb451c251637313c4689b9b3948376b5ead6fc009c3d22bfe126c0f4bb84c831081dc9e2bcfea8bec73e3a107403058917d4413338f1e48600296916f5d70fdf3e1d90d0d7a3ac56efe36ee15146be07a6afd826cdbb681402216bb12fa40228fa5b3dc4a0d7d54d3346ddbb66d037d9625b4c20256cbad84be0ee46d9bd771356439c161ccb0d878c4939e9f202d6b2923d1ed92d87abb24768eba2476e4a387fe4305c463c58685ba4ad7c934130e5552878763b8ea412a18561c09c089ed7fd1a18ddcee1b49c10a366c94a6aebed8bc67bab9f4a582363f9a1246aca3c1081721d896151b562a51d8f852c31246f6c460ad82e5400a969955b0a947bb8ab83d706db3cd25af381a81110557e708a5a9abe09a15a18511d63ec2465bedd980c49088f580cd601b0c06b37198a60283e1a8117501d3509a864ab57886b08e5829d1d15028ed09b11a56a56106564a6dc60cd804d130c47ad81c48c52e20d4a4848dda3698f7844c715c5754adda0c2ce781225c6f2de8a80247e17ec181a4d62899ca63e9152b2af460b3fe8b1ce143e4ec2ae75447b35c34daf6038b6528160ac51282041344505c2c168bc5623729ce2d9526a289182580c39134351305d0a1936f7eaac05f7d05418d3a23e76c1a680bd8cb07f0e33e5ec563b399599624ab540a435913aad4101c1e33b8420aa8d38f298428a7232f3a740fae33abd5dad1c86b50bba669db65e7256150723d8c22bae8557cbabb3f3d42830e9bf564fb555f355eff9476b7bf49e7eb8ad7742758b00e7b85a26f996911bd7cc5341bd3d02b7a57182d87c2f22e5e1c9fba24f67b1dee45bcfe1436318df73c3b5e8d5c77306c42dff22ca496b724d13749868ccf01fd8c0fbd88b4f220d207e67c2ffa1cd0b71cb111bd8cf7d102ce781f22d0876d7087d0b7fc4de85bc09c2336325ef4392c3f03bc59791198635f460b89e54524fb73b290f83449dd8b3caea8bd0fa3db4998d10d1de83f1c159702865771186fc2add611fd0ae23e6da45ff7670ac3f8bbcecfebe7355ffef297bf5e2f4a619dbb38d15ad358e3e6315f5b2101d1b70438b4461a898063fc7de0186718ff2a5be0c1857dd19e6f09da6df01d2e235fbb3c62d7885d231d34287169db569dd8b68d37d6d1d9b0e083c372c79126eebad23182c5e101019a3044804d38339d23878930298e157c28ce26936dda56ab27f3dcc7758a389594942e9b996bb52ad5ab54b2566bb5eb54ba4a9afce42ad548baaa8e8690a1320abfbeccff022e13c40557ecd4a80512d515bb894de4281ca5e7f3930d1031ab0047b9f3c9ede7516b10ef4917061e848007490bd5e3aeb7bbcc640b516eb77f9352cd063318e18b91e3d8fac6711cc78dd2b46975d3ea07e27134053b63c269391648b6eeebcc04b94edb368eab756686eb487cbbd5753536375cf0388ee338862c47ecfc9186a156ad473dd28bd78139317401060f4e393fbe24a723f47ff40f923875dfbdf47b1ffa482ffd20b05bd50097ca94202b021359ebf3beeb041d232684cc9692d075d09282f3465b5f0c2bc060b7baeb5958b1a90ed977a3608f1d845220429060626cb55aad568be588bd92e79c94523ac1be9225537a75b80f0971ccd1684ed115ec775166bb5361a43f2158ff2aa511b3d9b8da09a2d2318262aac39fd020866d596c8918aa7e384cb1f28a55d37a36c36d7626ae5b212e45d899f67db9569487de1c116c8361d858adf2ab42c7cc35a3b3d96c369b699a3785d6fec31a0fd8a8e1ea2971bfe64282b1251afe5326d8fffbb55afbbf6d4e34959479aa092925ca9c5276777f1886f839e5fc98a1c4ec14fa1f9c173af6a52c0c36f528933e4c334720a314639453aeff94b210cf9c734e1114566c253bdf0e7d0abd46aa170c825ed10442dceebdf73ee47954649f293b1db8a78f4347677f3754903ddc260f13366945c85432954c25234919c704e118bf230f1ddcb1752453c9502e336532994ccaa48c521baa101566e6f97acd58c591a639271039ba48008462a76d28dc0588331ab94cd7754d0242ad10002c7c0d52781d417bdaae57932d3a47659acd077c75bb374fdfa82feff55ce7813ccff32a28b4d1d203d555c0652db450e57a2b9452daff9765e877f7d73c48e2ce27acbf46a2b4524d2b694a24a574d229c7719423cb15e5a85d51b21cb1e28f9e102bf68b254b96a2ab821b720c33b934338a5b02bfe24b77d0093b8ee3467f0325d0af51272f0d3e3703db301211ecf793d2eec8d6db4637ca1a1de0824eeeb4bb9bfea6c3f348d20abbc35d94d00ed4d11be86803450f78a0f73c1f40d77b1d1e690711ebbdc82e1bc01d38a858fade83409482a8b7c47a339ebd1e0741c3191b58fa6209071c331dd75f6d14b62389f5fa50e36a4fa5699e4a330c050c0683c16a361b25263caec1cd5c7b7e9cf8548945a9d5ebea0652a954dae488a86cf7ebadf5946cd2d4e329b1f28acf63b52bfef5ba4f88edeaa655971efbc2bd1097a3a105db2abe53f5e2513529b26a7d31b0948617581f47fa7d74efbd8f9707da8040ae46ecd82caa28d2b19b580f7f504f471949f97e19f1d281deca75001af66262d943ee786c93b4ef6feb1cfcc8a5dfa3722c2d4a6538ac97794a29a5da87615d00b022c452f0653e0b71bbd520cf65b48edb36baa2a0495ff30f87bebda2a4ed7a3f60130e2887f7aec97432dcff0c21081d8676fbab8f53a7a0a26f386b27d55af56a4541d106e0a35960c20bb60409cfc85a511e826046ca04a081384cacd8abd65e003f92c1b2700e467abb4e8ef19c458939e7a4829d1e7ee1c4b6cdd9715b2761484ab05c00f017b06d9536cbb65adb9b42a552ad60fec4c8ab88ab87a544a51a8d46230e65a92459a552188e46fc725273c3329834f95879d0b6f9b0ef3912b7c2bef73cac3c2805969910197299896aa70897993c3185eb29573292a61a2961ae699ad63f05abc4271a66e02a8e718d44a43ead907a95d349f002aa75fa315b4a749195ab52aaed4557a9422f690a7244abbff2dbb63dfd2ca001b9f31bec41fbf99ed7a058128d443fdfba4cd7812f3bd8173d0f2bcf028a56de87e86d079244dc8f6ef7deacd665e66b2ba4976949deaf3c0fa067017dd807fd0ae8c3c7ca7bcf27fe74a0afad9034ed7b04a3db81228fcb813e8c6ec3be9df4388ccb4d6e727b52c3c3fb95d0adedf3335bad34a67195f3381157f5ca55bd72550d35e0c06163a55ab9cb086aa55aa95e2a954ae594da50712e0315b8d979641e7329fef21ead824fac8fcbd09722f4a28c22633b5ab7c33457700ea984964e4bc75b5d41d933f668d1df0183892540d6006d8709f4df7b9236f5f03a4792040ad71fe481a27331db62e65968ee45928a846936ff828b2d3c0b6fe9788b894ec78a2988709cb7bcd571a18d128e9f6f8ac7de0aa594524a295da976f480d087422f0ac0d42d8771d7102e9f28a5945a92e85948f657487d0a919eb8fe4e5c06f837164d84437f569f4224543d8942274bb2608e039c70c13aadbc10276f85c4e30709b342e2615f447a11813f802ffd166c70073f853e04f62861502e43b94c119c63fa4801430ade5728740ea6f95e7419cf2079ac7c917d388ee338707379a8846392f40d241cd3e2987602dff07f3510f8868bdd9a7139c61b089b7634bfa783d7921a74d139ba7796b72f826edf986003692101a2eba8648eb87456b2110100000001131500001808060583e190583c2e9c27d51e14000c61804084643c1a09a4410cc350c820638c610600620000000c0c91a8005bc4b75540a5a7c72b1deac50145f8dc4928dd364c074088a269ee6dfe486a1253dfae4a91eb83683c7be2abe30ad51f973c7c268c732e8ff712c14d2b6159b51439e66fbb7210af987b982a00826bcbf7e28eb27efa429df022e047c4271939921063dd42feddf0f2719645a76fc4a962a073a7af70baa8efb54d55a27d93719c641f8c33029f90d5e58d35719c62d7755351f582d2e269d3d628a6a751ed31a34f8240329c070e9648c5802a045db863d9cf40445b0c278df7d0fdf26edd62eb90caf0a8a0a1d69d84882dfb62ffad8faa54d3307de15dd3a4cade71fc11a4c9fe03c21e02fac399b2997cc8a7581f30db8a441934184df0eba87bfd4c734b0a3500e2cabe6d7e2587073cea4c712c021a900e439abe8a36df876321bd5e1dd189f53fdb99d07b13dd5be66c12e3107a6e1fb13d2e9aaaa80a707b64848797408927c17d4278565c732853861a9b5652083c50acb269abb8d77f5325231858e3bc3604e0cc020993c41205628091b626e07eb718fa43c37cb4e2d484f935d82269a29d0c2698a6213032ec325c24380ceeee5160a8f8307a02a297966fc2f63df573a80452eec54da143bfcdeeaee51ec6f06c1c04c9b27c675ce3ce96b1300961209fab8aae6c6968d96a2ef24ed067633fb5349caf52c366b82f5361003c7d5dc4f209b46aec54ceae2bc361ea71d77f6779559c9b30548c205520b84d0c6c00b953d273ccface5a88ff49f5320dc4cf82f7e966b2d97d82ea59e90de3eb2284f69e12fd4aa8b8f3b63a290a242f139a2bb71e7fdaa2dde223820750fbce770f88ce7ffa4963bbdccd1923811cb1a3310872841489428b57cd37e8bca7095840ec583dc08bcfe2a9259390a06bf59c1972f6db1ef75c123888d24db519f82c9223ec9311750ca9039825b68a01cd4888bd54a844d70eb0c32e7366dc4dc32fa794fe4ffcb78259a2e16d2c1397a38ba90c9bf56a2f4060c24048bfb2304d1612f8b233594f77fc494f62a29c14623721828a1e4b5264090272e3efb2145450631bba11192cc5da390cab66c8041590d50a9bb19390378271977000321aa13ec11b9c6b5ef849f56990610c2891094cc57ed207da0b58ff2630cd3030a2343b72fbaf7863d5c02ef0fcce269c574fb398d5f37922212a71037e680942eaeabafaca5f8d22e00672ccf690b3efb177ede1c8e0241ea5f6cce84181921802f9e88ac7db43133771e6ffe87c5078b9880fab08ef7f586d6c305f763c9753bdc537bc200d22a1b3552a06463d1c1ecbc7b1418fef7470020eca9b44e47dd34f56179f4d3979cfc99ae87c3d50a586b4e45b9f38162737f84268c7f031aeef6deac37571ab87ffec269b6622c4b8ebe1bd9bcca9ecfc587f01d74ae4205fa715b832ff7a18ccd52caa79ffe0bf8ba31fa94da79c386099d32273c4b492d095d0dfad08c5dda8ec265996925e12f3dc56165fde30179e24c75ebb34ad4d869ea5e7d04d122e6278686dd34d01192f098e5f11178f25da24ebc534943b0c54da911116a0225c41a7bc2b1ddc80969369c512d042a0983f92fc084a8ba29eeb386d4fb08afa396330a5009b14ab80775aea993744276dcfe0824b6c90a3c105ed2385304f95525c3c7316f5e508b3e4d48265a737cc2b2427e2d809572cf6190871215ddb9b526e68599f27ca8c7e9acf43f30f9180aee798c64a33295cd17b24a643b62ea6ae59d78f71369b67de7a77c0fe88eeba008439a19e32ef7b88b2095c2187ed8a5bba01de81222a89bf6167a78184d1e358988615b41bf88485c56ab78871c2f25a76443eb9efef0bcbaf66041011f07b8b71797d5d82c0d326c4edb90a633f4dae21cfe522bce3b749ae2a19ea31fe08ea8cce9ca9d4c17157ee00b22931e61f68d277b4ef9a623f4cd7ec43c2958fa53a43b50fd4a3fc47bfe7a05a39e5be281b6f58110248ac2606f7c1e7848cb2b5a0e3ed9875d048c9e8e69af173ea71ff6910b7d4dcf9fadbf1661c5db3115cb9cb1aed85079706255abdc40c69d0d2810a2a1ab821f4dc5d2a141f3c4a74ffb7dc1fa3337a8e53db2777235f1bd902d7165f482f30ef08313bc4d8e827f8abf1df92fb57482ad4f85ccb67ff6254990853988f8e34c1ff7c7c2cc1a8553ceb6f714dced694df8c12b789fc51c671a3d56e358cc4ed05c3c08be80b4322d7fbb964c450acd41f24390420649b61876ca9e84b4dae0b44ec88662bcbd6627f1f9cb43fb4406e7a4231e6686635002426c5c5cbdc10157e2e0d03896c2b2a85d91c7ea3a2388270c6de417c82f0296b6ee7ad0a33c5175fe4bcf5c0488d85c42924c9f66caaa4b7cb5c3c486ce6c1e4f736f27c42c10810ea7e52b86ca09826317c1f4e791ffa7f970d753bde5b1d8890e2110aae3e960ae47bb62b05c7dce68304d4478e444d0b0021a16dc5c09e560d108b55f9a83fe4ca4263c25bfd1ba7a8cac88314099149e8b8a3302525ad21e6e171c4857023c0d22425e956613687fb84caf0e1085f04a5dcebd05aa14149bc9000f40ae7d0a979eac196b3ea77cc1e900472d196d9dc10322a43a4b63824b1a73cfcef9544ad41a9f541500cdd601ccdcb55239616fe2cf20805af10064c9a2c103eab785fb5afcaa0288287402fb8881a11ce68d4acd689fc170f0dd4df4605deb214bf82b78db0c5dab8a95077a0c43e074b61820de2d5b9a3d319331124705fb27d4dcb192e0403cc241667f512595d84bd36f1fffa60f0455f9ac022e3402552c38da214794ab8f1f925952569403a2769c7dcc4290de6d76a71702b2d5b8c26662793abadf79337f21efd7293cb1d57f27c43e1643278f7014493a6eadebec9eadca9c8c8337e69fa88b81601253b10fad222b24f717d93155181b51cca11ba3a945fb686b5e81fd95feb5aae1d18036c9d58c781f997df3afd81d4c32b8fd02498cbaaf172d0ee81b76bd4d4e8d6f2edfdabf747de81b5f0a0ee42841b7f50dd2e6ca5f682e69211372f3bd5ed3101ad0c3415cf89f4fa0b43f7049528721df18c30e6bee31e41b7dbd12ab574ed3128f7bf85a1e0603cc1611690fa946e93cd34c0464626e8f1875a506b2264317cb3a1d54fbadbd5b0b2117bb166dacf872c6f910da40765a58474ec680cd3a095990db68c06cce2be4490b31c7c2e5c63a1a61a95f9a026ffed3669fd2ff1cd5b14cd2c97b97c05d12208576bbb3aaab596342dd5653ba3892f73943305d99ed76676ab49765defd56c6444a20d36059d73a3467917a7462baaf8c7f5864bcc84a436553d58061691eb013920b0ff338f4af469f67cd8fc77dc77b15a3cc7abf038185113104ba82059b8b47076ee02b7c3a8a02af80e0917107c765d0a9a602cb280d28b8a1f1e37e678b1d6d1dffd1347b2433322eb500ac5c669a3f595cefd6e21c7fb2cec274ae8e60854636543912d940011857b03cb3c9e40071656d41a7ffa2b2c27ba5e45e73e406acb1722fd0611a45d56117ab5793c0b41d7b8e0cf58a768ba160db3ae9670ad342aac065a4e48b12a8648c3b2b0c52498e3aeff2091cb30f630f9f360f533113f92ddc8d77f4c7184a3950b9960661865dc046af7fd502e451a2c1ef5d02ee69f95a2810789004dd525e386936d41afd48aefc038aad78adb8fd03b3b28580a296c543deb10f79af3a1cb06670df1cd083434d9e1baaa7105aa1b1d3c737064b21ba962fbdea89fbb4af8d4d1ca8045700b6411cda7b616d1b7a5269eea20001de27a1d306c87eeef553e34df3cd071d078ea81ef12f9b34ada1905a9bef9e589e60554abb265bb1315d20c238760207929536f6e2ba2769cba4c786d75f9f3475e1b14ea60bb0ff131d7599c205b8dbfd92acb7325ccc4b069d0563f30ab50c54812c66f0a7b0eb7555c3105677d10c2d6fa795b06c9a2b6e73c23be86dbb3ba01fa702162eaeb905cba71a18041a138899b7efff68b82245aa071c00a3f2af2cf5a4f01fafb2a3b6ae0202394cf177e37d1c739a6998d2d3ec9e60706782ff3ff22d4de1f2133e0b22c6c04eb6085217a07002426f234f7207ca3bb5afcb0cfd66c61c8770aa7ccc0608c53c43e753cb300cbe0d3a1c2319b96fb9c510afea4040e9285c320bab877d9d0af1580c3498e6a16fb58566dbe542cd0fa7d945d8194518520194d2f26278ba18b76b31a4a74088c9c3c35060c85d9007a4707b6ee0bed053c580d2efcf5911213e2300b0b510058f2d040c9ddc2ce13f8e26063b40f862245ad3920a320fa5012ae625435b878fe65e367265e5e35bee89b3f612ba3fa1465a463c141c99e6da0250c96a9c2ff6ea8871bcdb4ae49448a0f033d9ed696bf24981cf9efe568edfda34912c0e3ffa3df8fffda3b699910c4d5c183d98d05bd8129e1f3d1e0fe7fc0d5c813375dcd1b51e0f4f8ef2ce5234a35051cc4c0c93ee922e7645c121ac28e2c7728129633860f383211aeb1c8311f3b9633dc0e506ec87ab243dc6acc4f05ed9f2da9423f54791deb92ee12e698f3857b4b0f7afb5e33307655cee87cf1c5757db9c25cce8c40aa0a45331790844606dc4d40a696465c4bf79c7d10dcab3f7b8425cf62826480cf5e108686b6f85d1f626e12a8107026d1d48d56b2407b22b34d4807efe83e83782bc1468dd24ca44f1aaa2856140929b19a24885d06161de4802df2f01c2f932fe726068cbf3dad0baf78b193bd59c0923acc2590b53ae4f915fc0a22d3657d085139dd3d52c45feb687c9a03392cf530bf92095889bc942763396398baf7a754b0e2539a16db4cc63adcf983900d2f5cf077192ea85ce7c3f94b202f32648055b102a69b500501ba4cf025ba411714cfe7fda756d812543b31f7920efd20968bc0911faa7c23a24ad8ee628eeee220337093e246d31362a87d746bdadba15ea2625f769d93753775056fa3cbdd5bc36b1d4198e8984b67a31d8727720d3f1351916ec303c58efe0a0e0bf235296b3c8fda15d69f8270263d26de3ce55556cb61a3c2829c4241695c6874c0e6700334c567df1a25fd379579364e3d563ce9375ea227f13f027e6332152580f80a6dcdce6bc34e17331ad311e9d76f43a1dd64e1bf8402b6066e5f9bc2ff61cc4e27e1469e01f9c3cba70112ee9a4fbaf73a47445e9913963d3c6846c16d3307af07dfb56d9d416324d3e933bc4644b67a61d4a1e4e9dc3d66a002207672bc3b66b8cdcb0e09af3d3b97ca754019f934b61541cbe73d7c109f3f9535cb97a5f75d5c086fc2b4ea0cbcea4bd2284b609d4a5718201e069000ddcec080ca97f8f7fee1dbd005cc8527250d8a4bc1179ac1d74aac425093c162aff0f22605059b7994783f3232f4dd96c5bb1fb3fd522d2da87bc2a28d94e5ae2ddb2a6c6004f2a51dd7c0c3e79640de48147ef7a0f70c3272916d86e3939c8d83e88328bd11127d56b487089a977093b4e2b676cad27f44dc3f4d28139a4d6d81f5a91342974a1ae1b8634396d64c634708e28e48d39df050ec23d228cdb2d45ba753eae3f81b3282838d9c1ebea26a5d07deaf56f2fdef6d014b4d7621d7c2797c1532378df8d1815ef922536e52b1305979a9a64bd3544a486f45c39afe07a8d4201407dc16d2407cc31b247babe960952c56986ee75c5c1ba142facd46615372e9e88a360f0165fbe01e8ea572300c86cbf3e8fd1499d999163cd8f620e21433a520b80fd80e1557214b7a644f09f897091a362adaba63c5071896bebd27e1c9268f425239540699e6807a2c23136c082aafc4c29502628f4bfdf396e852a62522538c0780d812d3fcff8991528c5ecc10800607df981879205463d8e9e2bcd776c3db3ea0bbfae2b431f324fa3d1636b51e554f219273ece699f8f8088bd9757390e4dcf481fd97414486aecc9ced42c6db6c2d68303366e8d51194e7c443f5a8d62783c4b7b21db259b9b52b777db591cca8e2bab424ef7abea063244517d78ac424b1466e8cabacdae2d665388a6f811e69abd342e1e9c55516ffb7cc5924670db1c26c9932a7316a88a4b515fd8e2ffbc378d2465811f892c80d743e5c6206b1c0c5b6cc23f0bc020d695d64582781fec69c308f352e89d3fc132cf19f0951766d7875d656172dfded480a15532c3c5c741836c39f99ab258fc55774a35042a3dc04b5c1d89fda1f0da289f1296ed453795248dd9542bb27281048bcd6aa8f192f41bb39c9ed3fcfcb65a3c28b4f09ea79940efb07de19203573248c986c7746c95a1574a9eec60078c0f6dbe5a4b7611390671d38d5f3181facbe02a09e7a3f2bf1c276aa60be6d1fba3124c272ca8c67701db7168dfe3f3f5f7faca407b43cfdca7d442b390eb595404e51726c8733da3fe70154f7a671bfa23d03c286b8aba37efb31977c4d3a4d6405463e2b504a896cfd76ba0f754f3e801bfe5a18633422fd50ba93c24302eda7f820584210c54ee037d37db88b24cc733511d1711e5530b02101f3fe70cc297d1f2977103e3cc33f7f50c7b830bfd2e485d5645ef17fa1e98d5dd430a7c3fcab0f546238c6c6003c2c48af161ceab17973e96f58e256782d8deee55172021c039455d71af78f6f4a9b93e73d8e665b94e993710bb710854c20aebdd021e6964d1909a875df30fc3755fa9b61e29443157998c09c1fa8546cf1202d8c01520d3e9a63a1c538de76203d87ad2f673fe4d0ad34d90d04fe7d4ccb14759e339d1e886cbe9c59c05f73c478dc5d6d05d822fd26e9b137b42a97756507811d77b89daebf9b18e7c6bcf0e0f08e1c4152bbe7860af7515db364fc86436f315ec87215318ad64aa9751d0ed9286bb5a9f1ba25b42cfdc8d62a58655d82f200e27269f6a43c8eed9a7483e24ab1c62b45ca509e9cb0b0f84819cd8c7556ed2b8ec83faaac3949d56c3a2a197c9f53970f2c6c37af0272c8839b2017316b4d38ace2151e9f3f47893dee1c6d61c31d24449c1bbe9a5d667f0f70ac9159b9171bf2e221524b470c24ea40bc16341e0fc172b6d3172488ff911e9b5875a0b2626029b8419d76aca1031f60e323054b5eb0e0c350642731f88030ad1faf64a6a4076f1f40e9ced772881da70437f16168fa5c3fe4c36cb3245a04e56b0d195c6b8760308052cf8931f62fe34ff28210bb876abad9210731bcb1e40f6e126a82ef504461f14788de7870662695b53b2b0f75b58e7c20f6ffdc1fe66e78fcbf6390f5c76d10b89b7fff00d43cc1e0ca9167c82522cb7a5e1c3b0b052235d5e96fc6c87f58310fd0873bd2e551b8a5e198cb2a27600f5057b94a474fda69a6dd8f10837b6ba578f561e94f135aa25d0b508a58bf7afd30cfdbdfa5544192f6c7adc0055db1bbc95499e227fd40b1ce63c443fa438ea2b72d7ba454c6f6120de5f48a79049f4a4426c5253981a4e0d382a4a965d3d0fb769ef5a5d6d3d6bdd28577a3499ae518c3f3dcfafb4ae3b2011dfda41d042bdc10b643f1b231d11659e6d8cc8ac395bcd948932e81da32772c416ad932f0db8c4ee1aec87cb988c880a31167c56777e7851648a451d7690089a01ae253995c2511792f4adf9ed67b40e60bc179c7b8f2bd51afc0c80f7631628cd6a126fa6c5d1c66d568db8d32f7f6e39da066f5c62b9c49389d1c9ce6a70c6a41293dafeb7d726cf24bf0d9240b0231d628690802d50238fb5d01a1958150c601a9821fcce1c9aa2593fc3f5ec53ec007c1cf1c7bf2fee13bf71a34e04a4ded34dee000cdcc9f7b51166e4a9a640c97ebd2d4b287525fc226a5e1959851825639227f7948b6e1cf3cdbea9398373f86c8cae16429426c462331cbbef3a338a57375479af28b6078e1aea62909b4dc30fc2bbc50843dec6f7f28942283bce03c0ea1e7992ee34982e713d0c6b11ffd5e5ca23296f27e97ad7714c8fa414dd3bae9c205fab2d077e6349da1e40f620ac725f145565b231df862fc9405c58b2d507a2b699935287b860ec7da0150b3bb93092925c232729ccc1251a0737b6a272b82a21a65cdee1f15974490e8eb826a6a9b57f0940895824659a0fd44fe73be6101b7ca86aa235dd3984a65b72cdb6d801499f1f4e14fbeb22827f4eaaa5327e2af6d472faf5ebd9c9d672d221c251d89fe619951cded6cec9788680e5ea9b617f5e667670adc80ebc230f4bb9b1aa1e26a73fa787c3699b244dffc88053cae09c51622d0954d96ca725a7f1f196d7e7c5af4da93afb134c6336dc4d471fe287701d580986d5e2f9e7ab6413380dba9aaa6b469e4f86bc82affec94353ef1344244873851ef8ff96aa537a6cda05c3f9dc0faadd387c0dfe1c07fc775a0f32dde5c4bdbec0c6e454a172de29f596ca138aa27f77549b24a193707289be392fb54524394f158abfccbf0920ae379963689ef1a3819932ece9c585de74d697bf0b5757cce238c27f5aa7c015500fc27b751339eac4655158f8029a87ee2dd524fb7cf48371d437d7507a664e835ea064c0183bf90ed0d504f11952aeb149d4405f6bb08594b491c4b891adc52251c800a6631b2f458bd3e2fffb06ad5b7ab5739deeaf65f9ca59d8fe8a1d5ce5c62a378036d148ccea23351231951cbb37e52f153997274782f33a1bff0120698706e508348484cd6ade31e378ee644040cf4b1e94a062cd992696a782938ac494395e597356e521cdee2aefe7b2915e2168411d38dd3ac38c72f4aec8651b5faa50c5d0ea5417b75fc5a042a38c5b3306c7ca817f50fadf12602d0cc1b904e747fe73ee15ca4f31d0ab20911ee0fbd66c972844050aadb1ed586fb23b6beeaeb80353f31dbe00c9f654189568ab15beb542b692d4c60d17a8c01b1a4df9440e41abcd118d3c27b627b78192eb976effc09f3f911fd4d55c4e0296fdd250d4dde80ded3991d61704414efc5de9a403852b6c4c42c6f015c8e75a6fdf09bc45070e70d189d42eda5b20aa431a3dd036e894e60523c20091c0696aa367fde0fe6e8011381c49b84a0a050c3c200f997b00248f1ee8dbb7c26d7c0a0928128f3aa103d40d548bb5bddd4b873a1a799e942d4e4d4fe9080804574149f75ec757915984aa798b75522da221fff0e37269123ced35d12b78ca6782db74e630458df7f4942fa7b910ebacb38ab4f45b572cb43322d9305d409c737b6929d0cd6fc84469134c3f432dc1f420e092a2b1e613a948f502a5165f781d7cb447d97c82a9ef0a94e57d827e051ead65655265a167b8eb4ba198fa2372913af11f12b88db454dee48893c4be33b40347ad01a57e2b2345dd483e1871a34e6c57d36af4b4dd15651891bfb71450902334c1fcf33e97c749e07cde87cd8adfe233f02b5ca7cf9746660fce1b5c0f09653979fec34b3bdbb86e56df7663cceb82c65b75bc31233df99d32ab520f4ac9154a587ea42f7fadc824479061fc095476e384c73722962c88c394968af26b1189abf3724bc4bfa296f4245741f68ecb71ec5325171fcaf44cb1fff56ba063234ad465741a2a11e6e5ea1818898bdb7d37517f6f16da61623641a4e26bd1d5588ab6a9c6129d5ca3a4259b943d295427dfa0b9baf551f6a42618c906118511675b49378daedf12abd77de1975a091e6a8ce98a170a3205b749710175d879859b457ec988bd89f2100eed0a9c02e95a0ce7bea68d5a18dc223b08d0fcbdc4eea3438e9cb310f72464992be3f5fd45ec6b3cbcc3a282cb0455f91d6e350169ea9045aae6e0b8c5dc190511845507f105caecd0092e6430c512dcdad69d622a6af8373511dc027a65af7e8f3d3f52f6ed97416f8717938837aad82dc000b797f739bdbfefc1e0c740a49b2e53df5339af86aba1bf116d34094ee24db37a0cb16eb06f6edbd451c7a5122d1688702a56c18352d5ea017838d9e8df9f33d8e51e9c80e7cd84253991de7f31b5990e44c210199b5066ec89b61d12af912333a253f76615b7eb899b29c8df7b591740f7c9162b20038a6342796fe99a66ec397519e602f352dad809548bb3a70403101c069c3cce76b40a202af446ef9145de16e0d33c646e9e36258f9a4ffdde85c3155603e59c6a07296a3b93e0003e552f96a6734f6e42742cf55cc1d425ca2de539ec2c4c9c7e123a77e40791534e0697e3d4482716dbb5963a051ee7e6e381d443ef62e132092387350e3cff79c22e028cd4dbd70b150cceaa2cf7449caa20c679576d15135eb22bed8a36f893c6c1848d96b89fada8850c29640b639fe6368975bfcf863c5d27b59422b181ab0ac18e04c57a1bdb2a98a9653d16f6e651853137c7a1b546088cf06530e1f0e17c0fa75bcb8f2f4ab6c29fc3dd81e5dea0a4492573e6c0a29f8c090ee7cb10eee41c28c7f79dfac954d95b8e7ba9302d7fa57f2698b0a6c496c77b483767e423778200c2fbb5615ea53b751e20daaf9964bd37bc787f61374cc20dd36482840401de24097d9610e2cebc2273eb99c866134a25ad804b24152d751a8596914c2c01c5dd76bd388121d2171205614445f8166625ff79ed11b425bb4f50de399f3fbc3011c78da96439c0a85db5ead0dbc568fad5763c127c3be6b2a8c2f0ca463acb8969fe8480f289acac064b8ce5c024abe6abe476391f258a051486d35ca14ed4988a94ad0285dfb60772d62a2611a811fd30002cc86a296752d1dbb7bb4c6a88cdd9a5c90e99d02d6cbfc97c64dbcba749028e38fa7fc33030f7f6e21ae7e680600cf4b2a765fcc4b740ab6229776e1561eaf196adb281bedc3f8d10efd1df336468af4f04438d94987b88993cbdf7f143143dc6636a7efdcc838e65213a1dd11adaaa4fe2810ea161a1d2cc154c3c5c28d8898a7b8b70344edcb82ba762bc9c8b35cde31d785c59a2122860b117b3078339018d3c176b6f07c8a5b2b41fbda86c63a48f74cafc3d0d99819debd46a724e17656abfb21d20355cd0572a5f85612ec6f2b58486c5ba80811a90b9c69ccc90cd754844a53e49a3f507ccd51bd2d8952de78ca474df3543913422312024012c670e880450cb97a18033a464d5356d57d2f51f2bfcda01e5368d8db7c89916c773d4341f75c51a447d420089b5e6d23bd5eff3f439e7471418c0d8b68deedaded2d4ba2dda0d0c995856196d7766ac0a1473f5cfd9ef411ae7ddffe3fb2e76633c5d17d9eef36a420a236e6ab7159dc573b5156b91d4ac6687978703fe02861ab9b808f7aac5c5bbdb137e334e4a046f370c4ff51088ace0bac5c232a250bdd29ac26e8d7db9fa65f93a12203996726986cd1c0a1458aa42b16e43ac1321a4a31a3964e5e0486aace2bdf936f2bda43402b080b083eb1200b0894a64820c37b312138815e59abd319d121456276f22973290f406c61c489fa920d75dc084cfd5c1aaf7660f238f0329f8132369431b5e48eb67c612a3e336cec069afd52994048045ac69c7065c410d6278171e99e096a04e2bd23213532b970656c5a06b75906b73b52f51c277d5d63e70c7a2e8ecaf58c89ddaeaad68e40855ddf76e04d3f1edbc88e4093d84b3cd0fe67e88b983fe444bbcd88ccef64c2930e1b7aaa5be2cc90122816d0cbf48f0fae630ff71d621fda69d62042e006ad96a3bbd0588a19e65d1c8712ac8b587909209bbf5f009fbcd2e0935628a4e999635aa1deac96b4abf7272905cca58102d8aa9a8f80943d735ac52faf6b816aa35e868306b9d1fa2b56249fd644a23f49c73f10cd6f089bffaa859ebd3a1694b8b21cb5b0ae9f9adc17c23db222d00e04afeb2b980db309fa90700bea810a590d23f809b03eff7d30962bda5fc942057595f79e2879df7eba0df55ca77453e84a7115b1dc9427e9c6dcf20c99dec66e6cc34962dfb81a60498a40b84aa7b3cc04b973cd63b323ed1a16121d826c572db5bf4e7cd8a8dc5748741eda69685280d8c3c416cc87a983181a29a292b201e32f40ac3aa6d035c82543e2955ba01d0f1cd355b3035a18b72bcaa9a3ef6544de4a3729542ab11aa9590ff1d2f1895159cd3d46c008c608b0c940abc6f6492ae342c7e8c19072e896e93516d749b646de09cc355385753472a71310e4e5627d4bcd9455855c3ce353cc0efeefc5f318af3f731d0d26f7dfa9f308f0fa942c003c2fa7217dde59b746926d44e33031d1ecff9574d0b9e7f2e13537b0d167e903fd5dbe050e1da40618e7382a709666bff057b39b5373a96c1d6aff5a10546234cf6eb08289cbe8606cf75eb41eeca09184bbbbe27c334ee08f0401505812346546cb03b08fb6affb5378624228be5211f1904987d4c6376b8191e58634fe8294ae97ac9091dbc67018bbe695492668c02dfc01819d98a25e3b26be8c11891250211e19d80764f69c64d241c49a7f5b5700efb0e0218dbeae2ab0d41c6959ec1f7eddafb5c19221e0e57944a892b6580b8de6eda32c49fae0e944e8d7adf8bd841afa058aa9c69b1a44988ca0aa1f2e2a4804334c581c96274917e99f3098ec0cc8ec29ab4709aac9ea68a7e9a5cace3f1725010ff310979cb3cbfd06e3de92119c9f3831fae082e3879909c50c02c71dfb2e83233e1b6f43c0cadb38f62cee202963846ceaf67fb202cb1e9674b19abb2ee12a57e757bd8707f150350b4a52988de0ff06ecbd83561cac68454511ee69cad9b752aaa851cd63787db35e6ea54122436a4c3564c2058640dafdb67a0202c522dc68d26233e1f0702e8150cf955c7fc1557cc1e90fe3bc52a5750d85ca24f9f83a0576994a75a300aec370eb29877b107c8e656df2e7b990534b0c1292b3c4f5037f08d9a7e265fabb8c0296534b59b1b364f8e6203173d67ac9805d1011661750192f3907cd633db59a5673125d4f7c323590187c9b0e0f043a307e83fb9b22722334912263c16177a9bb3dcdedfed2bcdf53c5d4c32c8522d75f76d74208fac5f80259d1300bb2730c86b93a029db926ea0f0ce83f45347923ba25ff2727ae66971c9c2941ad5dc4025428db6afc1e7b72d7befcc6db16976584a2c66ae1ad676f6245334bf4712074155800dc048cd340c0dfdce0585128d1d53e0fae1d68229c6d70542413b1f4e5a4b9815be942c537ebf901d98158dc970a00ef63e77de541172f7da53678bda02c0aecf2c121a40bb9a91bde2e84442177e397264971689c0240adbdeb4e83aa18eb0b9cf6b3986c8a39865e6949231710c6e720cd05a5d7dcba28e2ed5f934069ee0b70bbdde8c7fce7d2ea26825bd40b5b3a04d3b62c8708898637e613a22e4e351377c95e0851ea25d85b9276850852e80b2c69ace512cd2463493a0eb80b91f2532cbfd3f910c1b2357a468dba2e7dac6414ce295f36b5bce993ea757d450f78950c1040ae921e1838a881fa99ddb27b8de1647364cb323cc77febac0c4d9d400313027c21d205b02820e85a0942e9e4047d65b9597dc26e14b95e3ea1586740e113e218772e28c21cb8042bb68fad17caa1536f8c7f54d816a02ece6e7b0bd09d1c487f32e55d7c200e0d036b473f45ace576a1de4007b639f3fe2d7e8d09e63e6c04a0bb682cb80ca0918b71ff16ae4e94bd4b2aa600975ba444477c55798d321a177506d5861c9617458a73d153b67857c541f84aafe698cced7e09c6600205f03ee14646461c63527d70843c690844eb7178b591122f849cf1a5fec5bc0f07275d5d102a440e5912f2f75b8ac5c11e65328970e9fd410e55cbf4ee0850daa696581b943869be35e431a472951d7c145b5d6df832c61e5f1fb95428764f2f1f1b13582570f19f7f16b1c275a33a04b370ff5e202e7755483fa0db6bc41b142a431a7ce87cd09d41154022c7e36581e3cdcfb81cc9c20aa2f6a6e362c766f4aead5719f2c3fa95ef972bd1fe7a500acce6c360455cc1535692a8d34f030aabd67b52ce855d67e3457b2089e8d12f3468c05403f414f580b41a0e302c1b8894a695b939564858c5c7b7b7425e1f8eab981acd58be6d2094cc98668ceba21168e6b6bc94ff870de594f041490ac3cc09e5fc80c818c95563361e5fc42656ec689cf182e15e4ce3fd8dee70c89afde7c072f5dce544652145e04c2c9f1e31c02299390224b68c4b44c3a1bb5342cb491900d0983b70594c132176b9dcaa69271799715a01ccfd72b9e058d9c991578fa81aa50f541a55db386a327fa6af400548ce9c2fbf495b9ba520b5df6b4fcb5369862a0d4f43464379c3fad7a6802f75782c5bcc9863c5354536f0a4c090951511415cb22d7d20e8152576efd513260582c2a1d8754d61515fd25b0d4e9ed72380b9e2683e62a3a92c5efe570b8995fcc9d620c46f2c1578f3c7bb5ca4805b3fb7984e87ebd06062b5b5a68fa3ab60a0dae27c1b5851e337d0f9e01a1f2d2eee0d74e092ab282bd7ebfe2bdbf0d419a8e61c615fb8a819ec759d4c0d114f17306584c9ef46b16f7761a1f0ad8973714897e3ff8632595af32aa4839d6d7901e103a5f1ea6a5153602d2d416128e927fd978eec93a40d438e5bf6bfd535dd8a13673f5b880a9f5ca5734526a4118b2b53fe96f8779ee452b36d3b98b08cddc18e2ff0fa1366003ea20ad894b4bfc2d6d3c73f7d4ecf32850085c4b6a0d97e17ec7e2fbe12269bb3de3d8c8d0d135de8e9f97a5ba007f91399e1b30457beb9e1ed01d2c9dd9903ca645f022d28b1c29c4c80bf65b404c16531f3ef30cf5297c8cc81e1907bbf29c463fbd4b997fce4a5c21aea9c78e732ec67c3f551734bcd1388ddf7a5a7bb1c10878359805e605fb4fa3bb28aac12ebf267927fa7e776656d325f74ba29da5c2bf3e97413234efabcdb51d309c362bae4cb685612ba04ee50e7e9fac94df9445490117765325d9d9cc2536f9576f79e0b607495e3e5a75285a0adee504b1a78e65edca0862f2852d1fafa4888d098df513802120c26800c93d2d729a0722ac8b1d43bbdc9c61b8b0d61551c6b3ba6d9c76844f1da1399d459cad53f660a5b08f37bd7b5c60f60a85597d7f9bca4a7928ffe83e1faed5b40776eb974403f42477c91c664ab12a5b8625cd0605719f1cc82b9d18a2f100b279901ab9f66eca211f23d9c51155bb0d67bf434fb9a5e52bdb13c148a4b4f5068124bbb1fe18d3efb69612cc1b021d7e7859da69f8af25084406f289bee11e19bce8fcda2beaa94e02b552dc890cbd8068f04d6d759d916c1048bdef77a5fc3f3c1df687a8592b0246f0b476c820d7a01546fe5a2f0fe1d450eb551a492b65eba8840174351856bc4b4b1af8062263f8685cb81ecfcf3adc661ca2f34dc6089d3d173092c634266b9df1a8650e8912545984129363f4db3caebdc130a758209dc38777ad5e759d4039ac2e68a3149b1fedf082deb1b436284a6c557a6ea1b0a3ac7a10206e4d477efeccbfc3004ae888dc3eaff9ee62b8248c68e66b66701c0da40151b2c41fb02b3a19dd0c40b30199bb13db63e566fd6af3e3812f469ed23341e3c0972cc6e1ac0868ce0043cee79f89922b14d4b9d40a7b4c5ce184f5850d4a91de0e6b0497598062604245d60b27a37a27eb37faf6e801a04e89b12b080f74925705cc9107f29085bae3a8c5d368ac4e1a76bffd6169eb1f314100acfc4923303f72a1425e9643ffba82c3da63b90ed822eb405711f21ae1915f8816051ea837bd335bc10c6a3aee24e0fa4c1323cbfee3062e1b00516f60e50ea2758f725fef3a27ad16fbf95d7958cdc20b5e81a44aec8935f4d17116ab8f59a24c74a55746075848b991017651d3d1a6b9e6185e591c0bc206856988f2f522d132cf7428c2b45b678aaa38ca5d2e99a80303b95381b8d51450ee7d40c4f18cf6428a07aebaf22533ff86f8c43c192df3e659320c71826e4060e945c449d008113db777a856e9c89050adc8a96765a74fb13ff8853936d1d60d98d764d2d07c0dab6f795719c8283d048424295effe0711e12214489a970351b55644541beffaefc7da8c1a86fdde7deb735ca5fe3123ee03034be9bccd4ff3df460a9e8f31be0dfb49116a5c3dde10d9c955479994d3fbbdf7051aa94da6ab5c7391dcfd2903cdb350e40a2bde5710bf582eeef32128e3dbedd2b20204bccfdf0b57754169b31d72825dcfe0493f17206e6b06103c3091bcf693e0868183eb34f80f00763e06f04e2e60abf92f5f8103e288ab431266ec594e0d80c68dba450c6ecbdf7a0a84d4dcbb9bc5b21866364e0cbb6426993cfe5dc03a5e61a6d62805d8c2d8cc44f04b4e07fe2b97f0f608bf43d47b1678e0ba9f5095e5340d492a795a0d9de9c398a79a9caf105763ad6f0c56d492989fa088729fc1d62b2a31070f3a3e7c7926761884194ef95076370ec4ed8bb2abe0fffd4d1e8fdc7bbff8a912e552d42e44e91bc82fdf8878098f5eaf1bdd0d8e3f4684e13a778d0c5fc33ed7f048ca2f9a82e9460767949f6c0ee7fef146590ecc664b28e28c21e309a0b004974d44447fd1ff477ed9403e08f27864e4d354aafe3bd8152a89610276fb71342a507220493e94da9266d2e0ee186e0a492695111dd17793594b90709daecf592826fa480300293785fab87ad630c430163dd2871eeb1ca218d6d84d52d58e8ddd4a6d510444b90db32b562d4a5e76a84c281630cd80a3c41468757100ddde4d28bbcf1282038984171203c268c697741218ba0bcabdcd5f82fd377a1eac1e09f9524ff0d72a8d43e5265767cb79ee7b25946abde2ce090bd38f907ab823cbb3ae3255e9c1bd27c8f4173cfcd6ce1136e908828d1199cd6553dcbaa45cdbbddfae2ae13270a5e15b11dcfd28c9f040b76e1d189879138b807a116b00abbd1850f56db94a9b5cdfa1c294ac24811992e510c463a31d8dba010494d8b77700b186677e94fae402bb9f69ce396320c1412f65641314b73280580872468d9671b0c546ddb8b585ff76210e0e028a98132a098d1127b4bfd7b3488a129bf3111144fe1ad63e085f9e63c6b6022b35a557a34cbe8f8d82c58d6f015ad29d46d4eb607fcb4bc102e3cc726d153d3c0657d4171dfc39f3ad31962629769fb432aa010e43a5c30b4e71c0d8ba124355c5b2a111280037569a63eac424322c432758f7a007f0962720ecf5915ccfe1c01eaeaa75969ec2fe588e808da49ce8135f7d4c51d58ccf37a9b8538576afe6218bc020731ffbbbfd76160d546f570d58bb2a9131090dd2c31967d66a471c1ff64f939e38dc7b4df8150bf4be18b58721ad96a177f577c3b5eae7a398e30dc8297b476aca74ca8234b076a5afdf339381200b8279e92e98bdb4cf4c0aa48af634ed738210497e6d000eb99cbf1ae704071a6c431c9abc8997c23749070d53fe4fd53838acb06aaaa79aa46af9c564305718235a758add978bc65d315bc0d01e5a2511b4bb03c3715973a4666b9cea3cd9b783e86ac224443eb948c2baec8c99d18300410cc1713b92202ca86f88a2f877128a81abd3b08057c7c25ea57f2a1f8d9bb974ac05c7ab0aa100df66600ffbd3330ce099be1dcc11f846ba69ec08b0c96b6649ad5fd96e11634260ddfccbffc49665abc458943444d0b339c4c445e16e114a3b6ac5c498107ffca4cf541b30c684729c16adaa0dc2460b8aea248bf7e19843503b9fec3bf7f1c6284944f6fc63016e6890f07fe630427eb0cb471f191e238a43f194941738a0d19930e8da9a553d3c681673a832e013911cf85407f0c8dea55c91b47c01179f94cafa55f19480ca240c7b5b1f372b6e405bae9495d32e36b698dc2ecc60353d114f89ccf724414c2d28fe8cc36308a5e6d4116da7f59aed50d38ba103fad4bbdd6f183ebef044732439fe0963305ea5485022c199eb4241ebc6f89dfce44bcae8f927d67ee80467dd20e4ce5955bd51f62dddcb617d399c43ce46621f6e5dd0a94c8c1ad655b532a43bd8cfbfb783eeba32618e662ab00c0b80952688a45423558401a1bf07c8f67ccba199db609c3c1d31db6879fb919e477ed6e940020665062a84ccada77f96af76587e404ef4bf562ffeafc45a14dc8b18a4129dbdde07655c940c27cdbfac313d04e80b032574a434c66bf19f9017e715220ee28f478f748c72190e61638fd5e763b5fef74318115a0d85c9264f6708e002a50fb158144a4cfc23658848771d8fcebc522942405345dfe00f9b6e8c19f387b13470d6ee6907606300300008382d4686ef3de62517c93837f2f60fa9347d211f5b166fe4d7e531c05c57210b88b29e56c1405cd6c9b153e3f1bb5c5703329037b2b254be23223e68746dab50bc1f25ea87020f2d95c818f21b63f386f41c0fb0dd8254155b81dee18cad39426ca90e4a535cb9e76a27a089b2c3ae6cf104e69ab3a99e937f4ae18940efae603b8817676e4d600c46d4e427dccbfc714dd5589f65b645eb9ff71c70f9c0374304ab21d76e386d628081d98f0b3ba608ede102340af0e9b9ef8226a8a72730635b0921a25b3a0578a813cd9db37dfae130e929edbd48b44d6453c29f2bea4c53dc358e8ec9a1fab04808ac103f8cb8fd59aa9992f8c241449b5efd1852f7bf1c9eaa003074815f9bba43fbef249973c30fcd29e5d0de69a4f6b6ec6cc12950ebc85d7ace1d0ce29d7095a3ca01db64cf02123814f82fc5bcd5dec1eb06582886a34249bfca144872ce269aaf50ea4b7e71ece906d7ebafa2a2bf81646f4b66a203e1c8b44f7dc48f2b90f5e7f728b3ae5fb89b6b4bfe90c0d3fac21ce81545abfc3f0d7bbe37e209b71310c9f53bbf4236ca4acc586a8a70341397f12344cc97bcd25f6c3ceee685eb9a35c0fad93837ca52af8ea92d03221df9eed6ef563b23941f87a4fcdca2dbba87a8c2b9cbd05a106cbbca20e16f93d3c26415e84d970bc54ffe830c866ab4549a12c23ff84645817219f91fdc101048256ece05958c0ef86eb38eca2b55a28e397904a0f8e0b9f94d39d49c689367610fed215e50d17a051fc26f14b07743aba98a218a1434f2ef82cbc168d72bd658550cb8c2acd8f3fb02ef70a9367ab085d91a51e8e5c4f2424ab56f0f25382d4c11903956da35da763f89e4da7bf91a9cdfc7a54ba3cda529b7c04ad2bd23410288075ef88b13a9cc07e5c8ff4cdd79d476666c049753515aea4c54fbd2bbf28020b6935e6b3b93a106d310cf407818ef8aa6b31df4d6d190dc20094924513cf488e1610899310ae60cf9946737833a074822250cde9288cbacaba54b3e124c00da42ec163955197ddb58e9375c0907220c93d4113ee729e8d98f6a34ef8c6713609972666b4f84e10c7f575742e1f6f8d641d99524446eba86e9b3e9ccc180ff0ec3cb40c8f2d8ef8e520e180c289d3c4ef298d6f86e257cfecadd1e4638fd72f611339af20bcc7b98b2d71b55a6c2c468412487946907f476278c10500512a6bd8386e0ef261d9b9d3f4010487bc45823b1f36ea63f71a06bb1a772f14a09d9b8c40d250b6b8ebe87d0c7292b8890da226011b96addfd8a75a04621a851d368a0a1879e22436408d11e66e984338db2662e48130ed18ab9670d824c80b189bbb74fe25b90258a03ac816cd00a59566ed2e7149ddd341bed866deb076fa4d0ddc32c0952815a633eada34deda23925d3949eefd2c296d2b2b801d502933445214d79399fc2df95b7d6a311add180500d0eb98a965cdad919edcfb2b456c832560370607c0ea605330a6a25c63a700760279cc1f650df6bc8b7f85776dc6ecdce55b4bc62eeca940663ec638412300d0fea94ca33064cb8f4e022a5fc8ff9b1a71f15227ebfd770a511be21f2aed815b9d27814f88f35cd51a197ee29273714c2ac5ad8f75a898bb44cd8571aab8f69cb2af4db6676db7b2f530e362b1c3bd0fb67caa48cf5df5607e56f78083e7137aa97bd7da13e3824bd3c5e292c81d26f7d1078a29935e05f7503659aa56271c5262391d6c8e2a08fff86c22f6bab3f3c1efebbfe42cebd00433f555e517ed822fdbd34de4240b79b5c2dff9aac290c3857463180f21591ac197e218244125fde54dfe404420bae25f8d781e28c9ea74a8c2f746d428ceb70243577eb03f8321ff9abd41e8e6e7a8ba6b4a3bdee9168d2ccfa499303d53df271a15245f58e0ba28b94377ad046138e287479d9ff58721587043af7c89b03210cc1e8249f3dc5f45cec07861b08d7f8ae5bc094b0a52c2dff86cb1bcce9644477114ee9b257e4446ec63de1b488b155a8bd9955425f6be3699c5d2be7ee10a4ad426d2a693d7e0ffc0e38d2623cab55f28f05c5557c1da59896b38c07f89f141a4336c304cee5c289e9a05581b0ef813fa1cab7026978b74413d261b75f6114aa1d5124e8cea8cc4dacf091128675bad07d6f2b6b55ba182d57cb5eaecec5a39c6479873e0e37587fccb6d07bb0544437e2aa8f2e89a548e87d3329626438df0f8f8becba8e0c41f45a1e4fb5a5444eadab86d8156b6319654cd1feebb1289a09664828afeb4adf9a5839d01c85a238ee5be9a24569d7146f5c5a9678908c3c4941c72b6666e42a3e6f4db434198645b9686ecafe52a100316169ef4fed55e036ccdc099afcf298d73bea505df608abc60225504566aae2b83d2a221de7657579748efccb0a7c70ba4a6d0837cab4d58e46f9a48a07756b53a1a5ea0f7635516b907bb64881c5731e8fff60097fe4335b275e56df9d80d97a7739606a23efe4fa7fb2dcde63ea992acb3491851c1f94bcb67a42ef5f7844d293388b315223cf205409cb9680103af2b223dcded5b2fadbd0d339c75bf63b19042aa77869f1ce4d54932f4e9b7a864a1ad8fe889077cc0c885ab105d6b4ea87eadc70793f21aea5e5418915951d3e7f59783e9a48007b1dd3ef033c6140a6b41ec4c64f715c6343507d05b88110e024350867ce3f6ed080e90e6d7f9a480556a5a70c5d17ae4ffef9011f769c763cb0941087965d3ce4b4cecfa81dc37f6359e4dd370bbebdb1cc895dd53a3b36fec10320f4b16bb39f2204210ab0ed1d5fc01cd489023914de7aca27c8891dd700cf07bd28c9278511f3fc2b9da833aef11cfc46f4b3cfa147b2977b99930c98f359eab14c71b750b8efeef8265c868c0d15d5175e1f77dd22ec47efb08c44930ce860d12497ea7b9ee5b6354be91e6d0cd85ac4110cf0d84e60855c5cf5c3a60bf42c1034209c8508d516a49e360014f62be407adb5165fe7e6d215f9a21ca0f1957a92d3bf40bc6687b37e8b90347d3e854fc3d06146acc42bc6e512bbcc7b90fa3fb627d5e5a70a3048dad8ac721f5f3e11112cbdcee5c1afd6d1a31d0a81c91ac1d6a50eeefb966737e3ba4728275dd0a300573c37a22470f859ff5db3a31ba6fdf2e5522b143ed2283e5e8d29ae429c6b6b81b843118e67509bdb87739b1527c63491df3cd2916c69c5f7e09c7a7e6cca493eea08c77144706ab7e5f3e1aa8525d3cbcf2ee1111ceef895f16818b06901c588cb4f71680c411c0a6e7a811ee6e71b0f5096304e6b2469cb131c4ab174fbfdb82d0d9755612428bdb0f78f85fb7dc4565580a61ab107fd4cc0b9ac381a859a8dac21d182c00efc11d842834289f2c3f487b761447d55fc6732427612a66dcfc6af7321812de5277a3c681f7200016c0574a107f1c133dd68d1ce966ca0a5d99fe1ea1a77e61b0aecccfa1273a46036a1563639926abd89d323eac27de61ce714c9b840849937535767dbc79925dece58c574bf4244707e91aa2fb9f26c01bad21602bdc064682a30e3f5e5a48c978d2c0faccef90cb904fd1e7ff7b13b67adcd221e6fa6ba7aa63d05190598dcc8e0a1b60cf58ba335aeda1d983b6ebedf045d4e77d11c2270ebb158c2655ef7fce3cc45f39c0a83f3d0e4104d0c88b71cc45dc235aabfcc9cfa71c115a9161fa08a8f946c2d205c023740cf063e0041d98a91684f080c5fdb00cf4defd83cf92a813f93ea01e1e91e33d8fc60769c8274b254dca39fbc8c2f2aaea89d0225eecad34d1886bde1d9066959e853474a71ac6fbb44714026c3b5af802c9b977aeb83338d5479c9d7ec01c86bd357d04c80e4eeed0564ede4b2444a99d9121b8a823934c270906b48ba2d1b35e0057ec2608338c70db90b3c77e5548a002d88fca8b68d07136a647de9d2b94a0dd8f2323c60ff2185237ba07efd8d49b4bd809bcd0b22a864ab8a279cef7cbdb22e0ecacd8cddeb6fc8fe24d94f2788d91eae144874a53abca9e1c019351654663f5ecf40e1b256d13a3012fff19af4c119a5d0a3d256c87e05dff323f3d89af38b8b6975abc69d3a5cfd70a78bda5488d353a97bb20805262c3884eec00c098c9472a9c17661c273e05d409c1d7dd569a25d0f8e85859751609e6805a8079169517dbb8cf82671034c8ccdaf73f8107d191a524d23bf0853acc1ebc400305425371bddbc230e35675770b0c5c27e0d59e294db4032b0fac21c83bb971c4a57242b40c14d088f9b0b3f6fd422a951da18b66a38752273df0419a74579e1d18a82bb8d21628ae1dfc1744deef83163062281099fba4840177acb4f68e4bc97c249f4a98c1e778eb63309528785e6ab1bbf9f48f1f155b0fa44a76e764e2b678c4e3282e9d386ee6d5e844981fc0d068ce827e092c66d7016f28260cae6966b6f343a2721489a3113fc9cb5a7b18044a2b3c883c4a076c01cdc6853036d4eec04ad9737bd327a350df1fbda99a86da8c321057903c15f54c82eb040daf29d310666013fc8c473b3242f8643d1fea179e1a3539d4bbbeec7579da5f2d0b4a0057639d682b1cfb7fca089c02b9ff52a68242802b71292926a7c2206572a1903acb4f705704de9f6be532ceac4a4b30014691d0a94c12096d7d4659a86061fb91228c073ea4b0ac2364351573000dd3ae49964dbbd915341d3c548430916af268b05883b15a0cc851bb7e755a0325a0b12fbe7b2c725484e50ee3087ff80c2ab5730b91b5e5e2bea2d2823a40a68745e75d0810cc1a5e9bb5b35c1c13af83ed9a1dd43984659e4acb80bac93f55b2efd6721635a0b3713048288f2a61c5082af585a61c621046c37133df6877d84e79f9d1fac2490de14c798440701c6d41a00ea2996e1288467cf08a7825e88ae82358fe05293ae71f91ab4ec498c3a157c01910a31cc017e27af57f926f1b4a11273342924f7a0d08a48b707560adb1ce9ae81fe0cbca97900b771bb5619a88b133157fb585ebcf264f26e0246a29126c18cb7692456abdfb48c1a102aa54ebfd5ba3ad107faac1bbe07c8ac8c4fd217e29bf142ae7d386cb0ab4e560a9db9bbc25effbcbfb8f35df51ab5e76fb81790176a6b84331800015895b09e8d8caaf5a2fcdfc518a2bf8a31ab50ca30fd6a28ab11f96ffd8a1be471c34613e29ec673cbe802e17373373d5b069f4d8a8531066ce2687f0c32e0056c1c5fdebdf3d163b32da327c74d45f97d1cb31e73596d0f18a3a0622914df5d2716788b3831cdc3bc4459bb9943150ef090bfdd1742ac89b472388cf746ce7c6e6115e31964d4d22849c1e50edc4c42fe91cc4d7f2ef3f0f66898183514e2b07b72b73ef9a63313466878209f0b51901ed12442924d60512bd10562b500965b18e8487c73564df6a393e1ee64099c13b880d3004e31cd1d3bf0043a9d2bc52f6875d7bb22efb00e01bd10256817ee8c7d02935cdfb4925868c2e556ff0a0252c6f80b41e3ae221a845b2e0d07188a6b1f22f533a02f2f13bf2fb06b317577764c40f057808b3138e21f7b430cb79f800f16f899372296268643d6c8aa884beef196ac118b2355181c687341856628a9fbda1222b327c99420912bf62b838599195d1763c1f7be40aec21581be1a12b2d7e267811a8dc4e0cc3242973a15308c24732306a05da8320926255fec276050ae29782f8eb2aa510f1d55d30682edef929e6ccb5d0342f4411187407021b5b11b61a4922ef93e94ad1abda2fe548c4a81b79c3ff4dca1db0984345ee5a064d070d09c51b7a89a423e97e198c75d99d0363112df24cc35c051bd577e3e6f1c4dd0b8d66a5ced9ab0648c8dc6ee700d445c3c3e5f0368168ffcb0a13e21202bfd678a94891c7be25fbc7f8b9645fe645c8e2de4060a0e496f2e1627673064fcef19ce19e313c1fb3384740fe77dd6155804e46fac78b905d906ea28b7771ddfe2cfaba204eb48d65c1c33e2c93830940ae44e5abf50e0c50e67c7b6500dc981f7831cc7d421a9cad4119787c1bee501df5b3e419006a21ab5cfaec44c6b28bf082ac31865784d732ad407974a6b9cb3fc788702400bb233c8250dd636e4cfa559e73dfdff531f7d151aba75b13c93d007702759d4a0d5c7053d3c1f84a46dfeab5f1782e766fcf738eaeb2bce8edee1a08431aa0a2c5743ed412a0d68208398d503d0104080c86bc3cb4901448a1224f9d4a57e49cc5b06a2357ca97f13fc3067a72dbb559af7f347983854a28597e0640619c0faefe4e306aca40c6c836b92aa3935154a7d2387994c9eb371374eaa0f64c651ca76eed4d2683286ec2a6c648e9e4b1f69aa91fac2c083824a1381c583f369b8069c1df44e799f0ea0af8fafadcc2c0804db33a7fcb5e324d55f4271a63d70ba7ad9ddbc59e440316f1ea85ec74e62d1d5a9200d965145f1480d93791ab88bdc71cffb508d587e6636dbd7c447f5416f087196abf41e65823eb988cb9341a9c84549ce4856d4d35545fb1936c8003f2a637751f408daef9dca9bc63121197e57c1133520e770a5c19795d9019c9668a1546f7a318b4ce74a0c49e0f6363ea597e60c77db1c54e20c758c58ca1e30c27a22e4304be0207c5d18d246253521d122c008222248bdae69217f5bed647a6b6df7b43ed3740c8f2cb7d8239e0b0f2fd82253956b92ef29f123b034040c8c841b013f1cd23f3bb0b2e595d5862b560d52fc24342b656393e7fd5ec1220d0b97ace46011d61c31b2cbdc87626858552362dead4bb43feef46d346d8133c72f9f09786356bef5354ae1d1720be9e0e382cccb9215f514d52783d63749d5bcaf56d9287cd287230339627380ea20a0850b5cdc3c4cb413dbfb493f34c827670404e281f5d34b80e1cc129f2e0c99c6f3bcd475a38c10e261d22460f6c68e59c50c328ebc3e069d1bc1860c69d3bc3f7682eae08874ce7f8a7022e130158eb8488c5af85590e35a53dd5fdf54d716e3e7713a331151b25386de025b0eba5362f4b8297d66ad5e6f152e9808c7e921145ec445cc0712379bc324936161c6f5cc4cd0c353c12683aa6a424a502f3d78c42cad6a5f4b7db57d0c364e32b3e8d8cad4d01c6472a33f002257cfd7585af45a41084f932c2dfe8acb5b8822b79861729ee055ba89b96fb025d7a6de8bd9e6297dd89fd206e4fcc209c29ea187d8e496333d2789a8a6dc5e20ad045363805188fd2d43aaaf9aa99d066429916e81e1227977e065a6ae7f3b85ca5069a59dba92d96ab00fd17203a110f5b9892251fa731b8a8494c2818734315e364e8366f66f69170672ed93283a445207fc617244e11ce338ebd22d5d7bbc5b4138e2b9075055f91b5a28741a71c22d857e07865504197f63c5c58d9b4556aa6b742fd0109fef09250c4c4cbd037a2cf9a96dd8f731ac821c863a5130bf5e0f86f498f5d22554bf1552200f753ec72111150b412f80e2f55aacb764a9234525c2410545c445efff46ce2f2fba6d58c3fec874e694563b8350820d4af9fbbe6ba6d14c983b3b6716b8f26cf73457fc576ed54206e8eba3ba6b25b9cecf347d9a06edd2a3dae8ce97035fc246050206568ec815dede61d503cff6e9b7753604cc70bcf80404082eaed037f629d3fbbec371e7a5a077ca0d9b50c9d38ba11bb4ae6f28032af931d3e183e4421c39c91acf01a99d6236755b914b8655d29fb86c0e543569ffa995eefe52d2342bd951147cb1a4bcb4cd4d69d9d080aa7bd07b57e15a0e0ffd8f96dddb49381bd0ed88defc964af041207569e67777a449d6934dbbd359bbc45a4b8d218a8e9e6729fcb26169f74204145ab9e2e67f9ac7569aa9e70471d0c367925e9932c564828a2ea5c91f7aab2415285edd2d224671d34e54961a5b1200a25754824a3fb596b699cf108b538040bf2f00d43e8e158f36ce6a77079cf78978d94735332300a5c663e441ca07c7415f7582d52cc565fe08186c0939a0a53e596b62a424cc454683919280a218f8e21d8fca2e0a0b609bb035413167006be16a1cb9eae2f76893444efe8fa26e0bde54e54cf682a6f91a0d2faf7c4f31d9567972467452d2524982c6706b242d35989285902e7503c14fabe5aa442fef51a200eb495490e6a397dbfc4d594ab7fae388774a3a775a3da383e650521fb5936b40b0beb12c1b87bc088328785b80981f3510da3114697643df3d280fb6608b84ef69a7a5904d489334ef150a74ceae6ee6d704ee2e9baa7f4281fcb16f4bf315774f49ebca369678186912d871e9cd21f8dcf28eed11284eec6be42140e551dd3ec59e9344758468dde9e9ad00caef1f03e4827aec27e46586a51de2d26fc2e17c470ea67463130669f0c6d34224acc32458e2747edb44ed167f33fc2984ae8d0e75019673cce3130e6d4f549055ae941a0c6c464519221f73c728bf376bf0ee4e680da75e910f0f618b0a3c599f425f67acf2c31f6fdb95b38206ff8eab309ef755f3c2c31965131f290fa771851352125e7a795c08159c6dce4894d046aa1b6a2af7c19d8e8431c5ebf29997fcc22b7841d2039e5ea8c01195103574b2800ea16fd15388780eb1bfb2bb052062be6adf61884f043f871f9179681b40f2edfb9ae04d4f09fc1df56daf2a5c651f6c1655666822177b1f6c27b3a2bc5d5bb46476eac65e2be24e6095e162c138b6d46112c54c97c18cddd5069b836aadd18c1112eafc6ba8a044e07fec0b57f67cdcdd9cfa478890ddf61ac7b04c5abb47bb8525cdac4776185a100e74d6e95b92c1e8872adb07a329c9a7442f5ce4895dd1cfede15d6898a54b063d8d87274fe88b3149274b56f75f5f6bf5a6ca9999611bfb2e114742f96c52600b11a4a4491b09f046f5dd3a3e546f39d177f561aa35d884e49af9c0e2edf900849213edba22e252d5767ce0404f8bcc4f6c8d974214efd982463a94e769bc95ca00bdc88a5f62f5257e62f2f5f9f5d2faf8af85073b11a176cadae0c413e57cfdd23803405502679c8ce929c3fa88cdc3e0db896d470480b1cb1b4b47be284049fb79e04ed2d7396e927fd383716a55d6a9a1443fad25ae320a87ae51c06ff44a683636ef354d841ce451b79e8b5e896684c22ac6e54bbe6f73a01c8613cc80d52437bace8c0ff8b7576550ad2043223bf59fa7a000983d56c3a9eaa428b73e498832606a1cb8f3f7fc3825548527004fa224cd934c06a8d82ab3809418f8adaa2892395419acd0338386b6d1f99754625ce03630924dc74575cb37ea3b4151951fa9b6c1d3ae4bf689489fed48e86078b3827d37421ae1d146c4e9569bb0274419b72661c39b43376b9814ca79f2fa7b28ed9e04a3d498529413af3b51373c16d0ce9450d6b74137e12043045b530713d5106fabc45b8767b30c11ec4c51aa224aa6baaebd9950afe32c82ea597765fc020b567f681c7247275e748ef440dcc1c3463105d35b2ff94c83e1a73d2ce7d1fa0246bd20bbe58a97835d2830278bb9406767c80d8db5d849a0fa73d95e06636009b583e7a712e7627a64cfe5866efe2176890413af4637fa870f6340b501b0393a8c5e4a71c4e71f22a594f6eff9c74f26393e865cde5313fcb432bc99f000f1c1bc69b32aaa99c3fe0f1113f83ecd5a65915bc41db9799eb6b1b03d0fad3c6be77919ab232e9b6947d02ca6e3350f57b46f4d0068c0e319de44b7823b5cafc24385408f55a95057109fe411d2d77421af97c490198168677da6e59d8c469ec699bf93040b851a50d8f57923a2b36b25c5024b84f7225fd6a9c4c76f389dca07c64fc1987d8ced0694e106d58ebe39a001e471f1552b831b5f050da9dc19fb44171265e8f0e1696166579104b09bbd4ae41efde8ce8cf49f4f0a987f9120962c6123d25008387c6383b252b2d89435dc002017675f5efc51b486213c6608dc104da9ba66fb31cc044229fe9ac5b3db9efd0de219e511893b408d3798bb57215137641565d5ffd5095841b7a3ba6c4502fcea53910159284c2ff910612f2fdd2802d1441b242a0fab9d74a3fce6137ba649677933de48ab67d9ac2ccd62a31844db8db2016fe6753f2708e37237d8a9beb85c71e2d981327c33823a8d4207df0b0121a4c38badcc72f8f650ad8a3f1c964d61410a553c7fd5350e43063a438adfff5fc929b5e6e1d0215fb11cda94fd962a69645bfb0397f2dfd9c1a2267a56cf6c14234184b7365e5b880c9ee6fbd5f5e300502344afef7ff8add0203ca4c7e021f0901f5b79f2c99745496a16dffaf962d4bfe02aa3b4fbde7af311ada018384652168fea2579738ba3daec9a2a614fc1bc6bf633c5e4b5e9c4c29437885f927690cc55887aff87bfb58dfbe18f087a0d0789d4a454be9c37e733f6a39b1a14ddf0acc8c2d4c1db96b3a42511a01b3ab80dc2c50265493cbeab9534c8fd878902b7846b72cfb4434a63b67bc3ceba0ff42bb8c552572a560c04e9c8b76cbda952e23031faa0c39db5b158833d958451a9a6e320729f0b40c69dac259afac3ecfc852d05dc22c71425d543951a6f592337fa9c4d51b0306ecda6d23c7a6086fb5d96564dc0f717cac82b7679fda27bedb4d79c118f66cf64873e374023a55e29389186a0e1b8f64e5baa7f0894646129fe468ea9e0b39ca15be2343988e5a85344edd1fab58be7d8add79eb726a5d0544c836029d6374dbc85f93636cdd0fe783485e42d708c13344d208a9eb57a0a2bf368f9f1978efb3fd57501e5818debc2d3281d176a029379b0ef90b29adeeda8249654530ab16f489d43a6f8b04fca74d48d42d44752e2206c64e5185b3f8ef31667ab347b0f4908c8e963fe49d5cb246904ec6bdd574d016e0034701acc1c75c86c1c0435b9306473cf8a413794fb46840fa559adf12dcd7c3e09f86b7b17c912afeb705c395eddf457d125bc23d745674d3b7be5ba4ce3b9464b93095ca11bdc6a65e3e3470f2c834caf76ebb588c3fc654af43cfde26a0e0ff79626de3cd93f5f8f3ba5fc462c6a9928914ab0f2ba7ccd50d14d4e567e2e9b4e1e50f60c8151d11f962ec175609bab069b70f93b158618d416064138b4eae630bade7ca82bbbff0f54180b654ccde3528a950eb43bcd0cd30d5bc488fd5123ddb8b5dec0f395d73e67ea618a501f0a3d9a31d04332b92e9af355373c1913dd5a98013cf8f2637beff06462c9c5ccbec42e3237bdd7347374d9b3b16940be20ff8ca6119368087edb207bdcd6b00cd245d9447f9e92e19548e9fcf72ea2a8a6e1ecfce44af6864a9758b5eccae389e33e742f235ca10007ac79942f4cfd1e200320af1c6c6938516268d3823bb20f53b8a0282c6ef2d9a5dc3f0c9926685c3ec93d6a488a6de61b2b57271f7b05a997c20a87b080fdd8c8980c4e600c37845a4db55de43b023e646074816fb68e431660bdb72db70c661cee0888a9db65fe98b8a1d574b7950b5e9e51b24146007a2afb9a1a7c8352a10edd1fed170c93753848c0f70e708c21b552fbd9255ce646b2ea69e1fed9110a015633d987ca8fb6b99fec50402a5dc40d8a349a4800272867b9ae501fe9a94fcec2e7e86a209210d9bc2aab338720f9e28612ba1ecddd5cefb91c7dd5902f24937365028a879e11c17a227b2b38cbc6d2f79794ad52eab5d1a98ef13aaa3e22eab5db0c753e1ed856349e3a07ce6389722be9fb40077ecae38bc3b619617e61450d276646e5879c70c1d9ee90206960e470fe5da9fe925e8adb5672f3a7526665807f031d960c749d6cbc73a0ee040252e87ffaf36703e1bd29e1d540a824631f2418e7ffc8f7dd35c9327d0fe1373d08755ba1f290771fb65ea28bfa1d1ce24551743ea2f5f9e4f2aae934d870afae0dd1273bef0c2d6e85de10d9b59b601d7e3d0b1933de33ae204da73b335c366dc9b5188eeb059f7a5c6eb2228d41903c0859b0ead3d901868274fa4068e3c813d00033741ea73c915dc548822d584e368c0afb9c8fff7d4650ae2c2051dc60ee3eacb4e37afa2d870529cadb159d725835f05dcd2e56e16c2a14855a8ab285d8cc94af527e35605952cb29ed045ce53ce96d3012d2b87cb5dc9c5940688b2ca999417e43f13d350ccf33f13a8c24599a177032b13fb0a9828bef842959b645bb4be9843ce7bacfd24360f686807e329afd84902dcc15c93da620e0216276e80ad7e32891c526510d878fb53da51ef66f25e457e971c56158744e306d30ded17ba8cc9703f94bc2c663ca5456c99988df30cba8a9cee778d8051fa62589b34b0a5bfed80a7ac5e9f8b8067c0676581436a7046a87e95aa70532b8a9e64899a4a1d79d4598b914dad5df0276cbf5a0c0e6bf643dd2fb9ef53ea8e356417cbe71d05b1b81a51ae3ddb78995c1490f492ead7046b19c5fa0ce2c89175d9010c59bf2695a926bcf96ae79d347fbbb8319e9c2adb28752a6b9c1fcadad91d8d95b286b34326808aa6903dbd806fe6e7f1c1e899571b0bb4e22296cfd7412783b127b1c84d762e11ce0ff1d7a75752e198641454cba8d94192cda93586a6988c8ef74d97f6d083ff746376eaeb14ec5a1d6dda6ef4f261a08acc46032a2bf9b970e7aa8f6a6c4d7857d1fd2290477ee669400ed6f746a3e5572b849367d029632653ab6106db9fd2e639531b9e1098218a4f53473e2bb3d165b365b04f09cfcff0ca8b42ad7dbc66e93900b90020d984e76b5260a7658d0826120c8087850a135f7fe5fd8a30b2ae7b8c3d9fbcff4558b267097dc8eba576a2270ce990030638b75b7d462729830b5b57fa46638c6c00d6dcab8381c768a80131ec9b059b39beb66a77973a406c9339b06c4f376df92b49ef6904e741bbd04417a93b388909ab029f1c5db8a19525995b328138b99748ea71456e7a413241b33241d030ac8dd5d5c4014163707b1cd0e9bc5f1b25d7fbb7c3b7ccc80b57226f47da9cd3aa090ce9aed9b97c99f4b96b177130434f5f044b6c3c5a68bca0835bcf0b8df831fbb12c7e6b41f4dc96f61b6636cd2d67b4a5f804ff482464b9b244922654a29bf05cb05c2056384cf83cb4f3f94103246cb4b5d903f84b0582e44878a344992244992d5e52095e2a2bc4ea7d3898ad39f2845013eddcb896047740d85c169fcd3f79c565229a8405788ce7890f1adf0991b0aa3846bf6fa243ae6f35be130df666a2714bf82f37a2b5e892b89a6b1bfe4a7782856f0a753105bc4901fac7be61d03d608a5ba1d90130cc33014eade7befbda11008a6bcde542873280cc3300cebb1d1aaa27d62fed80fe5f90976a821b06359bcf7db7e58adddba17bb376443947da7c5d5c2d9b4f628ccab04d134bbd429ddb44dbb30f6bc1cba3f346ddab4bb5bebeead9b366ddaddadddebcffa4157b0caa7e3b615acf2e938ae082c8a6cbeb24c4921c6b53056f988e83be8de2c5f9cdb40a390f320366299ea609867bfee7eddb59b356a35ab59cd6a5ab679b125d42efd9ae6477d352d13d9bc5cd1ddb4e9fdffd13b2a84ebbaaed369752ff5bc9c413014baaeebba7c34cdd7ce78c576180dc57ea2e9780ff1835df5a0c0758566d14f7ea233a09675cd3753500582601af4e4613860f04070030e5f5ae6b305b5744c0aafe0eb743a9dacfd0f183a6763a6f9add6b71690795afe7c26d3667295b7bee5262c302569134d9224fdd60a0fa6be050c3de33770d3fb81ca0b5306372440cbf400402652355e04920973349fb80933796e3299a6699ae6bd366e4c9527582854ea89142567cf2a8a8a8b8a429521efba2e123c9dd05ff6b9f8d59b0f9a977edbe72821b0d85c847f9120830d5b506e830a19a03df4aa4fa1de7c93d237e99b660875a25028140a55832bad8ef58c45e061be011df33561cbd0ee017bae39d3ec206ce489f59145318e23a9ec1142ea9c929488d47154a94a6e855dacb550f073b57a900b374de747b221af72d2b1dddc60bfe1bad15123d040086b0e1e3a22d034ae23819e711e1ae673d7f9a85a6185518676e27ed241142fc2ebb9ab1fe4018badd27185d7573f198ce3388ee3385e570d1528a59229c584086aa754723a7215e148bc896badf59215c287b8f9f1136ec4061a7a16fb8b8ed263033e680002dbaf827e72afdb0e158197c475a7c6832ac0a107ff93826b16c130003d2a5da552a9542a595b23f4b182529369753b462514ef755d2c2ca15333b78897e4f2b067b25c696a0b1fa92762a75eb2104ff53cb8036aea181e5c3f2f91c254c634454d2693a99e40d44a92e5ebb85bb783cb5ade9ecea853601d554ca1d28131de386d0a3c23145b955facc0627ec9c2c6831d8e500a8ba1971a60f1caf0b2048b311c80d6af069a3d50433b096faeb986fb7da09d84f55bc15361d1eb3e22f067838733c24c099c4307f4491e29a295003f797af0d64b02f2b390b72b14c81b9ddc2852a6e83c89e61ee7799224499224795d35700f1126241126b2bbb3ceb22ccbb24c0a07ba6bc2e17b9b841d8e8cd20f94153e9ee79ea184a29b5ab2acd600411908d10297f375bde0fe8944958a2442d14d36b250c846e0ea50c500080eb836290cdb68e0b1ca5bfe1da41b6e735296d910bd6063ae80715374a4d48ea4b247888e3dd924b66489849fa91d59cdda56f762ec7939836028f494d2cb0bd15014fdf0421e0b13d833cbcb711f98a8cc510e904d896ad4ef420e908da734e2c9329090115594463c58f644e3687f9891bd241adb22ec108b72c933f2f7a8568764c707ed4841375c21e602cae1d9b1b292735cf2ca97b3925b720e4b9651bd3acda1147ace096b761790e6dcf6e9724e0ecf0e973ce3cb99915d3e1a38f728c725d3c026954fcce1f4c901d57da20dc78386f64da0472ea01ecd9872b30cd4061ad6cf0606b8645f0be8078519594dd33444f40c2dcb233a0b7a648950a435605d526849672e130df3f9f244d1636b60c78eb9453aa67e57eb6299acd0eebc35d99114beafd81213987e383f6c18586cf3e6871d473a8ee368473b86f5a654bec5c465cce639cebe8481c57e02e4ad00e28811977dd019f046017bc6403cb8669b5da39a96fd014a53f46819759a8d701f298fb68f86e71e61370f9af77bd22cc0f75e1f3548911162f5125493f07307de65027f7ed087b756268c18b65ae5ce977b2f4bafe15c86e3388ee39274ccbb736e39950e24e2bcc6008d002e947580451f3fa1180a49ce43d770dce751b4cce71b8ea4c1ef3e97a26368a87d5e96b4c440f9ef5d89810b748d972e43c3e5cf16f09a2d7fa6a169b416ab9de01a2e53eab8da0758995d1e1db76929cfeeffae95e3f8abf1d3e273d9e6bb5ce3931d3bfed4f87c3a6ed340b563ee83dc83e30a40d45a315b310b002af08cfdec9b409b66c8b699e301d05371aeab063686b5f6b2172e71e05e620232c4b2e7a74df3ee8e5d1d0e232b39fe23f41bc21a7a156ab889562d485ce9f02af40097452c671983720b9feeea64c0fc13b90e33500ee1e59f58f3f6ddf061be40349c7b98cf5912d8e66be19b45d1a783a11fccb24face177307c7819592982455178896c3c0c58233472411882e3382ee7cf4745c64ade01c8387ac99d5d724bd6998157be729917508f3619a097fc0179cba7e3e35df8cc92678058be1ef28887cbbef21a11b4cc679719f9472f2b36bfad805eb20c900a885f5436caadf4c8c572d957befa924701957000298c4acee7c301c85683114f96731875c7ccf8d1c67d7806b843140f057a84734b969157f2966f68596d2e777b6e9101bab7e5e215efa2c16081d24858ad3c2e832151ce9d8d037444e066078fea590574298c73ff29dd2772b350bc95c9dff01f7a8696a4a6e9c65155852af543dc756ef62cd2abddb19a200ca267e8fbd03768e5618817f40dc2a6f1ec45150d856ed131d46c9a6adaf19a9ac4445b49764c74a6a292784d6de2325d9324e19fa087a84dde888ac52f51533fd22fefada82615559b5cd7a97bc61d46afd174681ace344410f4d4343f6ed1338fe4838ca7919ec60424000441a01452f8f154d69e9aaaa8d1666c4ad3383d62ecf3c7d17fc450972601a24b744c915a83f0082a3c8d271dc6204174580149891105a110405c25741883a800054200319ec6244e2f40d2848d0ba66a661cb78e11bd70aaa083b516afb0788deb0842a967169ec01f09bc0061d82a40c2046038c2e03ab88ceb8c54e772d2785d1876eb652d86a9986853ead4bbbbbb3f4702ee8cfbc3cf0a8f7cd499d6ca82ec89946cb56a619c1287cef4c595d65aff71b390c2a2af2ed18d274ca9b121464bd42d9e26e76a85d918f4f0d0d3c3c3e8299c9e6ef5e0ac56ab9567c3f3562b4ddbc14385094f7cd05a6b075e7c20c1627e5a6b39841449962b2749cc23aa26282524e9799ee7d119aab3e309c93478244a0025144956b4472b27d414249df1d6553eab758d17293c57d4ac2a3ad3de2354499e4ca5195a928eab91349246d2485a95ab7255ae4ad34fd2248f409e48484574828c438a189d8c41c62145ac56abd5494de7399a3facc6d509c6d8349d3a72498ebc7704d8cb28f8ca66a8cea7d0353448cbd0d01d466ff452892c91259205631ae487d2905211a5911c499324c91a42a10c8436e083070d3d42a150284492a927caeb254ae45484f49ef968d7f38f86f9ac7d62163e97826635ab594dd334da1d734ba552e95dc1cab2fbddeef77341462095600123e89a175c4648d35c3d3c55ae3b991052a74898f530cc285f28c153e04678145fc251a46b4631cd9b8186f92c468f934422e517722691346d070f179c4a77777777f7aede1ca5949abc887690144c24f426ed0799fbf943b52ccbb22caf4b8b5ad6b2a2ce28b5274935ab939aaa4c52a9542a954aa572ee546aa351c4094a52a32fe2c4893412c1100b5f60b1969563a10a7cb5b004ae496a595b50026399c6f5ff6421dcd6a2ad90b5907a98cf57fb52a48ff42454288a42b196f553dfe2add042287eaafc944d7d2af5292f371312944ca3ebab9ed37c7d34bad68f8528b85d86e690f3c341f4aba74e0b267c7d622d3b500e38a168f30d34cc58ca06a7b0c32e7767013c8ee3986b78d5fa7d8afb5ec9c37cd674c01555cb0759080277e832b496652dcbb2ac652d65144192e5ca4912f388aa094a094692368796e18ed13e5c75d0198c1f80379017587452c454872ac791cb95640c2d43438004e1cffca030ef425ce603ed76bb9f9bf513310c046088816878ae76c7e7a6af78d4ef668a1b5409b502149193c934dda04c138599a6699aa679af8d1b53860e7ad5cda3c70668e8a6d775b917b829752170a61fcd1887563ce86ab55aad78d47aba529038d01ff61867173dd0a104477eb0d65a104c65daf54e5afeb30603b30985301b1d5c8a7a201f6d1c8805284001c228078866c5c84d9e032a48ce491afe7d3e2f290c0cf7f9690c8c41e1ccf12948be388d72806c4c588dfa5d28401023ee5df80ce47918c268bb3ee0365d7cb5b68f9447da47c39a2c86ad5adb9349a8ce94a73bdb7d9bdfb66ddbb6bcdda6792c14efed981c83f79d7b3d3f315a5a71ba7165813fbb3cc1a2ef34cd969f07fc7da287bfe5a7b67d94480fa639e557f48d2cfa86155e6891c2c165ada67d626bd9d5b0ec6adfaf5ce673876a012d4183bfa24318117eeba4a413cfb2e8e797bfdabe5c6ddfafb45c7e6bcbb9d5e556abdad0228b89f9b39853df2a0974bed9b42ca3f289f77e3e11e3eee33e31b75a1c07d6e898908d9b8ed9c1e3069cfaf9171f4c96144b88c565064b904abd7492f4141c71c4e98b68bef32e84e5092c9fd862884159348bfd84e513fb0cc72c7379cb2fb0e4cf083080ca27e3dcd2322d9fd8397896ccf26d94a394858585858525b3b8cc7099f1c98d9f252443c60aa8c683a18eb121dac183860771721e7427722ae2a907959832be33a2d66aabbd31cd9badffa7c09e6f9ae69fc98366c7d4d4fc0c5a86e6b57833090ad5738305169dc6cdd987606b6cd3b80adc0c30962fe87234b016b4125e1f17e13379f0632c0ec2cfbf45cba85208038b7ebeea513553b106d488f5390163d96607c9086db6f92b963021e2a6693eabd7da0e862bd3c00f7e06b984b7090f839d5074d62545665d4e9e60dfd9d1988096b33c03368388c27c773842e7953dcfbccec3bc83713ae974d2e9a4d349a7934e279d7daef499fdc8e4fb890f8f74b9bb63185823e482c4464130c46303363616134ada41d0dddd6ff0d07bf0114f8e0ffdeb4f131004c15a59250a957a2245c9d9b38aa2e2426b8cbff4b07bf66299a6699af61d0e2c7f56bbcc65b15bee99cacd8cf33a508f34500db31e7da77d8ecaa080b32f14ca26ca35bfd84d09bb37a29f348d4816ba41a15028146adb68183b1b60ac29d87996251d3a9aa63b555452809df4b913bf441be91bb507e572a6046dd206ba59f4f30ec1dd24539c43601d55879f5ace3e1a375aa641811e693466c83ce20c00b0e234d241013aa91110da8339caf2075a4223d9870348619ef6608eb6ac65d1f3c126fac66e6c422ba1c15f3dc10b25241fc10ea7ea4b4c4cd5a952a2a4c7799ed8799ee779de7b73aed06064b15aa723534c52be54e4c717c2627dba4fd77517cbaac0a2b35827d64a7edc7d3eb9eb3e9f87f38ae7173d29fd0cf9e17c92c56ab1582d91889e8e3a4f2ae564b59a8613f2319a46628d45cf9375b2ce2d735974161774c5e4c1a74d5c8687929e22ab7f1485d1314d2a53ca4404f53bf74fceea4c2c16cbc462b1582cd655a38edbaf149165fe2b58e5d371db0a56f9749c870516a909cb70b295560aadeeee6b93650765d9b5f9db319fafc96432994c9a76a2678f55e7b8accd19e833c34d7df4394ef843d163a1891e9f50f44e9c448e1a7ae17b87492c7a5ae81e9d494015de0cbc2d3cd149abf644777787e82a1402c19cdbfd5d6afb1158f4540a0473f6ae86e323cd5d9645279908d08d1e0b23984b8c48b14003dc581c0109967b7a1eb4f91b7d39b67d46ba8fcb9a566d1c908d38fbbcc44ce025f0767531d40bac7181dfc5d00fd21c4225a1b761437ed5cf895d951281205523b50245adb56bfb05fab073dc0812518c2320da1045463c9e37202ea8afcb36713a91669323aa22292eca9e7b3a7912272fc36ac7dd8ed3a8f0b8ae0b39bf3714047a825fd0a61c271dfcf9860ff866b7375eb361b96e9f13d1beee98eb52b400bb0ccb1758ee220446084ae8c6bca75396ed18adb5d65a8b596b2db6c1808a2a701676a1e88516eb70703178775805d8731294babf2021438c9aa6691a160aa5f22766cffb7080bb58a5a0ca543e1df7659fdb5d16168c3d2f6710bcda6adb6a846c885eb0710087459bedde2d9ceeacd2dddd38c0526ec011515102a8e85bd7759f8822c5eade7bfb2f765d1fe63baff3c511a81110059cb45062b4b9d0e2240fd2329f7750e94174538ecbbc7773c4eedbb13bf7ee6022d1cd329ed389349b1c5115497151f69c4e2b1b9b3055390cc36c9aa63ba92801b2d1d3eb7ef423d2ab4c0394427802cbf7c222a27f813c9d5ab4568e0a6b4dd3344dd334e94c2804821e78c19013312851a31c200ad0190269c4e3194808053838473c5a1645d67e8f553162cff1bd9c7212e6eeee5cb6a00fb492d0039eb9bc7508702f412215f61224ca5026ec25488ce193e4c665ff9c84611223b9ce45b6c8d8ba2772022c3ee9650042dba3cb2d1fa8613cbb69e24fc5cd2fecbedc315e2a7925f16710be8b5baed7855d6e5a1b1bebeeeeee9fb5d7755dd7957178c92e19072fcfc88fe401a4977170c92f198719d9cba4fd8e86e7195eb24b9ec1cb335e5cf2f5edf0f20cda61cfe4cd57758c0a26f6e5931be80554432b096dcf0f7cb8c16867809e04b9cc7814280c7bc44bf2c12dba00038a49924f3ec992f1019062b3eeee16ffaabb6d60791865fd70d68f1387fe1d8e6a2ba537887af402ce0f51005a851911dd116a02d0ea23882602167fa2a1070fd66a87b53f4c2c168bc5fa11b2f1e1a1564abfd5067cd46bafa555cb5b7655bb691976dd5aedadf6baf7da22863ce6ee8e5dcc8235b831fcffde7b5d49ca2ccbb2b274f7a157d7d5ba4a4f728328fa0676c3d6ec701922f80f2e0dbab0fb6291893c51c64cc6fa86a57d03cb9e9b66a84a7778749e0daf2357b0539e1efef59fcb4d5c64714583249779d702085307a1cfb235e2943c4cd96a95996e19966559869ab68347c912c4da16ceea455d6932f03ea30116166d1b823ddb8cd3575f7d5191c5b0ac0a1004310631884bd6087a4813d137b24c81f01f7c6479211c08acc9215d7f307a66253fdda125238b0f454616df0832726733686615460d819587fc900a9272313e2b327056c99fccb29245af594dcea8edf0b18a505dcb5a11f075752c2cdb2643fb8cc8f872808cbabc8525217564393b060c7c84f0b38a11c2cf1f28210dd2cfa23b40216475cc93a592e82caf94eabe3f39f102d327e7833db0582c168b755d66ad6fadbd027fbc2458ec966a478808870b85df753c5b48bbce092c86b4f0e66f5c81b3f01ec1622806ec490cfebd3871d3c239b55aad560b27f582eeae5d5b2ab5f4a2f4ba905094124a92a5cd46391b6b6da8c03603010720ec254a9284b69b408f74ae6cb3d8ad1728e0ed72047001fe2e5aa1a396ece0e43d95dba8a0ff1f23def702fc9f93f02181c56ed510a769bc898ea9a23405141d128ca309383bea055224a2244992f43728176bca759ef8576e93fa5c8434112962e3505e08bd2e6e09ced12a702a7c65d246d3b4ce33d6c960f34b16d8412fa105d56e0277dd6bad09ca186a6e7930ec0e077e50088c57f0bc9bb13cc3fdb04c02338e89c88ba05bad6e75abd53af2048f154eeb3ccfd3da8eb91e93acadd553700267d75ed9fd687099bf9db59496e06cb55aad160e86854129a553f0f6ff5448ad6507b81a241403096fadfd2ec77605c6e1f770db82f3dcaec09e4bdcc39bf252447e9ee779f6b8ae2c0aa6b5d65a6badb5ed3838f7629c7316e1d0999c5bb9955bb9955bb975b3ad954195123aa71c209c12e48887cb40424490608d78b454a9d4342dd664537466465e59242bdb4412d65e3bd6644ba59bada6597b5df6ba2856b14aaf6bdbecc57da41691eb889ea12814132a1e1b2459b52c8acedc286090c242c5c406255bea9824d7674976b4452c59b2da05f9fdecc97e3ee29456ddaa944e415114652d2a8bac9106515e439f582788405116c5836dc1684b2b9bea9826e7ca96e8ca96ac2dd9922dd9922dd91286995e13ce28986952cfaf188f42dd7b31db11d1fd4cf4f32c014785f0ff47f03c8ce1192108660e1411423734b4dc619685e84cf9284eb4ccbce9cb0b9ffda8042e3b5ce9900000000001c31500001808088442a17048342295b7b23b14000d537442825e3e1b4a849124c9d114a30c328621028801408000c150892c2ddcd8ab999bd4543d9452a28be475855e6441efdf869a9c7eed04c8b24feead4d784bb5e98b0eb34645472f2c0aa6a5cb1e45c9525fbb94b0cb7f78143c9c394150da8d0fb07dd83214b014cbdbc732b77ac29132d9d931009fcd1a086ecfd58179be1a213dc83a3c45775a5bed482df17138484987626691f016730dcdd1047c6aaf9a6b54421a328c5e79562cbae5dd98150d7d18fea05a2d2282bd9da99e3b53515262afbb684310daadc2e33b5bc08160d9c1027a6e280a71d70e120f70c43c48f97d58de2761878cdd226f85980c9a9e93cd6f7ac287d8fa75c023f002e713c5b76ffd48b5ffd85062a200daaaf1e0711042b3a8126bc2c5640cf13778af074eaa29f81fd4e241a49165a50e9f539d751e5c906d684d935cf592db0d35020ed627c712ad834431c04ac64a13f7a72aebb6809974d250527c7c41f7f427597b5cab489f7c2631f34e88cf3ae4f42baf87af76ece5576c5bf0b49233e4484a6fcbc4d814942b2a9c99f313cd00d97a5482901a68aba5dcd569425b9da6c8512bda8a04f531448d64143806f621a0b7ea653ebb21fd1296bf63551cb58412662de6bc64232558eab703222b9166339d28217a538498d8c55f451fd8d1af76ed151e5d86b2ff932ffe56c4c59b4e0610aa12840804debdf5af4577aec948eb8fa239b7f47fc6f92f4f9f5753b41d7a0a1d653f6c4625c01e609eb15af69727ed58b9a14b2cf2b0ef42e4cfc1312b79cc6a86359867f18b96bee4cca441a7ba31888c4c3ca6784cd22a999a54e6700f6238f43715d7c03c7c9345e2eda3e80047325f9ecb2c8e083dc48427a206966e39142e75cc1cc5a3263331e9ca51639ae5996b82cc04e58f19ea44f3245cccf0ed4bf25b31ba3103ad980316cb264fe8bcc08934ceac860f64eefba4d5969aa5c18c1d7948e8424d9199def6ab0d9a1152cc7d11fc038e70364293e7f79e80d20d7d2f823710d1549f325459250e8157debd11b931e82c9662e4fd850176a1b6be3b6c7d3bdd29ac108fd47ef07dbf639aa9fb076792061d28b6dd82aa88d417f2c7bfd63498e3a1a9818731ccbb2b967def42f8bdac8fdd6c19703c113ab6ce9e2e2236205166a3a77f4f01d4239b635650920adc89154b0d635a23c631fe10436de96ce08af2ca3065bfa1789a704e79d6c8cde9cd1a1923d65fa01e373bf629356c1448bb36329877ad885d023c5b4dcac57a10659764699d3e2f7c2c3b128553b6913ac4f039d903de3a342227c0212916db8893c904313ff3b5a92336f6b21242afb37af5c72bfe908db124646186a3fb597694b6930f12dff4a139fbeebd09a57638f42aa3d5c33258f8565a29fa0915866561aa8e2da28c3761ad2884407cef8e13bbf490ecd17d5fb1be6bb9366b8fcf79418540bb930dae190e431c90dc9d81a97ff9f5328331a95eef4b243f6efc1e13c8af4d453ac47c1cad32f35a98afbdb4f5303d54434b73a45991f9ea0be154d7fe48a9634fc5f5104fba3e8209aa723ade8f9ca96ca9c2bb4515f1128312160e910cb9249ee7b93b105d91834d27c25afd9ec80ca7d056b381a4309e6d30d258f92cf6059eefefa98d76324424840a08b839113193246ca59d296b6819e6765a3480dd053fa0bfa54a876a85bb1bbe512c7c105c9499d51b7c7bffb46680060d06dfc18a9dd4b735a6366b41a07c84124ea9415ba8dbfc6fd31553f857b0423e67d215f3b52a113bf8348f4c407199978f5ceb1a625dfbbd65e570d5485b6eaf5a58597b2103a2b9092ac094289327835c09a18acdb282964aad6e6e65be42ab39e767c9349b9fc89f96a4599b857c6ccb6f70d3b8f260017f8b9bc50ffaaaf705bec76082e2d52cf42ce46832193772ed946c0d51f87d935ec9517a2a1dd450760926bd0cf24167426b79cab2e456a44583a3a89de85bada3cb5f48f567504b26db24e66c26e00e01529e90cb19c29375688e776d1f7397e4739dbe1ca563ad4db46de9be29db682dc5068e5d8b1f1c944094d5fbfc52d08e9886a38b2983efb848a4ba6dd9e4ab1a3c6b5bbb713cc4bb07a21f13f378c9546447b40b9d5c3764295d85368a96637e3cab8bdab6fbe0f8bec7eda54bc205bc2d75953d535cf8a21d3b812a47eb8c76f51bfea362c45f6c87640d79d2bdbe392530fd0d22233d802a058b8040a129f1b7f229457e0ed2aee6a385afdf7cb0fe457e1d320bb0a90068c6b9f39cddd8cb0c7740ce68d61e094cd3358ed99fd5293a661d13250147381caf90ac58f5bb87ef6cbb67aab2e7156537593eab8f1db81a2d33f5d87d3d1046075ac7858f336c9e4f54b03065585d57298ea3d6a206b5db8de10eaf2ea6159684182c1bd447c407aa4024149f67dc69612c511fe0413d63cafbe347419a88e88922a901567780a0174a07ed577dd760886414ad95eab06d2f1b01adc55728ba22793c45c155e69bd6a40fdec8068107cb9e9e97518ffaec2545a5c3074c384f3bf7e14c80a691c69f272ff146ad861432a21a8cda43a420e1f26bd1357e29e1bd50630cda39d6c6d58ae035c4ad6120430a39c3d2d08b882463e094073fab43fb2905a94cf4a50e6eca31c4b0493857b59174a84afff3f07af2e9371857c2c53c523a93d180eb59cfc3e1b56f35a5ae0e377840be332dcf11f26041cd29ca979a20e84c27b2ed612a9d4bc3f00f5c4fdd6e8fd801bfa3c0ebf2f7e28c4f406a5f472558a880c2568dc266fe86f51c9b9e22e62df55e1b0ed003f5ab3533641e6f0d7842bd1b0776a6cdd7d63b21185e4f96ef84ccff8611ee89aca01daff09e941101c89de345514507acf9635494e88e928b08ee93bd14951b00416b84d0eb61b10c1d2f73087724c45d8cdb374b85bb35c2dc3fecd85a230860b1093419bfd64dbf293284571adfb27bf951c70ac945e9a5f6b54e7917e025f16b1204595ea3fedae44f196ee001af608028511be53e9f613e9c844dda4741ef94333cdbef91f0f16ba810baa132ae82c70597166c28dcfe3f0d9f3bc01f78f62ea20a0d6358d00e15e32783a88277903fd06f672cfbd3acecf5d7f29bab88ea85b3498e12827d984e489f360583fbdaaecfa6a6714aa7d21c8f23334f281ba678af6733400fe9d11e5e25d0d1ad1497716a3587a749076624f81178015bf47cf0106e6818b828ba65a2a287ab12398b605e17f4e97576db31c709d88e8a5585b6a1bb512e628e73731fa8d754677cf6ccd9e9950874337f880ad364826df075a46ee444ec74cea06b678132e273041644d8966a8f6368390a121b8528966239f2ec58e798a9fa2b0a8b5e04d9488fce37bce285e27b803237a5c45d382ffafbb3a0be967a2b126917e219cb090a06de48a5adfd84f623b681742e860197dabc1f004109972f51651c495bb32c0c4a9b93eb99a331cfe8cfdcd4df2eb753ba9c34715886ea2f07814b2a7e633e0bf1fd49e6585e0e07e68dfd1051ac0d928acc8a21dfc91331f8472792227d6d9102c800f8f1fa35dacf9cf3a57122b5ccfb86489c049f2c6d27908adbdc1d3aaa36f1d4157d311ff0003f7c2baa78e4f4864161eef5ca101c97eee9caebae37532cbc6b034f320ee1780e3c626cab2df674bc978ddeb801cf5232133ff18bd52044ccd9ff0912177a1cb610924c1ba15a2d31a35b428223c89d55a2227e2dbcc2afe5c4f835d8e658dd92403a8730d87b9f851d70f8a6668efb1aa091b3ae5b9cb75b99173432730ce5e2e6ed66c972bd3085200b8bcfc257aa77cc964f483df88c311b89b2d19606fa6dc3b7953ecfdca90afcb81924870dfba42edeabda6b7898530583e060c6f27164cb67b094a2e3f60838c84afd8e95a5af628822719b919b1f0eca7a68046ae16bc17940aba218da2401ef4ae03d0ccd018e2808d15a6b5a0718b24e6d4014e303f3826ced880eff2f206ebfbaabfaf8588c3c2e623f2f51bf1cea41e7e4c7a13974921a5f42b346b01759346773c6e704e313999fc3e198dabd2bfc8935e5307991f1253156dc2a2c9c516f3b0d8e8871bac88740b3f477356c2160cdfa29863121b5d6dcb917415dd70bf50691224a16809f09b104b0ffaa0e0652c28484274c198be2d0e36a948f873f36c03133caf074bab48916619934d17319b20367ace9ced6e2505ceee5aacaba9241ac2fa4d5a228ce9a7192fa2b7b2de3581f858b11ee84746dc640b9d31403ccbc34194c1d59d2360cd937da21bf0e4267503cdb2e8872c9554f209116b6c60c7d0b15f0e2c2f854088734cf646a874709f241d82ca9c0e6c1ec9d90be5d7cfba4761d64520719a94ead7fed8f17e085c0dd17115862d334067be0b9b8e4242daf953f16b8c9cf1108a940b2788d7138ce0e281b20a4353b231744df56a1cb8c97af32eece387a3340096528af1d160b6bc4d2a1354adc74e520ccf3cac1841c0662c122b12ae26ace04351ca9657dffd66dbc59bc0434e5e4ea63b1a0a49fc7bc3034b8c780fe46a50c7f8c90dd1794df8c3233b61750f470c31005a899268ac887146ad8c11e16df532592c70cab5459eb768ac26a80808ad4d0e3711c2acfdb21a80982b5ee83354b2846450aa51cf16c798327094c5ffa98a28266571992ee3929c06432fe51fa14ef291452e77c8c32e18de83a195d1cedcb151281454849890fb730ab7642ea139bcd3c9a329a11fc002aef36f923ecb0c664112d8a557dfda6eb513d98fb5b6e2254d1364a788197055c422ca8fc1fc917d038c170dd88d7a7bd2d9a1909e1dff0695f854635fc37cf9f03a066a6f8674b48ec80253b1b33eee4cdbc32e8cf5c7663b06942f0103495ef4181f79c00b83240b5d7fb635d31a19db5ad1533d78ad5eb7f5031e779ffbfd13b46aa49088b8004b6d7ae3a15b8644931ba55e535998fc83cc252b1d1ae62eb713e6f94c492a2d9fd6103a2b9d77e78fa7588ca6c6b4c91b26a3ec974954fcdbd4d362ec644b2bd68a7bd4ac95343f73d898f26ee3c1c358fa5bc574b24ba47094304be79cb4da72e345b8f33678a04129f6652e3971ff22047e77706ed21b38e44113a1f8a347582dbc64bc4dcdab25067118a38c438ed952a323a9f83a3c375d5c5d75544f0432c7d416fa6d0919403ca69756272566d0fe9052e013dbc2b5287add033ac768518eeeadcb7faf3ec4b9abeb7538e2b836f5530a2fe436a1314ba486c41bbebebc2fe6ac95e0f6d7e9ad1065c1793100f255eafbc923186230c96ba9f2afe0f8555935a54a542bc54c457755002bd6c969ae0006003a0f2b73782b98681d45941b3c5d1f512126f253e1a1a03f249e0a9b59d8c169ba8ce5caf66cb16c74fec5e74c46097c3baf86302acaf298b6434926e9d337b30e5247364e981e2250fc4d6f2a4e5af3a59ebc51bd6382e86f35831ad7ca7ba4b6a95c1cfe746325ab5569f2561fe53156622ebf91d08a3c377fb6425bd6b710613937c0436c4ec796cc1c23c032cb9fd4d8ceaf6122abc18169e34e098331872e81a7caddb273a6af965b56efa1bed756313a8f8ee8647f24909599c4a2196542a0f96685b840e6daf1848b4b1fd9393409d76229d402340a325f6b1ce00297cc4680b79fbdad7c8bbd341cae7c95b92c1024ec2eb6f4976f669099f0a4177828589554a1e6dc8d9f47b9164aff79661a1e11fdd1e310d2933b224bb08ff5a86250b04c7f6653a0ab6ac9b1e93b9fea3ed7b3ac9c76112fc240ca3b78319fc140efdf24770d3f4306d6a33b1a49bba76db61c478be52fa4cf948fc51a6a587b9a27288a5ce44d857c3f047db005c13235bb79019d1fc894db43914083c9c90a2cbe950491fc22a5fee2805ca40da169e83fc1e6b82f5e459abb1dd35e1d6600c3b4d00f973c77325b9405b270ff2f55ebf047aacd26df7c2139698ec5bebb31c9037c183f299d70e35e8340072f4256dfa56d93a14b9c851f40e9b94b89790533a4a808beae3c3b83d08d105656073183c34f863b8f48768bebe057e55cb5be67a04f0e2d7ea5a99725de18ccb1477a74fc87ccba2915a16a593d49cc52687254b5ada3b15f61f9cb721bf1cf4bad678c2cc2b343893f9f0cdfb6566b0a14623f601185b809f957855b7a6f7a6a6544aa9456d3e85da043b47521f0b60e7505b0c335a73b5fe20d9301c668a083dcc442d65dedcd77386ef481be2c844d4ae8cda706627c9598a6854c3c77a6e04647e320dd7e887a1b8466f188c363fc31f9fc94170fe55b210d6b80741c1fc746c954dd99d836399066470e0fd938428a7e1780e1f4be40426d1f529d7626584d6934be930bb74393fe6801716ea34919cd327c5849e2da71ee143b1b134cfbf12ba1cb60ad5a49a9d80e72c4de474537bb21d27e0612532e872e90de18a426b08ead20b64bb0086325754a02e74b95cb9a45dba963f10b630d5a15a6e4c09581100aa30b33093c4098fa8214362fb676e06ea4cff1310b4d694b05b760519e5570fb1e522ff088cd2aa1cb3731797f6df1503a4bc0a7857f9086747f6de797278dc203b5f463c6b8133217dde9d1fa50e6cb98ae2b6902e727512fa332c0735b6dcefb3e6144c3756e2c69835c79b29fc05a15140271d86d973cbbd5005eaee72d9724048ca0b05bf6839488fa1e5002d715a943d2820a65f1a58e2352b55fb99f9b5f4e492a12e4c3b0ba06648350b0f240e0ac455abecb750644ec9064f7ce55073192d2ece691d217c3507705d6c7c61524719e9ca3222f1b8475912e4ee159ceb3b798abe032330945a539458135c6ab713e67c7184f0260702683d2a8e4c0b13ec223d61b9bd01823603a25724e2d174eb60681b8bde11de521de241c8dff470f782bf55abf5128ee0d3902cb002c74c5cae10e76271d6d81fd0475ecf48e551628ac9485143393589e85878c1374c6f24e2247285330b599a56670381222b54e57d3c1f38eb00e8dbe5e7046f808477978bf0ada4a7bb7be62b1c9aae02bb7934731c3be8c964805f3287b46a2ee216e66d914b916b130d063616a6eb1fa44d004bdabbe70860859c9675f89fd5978a6a7d601a8a0c6c1fae93419903aa3839e24ed43ecd44a80df420364e7ebe333a295f95328ae6f9a3fcd908345ad595069bd72be9058b281f33e45bf519c5f1714fd39b82e473a66ae4098982c6b7cbbfb7b30d949e94624affc426e449b924d16cb8827a640bc76e18b170fa8cb6548eba488d5fcd4de90ebf8437427ecb2906e65b061d303a29e66dc3fb44902e69ab1836a86207e35841309926d3dcf3a3e6b5908c1f1521f4fb9c34bcdc203d796a8b0be8a385b92addd0a36b247beeb210c0eb8ebc2f48770fd299055864fcc5fed0f37d0b0c8992b6c29d2c3af021779503ed1aa89ed2c3095c43e2860e50491439c463b290a7138035f51df94be4576dfed226544362c6aba12f8b37837952208bb260300e83525abbcba683344af6f7c164c19a472ebe85f68c54264fa30114a9f12395e88ef9966dd6f0ab5d1d3bfea1e8f8e36196769a885f841a27980dd7f47c0ccadf58434f82ca748728cb91a525f466290c34993462d9a71e7a47f0e5bc3b2303be19588d96e1b89b8ce24a114da4334ece8466647034c093855ac8539f141cfd72e89a0de523cd72f2cc65bc1ce9997dcb7291c4424ac7c4e6c2e1a5982d36c6279fc3cf437dd149b5971db863aa414fcd673c84b159828a1e9fc66412c90a70036c181425d6697b84515b11df40700f9d857f1026f711c71ee06e94e86c476ce1cc6cd1b81cb8c69ff05a416933e06d6d3cd18761cee8060dac5cbdde9d9f067b4d622f39e8d3f4c221f343f8c1ecaf2c6d5b9287ce4dd85e43a99148aadbcba7ad20fcbca2908c27a0742b821aa2fa84ba5df63795b17ab7a7598b87fb9b728bac19576f6d9a940b44b964924d82e765fa33ad33924e0db10393f5c19985a36f3c0faa42717072f9e2d9a34e048004167c9dd0ed4f679cc12158d91466e08be8b694994c934acd82e7d452a9cc2b4408b084341a74443863cb6713d2d378627414eeac776fbb3cd793d76ab71abfefdfca86eda324276ce779afd6bb02eb8a6a8f232c0452273f73bad5729877460ebdf9a663ccc4f27e7f559375d252d37a0519b1fff036b77863480d83f0a2b529506d045422c94951b18b221a269be1d92846dc607d0da70589fcbe88f9785c6e60c62f37a940f889d7875266054b07a596cab30915957ea5f201e44ac5595279d2cc7e1565572481f395b24b2bb880e722e3d738a166a4f213a06ba7dd128e1580a5893eab2614b3fbf6d6db963071358cd20bb2547aadf04537d228fc6d1a72baaf4ec38c905c04a770f0b236433df01584455731321862ca75d60ee4b5fd7f5ac9ed4380ac7d113bfda8f65de0d5918109bcf810a3b401340879b4f4e9571b2f5c1c9fb1a71fec99ee75747f6b6efe9a9460c001ecdd2a5ea819ac85a6e5dd8886937de53d55d81ee982bb5d682b3c0e921d8ddc94d7122499950407d112d03d8c9310e6cb3f58e7d31a90e47a6d7a729fbc425871ab26da749446f36b70c8838088a34c3dbf02748996daf5b45adcf85e57a88049c85b9e71f2147ca5dc3a8d2e6a1f84b68bea956fa149ecbd97b822d50913f41f6902d4c58823d9e3ffd03a911d04ea493690bd0d4065028fddd9c3088edd487b5887f8588cbf8b70d88a160cb90aa62536b6334c5dc5001b3e4485dfd4b8de407b1537299bcf6c36492d7b03762e3122d59d2fa0a610e8d4067a0bcb816417b52b34ef41807b65fbe7545ad51f11a82b4a395f498bd87b90915b19d7e1ecba217de937d3f68f85b0e23ca03438a623811b25505499286c3d6bce49b3bb4fc701d5f45d9cbe229b17ec6b901214eea6c5bc2b28e8695a111e9c8e25360b9dd536b29193ee9683ec8101c3d02276b85d7bedc25022a0ee8a5feebe209e568e868b66ab75febe44352b82e8b6155517c89c4442f265a25c38466a0fcba093ca29c4779b4a8886083a39ce39c6d6c836476ca3d497a43c7fc9621e5eee377f5b295b9aa40c0592f460388ba129eb18683a861b447f8085f42df80a4e58f068ed7df32af9078671d953e839581a456cca4d034aa923db9bd42b0723c46198a237c0d18cd6a6481e08edab6fa438b4743cb2474486e4b8035ad6fcc4530c03068da1c04d62c472b823f2f11d7a5e6007c84b2a93c9699e6696c29ae5d0a4937b17d0f4344053f481183087c2bab8fdc5b87fa62873cd53811d911e598ef194408376707b8946030aaf60689f790ee014b8de67dee1ddccfd149e469a9644a4a996b2f8a7a8d8ac4437209bc8df37ecf054c9fdcf2eb5c7cf87d2e477a9a540286e6d821e898f2ff22d5bf63bdf879db340f9dab9ddcf984a0d53f1bdae520ce76cbd19d5efcdf1ed36dfc1259aa6c202494bc4e02f60db1e8cac542162ad04efc1b77b4db45a73604514e3519e65224ac4ca4fcc4156680bc0a7804ba455c8f673070a4c3fe00b02d5ebf47598a635a18099c18da48e3c5fea6421b6711e9d211b437ac2ad60ada11d3968a7e729f6850e60a06c13cf2db1d17cbb4ffe5905a41f877bf454a24909238447cb2ff6f60497aec953c884b35fa82c4496896c765fecfce8a2e94f8d4fa7160d356a1538a0d87e80538f9f0185c38342c8e0b5a7553432ef4185b06dbde434887f6b2f9dee6d0e8bc59a458d226ed979d86a342b2b331133b72d4e89dcc152da2a6952ca3f67ca0b678d34b77dfca91654d5b45e3177b9f2888c8760ad99e99bf2a21a05dec8a82e574b1cb8aec136f8d9c58e1d921cbed385fd5ca04cd98b20440415916e30f2d38c211a2d4bb96e82191f7085c495f65fc599f30628ed0426078bb8fc2a81eb485213d6b9b8e3ef5ebe8d3dd2d2575a11597e50e26f7d32a4c50a9fc762a95f55d07b189f4039af12e4f3afb5d1bf642568007b70d96e9e93433b306dbf826f8340fe257444366729197fca98c1422e64943d46fa4f36db1e819c3096b59e9b24ac4e0e4940943f6599fabeefebdc6d14eb9165edb05f330ac5b6c0e27d17fa953c96a2fe53908b8d88c4cf76a18a8fac1641b9172768f611a7d3c904c7d0dd0a0f7f732dcf78983d1f7debefca4b0695f1c3955a3ea6bd61561a01286eaa89249ce4329ddcdb6bc11835d5b09b4072d14d2b1f75addb4a908b39fc3ba179c6c23d89688d4a374e8fc4a44bd846400cd0e57fe0ac52c3da024e82b94c3cdb084685e028256418cd8d57430617c9c14884bf877dd871c74ae9498bcbfb9173d38722f84602a18575e2f4185f5bdf44c44ae305019471ea44061d52c08564f9259d3d73d328a636ebe6132ccb7e8d6b6841c1aa00c93f177c4a270b52e3579bacb8c37d4badaaabfaa7758afdc594a2d09cdc02f66d89f03c780e3a001b8847aaa2831d97c6cffdefaff8eecde42659dce336870af544a12a0b1d58af12e5886ea048a275e4a25cc01c98d8b66f2fa2b5c774aa18528259788466897d323c54ad109d97f05763c8e2e1c2ccb7bf7fc0ce200cf596c6b8e28ba55666e0eab2e3fdfb6d5ad00dbd1ea6773fc56cea76d806a02ee0d8cdb632039137e56347d69e18b46715a5f4809f6200ebeeb8dff6f6838acfeb260657f58ecc0e0c5e6a6884bba4126c68485ba31f37343ac04e89e863c893f129f3e92a14acb844976f6175c01472b24c56d270116c7a72306c070b99b18822656006d40253b1a7428fc5b990b717c568bd05cc7b11f1c13b9c2907ce224bd32ec89aa9c891999dd2526d9addf0734c78465186b42accea6778b5f06dbc507b27886c515172a13e991a915e7c7f95d181b30d20a864f7b7c3d8614c6dda8e1475be097025355c8a49d671d9b46723200eb0a9658b803676ffd2d575841afd2336ebfc1f53ef211d4043bcb34fe88c06aa0c446706b8b3b2283671eb71f9568e39e1215ff4372088d534564fd3322fd25554710dc387b47f44bc4fab8e1b6c707e9980d9ff268ea72f0e2afb2f0bb70090cd1b0c94b3d61031d64e7d23110b6a846b26cec0b5560eba1d02c03e54320f19aecf6aac4aac952585b8601800acb40271a67fe84c7c317b3214d8ab36dc2728a8135a780278e48de7475467bb39e9b7343ec8f8d08ae9532e8800400c6089778d57e0a12a7cacd0a7dc5b5f880e7854073ff2109118c9dc04445ac2f0e7ff78b4a559008c6ee1b841a88c8d906b00ee5acf97466ec723495241af1ecaa9fe5b87a7427bb9356bf05137f80345092d3fc339b623a826e6d2f28213c83dd1b95032fe22bc7f2687b2c88380ac3ec67ef491b7dc6013b1ed877371b72028ab0a506ee4d36250438335c1f7f3cef52435f7f7ec7495431a1644f5863e4d1a4250d60aa2b5536cfaf7de04c51abd4de05ab98d7869d64df4242174ccda7d22ac7322379e4d19305697244e5124495030241406573cd425cc4ee9bd4d57c033f277ffaae35fa1096f3d96e4eaf36dd302bbcb80f64ea5c4a7978dcb8de06b1824c9cef5a7c72d741f6609c0c572b2a65953f24f71cd42bfc7d29a019767b7b5885e1485bc31a8ae30148020032cf944c79cfd3b100d76ff7c99ad6641b161f3432ca1d70c5db91064f52f86f977e71b5b5f489167e5ec635081fe9584180a5dd21855e35b95e53ad4f8d6b372ab4a9a39a6abfa482a2d19256d5f63b837c4f3188bad3785f4514d00a699850f37fcf53708d2a0c06915286d87adf1f5bf8d355063c7d605860e73e537f15f4806dc6bc08d1093b77a6025beb459624ed64e539ed31e95e8ca5ca440c5c5193455a43012b7885cad713249cb47ce2846c0283a0c490e6880c57beaeecdd5103e99851790f4011afa5f325e235877a5a0bfa96d38f535810f688019782d41a36f358e4265466a0468721eecd40f8193352c4f6aa20432475984255634430b6663022534997844970e1ef01bc69f4f63f2814369d2f745cd6dbb6089657f6eec3d081850cd36827b73b8fc911951ad2b761d81dad57462aedb1102365ad864f5b7559b924e0c086b05d425cf2bf8d36d99cb56add289899f8a7153e27c6a266128fbb88abbb8ee24c4f8f455645177cb1c9fafb5a549f95cfe69970c3f9771e732adce75ec10b16acb8b716e0f22481889b92b846525942a72ba408a11c751b59aa81c7504123e09a4162c554dfc0911ba01c62e4434b542c78fbc35f576520ec9d2115e4e4d5a7369eb72326276a29a5856c96ce2a86635025b70f0ae0d84f92d5a8442bd4d8c797b71d9f2df80ec33f2f8f36823d38a39bf37b77ad434e09704aab409291dfda2a8c01775820ab32aa38020b453c68d7bf2b9dca0401d51182d56bae2b78bfb411ec771ef526ed0323de1777690c47c9a20566f74d9e86f03b33a4613e80c45f1e0c341fa127e0e68d36d3a2846c5b4e944a2da195f784658acea1e9134be42dbea2206f4555bf142020aa876930b272764283095da7fe21ff07c27489516d9dd280c12ab7184c3c55df743977c669580e6703b0f98bf269730b4036ffd27bb32df59e6c8f996f539c046f880a99f85b2602917da7059fe718875de643ffce75a9400f7363574b9cbd3223d7d718cb7d2ec3f3e9fd3582dc4180ea186ede1831c07fe295a4c5288239fbae18ed1d11deab78d40c1217e6bb663fb742dbfd620eb36950a025045a1296f1c8cc58d69f6e99aa882101be12048017edc87860a4a9e53dfab05ab783ae6ea58b237d364f0e1e9c5871484c94ec394d6cbf4da1d54ddf72c08ddfb9f4f0ff393c5ad830534777bb1d4f4cde55f3659a8837f459e5d729ae3184105352b3e69d8fe44131ce64e0cec3808685f7ac4fcd40684646025b97caefd5464f295e8f4e682a74202773e3b15dc9c2cd7eee918a94fc7c454ec9b789291769d0d32ead9070df8284ecf81f0cf91b56f2e79871bb100839c550e1cae97a346dd6c62afdf4edd7e810bd910e48f717c159f6765a5bd2f3d601cf63427009544f0fa6abed4c316d45f10e50afdd501d33afa9e105a6e4a01e5d6c9dcdf274e015b7f9f008a259974f18b9e4d54aab50c16ad33831590e00c96f9d6af6e3c016f14a652b44a327787dbe7d3ac986e84da957cb4aff994b940a922e81ba2eac0b8f4be962e3ba8d554dff58e8b897ec40d0c8f4a680cb11a228bf3105e32617b096bf67b319b64cac1d4c2f74445371329a21c6d403f65529de61419a800c9b0247fcd3cd8f96dbbdcafd730657e651519359cc2bb5b08d98f819158ca983629e2239cc82d13e46631162cfbe46262b1c7dada16f2c36d9b0c520ba9059e92a496226f9736c94c39d266c98530f51b2ae9cc0f3a633011e2d730a6a22563b272306dc398d5e52ac580310883bb690595d3957542b4ed3a711dd780585f5982df60fa42d3aece90db5fadd375f12e988d65ee80b90083614da7298a8bc39ff1d9d918a65f16ddfe890ee74db420f8964ac240c120e59d942b0eff6f9271b56b23cba943efd4d439fc41da041294b8f263f3ecfc14298cce3b6b22fc6365067143721287443c2b33ecc42ac551b30a3f5f00a75410a94698a6f9a87829e8de2d9c6b2564e118ada4c223d8f44b0cbf3f934942d02a4d4d0511a99c530618cf69b5c9359f826644addd9270d0675e06fc5d1ec8bc2e480762caf181a47ab0dc13a95462d5cfb93c3150bce59c9231b7ed5034e647ef9d4b21b960d0d6a898bc528551ce0fc3a027ffbc48bb6de3167f2a0a4e9b34498774b8f03c032f46591f1e5b8e8900d1bc41b404b1b756afce3c3136dfda82d4cc1a5cd7b319a2b314c33790d4b9ac47c22083743da1428b803910891909bc200536fc5ce2b45df325e0facb0864356634716975bbaa784408a06219900948d87d54e7208ae128e29792e4ea8fd560097330308a7e8ef08225629fd84ca6f0a92e96c2178e5184aba21dcf7669ea49a2722e93145ea34e90816ca4306fbc759641a882854e50d0a4173a06cb1d40bbb4af82e278933a5030d220bbb944100b3824a05507ad6dae71861c8d38cd42d68c62c7ec37be50611861950bf32661865fba32df5c787b951a86f8d775ca81cf594534503a4d36b76e495251a8855801b0d734579b0f8bf4725a16508503bd16f46afbbfd8a31fad25ee017ce43b32651569f5b26d3bd9e4db1354910b8cf51e40040709681115b9a05d8c582ada63aa48b8ad53f186f716852eddd94ca08a40aaceefb10a4515d9930277a5f10a19cadad1eb3e9f5930cbcb2879ce29c67a6e08fffc77968e454646ceac21dfa2704590027552259af8edfbf5c4bba74853c5abea7f7b512872d2c338a0bed9bd85de5a6b982f51d10903e204afdd090ac23e4391db273bfa630750ce748308c5428873b92858a481e771b90ace55c5de1c6ecd36def60e73e43491c9f4c787889a619d90a82298e3b0969a88a03ac0cedc5344acbfae1d3d1b2313084a2da8f87ba1d6f82dc2de50c66cb43c0291c5c8cc0e7ec135534e3e26ed040915622d22c39f7dcc0a26f137b6daf65acb96aa95ff2b50a6c7ffe7f2ab0954ed6e923f1809f872857f44e54c264ed7732d28376d9d4a1a055007fa10fb923b28d259eafc47a89e9756ed4357708067e0f6de7c969dd6200818def3bf5c421dab786992512a3939f467494737d48d0c0d9c79a78b7af3c2252418475ee41573d6fc47c9a1801ec50ca36862b1fe984b313fc083b0d0f9be92476e760416115bbd4bd87da197e1cd83874cda87c0797136c5e0a578e55d7e71d8aab97ffaf2286a834db791270e217c0d9fd5f706db36e8f24d481e821f17ef33dd99797e3674fa560b28a1f38af89d1312020f17f146a36c1d23dad75c23992c613a9124d3e5545cfbd3051b91e55db54b1b40bd43a3f8fd9493dd1e3d5f6e48ed5a29bf429a614671f9bdf410479363bf5e4a7ffeb86d3e013628fcf8baba271c94e1bf6e1f16da3d772b39d75304b746201a950052c08f9e7d04828e86847d186aa1335601ad0204e836bd51adb0345e22508484b6cd3cdc9c0950916e318c33b0e74e7f0de57678c8e712b1c49ba01e7238a15686ecc70963a38636c5631ec7df0b59b85d593f2817ecd8a432bf6cc6caf9a9729d7ec93af35ea781b68ae94b550133c35b7da0c8e0f8f4b0b2ea4eea7dba7d39568f2acbf999916ef77f3d9de4ed73c38969bd3675a10c04c9c8902210671f88711973785f216f81b6be9043ca3144faa6767232edb5f6bfa7963ca3d1c9f3d5d81b28ae4d5cef5e22275433f5b4ad917a7f211bd0cb08b0ca7663a2153329423139edd4443597871300b00019ab45225fcc210cff1104660cf48339124969758af975e8a5e7cda4d37e4603270c6985d5facb53334a215b520a61802926db529552da0df2c0a1d2463628b36857e1e1d94813e212d98c984013b66246fff8bb094a59fe4bab6ae159bdc900ad4268d2cc7465d74fa49cbe73cf8261180d1c5b71f0ed938ae74eeba71cdc19a4a5dc0028ec16553fba0bedba34897f73a25aac2a8b7c79fe2a7e287395812d434c3bdbab6b0a81441d6495ff76dfbb55319af7c93cb4b6e9257b095d2c008844dacb0825f6022fde172b29b9cafb5e2b7e622c789d22eb73eb78461043a173e2b933e28fa50ab224578da1dbd6fba8d219dcb0fc7d15a2c1acefdc32a07dae30f0177b15265ec84bf153da268cc814640642cf9224341849e9cdae095a95328ca43743242924f9c1c61e924c9b38e117ad34c2d78e90c5fef223119947ffae2460a98ffda68add68482ef2a462689a320894dc1ca21505b993ede407aa849cda1d073298c966dcbfd935170382c4d38eb7a72f533645919c6a71a48628a91a831da57ec9c9b76225a00a47dd99ff7b93046e2f6240cddbba497e997091b103c0e144aa447641422b238adde70758c94311ed098c55ea92b407569cb365a8563e40474d7fa959dab10cf06a34392bab28821b43e00614fed85356c3a1aaa55bcf61260f3bb43e93d97394845a885e55a0be27988c2c48ae0737fd175fb38598c3463c62018ea43c5272098b73249ef7c4befe1448e86384c893accd48514f76da6e391d2a91e7cf669eeed8971b22cf808d0e20213299cff0f6f5860071ef14a431ca3c91dcbd96acf168933efaceee2b411c3a05215e3f23725e70a63ca02945f908726579ca97a85d468d75945e883d9106a53dd93265564431b5747abe0e48d0cf96782189dd99e06ae73e4fd26982f595c6ecb4dbf3507304c1a5511a345be9049f10fe5390c22dd52d2aacef0235a87d1c5cd7a8da2825b12d2e9bc54b91b6dd1857f9c7963d8b58f6c18e05c073a3224ca1d117885691f9458d3f292e54dc6fbe542aa982b9746f1788e8e0a318e7b0e24c458a7d2389646df1fa468e8a4f0c275e5949e5535237c57a8ba7699316ceb1d407f199ac0893679398916ef827ee280f3b6e8c027bc8029ae130d35de8f4ea640802cbb44baf51c8c9ad262720902dd02424ff5abf0ae738b83fc0df9eb46d4e17ea60b3b887a273aab899a30a0827ca0f79e0b8bc65829edfe9119cc504a7c2a691d102e74efc41000a039c7ffd13d6267262f82b4c85cd8986f274dee642e56e0009e97e887ca37137e0213be7c5c6d16f771ecd40f15f69e95ecfa5234cfee5a426aae4cacba826f1fd7bff17633cd826251768fb6b06f0dd44f50c599497a60015dee1f1ba9c3c16d5e3b7cdbed23ad6d7bac65004cf4f9613977563f6aca206135003781de7617aa53590432a14a841c10fa17d3b7009dd31b0150058d569b0d735430ca428c4a00e98d7fcda98d9afad85c07966b9d0374b0c38453bbc5c36c331a3337a0bbf23730783a31f860fd166eeec27db2eb80834f5b84625e345207427fc3d84633141c661b4489054fe0969d61408c6162f6f513c8160516b1aea89ed26516407d5605e6ecf9d77044c1da1e63ba72d49aa19b49f1e4844c70e0e212ff9467ea48b4a5d0760521d03af1081b83fb61ff05c664034c70e4a230ef8236b00ae89c80e62a7051c911d088c2abd88ec8033522a68fb87db47dbb1d03ea16cf1c1c9273d99ee14c80ed27da91d1d341b0c78d00e1e36723b956a09d4ee7f2eaf586707aa9b7e44ba615d7763ddc85fb3dea804fda11ffe45b0703aae2efd40b27e5fd01f7096f3dd9575d2325397c3e0e09bfc5308ac41b21ed448b10a32823f2614ec147dd003f97857224a761bfd7c87fc01271dbefd6b8b63831746132883b6c432b878881d462f8c64b907fe23e234ae8611747aac8dbed0dad83849d648f99cd6b6ea21ce5483bfa8c6a4504021b72de308264694001579647cb42e6a66a07277a56b8ee3a4c50e6cd4a65961d14a67a7b84fe0c7313e5fa2c4c43d738380d8ae5e23938b361a3eeb4d770fe657e9a198e0c4a623c17e337d63e9961024365682ca5634ae910e8e0d2d495300ef162c670b78368f3027a87398e2d36f36948c8313008f4e062b85f7bdc560c9a66e1c91ca9de004f7b1cb38ac571f9a81c587204b47bda16e60b7d644c7fee79f66f2eab53edcd93b3bcfeae6db6dbbb63c21704fb048c8723129d2c40539d862e6f37db337df49d1412695eaf3ed515111b2a4aa0fec3dd429d127b449eb60a25a9100f1466ddf30d9c76611a89e3672b56fffc605839890635541cfa5362da2ea532a34eba9b10c9629fa14ef1bc0be4dada92d00906f811ae3028297e1fd156f84fe4f0303bc77217be53897629b1142cd96c9601f57e6725472c0b3716c2110e1b20824cdce5ec02640fe2bedafaad274dad4bf6aa8d662b45ac47cacd2d74ba53d8bfcf92957966010e825a1826df349107d95feec3cff04a335d96639134e7dba1606dde82c2ae0dd9b81d1651c37ff369601587a6c53fd5cc8c38adadfa2ba43c934c129e653658372f6ddb0df3e33544caf26268642839cec97119ea1bf82efad609ffc42b8dd46d474136a7986eae95973f7acc1acb8195a70d0b3596d931c24857839abe40c452e390bcf33e975a4e4255d487f5f173e56bc05db85fb98a7c8069d4bae97a950ad51fa6cdd788a20e4d2afa788191c2303977955ba1939e5c8594670690a369f956d10cd266b1409f0ef1b30592a01fa2a6b777e25149d24de86446102c11584e61e26f9d37b09f735eeaf68607831bbb16075211d896c5d27cdba5f99482edd822f44042ae570fa249e19f50563e056e41cb4f019935c04ce1f478ad260569b721afe497856c61af92923c2edf16028cf65477fce56724b13ffc3d30991e89b29f101831d715e00ef4a304bcfa1a5277a94de16cddf5eae3c5fd9ff562312777e04f4ed4ea5243179fe5f44fa3e64f26df2d1df59d4f73bf14b82f2cde5bb9c5109faf9eef0893137224a2066a6f9aea3358f664f383d1b8b8cf3856f80334d3958283f2d7bd74902b28f1c888b74948448ca1be95efef9a2ad0a18c57fc9759c6fb907c41c89d2672f12fc26b90b376562288e5549e95aa1ca447a245d842f19fcd5197fc771bef00cdb6c4a56854b006f6448cddc84be01382325e85930ceb74c852ffe206421747bc45ec856dee7e67c952a78c255b9e6a0f2bf3248e97935a65cabbeda869884bc533a94c7dfad72406aa24c37c80c23419fee28364b0837c7d2156c3a50b051e2d1d49cef66b9d7152509b126bbc7d7801b27c515311175deaa8384a0ec2e3e715aaaac39ed95d7de7ad128ff1273bee0d7cb3fcee70f3819dbdaa1339b41e995efaeeb5a6a65e7015e6ba1becf7bdff67973b606b5c936b6245d26d0af47fc746b53a9b4fb3b30ee36fe48970febe382db3d734ece3f7d43d0c59299686871cec6cf7e09401317d21087284f384c9d9ce30092db0c5fff2bed078e0343c35b3011fd138de43dd35567822f7a422476a558a381c231bed794f72028b43eafe664ae5394d4db411e5203c5da6a32658a444b4fc0ef4f3c6f661e73f1cb09048b1557c8fe6fd5885c8b7122b8ab494329495597b1e504df7e399e50ab21de2ff8e0f6b6c60d792816273432c029b431e8b17a111a7a3c1e759903671cb8e1ab0d924d294be502495e23b5efc941ca720e1e0bbf2f56352ef8262f0a62f6ed87d4629e4fe09893b676221de295afca56bc3247eef64e262c9377cceafec2ca688dd873c4be3db2abdc246a28c7cba2de78675d7361d76c840a651eecc0a2db9ab4e2244c53ed942c3e2a3ac6910a710b39cddca4ea2e5c52b2bc660c2568a3c7583060b5a38bf4c7f3ea190ba8dbfd33db917b13c1bb45217b54019f53085be7e1de821d500a90c64173c43782a49adf5f5181c80dbb7f41c1f9c9e13468140635d8cad9f0486bd0a10b1ff21bbdae377caa8d3e764b8b2439b38708578e672382de17b17a20b7a7fbae90e62080a143073a48098fe0e26727a84741ecfc5bb473b59c71fdb84f3caf7b6d1ccd13c68f599f8fe40efe26871f641fd3c2ebda29736131809387d833420d65c2360541096242876668f6344351d469ddd307640f6d2f58d48586c90cfc69340d7758d3ce9a0d9f2775e0835a6781b6806871a5ab7151f2190168919d21b95ef3ae8bdfb28251d50d6650cb079bc6e248c427e34a7366e71cf048584a90a9f7d185180076d3325f4f2c30c1be23a48385e12c2b3af8196e47a183e085eb1f02237317a52f875576d062d2f0e9b0603350312819bb6bf690ceb789319728b7c045ceac783437796f38b75bda27b9a3d581a34c3c5d6f41b982b017a991b3147c019656e02af822f1b05843ba721b4ab531ac04c383c3385c0a76160c84901a579db7bf4604a20da31ba8ef88886a123fdcf95048fc3a184234c59a17da25ede799bfaf9fa3b7e6687dcd87048d5e92ff39a229655c0d55aac3bcc07fa3684e2a357854d0e4bb529d7af08128fab5def92e858b01e2dfe27552b3cfd03c25cff80c77490782cb0d657141537ce0da170c5315a42671bad21b472d2c6ecebf0deca0596b2af5111695b398143c4c717b1bebb7ecdffdb65c62a81d5af918cf6f0d58cbd655da5c82effdb54960ff63364658aace409f922f2671e66358953347a58481570d3c0edd9a9e0a52174873424555dc29ddf8b9a5594e1751004d016ca8570be745feb7ec6b0a47cb2cc79e63af02d06865af1ffe14576f562e536f7437cb70010b23ffe718649584d0507b4f63714be9047529e808aafb75de0441980c5035a980530c3e38886fe823c110e0b10f54a8cccbae674f1126abe51666a91d97814a6970a32d39ad5f76d83a3319d1865bd14fa2fae908edff19564b2f6d5632d9de0e7c0cd10471a0196629a5d61e99b09c4664f07a8320faba24164460e1fb4e263a4d2c8cc356f51a08189f95e2589db59ce086e884e30f5dd3e3902ef04d632fc530cc4479f8f166a3b8d6634c17c6684b2b1ee3a6c5b7eea07082fa4c2199d5279de8567161f48145f7de3af54e94f47ebf4c066ad4a57abcac1730896e23cad79aa8b5f8b57e4aca258fe438d6495649024f0e308b376744b5c662a6d1ee95c2bfdfff2197d486988608a2aba9efd1733342e704ec0c42c80ca78f4b6a4572106ead8947bcc8c01735d465d4c4b77ed1974cff9f3e28a04137b0144b52a7efbcd81192855b8c5f3b9ad29f195de871c4926d4a1aea5c176ebb4abc54bcbaf0563bdcc26bba15a58f9adf699d4e5d53c500d6f55aaff687dc70b78e0e3f762e240385cdd1521f13bf5c2d2ebcce37baf8b5118e905d15180be43f3d831c7c91ba7674ead6eaef5a18be999e09bd51f94156683f40afe48e87b1d1a77b02247268374ceef84bd1cbb8313c75d3819655e66fc6f8d522b94a518cf068e4c9f76d72ecd5910c8354f7d71bb194007723d16be13bf81c37961c4f67cfe9b853ca7d350806a6f1c758c47573fac9da52d46d6675e361f698d66588260358a4d918f59edc01488f30e8ac6ed7b5af8951aaa062771fd4dea41deca000c2eade0a404df79f19a2427905b1cde9fc72b0ada361dd9147a168e2fa1aa46304affa74a4e6cb3a68d3b9db3d248189a350a6735677869ca4783bd1ac7663545ce792b1dca963bca87db8578b52405837412154a84929027719f8f84c62a2933f452f0ea85131a970369b24df26211f74cbcf37f263ac32a22e128d80981151c435e4a24bda86b105873e3d8e660a8cc23e1698e8a146a5005f2054e9db911faa763fcbbc0205c86ddd23e47e91a76895bdf3f63192ce712ecf12c6d714cba02ad6b7c3810066ceac7da83c1ca30b34386e61e18797161fc774163e909b6252152b557bf6bc036f319dd7537f88b4358a241754aade0d866bf689c6749151dd9ebef34216107da9ae61a0ddeca5d3eba7ae18e4c9653d14865a645973e5546bb530b430039486bf4b420373355953677a5f0500acc856c816a874d831f3ed858e9c1e7d3e2a00f06b475f363a053e6b846945b6b53e8f0de71eb37a10d49252acf174a529d4298db602619e057e041044331ad64caadc7b737898a93849bcd1ddd96e9f81bc6d207dca8e6f1f187a0437101c8d9175d6ad37057a3fe6a7346500126370d06b8c1ec92aa651ea3b7cd17ec37912f542132d448c3024f53e8d6d47ea321e98914b0a7c394be700125276e155e7256f3c532c0fd5d1a10ac14401137a776be45ab08127d4b805f9c83506636f4bd64732f9875ff0bf5a69e0fd42935c062cbbb816107e9ec3ec6bd5688e4a9b414ce28fb04b31f0be7b5fc48b80d194ed333673a6b9a938a8260dbda2567726e1664fdf581fa5c24a0ac561909f5f2f03d204133b2a38548f56e1d750368dbd4dc1cf3dbe246384de6122e22e2e022202fc7b060ab5494bdd6a67f48383b21941f18d376fb4099998f7139dd6aa271573919b47466c16d0cad8580d00cbfbe55fcbe13a0efef82ecf1bec2592070cd366d9f721db3d9b48315ec9205aa262e35f2868c4e33b21a0f1dd016ca436160d6690116086294f17ac4c9e3f402dc04c940d509be40d54e10893d8e96a77eccd6ffe7a6b268d4a1a22a34d1d939fd67f525cb21b8d4c60a09a22470112fedfcaf8deed7e963597e885c8b099843db0a8bcb386dda43c9f9961da5e68b1582d985830e6bb5fc285a07c92c27c4a4c537983cf5d6e10c1dec23d657b5d612ef17650efa05711ef6c6f124bf152e856edd78b2a0f1f0a43c88f71e1f15f40102d9bde3a49a798fc85eb214e8a0f72cbd3b256d3b5180a832cd500359f97edc354cdb90466611ac6418ecc6e40c4f40df5eb56f5052e000346f7505e3fce4d5d18ee27e30114a96b17d02b1dac569fb06f4cb33b85f8352438b93d9d4116ac376a3d92d93ea66c69c5440945f74a43ee38cbc3a5c2dc35f2632d6ec78ef6479916d47f768b0fdf3715e402be01bb07b85e62bb1d8b466868a89e6a83a8f1bfaa8a2775453530e4122c9f16df2ef24f77b7a986ec2975ddda3678406e06d0ba7c525d6610e773c83d623979fe9cd23b00d67f9ce87f1cf2dabe327792bd225d9b3ee225fe9de7427f6c3651910958ffd13cde55eb05d90af8af11211745872a595e0ad787b6837055ff53b5881ffba8833a332ae57b13721815a47540549e03a9d38b6b89aa6a0071439a390c3414399cc49d0a27e33259696df06ad09fecf329718eae36f58219823d8f816a0764efde7542999aed9ece46319b876ec8219c4bc8a79750b6e87d02623e293a63b038c50656abf90098de7d1667f0569a36a5cb19f7f2baa481ea8f53570bb0d744bf5ec6716f07c0a00ee32d835a428ef30e3071c9b278fc19c4089c1fd1abda374c939dab7ef38083315b8ad9676fc40765a22c727640fb24635b86da0e5b9ee8faeaa1811c5152154195d1808354ad9da2a5ef80348786b0c482c7aa44d81f39781ac2e9e87616504ab229033de9bbd63ae5ae105b9f282ac1e82a3251e03b2955191efed4060db9455710c618f92f4f57f512e5eb8aed02d74d71bcb11c0ec2e9c4e299a8ca72c600954829c32ea7f67f7bd1e1e1f9a9844d9614cd8f5b78da4bc455638707a299050ccdab95ad4d2a91bd9f81288f09225871a358af4ae09ddee54edb7801bd51ee01350924555abd312590b106755314e86b4c6e4daf14fef4d4cc60e7f21827bb45d5612456206a2bc5059010ab52ba3ab568fe9a8e1ff303c4ad6a6ab38de4887af7107b9cd2408d83e154295cf57da5500f0beea88d89854d180389837902504e11ecb1f53270de9d5811ac4224c074463702917194b2c822b8149a4293c1f02c82a688b49a1eb55594927b0d051c06893720d4749e07a417403918c18c2becad3010a9badc01605b18c1e5e37b22c4f508af01ab719701eabc0a7489c5bdd0ad4e9e55a01bdfd295ad85128f288092325df61963020247e7816491842902553f9e4b05668a367639fc7cef98cc6afd0164891bdea9c9c25d386c084dfd13651c7ea16022db893f873e5072e599b55d627f513aa95bd8b1bea372c32893199219199753993a484ef18e8af7930c893b91c5206f254dbd28514a14ff14064365eb1449bfd23165e47cc87593a6a84134292963c87a0bbc8f3e0c12a3ea649bf499b87ec60845c44adea44009bdb273fc21d236ab7e01e0ddc267def96c6474096a8afe1a49f55a2da00a0f30a3fee65047db0d4da64ab1153a01b7c9f7943c0b8e30df564b149b7685a617388d5e2fe1564ab3733c899ab642e91da185d424122d84bf410f2e9e79064e70497869ef50fb9b9d8f463114c403296c93cdcef97415ce4c0a0c5a635d388547f47043c623f3abff46f9c14ab9b5f1c3357198c456d2019a69f448e52f532d8ed2dc474a96522ca1656f0a770f6626abb5384c437ee3857b212b3b88aad59e6b565d8d47b6b79543ad7e0716d3cab2907f512b9069d5c7de3785e068ce7b312d1cdab4765348d658fa6f539a5d8a7306517c8d37113868461bdad4b1f32a7919ad25eec686b1f8f84cea836856a94a07456a51ab56fc96a91be026c15dc5d9c07521a50582f99244e8750f9b6d575e83373dfc4e2605ab5812c920b9a643133ebb282cea0334f71e5437d98da1eddc0f50f36ee7cc78966c6aed662be312c8b1ea311f8ecbd349051c66e0229b54b499a77402f6a3a3f4149e629eb977c58ef0c0ff87f14c8a904ba219f297141efddad5617102021c61a5053ebae4d9c9e40123ca730744ca68a357114b0661da17092f2491aae12c8b9c8aa72d3327723554fa5f75c7a5be608e7a5674ff534e9e82607355abfed7b234eda1a6d6ab9ad3c35814553acb070dc85d0ceb806dcb0f69f71ceb8ce3878d708cc6b848e8319275b90b83a1a72790c117f7ccca65503c5ca178121a89eb47cbf575a7b7388b6143e8d551cf57c085590ad40f9ef56f84945457140875b882166d4a9d22e92a5779fc880595cdb47265f5ed1241bed76eeda69e00eb5d61b272bf792ca393bf73cf5b3a51646851fd5af9f3937981de9abfc75a7aee959e9e2eaebabb5dcd0316e0a7b13ec0a24e67e94cac4375acfccc01cc7496e80738c5d8f5fdc40381d05b8bda1e770541f9630ba1907cce783756c9803efd43f977c8903f59cddec74cd972bea9de860aa5c18db6e4b47e591b265d91ec7d476950bb3fdfd64c8022746c7366a060b451c0b502c9a9e3df5d74035bccfc9d16058f2ef1ec2f5c15c3f966850826cd7aef05814aa51a7918285702938a996dd0c643647b384d4f9bbfb85f70209d4bc1cb9a7b4100c31d220203b88d597acc1d6060e7b986c1b111f9d22a211640b5b8c0e4bb40482e09242ad3c80d6435d2445decffc44d03d8053028147f33ceab88dcab9b78592cf697893df46af231629a8c8b6436090e95ef88b66167b75c7b6fe7ee52edfe56fe2c97e78aaf11c57d9dde7d9c52627399313d27427df2885488164dbbd231b56761df171e81648c14f158de379333e83f9b7e1501e67ae5bdc9330f4d46fe320cb49d2b055d2a18ebbc4dcb008b9e41f1a72d54027cba7687e4419b658e4049b5726eb240e060cf599b58bc06966d5941c8ba904ff337ae8fbfe85fa838065b99e0a049a0b38d3bc5086095ee0d5a298d364eeddb1cd93fc380ae6764d2e8503201393b6284ad90ab557367dae61bd08b604d2bcf81979fd4a643b68764d4c387022799fc7be21083eedc7134f643312578f9fb2d2e6fb1598eca608ff3cf9a49ffd41ed1e525077a9b4f889692d4310515a4cf2c3b2379fd533fb12cd74ed248703ebd74bd544da6b8bfb83f97f39236b916760f5df44423479df36bc6c53c5d4f03c054ac0a85d9076444e85026ef7c4888c483b2c46c010350563f5381bc58b5af76f2698b0f52702a805f1a3cb9a7c5e423186f93532c686d54d9f39c0122c357dc5b01cf2b88b2eb8bc7e18576a30d46308c46075267bf043ad3aa4606f8ecdb15f860c69d4133c03e42c1775ee5ce4c748640e859a20502e97df08009733f5f1e93a8a8a959bacf20cd4b46ce47768cfb268248e00873d8e7e0ec1380948c087419910717c2a385fbe93b5e206d8106729c2b7e6e74896a472c953f9a2f80c82f2f285d1725c272ea735aa949244fc9f7005d2a43d23faac8e788dbe840436f21df1a5c0e11e16159ffeeeb6ecd8850d21ea0187c41cf27aec5783b2c379a04cb65a6c209337ecfcc5433d6df6ee600156b2043b2536eaf00aceb28ccd3a6b86ac0f73e8c9f407950221f1f77768339590766c967c617fc651e0ba92e2dd006d0818e1cd3df05a29d5cb5a5e5da07c69a3617f4bd035874718f4e7b437e20ed2904258405d2d3b1addd8f0c3e92507970281cff5b1618db7ad3a6405a868fa739c2969ea9864d8f1081d203b6628d50f6ceb2c26422fd5920974cb92dc8153227eb4e482bf12802fc626fba616b9cb30cf3f087ebec37f8033ab4bce2cd1e0c5e5886cbdf6fbff038784f9179ac2800608f0175a8ff5745361ba60d3835273e0d1eee2d1437e2f7f8fff57be2baf823d78bf19be36b1a485b84031c8075097e19a71e8ae80882dd92b507b67152011f2d3c737bda76db5d1d316f01efaf9e0ed0fa11738f5e8808e08adeca6ef235aad17838e0bd7cbf4b15c53ec793286025f6fff9fe46ec95637e1e095e5dbb8f8a115ca14baa15e373c5816d386c00acc88dd1a3e472e4692b1960d082ad5e8a062a953ae6d220529e4ca67f17df9bc69cc2903db8e41ee9d7ae41d63d56bc0dfe076790456f18bb160b7fce44df852d73d6067791eae69d6e9b06c0d8739433d988f0fc769f31e2c298b9ad0db0dfc0aff0a22f6b3c008d09c906b081c7131a886c5941c2d9e79c7db5303ec6e504c7b7e3cf24d28bfec4aa12247af47f3d5171d5d9dac0fec0387de709201f235138832da48a3432ed961ea72a140420a48a820c8676a9449b62e2cad85f63f64e779ae341898cb722d6b5242a1cbc49b43c43022c895d864a772881ac87423db7a94fff7df2d763957156e1b0dc2431f6545b9603ae4ab2838e0a5d7e42780ed6121e4548ff02959e8ac6581eb102060fc1bf5738c7321d4e5477cc73ca51388e1603e00fd6580b4064561c004a4796ce7c0f0d561e416a1a48ba26a5542456b4f46a38490c4cc5dc4f826fef82c8eeab2aee892b78dc7de1363c86b019d5d4817f067eb7a8722575ded07e5397b727081a1c00e90388dc9ba09c21fd16b23ff72a1fdaddb012e174579ea4290d4709511fe0572de5df6f1beba6054d3e8dddd03342e817ca82384b9c487db5ca88a4e1b73fdf500dcde5d47a419419212a025ed51b6e64cb18310994d6c36a95b7b8e1cdcfa4a27d917ac8f10247e40a03f56497894bc86cb3345863c7ff0dacc3f602c0275e46e4c58d126a59e171544622cdf587776645ce89df8007a086d9f1b786b7c50040e4bceab847c2d9fac05bea40a6848f6ffebee3fd053e30aa6803d0e9136b22639d1bca08785ae8fda927a81959ea3b13c84b7081afe73e1215c97bfee0458580d59453c853ccdab3447149d260a9f93f960aca9e5949f7a2ca6f7aeec4c8769a2f4738d79c632bcc2ea737ef4482cffd457f0658d4ca8874a7140ff6e76d9dc05616ca4ce82bd05b7402c3e18103f7e21b1db179a121a823043777d7ac698a8fda28be778df475629d4e64572b4ca5061656040ba3ba79cc3299c14aabe35baa2b457232c12d150a4e9e0d3b7a4fefea3e3815fe300a090e6c6be04e8fa511f7d9a65112d3a48e35fb97a03077500ca39f10dc249dfaa7ce67edd05501135aaf30cd2935eb06de6f204149e60d41099dc619a236b7d20634209c318138f3216a69a1c9770e4c3bc0999b33f23c2be1ac6cc98e1f48b4da194ca9106f257c8449dcbbdc525b7023261ba145fc4e616b1f0094559b7942176c7824f3969be8c09eba3fe4310ff76dd5ef39b018990db75217b3ed7577815e1b96ec5c271f1bb8d6ff53d859ea0c980335217e24324d4ac1fd5581b6483f199990c6dff014f0a6c7b46ffd0fdc1d035ab33d595930dad53607e5c8dc585e72aa45b8863fd60f0e78af06e43efed81b58131b6b27d4b4fbc55899a3031d56ec65d89c91a1060a27c1604690d718705a8e83f9457e7f9e6c1d0803e0fe7bf2e049fe9f8fd2464b9b249152ca94646205860574053e45755a29469c4cbd47d662f9567a29a59ebd3806eb9ab8f7d251278aab64431b924e4c8200b14aac6889946559966559965f7ef9e5975f3ecd975f7ef9e5975fbe17923165d08636b440acf811c9164869436bc3907c7d98ef67aa4aaee4743a9d4e65792a4fe5a93c95211b96655896362c4b1af35f9665f9e597effd7f59e2adafa0c39e96b021b640ca29eee28fe1644bb2a17b5d156c68c37074981a86a10d6d68c397ab34030c2349154e766fce7064dba8497cfed7a84d4eca904206bb94fdc514a394d20bf3b277af4ab9d9a3fb18dbae05c7fcf0eb303fdcd590922dfe8b81b2cf2ac8bf0b5444d3ba4e52fb730139ae6c67cf9bab0a5384385d40eb77398b4c8395bc3ce62ddc2bf71572104eee63c67ac4324692363732aca6871e3ae9c4c483003129119d084729c7711cb771383f080cfcd1673961ba143e7fd6e74a702cda86ff681adc4731c5077a0ffc89b6d1393c98a61a8073f5351318cd976841b37eb40d9f4247ac29dfe3f09e3db9f3e03931beb66aa7b40d1ad2144c01838ebc851df3cefd4ee5b40d6fa552ad9c0b7b8bd56a1b1ca6651bf66d4a350d7fae33a59c06d399985eb7ba065ef87c4c42071eb23c4aa93dd8ac1437e23ab57d380ab930f470348249c3075238bda0280f3dec74b84dcbb0e63167f4f0416524b9234a29a594524a299d18e7fcf9fc87a80ae35528346766df20d8a30898a04b8f7895af4d05640a2a910f2158562511f742784c2d51e76f673b140d8ad22d681628ea0485eae137fcf862b22ccb2e1a97c025a2a64b494b391d3a0c8d6a5d0b0fdadfd741cb287d8c7eefc0ec077af8eb073ebdd8fe3862c6d829a0e60a35572857425128cce650da36f8b9cea6c53eadb97ef02b07ca3f1c4551144551144551144551d4bda19110c2d0462770220bd4a3380c3a800043eebf66cb081530c678bb19085317f1da22d9f5393add17395618c3b21a343964e4755dd73499ae1d95da1db9acd4e693bfbd817df695ce15ad533401840483115a26922a884832917600294b20330048e43967dc073d938934994c26934966fb81c1efee340a72fab6ab967620d6e1f9f953dad54c8d408de3691545892a0999836a728de3d829cf7d918c580a96923d675b1ea198353899550c804963219988ef68d2c4a2f965d883e1513c67005a64fa9d8f233e4670a6625471f538f2e0466c15c2e73bda862b691ade4614610a57620425aa2528811c57230e57e24a708cd738625ca372857d52366181a1cab1a9944629f574321319ec548c4aff1472a5c9b142ad344dd33c1d4f87b1bdae9661d85e375585154d581bb76d1cdeb86de3f0c66d1b87b7546adb36ef25b4790c1bd0cb5ace89dc14881e33f84fc76b0ba2584a3d218212726a624a8245b1568cf1171983583948a886babc94fcda637d755d0dc5ba0ed234b80e420a1264073a08d28320a828c49448ae3872ce5967d5461a83c5b410af61ced42fb848824344c6a1f8ca552a8ce17f956a6555f831ecf157ac93a12efecd64013e84593d0a93cf875059b6e322759d7380ab542a95b52fab8d8806843585aa6e0200838cdd8729ba6e7713864c43eee59c73d79b5a2f16dcebe14904217e3deddbddb79b826677d3a7d76317c83eb6f4de7befe6850c7eea67738cfd3f14b04891bd2f640a81ac83c5f5ae8b8b32e79cd66698bd40c8321b17727d50a60273e5f3b7995d4c61b6ecf0813cec1235c3de5a6b6d6685ec9915727337441081ee8af495d1227fdb9b9a1f993e58e307ac0f9c2b1afcf3214992bcf7254496aebeae797154a4722453cdf615b95ab739b8901ebc5f6b9cf93db6eceb532f5d7fbb52069adb83738b1c201ea20e39508b38ec8b21438e5c6ee370d4dc9426ea9b3d67164eaaf03172a2078d691199fbcbe15199938cfa9426fea02e8c658a1013dc6863e17ae374fc9024cbb4d30fee614964ee734b877bfe3fb8e7bf83a344d091e70fc2f3f9c10683e32ce0f9dcd22a2a313a1ff957de07d7e5ce07d739c863f437b017f58ac6ac742ddac7985ad72201ee57de022a1fa30bc2a3d279ae9ba20e688a3ca029ea96c3b4683fc4a7817f611886611a913a52977aea247d726fac25ed63b8df3aedb5ecbb3522d5544b602d655ced50cb6853ac9a6a69eb8a700fd6d2a804aa715243b4711c4712d7d295611a892d79d552b6d512564b9ab6615ab66d9bb665dbb665188661d80f2e9381190c06ff055367b2a9c5bc625ad1812ec568b46dde8bbbd490c18126071ca81986d4596bd79e329ece26e3b99bcdba2b32fdb985429a0efed3fbb7d66b1c845a6bad1d155e593aacdd42097b10dc6a6152eb6412932426afebbaae2773c64a53355902164d7247c3224f56aba80da60daec89ce93cd7f512260d876927d3f3f7c6a2499324258720ddbb45b65ae410414c1b4d360de736ad5bd98f16d96a915ce7d2c1c241692b8c1285c17a7466bcd036b6566bed5cabd4225b64a9d52a795a2d4f89eb783cad96c78343c7699082b5d68a22c639ff6b1fcd8de7a8a190e7e5bef7da720b916ba9c1307cc21fc6e00cb75dc015007cce8217e459679d2dfdf88710cdcf067bfc36d8e3164c9ba29aadc51275c1c0206a218af7be84481d23cc39efbc5bb4966d91e99555f22c45d0319e0c2928c20b9fff006cd04ff17ded9b86c5b6cec7111f5b37b51d884ba6a0b4b71c8ef97518688b4ceb4f6e87ecc33bf4e31bb82ba239e77d5eeeb82ead7c42a7d3e9e42cc34a0c337c5e064b0c510534452b20950c6a510cdd8f23d8b3fc87a5e333c8685106b98805f42023842812e3697c72376a10f9597e05d4b2a2f247f011ed8710cd009aa24f86719b8e180ed01a484706c845193445db16e33d5088ca5017acab9d9597e163f818cf926198eb1ccc029a2dd218191eb33c986374169dc7d1438dcd8d0f397ed001844aee782787ec743a9dd99d4ea7d3c99f19c3097905bf4a1325a81edab74c974c6d24c98925460c31805838cee3a1c901877b3dd4d8b877d33fe800c2bdc65ea094524a29a594d29e349c93e5edd7183f6bf8a60058e0a35a648a669195b7b1a295ce8710a2958f010ab1688a3416509f20758396c89770210c51a4f9114c9c208a348a8223986089b47a04a9092d91664f80d30529d2ee14486a8012691793205c4125d230145054220d2b021454a148cb84ec201469dac602ea2751842b91b66d2ca036c2881ba4441ab7b180ba0826544889b44eee84c0f3d5074d26478c1889b4012cc94f54bee24c1bfea4690871184c9c3397e7b36a25cb790b3b4f2912d3e355b5c49c73deaeb6e68c2fe68c279f4a075622a52ac4fd49dba8389346eec0ca8433e173fb2bee7d6c2892413b6ab9401a4259d1987ca3380c062fcb8e62bc2562ce99edc5061a3a7634b16ca29e8ec807da519de29eab3a1d8b4bf2d90e94d9280d69d8aa3046476d10af87e1741ad2b08661188634b4f6e596d955be18eff5ec23ce0ec018a6d9bdb53692318f5266cccffe721ce013350b8f34d4c54e549da84a1b06398274a152428ebb5add253587aa0b25b0c6d5683bc085942f56f77a09d5f03c29c158ad30cef93fc7531ca190e7652e2efe8f13f185141c0018e48f98c15ead1ca673accd716f6e3169f48a0aab1386e9c8844194e3c175e05f57dd884bbe21efb460ce894321defa8155b9bac77814dfb0cd5952664c06006cafac7316f690738693e9e2bff2e273af24968f72fe7c7c544953e54bc8988b9e9488d9b91467b9776d6dc14d0dc3bf303d475123a68bbf14244aa22a70161593b88f95847125a9703e82290916c5b6d65a6e7bae7beae2e07f18ee66c2b611ad1e6dc3a604d75eab65afaa3db40e773219c6183f6e1b3a7a4c7c84c76ed6a6a6ea7a11258aa2288aa11a353659d45ae76ceca2fa824c5f730fab9cd18b0cce32026ee4ebef20d41b5399c1df8ee3388ee3bda1910bafebbae6e4ba0f253f9e4fae992e2e8009865c43155ef3e347f6d8ffd87ad42175116f6ac81a92478fa68bff689251c85f230ab98507017cfdb4fb40ff7f5dcb0eff5d8fb300041924d1852d82a08552047687795c2928a145175f37a7838c530627596373e303d7d1e2aa21a37531e7acb356ecf3c7d35a7b85464411a191a99a53b5aa610d925347505a1508ebaa54dddd1da2636fdb5328582ba20926910f219a481729d191d9796780e763541ea4513e5a2ad1122d8d4a9a66b79e344e04db69d756b56dabda76752ad59c69fab967ebfcf643883815ee8710691d100da94b8b4dfecee78e862d990a25659fb3ced3d11235512a326847d9db133dd9d169644fa3d3a87b85655bd6cd3963b37fb9fb58afb88d82f4823aaaac7553d5c861ee4a4557aa95ea5af14156a9ee6d194f5cd7cde5836a03d7755d97733756e4f999f63df86fa0ff5cef85643006d1e4e01e8e1e40a522c66661f39bd5d92ecf59af8b5bde139ff4095bdec4a1987ecac950fc496a625fd2232715e0d3beb62dd01b8dba4755f491f8d51a02716f840251263455494dc345da8d843811235ae7237519cd1944883ef2d168f4987a4569d6a2c874a6711e001dd3316811fc64568b22bb1645ee0ffc7c9f6cfbcc19ef743a38e2e7b3849a2a52f173823cb96e063d3a4b02eb01d7389c0e2bc8af850cce164e0cdac59190728044132d22c960e8a3223645418aa2288a62ad9d1f4c1ba0c8f33b5a9669dfb1d66e3ee6143e602214f9b85e080f293a72fb7998f6825a76b83f5da5bad7407ee0c7ba2258868b4075f48218d4757e1d9b0ef795abe97c77b8ac6e71f30495fdd168341addfb121a6952e8a9892909a694528a67eba3df4adc05479c2871a2c497cf3452d20ca5826b65328daa9c24a4911494131393c9f484c94d3e254ac95af156cbd432511c984c485a2d8bb24b9a4c319551587346e5e20a5df8dc3e685354455d4ca696a9656ab54c36c8df72fdfcaed4d5c42f158d59b99e78a916148fe9b28a19e3a4542a954a792e219fd4674fd4c55b4e52d99469cea04adc85e2c0549a4a53d932b5544b98584c399b4ca4c96432994cd70a0c20b40199265babaad86d0cc3b02539866f080df15f7f6d13a9a1e44dc6f2f939038446413748327d709236ee79dfc74fe96dadc66168cc0f3537b4cb51ae7a24ee28cbb2eceeeed6b29a39a736b5d63410f7f5c7075bde344dd3b4a7716d730fc7d542d74a69571965c6d7b5d61e31abfdda3d8d5f208caf8b3a6a9cac3975ec1089f0002214d262e9506121d77b6f8dc145fe0ffcd0f4efc075bf7ecdbd5173df723740e045188d829e73ceee401503584b336248dc7b2fbeeebdf7d2eb567bafb5b8de8de534978c4c4a46ec88030799d2c8ac7d77c39165a426c284c9e19ef5c04c5d92df3bfa7e942cfe74b807f381e9f9f370ef0324989e5b92ed20006814a0a8708232423a31b15fe9052253831ee10371f83a1a2c0f6f71407f1ef26bd9c1437efd1ef2ebaebf1607f4d7b2c34f2490beeeecf7a41225914824128944ba3744ea187177afdd3d8634a626c9c964c4f675f12b810411a2dd6ab28cb465abbf6f4eeedf98e47104e2030022b93ef83a34c78300be3a031872f6f87176cf823a3e903e109faf3e8d8fa036b9974b2cd94734943503b5d4faf9d86cdf22feb40de4b5d85246c6fbe914db2abb2bc5ad467bb04b353fb00efbabcbba161bfc6204feebd1bd5a06711797d265d865d865d865d865f8a2a2827d9354f6aac3322ccbb22c4b6b5fca184ca04e27146b0a9395125594d493d309632ce5ca329f33442efc099281c4b4f8449c928373c8669c7d76b91b7d5a32636a6af83e594c27a793a8042b419d5a6082214af04dd2de93b34e18630cabb141dd40ca1778d049e183dab60ddbb0ee31fe1504e6fe911a615b66b047d5da17952f68d6da69a7c51d1b5bd3384204492484d664145d3f2f900f7e4377b70f5b6be0e694c1900acc21b7f75e5c20bd7365ec7018eb27db699526398d27c8f56ac2ca8a528a1208655fc103d58884095648018491342a41c2c1109c71f45e42b4c385765d2fb9862d642b10d7556bbd735a4f167d5517c624d684d32a8a1255123207d5e48ea3aa474a2bbd51a96846158db9c944594959b8062bf2a42e2fc83a8fba786800a50cf6489be8aa010e64e0a655f6e80a3d5aad3b8ee3385e17e785cbc0b6dee762528fc6e76f593e9a61b08690b135c3bec4f45cc084a906a37164afeb3611482881d3a3ab464db9412142134f89d290bbd3a0b134e744067b341aed20f7e0e19edf7882f60ba533d4d3c57638c820a6251d6cdbb6b57a6cdbb66d1dcffbcf4a6470b63e70b2b2fd56aba58282eadce4a03a76c86053065b01711c4d0e387aa8b1b9f121c70f9ece6a73ec8566ad9d765ed8cf8bf1cda670041654221f42d0a0c24a1465ef43081a56c84288487b2166167250240aa2ad6fb801c4e1f3cfe14f83835540a72b420efd10419d3316490f69daadea97698c942fb81ba150180a3b0c851348c04d08019309d38a9950088970088e1234eebd84b073a75a6f46a50b46f7de1fe4fc00ca144d9053349102711f3781918c93e8fa16283214597c6837eeeea57a363e3428a24b850fc0f8c0a9f201111d50aedbc950971c2e0d122a3e70b27a3055d5da9266e4de712ccb1d9f4ff9293f234ea271eebdf7ca9052d3552c91b14506272902df8164361473890c2394423bc4aa6d35ddddf1a9187bf083e149624badb5f45a0ce49f18a3753cdcc5ffab3b505fcdb99da3c0052600030d683b546050426b5c8d754790107c11da71af2b546305061a8b00df9c12c5dd2f56b7680c0de70c28c6d7a45a0330258bd9635bafebba2e989c6f5c1de8e3bb78f8879f86ee91420a84843b0a7d5c3a4ce91e48451cdbcf4e87fbad0b627b6e7bae1b421404f79d0c743e3f7d95ce67117e5af2803a201ca2f6da6f8f3d2b88edb5b7400bee2ca0fdd65151744ca7d0162d411a8496301948e4e11fedeffa9a6538e6873b1ede3474984ab32830bfe811cd4a352bc5eae80f2125d13d275e16de161e0ed6c562b1582c16c65e8e62db5a6a69cb58eb323273a63ffc7692572783657e166152978f0a191742768cfd6554f6de1a342a5582d184c5643585848275a94bd376bd2297ba346738b144c74fcba7a2fd8794431f7dca074d29b207f1cdaec8a539d3ff83bbb80fee6203039386ff8c06e60c77ee060ff7ae07bb74030cec9b9c0e334aa67d373bae86325513bb8175ad2a92c12ea14aa55229c707f740aa9b39c33327d5cea6811f43a1b2ac860c0e3452604c92d9c74c4336b96d5c6419b68d66ce601f27e348c6e29250f03fee36f206c69d4291ddc6fd9cdd3daebfd129cd094992645ed180a13a94a8c5c0d62776d0597d630c04cfd71d0e3232d78533a4858316929648e2e93c264a85802924896173860c0ea4e6311821b5bb975d204e51d92d2832385b94bed8da897da23f421f6d3320cd39ebac5a5649b4da11574b29a55893b06fd29ce990c6ef0fac94c126913e0f88b130580eb30a50c06c8a0893c6942839547cce240559cbb08d524924128944b2f6a563857befbc531445ea7934a48d628be258924183eca1b73c9bf8b1ff74596eac2124113853b7033ff5ebae676022b78c26641f0d046708a70cb7484b5ad2520cdd7075a077832dcb39430cb2add124947a61e2a16c5032039939ef17e2de776df45b88d77879b1c1061a3eb16c510740684d42b69f13294d454585eb171d667ef4c39cc8ff953cc0f96cb0fff8b3b7d1402dfdddca402dddd574495dfc4b20ccdba1bbea5e0b71af4527f93ff1092a7ca08fa6286f5996652903671cc7d32a8a1255123207d5641ce99c18ff77163295a9e99691e971cefcf56559d6cf910cf6785d374a669fcaa8840e7a64c968040008405000831500001800068442a16040302094cdc37a14000d577a3e805e401c48644916c46008a2903186190288018000019299425500c62fcfd202af25a3cede476926b63f2a54c0fc9206d818fc040159cf0a4f6aefc8f07baa91e60ed749d43d2b2075d1742bcd3bb80fa7945c6b03d62f00a582e2d7e383ff660afdfb19659cc6015385c8a50af437bcd7837524f7e494b6b04eac50e64b6d2bfa44c81c8133c6b6b6e73ced05c1a6c24ed55d67c4e793a6a61b9a093193f59769678b8a9688a82c81b1de893b294755e589a146de29e630e9526c64172833fec060d2124d7fa25cf39990a45895a744f0967917d7c338afacc4d92b9f99e2f1427963fb6d07bbcace34e5bdacc0d7fa2d28aa0865e6ecdeb93f5f49cd395c52ea42f19c6a8e88b23f8427b54f294d5953230599815eff254d31dc54b89aec8180f0f077c75465fd946cec4cf1462a94c23935156412b78c13bc3b0c5dceab9ada90ac8ab60db5aefa194ea07c7797fe8537843952fd2c2fcfa626275d37e31da9e4ccf61327d44dabe18340429de298001def786b431a7ce3484191777928e9314ced8158c3ea8e836b83a3ac51886c4e8d470523b04b12c0e8dc2eeecde12a9cc60f7b6ebff74d1b80f0527e7c2677df060728f3455f7a41ec225d68c8fd8579603fa9d283b0b1c37a7e5568a19c1b3586e2830b848d02e99c4dddaca53fc844f1a25075c611859fc4e19206d7511e6e3c024201e88d02ca61076ff5210056b04017c2c22a71ee401e494bd4f7f985fefd0eaa0e459f1b64a5c723c562ea632c58793adabfded5ef5542a57da5716045e64bec5801589356b54dd105eae52abd44ac0d454f65a70cfcdc7553f8844295d833232334965459bcb2bb59dc359b221cc1e6541b33ba8bdd2994ec0e1b34f1aef368c5baf83d472599f6467bc44adc24ae290a6d0b8a8ac76bf3d42a0fc9f668b18bc958f481124b044e07f00618073b1925e58c15c162a9b830bd28f0e61c41b2629abac63472c652f087b0676e3b2d85f830ffd62ac5e22d87846f368b9a357c2249ce95e10fb9080af3a98176a217ff4d3d00a6e4bbc6cb81877c78e0356ccfb560260c3eac23fa2d9664fd88a11e08fdedd7cb6ec92ccd8b522a1d41008cc3c232bb64545679de519890597285d53b2671ffdbcb0dea41897c74d49caf1341b38640217cdb816821984cd168191389176c94cfeb9e17e068949687149e541f57a3f69ca67466f159c142a3af050b1549b8978345f8029501125fd07cc395a18741557f54496a191439f69def98e4ebd420bd64219404b58f3c91af362336cffa5c9eb0e42640bec22829b772c9eede40fd4903d2869df7778f7ecedd5774f4b100e746776a2fdde95ca10a3feb74a75ed01bccbd4516c332926999679fa8951155178a92b9c692a0ff1e3bb012689f538ffd11e70539114f90a13b0da116f6e3c87c8860ea453c91c4518a98921d5c6162d4972b74900ae45fcd8753cc61c5370c9d9e11ded5ad755fe3d0732817759f78221afb637ec7c57e3993e30fd67becbf4ae2db726811d0ddede8ec02734a435f2b192e4c8217c0ec681c956644890b25453d4b6bb720b1302676c43213334d24ad2b627409b327d1bec1ee3f06fcf0cf2811089ca9eb4326473277dc27d2523161c5617e954bca0df6c2a9654d1c3c4ac6719087917a382dc680231256c87b822c5a9e22c8c20d0035e542af3649632d6a533983ce8842a5e13b6482689a6641792c004890c1f19842737664dcdc43b4dc79fe6452dad542b3c48a7ddbf4fd26ec1b3b4be258c89f982de888620b28d7aefb09c469da0dda58e9282b58092d48f7e06fb59753701b436c87777b513159b43ce6aa785751a5b7a13fda5703f485c159a418ef0c1cb98b569e23eb32ad5bd151c5a4f75409c3e966de62d24c66c405825a5caf780d4868827bbb2efe6ef83e3543fada09ee8acd91b92a9b0a426799ed9a2b40bab7a9abc3c5db38cf3fe312c2424237a6ed5cd106d07b71e64f297a4626c30f4afbe78e2325258c0087b639dc9683ef76edc4dde00a1a83b73fcea0e18651830b82d60940785535eb302f5506faa736f3df332b86579a9a462e8880934aefc18270b8aa317628cbd245e60eadf848b366b064eceb7d199d023bf3f16100a7b4247af47512ca48b29a84359047501d7014628363ab2d0c0942d7f851b8cfc5375d3a63dad817a3b8aea4733952284530a9b16fab7827aa40cb3a437d74f5925b64dc5c9ea6ebba9a5c47bc79474b1cbaac42e3d8e96c8eac84225f706d16e113eed66a1b265f84ee753c6521a063f4b09ca749bb03cf7caa3a76c7f69e8b1f1fcfb99dcedbb67f5d3ad68d2390b39759ea25be59ba325308a46b3023a49c08630957c34f3ae33cb446b0b5c9b5e9526978bf55862dcc9afef56b072f2ead393efa23f8971793aca9c6213419acf51fad0cb2b6c17993ac0a27d94827d9e38e3a8725b527595ca7014cc4496c734fe964eddba4eec1b5bc25595712997d68405793ecb08b2cbaaf88c396dc02201d1baad3daa4ceaf59775714fa3cbd06802c95878d0f3431633bf964fe213e9cf56e1cbea79aff2313b56781c3b8dbf7e13aaa0849bfba71b82643208a9f129918a03d868e86987b589aa90fcfec0b7c3c4e4bb58691cb1b64593e2d5d923d4ad3175c5cc0f5173beb928e59f8bb6840abb9f8eafc30ed483b8fdd9d245afff0c2ae7cefa90b6d35de87e1e1ee29d862cd3885499143fb27d970e85722c5c52cbb3ebf78ad6d0e58a9cc5f238cb86d06ddc69ee718b9286c15cb7108c70aba2b3e70a0a23568e62a0943c745060c106e314443933b9a5e18adcfb1ad909891b11eb136071dc8c5507267982866d613a0ad7388ab6c2bc329d5ed826cfc012a76f19c9a3988218e8294067e7c5dbed236ceeb30619a490a95906d44546fb7fd59222729df91a7a510eebbdd71ac0972b124012f970c39b4159582f8f0850e028d6fb234f1244a00d7d4bc171e21ce81ca925d2e2afbd23d4a56eb6a5023d7193fb1546da43da033abc5fb0f2f2bc3f54290fa6d20c8bf6047381407fad8c58c7a92af6d5b0514ae1e97e74670e750a1d0d976830681fcc6e292f87305515ba69d0a87b0f01dd8436d5a65b0d7dddd1c1a4353ac00f33e38446398d9f5744f1deaeeb4e2e164462740eb0d351137a8b3e5d63c9362a538e21928fed3076071d05c96d070ceac4705076eed0a1f266c64a22826c8cd4769cee50b3a53303fc4a5bbd4e210c0d2645487d0fcbd224d63d8a5eb6be5ea590c1893c18a1f5a3c2c68d287343f743bd6d904ea00c0804893ecf34395237e5372b59889ba0c9d1fa2e302d13c2e94a021aa63df3faa3e80bf1eba8f27120e25845ae8358c62998a84a232c5b43d11a4f275bf5aea2ec8c1bab9842ea2614ec2ee030530a52ba9101eab409698f939c7e1206d1aa3199288e860906bf12451fb6a486c9a6c6bbf9bd75fc25246bb6136bbcf4719ebd4212226e7b97ec531c84c31f66904e631171ed4d55c2d0380c682eb5f7b8a837f3b0ccc8dca8ee36381b7f5cc9267ddc4051203afcde2315f9d4f4612538025cee70a924a19ca6620035decde70fbf2ccede068b1ddb27b9edb09e66d03ed5aece4938ac49a8edee848260b224bfd6e7962d97ee60f805d230c8d1dd279911c49c01ba0f7a87b04075784ed76de05730e121c45a9043a483b9a235152f0964b3a2878527cb45ebce69eac7dbb2ad2021d8d3e9178189e9f73a0b671b28a2384421a7aaad69ed778b30e8e077e63a7173e756f25240fbe40ad1e69156ef56843d75efa0742a11efea63ae4d076295fbe6fa481a0417f63b7b1e5eed1b5628f40e3c78686b2a31587c1be4a591bfa28626150f583d0bde714e8ab8c753552a4f6ab55f77010a542cba9d000980deb78a603befa003f74ba29b677d53a72da7eff92310c0428693d5ee7fbe99b32f5d4b6fe01c78a5563afef4336d82e1e2214e2db9389226a958270abe5b7264cf60315f5925058e87bef5637502b2313265f7a7d268c1bb5ada9008fd6c01da67c69980ee4761022d52020a4e2eea3a905fc41783f12068291f85803b834847ab62adc49313ed6f52c87ae37eb8f5204fdb9699cbcd6887dba04511866690ac16199ab8cc8c7b0827dd10608367e664cd177b3758d2d1d3a2bb8e7ae8e62175da57d71b007a244438b459b49ebd2aa139882d373b52acb6852b1bf4fe480a6e351d3a6847ecc255e889ab30701322702ac1f31cbfaa16fdf60526665a88cd1ba9414600720906d97969b2ec9cc8dbea16bb005cdd17ffaa4b2eca181e279fabd82e3734013b7c862760ce382162675d95269bc90612924f1fab6e883ac02697a89a58d4e89f567a4373ebd3bd0bbd279d5d20bde63ff24dc10cb2fe235f7a5c3735f09c3529571ca80b6c9dd9b078fa726bf31555992765c38c5c6480a32da180196656575e9bc1044b9758bce811411fcf7d2f79b2632ba4bb46e4f9c1a949e19f07684770459bc3546a743fc4daed6874e254238b7ec78120514ad4e40858343c960ac18ba84ba168b793da5daa2748615689ed5e1f8d6b3a669b61bb2c0755e472892b60e97f16e0e08557544c43e12ebeff854cdf4a867343e8c32c570b8549879b5f3e323d5efce881b44d5298a23f8d1ad7e026c2df0c58fdbad2a1d1f57286163fd8faf0414fbd9f38be581b686316684b50c2a5fa6a54e2b41462f21a7d503f3aac04aac67c142060662c32aa1273d0dc0f8fb4499947ea15c2d629b125694007e3aabaaad95c1c475def01edc2243c1529e16f86c6bee1cbbcb233b38414f8f800c2ed772f81ea9a89a67405e8db86dce67ac1e266818a31a1dae67ae4f0053697d01f3190302d1de03d40a878cd722ffaaa693647f36581f2b4427c7070a17948e194be98e3eee1b62663f5c03944cd88462c01711e99d943610a93f2351147d0e02f2633a9074efd3295c7f0fb9b3c6c1994c911950e2c2e8170e98f0c85656dcbf80bcda19af36be7951e1d634b7c154a5861e3df779586ad4ac54c34c42e00323b66e1c3f6ce7ae6688a4b7aea13d433bbc017a036ec43ceb13e264e04141a93ddb719f9b3c41cce03eccdadfb9e8ba4ab95dfbd3712054ee8c846cab5f936aab94c202c0a2949f90c9bbb9937ddca230fbdfb10a35fd5b6082487f18eadaa173926a8324017ccf81574eaad92f8ddc70d4585b75e34e82c643d70dae5a300e26ab120bebb5a83c12917324849b9b8f1c5bdf154af4ac99435cb445eb02128417149b91c116081bda59aaf18140ef2bc55207ae028835fa038ca06210bbfb11ac43c50a7657c687a24508b74ed3a693f1a174261006d245692616a5fa3618ef21138a308bc0b946efdd1d8fda64d15e9256b637ad0ef58f0bd7e7a0982f7ef138555a04b57c695924fbc242f4c9acb15afa8416d5284f21424517312199d119dde35c73bd8625e7a54d7d266eeedbadf38b837c9fee88fda35690429069b43fea9282e84d47117a465160878ac87ac67df5bbfa8345a3f86fee172bfff0c0a2184e9914a09b02dc3d5e5f9aca70c70c4db170e68cfa030975008de8a28e2e9b124792d0606fcce338d5e11dc1121afcb516011f4167a9f56430654665c78ce470fa902e53ae49807a477eb9e6fb4e03beea5c03eaa5423744e0ac17d1abc548ce8989854726e764ac4b1c109c490064d77c6ab5d3f9302370a9143a13824f53e13288f9d5b1da91cef3031d3074afd2dbb3cf4a6d7e9c13bae16a0e108297d07e69e248354e6d527f76d9a617475cb14085ac564085c80276dc57d4b6a41031a3feab429dc45e61b3d19abc7582622174e7f81d6337aa97ca672a64d5562679c80d9dccfcb8abb6397cd7f8527c7fbeb5af40ae8e64b4a278b23dfcd0bbb6030ae95940ed9d30f21281fb21abed9ee79ef14c78dceda006d7b6100514d9da46ca952809d4d42f8ae722c9dcbec8f9365eaf22820ac3b907dd7ccff00b164bb74fc3fb8e26ce70e49d8fbd5289cbc4e3198515d2e80ec75eb1f728a6cee7a557ac117f0cd8f189c55134ffe12cd95da9976b9dc750c1b48cbb805f6e6a0e8e928d4cb407eb2da462aa9deb1c0ea86a0a5fbdf9528b5af05241d8519c745ebf80d00271df256a2969e7c6a0be9c8ee8b8bba52ae4f9b12b2c1f7e1f6c6cce1241beec981d945d15d3557a61078c11cf3f229e81f670ab09f7aad3bbc055fb5d2d3d16912d8ab7013d1db96cfcea00e5d10f5c5201aea9dd38d4823f563839eb0e60c862e46d7f228f75acde2270c01685e8994854fe83ee3fbfe42350f4bec63245bdddc83de2640a841670c1e67965d181850347ce1118bc8e69f630e7036f374b6f382e7a6f5d065098bc505d3bf725c4128a4ef7e2a06facb1878d8d2db970ba5d807040a4a4e7d5ebfaf993092dc170719ebfe63cea2f025a64dba471b110192343732696ddef6f38e5fa7e8eac7115758fb971745998cf4e0736073a719a537b3e16d89e999ee0f0f244052adff6fdf2cb6a84b37b71ced93c9915bcaa99f23b01f674cea1600da4f57ed62664d4f120e8da4a1843612b108688b5938086ef15b7e460a15bbc0978e60c7a087725c212cdbb0a062b5d93e5d12792641c4f3bbcc128c8e6ed5ccc0851778ba5861aa36a35bc8fbc4caf891a2fbba244a7d76994c7e7fd1f2cf449613c71b72f16ba0029fb0191e93b45261e64a74832ae3c1ffd1982e5ecb7157024bafd75c1e5812ddc30591b09fd2e72e95fcf45b060c5a2ce914234cb65d1acf974629f98f274ed0903602a73a6391f4f6bf702b8cbd9a18a4ac92b075260403941e4fd136500848f702375294b3b47ef6e78e0499b33e4deb9cb658714d2219cbb93f422d78fa87f17553eba3224871548bf275b646056b6514009fb8a6032fc0e9409750f177df7f303987bdd75cccbab3789b56c794ec7f85e6fb232c4de00a4cd6e03d8ea308b25dc3dee58fa5762708185c82bf88278b96d8e305c9453c5b830103dcee14fd87d1f76da413cb7a8cd30f2e878fa02eae90bac34967f7120a7379d0e0f528db0382a694bfeec64f772e546346ff0170137718ec7d2a33bb4acf155a03f18eac426612784d1ef466f4a258081482589ad54204e5997f3e92c2c1b08190ac78fd80aff472a37ba9731cf50f6c9f56afe1109ad1af84d10bf5ce63ff9c609860912d3b6c06ee193d855de9ef3b41049b757f5ab942cdc0df858e16139267780795e100e04503cf320a1fe06e3a451719a441e40cb9d0e78dad4095f62094a27b7b437232895bc338f5ba0b4938f4cdc406942722940c851e5cfdfc4b93691bd718fd4eaddb71d050fb1c37cce18dc3617782509c7175bd3625fd4b0d9e89606eb7fb2b00a0ed54839804260142eb72a4477fa1c331734e13798affb63729d209edbf4a50c4ca900a98bb4a0f358291ab785632966ba14b57b92ad73e04fcb49e963d07fa266dd8a050cb1d6a95eaa7c18a8c225502e5cf8d4bcccf453f5998aa177e5a5a53130ebfce7c7d7e76ee4c6668412a52a044d6fc987dcc133df96880185254081c738184688e7dfa69fade9376b03d5929143d3e480eb9d3d79c42865384e3f24eca2294d5a8dd6947442cd3ee8c2400cf18127261846273c3362956c65b11a2ad147e658006d611c68fd564d557e73e988ed819ad84c9b5454455adb63458d31bb7d121ecbde3e5e98476d89485bf05aea48ac27112c4c6e1dc68435c69467617ef027f76b7f65ea0cbb34b5c44ca0d8096aeae18832b541f515cb59bb64f6a08f7d8b69314824368fd2f081db48c7ef890c3b7799afa9d2c18201ab1b2202f2e42dc8f672e13f0c9a2be755776c3772181490b98ebe09baaca3d6f4b3a64e93c74803dd2084e34369709fb7f5949c739727af81c1707ff4cdb8dadfc8c00814ae121090c48a9af2bc882e73eb046c1e26b042c46dc3143ad20a78110a0704374546e3884b8640a5b06b57ae60e9600b574d4838281571522703b89a54526930c68a26c3c23f53946535c1d93cb7dc2e21bc772fdb4ab067aa14da4b2719be6416b92c707a27622c55e204b34085a743f77148d6a88972d6c7c3d7b4ba2600a1f59f2b959927ced9917331e3b8e5cf5fb76bf1b2cb3108bee4ff0d0a7d01a882d87e30140c61af2d3212c971f7ce678084e8eef547e0bc65ca54431a0534cff6a4826982847f2b6a8662669b9e48064c6a7e7ae0cc52ae6655518ba5765a7d2c57f4a7c3850dbdb2e5b8f657820f72dc215e34dd75d49c42454afa42b118ba87e195d562e56c346d57d4c2da3f5b094fd492da6b96b41737a2e632500f8d0f67f53414132b3bbf1f17b861de0dbad2b0f1299f402e538ac08b200b901d054222e08f4828b6e9062094cb11f3578de02b4a15db227bd9a111416762043725c2d7a533a518608a0a00ed3608a1cef5b5b0cada9dd7f4474236a6561362710bf3350565a1d7dcef49c17775e161c3a720fdadeb4f45696ab530aad3fd4e190227d0cab95f0ef8553b98f6875b514696f119c0a18c7083336aa63912d980b351313ecc3f66c27a4e61462ee23a0a7303be455d81a6b5140fafcc1a71acb192b30a80b3dbb29d737b67a2238e9c7d8fe553028ebc9726f6391c81a88f33a83dc9e372dae6cfd756ac34336d1cec50485f887843086f90d11b187d6fe9e68062a59a86e14f646e01156911e652cf54cce7ec15d98bcb68b0402c29ef3f8b2ae24669698192896d29aea2beaed1fff9c63aab186ebd93b4557db92ba813bdc8edddcbec07112583da2333f62e6bb075bc12c88f7c87366230b7ee84a3351ffe7952f8c03a0affd90a56745a28dd8f3cabd53a33b7d0e5029fc6af0a8f8168b1fd4364b1c5e2ab7fc0a94a2b4bcc7904e3bb3f58d139c2dc8c5e92a334222ec27a10a1cee06c18a2bdefbde33645c916d10ecef3e38994ec8a8494c2aabe934a70cda2b8f31e1649082252aebc457394d42cbfd0983ff21625ae9a7004aea66aa6c3b6b70817d1bffa9e810ea5599461276c1afc51694fb9357da37fd556fd96de313731a7e92ae2446715380694f9f8f3a652b8f5cda16db33442f575c54c7f42ef0da60bbc456c8a5323a8c410d10b8a134a49954a0c1a6d7cbebe91463756f0a526aa7b369438a8a6f9c8110a68433f6c35097176ffa01fb59786f85ef4b2694518afa5813668c5222faffad826b97ec92c8b0739989096e970b366a2b2fefde632f3e199cf492833be9a54411d9d78c536022ce502ac1deeabbd86a49682275b51ba0e797c18fe92708457230ce35cac66d4701dd7ffcdeb316fe4522f27a3e099653808749ad7d87c7518fa02fee83c10e1067057961f5a4725127e11a210ff99d0f9065629632f4fc0e82ca94107886a2f40a37731c45cf22c160962f3d59129142d642263f280a9571fd1127679786cfc51b9ff4b9f25f0d3da7b2294c84f3024376cc0932069de82f3141823447403e555fa6e04d6511272cf0d2228c98a2749be9ccd2c0292d060e4f2012278271f012e2c32b652360bf6e4701fe2b84adce4a3078dcafc8cb8474215346293afe42439bf02cc3d0da7d92979283d6c8f1370c68f286441858c61fb266776eac6f8dcf2c36755a16f7801bdbc9bac2d4492e6ebf76efe88df38f0d3b87e8c4a3999236902fe525b2db33e36e666635380697508fb3aea391b8978c6aa6585a3b3201beed8a582167e97a90ac9dbc75a6513902f9cc9c80216ab400eaf1e7433c306129641faec246b2826fb711a2b0f5a0d7da4bf0a249e5c8ebdec98ba026563e0b81ee30ba846e42d01be1374026089fa1e58a7f3900c1d04e11d15307900b4c784a3fda73cc3bd8ea1eb81bb8b232613c6fe6344627336b5431e11041e9b5dcfca3955c858101333642a04fb71b99172035d8070b5315a918c639043305f214c083388abd9900203e62c52c9c8c3382c276492b971746b4427865414beca5f1e2610ed5a27e9aa4b453083c5652ef161cc04f1d49c3b6ff5f40444af49ebc71e15de033589e68c5b982e1beab107b85793864a084eb38182c94d0ca268cb4d59c54d93be162f6fa4025baf51eca72dade4d00684ccb84fdee4d5d6ee3e7c85cdf13bc1ec699dfa0018a6e3bced9868757a34e860bb38475586ccb3177fda5b6fdc859b5600580b531706db503f2bf55a7416289fed1c5e877baa0f37871be7c09c0d0ef7b7ceb1301920bd49902f834f0ffabaa2611f08230fb4d2e7fe6e075c0a1765e753b899b6bb6a5c5a779754369f7e4ed93ba8bb5bd4cf7194b9580d8ddb60ce85da4e0bfb63faccb5d4778750f2eedb84bf80c3616f1f8542bea8fdbc22eb978e4a030819d8d705c0c4aaf12ba3f1abe4ea7316bb8a3f94d0e694fce11cf6c935530474e099db288d3fe924b72e99f64aefc04314e637a57454e930b43060d314e992b58148d3fea48cf94663b07f71ab7f1255924d261158c472bf0dcc1630a642afbd931ab7d19d340c9344aa8f4daa86be9bb031a1ba52f730573028c50dd95fe8e7447d39832e3cc2d5aa740196573a802b4b9b70313b173f0d503bad24f0fbd689e06a401fb7771ef79d80d88dc4bd36bd243a4dc198646261176094746ad89fa779a74dbfd518c23d95335cc6b5299e0d6467788452cd09106a3bf62e537faca88892f228851266b1c6343248853388634e9a733005a145c6d46ce45f1921a120864c79a9e5c4ff0a26603e681341c3bb25bbc16fcce7fd242a904b81271a8b07c4c75da1fc39925d9baaaa8575286035fe5d24571ae28c0af6abb6c96171e6bfd9c4ab18527bb2d018c6efac2764855d70044cc800b9ede4b6b7ec38ef28738a21933bcaa25d105dedc687e8b8189650eec4f5d1db1dd32ff0ecaaabcd16caf445bf47d88261c2f869394c525e10a758e30999ea6eae752dc4917c6d1deec71265482f4626aff6845f47be3527caceb76c1153b235776be5e6d2a4a4ae63c21000d750c548477260fe0891eb1b9e9f1bff3bd9de9ad952f1b4fc3cb47d12d2dd4da2e585fa095755e56e2c9b6aa40573bfbbc6f1a0a34871e358454c5c9faa2908d4b50bb10f403f1e381b35c9ebb39d00aaf58374b4917498563dd488e341b7c0aea43de8f160de4cb5c2a2883998f6668a48e9e21d96d04f8f07311f34efa84c37040e92591d9feb84c23f32a7d7ec04f35872b51a8438a9753ce82171e73e0000f2d7beb6f236f2ccac0f548b1b06b4a254df3508dbf3e37aad35604118eb601c0f4a2296ed132268a9b805be5a5851cb76970ee3385bcffde98f07bd7a968552abe772ed5dcbd45946dcc5f6cd302ed6b8570a31f7d356e323f12f77e24141f7fe9c76d60dbaac3683013e17af1318282172ea45f93eda84fb338432f4cc5d488ccfaac24c6eb676c8be18f9a2ddad0c7c02bd7f118d7d49c669fad6fae566fca49f9d9adb279d4b1e6333ff0610e1e253d632e83437d359ecec21d3de502f2fe217923327761663d93a7c80ca6c2ce5993b71088ad7652650605e8d982beb012c9159c317839d61d64a9be669f10e85d98d582c2c36af66b5bdcadf66ea4de334682d559be1555348dfe01e8ca584793797476d32eba446061bc900a050bbd3676c3974934f974a10d3dafe8630fec658cb812e00bf7b81586474b0e25f3309bb501781506e676da8086f2924888cbb7ffbbbb9df2333ae069acbc92f069e56e916ba901b6e9d82067a262d6700f085dc5a802dbe0e99697723cccc2e77526c26f15b34efd455aa20374acc9b6114c6a64e36809af56de7f30e72e3c1b27f92f1ffa46ab97bd003f0ea39ee3e75263c072aa9b567572814d68a0671fe698c82ec6003f6dee5943232214b76d49e39151f3069e13c8a378baf3b4d1d08e0fcd63e09b8c333703669bb65250270f60642dcc63b0eb78bec38955cac4db336834fdda5a419e9352c371372cec4f59928b60ee5fa87080c7458df60f82162acd52c531970f0e3a8394d1fd6ad860397afc96c67c400ca3f73f869f892af4b1a046ee5ab321e1f65b650a76e2ab4ef6ad459b23600150b76b800d6d88d3b36ef11dbb710024165580b33fbc125f073b4e6462a4d2b1a58f4ac0315a3b5709f7624e63a3dd1ba468739dc167c3887b3a9ddbc456e12853aaf91cfc9e2a2259cacf0c5794b6ed9f3962840f5418c6b772cf0724b6d6e3c3da2ac764d404d5e308313cd56ee1f455c5f79738b885cd5da77ad4a3e8e2b33995aef7c456d0a7f5792f19c840e0fd9b67810e2f52d7f503870003c3cf4d7508d50e2b6c55e5b93849ec62b24bc929aa822e139c9b57a6df12b4818a15622cf075f32e9eb4952e6bb9510c109e4608ff6931025cd02bd96d534175587fc4d3e281ec845f68dc2aa060e94465c30bac79c2f8bab7cd2b096e3b2ba66c8f9b18ea34813e2e172bd55d47ef937194cef571262da86eb9ab5b32d1a317e196499a093181a8ffea317296b928df806c3f6b39d64ad6fb8fc8a78b16b1da3275f280f95b13021ea4740c91bc8dad97ad2fbb7b6b2787745aa5a46106c7edae4f062cf1254e8435b535f477dc9f150e5488c20d88f0473199146c83405bc8b5507814d8811dd049c4430bdffba1dc7727fd5a25d5cc547b4b36d52a4b4cb2354dce4100ccad5b59eeb53617671a9c6bedb62c6ebd901743dd9061a73286ca38b830deffa0166562a5b61fccbba29db1056722ee328bc16b77912e85b3d807c41b0d4956d295c6698e070645823240216abce102d212b3ce13a24ba87c5ddd6ccbcf6238743943edcecfd462828d7c2ebd3be4af91e738c60686a334beae5266079160d4aaea3cf278132342a6909cec0090eac12b2eb41bace7b602b7f4e23a7eef8ced7ce0996b3b9891bb51ad6f799985d6850c0225452228c159eec8056261ef2ad00d85bd12ae2b9c5e2642bb2606f092c714ee3c9cf04ae1e4af59d0e0213975ff9c2de2281884701a08c2806e3e876851db50256794bbe278afce23a36d95b7474a3dc80a0f7b6f64ba8d3e82f15fe2ed04432f147e9180561ee837ffec21a1a13bf5e304d39bfbcc6b0c4aaa71b315a4a0f7cd437b090cc280ec7d860fc7e6ee35315ebb906138ec05457f98b36530933e1d391c424943dec3701995ac57221b8a2fcc90063d8a8d12402561abfe833083b300c6bef70d9d53d1b79eef8ad7a91b95e7f1d810071fc6dc24d44f18e345bd4882f30d4d186a09aa3cc945293c33cdd60448b93432aaf85651dade7d436ba4f0e77a8bd038eddbabe862f7818be49d9816b3316eb08e47aaa6ad756fcda54138663c37475ef6184ac38125863b4066babe67e3759b7523fd551df3073cdaa10e6aac83c075303947d746e0bbe7c0d73ff5844b7d15a45e834601395651ff71c1fc2da244af3131809795685e3d11297b38f7b5ccb9b5663cc2aeea5664959d8d65bf9daddb1b2ddcdd87b2895b241a80e22b326d3563c80e56a769087346bbb94a9fd96c989f158fbe28377ea9f6b55684b4fbabbd525399ee01a086e08ff5483f33ecd062dc32b54220315d99098901ca28b3403e0f89a07a893d67278a02bd865366f915f1dda5a7b2c71227c5314a1bc59e8dfb2aafcf87e07433d25c54e9b95a526067e95fc40b6a6fe4bdfeb78f4606c5ac4ce2d1becc716dc2c987fb919d7cdc25d395f2b73d54c4cb6fae1351b928086be2694bbb87e244bfbfe15362386db0016d7ab02864a3ca2aacce0e98015cbefeb3a773e7ffeacc296df5c579b3d0f07052a1b9f48aee5f12ebda73f79b95031bd48f223c4b638f8d4eee87fb1ff9cfb295f2977cf8f269f9f93109573ff27ce7e99b8bb31d79d9ed602181829515d135df8744601e8179f043a86fd6cc057b06fef9af34ba30168e46e553c5b3e0fcdf9921db8a1a419f6c7babd0edad46408b9b270e863a32cf896b3a1ac7f25992fdde4579bf7e95ee1cb161aeaff8af849c32ef86009005829537f0a521ca54ca16886c3fba55476b23068d7a24bff95c6660dacea458e1006fcc4197d6b8bbd20b77f6b14bdfa37c5c495a6c5b0374f8bab7622b56039fdab072030eda1ce03d6a1330e0d33e0046d4a70667c273a5c61af4f0b193b896c5fb99a68b26f0a7b442001ab48c9e5fc04464eeb386d3947db3e14a2c38c7a25186e75330c5a816c210b3c65b43a6001f358bc0c6d8406832c32ad5a3002fc53b65f49ba405240e31b314f5aa1fa4dc9ee366fa7e0c4b4c5680672a4b709da37d2c9448ac1b8a317428590dc47429ae841e25d718682497dadc2044cdc57540a0c67e6c0f96033ad7ed71b48bc524a129c0a4e0735a397cf0e51e5765f56279c2a72d0dd1d59247f8abc64ca5d0519a691b710c8d5516f27b1cc6930e99243c1971c65d27414a9c34579269eb13b7ebc4e999ee7cdc39dcd3ba037af9eda42ddbcd9d882d2e816f85b6e7bea388e452b75b7841dd859f25219d08f479dde74cec802e86c81624cc7229137d0979b3ee3342fb7f949b9ae9fe467489b7b756bed08764e7221da1abec729c513bd42693470f36f84c8205b9e793515d70b8618bd0398b6ec7599ecd8c4995e481770d848d81b6a1d1384646521155f28833fba10cef0e603ab91a2ee3e0e0116b3a7685f474d41122cc947b33199d6262ad28b06047715a68ef2c94b329b38ce8d9e428e894c0197cc66e05ef783eaece0221a2437bf60084749f82e3fccf9e779e129c49f8ec1624134fae6852f8bebb967691ab790385e6c3aa4a35b88b9fb1c6d167b26a43e6d10c3ead4a79a5eaf8fb68903bfc5165311637b894a16a9c34eb438170f69ca781d6885d6a09bbf8bf0dab88a38a34f75d0f427d33b3d2a796156bc3929b30efbc1c1e06e226cebf61795a52104f5f95d470534bd0bc28bd19457a79a6649c7625820554d2ab695f80ca0c708f0cfd0e7d59515b81bfd9b0758fbd2b9d7bc1b59e764466416c7ac1cfa87320eafb891d73ac579d984a3a6d41d82e671ffff9221b7c57edcf2187bb0b2db16e0e445722a1dca40c78154dbcf7536cb2527d87f4d3b56604b63640b6b3978bd10af4ce70d0608c4c66c8d6260c8c802e26c4df85291528d6a0c5097424aa31a680df5ec9a41070144f342d30c5d8a4148283cfe5fd74a4a5be354baba5e725078d7d563fb4939b36681cba48a2ff5ba201ae93347547f696b5820b66e436c0c2731d895458878eda0d6d6e440d60ae1dd3a5d34e824b9cfa96dcd69e8329237df73cc6d8be2becee34b5d7edc53c9503a44138031a9be24f935b8e3defe95a8abb6540499a91385d55545dee69321fc1272d08e8ff0b8fe0a17e653f0254aa1f7fa368c6d4d5de4f29dfa8ff4eb976df7b8f554a571b34aa371be258ac0bd21078f044ab5154c9aa10ed56668627ce492905a42feedbcf234443820730740fb1ca9993869643364dbf4b4c6ff4d92bee72e44fcbeccb7f731f85d23195fe8015f0b5816b8290fb37505751b8868c5365152005b97705289c65e7315b5b69f76193c4f6dbb69fec77be5f305bf426917c0249982589d14a620c0685b22191c4a6365d17b8f92b7d3ca72c35f42ce9af50f627b0029ade625b103523a367a696014f20ded1862fe0e33ccb19092868b48ebb4ad43969951db46390c6940d8c8e80d74367915a7780044ac04df259b3db4c7dbeba6e6f56dc61edc215b69b283639aa5981de21d14b8a62599a0e59538442442b2235b090a1c5989d4687fcc93b9eb0c22ce8207fb6a0389ba0870738319a125d6326332756fe54bfd33f9973c0c35e91490469c317536ad3d848c878711dc1e52952ee89b32f939596fadcbd27145407313520dbb32e13c5229e35f9b857cd3d4604a8bbe63cd4cfe9838774a5d3a72063812d1b66e9fbf1591ea449964d4fd451e92f5daa1a5fd6e6e48a21a4814247ee3ed4e763511169e06f9c197fe00ab271b234f27f993c0c8011d1401b3c3db4754ce7a3ec6be141f91efc5a2de8ddbba7804f99987ac9cc8da33607dc479de58405843f2ae6ebc8533efd97282c32de3de3e835afa38d634d1aa87d76a9ebb0801e421959959cc34efa54b3cca73927fcf653b56de764a20564f4feea9a7a7c2603fb351a5934b33c0a278a49cf94a7822a52b35c4d70899e6d1a0e7b384553f4416387808b62bf3766ce0eedf077fa364347b031024044b52034989abc811bf4126830bd443ef833e5d92f47b1ba082a6232e46cd3ed6dde994c71ab76827e18550cee24921d0f157683a711cd58ef8afd3b27bca1f97afdec1eeb2ee009d895ea9dcbbc99c3657c7328ddb9258c1a347edf58200739425ab30d9c1ae1ff84bf291e616f5b16198da5f74ef9c1aaf881ab447b549e7ab1486cbefe1ca097e79ef0b868d7a501c64c178a7756f7547286aa0dedad17b2bf1c4d8608588dea2b765f6074d57e54dc95030e1a8601650ac749c9395b254de1a79833ee153339322bf006b01e7a43a4b15441857e808593d775c7a85587b56937194434a089469f741a236258cb04a3d5dfea4079e9fcb2711a1dfe68c3bc22d150fb499aaf431056d65079029454728b086999864e2e7fd1f968f72d37ce77601a727dd182715de56e0b79fbc661e66c289b0ad74037a5e536958fc95eaf2c36ea6504917550966164b5ebe2e668ce265135ac4b5c949f9fccaddee62f3d9e5905a91abf536bcc029b51dbd38f8e01cdd3c414975086a2124e23ccbb19818680b2d896a750999a9b34b826e45b95977c74981cd0fa9a64bda3fcd5ca96042f914493b8c8172f4e6558594275025dbc90509b84c5580809d22cbc017b9f923bd1c71b845a928bd91ebe31a50af6e3a9f38d24086e0f3d00688c88cf8b4ad315b6202ee7cb25674e5823ee5f269c7c281bcb207ac90438a56ec79a5cdbce21861819b798c98d326da3c06644a4870ed890150fa72fc24067cc40d02d856ce689b315b63d1c92a1619fbd9022bee6a94e88c25209fe68446a40b229005f70a4e45b509bc78fcabf4b0995ccd9e46d9058ee6e0acf26349a9dc36324903bfc6001880736541f402c5d40350d0e331b822d84ecf43295e28bc7ccebd75d1542a6b137bee79bf053816251ce6ce89883b1918e6f879f1a51324801c53b3edfeb759a41730f928ce53d70eb3a01608931723075b9030503eb38297d38f3b20a444952e7c4054a0971ba4cf911ccc8f3abfa0dff23fd170e0cf0ddab25f5ce308178be9678a6f7aaee4b1ff45bbffd4e1fd85d9fe92cbebb576bf28851cff3924e5ebff27c1fd184c37d64fe767f71f492abaaf7fc96ac23cb479ad16c5bdb4019f4c84fdf17fd2ef546278f4c27f937a7c1bf053772a53be0e7886572a930ba9a9cc55be9190a61a23b51486c4e6c901dc17a6eb4e7626fa93a7051e22163f8d34ff8cb2f151fe5879fc8ec3f09fffd50510b52cdad8cad8f5d317614a36048f3391ce800c49bd09bb91e90f47091f96fd59c1a5a7dbf47ef884e14bcc756f59815555016fa8cc0dc3fdf9de32af5f64ee14ff57aee0a26cefd51abfea360c8984add837d82b450da5cb2cea355f7fe47b63fb196b8bfec78be2756ce9eec2cae5802662eca7f98303388efdb864fd1640f6273d59cf1f32e1d81fcdfeb30e66b9cd800f5a33eb1f75849a41fe574d2c5f0d4e0cfbbbd35186c238f2a7f064367c10594038be2e51cc942037a7c5f2b1a6866239920277ba1a65e7a60c4a23912883c430c41a09d8e62aa53795cea2cfb61624c836fb879a9574ac9ef68bb7fd00b5f3ca7f94766e9c9a7e225502fab91b99e0b8ed631c1ceb84ddb012b8e93836fbf77fbd56001b1f8b18b61a43872df64e6a94bb90c13e32541878b96ffcad4a8ea41924ef319c0a65e6e068b7317be246a2d1744682bed142da46dfcf04fc1656829a733f092bec40124f0027eea838a5409fb4e1fc1b8e1fc245973fa6c53fb9a9661b0c0f9e1ff7536a92488d09ffa3c1b5a1f0a4cc7fe88ab0960d04b17b4d8f79bde38f0b2b24f1ddf30a3fd75b853651b4e73da1c840a1f0fee536fbabb213292ae7b1ed40dad312024338167b60528609f15457ae7b6941e8ab01302c72f6db288ce77f7161eb35dcf89bc84d70d36ee6cdd0ebf1c6ed077bf5caba58ea497aff1d9b3bc4cc9802a7b4ccf2f125482d6e0d1c6c4cd9b43fcae01f82c0562a8fc3be3fc2e534e939fcf2aeaf4aff5b0cb7aaea70a5c1480de76e00c2b35b3a205851ff1265490d126f0df0614250de7dd3bee49b0b39e673f2e4a62cbd0dea0e1f0421cc345f63afc80dbc51d1901135342f71aa1d2d5862118c869d050726d2ee09fef3e1384078a916accdbdc7fac1bc9e4042fa26fb9fd80551a75c67bc8b28c6db6072d070f54dbbfcf7bc826fd172fb42ca356cb4c4a9761107e0214e95cc482d756a342d66b4b6618d976349b25b263d2ec79b53c7d439cb1bbf8a68d0bb0ad484246f111a908c2be6adf43360fc50e093c311deeaec42e0c5818aafccc6c815d3244d88999daa4257510c34b43eeebb2a608b674e2b3bc4c38ccf2659fd923c0ebda89cb2d767b7eb9dc727a4899a605b7f5722bb8882b280ae252dfe0a925dd002ea5db098bdc349b2d9a57d21210eb44c4f9961c7f89d136fb095f9e0cf685788fed0e2202cf27780eeac7aedecbd2568d63627683ace9f5b6c69d8391a6a2b61d18c967a9c050ebc93950dbff161e59cd6f843de895d14f302161659c0018c49e36cb7bd39bb81a8eeabbc06540c5933db06a557a904a3f9c56d9d0786e4118e1b4b53eca846ae0212f930d863b8738af580067a3ee5b6c6ef1a64c1c27215324817ae8ae9edd62d0af33f031de707c06431e6e2de6f00edd47958444e5e7e7de3da0349f4670a6aa1f38ed2300ac7aa606d111080b2695042cc0f434870bd3bf710cb5255fb557f71ac6957f04684ad49ac63ae19987e04fd0981f4930dfafa236c39610edfc18040e8fbd7afe72dfe70a04597c197fb41d998c8449dfef34e650562586b6d09a1cf9610559311b157de674834e0b051f0faa74b196d14074e5d78b5ac6fb2286efa45a9a1d1189ed4573fec3323dd514af5d0f5de4e4affc147d2af527a69fce7b5376a90a4d86678ca62e56f5cb250fe3796fe02ae90c4865677b9874c40ace6bc288daf987dea8c841964cc37dabac6b5885e52234433dd6b00525f3dfea2beebc068bff9f6fba915231bc603b87d7a227dece951b89e2c2057babef5c57559efa59b2f7677e183d53e718e978ca1df9137daf4359a884937b40596392364a48fa217255d2180817fea2d3eee3352533e66b8c561aac40a0fb33c0044280c7059dc11cd783222a481a0cc3cd85d985b13ac204b790a357a845fd1502e9ff07ad4c1b51885e1a2a374748c00af687576219f57b49e8521f7e46055b6d1ca53111570d217560686c50c58615b78c04c1e217d69cf5d5966d6a6fc20a0b5ad2d5c541750704a9a072628af962b36de467b563026438d740341e4d359759609402c1935ad60eaf6f99d06b02f8781af16b0cd381e06f58745309c882490170d5f546aa9221348173440a0a8f755f335ddfb2680a1b6975d95fee57ae0990c4714abcf53f2bf79bd03e8a8e8b09489dc474a958b10e62c9992caa0a5e96853d7f2c490eff718273224720119c453ae3cf13bb8b151dae622a1406ec6d2b00913c26423f6d3ad5f16ea1f1940363aaae7c989742b8530d7ebfc07c6cbee8dfb1d7911c91f1b18e570dec5182dde7846af6ffd200231b46629dc0b791cc674c0568c9e4ea6b8f86fe1ec81ef7ce5dfd505b78d36764fbd5d17cc2e357f95e38e885c71d3c954f17dd6209ca7d83975b9fbeed37dfc13966708922ff9e13ceed8baf7881c543790cb87f5d2f80643703c35c84351cec52905dffe9d645f08bede8eac7b3377119700dcbecfe8850b0d07b1a710a90190b66965916bace8ed1d34855886ae2e0b0ec88e0f50f61730a87d8a062fcd810156c26a3611373454756110286255659fe2515023ac282c454b70f80fe0629c2a5353b1802d35e33e619e4e4c06ff81420ba10bb08d31ba1ba20e252ee90ae239cbeed027d4af5f06cb7482ccf30bc254b19ebcacad8b580c44206ed0ececa6682e8f7ad65e77a43c663103b3dc23cfdcda54e48de000473fe9d6cecec9bf20dcd05e7b32b3ac8b71d522029809e537f61b796e185dc8d63e700f03c07e997dda89672a6e4f6eaf9058f0fb884ad6a6d3b8bef13f6fb76da64c2c90233b4455769bb06b6f3b7a91dfd753c986ff6157f6a7e653d96fd135e32b39aadab067499268eb8650fec8dc0261412928500763aaab80224dd009827262106a2c900a6273845a42bd3d07c5cf9f21138a82eb3b7b2be3b7c264ce752edaa35d423eb374da021dc2b617f189aaec1a368202bf297fbb6011ff5c0157e65cda8750b7e3adf2536720b8cb94419bca4a4ac02de87fc9cc95c1f64df8f02ea3a05e8fc19f85d63cfda3c9257231064f9979553bf202ed6582d1dcf75d8ab168ecaa3860cbc08f1f188a72fc24754cbd51fb5fac4f5bda78107b25d034480fa370074ce14c2f98e31a6340372b6a85b2dd7d1c10b65872871a8c89bb5b6d6291f1ee8c83118fcf4c6876d2cf246139e3c829f00c64132065c272dd26f5ac72cece4568586c93564d70375fc7d510ddb0e60f86a1f0adf11ff636dc17f622a87faa8226278d1085022421aaee77122a234588227dc00dbbe8c6066c38cee9bda5a9e93c8593b917ca05a114fc24e03e5775ec6095f9036aadd1e386bb00e4272617657d12658342e3426bed1b2278c556ded8d22ac052084371a8558bc01933caa1188ad95843ae00892f1479aca1f8e0c1f94654fe6a8e966fb04ad38c19ae7398c83f0b65d8d28d656215ab5ec576a3b6082e9125ca956472edcd6a12502db97b0bba51b225cbfc2e490961f3f3fed7880fa08b59aa3175cb616083a360c89279efbf5166a5c151d8d7993aa16adc259eaaef8c919bb1881ac1a2c0896bcaa74d5b72bb06706a6ecabaf198a65b58b72209d2378b9bea7a78b779289c2ec7254d75c3544a945e6d79cb32f42bb8a4f0b00434151163a3a4d593cbbc6426d369380a067151dc55c3c44113fe7b1e8da9612806087161ec3c75d206ea41feefa987d925e7de71b401fa6bbade669e311a6bbcef828393d468030486ad9d006611fa4aabdbb8abe9ba4aaeb9847216303cda8076fbd851cb33055574b202204a0c3c2c487b8dcd06b37c294674a88fc99b45c6469ab17d8585d2d106407b08eda019babdd79f360aab765d93f407d2f46c367854779e40c571bb6e430fe9930201960b2bfc186d20616637db5bc8be8be444fb7c952140884398ca66b1e00319a88c4f6b36451b2c899e79d3f4a8c5d5eafd4156c28b2ea970200342957ad083f86c10fb690b3b74464bbdfee0b301fbe685ddd420971c29401349a7a5e61a7472f3e2e6d676ef9ebbe86ee1cb50fde19a33ea892cc59b4222cad73022a4a917b128a7bd6b60622979bb85b2556ad77a382574afbcf15e4265c35577811ee9148a5989c3870370ef610b364f21914145a5122ddeb6f87950dd87e3c3ab69dc869e68481ae43b4fd0b36868e06a391770e823e6ba442b8b8453d80d2c7088bd6aa039971a91da348582c4e51fa7eaaa558a09d72d0cb232b291826038f626a44e4d0463751c787c021f6f0ad2dae04ca9cd1aa4d2466026c8622499ef7346d7fccbe7490f2d9df3442051dac959c7d784330b081fd3736317d2cb69ccfaa5f43e554e348a07c7a095eb4101278aae78b6991289f71e3a2357e21c2fc2e0e793a206011848c528fb79c64f53f219d9a6c5b81a1650908ffaa9dc7e9d7ac07c647aa10b15a8fd9c31be18ce4aca22f00499341521cf195c24a79cd18a2afb1a332650ab7802d8d1fe5baf5f42471019d50091b5ff0592954c789db462c19ac469a16ee40393b308b58e6f35104913ac56f5bf340e7a4d0dbae945c56cb7e0e1d9566febe127029447e204513d76f5fdc4a470be7917d9e972f0e2afd2430052b0a9f4a2dd4669f739f8edf1917bcea9c4ed0079fc420fc25f83879e54cfe90781411004cc33676ef018a598ccd600acbe051a3e1093ea39ee4d1fef0718bf9540164a5be16a1d48fb887b09f902676f91636cc4d9ee83a4ce38032c88dfa74628a7b1251732d4ccee2cd4ccb1ccdd36111bcee342406ea787c79ce397508e5fb2503af2b040a824356724e30a502179bb8c6a336c3371d4fc658935d4181f5c1a793c8f89eb10cf53ba5385894efc739781f1cc15a12f910dd0cb5fe0430a9ad4ad9e4569ac71aef71079484e34082b2df19e760f2a821145f0f7048d6f4146aa939984803a547d77255fac21e1c2ac6665f153400335f81a60630f396225f3e2c7465cfd14e6760b20e9d6edaf1bc6705f4cc8cf575e2c511dcfb48c8297d7e946c67bdbdffa73a0207c97c0ab0e262cb5a7c0b733a4bd8f326439cd44a81de72989a25a3d10853f3fbfdd1650b725039e2c57eac1650f9b10cc4473dc3a04ae8f4c4dfbb087ea1c1308b2e0fd02625aa1b514395d709b3aecae19c0c31c5d4c0283126e959efa474bac198aec8a1a6c53052b04857520e8bdbce80c7f34beab607e660bca8bb97ff423e8d1dc7537b1ffb2fe2a115d15c5bdd556cb94fe4475856c94c9e296ec990a240a0d331fd0536d0722cebe172aac5afaf47f25e366b9718e26bf79987f9202a9c829c20745c35d6c2e852c984ab202a6d556dcbc04de62c74ea6ab2212241bafdce3345136362a721c1c5c2019b6718b0a7e736e5acc00333d014d7ad6a56d040afde47d361f19c93947b37c97e24d47655da3327a9f594e46c9f43c58991718232130a546c03cc0c81ac3b12e2361222ec87ae6b40b951a126ee409e849cf57c5d66555399ebe38e0b95e8b2020b79363c5fbdf73e0ddf2d0f86165ed56acad1c83ffb6a1192ce5941e28d3270113c5100fa50a1ce683430c4301531c6e7b1de339a102f4b0e6b6db4f23b16b3fae68f4426b151783312d294e22f330dcedba593582e6af622e1fa298f97de5d2f9be1593b7786e33ecdc2a6ea96b036d7e882fd37e39681fef2b7c0ca90f464bff5e2a1933be6d8547859e972b94957afd0d0695ed206fe0b75fab9f04cc21ec242fb26c8c09024949feba9f5daf4012e25b6deeb4f25ef105f223b0c7d71fbfca6d43f5663f25c5ccf86c15670b0434c33bd77cf31563bbc19106981a6557d2e29088d67c468b07edd397aa99b1a6424687092c6ae79045722b4215280d7ac5f07cb1180fa0c91404b1ffd0c0dfcf0b41d1dfbdb1dc6e2575ad61f401f1f71827f541f9cac859a20b6e699450c1813eea20903b01bb8c29c003e2a4fee05091ca637f9e1fad0ed0227512a2a36bd7e63aa3f66b388bb52611d2e246c9cb591142f0373b06edf2c9fd78b7978f6783a1a7f090bf18bc5fad13bccfdc237bbf7cf2512431cc7c5430f313f0fd7d4884598b9cb2f8632099dfa4836a4a99296f0b8a347754e78c3e08ca9216a0423366b4e3067e76da5298f5aca1827f1d4bad6f03fa2db26c1c2349f32c0ba210bbcf835823c8c420880a0c8ab1b4824158d21b1cccb65b6167bd85c21dda6e3e50bdbccd0888b10144041e8674e3036d6a178b31d18ec327ee9a943954383479c93fc21a8fb543065b21654a118c13cc0f2c34c7889a633f4904931d5ae0d2405b973605be9cd9fadd91536d990f5974d7970239065addd0db7ac20f966b58c0cdc9f814d94e6ad31580f27424b7189e1d3b7976e03d52238ff767396e1c4f92966747657b04611fd9d63fe2e0811485ce948c095fa66ae95417786cb8a647c6074dde996e4f96d5200fa995f495de60bbd321bfd2ae379cda28fc82506666f15e7672fcf57298bd7c4b90f62d4ff8c2c8ba5ff6d285714264c6a6821324f29d794f9c40a74abb0bad2045c327a35cac25c12a6f60f64e035be2b0b92fa2ba60f9e53e183229f99d0b399168174bee85f673526349e43440e568724b72eb475515c35feecc644627f181723da4c27aea1a9d9eac2474b71bd45675dc744506bfab468cf8a0dca0b2b01c517c37ccef474b744b3baf5cc8658444a1c87c849f550b08a2612abaacbfee646a1bfb6a41759b1a0b44c0de52b38311ac897d613c277d5efcd100404515274294d4e93d9c1da388c9def1ba4b5fb66c18451f11eff5a5e4b25f62257a84f12aca9a08de493aadde18408532f7646c6bc75071a17773b2963e8863a8ad04f10f1cbb87670858c9773ef6195aadc4062e7f996f310e0f8a4d4aec1a157e4fba89a2646b5ecd292a75c1106a549dd58f3835089d5b00e074a822f49b00a10753b5bfff2b12d574d4e31daa9faf8a9c348e65b610a61d3f22769962ab6d4a853f17c532a59a29e36f93ad5c9db2ecae4ecb64e59a838b57e599d04719246a41ccf12cd3995bc7766229488946189c972b15ce6cc5924798dd989f05db20731dd2d2cd1f20a94c1063bb75b9e4832d7e84d6a1a4de7573e5673f217bab3320e470ad88db461ef4a5d85e6f00d782b8db003be0c420cccb532a782b8608a898f451c96fb7b92f3129d7a458547f8a928e373db2134f70a36f491a42164b162ccf162496645d1d8d9c4158ff59da588825aede81c172a59ac534ddf4a873793b168c1aecc059d8b5dabe17a2b865b1d32d0dd6ff646195ce9e5f07f684ff2394ce5f6e7f449b19574bf66df92abb02ace7520c1a4444789ee12abb63d8116024c62c7abb87e640e79ef8f18d96462fb8dd631a940e300c2ca9ade500ba0aae6152183d96bb6403537878613454272ed7867e19cf02678358373aecf4b66571b0f83f850b5a7c652e674ec8269e56348e02be54ccb12db18cd69b23767e7269993eeb8d8c5279645ca11ccccd3d2fd1da18d5afcb718d96750f6bc590184d1854c8ed27ae2798207ab7c2a3ae29907e1eff4235f7ebe8040cb990a7051891757bbc9593d08699c76d50ea016a7aaad68870448f34eefd45660204fa03d463e1c6409860edebcc3a27cb4b508ae45cf2d2acfec0d91f51c4fbee0c1bfa261c1c47d152ea3a7aff3a8f065a3e6b192892faf85a03efc5b5d6336941fdf590607ef8acec096c22f39fba11a00f6672fb08ddd888788025d740039f11758817a72c055ec1889daaadd3e73baf9df1625a0891e750604b302069619fc0036ed6c53ee25bacf4ca31e1fadd68c77ae542f70dab8826b00ed10106a541f99b5f54a5fbabebd0d991ffaa927fa78d560add28fe08fbd66145349c2e2591ee4aeb1dc256fd4ab7352f44310240b89fd3bfdb9d289fc9d437379c36058bf256ac3a63483b4e20bef9252c8484766c03d68ae5acc436ba65662a80b8ccb08de04fdb449cae5ce49c4319e60f5ce8fb4972ab858ad2e602af963f92054348b38249379e1ffabb2df5b9c220cb702f92a7062177efe5bcab725c5e53943dfba69f310f71d429d497d96ce300ff1bb5f551f305fb3492fca17de04273f2bac0194baeb838bb000d2556aebe7d40c31255ac6536af19b34a8a3a040718dc58f167d3d16551c87a017f87647a27562ef4830a5c0430b673508dd90c55260248731939ebc4bdd7abbfe6e5e081fe0009e79371a580ddf7e0717cb251225f42b267a87b675ddad92aaa3753463bfd230b8ca0421a22e61f57bc33b0d0fa1a2524643ea262cf51251f016a331f29ddb3d8749606f57fb6584b77cf6b90f742b7c7b4bd0c0161db3b173bc0ba93a8f554e5f09caef6ac7e5139112a92528ea04e0be8099047c3e640f55db57bd8969c0b7b5a99feefff4d60fb8fad07eb142c21c519129a669825994fcd0acec5602c9d0bd3b59470f8b43d8c0b730eb4b5dfdef619b2c01729bf2d596bc9125db0774e08a742d1a6cbc53f436bc485b1ba429198ba7cb428f9bb216e6efd17b20676218157afef3f81ed0734e471f72d90250c17a8dd75614ed910756f64d01f59be57060ab23c484d8f196327c48346d1929ad0cea5c8ef4e918a2584c625ad33ceff46f8952f4d7607562ffeeeee633de6d7a2fa421ef27a980da427cff4d45f5962c1a70d6b89b8ad58852aab47af230092d01cd60c98321b4aeddf0027da7273549306515d914fbc4ce5cd25c43ccb5ed24379381801557f19e1d7d2e14ee0e256d89ab0a422915727d16cde6dd128d9d03d11b51d5c69d7135534ca6aeed8e5b05881fadbeadafa458af811c91757f29e1cff2a0c5e12ee76c5f5ad9e80c4d7798fea8bee7b8a3dca2e66e4ae3980c203378dcd52172416d9855a0d0038099755298a1ae0f0b86acc3392c5eaf17c71bde76a3e32f3963e470ae263c00438661afe371977a22442d9b837b99251b354b08784bfbc3e21cfc5134a2c68a94bfc1b8027819581db89aa3763b775864d27c978fdc8746b1ea82581d063026a2ca293561a8b1c0decf1076a18aa4fcbe73837f959b7ca8216874b03103fe1a43ec22340878ea697d5d770bb87ecabf7d56bace48c11aff6a9b08309cb341b7539e3c707fde42f72c2166a044824360e24638c31065ad1b82692c0521a8871ed51ac032dcce313f6510e0a4160ce97a3deaa8e68eb900d87b7c2726ef86962376a7e36186fb9175607a6aacd97b7c406c367e85bf8f9a6355da3e167b538bec7bd541f191b1d2a0176dc2c4832138bd293b54c93dededf8410af2f72e0e37c4e033f3f8980617a9e2f4341798a5ab15a798d1660d4028dee75adee4d0af74649470a228571e9d94b594981a70a84916add37da6026c110e9bfda93072314f35a18f956a49698bb5fd8d12cb481e51ae5dbef7b5e53222c570be1f9955b4fc03d825b4061e195b5ffb07b9eeb3a750f40e848b817fdf24694282d0e17fd0fb860811cbc7565c19fb4592545b8fd971aaaa25aa548479b6a5babdaab57ac836201179f129a1333f085388eb3986fefd370dc56b88265e303ed6c0c0c53fdf443d9502d28835768ec33cb110f7d9dd9ad3f01c39d6585f8cf0b5d2aafd279c421edad415dc82ca599c8ac29caf5d6435e8cc7f9a52ed4cf5ec25372c543812469789f28fd8d8d96072da1262fc9b892ca149acd93dc58f18fdcfdf45728238554a64821c356a1ec56461efa9926f4a5e509b7715c341f4eb15237c60eb4c7e2b41340a0450b4d1c0173e7dec2bf4c0a7248206192cc37916e860be965b151587766cdd785fa005d0b20b2357bf7717ef8e240c619e8aa01c627b16de979b3d56cb8191983cdddac1893911d6b4dddee1591f6a01126d65a492b11b074415e2c07590dc3f015c1182b92323d2ef456a1abe24fdcbc12cc4f17b7734be358be389844cb19537b0527a42cc7a685c4bef8e4fa2991f0bebb45d38ba4f3f44cb51b3cead60c764fb02760c51dee1e32a3bb671f67585322e23e86173630c0413383e7335d77df0a96d0ad6a6161aa14a4d4815a3d7f6cb74c4337da781761fa92a77e06934966e735eee1f9340410151d5de5f0247908ac95e60d67ecd78b9e0794afda2c47ccf319ba1389eddd974eb73c23cc763b884de374cefb9060dfa5ffd031ecee0b1119600ce1d2d069986bb1f099c0b036b1962c54a5ab693fe44675bd83850079da9fab5f3a073af9cbc2154779793ab22e7225a272f7dc599386f61ea1a2c148142d37150ace6786b2ce67caf2fb90ff46b99a616fe544088d99d43549b818211980f5e374d0aadc7b38ba7162aea7e785548cbda4ae85fffdbf7afbbba4384c634bdf9f97d2f1f5bd540689501364980a0e47b9e9985154f91ff9407dc2437901b9ae648fa30021fc638ec2af60846bf3123027f2bd07e508da449c3a90701a4536e0781313defe21cb11482933251afbc50b9164c01b75f9327e7ba850d458f7c5c27632f825c9c4585885ba9f735b8ba78c2b56740d6abd9460069c5d02aa71814c5e4010424e079c48509fbd9d87254ec77dc26891177202d14e2a3955c91b46bf28b6354fcc4f99f5ea70983f81cd73b51d11e662407fa4dede007b5248697cfc3e000c01690dfe98597fe37b1b3714289b67ac7dc86f7b77db724b99929429b704f104a80477766dec707148a66463078f7b677776efbd3317a14c9d973017b219ac07180960383c2d2ae2869033e9d3592ae188e082f2c4154c18d8d431ddba278a3c9564aa2ae6d9e83bf2672af98ee9c7dab1c88b8a6e2fbae8e26c36b3b163cb8cc717fa640bed2189fab0042d82c160303aa444049986408623f758866c48b52dd2e7ad20b1e5615e248aa2488b6614082ba5052a5a24d222f145175d74d1451769a92864a2289b55516294a468ca501351f48fc5542a95ab602caed2297acc251689f090cc61be63c23277e46c478e335a98f9656eb1b9154571c8771e3063642fcb2de2d0c762624c8c4df1b38781cdd0bfc83136c4846886bea585c5863445c533994c9c899fcca0cc4cac391363be3391c4c498288aa29128c3c58484a702134a2975efee9eb5d65a6b952d1a5488ff7f57ffcc1f49a3aa2bc4fa081b0c8e87367c17a539b42d2f586bdbb61582a769e34c6c7928d03b27744ff70a3f9a2a5dc75f69cbdaa75572b6eb68784ab8ee5adf29ae11030c8c90439c40ee20ab136c249bcc1df9d32f2cb37665eb8f9b95c8de9de04116a315118ac9623c82c631a8729c4c867f51fc9ab5be93a5a4f87f7f1434e0ac22ee50074ef0f6f696e0cd04df91220a5246ad76fb82c2ad0341e4d7a13e35a48c897bba19a48f7529a791283d294ad143a186784513bc6bed6b04293d51fcc21c9b0a10701cc7b54c06050f1ebe834307997ee5e13bd68e76a45f47aa82ab64085f69c4b2d609a5b4d24a7311f1f0f0601ef9f1d42aab9492561a004a29a53cdd0326b96bd624ed56332cb72ddf0e494e8e0b735a9f0b3826332546b20ad1134e26eb3b6b36c8646fefd4913b3566f774cd09b87ae2ca2cfdaae47e7e5923fa407804d594a83c0de2314390134ab2224231598c870cea710cca525a60c4590b8ad6de60b797b5d676b693373b4d34786a08811ea0943efd1e57e62a3105d2fd218b92d2ffff27219aa064d908c857a8f29ee5d7e56971dc45e9b04baa586d569bcd66b3d96c369a1a96bc49cfa3fc87abc96469562b666a561af8f1bcf6214d9d37ddf6530d431d3434185ce9a8c1290f955aa1c306a3b00dcb632b3e8cc23658e8c3365f0aa750df89357540d64b524cc3bd3c13cde42c57ebdb8fde99424cb57dffcb9891dbade5184b7932cb32c7fc566b6db5542adab79a41a81431f43385101942fce1808f98ec47ce600508db8f47a94f4a653fa5f724b11050f020f4336f3a1f23ab905c1784c8f94ad22709e9c3e15aa4c6aa8ff69161a45cda409eb8d29ab5dd51a5846f79625aa4a2294dd38fb5f7ce94e40d759bb7a1a10a0299705da247f754fc61be4bcb98d84bc798a69fd7bcbd8cb1623725a48c892b8845ca903f73e63b3f676f4428d631a69096f144f20ce336d80de6dc57a5acfa7f251ffa2e3ea890ffdbed769bb79c6ab3b1c1c3906e45c62f5fab1175cd03305d92268d0680182e872b8f2d1fc4b1a4945f721314aad631a495ae8d58c669abd97258695d6925b55cb45aad56abd57a28c116a19f0c988e1065fe020c068b113d293214246b2294e46130a2144e5c180e3da59340c81e2409ba2f1216ac1949605316a1425c6bffe9ffd75abf4787bd300c433725d9bbeab24dee2a56fd731c676bc51c0854bbd51213ee31752c36b1660fdf913f9d65e1e8b85c978d2f9c0dcae1ce22011934f0be1328e528e59842a5505c9696c6a5a571694ca152282e4b634d0a75023f8f86e6d6d0d0ccb05e1ac8174b9c425dd673014f2770e9f34ee0d2d2d228e5f875266b39eec32fb3349868e84629471a6818979696c6a551ca51ca71695c1a53a8148acbd2d29842a5505c96c6142a85e2b234a650291497a531854aa1b82c8d5746fb2914ed5228da9530b3657574e9c7142a855a1a1dd3cc9f203dddfde481cd719eed4cde079e64ee0c4d0dea53a9e04085940d7e1256b9111752a9542a582c52ccdb54e23a2bab54e645dc572b10a8c32b160b9b3b02949cb9f3e2967164b1484d2c361b0b162c6e5aa06646d3e8c511a9765373a52865b1c614681fc78202064ee81e19cc005be31cede346264c1daf7b4c4832d913a427376496127b0002cdac58b0bebf333317df197c71cd6a0e8140175c32651f8805fc64bc930dfc6c349b5846486ee44648b22732d993f9a4a8a8850b9a24e24c9e96a32f5e80408fa977b893f867d8b13a09674448e845786791196bd10204aad8c50743465e94aa03b9924996ed6028896c68064dd88b172fc2ee9e53b646463264b676db368b5c33cc0dc607e01750e9839c6fcb38e6c4f87c25b1cc70da6208c08454abc9c4298aa128abd0aa003819df0c10acab6b309e369231dec4681f37cae91e0efb282b5a2289aa11677d947d93f1998cb229b359abd59a0102673b292e91424c868666cc980142081505a51e4ab2888c8100020821d0a0b63361894b450821844063004ff3311c36680cc05fc9279938138b339c414e3880afbb71efeaecec804016ef002db15baea44fdfd626423718ebe880401dd6010f309be7abe1c9dd417e98e3cc3b753adcb58f8fb64da69b4df6954c9c297d1ec98bff0f7b0ef8f89db4f371a0e2effc8162ffda7bed94d96030189030317c12872d2e32cc185923e50e294676982c5b0ac9a59a8d1c6292a584c1602afc4813c84c79132365bf66912ca51c132583f8a70e8f0e08b3ece15e62c29a3a17e46e8988dc81e92a625ef85a9029b661bc84f1c0b54982041b488211207b2d7bdb9eb20ccaae8a8d62d692373f4431029bfcf8bd10f48061c10d6eb8410c90c01811213f9e3539263ffea993b22c14a16acdd60c144262160a304240226c322aacf049999d5a8879451481320c31898e636d20d2d13c2676ca6ce2346de2f494dac4e9fcba2a13b0400465488d881f375800a302e48614252b416644b622455b84dc18628c727362c80d22642a03c06e298068cd56b843162a4a64b1f242873b933b4a96248cc8d2c48b24ae4cd08b1bdc0bc307395c9a1b865c9413183029306e23123e8c4938398c4e44318a995d530b3831dc0fc90c1fb8dd8a830b19ee69042c48fac105946b734749a221d55e80b99f3b4a12521049361843dc2f7794a42c2ec4242fb0580e6aeac8562a779425885a4bb557acc8d1e10f733f2b28ae3ff247822b747c0a8e2dfe562958560a8f6b4dc04b695ae155c5dc63ffa16354faf3f3d8621d8f2d4b87e31af3a79f748c8a6b4caeca99d18aca95b439dd87f45e36b7784c3127574aa10a787dc34287fb0aadb53fdc0659426e63990f4ecab55163864bdc76f7fb7d79369e9826a960f1be6e60b8af36aae1ee3e6407dbf4e1f50d0b22aec4ed3f6e83aca1dbb8c674406a8c104c62c07de34d164bc96d2ca7c441e99dd6ce1e8e54a526b9a2f55818cf6a59ed58c2ccb087013e4bdca3d216441b524a2965ad392d5aa7848b2eca66559424299a32d464766777766777164700c8159f26122560820387d8d0223ffeec0e47bcd793c58911a70991217f8b204fe495ad159f6007140c65b9c188177e2af5916badb5d65aeb10b533042830a29fbfa3180c29c84e72e068f2eb60554858d48f2d41669828aaf9e1e57a31a2f92166888c6d26489153134a170621b00922991b867cb4930e413c116c126412bf22221d0e2f364f052d58946ac871da8816645758883511c3d145e9080814457028156d40c85864051c369821c1d104ed28880788e0606244e11544a31caad150b8d9b25feb2c094c98e4aa88b1706d6cb872051e82d89e1283a231bb700205930683c18e44d1a202203da50363a6307eb860ee285aa49440cb0b5c50b95cee285a6090e40a102d4ad0aee78ea265091b19b4b828cd224c6389e90ac79de30548dcb152e852d0414787254ee1c76311e6be46c8d415e4ad706cf10a67e998d8aed0f1987ad8b9cab231438a9d5563b278b82f55b70a5f61a68e5f2d17a5dcd13da92c318feee964171e93b58345140fa878078bc96dec49a1e93d4154eb110cf4e8b0ffb77c3c010d645abf8ae90a02f36b4fffc71d6b8489232d0f0eadf4516b25eaf6705f570292a7016d93ba8bdce188dc12db2c16af6fa4cf8ce57eefb4923f3468cc90051357e220b7cbafcf7d5af54f25e276585610d79faf62ae9a2a06575cfd5692c94a12cdb062d34a369961ed1d8e533b9b7cb2c4e7cf4be6563672a1262e4dee23159a6489714c706b721fa9600308ee4cee2315845ad0eecd7da4028c862bd325044209181db81f0c421e6e97fbc8470b4224d73b7231c68710889323372a37dc0f071832a8618ae8c3f5721ff958c104b7e63ef211a44596cbe53ef201a3b9a37499511bb8a74295c245a3e570d16834174d872b878b46a3f9d72e1a2d878b46a3b9683abe3f4551dfd5c7db88aa48d009aeb9e3a2d1683453cb90728a0aca08e1c4613b2146feb8bd132de68477174d872b872b29c9f522486fe58438e10c1180c879e9fd2d9ba391ed235bc68cc9d45d8a31ae5f07a8073febb2b56b1b756e34da08a12bfc2b8e42152b57925cef2e9a0e570e575be92b9df4d2c33027c4e9300c3f10fc4cb7939886735c2fbdd58a31001f12bfa40b395c345a0e178d4673d174b872b868341a0d575351f46decc499aa2f9c2a3c2fec7a2fbd7be68781a9232fd05d5e49ac42a680219f913c94deddfdffff9d524a29a50fb8930419ffd34b7777772ce54bf9ff4effab1e67b2de75d6c479edeeeff56b777fa7a07cc09ffeffc6656c58ab52d15a2b5d514a4998f7b3dc8dbbbb4b4ce9af7000e4631266f84e030306954c316d3c95f3a8fceaee6e4b6baabf97af1cbeb057ba12bfbefca29452fa2ffbcaa189dc8ca59452eaf8e5b793c194524b29a59452ea81df077a4aaf97e9a8dbeeb7335fb9ee6a739a46536b28eabdc8eefbfa724f2f956259777ac78425ab9b6567284daba69452fa1ed8e0e1779f3add3dfd294f6d7116f5594ae9d3cad98e524aebed0429d64c1d8952eae269e5a03ecbd9f99da9ffd27f7de03bd269becc75758cc63cad0f3cc9dc194a29d8dd327786a60675bf199aff477dcdff7f8f4acd7c3b5d4decdda2391da331a594524a2b4d0d8a529a9ad286e2d777b516d72a3357ffdf4bf1fd972b255dbd5ab955df2b85faff5e2bebaee28a1072ba7b6581edb66ece4963ce3973e6f4e9d3e7df64efeeeeeec972ef6a55f2bc39a7e339fd954657ed7dce9f52cee67467bde69c73ce39a75fd895787e375783faffff9cf596ad7f73fa4c25c5e0e7513c29ab398eda315f393a865677ec4febe931f8d9dbe3563a660cc5e17dead0ee6581ec983eeddaf5ed9873bc9646e7bd3e98d8c3691f1242fbf4c6f3f0347926fbada6d723db9b2debbb9084d0860b870e04341696bda9b9412c83db08374f4b75c3803bab9532bae72439cb8194b21e012d63e21bd646a57d35dbf8367aeac84a95bca3bf338060a6cad10c3c7490bf07c9165b9e34501d2141fa37412808216a803283211b55e194ffffc3c712875454ca01c8145b8aa4859b2e2f9e40e7883f715552c5c95083235fc28f5a95e106a2039951f10521b38420e28b1c3822dbbe8d1995109bdd2391680890a932c00c63d783aa735a29ad1fc470054a7e61a32b5492ae8c30aa5c8162063e821a1331519d1626ae787de3b5c91b00305c4e04dcea9e1499c33cddd399c31cc7610018b935b738dc608bbb2cf0e3e66648cb6a750cae95396bf220777409c575b524667f28e7ab1a8ec3e15fcd9793c3129ce1309e3a26dce18a2f873d0f3768b2d85911086fa898a24409b2ad768616638badef38b6f8face636bb15d7140826bb1c53756646c599cfde1b0b5788696e369cd90c3ab2188ec0e5ead153f9ee1062270936db7188fb0a0851147a0c0d8e182b98f8ef03074bfdc4747720899dccf7d7464098e0bae29f7d1111a5ccce0080b2e5e7069c2e080e0aab0d4702dd20b1a2ee782704603c2881035301244d1f5dc47465210c28c14d9c8600829bd69fa899a4f2557396a6f3a4085bee3d66788ec58eeffff2d27f32f4ff3ffffffffddddbfcf1de3ffffffdf29a51eedee6e53edb8eeb6ffffefee9cbbc9762050e37777f76aa25e77ffd7dd0efa9ffeffdf72efee52e6ffbfbbbbfddd5be604de53072e78b2bbbbbbbbbbbbdb57afeeb2ad34cf39a7bbcf5ebd26276d87e7ffeac5a2cf9a33ecd56b26b7cb4e8b27bbddddfd3bb7dddddd4d83a3ddeeee4ebbdf7ac773ff5957a652777759a9bbbbf707fa40ebfca4ac5fdf7274e94afcd57274cc3fada6af6368fda1e72a1dc3d9aecafaf5e95793f7b4f6f7e0385a9fe3f073958e01673f06fe7e4f9ececffda9d71e88b912d3ff80f5ffb49a68db5775d8a1b5d60567bbdad3e451295d5c29c1126696ac3779ef792b5f1779deac6eb735752bef0329066fc870ac578ffc2c1a13ca4df8815352d938373ff89dba6350fa2c21acb5f7fe0dc2ca580b42ae500d342ee46082792f147102222345e6c8080c34b2966bf04f1d1f138d23188e589c2c13832c834f2090cc494b11f80211ca046c054e6ae311413c61e445952317828e8488744cf95b1c872d132d20c95c01321d8283231686402ff2a53c62416c2d31edac90447eccc18d122b1ce5aaca15ab00816a132b5635638523f9710a10e8392b5768e48a6d40a07a332b12829d5c71aad66a73ad15dffa43ae18d72752a6a88043814095e21a2247e4a7921fd38040bf82232235cc1459f98b45e6a60643029938a8d4ec5de88e1136400013170000180c0886c3218118071339cebb0f14000f4c783e5c5c2ea48bc3a1481003290a63200662180620c410630c610632a8ea08003bb571b1b5d28e6ab92538f456f188d0a93bc2db4e411b91dfb7b4856b84d6e480282c8c87b213b860ecba34d473f9b735bbfbba91cb070e27583551eb3da938d2af070a9dea78e31d45ae15bdbc4b472bb0e231cad3c4b1669cb35b5de25156c939cf179ee56638e10ccfecb6a85f71ca3d35163060397332c2b945e7be8b899d412132c255c1f44537de41fec2f8aa318cf66b4d8990abdc9bc6ec35b76dde7ac80fdc36a53e9d587abf2f40e01e3970815631636c60ab0a1156e1d4f90f3bb46b4f06b1e5cd616eefa50cb268764122a6fa3d35d117befc737f84ea8b99db2c2b047e2f1df8604004e03229edcb7c70a7f661750314339dbff564fcc0e9c9a593a0df6f6882278635bc7ad7f242e6bc7fc8b650b4fe4b03d8bbb5a6a20cca8cfb705ff2d320d58f8250d4e15356a630b331527f21d78f0b09784f078618b31f88b506c5d3532bf9d045cc023420f54490e05374bdc7cf38d30032a38b8a40b693497c3e92b0198836f00edbe47f10128bc16a040f0755a466aa3360164d6d76a313e9451b5ccb67e7a33897cb09c30f710a3d73ad16548444cb207e87903444ee736dbef78077cd2f9adc6e9d6d3ed530e1a06a5634c4d4df1e24d7ea482951f4d033d2e2eddd15e64dead564dc9fccfab1fb39bc2e9c18b095510f64e817100070a66a3277ae0f11241a121fcb9dae7e9388d17891a97f1a180b4c38bd1126224e5b5f1888cf60c1c7c3f6c8dc5c77bfd82b912b1b53194cbb4a049af1ed9f20d4ed3bb4663028c538739f5839151ced87d66d92105c67058f6ac9c9f2c29ff0bdaeac1800e4455c7948ca6556a6abf7ff36e1a195a095bbf4daa9614e0733785d569c6900a7456c9b1520e50b3485d0470d4c4ffc09715281b1227c1a850f98634100e43b1cb279f1ef91e2a0a7458833734d31443a01cf2af6409bd49ccda11d47271ae8d5ea0e2b1df7e0d5b9e358ec19838921dcbd474a9b967b9494b4fd6918deead420956a79bbc567fa0f0f1736d0a0c00ab61bdd61fb4dae7c1c1dd7fc32c05bbc67e5e4884425336fc30b1fd3be75c93ed4f1222ea65960f253ca2846d2c0f4646142dee13f2dd0a4587877bb31b64186d40d1983b70503c40c61f9fc363133d3bacfb6e5a6d6eb386ba0c6d09ed88f1daf1222abdd5a1fc352bcd6c50ff4dc9dd38c2c09b5f1fe02413f3673684382a1dd44ecf827e7fcc956669913b75a0ae217bc764f8efb0a243667170f49b01417109bedef089cab7c89c627a9f7a7cd11493dd61ad6b4764e46300cc5ac8eb08f3b448148dd44a212f5437073bf71efff21b519d91fcd1729f4fc7684d2824d965102782691e37a21667a4a99c44e0950d4c7229be3e1faf9d1031a03029070b454a7e32543bef3069c915bac9b164dfd320658d64a7573dd8875b1376feb28e972e1266fee6bc5c75bfb808279c340a64e7c16e01d88954b8a5e38e6411fe04adcb71e5b9b20d20001d2c3b3489378b067930d9d171d2166ce7b6059692dbc6b14fdcd90ab2ada6cbdb63c0177a94aa6ddeb62a3139d49bfbca036d955ca5d145d29616274e6074620e8250891af575cbf83f6842ecb19b97a1e8c4e684d010dde9685cccdc21916e103fb0570e37e309a892693f9af27a698227b21cf152c854629ca0b34cc9a11a5b82278cab805ef2a2fb4aef2a3de8f203ab31848683d4360d87393e574bdef9e349ee0a42f7c1e13f0e620d005ee4364af3269467a237b46bda1e525da1d3df2495a4cea457a4b00acf0597da411502a56c7e9d3cc79a3f1d3096b2ee52a95aee1640880977a17450e8546d5ea6f0808b0c8f95c8815b50ab034236e2203c299503373d5acb9164df38d7cf932b14842ef4986b174a9b2e4b87f125d2a8ffd96d5517aecdc05c5158177925257489e9d90bc2cd2d0a534717bd0b2e8f6d4444f5464642f883294c9468ce820cab0c1d66012be6bdc7e47b871e7ee9bd53748e9f69a75a1895a1ead7aaf550686e75ceb529576945f464141c587a4cb3204891a18e16122232c485208b71c93edd5ec52daa140c96dd4f0b30dc7845f615e34a5a515e7cdbca068b8b1767bd0a3711205ba86ee9d3466d51622022cf2c34f7df617bf6e2ff800f9fe85e708b8417cc4e8943b75ab91e2f0117867ceb8c38142b34a2ceb90167101b8de441a66b77fd6e96f6f826d3006654bd33989cc79ed8582d574a764fff390e2e84a2aaecc89409f8dd638dbc1c3b482b9ef0f0c0505acaa620c53473345c9b22df075a3130245c7c962263f3b1911645aacfab545a1874317034359eccc515a07d2021d340647c1d7cb00555ec111cf988773c93042087c75c7cc67b354c5d2b3325c5cc260cf05280f81744499cb1bbf2556b89e3866e94f2fdcd9a112cffc07d51feccca81b3640ba1224109303432c4625229f5e8d87c620118894ce78261509d979c5a7b1285229d5c3cef4e688978c40558fd2b6350d0c15359c7e58d328a8326c6eca1802fce969b51ac6fea73488359b3c192ff8a0ad9862a79f2cc9463e5957bc0008058878fb6f122f4dbf4b649f0e61cfa6520f9df547ed9cece0d4cdfc2ca233347ec2337e9d0bb503c31b3c221c7080c828dd2b5439409105a0ebf1bfce1bc31032d85921c6443319c7b75c39cbd2729c9d510ddd074fcf1c422cd6adf9c80a7c3f4b091a699d1e5bec8e3bfab5201c630048b68043c31f3b66df91beae21889687aed006a7d745e82c50943cdbdeac4f3ebbb56b81c14922723d8f1e163ab24604b350a78632168fc3e30a1ff41d0073f52684cb9f089405181267286c475c016b33f306eda46ac0481d4fb529ed3c348860a38e373818b30e32f97b8bb124a366264276c5c5dd345b9b1fbede1ee200dcac8e02e2fee8dc9537f0c9e715fd6f6554e15ff007cbcee52bc1cc1e088283179a5adc121dc5e240b593518c8defdc1a41a12ab8ca33f19833ad77a39d0d80c2326f993ad123838a40757650782cd4660d175a2ec5195c84a008d94d24a6c4280f6eaee278807d2de44401291366e1ffb067dce109852b637b10695bd1226258241652a93be4959341cb5131f29e57c7ff4410f8551044860963b1130428e467c38a12858bd45a9504565d01f6ba0c901a2b90acd357d4e0094e1bc5e08ee580141088ea9d41bfd321ee81d8b055e759ea417e1ef71aa9f3eaa2a9d730b028a003cb997062e0cce01c6842ca64c94693d1a10ef3d0ced97afc87991acf5cff9fc785f2a13632caa1976a054221de0d73272465f2ca3eaf7f2be160f23a6a61b8a24b5b6b2283ea1ea3bf40f20c75d8de09753543dc80730bb09e6df2515280c52e3a4d0861da1ccf871f04f18a15cd8b00ed03fa5da1a527bf617a5d1e071bcb33e5f66559a8dd31801770a34e0acd35cc1882befbfb885f41bac8ce8e1bb42b18c16b5d28ac308b12d90a1c20f01c405c8e8f5c38a8e3b09bb614d92bfa3cd07b5f55ee47073e6601423ae0d1cf349a2eb1a6c3bd4043e8b156d764571a3b1eba08519a686bc0ba4ec4af54ade220078b463b1cfa3f5051854869fce246e841b834ec0683c589fb5bec071f3642bb2188dae89042bb2cae7c6f76cd42d1054316ae824c36750f883dd77d3341c56f9837ffa0c86500de8a297ab5621bc4162ac042093f2349de0dbabe6e93cc8a00978113b173012111db42b857dd4c08cadcf8a9f89443f87e9a8e5f46a5a5ea26e805729e8b8fd2053a2b84de1f9794b99dbc9c076c166d61b321b68ac2ebae1343e839d7436e9b27873c26988b3b56eed102b978223ede406d382c081d5458164edaab6c6da1407a417eb380dee5721459c8e641d68a30319bd2ab71c5487b42b07d183e763c98001f6d913e2f1a2aa269bbf0a462c8c10ac7825ded2b39c74d3e2f344cb2922bc3f61506bff128bbbab4e6a70257b0a5b5cfe7ca82c838bcdd291a078233da2638400e2b1a1a1e592ca09c584dcda74a0fd30ed18dc5121838921208750de5ebb6442a13e42127b3881d997329bc0223bcdfa30a29430c59b430f5731578275dc7a8b4e50e2e77d0838cf293122a9a680197c2e03cb49b35d70cccfc043cb5876e4176adb22f4905d8bdc2cef495263851d456c36deeb236c50b70cf770b28380ec20c2f6b9f9e4108759127261bf535613e42e2a3767e140bbbfc703f919427277591623c6141a4f50583eefd812f158edd7841af0d01316d1bc3e8631a501883b7328caf40244e9f005bf83c6a7512a4f51ee94de344602d96f05140fd769a3c351ed80d9bd7030dce6b9c0e913988a4f0b2db1dcc8feed0dd28253b91e57d19330738ee82f9cc5890617a47ad0bd3d229c701749d200d2eb8063867707fb3466ae9a72e7bec7df4e2baf69bf5f6ea0de4072a8c3280e008afe7617cf388fded2bc9411892cf9532963c4d4f0a888f743a98f44ecb7fffba61941e9989f0002fc604a06a4c00562c8462ae47d433040f0d1c90891ce0491294c4c5c985bca61919ba3127adfa366db86b6ef2433397c862ede49572217db7d3b21596af8996ab62446dd79eee7239097707ae3610d7deb6435c9bff8e6d565eb652904d46badbc1f11e393ad78ecd64033bb1594921301dc629dc529fa815df6601c1eb1fcd4439dd9a409a59c8bfba4990940a014137357311a94a5e73ee3ff18abe813048a9e0d483926f9bd68e3d9e5490b35140b0bb5e31ceba77ff8396bca321f38b385b902f493c3a5b5bf7e9afcbce86fa1e7cc3e297270f6a68952e9fabd6377b0e946901ad59895ef6c34dc4aa56a899aedf3fdc0d14d616fb08a5ecc89d2b333764c38a07faa87460d064f557512717902b37029eba00d92a44844a117b8374d5153c65346b3e22ebdfd1c20bcdf030ae244c1770ddeea3a86995bebcc219107f897339b5269ec8e7cd46c53fc1b0521b36f3d7c94db3e2675aa9ff644b4d8ec76b75d84f9f4ac72cde4a7c4e034b2107a7081866484a7da76656423629593a7ca81c1e9481d0d41967e3cfe00b44451936e111ce5ac1c2c64bffe56c05688cbd1a1be7a025db666b0d717ceac7e7f6dd15347ba1ea26f8d3221e68764904a5d1ac3792eec48b8d652a0e3fa21a4eb727717638d83d3043b3e7f4f32560439dd21b809d36ec81af37ed0b55d1b89a757f790d1a397846d84d5b46696a3bd6a6d12bd68ff7851486b0db7e628759e7e297239afcd881d99ed3234e4a13de9a74441b28c7691985d35ef3d06ea8b388adcfb0ceec20831ada9eb4e3f7e7e6dd1af5c29b9897ae57dba4e4714a3a5d391bb49c7672602934d403e6b483f49099910ee98983ab6eb252ba4267ebe742cd6cbde53de7b04cce4cfbb227c4dfe72f2d82c7d65eae4a7e3ec8cbe9afd0f97d1a2f56a1130635012ae1222b38d216204882ca83aa80131625dff0abc2feef24b82846646a9ea9ab2af577da7a720dec7a8afc11a70798c9fcd702503e217f133a1557a6aeaad46f3f2ddab59050e300515f8e4c9bb06a4644579f066dff9407293003862807bcb2caa5d618349f7bd81299fdfde77c1043f7777e7fd5a0ced70cadf7cbce5588020a266fc1fd8f6c8ecd0f50d3d056eced0e95c5e6c00d8b4dd0448adacaf0e69b5c067607177c61748bb37c4ad6f02eb6560146352417555edb29d5d1616a799ee5c9fd5c092ef97f9bc92646a9ccae6bd7324de5812d5d5ab96a29b07aaf86a66ff0b7e4dee6fc8d26a1e92e2e317991507a1bd22201fd552177d1e7addfcac2d686330095d4be520e15e94cd0903b5b9b1bbfa03edd82dc1871c9d12e8ca1d6a4e6fd337b3a64db7fe896f2bd2a525899bbd62b6770816c21cf0389502280194cd9f91c39083da68306f8567e6f70adfe37e4097225d8a57b8edafce75be8cc88dfb0c4118ddc5df05c32d406edc8777dd0e459e4995d5d1a5020e38fcc2c3047f134023aed8328e7293dd1658bee72a3771b40f9036cf06c8c644b5e5334dea15f3e991bcf92ed441ed4081e5a1c1fb9396dc9da999c6caa5bf237ad0468633090dadcd9b6eb53a635d3d92e8f572c9cedf4076e34601040c3797fd1b619c115b48003698612dd274322a0ba5e6385c499cdcd04d7d96aa41bcc5d958f03677d18e9e0c858c7925b4147a189c9909f24b62f1855db48278587d726238e474971208fd2f23102564415f1621c31b5ae7020799b151f659769a7e71f01baaed61567404a65dd1c2ea7adc9550aeffff50292fc1161f0b104cb161ea6a704aad0f460b244c109e476304daa3c2b3898c466e574bb6b08a5308e64c2e194f3d050d85b81ab4219fb074758b2bfb847047d8dd29fa7ae882752bc52570e8990732144fc371222dc249cc9acbcd1613cdba85355700d5243166ae8c798de09341d44041bfc8e87e8087af7ba75aa7d51eafb78e09bd1740d58138b81f0242c2cb2db38fa7da17951fa94523bdada8a641129395ab908721a50721ef5482c106fae070630c3e84126f2e7026e2cac916608e2c9cde19bd584076d4e58ebaf2fa0e416a30058348322c48336798a719bb8ef477d4d5a8106d437baca5adcfb22ef9486b7ed95a21fd11f5de0d52d56ef777b0f86faff9bd024772878758fe0ecd4b4906ba855af06d2cf03f74d715a82999b355edca070660d5f4ffc81d5cab272f1f3f803edb52dc222d207bef40e0751e28314f23996faf2f444c7ae53d6193bb016f9082fe5f724f903710fef65f740aebd792c13a7a45a1fa03e6da6f334c5258f63d4cc66950ff56003518be09529c1f3f94197486ea0ff39e67c4cac99904fc4d0baab43c29e2546c9c013d50cae6f2c49f67a528cf309083eb0c44671c5fe34541f08090dfbbc6544a650d1c8db8d30eca03e0eae5f27e2cacd570311733551af20eb8701fb885d50e9838f154f1daae4482deaa00119cfd741fa0953135681e38c633b0c35c4b419830863958d5e895f8f14616d5c7565d4fb6404727bc2606092825728b4865a8bbc548f55954552e1aa0a4f03930a48f64f8bbf242355c8c5f7a288b81d4ca01020f1799afa9cbd4f76d8d52651e10956d0aa1720528924bcad9e97446a1dcc2ecccd12493477e6994fcd031647126bcba93b6d09f80ef115d1b8f5652778a10ac4639454dfabc77057ac09f3d69b3e2998a63d0b5baa4fd16cb1d8cfb8f9f8fc496ed431aa7a5748605d6f74eb7c71aa5eac94662cd0a61da51a3eb3240a173821c467965f81154a185713cc1d94427a9284467750a918345fd158c7e1b30910b84dc5b65d2f8053252b006cc75785abd70b4263d62a80233ab8922ae393216a9d8a5674c12ba44111b3ccddf2a7b02e22f6ca7e5ac943ad686f0a4cd76c5a826fe61fc45a676c129b8383021bc4396917a802602b4271f407ab4978046c92c1ff39692fc6bee0e29c46987074c60de4820260195e75091838e8dec05f427593b0dac948a1d76c265f0489d4f68ceea602d18ce71506212d51f864e60c1ce9f77eda39433b7bbf1a0b2f280cd9d524392b4ff0222fe8a62acdd6b36c2c2a64c07b946ad2567cc2ced2d0d9e1e3d6a56184c10712988631762b92f137d3fced2037807fe6d21ec72827813d073f828a6c3b5bc573dd7a60d265e64dfb7476ccc961827600a891e58027bb7b8f044dc299d4bb0435902ef03b31fe65845581c192be887c17cbaf7e9ca897c250127141ba4c5f76c0a3c422999b39b7b52a923d0870078d55058891277ca6b3504582dafc146418a77c22c0dd1cf99741164506a70169f1855135760b3494832766a53947b33bb40d7f656b8caa6138c4e628ed40169c036c4e3e2386ee31e0ae55852b5a769c78a6e2a9f7dbb6141765b01f0502955608a78a7098ae16e75b19590c20a47359c6a144545628fc390d1c8596b8eef54ebf11ec2ed84b0fcf34e784a65d72f7a592c16cc4b693e6e14f0868417130192a5cf2323aae6c51d7cfe710678f8c87cd61d4e2be5c98af5311809b45be29f67eb5e30d6c9c6a02c5b5b6f09395b5510454600f6b673a21b2a9c50f177cafc02a20c63e23d06e898f8fab8fe0a693c8d3eeb800d390efedeed045a871110b5cdd158c8f0a0b82adc973fbf98096d0ac01dd8183ac7572251fbdf2f2c71a8306ecccba9b430027e74ad1f5eb755152d5bfbfd4ca1aed3e11d7b10e117f51da6662015f828b08a8c16f1168356d79a7a3d2611fe5bc0f0876129fa873f06c5dd54c6776ce2060883d41e41ca63c08141eae13a884e2059ff6a73ad63e01c127a0da3a4587e5b54ba8ba97465b216b997b6dc1bba316196c69d9352b10bdd0f94684bb6e95670ccaf6d5002d915ec185c8a6def1dd1194dbb7cf828ab5f0ac445b23a6a9e87e407069a6a5a8b06eb13084c5ba02448556f6680497be2e7fcac45743a4c492b69bf053a5aea7e28ca77b1cbb7a63c40ff29d5230f1470f22c315df38e99a6556ae4e8b6e8c39c366a8a96fb8bb7a8a264ba12d7e3d0bd689b4d5c3e2267adf52fed6ac145b50640ea6ce06b3e63245a4fe4cdba64454b0e182a5e640d50f39cfbe1e064a5e0d24fb759b7741350c8eedf6a4f64bfa7007bcec918f2b203c8dd2c7f4285f9a37b3e49a604ae703eaff2c1c59de380583e815a07585faa489448890bca0092ca8ac6fd9f9b93a9ce647b864f8213447ae2d045cef5568e5fe7c0229e33a56bff2d42bb75d1fd3acd9cf60ea464fa8aeacbcac788d094219caa9bdaa04904b148401fe2f010c0b5698a438ce50c7861b0096fca4aa3f871702dfae180c8249f25abacb8655612d1449efb10dc98bc1665c2fe33ee7f92500c2ddc59418083a4343a996525c044ee8edc0be298bd13f28be8561fa401dd0ad150530afd1108238889e9f5fa64f25a71bdb08e8d108e82888683d8ff8baf82b4f1709109e927612d95719e402c2ba0b1b9fcd1fc5bd9fd9cdb18600a626bbce6dcd9653a84b08eec2191420524f6678dcba412d422656b898eeedea9d2f2748d57c88b655cc3c31ed054a4dace0a71d7b4fd51bae7752b81311d65d42b7ede5e3c477845f7a06bffbca4b41e4305ae1e4c428ab7341e03af9a0b6ce8ee438b39ce09edf48025528c338b8c807b737080003a0884764b972dbac5fd8a1d196b480f6d5f1d9356a206a25ef7dd5504b46973418090c0265c30072b3ee3344a6f18d3d19cab8df175db8b3890d88d12cf097ea8e2f1543c1a0c06946f556b3c23781df1af9ac3fe2dbf781e09c614a10697c1096444f7e0da43015471de4d2bad04eae4493ca8a56f6cb8a4cb78c10fa4b138451702ef10394d19d2319a05412bb045efbe89a76e72b7dd9a4c0de56367ed8b2fd1605c9e5a4b7b5291a8cc992d2e2810246c8f747b0de7f7992395cf66dc180ea9e23cc14f5a55945c28e822cf1b58e4c7240b78b22e4e0973cd5726ce60fe5d20eba9fb791a036c29aeb8dfb13e22ea42418b06fbd28566c9746891fa8b388e8791c48c000b868181b06d06376ab1824ab776fc08c60a26c537d49d1f65d2d4f6830af29ef5b1672d0ef57c33f3fe19588efa84b41b71ebcdc6c474af7db3907f989235fe93297d39a50c528769dca711ea614a4f773951a3d3f6e1040ee7df39d6e922ccde0e0a8ca9aa25e29e03e8601a4956f4eb518588b727f21bae53405946812e8c89a32e8abbf7452a6697e78132994a47d69f6901f05dca76e278121f5dc4ab2f9228fa2865b47e93564094e39546ba2dcfa9920b84e2c40ce523f8f5dcfd91b25729b4fdae63ea89c7b241e1c596999fcfc03575cbd39fb0695d55d8e5152a58a1597eb72981311e4b9dfa368b8ca33c78977b54d9d3fb7525162866409962bd7ea34e201131f8f6c21e9a518e98fa4668f1dbe156cca4771886439bb43d4567ddfb34cb9dc318f3731c24f82df80305d1be941461806a6670859c3fbd984d197d53f253b32045021f40091dda44c0175408d80b235f82b40d708bf5420b6e1612c43bbc614c394a8056e818b82a0fb0645e94fc71411c6067aba2cb208c3b420e1cad140f112112a978947b8202353855158fca35bb8a2c201fa1ad233f64e8891cf0cdcf6a73b12da81546f188eab38bc602850d241a538514f839d01ca624d5a7b05496136a5a4a80fbf105a3303ded484e83f2e380605eb4162826e800fba03e24ee8082a33e003748787b10d426d980a89f21af41144c7d85cdb6af8d15a60a5819d31658226128875c0aed86a401109c64a37565b310a4306ba89614ad0d895cc682a52dbc0663ee9845e66e46c5ee09542140406e70a9c80b40bc92905961153b742fd27cc0358f9d22044411da85bc8dc264052b8d13e3be0bb9fff5be8dac7006c4165f30f349692ce70d6723161e0a716c2bdd6c29cfeac2468d3c8e81bec420dbc1f82849155b98a6500cb1db90c2a13fb8237578424368653da4bcde9f95bbae556858a874b739ae2ad6631a0955ca9247614b59e083784a6d777e5f555f9b1a0490f623a95dc651ff8f81ad093c29d208a832dd5def508bc040bb41d2775fc1bb80365efcc7c14911094c1865c6b9ebb71b58c02552149d21db10dc93f09043d9186996c0af98f3ad3a06713c45bc3abab2590e318aee9e0db39a807566fab1a0e17d8a0062d935d08d59882462120599e6eb945e714a131af453fa70f40584ae71d703197676c62b09afc5bec59786a72db2657265298f89d7f3d5d029461480913ed70caf81d9f31cae1495513ba0b39451a8591dfa0ceadb989d6036971f24c2f75d36ace8b8f298413dc459b9a7c74ec1f372833b21cf0f88500d4d61d930d0de98472abf6859082f9beab109e7b9aa24f80f67e2d273ff3660ac2ac265a037409cf83bb723840be1dd83a47b5946a12a07a3128448876cd0d456554e4a2c6d211009aceb63e86c0137ce21cbccc84c23d59131265004aade2336d2fedc26a1c2a9cc5aa272fe0cf5f33d27a067eba590e0b719e646292547faab164bd0c24d5a85bbeeae1d4ad72155f40cd2c72248bb0a10bcc284379c751a96a5acb54b027bdadb032b2ca0a54aca352b6b0921913fa0e7a0618c5602ebd6ccae333feb8fdc343f2d42f3aba3f411d69d26c3c5004bf99b3bf535877cb58f101d43d93daa296a94bbc8820a61569671d65e61979eac9f2a86a2ddaeb501fab41dd543c742da0e4527f04000a1c48feb4d10bccc079c4b36344416ea0948dd4bd7f0fdbd682094910ff468616913c01b2b07850e81eb873128683e889d785589c3c0dafbe61b2661f1415db244c18cfac9e30e86774b2b19e0508f3d687b4ca86613442315c42f1244817a7eb07aebadb654b3433e886534dd3839a6da9a58dda0e68b37dce520c9ee66b8fcb8ce6361c8bdf81d950821ff46cc7b40f97ea5d537e578859378357420f722a3420676ad204a8d619948677d93085936cf314a8f5bf93fe690c4740af777268f6ec35f0427b45f3cfa6da2374ca57a03ccf6df6b1103388cddb623884acdc4de051515d958878757e1125044ef03c9ae121ed95b353b00efcad93c6dde70bf12a077e7e227f01e2b5ebc00d0e0bb59617434cd40503490e9ac80f32265cfd297efbe64712357d5027a021f05a6c62081a890012bdad8a8b123b1a38ff706c0caaa50290c5bab95294c4c5c1731831ce932ee11a59f9ef712a080343d08c527263286cbc0975a4965e23d37c4b519cc4a05873e0376443fbfdb0e5d645fab842e6dfd6950389cd7cafb99818b709ba10eab352f3946bdfbf381cfa0e644df698a535ebbb98124d42d12d2b86dc702eb21902186cc0ff88688cbe46f1be0263ca1762029b85414ded2ff7010dfad105920f77cab0d7f4cda2c2630fda6d469ec3b904dd1ac5de0e623cf6a8cb46c709016d0dac2e5bebf3c0e458bd6203578dd9ee112a57b4915149866d828525105ba01ceb9f21da4d9c828f28eb8e0f5f877eb362c0bda0c6d117727344c2367b10ee3951aac6dd98d2759328f0ca6a99e7ac7d20f361fca569d8982d4044a028dce1d28bcf18d548cbccd50900298914726dd6b47200331cb7cc8b519061206db8b39a0ff8b8e6a261f9134f23cdd44adfa5e30b2c7fff9da6e6c6bcf86996a6535df054f3b58029f557e624d0d08bb1a8ddc1f195a23f4ec51c69cd7ff81d4257ee3d52abd59dbf4e6233611d14d001dd26cd1b077ee3c5f24f278b34e8fe201b830da21be340fcfa2d17f39924e569830e31f6d2239f7a09e3b9ee2990148656787baab78b42418742346653ee38dae9da3e2d363052ff6d704c7f667852f25862069e583fadbb1690df6e4be619764b1ae7202e9b583683ddec99b2fa1395e3271676b68df627c67d35d2774a0cbd91f65a021fa06aa6aa0059e3d9407e79ec8fa2941acaa6cf0ff4af99226a0dec8ebf9f3cdd36b099eebc434b9b235a519367d2d220fc2b96e065d4a348fea9e6cd2523c3a56c2721aaf8f51bc270345bae13c2da86b3838b8e2c7d708473d1f2729958c95aa65143a597c9d2e4b74eb82a6ed184147de7dc465c2e2a8a84ff5a0003d1e1fdf17d1458932a442ad009a2719a2bb145b3baf82c39858b5ec5ce8674599fff3828a48a3ceab0df7e9d86ff04d6fa7cb6e6bc031c2aca0b7310d090d812372faad95c09b55f28162f13835933c3b46a998600e87588ede951b513b77a1f43af5ffb1eef6801f4b93aa00e362310a02ba26d2dced2754a19156d7d7c246cbc939a6aca78ace58983c035390836758af77463f8f9fc349cfcc27964549dac7778a658361d84137ffa7d46c4518caa2dfbe738f7629abf505936121feb49babd1ac44ce3275a63fcba3c4c6b0d3fa99a5baa69dea226ee5b6d4fc5a92af6a5e52e97b135e54dc89b7cbb690a93b5dfe92df775bcceec12e88bd322e63e7200d213a0a9f3c4e92d950d63051b65ed3f42132fba7ccb62d39d5800f28158c2eed121b3a17fa7d1326584537390aeaae48f5d97cf95d3f584fa274acfbbae2a5ecfaf0ba6748afcfbedefc419b62b55e2efb49dbaa3770d25ff99d2aa31c67ddc7d5ecd8f38f42369502c3874e2d7beddc255163b98374571302598979ad8b2ff1524934dcd8f226717a37680e0765d434671d730f546be0b3f91a17a78e6f5c7c707297950a768ab7014d39b0986a31bb9feb2d403567f2fe252cfb926c1d6a20eb6f6e74ff8c11c7a06dc871c0fbae4c48bf59aab81557ea515661f236a4e8dbb07b38f824ffd068b786666002b7fb9b477d457a063a059f9a0e0110363897dac74d98adc34b3fd1d8621b28f992117bf00979eb09749c50e2774db1efe390935726f1a32da030e7f717da7b12b3c375490a6741e9edb81045d994a0a677bb87afaa66c4446bf55d68a1a11fa55249e671dcf97cd664ae851cef4552332eb1c76c1af25d796f495ea10cd49ca740a95dcc4e6e3b49e899053d542bc8ffb8441d335228fa17ed3b5251a8fc3590ce1fbdbe02135e831b30becb6438614d5fda70e74e730777331f43efc9a63348eb8fd7662b0d97386d002d8e06cac2354ce136bb82b5619ccc9f85df17f630c783912085bd1e259c69657249d72835db4005ce694427d4f0d5be59491c8ec4cf0112acdbb5b8b60da1ebafdb377af7097684703747f45f63db5f62e0c5403b497349d457a7dfc1f1d72dca54b40d38fc69f86c24f35f13891fee5b466f6821b4a0472dee3333a8c5d5ca5d1f0c18ade7cdb6932d9c75eb56ba342911760349dbcaf84a98dff196a9ddafc2b8ad6d784da52b1461198fcb2e318f71580bd217b628e8a119fba34b2219504d3b0ae0d39a41da448a85f2474f35462498f85321aee754e204d2e05bbc1e0d4fb167c85dd27583ac18f7ef81268bd220bf2b682b4e41fab524c55a78048aafc06934f9c83d641afa8c4e42ce80445762e5b3db843cc681229fe5b7330b50797105598237cac3a4235c397d81e0b6d22f4cb7c9c9cb341c1e7b494b9cd48a263b27f1cf1945ef0b44f4fc8c95ebe4fa0e6dbcf663b1693d8cb636dc7d2c4ad5ebef3c35b861db3d257c945e2ae536ac32de15ecd5369b8f1a254ebc5f18df7026cd7913706734a377ccf79617bf81b85f0e36776db11cb956dba76101a033d42e33945ad70fb7f1c6be0b9af58444c7f1943c54b3d485ea095feec24b208885e10dc260a5050c889fb21b68280aa67910063523b8ef200ffb1397296039e22b5c9b46cf1f48a88ee9c9200de0e1b98b8733922c72f7dc615f62d6456927a7517836f64c98143990bba0905980acd21df42b0658eef1e6b7c8602fb0ae65f72397c0b0b47f8474aa43fb564808c64df422d25e81d93bea0e04b646d644ff8161af25227172ae54ae674649a25f30a5ff42a92536c7c678a1df5e1ecc35700fda37599f7ac4afe7c2b81062b6a375f4e224d3764e1cb93812caf44508c5b4a2eb3eff02245f3803acc018e2ea94da9d17afe89b067f96b5195943c3392b8816f3b08e9e1c0b7ae7669409062000491bd344303f805133b314417c91969a181a339cda45a721404d16ded7dec0ae7e9bd9cd9ea9810c70a2ca6267fd602c27e4119779102d1e3cf98c9188111f8f60b4a85c14be980a14f31873567713f6821f3c5d52c5c9779472fbc0cf6374a3a5bc44d91f8ca6c85e8d6d7afb5f9bf7d65302460a01790d50f644db96bb809cac62b42952e0d7917983166df5396d739d6e871045e369403c518413586d8b27c3d7f350b24a83100c70dc3859455671e3679a5c9c092e15cba6012882170131a7022fcc1b739cd1051c018e1975274a46664e82583d57798dcd4183ee2fa6080dcc7de6f987979db612c6ff4cf1d89b76100b7abafa91a42d19151886ce07ffc557a49f9dc49229a889725da0bd10d60dd5246977b581e74772a33774b06ab9dc8e941efe114ed7581c616b66f0ba3a4dc05ddf48aa372d0cbb53c1c44596bcb14637f5e1121a48b14b569e6b86bca70bd01fb4a3a7277eeec368450d309fbe98ca9ff87b2c237d25d5a4149de38a5be1bb17e7fe666a068ff156d7b5b0cad2fef355fc14fce04e2496317f82db4727209120c0f3a2fd8be93e0775cc3c6a6761b8b75e37f99d4be23496e5708b92267735723443da89f0f96ce450360b9d31d1dfddf48055efd76941f07f9413e86d71c5d1df2e1324cd89dfe84028ff0c4be4a36d4b16eeb364f7efc2af51be8d1d7eb080bbe41c20a01be827d098f00f0bd1c52add0b82dee12b137753a71baaf8c30fd14ab81f33402690723e747e894b0f4a3c1d3b3050ef9f669c3d1da11215de238dcdc12381d8c65b2eddca014bea6d73dedbf0c232ec5e8ef5e4e786cb44a6d4631a99322d9ffe0c3495c196a35df44d56506448f1f1739d5dca53b8fccb3819e7f17f7673368c8b8048ecd4e3a6daa1072d75ed32d8bebc5c49199163c3394d926e24feb7106fdcfcea91599fefdef18d524e90337a92d9ad1586545c1dae1c3df6e040dce7f55a9f488e0011a36fe9460f8528fbb3e177127de898afd0beb6927c13fbb4dfaa78617b8ea234574b7b905bdb0f974feacf00be10cc7e0cc0f803c0e1e94031d30b37793cf72027675505ce053c3d537ce5b74c9b55aa9a2fade9faefd027c2c60d293d13ef0d7728680a70fc8f419041c9470b558396ecd2ceb572221bb3a7598339d2abcf1001bb59edba032a8446954d5f42db8fb5485f68c626c60feeb555da8df251182fd708ea05b834582f7de84012ceb40dd15bddf0ad3f86fe4de17585fdc5d952832b995316fc396fbaea639da06c606029311fd9251797a4ae870d137d329d2ee8f33bc59fa6c755f87be7e3382409c479c7889bbbb6760040889297f1a065469ebd96313e988aea3372293bd89b626fdd375a25b1fceecae324cdcd6a708b53ec4574252eda425044b255c78d5e21a3a1c29daeb68605c8be2fa4dee0a026d1f80ee24e01bda2eded0d8e1c07c0a3ee8e80784dc22b846d7be67f81b618a4ce7e186beb9f5fa6c8daefe87c2fdf58691319f0850ba2df97e62411f732b3e714a85c46dda776c869ac65caf1a74d493f55c7b1d8ee15f0be33e784fe7174b34ed3beeafd92026445b84eb7979050c0b2d16733cab7a2e6202459d61f9053746283a111294e593fa454c66749a7254c150bf507565f0ebea91417d6d20187ec42f5fb7ccd77290f46294acbfc67564d0242569c3090c90deb09200ec87d86ae83b83830960f96b5248e930bf753a9d5c19f59a135b686b42df5bcbe7ee9a9ef299b086c72ac82829e8ebbf5620d777324a5f94cb1cd57afcdb4fa9e3c3c4a1432aa1a3d826d92a3671cec4262104b8d070bb7e7a515061383133f906604a072141fc965bf98de1ff341069658013977dff786461b3b13621f27d4b181adde0072d163b1572d8ea08d1529612230a3b9d39a8c5d83ffafc6e30576b7796377da74a31a985dae10b0c7b4dc9954b3f7ec3313325c60ac4920dc5d19d93e5798189f7575938d7a4f2635e635e7220fbedf9f0356e053f4bb4866636ddab29d13b2eb767d71fa6115cc85b492ef5d7452ec4399c670f00f9b453dfac690d1ec00080b69305949de57acc2a1326e66a0078191fa68ea255b76a1357456f5a125187b6831afda8209ca31716a06d782e4ad4f3988e0dd2202ac6e31c3916edb999be0a17fd97a38e4e61c7348bdae9dc7102de2b788bbcaa36ba7221d3bf5a2d1afef867a39a1f64d59218628dcf9f8048c942aea766a318d86c902d31226baf33cfeef09248a0a4998054edb0e98c3a98b87d8f3abeac04db5751e6d1cb6ec93d4d36d65553700c782fbe25bf44f9dbfea94ac227ce586521aba6ade6c4a9c9be4b4f467ecd151f997c7c6255b09abb95098f0fc4f56233168f9a833ecfe0c7faa7aabfbd7573ca46f6c23d2d11dc0c51c61b56557cfbc10501d05e19c860f34f810ed214e774dc3c2be45e1a44f2c7e9538a622f821861c4c92fdf5281a7d0377a7e139d3cb9584b921176745999a5aa9a9e465fddec800fbc3e54c417d6402f525e9e157cfcdb6e864f83c8e0e96dbe54efb2ee0434485c96aa3d5fa1420cf0ad78ecd2943af11bf5f72269e5a64c879bd9c4f005dd5465c47dc4d13c66428042f9dfcfff61865a926a5032501cc4853f90b9a23ac75a23a943e7d85c51330a2585b43077489602a60a3fa82bd0edf6f46811adb5ae829102279f4b526ea99bf0796b48bc5902521da50b95aa89dd91b9288d9e00dfd2b804f6cdc9b08394273464b033bb8b2013b6cb7e47f8807f129bde855d2b7242c147e66dba936f735c1123a79efc6c44c03a0470310fc0497e2e17f857cc340802095bb10fb857ff1057868c6239c054f1d058889b50cee6fdcca11aae53a6f454c2d15ab7a16039702a2e43539b60ca68e562c13d4ecb565d95fae5c29e3e669565dfaea37b7c5b4b84de44580c17af18278e7a6d9b564c406857746e4d42560c0a1102d2eb069cdc01d2ef5330a2bed9bff252b9e887d284d57c89b52b6633969511723e48d627ecdf26279c93d6cf263bbe7370aad25d10db31d38beecc824152bfe11d6a713da9df7eab0f1b4c452e8d0e8a320efaa6d49947fd82dc34b389601d0f8aca9db686f6c8ac376f4445b705330038ab7efe3b3a9714812b6b242b2d6a22eb23bfe6e3a613f33d4ce3f3be2ee00e02662aad313031e3f790969fa4276d7329de32355f77a9ca9b50061391b232cf0782c946aa684b99c7170e367e1a72a3284ac33751d8c95f28a4081fee4c86a0d97a094c73947a6e435ccc81e3ec3d0e3498ea5ae586069f8193e63aeed1282f523b0bd5f0c93c7fe16aeb05f70100d4789a3fb2b036ed7f7f602abb478e5d63bd3381b612b61ef7fc1bfb3589f88bdf40dcbcc6bc95aafba8ed05296e40080ba6b79d4664630a21b62934cebac5fc73a197b0eab7b3976cc8d5674d457b5109bd70c71089cdbed50f0490628bd5c47bb45effb36341cb17bb19b7ba3986e3966b0207411479311b02189672e33c4148e5bb06593f37659d196eaf9b2142ca8dd5bba539dc77131e50dc29426f873db21e1f0e15c8e35fa2eefea8f6dc979c0e09cd9d7272c8ae678b90411c97e9d6b004e12a37adf668e349f52189c83554c356ba386542ab7ab81651ca45e6afb8c3453819b252625e595c5e317f2aeca3133081de114e370c3322baa1fed60877745f37d21382fa7a0f95641d60570eaf0f9930cd14ae03840c1caa08d3a07b4cf0a9a0d34770daa393c75bd769a76580d35d2e68fddc7404d4b65c3dda54a7b7e3854c1fa3fb9aa0b7eb14f9979e8aa4d0f7c1a16b1d6cff434f2c52382ba795289d40f41c3c2f31b977e86fae8310e71fb58672871e92bad782850aa25dbed375eebc901dd0bd9e9273c087d2de24a51434bdcc92629f2ae19aa69d0bce0fbdae98c03ee5e261950b4afd1c7215b119783a68bad0fd1dee23391f8dcb35394899a352832d2754275e99746677801ee5c2cf3329fe89f3123193ce52efd31056cb003d2743615bf3e7515b3013cef3f388569f47bbd5a0b6862bd3530ec34707f4fa14360337feae21899e100355deb735777fad88089ba3b921627a53d82ab89795e033ed0ef0e879598a3163d4229eb86145e8dba7db6b9e4e05069928cebc7cc49d2b02f4b2793d0a6da10c5945366e2aede6a1639678e3ed7f2a4ec1da5b8c26a78124960b70b461f82419ab75ec0703ac0286f14549c844e937920c94347d20e4582e45057d2b3e5baa3a51c9a8fc05407ebc2c5d420ff8823ee160f294323bd0005052d25ac938e05f4ec0eed7d3b4c9e087c834c02433fbb084f2531c414bac8920b45ea75f52a61698ce6e747bd2e018a7d3c1cd1b42f96f3dece717295958d84b46d8d75a4952ea4a176ee9a87696e369ecc53d7465e4d3c759aed2957f5303b0db25675c0d4b889ccfc4f9150b5d96eb16b49f61794aa2a97dfb397fd3b6662766a209ac593ed08b00d88eb0131bc97179612d911ef00fa49e646b6152a13ef69f3fdb0d424df27496b44cbc3dedc3ccc7e25c96d8aa710bdf2bc45e85182e202c5b0d23dd65820dd6a65506376152d389a5e282c8b898527b317256bdaa3be98da98527c510afd369920d9b616058b2377b9e361af7dd85bca3d35331dc9d373fce60bff7e704678fa1e3737c79383e8c19f8760554dbf43148110a6068817e9c8ac81272e3fd2acf50bc1e0320018bdecf258d48852e9ecad58cc3faee4befc393bbfa76b18d66c07621dadcc78002ef3a9180594bc2267664547444fbbceaf8a019d7c22080086c4748b5a020d90209b3acc2686b1fe14c1b02518bf891fec37823f95658a15cd83ccc8a023c71c5e8fb9021d9a3cce4aa48622bf9793f56e2b3f7f9a6dd006e803ff2d534bddefcc3b24b60f90a508330be8390941d8cac27a1ff39bdddb2dca07253eb8ff74d3db78837cc02e3d3faa41e01012153f2b15df2423cb48332bd4c7625ed2436a61d91f1a0389906dd6a74a40193f4d051fa7a61472c5e709eef74f16f307e42fae827197d22412ad6fb9c9a40d09a9c7d49495b5ce2836ae9f82ff8dd452bd35b004e658a2818f1c32e2bbc345f80ebc54afd32aeb33708444fd8fc6c9b43f312991b83a8c4c7d1189bbc83fa131559bbcb69c8415099c3b2a14737596183e465433ee60189fc42c772a1175990792aaf00c7cad7ce604002ecadc5f73c650910570eaa49a7d602c6ab799a87a256888310fa8ae2ece0fbe026a68c2ac6fb40bdeec8a921ed3dcd3d9af03528a866778a11da18b06f8f85a2596f2170f572a6f939559cadd9e7ed33c225c55e4ba9aef80072a550f0d85931ac021f68703657f73e8925f81cd779fdf4184bb4cccdf667bf03cf4eeea24c54f08f4d158faa2574ddbb9edee98cea195ab7460685eec506f6df97d14d5e5989c054ef428c2b462661433cc2c7be41a546b09c3636792310c300da66e4776a4848b3a3ea6ffa0ac9896aa4d0f77bcfc15eff57bbe5ea37a0981d1d07131ef6c333740e199a41d0e12ff0c49f0f7117b31c49ac91ee9d33b161bba92904026b665d63083c07905ee95d9ffb49b1df8e4f7ee3b92280ed13a3084bd324e3dc161787a73ad8b36eea42d90f332cd30c8d10c34e65790d59095946f013a3f630b81ada24c075bc00b0837c9c54bb275ccf57a2ec947789d0b1c6595bb7f79399f1e9dc68d804a486c19be164a450b431381d82331abccbcd0cafa1934206e502f76478904c492be848b6559c3e8b2fe65f947c2edd8f245bd6115afd0974873134f6730b0cc74f359f06047671ffc500f65432ead4bea9f1fa6ebd86604267d1e5b5766e3d77d3c526e3cc2ec9c21425b53390a0398d49fb9a3775f4b65039932a8a6d3673ac46ebe94706b4c004eadf4bc158309a55493ab94997836fa9ad91e698ae1372c7228d282db805765cd2d761d81debd680de4b9ec23d23381fc5fe2f6017c49a453a361590a574c1ef05349642d9d65a687eb19753a73634726d6c6b9f10b21bd984ecbdb70c2d0cfd0bff0b3f6032f4832df2bc5c22ca9f792d6543552d8bb86a6d1131a33c3f7f1dc5196c49f4381bac5505fb655f2f419221d6ca13758d257b20ab394964a711b8081c64bf7894e7446113c6a978ec29d3b19ba2299ae26d748d797a244489f2a42d5a94679c731e80c7bd899e2623571368557cd441d7a0af8936284ed163294cbb32201e8e38a45cafdd20e92c8955927b49ee222d6f56eafdcc7643b416d073f373d3c40dd0cd913893dafe9ba01b2b1a47e92d858f5047a12877ad75b38158c2c0b65c2b55f5a9db49557d2604fb5a0f6bf58c72dfe3c9df25942b568fd1a071c05c8fd5a0716cafd86b3d35d2ab520684a650d028aa0b88c419fb7ada13674eafa74bc41994d7d37aca449c49793dfd8933a8d7d3261a87ca1571a519712d1b34f683c69ee819eef518111a23922b17afc75a72f5f22c0b8c091de2d124b25167d80e723da6b31d4da07924ceacbcebb118cf27933ca317752d4b94eba7075a264d9dc6c1f2fa0b49ae3256fd9514672ea1ebe7899fd3a07b814dc1374790eb6554d4ca95e8df0594ebb54efdef01f38b85dcc3fcf73017f2a7606eaafb7d0e2b75531d4e0d913acc6f52877977188c63a4c33730bf18e71ee6387fcac2dcd4bdb7e56f4bd5a5c373f9c815cceb67d2ced52357a9771db6a96ba54a3eae5aee928a4172d5826512963128855baaea4d295526a91c72404a8734e381e9ba2c48aaea3defaf35283b3aca3cefef3a6b934a317e7e7e689665493229294b4a4a2a259592ea4fa9a816957e4a3fa59f52515151517dc62357f54866414212a6eb8e783cefef2c4f2614399b954f9d23d95913922bad2555f55b140d9d904d91ab1572ddae1081c6a3b53423882bd792ab3aa426916bab12b1db85b91615e5fa7ad4bd5e13925bc0745dd7e10f3a212857a05c3b5bbf6942b9a6d8934c922b6d8bc6d1597ac1d28de56a465da36e3d5255b52cb4aa5ec80b3a50f793ebe6936b85c2e681a7a1d322570d0bcec684371e93a954043724106f411b904c6a1cdbd1651a7a26d1383a5756b5495a4faeda0c727df58255c97832dbc18c27d7dbbb3511bdfad3dd80a2578f72b723770b8a5e7dcadd9044af1e7537a1e8d5abdc2d49d7a8cf82e44a631255f543b2242c916e893311942b52bd8bab194955fdcbd58e90e4aafe48557d96594b91727de947ae341ea9aaef4ea59f5c9f25c915e72355f54036a8133a51fdcae596e091ab0d8b56d5d77a14fc65f5167fd603b98e20d713fe626496bb1d758daa823fee27d7734cc499ce75abe79a88ab529254d5734b74492c92a423b9bee3366ec95614bd7a0d73ade8d59f56ae2f25d52ecbb623d376546f326d474f6c59fc9492eaa611895ed57aa257bf618d277af52f4ab09fc693ebb72ce20ca713673eee4ce2ccc9ebb7a3380373fd8614678eb6d8a2d892e2ccb77121d7d7ed9f6694eb2bd251d5c9b51ee57a4d6e2cb7ef8d24b9aaaf3ff0465ed15b1d912d25e55aa170dfd29d6ccb1fd3552e54d235e051f0291726814217228141f0db91cc326bbbeef73c996d3bcaf5a5a25a2acaf5a62b03af1387dcb8c18a7d7048a6b883121fe80452de8e51146c7300948323792b8294b7225b446f834582608ebe59165b5f6c31814724c9f0ddf83b62a7a79094b767f77bfe6011a0e86dd656275a52b5bdef44125d230bf6834ef01849e2d99ce0c9db9dd0525a0e84f2f6147cdcc95f4e90a64dc1246f5f53a19337888b00e56d4950be4e04925c4123494418c915f6ed441cc915fd56d2a610ca17fe9a8a9dbc61970823a9da0e8d24c915114852b57d7b864eece40d7f3d4552de9cd0c91bc5441c49d57623db59833b20cadbbf9ec2c8897fd9edbf0e3601245792b51d36112457a9d3210b07b250be1d1a218a33a56f4694e4ed42793bcab552b5fd74b72b5bfcd9bc39d1ca1beea2b71d1a6999f0971519e5ed2da191a3bcbd6336ec033a81a49de8641a59a963293c91a604c237c2f0235ee396586e0a0bf3b557ed55833c1fe409ca54889ef6348e93d3d31750251a473d7d71faac7519c995fca4cf6ed02b2d336233204091b32430394b22ce9c4e9ff1c41994d36744e24ccae9b39e6c8938833a55397dc644e358393db5226657d82cba2df2b6c3247a43323d6dc995d6d276d094073d633a3dfd41af5a4e4f85e4cac5e929915cbd9c9ec2a0bba90d323d9624579c516986e5dd3791ec0ed5f18aded1cf34f4449a498da33b3d768421c5192c08f3912bec545382cf5fd0a5c08ea0b3cb039916b58830a1be0ef31ed8535848ead853c72e04e6d79b958a4f3d8775dd54bcb98e3d1ec39ec238a963c781f9f51cd6062455dbafa0bc3d75b7c36cd7eabace0ec643afa41b34be974c5199a6be09c10ef3ed02362c8f5a7336da6ba2f6c286e5baf066458457645b1692730086533cb06387390cbeb9708ac7f5eba9a7369b882fb072202758019295fa0f9d5b7295d5d20e4cd7791e1090d692ab52c9f3feaeab317c7c7c7cb22c3b3a7a719b554292484825a4125245aa48b59515c995d6aa3ed5a7b68a327d6947ae342359020a92463b3b3b9ef7779db519cb679291dc82e46a23922aca41d12afa188513b82932e5ac68b2ed6c44db5644e4026f45529564bbe58ce4aa32d18670465a129c1167945ddcd45fae4b0b22b532bd6674fa2d081555f4305d672dfea0138c720065fa43a3db82327da32e391bed84324464a5b62c1a47e76c2bda582ed7ddada86b703c5245b72a5a451fe401fda7f3c994ebc95453421aae458745a65b1632e5764a2520d8526dcd23997240dc4fa6af8913a971d04f2d7a55ea6b080624579155da7822fe361764faea851717b24a3b59666d0793e33f98fc957632fde9724c448f1ee5723fd1a34fb95c1397038a1e3dea7247a247af72b9a0e8d1af5c0e49d7a02f01c9d5b64554d16f4cb22136898ec8b9254a4672b51d19657a17772b922afa97bb191dc9959665d61e65faea2357db4ed6551f94ea93e94b48f42d5bd419dd5b2921657a969b237af4db8e5c955a454fe92dfe32fad3b6f375cf317277b3ae4157e80b10673ad31f20ae2a9254d1d3d3e4e8aea0d47b6735ee8aca1d61ce287af4970afb71464831227596957a95654f6c3ed4e5f45b12d1a33cd1a3af78db891efd8b12ecb693e9b738637a8933dc29bd8d33df29fe3828ba38f37156c8f4b4fedb8a32bd762457f49a915cb55c6a49996a4699debe3790e48a266a3e72055b156fe415bdd511d98a94a912ee5bba93f5c974e542251712a1842012940b83e0912cb3b6eba4cc3e9a5c5b729565d6766d65fad295b9f9891e7c0a17e1728d883a436edc913756a44adfb8ff88adaf93bec8f3f2bd04630b26f9a0501e42b22e5c845461f9cb4af7061f58a49964753a9e7afaab8958ffeef3ae234a72fd1556d46f22d57fad848672d77e51aa1d20a7eeb4aa9ea7a7d24a3fe34fd7a89f9c022a4ed56f18d65bba76bfeb80c9254e074cae18967047a1954f7450628e1f151376cb3d04b404871f9be51e025242f412af99bd64d90b98652fb21798bd642fb2972c7b41b199bd64d98b245a96d1f6608430d2a04993096343d4b3ac258c11c6a15894936453f41941932200810a2ec880154190239011723c2ace7017908e8ece0e58f15d5c75cb1700e42f86c68daf93c896596cd14cb27c7f7088849192892920921bf9834994c025566401990c49a20a199790b1892311494cf2452553c85f5ce26345969734e0c97288065ac8f2afc110b2bc375483a3dc4336384296af714196078207135ac8f20c18628249962f020a59de88a11b504107073ad8c10119788a14a1c7a788157e868a8021cba11f21211ea2560e9214191d21dd20cbc3241da441e7063b3b60034f1347e85122cb1e3282f4e30411509010114f963dc483a32c1f731667be4d28cbc34d28b7d014497913da8c907dc82dce40a20cb7242cb825c9cdd9c4d2155d37e4a36404435f68f101419e9fddec253ea8424a9e73ce39b119f97a2791628a198c60ce20075d0a2613544d6610021990010c43621c010c2be8cce0838ef95aa8eb1c1d637a65a52ac295f686456e4455bc8c19ac4cd7d10091e55b75bd74bb7c1591e5bdabaf0d64e95d38124b298cb6157d4cb4020c482527d80069044d8fb410c1bdc71ccfa41fcc16d0132da0f1424b0648f9eba22cdf56c877f2323159ee2119b4e8921944accd3da4441bb2b027b987944022a2b50f52061209d1d09841a38b4ee491349a9877727970b62f8aca646ed0d050e5c8724ead7236d8c466092bcd8e863703001e958d9a7925bb2eecba2edaa815b9494faa68eebc24362f99f3a4f39a5794534e39a5fc94726612ce89ea1848e3ec0bc503adbbe17c9c53e2ec0abfb0e7f40ff62cf19ac8c3cd715baa240f7c81b9b806b9d3ed9cd23f8c2c78c9d95ca39b86e250a877af22ea5a09439e9724f6933c197ef630e529a89f6e6f0afe687c3437b9beca7a6f78cbfd6ec03f68e496dfdbfbdde4fb12f7433ce18f473edddecf3ee5f56a29d7e68616f615e67a697fc8a0ee77bacb4b9c8dc51d3d18bcfde28e5e0bee6ba50d5fd421d75d43dd0f66ad391d50e59eeef77cd2ecfda1b37dca9b4b4067abf2cbdaffd0d9e21b5a5815fcd5bb1c723aea5b6eba2aaf9129d74f56b939b906c1320a63d086cb6b644dcca2075d60df6398eb05e622c22c05c71f8b4ff01dccaced3aea792cff6458fed1d44896bbf2973b391b95a7ee4bf4506f7977bf9cdcb1e49614fce5e494dbfb5d057f30ab1cd67acf9391913c9247aae0abca57aea9ace0c0aafcebb2ca6ba2e491f0baaed35df0e9d8e926fc7557777b597e6a39ca9b1302f5165c6fe7eea2ce72bf6794157bb8726d4565e5d637a75281e8ac828360d93e7241c03469d26447ccf51980b9e21fa8af5c9827b74326a75ce5167f5008eafc24f920f51c411e2323a12322a496fdd745349efd2793abfd47a3723fd34f6f6e07e4c9292af88b4056b9bd9f4cb6aff77b9675d7fa1aa972835c59e5cdd9a86020327f30a3700564720afe78d87b7abd28879c0dca4fb723109d2d0e7205d181264d9ab4b209ff90c9278f3b72b5b5aa1ec6e4faac56fc6df8078ddec997e7933c196f71d5aca933800891e8d1ba71487b6534cb7ace47c80361e7c060c4e090d135e2bf1b06e0411bf1dff38c4cf4228acbcd43015d63e60d442f7f32594787862a80479228a3a5101262e180d8409c8155682f62a199340a3212ba060c401b2cbc7681c007a00d5647d7f824ccf276864d58e8c3920f9093e1a14fcb962c0c90abb74adecba48c5d7d5d04d334e4e7f5a169c09ad2101861bc380c6491bbade8259d64cbba3b2667de0b68b210ca0c4050a28b88a8c602b2bc5ac5e50b0359decba65c49d69793eb9039148221b9331c02c18ea9e5fdc6e98ed3f22a6a8e38c8954ddaf12100ca2fb987424064fefa25244bce46bab80c41cadf3cda8288792ec10940c81f3542434b6e2e2e93682a89392ac87e14c800f2a674c43cbf247a51069616d1d60c269e3f716662d1aaf8a905518e474747aaa0e17137d6c5c54546a665c4564427c3eeeaa0582d5707c5822b6fce86e535918888517ec94d44e40845355607ca53de33af93cde97dc234283b02731345afe584cab18b7abd29775ead6be5a812cab5e6b0cb9b8118c4f591a2c750ee970198a27807cae5738d440902e445c6404ea6c03cccb31586951c3434f9c80704a6264d9a346175d7b0ad7427d09d4dcc9fe8c5cec7757aac00353f727af2751ff39d2f98fb28a78460e5698b0201d1a0d40e7a79fa2eebb2dc51a0959be5a02a0e3081d8586ee7959b659cda51b37ccd12a78290f312a778dcb4a8d103666b12b576928841d18b4838ed2d1807e3f0d8812c97df3c858720e103cb05a7e40e64a5a4cb7ba4eee272212d4fe11e2e7ff90b16d2dd05a724e6f17297964b2002f448f42e6db95c5a44e5b4fd2146b9a7907b68c808326d512671c6f4f82c1b621f4fab50c9f1b4886a91291639d22ce47829ff6c892691bce48f59447d81fd2890c4418c381267604bc21389bff664689e9d1e3b6dc9a352504e6f59a76d207996154c5530968251f00957fc7979074dde2890c9740a34b31c2dae004dc6f00784e9a6933b91442f4a1ce40a424893264d88323689b6162acbbfe4eddf34ca4e232f4faa745c789e6e9f1fc53f68b2f60dcfa049247a1113faf901029a447312116597372bf566c9899d3ebbe6d2b9d6d27579777a7bb43c75ec0ed1f2141662e4c6e5dd85a4de22790c4961173c892065f9d91afa409119904a1b9d5402b59800922bdaa29802490a84bd33a53ea4c4a91d9ffd494c7fa80f6542ca679c8da42d7903135b391e43527739057249e13b7bee1c123df9895dacc17e3979ee64996d55bce46c36708420fc60c8a5933ab21f6d115d7174b493450f529cf926912de2cc377f72fce6901c5b4b620f2ed48a38f3fdf801eaf191ab26b2f3f95da7adc6e1e3c215b82e9168651a85c6d157e44f6e91618b1a5d17863c9047082ef77161d85272e991a872b9cd921253a02a02daa2401f355a82f6f4c096cb5b6e4abe59ddb1a3388321c955cbe3b124aa4377e4cac5e3290fedd1828d3ab9b38b7be94855fccb9d4952159fba13e9ce23a98a7771646dee2110a4217ff3c8e5c2ee9690000422f3088910bacbeea624a62dba641ec5792404ce884b0c3a310afbc122b8127df041073b20db927be80936e427bcf044d14f4b99a3dcba054e2a3430d04498c92e92ba89acf8c88a184708aba50a3b02464c0a24403188e1f3a29c72ce396717a44ca459a994d1186126e79c534a1b39e9906c49447461d817ae2fcc78c10848ac0470de0c6d2e51d367ad22a3226dbbce5a795df68ca7dcd825239c48d7135910b66801d0132420b2001e9f2764d08382a427828082999d4048aa3a7b228a152c00053a3a80b0a311bb30ec8261041b2377145d10438c28788105387cc1b2e48ec20b4e14c10b51743d48b62e22cecc13cef4dba355d76d77215f4ac0130e6e1a84105a6b6d2dc1ec1f4d861d764cabb2cfd4d8ededd10768b3961ac34abf6e91d28fcd2eb863348d3078cdeb9a9e91adb9878e78e10a360bf9fbbc47433b524516062928967be8881443ae9f9c88a717b439693721bd539e2c90a3ac79e2c800cdd81ba50239c6d32cdf37058165898174938c6118e711391ecec72c6e51d7755d5729f7d01126e4cf7ee640eb188de330d5fe936b13b91ee59a45f42a508c9f628b5c638c3046182352aeef187f72358254df874690ea8d8f317e12212455f1f144141101e39d58bb76ec12512455da8910922b228e2e2b9e42286b4652a5d5973eb803a1acfdeb298ab276680429b3b68d2065edb0891fb98a2c4d3bdc01529ce922ed2db5778cc61323cd82968a5a9cf893ff41e24b9992513e428886ec59ae509f90c77e599e2f8a6ce7963458e9237bb06338a2feb514ada20ca5100a6aa01f9f9d3c44d1bd018a1ef4bab33cbe6b39cae52b7749a9682a97e5a8fb37be69c2450a1d54d20a5286c75f4be1c828432914ada0e46c22c65e2335ecf60d2df02704e8f04280ae8bb3b988446fb6cb8d30d83eca31c60bd2a3e84d9a459eb185099bfde0cc277a970a3b1f77ae9e222bdfff681010504f0aeb89de757474a4f21ea9428332dd30d91367a60cf2bcf4996ad0e9c1ce90680434d48321ac83f29c8941974b0cf58027cf5324712615e7e74ff23cecb1fd8ede8ec9d9348ef826287a2a1772f488521a74a9fc3aba7cf2853f382b6f79c461b9cba3cbcf4fea86e5a9b33ce5027de42a75d31df5eea86bdd6de95a30cf9d4279c68e3a73fe221267523e3f8be40a7578048d60512b7fb2a7855a9ff168ca783f0a75a1905431498a1e44f28e1e3475324fe2ef47ec9c1cdf728593c3ea4caa7eba46aa8fbd71aa69d668d30873f744c513eea993933ae79c73ce39e7fc49e5b84f8e3b0fb99a5a51a61742a4492f26d19bd717a06a7e9221cfa9863c4faedd0c64393e1edd0b8bae31df42996af205b2a767c87eb2470ae105b5679d73ca25c823a0b460e1b116b6445bc1c2a2b460338de62963906da52c3be1cf5a3c4dcfb2ad44b169858db95e42d1ab6e1983691b0796a79c22cfd951fc93373092a704c2fcaccf354543d14e9ae998e9167f59663793e9d7e46caa50d64a375561adb5e22f55aa37dd1f4c2653ad37d592092b99ea4b2b7d714a3b764dbb66d26ebda95ec3b2c725639e0c8f7a8f05746f298058de52106917666ddb36ce86fe6272ed3a6a1c58f40a8b42abe667e93acaf39ad46e56deb9a0def21edd5db09095776fc1428cd40b5139eacdeade2a1d16d2bdfbca85a0aef2d4102a47fd46e5a8577cb3f2ee3571720a6816cb4d5514eadd6f50ef5edf756fc1382bef8ea302391b1e2c3db98347b10465f815a10caf72a5206299577951854df140bdf3d7513092e208a7490194e18b6047c1285f4771267efecae2428a3312338a1e93c696604558ab8fe2104552055f449cf95a8a534ebba6dd5a51b8130c046257748d79b1d6c5965cac08b3e2c87e368af81167e8e7a4415235b9cfedf3725e6e1167b417f5b4788876bea983b5bac6445dc594325d339d6ebad8d7a9d33b0b01caa6db37a7c3def49a78ba3f98ac35996e4d266b712a6551ac111a8a351d25eaa06b74b6a65b5392d5546852ca8961589bd2de9673ce0bc34e4bd74412e38c104278b718e3c4d98cd7fdb2aef15d2288914a794929e3c4536230cee092f18a18931ec4b0c65f863b5ed7bc660e8573083b646a88fb983300ff86cf9be1af8d72f6989b828f390ebfe1a68688390e4f41dc23e638a47adc7018bfe1301e00690a6cf6d835b128893d429484a0241c654882dc43486802094b202189dc4354d8dc43541cc9dcfd66e64e4caf91abd4498c13ec611cef2a5c93c9958753a51838558a8153252144259309008f817164ee6537ca30e336fe9d9c7236275807b762e3a6e065bc5933ae77d57bc4b80c2c44c663c8c0292e47c815d0ac4b55ec72581e4ec19b19f70eefd5780c8ce3bdc671627c460a4b46885291540ade06be078dabaec238399a164fbc9be262dc1477ee9e7715c6b171ef3800788cd410337ad4788af3aeba9018329ec3eaa1bacc65b0100f5fe0467519188706d748553c1052156be29b65037f4d44031b816f1600b0e44ede31b2779b756760bf4e7a76f29314134e0d112386eade853c06eee10951e14b95097bb8a5ea6ba19cce1a0e17c6ad81fb74b38cba7d1d9e0809477213292122520224747294fbc18c029fe12c86848a868ec024cbe41e12fac94976b60d951ae21e873be01e079c72003e8c18f77e13e31ec63172729c141703a7e05537321ee331304e8ac3a92166dc7b8f19f7b0102337315ee342bccfc03d6a7cc6676021315e03374b069671151662e466c655172203f7505dc6656021dc55b8a5ea44939c0da732e3a64c4fa5a49868524c29de4a0af7d44a8a938153284f7137329e4aa1c898b1825354b65b141977064ea53cb57273192bf886c655dc4da5527a7087d67ec6950144e66a514ad69eb0acfc2b87f98ba7522e9ef297a79e4ab9dc9452397794afd0a4c0bcc09f8c8b14cff47217930aa7b8d368c12915942fc8cc283fa9bc26e664538a3bb9a9d4cab9a71cfee4326e6a050f41e3aaa738dc8306567dc6653c95827bccb88ccfc0324e375d2032a79c64dc96aad355b7a5ca05d2dda4499336649413cae995b34139eafe885ebc4518c1c28cf21f7e1cc2c37b82bf994d87af917106e5b18838739a97717892c15dd51c96891ec763c82f55dc077540c8e16f66eed7e1551d3e3b39bceac2132c13bdb73d577ac3ebba258883cc2026c7711cc75d1cfe70327778af77b7ccf418387572d5735826ece1d4094e0de13dc66fbcc7f8c963601c2327f826c65518e75275f21827b89fc3aa91f0367781d0264d9a30d921b3c680dbac184064def007b38681c8128e000eb759306eb35ea260bf1f5d84b3ec628fb9cdbae136ab46cedb196313b78ecd81892fa630e79cf2f04a29a5d426ca35ed1aca0f9a0c3921b0b6fde446cee6243e27cbfb3d4720c39fee372f67da6e6f4e8637c8b55ddeef46deb8fb6d599b3ae8350c51d77e733e85c59cf3f31e0d19334fe25544eb984d1e420861a4810d3112610bc96b0def4723c39b60609b551f5f83c87c437e850afb6d25d4077f75ce68640cbf9382fd3cf983468e9797c9e2cc87cab285a609e0ef11ec0735001c28cfc3b8d6dc8e79183fb9dfc9eb8366a4e4ec7508c04bb502086c9251ac853fd6da1b376e683a04e03afc6ace2687d7c4d5ccea5773363ba04ea893a6556b6db53f9c6c3c8a1eacb54a1e0cbb2857351b7c92c706361ee5ebd5daa4dcde949b6e54123d28f385630bc79947f8d3456022ef50842345189277f80e45207245cee11fcca72da0489273780e876289c66dd8c9c9c93cb9e289cc41871c0e374e07cc395cb3a75faf185669d0e9302c2c29296f8e8525e584c36e6861535236cbd9d4aae3f4eeb5c26a391da7eff0eb62ab9f6ee7541030ef70987738ccfd72f20edf2108017c00ef68accc8e9765f6b7efe674c01ff071bc26621be5ea75b8343434a75c3dca8534a7f9e9d809e6155fd7f59caf9ee535b2068919f52d25057f39574acad53511b25cabbf2e7b5d36d6b68c8a5a45464618923b77468216b2c88d043128b1b9912006241ba783dec5bbeb020a26906c81040a22499664fa08c5904c1fa13852842d327d11aec8f4ddcda27797d45de28c193366cc10d23366cc98819343e35c0aa72cea299f05a203e6ec33628d15eb5cb81cc31f8d8cc538eb23516b0591282a6971f6cab37036b57e72db9bd38e72533a520ee328efcc72181e5eca711e5e13b59ae73c95c2373cbc06e3f4701e8ee3c3733c87d5dc3d0f37957a7eb372dc54aaa62663213d9c87d7f4e003e4ee9b7b711eee759d2e9657cc741a4f0162937d06d681e3dd4df7b3f93fdd171708ccdd258763ec4edffd59fddf1d1e77f45e70b76121e8bbcb7b81c03c00fc65c771940b738c9d0a6773e10f275f3511757a146743af410e08cc28f745cab19b8284c728d357242208657a8c08addc435464d1e51ea2624946a293e9afe48911830346a9e6c3cdf11e2e0f7f716146b930972ecc382ef67b61fc8147cf72dc87332007f6e10c38bd876bcd5d3cecadf7874cb6f774c19a68aff77ae5250f0e31317e612ef0310c8ea84b4e08fa8a534160c791a88b44b65a8b5142501f6e0e0bbb9d5fdcce289defeddcc3cd61e1fbe5644a2477eeac71a86b184cb9306f712665666b32bd394a4da68bcba2579f72ba66ef1724460e01f51988d9877dc7fdd0ddbe539e723acca89bb2efe13039ce728be4c029fb1ce7e1ddfd307cc2457ac0298b83d8e1399c073cc4880e01780e3cc4c8ea489c656426b56387e7f01d9ec335952d09b98790f864c8518cba30290b36559f9383d8e102b800b00fd4777867980a2287d757ec03f51cde7905f3950e872ff8fb75b82e8e934f98b529b76731d9145b537016bddabde2d3399b0be6a77b17f7833987a37678773f9cbc3abd1f4ed621871b24e61c0e7383c4bc03e4ecb31baf34cfe24c3dcd2f997ce3f6ded0c2d2fcb3f906cd339a67375ee284a04d9a3469922bcea277e3cde9a0c137be9ad921e8c6953a5c49240057e670e51237b01ffcc99a83c9723dfd84bf99ed3526ecb79ddeb9721c67632d863f1ccc9b433dce543c3b16da75428d03e61bb096aea32c375a8efd861baedf7043938b93afb7d88b0178fa0933a5e4a4d4c81b302cbbc7c0313860f853adc51fcc57095bfffff3bedb5e3140aa2aabd61b99a80651db80af7129b4c4ca4f39fe753cfcb331efb8f2e1313e6058d403e601b754c9e0962ad5ed8cba9d61b4beae0ffbd34fa7afac5cc3b69595affe74235ff57e1727db5f29c71abb3f6e4ec10180364eaf91315f077361c6a48fcab1a75c4d6e30299ccd75dd0f02d95ee56ad7c500cc11c8f55f677a733627db25194bb92b1a2a25db265945454545e55f17d9fb5d08e4facb9aae1f379b7000a00decf67e3a646c0543a213ee681a07ecf0977df52cf8eb360065fc05fe3299bbc0aadfc62127848d9fbc466600dc0f66188745366ee3326c5c067fd606fe7ed0c83030004ee3aad74800dcce32ee97a9eed7e51af79347c8f0312ef793f8078d3c4fe3defd6e320da824d2b81dbdfe2582fd32ad844f5698c8f525ab72ad7f78390bcb3597bb84fde2d191f77cdd775c1d2673c1b227c76500c49e823fef84bfabe0afc38a60dc895e123d70080b49f9e61ec2c292ecb3454fa6711817dac9306e3fb3761921a9878c4044fa40266c66b6ec277af51867637116bd8a82ae016fed0a13ec2751903f591467ba43974395c34b14340ed36947bc9049103077f466ae4fb95ffde99033611df51626058939eaa864e070cfb396d6fbf066e5780fefc1c37dc0427c380fef010b3172e3c379b8901e9ee3cd8ab906b7a813756024459d24265107e64f0dd1c373bc470f38a6870f98077c1ef9e3917fc8641818dcd13b86f119340e23c62d0ccfbbaad398110307d59bb39171a1920b7188f1c3e06f66986b37f9f47a0de35a6eba761e9ec3aa16a786c8711e7e93e33c601c23f638f526c779380ff039700ecbe2efd69eee07f329e5d5d6cd743f1ab962d36ba4e9dea7dccf7b7342c0b88df9e9de70c983533b6246ddc56b24ccc5e1302e048231f7cbf2fd2f0e312e447299d4e8a35e666badf5516f2d13ad96256a1129c100ccf65048e8542f54ffc12024156340668b81402660abc2d60b201344ad97291c11b95ca2be1e5efa1ce5b0208c98b75cc1bce52ae532390ebb28d79f52ea4d3030f04793eb636230c69e973fc92393e30e19e1486e79e9da4ffe82c48cba8b766df3af18ee9cae35077d603e1df6f02ff3e19fcdf65f97ed3f2fdb5fae78b87dcd6da972711f6e0fb7e50f997cfaaf9d2ef409900bf330fd29817a0c63c8266c0893b040c35aa3721ffa68783519da099aca18ae0bbbae09afa0417a45a88529b33c23af2967f462cf75daae9479e9631e3bdc6e68c1a68248f1318fca9d3b7f13a776a0327654c660cf77ce1d67e4f624547ca09480b00d4dc02e7a51ecca4209dc381ad6de9c19c811a3f89a1cc8f130cec8432886ace5a5564d9c6530c3160d34b863769baeac35ccdd3967420dccc8b25d2a6345d60c4347168c11b200e7ea406bb96261d89583102c56ce3c0246b80520682c0d6e57abe6c90296456fb3b0db2c0d76bc6eb396d0b03967841ed0e68457072694110e41c37266a075a9bd920660074e3ae7829009373c10d4829d12a020091f104187e107f69a584e11248c0cdda00966e831ba90a022092d4ef043a63bc3fe6e83fc793468aa60227f2dd44986aaa8414f14296862086a1882c492b28972cf1b720f1535c9a6dc434548f688d6cf826d2e421e493e45d306441b0eb48ef920cfbcaeebca2ad69bede6dcf29cef0b4462b7c8764d6e14723371a66aa5f78138409c89327af2317a387048f476a8dd400b11fbc9e46db2c06e7126420ce9135ac764119569e24a9b8176552a22ee207f4d09c87798d422077104b4b09f0c16f693c9376b15bde806f2754920aa906692269d60e155b4b0f0f29b110b85748dbe0a182c3c8c314231681d53935b080c36fe9a71c618048b54d0b41c67e39c0c71ebe8d93d67f7ecd9b3bbc140318c6254ca8961584b2963f2bca83c867b46b694f328e8c7a6402b652f3dbbc41fbc32eca39f5d83bd59edf89af304cbf097fda2b77ebb27d72ef7c9c38c524c7f7439fba4d8857f1421ef8f2ec74b25d06fdae18d2da4e672230c9b768880f8fe341c44faa83888ec472d621ffdc1893fd87de1ce4060ee5f37b5e37afdf5fad227f6d19fcfae5d3aebb33b2b9e1b86951e5f7a8d2c5d98afc38abfaa5dfaed66af77be5ed330bd2d61fc75af8f2032777fe384886fec235ed334fc553c4bf7a15dc336cf4db5cd73977ec22032633e801c41af8f2012bb3ee23b5f1708fce4769c726ae21df1fdb8a3cb5fd32a4a7d9230768650681d330fa1fc25040b1f2f2bb0e53819e9a8699a8f2247c8b08cfcba20b88edd4706642ec281eb9d254e0eeb7ae471e1ebf04e213a595e0f8194980611ade116b90f9108a069dd0ddf57b0dd4162ec9eda15ec372584337a57b0d9a78410c2ac218470369411e9ef8285ffbaecf22f71e66edddd36ebeedeb2190498bd0f2184597e9ba8f932e7dc8106000fc06bb0514353386ae5aeb1d316bd993b0a3028c95df41aff784772fccb148260458e570840a0d1488185ef4d87488370caee6e8bd45300e5eeee9e32c6c4c4995496b7eeee9630b08d7d3492fc804681902a78db51d84290b2a09021514da7e4869c0228ab846b5860371067e2e171e20c4df4e05519e64f468706f302da8364d9342746310cf380c019bdc982655858f089d82b04bb5261ad15ab87174837418a335be0479ef9b94d21996cc43c4bf70d7f7f07b18d9d8795e422205b44f4e6514e60e790a833758032c59148e4c178628c31c6083725b496f029f2018fbd11208d0862fb4e6cc27e91076a10a7b2c76738882d0e49d1cad8e3cc1cce66346275a2146c7cfd23ce00cc1f95d8b586339e60df6e77967f453a1669640552d0c91057405a0106223a39620dbb189cf9ab3ee283cc8c1dc38c88de3c8d94c2d94a944c1d29298c3a3b3c30893c672a08cc47846fae07cb3b8c8343852776585d77712012419041100bc7e5382fc7717178533b4acfb961b99188bb474e47841c8f96d36b5a844f5d23f4dbdd3175c4eb031ede5410a5c797bea3744af107e3212704c70096a7ae414e01cd6a79730e60790ae31c216f4094c44abd7b0660ce5e23e32d3dbba597ba8b0341c03385100bc7053ea2a7904410cb883cce0b3ee286198258296ca474177c04968424969193b7b038c1ce97ae4c5db973c89d3bd12b510cd57d6641e6cc6cef446f422b8c66963ac212267e8aec767b7a9a379623715647ab112caba8e8cd883f1a18362961bf78abc3be747abf0c578046f6615fbabd56baf1f4f06218a5cfb0434b33faef26c30ad0c8df4df69e9f3a53c7b4448c31e66843b1c89361fbed1661f9d54165b97de441a1c1da4f163c87b07c0e89de1cd29de52831c218233c1dde53778dc0b35c24ce32f2d2b19c8bdc351695b7ca63562a7dbb5f4ede340cfb0f9db1e76478bfe7084c085f57de5c94a5d39b93e30d72e5151c0068e3462ebd466eb7a3b7650ee51c8eaf9147dd6fe6146c44f47ed0c8273c77a6ce144a7189fad947457cde99608b4e9e7df6dcc4990b70df80c8d8558185cfb2d935127e592ec272ecfea0c93106b119fb01224d9cc93e9fa3c6932b16dcd1cb6663a16b58a16dcc6b175ad135e6e98557942e3cca227a33ab6d141fe329463f9a4c6113585932eca92c8bf391888b900e3e4b92f2bc4f134079c6a03ce77c4667a6ac429e3a7205ad88aaf979052c8ade8482fda68ecffc9ccd3242cb9167b24e0dd041823e72c5806cde8839fff2010bff02027b09a5441b628071f61704cb7d7923ebb79d114e4a31ce112cfa2b23555146aa6472c7c3932a1739b0f0510a1ce4f8971cb0e2e5fce7498f860b27d44352d0c9b1832c2329f0e4af5b9c8e148a90bfb6425eb38b332f71669281c6cb08905808641e73ef319fa20530b862152e256cc8c109ae147477cbeeee8edd0db1abb1eb6aec9a92045a1c4084534a29a794524a6d421bb023b431e5001ada90d25a137607703d8bd08694529a8080958680ad602963ec18e337f3978ad0463fe6993b69638ccc119ff2d76519638e97dd0def17f3c92c718cf29ab223c45840712e8c5ed725e31111bb9a482831d8a4dbc69cb31fe1bcb00bfb81c5ae26f3921884524278c2a08497d863298310c3221614e3a6e85637567adfa3efcee240b32ba6db867ca984bf23f2d5860cc32efafe840328c5b671c1d8309e408c4e194f5e34db221c006cfa2a4b598c56c26a29533cc4c2d7c489d9b8a49098e7b5d2f6755b561bc7d4b87b6a95fc76a53c59205fb133407326af439a4ba73b2c964b1848d79047e44b7275bb22044e402206186c29f7901236d0ec25bbae086722f4e0fb82d73c84104208af39bd6c622f110ee03ac5b2972c6799e72c4d6cd2db7962d9cb8bab94c15206e11bb3557cce2cc9946491bf4e52a4a40a29e418e42b265f57138e902f254ae4ebc615745032f97bc9c1841e26846109567ceacc107476094755c8df96278aa80479526188880b4b90420c2588c00a4ab8c2da2fcbb009f644470be2764a5089f800cfb682522b49ab415675404b4f604e10ca86884c70cdcc4e91822ea080080f04996147676768880d44e814a5d44026cac490813de58e420c6c802164b5dc518ce104558ce1055f18c30c66f4d82c771463c88137033ba99079ee28c850c5fcc001e2cc67a3a60040a0ae6ba27e20c08413af28818040a402851f9ca025a60d489aa8ec0401c8058c249028a11205105c40052888a09288a0029311b2eeeef6011172f7690ac0a33f1b675266314fb0f0fd3883e7212b7a727fbe5c97854b50824496f88978868e872965747663d735a38c538832c2d2a905c20b4613b412b029ffcd39273c651f6d62fd9a98d1eb98bcaed933a31836e7ecee19e48ad3057d2161e1e375cd6be294c472c4a2db66d9b265777777675052011e368700982057a4b4616268c30b754ddc3171e144cd84104a3803f003fb6519a8a58456286163bc1a9b33b330c88e3dc3b28cca2ca318c33d6397638c1d73288c4dc4d87dec7336f6c67e659916331a23ce6a60d1314a6cbbc61170ce6641291cc9f663c71c081f20e535e735fb03ddfd5386724af99a78c1fce564f9a923c2a3c81d68b2bb5bcaeeeeee8e51cad9b108730c5d843093ff72329c17964062d89c58b7a9d492177627463367dfae715d18c5e6293ebc2e2f6bd8d735fb7a4d2c7537be2044b141264f39a544a26577c309610732ece779fd82311a6918bb21ceb07c733912c227c42cce5ca62ccec0381ba722ee5cca16e83296d1f79db3c212cce095e1a71c81d4eaadf5b262b54a292fe7155a8c51da07d5a880200806913065f6f929bb67e4bff92cc64b39e79c73ceb89aac289b905168cef63c19191a1aa12c7dc83e64d3eeabbb9f050dbeb24bce970cd3c83db3d2bf790c675225e72f0c772b20a96ca8a46d15753d8d3a4533224000008314002030140c8804a3d1682ccc13416e3e14000e839a4878569cc9b32c88614a19630c30400400404000406466da00c0c436764055e2e393cb7f7b1169696b44dc3d53a14306f060f1015863a2d2a8e5843b9ffb1ffa8beda4fb023d1822bb81e7dbad20d60725548b59eca7d708e201e3b7810fef2df099633a29698dbd2f1ced3defba012367169b549e4d97c89dfa747367248fa832b38c5672abb70631dd62fb7c83312f247bb3001a6313212385d857242d53ca162676cccbb2253d5c5e75268c23f738cc0ed896aff9c3a49f7b4211fea5dd17ebcc4092a25af46e284d62ad80824a16614b6585ce602bf3971b0c8d51f74d0ae44034f3aea9ecf41d62ad7d8108397e3277bb31d0a1a1c8771c6c2e2776520bd4328b64ca5ef0d7aa8ffc40782d4e095f26371739baa153e4cfb722c8e14744f4cdca76cbb26eb4068f060f03935dc6e692010fe6aeb8bd090278383d7c603c47eaefa506271e63afa1838ed54ae28f32a6b9064e12c10b0b4488bc7adead5c92720472bc2420b75c37d19f2ffee648bed2445cf523c8b70c463f768135a76089b243a87348f645980f14e377a9cea930fc076a81638f0d0d90086e1d54713a8721f80ec81b7ef4c39bda043b160e3243110d5424069a3480936c7a4d954cd512e0642e15543b42337a6cbdbd8b8d281116e2510fd134f9c9f57848ecf632a45b81b40d5be89fc85854397205ee5d25fad1ae49424ceda8258c431a9d380e047c84615b261da9fd3d61af153243bf0aaeacf3c6b18225e8c690dcbe4416ffce18c230257e1d8cb20e0c3e1fe18fcd421986c2399fa401b3eca1ef13d0293c667ddf4869e7858bd67ef8bded878b01c03731c2ff9be456fff7694ddff5f69e73e8a185cc91e73b8fe6a044ca8a7ca55efd7ea8f8961e72e36431c4611d3fada9655befdc3426b1c299cb0487195a16511d77fe3b83ac56a9502e95d8a00ccdb971c23c1dd65823ac470d3418a6fba0523b28c099062a9e98893b143d5a8e1e9b0f2478495e81b84ac1722cf92302aa9269b36de546705a4bd228249def296ff7fad39edfa4317049cde183030ff9e1a30389330ed683ca5bf18a1f1c5d1c51b32529375d164e4ff80208ecc4ac8f2b3bb7c4420fe3904d931f937293c371d5466e0aa0e34b00d6acfb4e0c58f7f476a28e13f9884e0cdee4b5eb55c5564bcfcb3edad5367a6a5797caf0956978b41005a2bba3275d275c5d210e7f919a800383ae15989db5bb8c2f32433a440bc8ecf88a61e53b75b7ed8a01c2e4df9c4062b1c2b6fc48f701d70a8a87e34aa2014adbc13e7245a93bb0c2a6f41108779272ec56c733252b48e88ecfc0c57e7b989db3a5ccef273b165d5394961537143bd85c1bef0447647df647a9ddb3eccdf51f021bd69a5c4814db14a2df1b3986e44abc4dd8c2e9efc10bad6e0edc875bbff776c47ef0070354dec4310362cd23194b7037e939e3c6dcfda97aec58aeadeccc3afdc97f380e9a284383367c04da44e6f14a2f6f6046bf80c9598f35331f1a0e5113ba8603c2f863028743239e6fb643fff67e8948545b549626777658a5a50cc544b4e1639e4888859c598a9279f210a81dcaa528ceb010399ad36a9dcb4dbe86e2dfb9b1ba3c9169a3ca4ff1c4d288165573338e200b8e6140be97975c0a4a01d11b881ba1b0688513c657481b947a58c256a855e657015c8437fe54329753b0f14ca5dc2466031d3003d348ac380c969c5bb6fb579cc23582874b87202068b76b906831396ed9352a33632cb81dccef939a2960823a5a8714fcf13ef3f4521445ad7fa4d496d3dc4efb1c7837a2f9ce1089147a7b683113975e80d235164f2c02992f69340182eee9aea434f4c5b3f53f804bc98b2c1e60c6d0c98471c064e711a9cfcb5111fe990de1f10265f67d28cd21a74a4a6384d49e53e43b191a7e781f1d301129f523d964b37957d8e146ac0e673fcd126e69718fd540016b8314dee79fbe432bc35daa3f3078388aad066ec691bd0709840ed1d930e67738a2b355b4cfda2a494a9cfd26c6463e17aafcb5815b5b48ac8445e24d5f1cfda0b9ef3777c0f24a2f58cab0e4e63fade0cb9d2cb593caa285d84c4125bd80a1c6aa230d2c06e34a2c78fd74dac4733a1287122223f3ce7351cdb88d925b0edf02711ad3fa38f264d2f039299df95d04dcc0b17dcd11f1149f079538689e70eee6257ba8ba85532a9afb879e765bf9c64522a7f5aec4cbd9d8bfb1689f41dea405ad318c868aa5391754a78f8d35b47db53e8ecf87fec02ea293550fef24dc6e0cfc11e93b502d626c3afb2e1600409277155fd28a69bfbf18f2a09f67cdf609f61f8a3c890673951edb4f23d78920cdb9e08b157b1c168f45ad2f8cd73931a232c2b62d5c4cc108516dce83a4bb3260006e8848136064b63b69c5e8fcb9e3afbd61d9fc4623bd2487b5ab41c337246d643af851320faeae845109fdc4600f216fe429290eddaf6aeb6a8648754188c8b3ecd379e96ded7132c80288aec874cd785721725ca353b4903ba297d656f76292b5fa5f4f9be561c4de639a6aa0054f89af41cb7b338048ac33e7d8ba371515f93c4badaaa7d9222ab3d6dd2906da792a2769d17164a3ee090c88c545a3694bceecf4ab1e484acc4d232c63fcee98c29cb492f40352662cc0147d07b2580a19392e91a0dbe45ca52a0252f0d1fe4c976749a875e91a9824a4ad32df1acb490a2958b2b571c5061bb57b32665108f383c4a7d463fe69469322de535b6436ec0d31348b52283e2ac868bebdecc72e517c662ea1420e2ea0be47f8df233a9dac41e771df965e986cd3557f8ccfc73defadacb0c835c53fdd2f0fd4b802e585a67f29d292d08f49912853080132ccac600496561df98a1fdb943f1a53bbf403c907e392a3125bcba002b1581853775d46ad8a84d89b9d0c527f885ff7a1bed4a09bbae225512e8eaf4bac07d6eaf3db807dfa8edbe84036738d189bbb8e0b83da99150ec914475556ce094223ddfa764366ea61d81ccf668c9a81564ad7c843654ef76b2ed5bd8407d61621d86023f2d32b1d5b38784d4179f1db676e9f6f96f97882a70cd33e84d51da044ce43897a29e8010191dd76e3020d94cd3bd912696a4b2cbf873a099050f5050e1ed2443331784cd4da9d0dd116b5681b20de4ce509416829d191ad8f178d8b6a168a1a68f7a0a9d509b59d6cd9d732b57451ba7d59caa22d1c723552f4246b63fd06a5267695481dfad90f4a1646f68917cb45692155f4558c59cb9e6f1612902f88593d046c10915ce832aa5a74a0cf22c141fd5a75c91e608260d02db438b5ae0c2d453b39ad74716da369e58239773c59fabd7cd27ba89d90a52ffa0c3b4624ae55a9d99aa145307dc6f5675e79e3558f1f8cb09086e9e61f976c3f7f9de8331061585d02b49e0961dc181c4d8e048c0529375f4f8395255be020b88ef7e248a757fbddd76ee806ed79c8533fb2f1f8b00f0b42b8e62e22726a128239fbe2fe73ffad1d0478714e044a519fdaf4e4706b801dc16332d2184bc824e9041b6a6734ce7585cf52fdeaaa9498aa3fa449102819f0005b45b9f6667f4fdc400bdeb3c8d4f96a8144484ac9b8192bc7c2446cc3f370c42559c58fd2304f901cbc2cccd89bd5c4db98ccf407e05db64ab4c2176dc67ff2738b667f9706aaf55578e3980512f3c94e1c83fdfc096ca7b00ada08c4b9a7f2981dd8c3f3f323baa278672bc14f3d8d8db265e2b4c21815a78f7d19a500fc55762e262930f4b3128e2a7327b23dfb944c190fd25da2a7a6c2f28809f1e7390bb4a77614e8d9dd4d11a9c4412a6b06f6829beb8d73898743c877264b60ec764d5fda11561eb330f0fa6fc9e29f3c61444838a18f3b7b598987bf87f83cf8a0c707d900fdb815b7b5302e2e0a0eeb5c011cfe26a6475f8608b5ece79af3ea01222d31b9b3853f20b4569878eb9b01778bb180136b8917c6130551a2ff655fc189b32c56875b63e8277465b8259b2dc8a724bfdcb9645eecdb04da3c7ff11aabfe6a5f52026c4cf1978ef96c9f31a56c230882b9a2feeca8b39a187e28e1294e2f79aae36e05dd3865255b32214d4ec03e3f435724c100194febe1452bdf276310313add860c512a98e9de30fcd174571a482cbf73c78e5c08b4f9c49db2bd84bae61967dc7b7e7e807d9ebcf9af37e5c1af22459ebc14c8b2108d54b7fc92f0e415b25d293d9c98c921088d0726366e4762d5c16b62a72d3e058764ec60809aa67c49937576038955367aac259476368557b563c9b418ccb7055523bb40ce828e8ce4ae77d8b8e9acaf4cca4f081361b3b084f4f935099791bfd4b1751eec0aa0d1a604816ccb54923ba5798186c0690090ad100ccab762315eb4d0ee2fa776a9d09933c891169770441c83ec869ed7745f9ddb91d665069cce12176d12dbcca8b1048224a5119d499cd0082e0b24f74a6d6cc4640ef820b6ab6836cd6d374099f74421d39336dd554ac6c59a6328d5c4ebfddf7e5a1aa4db59b44912b25d2b1637e8a185ac0fbbb455a2cc51c3d06059d4d3bc9b72b419c3527e85cfd0ea43c1963f023d279efb094fa4a962a75a8812a24bf50fd36234b848566b364e135b53b81e8ac88f11e3898cccc3ce692e2bad8a36dde9b7d61a6aee17a0ad9ee49284a283424f4d9130023ed4858813e0347fb41d54f96f7986dfbf09875b67ab3ed2a2c0b4e485391f37334b7280ca971cc8638e572d60e46b5fe4ba501c40d5b777741f316c6fd5c56704405ea6bc294616ff95d3ebfbed3fdcb256d56946b6c04e058d732bdcbbf14c4b2b5465581d70a26202dc5d50b49f321dbb18bdfa69a0c0de8227873ee9b67647f8a3a7bf381295f219414cf7021d7b633b7f95b04f43857ce6998beab7ed66e02ce435f378ca9afc2d9c97267c9289dc9ab53f92b4fe7b2b506ce9f49b073d8bde4c82496ea32c16c043674bd820f5acb061b56b0edb407cfc93423bd161eebcc7be83b4127e1fd4609651aef10343abe834f1f164a3c949275cbd854ab9e294e231946bf9d5bf170a3ee72466d9a6562d7656d0525d3209ec8d839fa8834a3af685010b97a062ff3ae0c1a80a111424f8828620829497fef4953551fe52ee21f884a3fd53dee6fa17644471f1c59f7f7e01f228baeba95416b67d24cc975394a85685ec21e576c7446fbe1a4a701dea87f7381a10a929fe6a751e80f5d7a679b56dcd61db6553bd133de15a585c1cfc32babc38e429df36b9cb1b3e93c10911a0ac7665edbb2de33cfa3b101312a0c8b11e14b129197ff0eb9f121a617c44a805c17b44943f5cbcf4c288a5aa32ee2a5080ff8b943be8d6e0a68ab835a78f68c279e43712e350c34c502ccadbb24419da5c3bf22bad29696e0f3854772d50f7968b9ca069a2728d3905fa00329552a684e4e9e5f82d55121c1604db0ee006f1e0de516ab1d70bc0ea68472480b58334b11f2ad7a5ccb5a4032ba260cb212eaef0be6ddf4c95e11c346772740989e9faadc8f8c11876f861e512b894d47a39921ab4c6460c6e8bebc09354fc804cf9b59bdad3cb94ce9085005eac5cf49533945096844f8a1113b7b4af69ee9e75f7d0099d6e75bbdd39c240ebbd0213f73a4df4cbee7052df8dc2cc845f49324955886d000e51c89c8bd9a78c043fd1e48941fa253a1d8fdf06e00d28860ba268e24d049d96a813972283575024338d4c151e95c2a4510653434596a7d1d305581dfb7be6c5115b9a93152c70f647244f735a9298faddff763b70bbfa4d5901bea34bef9f4219f634fa35a93533000336dd0d99116e4d3a23880084464052a2ec469d321ece80db6bab330ab185b32a876637306004978b49bb01081d3a58ec5c3b2f9b3b5db17a1a4d2e9c9236631b8624cd2862fa1c324217ef737e0e46cd79c08f67b5f39db24580b8716bfb096bc790fe1b02ac658c8a3c33cf064d2ccb50905963826c6850e3a4117db24ee9b2bcd77171dab1bb4aaf63ca9847a0e70cdfdf3baeebb801c617eb6e7dee919eeebb194b238fc8fb64705eaf05ca0e8fc4f56510939759682aa7d5277b968c89e172ec3aeb851a16f05bc67c4a6715cd10a8db04dc5deb3457c757a65c8a3c667c5e3ba3c81ceb5036cc17bae2a9324c055af9e7234664c3a306f5450f3162a074bc5048b6fd7663501951c48015fc300a7a97dd7ceb05a8045ad549414d086536d6ba4fb877585d07e99f7106a36e009c422fb77d27df07a1e93137268b1bc8a23be72272f76ffb3a32bac5248c7ff8fc7ad3d146e27950700e3c2c59aa1f5c4a0d083d68187521a83e36b473cd5005f19b35e6edc23ccf897a4c3fb93f859dfcd1f0f26cc45a40859488c8c4a11ed9aad3d6cac4c0c156f8332cd24c01b1bde76017e945da6bd1e13db405e8aa020319a69b4826017a36a4afa4a00d51b3705cd4f3022e88353c8a5d0dd1fff6dfe234ac3abf1e9156411e34def8a982a74b9911909931e5338cedc5342f31e5e251b9739911c2954798b5140edcdbc72c2db3d9802807395a921921326292c6bbef0c4572a97bb5d7cdd5049a2294788648820a1fc9bf143429fb3379fd88c2ca5be114100b2711ae057f598cf75df8ef1c38865325177b55648a1d15b7a5252d04c2edfb49079b9a3b4046b1e1f87ce526545a018a071aeee55747c87919e11237180b5ec6e02e41a2e5f77de1d43155514b9942ebbbcb2ed59a8105e89fafcd38858f8ae336a7afce90c14e8eaa874e40b64c03afca87c1cdedb4261c9ee486e88f09527e4c24feb0924caaa40831326cb457a85856eb55d282780f5b939baf9264cedbcc435326cbbc3b661ee33848592d6acd4d983c34e06a7b5bbdb4af61f1caf746cca12648d9da68786846bbd6e645615496d98242df15a44904dbc2e4721600338967605d57fa23233cd0d4af629f0ec9e6ab90fd4a7412817a33467402b8be0161a62288bb95c135cd23402a11918fd250f3d6d0904f1569af6a54cbe389600542e2fe0b3707eba68556a9c522e8fdfbb31b6c46212d765977a0286045fafb3918d38c38267b0e3a972d811264401cdf3586844982dd59af7e0058031992c77450b0250bd7e5764320797d48a0f3f36030e45c1a21e8be1cf44814d080a7b15e267236f4740a927d028e4ed0c97bfecee88272bd827341c703b1d4c857cca9fbd3d10a96b3e6f9fb1421e1eeec91d1cb30cca8506c120dc7a6c616e370138a02f2ded6940db5c1e92941c416c2f2af7d50ae5381fa970c8a0a44edc0fabaafba29f38c902a34f9f523050d944688ac0dc001df3ae96a435fa621cd81a601965f2985b798ad3243c70765eb68f7f67e1584400b5d9e2729315e714ac75b0a0b32c194a75a3e9c0f0ee1acbdaedd40214324409db721cbff86ca6887b90b800e7860b9ada5e3cc499096a954134f4e765f6a58b33abc4dfa1e0221b46fdcd08adfe3953393f16a8a2a117cc52bf1d0f428209ba78b10481c55803e95cd3ec79099771ed3507e7b5f6551fad4b49f0f7a32c71a052187ee35c347df512ebc40bb7ae94a2389137dc510b694233b731af2a00bd1507e07c441d9c2f219b02e923ed5e5b7a1bd0b544e226474274ec8f35a85130468320076bc9ce0c26dd033b154356d9bb33d46c556988a5ec27b97de18f4915751cd18aba8dee4a73eb172742819871c40f9495d1121f4becdffbdb6617d1abc3152254489d6236309efce375519506ab2fb31c2448b9b32d91efeaf93b80251915292cfbd573dd5ad75726989e843cf9e3934eab3bbd7adc7087be75d787647cd70a5b69ca3337b327dfc7ad5569e23663ca5ee844b7480054b31fe72b96387aca843c0b59334d599441dd664685b56bb9f4db46997cd731d2da3b0089ff13c11b09f98e135666da5306a248611337788444c59ac0aa5c614035ff50f8272cf339acba389309006f8c477867590bf080bddf6bfa523027ab143c68498b11728afac88f684ccd8e04806c91d9312fe8411b6a4937d8102715a0bc838a49f1d0e0b9e27c047a1cbda06fefe380392fa1fbfedc21a0651d93b20c81a1111a7ed0c44cbcb8247cb703e575646a5241a663350e5ee0191bb9167a60cb71321cd7211ea634b735db159bb5b420b343d4073a333091046c703e45a5e43fb48b98fbfba7761d74205c68f733cc25bd16db1a4d633a9cc89d0cbcf3058f4b58e231da5fe8de53672689e03de0ea79afc6538a62d36d729c33c58ae66712493a44c9799d4016ca6e249e2dded55ee119f8d43ad1be5920f725668c8f96465db3a1c9ae67468b8fa006363d6d2fee85ed9eef9a113896a08d35bcf3783d121589757eee278b94e71e4e94e41bac9c0e7bb844740e3abcb211ad2060947948086da401d702af6dbdbbb18bee1375ac4b2f088909309677193aa468fc3125f3d6170a1afd7fb1e551afbbfde8f61d0b71d00ac1fe932c08d62382c1f97af39417f7cea6981b125f2f3b5f7369060f67adf4dfabd5ef3da7d1eaffc64e3178c0df4958c132b673b7e24dcff797937f72567034de94f3318ba0f66c241df9003c2f09f9f71b71f7974fcfb6e2459dafe8f2135090fc1cc1468b449dfc2e5b67d926c0af2be9a478da0005d53f3fe8fa8356d2470d01185b0e45067293aa82fae2ce8445117af79631f84375d7b4f9f4e7855a9457d3181c3d0a6c13dab0a1a41c8aac32bf5c41864cb436c97e7c739d749b124941dd404c9af42f60f4da322fa05dc5aac283f9fa612567b80a664763787b9f1761fa06ba811147deac1207d0c6ef943c33d29e9a031bc144b10b32e30cef6b7d5c7ee4a1762825590e05f6b3afd1212f95458bbc1a545cd66ba2adc3b9a55a2aa3557770c610c6d051239da83d277001dbbf035b77c7ce6da840f04d43030a3f065eb5f74a0f70b78f2bdc7bc9785c5b1369c9115d790cbbd8cad3d8a23d1efc6db23b8faeeec31c15489bee096ec7381f8729e6886be1837658b80a135c4ee7ffa6d06c6fa304994bf8ba9c4e7edc8f50836ce1e1c3d386a1c6edfb9ee95e5b1bb52dafa58f7be9ce5b0e882195125a53c4e33e795eefb3352da1c20872bec804997f96659f111ad109435b95773eb88bea1c124c4516ee559e7c581d9b1143ec5ee10f32c519e3227882d82c4e74250d429c23b044616e0a70cdfb63b616562f6231c37bb53a4e4206c21ff4d9d1c431a65963fdd765f068daaa97d009778b37dbe92fb13bb0e3ebab4821769b56b7347a32d1f0631672ecdadb753719d6a17949fade60ec0f1d942bf35eb5fdef580bbda9218bdde66519d8fadae2c4f36ff389b5e8bc79e67846aa4faaad24854e95a55e654cc4c4da3720feec8dca98950ba6598e9ccacaa09a404d52945f55248e082d366abd210594afaf5101cbda6b4ea4abf6e3efbece722946f79c2de62772118514a3e5dbff9af6a22b5ddd7b152eec6709d74341278a99637a94e297d4cfb40c8e954064fc3810ab0ea6e5a2b8d77f6fafbadcbf05385f82396fd07a0441b857f752c696a469414dc1f63fc4589df6a018d5cb6a54bdb3c59219ffb3570172b02fdd65bddba677814fb1dd29ae243a7f060bcb25b7725286bb3745eb7dd95be95bd7c47c1aac839153f2e480b0b1ff021e8f4a4cf3b34e5978093a48691fc83150f2e875c0cecbb37f6381de797e0ef00dc8b883d25f9dc24a2a034e05e45682bdd7fb3c865a501f63e52af077a3ba2501270af23b49502945e25c95c44e2a72753a338155f4aa2825b35ff71efd112e499212e3b7a81d00693c768c07a13e736bff9b4e35dc5accc57a344bdbb776326d4fc8e870aba582bb7f1ce91ec7993d40247a9e4491a9ef95a5853dd4551a860f1c5b5611e2d6adbc2aa5c6d152e5a546a87f6a3e5a5e0221b658e30cfdcd11b9d873690b833c345182c771b0a14f357ec53a0d611649b93af066f6ee3a460943cdb21f3724219771f1977b5f3846750f6424112a885cb10b10f48ae93756d470ee3ed54ed81ce8cae6c3877db6641b0643689973367125ef406f696eb58c67dac6f1c1767d4eaa82d0f4d35236c998b3c0bbec8301ef4871eac90dd66739b2c73936dd6eec169479eff6d8632302955ef3e01e20193c49115aa31773c625a1eab0d69c01fdbddcf56b6f6a5a799d34d86438af30624bee30d6aa1498514d436a73927182302d2d4fa426634492cdff758548690817c85aedda916b44073f881d0e1ce6aab2e7aeda4cbf24cc324c61569186dad962ec6de7bd095ff7b0c8d52f1b64c9686f70c73a2e3c6e0e29c283811b93e76f4579878bf19a1a65cac5cbf1dfbd53ce1621dcac6b4bf1b5143a90661d14ea2287de3c97d1066a4d405e76c20e8d9ce6a622ac9ee42d232c60d2ea1f672a24414458366e6e12abcdd3918c01d7e70aaf05ae487994a5b84fb97becabc2e6a564d1c0638516c384cb27f148d497f208142b358366adeaaaf229e360b28779999395849d5fafcd6d89d96b05267ada1ed91f9bfac8985d396d38f464407d46017dcb4867d7c5d1ec491d235a0d1c65233840b9f8bdbd237097aae9ada2618c883db74dffb7b2f6d0adb19c76492332b6e8c80d8f3aa6c582595464cc0ec84dbab790bfb1de86cb54bbe24e0ad25b19ea57cac39504a65daa8791c17b2d3479dd373ea8e9f0b7ae0388da35b29953c9d46cf08c263d24e66a91195dc748a67ab3ddc1d1b565acd2a55109b199c553d040501ec9c3c101470bcf49e45f9b40088dd22a0a40c2895f8e28d58871779e145562689e9ba8893931faad1ef3b13fbe728d11f0dbdab3d898ca387ad000b817166914a4dc89800c81c1b526ffc2901db2bfc615a645ea6d5d49ef7728900978c9ea1a02ff31545255ee28100e4ec7053a2631e946ea050df4559fdda37ba1b8ede8ceb446ee7e24ef41a7d0c7caf81b65b67262f7842ac705fe36f803b11032c3f4eabd191de8bbddecc64624c49bb0c098b73095799d5c69b0394f3f884c4be4bad81d892de9e56b8562be2ef4d20ef440d028a9b13a11cd8fa6fb9661524895dd322739c762bc7c13ab66d7912566709ab1f522c406dc6e3b54549b86a7b29fea9bc4604bde5d1b4330817d0d7647be40bfd5030cc6601df002854190fd186ec7ceb90976503dc76238653577055a4ad1034198b32d30302fc9bb6560af0705596ca06b4b6107c5003c225bccd708edb6cfb9e84defdef90357990be679485cb9f112cbbad0c9513d166aa8184818749807b2665e0a08234a81b8d6ae0558c251e684d3c0897d532f09a40b1eedf699639a1558f6c75e3761350465114369edecdb9e04f2304527a68183230327280e61d2ca7b0619efa07e98b133fa0b860a735a04619e2002e9e9a71732bc51bde299beb151d4d5b692711a827bbddd1e43592bfe844ef243065056e7f40ab7760504c6adb9d899c2a601eb2e44dbd635c1b051a8ffbfb1dd8e9029c9d15a4fea5b74c905cd615ad9f8643a9af046d59dd90121092022c0aabacf2a7dbcc82250f451e008646d51f0c655a6c86796016fd266ff7b2ce86f13fe6154a4665c7333fb049d6f7f9198ebfc7936e74340d74c3e745e6149eb31988b046f38e522b2422a6bd154ae0fe6e8beb7aa4c816bd11436a59956eb134426ec501ea9e631aca105dc86c151c0aefac881e0009149a496091be97713360f935337768040b217d9c407828882dd48091779884a834ca2999c031ff13681a1884d3a90819ede08e4b07c8fc5747144c598106e02d7064fe5207613864ab49276eb27a370b197348bd556d9881174626102866a2f862d6d6c10004f2ca0887d7f17f17343074c401d78ac8fd3f47ab55cb19f757a18b1e33b324dc8894f0307fae4575108d37cd7a08aa9c157df624a116a225a1aebb0fa04b38009a9cecdb28d20df2629a4346f2b48fca22bb65573bd28652d02e4709b663d7923586ff586f131a1c22f29f5303db8e5828011dab99bfeab744e816123123b66bc96e9e64e758f805bb202dfda12395c902566c5f506626f0e2cb038a9c7676817dc72e1bf196942893112c3468e306cfe1c0ed432497c921c6b7db911b823b7ccf7becaf0d1dfa618accec50ac6fcbf48b2e01bd3d781a7a8d21bdbe54c612ed8c8e433ca4b7f8a5328d1dd9098ccc63801d171a0f131db61dc46878a0923693c19f599dbb418fc85c7c73b7e04e3ccf5786dce0e06e084541d9e1db27adddf10bade7a791511e0481bcbf048da90aa09bc7bd019eeb51748644e9e6a923a363e1bf5c6b537775d5e722058fe6402a725e61fcc89a6ff6e379864cafcac0c03a844438d9aab5a97792ef193d7d5023d706b9a5b96c865dd4873b350e3322886d3a512bab9f27c9fa91b3b870e26647d52626ba26b332c17a22be9d4add760e3375d028fd10e7a6df2c416119c56830b8e0fc91c6235d39a162a047cb692e56eddf63d7138b0e26aa3fc693d7562be780c432dd54fdd8e18a57146bda6c30623b846f82737d685b2f42141c03edea0f4924e55a45bbbb07031c9778047e62463849dc3a2d25f5666a2b502a1d3593ff07f04bed875ce1105562492876d59193e79ca4135c387d005cc5e758faf234c8128f740c35ddb4903e5cbd69150b9e5e145019b7448c24b12aa0b60c1c0461fd0db1ba51eb4a1b81aced55864b569fe754dc6418de827663bffe0b38aab8cb0b75ffba296d84bb6ee7a90d16ff3ec936fe547877db655672288790f5b9290ed4f9285947453ee85522da2e033a6f5ccd52c828803d458c84647e09ca9e66c564d46b10c85666900bc5149bc5c6468341b3f737987aa0b7dddd2804e9f5d1f0318d910a14dfc9202141cfd79a95aed2fbe82631584c8a44af2584d8ffb2a56c90fc9f1d97abd66fb02bbe384d339fb7baa271dd04cd23f7ffb3faaa7bc34843f0f4353f21d3748eb401ce0e795831e0db60024f47884def0f5cc6054fd99906ef4c46fb872ddf55d3043f4e6c23b3ad847f8232f7555efc0fe5232d7dc7c5b3dac37be54f9781e030b2d2cc1f41ca1674b116127a7ddc50febe8eb00f1d14298791c65381eba2fb49474523e2ec5307a31bc65171ce3f098087a030a79a70cb844e88d1213cfa58e54904d86d2e0222075148152a7d1a2efa27041789aa4f8eabfcb5a43f2f905a6b01200967a61d15f0ab11cc558d388fd40ed4206662ee52f41930ecf87a0680223f8ee0b1f197e21e1666d19f6f4d5b871c07a92ffb8b8e90e30c16213d6a8d2a4367b5d207fbcebec1842e5dd7ce59a9a3f330ace7e29428cb660a06b3436b169ec66b92bff32f7368608a25a3ed7f16ec4ae410111a226c0194e7aa3a98e7fcd3ac238f6f34f5fa5711c37b6081281de0ab688ee667448fdad44ee54da9cf6a0f6f94cd0ed114f019b5bf2f9e6ed21622f3699a4437136159275c437736875adc7654931be3bb41d2e753407be0984e923fca1b4b086a5912c24b104e922c7ab58fbc374a12d75be1bd2a48285b00365bc3692314369eb1d71b85c42f09d747ee0054c0d9eece12f11596e86443013058c81843033b96f2031e8c2c39611d1f6fb4d384109fed2869b4f5313da0b9800b41ec60907fc9894170486fcdeb5835960ee31fe63a0d21e3f8b1539e938f775b1fed43ca09b0d6f9056384ce40d992008ab0bc44696cdcb99d24ab7bc031b40ef7ad59c7bc93c47bbdfcee8f5643c0504dfa659d794fe8f8643a0bc2f3ac9bb99091af3b0ae2784e23489f71d22f42a09e46b4de865befa2a346de0bc882642231c1f76cbf6094c23002be31429d12222de7450de501cd8b2af9202750593bd38b6c35e2cd25fe67819bd523ca87e5c24b7e517b746a23016f645ec988393a89f1122b62fe4862bceb1dea888c825bd5960d4029831af2b9aa1075c647291690fba5f82dfaf332ed2540c9bf3471b8744ff79882d22b5ae7d318d70192b5a12c4460d46cf1af641a69975b7ff07d41ef2a65ff1e51f36512af357c16c414055e5b3eef7b5268ad64650e4fd8c19f4f2fa3a842d1f2cdf0a49005f5d0350141f9490a5e2061dd03eda013346af81468b1855f1b9718020d0d158973a41b38b270e3ced6b50c4ff42bb78b4a3361b121ccfef2472d4c32592a2a3f4155dc202a8e784b661a1a28d224714853636900f10c37a31deb824732a3d56aa2daf861ed337d7cc89e54b648a78666279d903d475f7d0520561a4a2e1c25ca1a11ee59543d90ee4b80aefbd9d776187edf7bc35b3df1efd2f0c8410d5bca2536c75bbf08f976efdbebdef84cbcdc40916b9d38f1a13f32ce1f679fddd7c24f5f926c1a2c0a5ff8f7a657eb0bd21f1a10a0adf82e62abd75d057b0960a08773cdd04168a40ea6b9c8cb1bce3f101b1fd1fffa4dfcb610905f8279b9d2ab87f2db75757ec60c93937bba28a78d31753e536f04f9e84d6c7411adf693c26ded1a4451d08afb978bbbb2bc12591f520a5d2bbf1aefba606285c479ebe5acecba1f008bc9b4afb3503739bf1e6bec461d55dfda827fb3195dab61702d48c505b88925e2cea0fd0bc86eb7bafd2f480562bfed37fbc92b2e2e3904b07100f8da40494087a968425c85bc743e011b137129fedd7752a66f1dcbbd975e6fb14a047ebad528168f98d9491fa140717bc20dbeeda0c0aa4ee2d0498b2e017e21fcc06c5a8dd63dd56c7d93ac939832189bb20e7816c0e0b3d93e7dd3cd51c79cc27397565fc31cdb4ee34ef9365c2812c341c70e4d312e0076d0154dcee2682a1ae2aa9db1881acd181954a2e1743661cb7a3f524a855c6f05e4629578c357612aad2b23bf8b8e9151b14a008d4cdf7d5574d330706ef158b04f941249c139d7b2aea6e5bfa16d649055a7706e8b883c0d64a3331d08a65e56dac6fd367ea257f84ae464b3fcef0555aacfddd6f6fe9dc5a2c28f2b0382069deaf7cae4c14dde2a4af2540967ab8fed78d2d377d70084c2266d135b61410e1463f212360c290d1c7c4792f721b046033d6ed7acf193405b973818af0ec7e026c80d0a28b9fdc09ea73bfb0d0fecdc347d193d107513115d520c0739fd34ae7525dd795d0a4d6a8bd189042303c3944dc2c3bb913d40a918722ad5f08777bfa45e90125afe3160a9cbc067918fff1847147324879610249f643f88086295a6d7ef1b1ba0c6999f4fe41f2ff9b88d54445b94f58b066d56d405b452b3b38c8f51fea981f4a21da56426c1cbd71cba4dcf362e432481e4bcd317b5bd0a67db67b0a559dcc252b459ea1847dced7c8c8b153e3abe034f70e2b29f708783ac9c8dc073240f49a7609662801c9f89c48d55772ba037be420b47a473329e21e033021ae4da13729695a7bb563f6c277ced9bc63e305ea3bc2b1f9c0e7227099230376bbad2091e51edec9e4dbf0d2854f8e75aaadf13a5adb51a95bc19cf53f1d13716084390d1e765f3d4b296079700bfd7647086e9e01d4e35223a303b9a10fc061962bb29667f31261e7d6d00ab8b044ca292b91ddedfbe6877d260096660e23c6b8c41e883763b636c71d787c019b3eee8665c401178af18a6403dcba1c8b6fdbeec3a0fee6b2878bc03f30a82ea62d2cd60c46524e445205c68cce4a49f145e2aea07be1e7e38e41ccf7e091cddc2ab0f708b9d210c8bfc2af7b4d9b7ab67205b53667c8e7f9274e2eaa3c5e4cff1b6d637b802f189ab81b1528f912e0f398a6e9b6dcfbf2cf968822d95a69b1bb98f6bec02f8b2d4dd4e5ea78dd4648d4e9835c2c5d6ea6e57a633033fef44eb478a056a485c7523348583bfc549673c39768c9e0c51649f6792be29a213655f54ee2df44610776c42223ebf6eccc4fa6eb7f122fab17d82b85941859e556ec947e5af5ed293382485a5dd2e8fe0e34edb3e6a0804e92f23a4bf9892102abd65310a2d65b1fb7147d5a29155deae468fb8e470296c6a9c55f04b2dc6d42ebf8d65e6ceafbb42cdb526f7b1af844b7185234b687b6fdb64a530c5d63b60b062db1a9b2445b6d424f563f784e0ec58c70ccfc6c6ce04c70d2d7e48767ce7ac6989679918446171e50791dcaa69a6125c580e5dcc4ca1bce6eda84dc3539c95f658c37075b16f6843b3f5d306cbeacd5ee5c14906f58afd327cde5c43d5cbf7530ac0d510b206ff85da670d5ccd1b4c40365287bd48fd8ea07d33241f7da4b68fb0b4a4c55f481f24118143c7a52c5ffb219cfdab489303321cd0dda1232eea3ad777538a4a7ceefe8b01e7e10681d51bbb283ced841ed0d0df633d40fb174de1397c9b193c0e638828e82e2c9a20f59329ca4287d61feddd39fc3e476e4d5800cf9fe1f2ec61ee4313409af24d344c38c57c74b3ca3c0d0dab0ae4e8b91c5266605797626ebad7ab3238d4a7411d554c2c233d6c96f0cd15fff9c548cb6d38a7633359294a4c2d203297e0fe8dbb2392385632e915ae5d230d9eaa57ae5ab014b4519fa4aaa953795f9385ff9459b05b7f88f98cce6821e3532f27edf388d9d98fceb5b92c3276c4cd6567375a29dabb52a75da242b7a314809d267959b263df1e6f373b9c77d8861c95569c9e9797aa02b7883e0027637e1051995b20aa1e33db0bfa6a0c679088da269cde95258d64874164f02fe2a716bc6805bb2b5ae2e85f77508f28d09e5a493fc7c4b4ff811d0b205399221532c678a4fa0b95df74ac3c736a31654306a9dcb4e9465a15adc26c57442449a8c83319efdecf6d74592421c0860b68aaf60728912ece1ab8daa507e32caa501484fed83c9a5689c2de2ec0af973caf23d907b403a64b6b1f3c21c2e12fb07afb69b89aa6b81718dd20eb03e086e3cd27a9b64ae9b033b693fc4e1592387b1aaf070269e2f716cc694246582a7a7056c2ff46985a397770620a0b33c81ca8c908f889cefde81ab7830d7162800443649c13961076ba66bf3ceb306c45cd5848f8b6bcb6495f7013fd3e84baad8350d369f53c105739851238a9f0c3bf7e03cde451595445093100e63c376aff60a216a5d51c43ec80bf1fce647237a7f22bf19c83199388be072c3982af0259066fe01afaff41a285f6d990bf365f5546d455ef7ee8aa0c71fc95033068d0fb35e36278a00701525151b92729f20716ebca2a2a91aa73b8b3d7cf1095874df4b74ecbbc19523818c603877fec492228aaabcb49cb0c5f8256267b76647002d288a0d23591f620067e987c241bcd08ce276a073e21b3acf61331edbec6bd129c2e7ea3df45be5808109dde59d1baa6456912390779193ef5177f0ff4e977552ad603758a1373dd6b5094a626b55de0f336a058e390abd183a49cca8a965afc770cd8279be614b1ab9d1e3cf458490030411c2e1f2f04edaf1ff1a33d3e08961988b2ce49c2b0636a076cec93e6b2c90520cdedac14c4a87443ac5cdb189a42223ee196d21483486df2a64820c35ebd7b680b1b7f6a3d5dbb5756a7ae7d79a82ff82ff440b4ca9afcc1ed44dab63bffc93a3ec0d87697d2290374b472749fc19a42ea1db1dabfc41cd085fe6d52a1a557e8ba08e9d01c7db50515c2a32e3c5891a7ddae9223edb98edaedaa01915330d2b84e2e27a2b90ab1098c149a81b60b522e094923e158d9fee0f9b099a281d6ab3e9445ef4f4fd062624736cc9e620d256743c62c0d387c348c2fb60453d74f1a8a6164cc68b81077cb0d29d4d2846ae974afff20a6eb3df9e464f66c47b4bea18301c2eac14adcd9bc56eb6a12c7b6bf45c0acffab7a7b1fe70d69f6ae5093143d7dd900ca021ae1c97700ffd1b16617b151e8fcb7ed40af480580c9cb4a4c92c69b383cb88edf652feebd84bcf52b30df8dc58f485d0e74a8b2fc908bb69f2271a9412c9077331f537765b857be7d6ff48e0601b03755eaf7e5d2434077215131d55aa3a361ced31dd9f7d055e6cd589a5a48a2d43b0121f94b6a8bcb8353526b8c16ad83f7501a2df9bb237f25fd05bd8449b295775fbb8b6abe34f0ea66038f385c2451ea912db50134c77dd904a41ab0c54c1173434742c690ed384ad073ddc05144870c08ce31fdfb5b86ac5fee2f9e49a276613da33d682a862fa272236ec96c36015446e356d7adf5fdc197d757b7d7258eac95d90481973785b6471ac802f726e17dc824a5860dfd60f98f253aa6bc8376c71bbf88e14fcff5f1b0f473a03229308410fec36fd004946a8b6808219e95c75fc36e9dea6cc242d4d2f076e3c17bb0e7602a8012afe9450778acec6ecbe39d865b8e73ae85f9f3c448e0d7d5ca2bba4fadaed67cba29b985e22a2442daad690380f751dadc931ac9285a6054fd05e01a8ed801b0db90749644673383fb808a8adbdda51000bb08c522fc5768e8566b4280b0408c36d9a6d12ef4bc0a31095babf53c64793a3b794eac375708c3769ca46786bcc2d90d8b9e2f8dfe18453cd24f1a495fab30be5130fa2e4a5b1d22256f43176c4a58028ff12eaeec070eb35abc8cbb5beea7a0d3e390864b13ccf18d74debecb0f12e4db6b1b2608da8bcf3c4a6c85eef8db244b691d5732cf20f0d8aab1f5b8aedbf4777b4f4801a491f40ac7f5735ad60c42689138cb2d648ca60fd67548933c869d5f640aa29fc533c928911e99d375fd8a7ebe28309376500c48970cd28c020c7f3a338fe889e1766b276c6d175e090263edb794ac371a1a22b9adda656a7d3e3712cf20dd1413999b0ac37f0ffd3227c40d136a78c74592ad3d91e89becd842ab08a3d0b16d0ea230c5994ac0bc829477fc6e8891778006c50533b83a68542d30c36aa914408f9190ee72b73379b1ee6cdb9d8db26cb50233137ab3cbaacd0736f5ac3995804fe4949bc0dad0e1108f96f39c07e1b63081908ebbf9576235bfddb489460bfb94217d09d176947e8d50dbf4dca0fff975254a3b275b297b6930b2d04d9115e6865208cf1097a0cd1c41df0f8ab22434398a976fe8872c37d4171202fdd969cd66959ff487001c41dc62dbabef24b40a14e733f824bd4bc401cf0961aee662ce2c08e7e71dde9da5e7fcb0464ecd7c7b652403d712bb9200dc6c4b9dc80308b91101febeeb9d0ee049af0670d0205307a09d386e1016ff3e0247e563addfe562bf8f1b589a605cc713459c887111c1a6bded18b0a33eb3db58dec051796312261028fd66af69e9dfb822439c9a0274162d61a1a4c72cf0b2208404114838055e53ff0fdb86856d3c6e4a28365056c9d28b19ae8fcdbad74e631bfe053aad406170f9692583f09452b3f298dc7cf2158b4429cedcf38619e620dd60723a056213103904f39505fe4b956ac0e7e4b4ce3735d2db55e2400d27d5201fd2bc24cf45d2bd8878a0838383450e6d2826565c180a2cfc76cce131ca61a05bbb11fbdcb8bbd67e40d668ad90fd0a11ac827a5d07cfa0555d2b59b8f2f2b4f90e833ff119da56c87108581c2d9b5a7a03c70c39e1005f18f72845be647e7cb63c6bdd8d58bced3ed7df99f355492a32ebe824ffe90b33e8bf8eea189c64038f7870ef9bd1aa9fee79a43dfb806d11e55e24610dd3e0a635a2d039fc55e449d1b62f22c1662f619891f0ab0ea2ff76653c17985fd1b25881be516a66449f1832ee84562b63bf104f8d327b1e56bab6d6e14ee347a64b5bdcb23cf1695d7c85ea181e875c570ab971cdf2c80a15fb49758caf03da703063f19abdb6ce6e4356fd1c131efc9b2cf1d3dedf968608391ced7b96ee6d071c6a8e38ae76cafc922c3dd99b7eb5c25da32c8892435d119e8533fc3bf65ebd02015b7013b82996ec98d18793a6c43625f0ff004762ab4df9c04e83b4768ba6df27a8b407f5149907819b5344f0e4a76c433ef88880293866ff353ff4f4b5f35789694ea52008808e815026fa225f0cd3636f65846fed129935f410b495045dedc4e2a706cdc292203e0e4188a27690bea5f86f22b7b5a857cee935cafb9dd50ab51b32c6ab96a104271a12afacbd795976718602098b257abaed5d77d1eef63650f2db3e2f815b83a359f31a74486571335467c4640d5257d731515bb0ded783e589fb8559e3d7f17f455115860d679930fd5984411c39c2df51e320acfb8c2b8249f0f80501558bc9d97e4edc268de6f98b60905f169ead6517061521999f68d1e38c251ebca319e222f8e48f084eacc6ce695f847925a83d2db6b75ac221d6e092e7338a703e66f5ec3a3a4930e1ec40a7805b167b1256af10622fcf44b11dd191f4f145ec0ac5fec7c99845beed924138ae664b56fe112ca3ff0a2212a043651ad8b579f32710de1f11856dfc12c6be0f817fc81a0e9e22ebc75806103faf3bc26ab1854291ba3ac0649a8aadb04452e18456af98617a3fd3538f315e060fd875bef2fd0f58f8c2bc0d134e14f71637cf880c955edad8e8b611258b9d0895cf2304e46380b28b1c598043e82dd57e44ad06bc617f78ad02b205c9a0d292e0ccb903a834989365267713e54690647ea678a0e30f8545f43420a700b965204ef96330855644eee547351a9153ddae1175c5c3a49615261002a89a3c7d28061d6870254bbfcf8c4d3f232c3b458bbdf452b4ee34f22b3848515cbb8621ae96977019660a2a566dd107b2e69d8f88b8b63a3a3db4c384833cb5dec81772ed77ad64ba5d2f574c2cd0f344976ed58deba32a0351fd15b151547da3ee939f239e01c7243e77da80e84b77ec27a9ade9e837aece030d51662288da86781348da2cf9e4d91fb1cf94d77fa9d82927fd506624718b8cb4168d64f1be9d785abca0fae1fe967e8bece4a59e416fe8fcb06e2a6bf560d9025878d4ec80b8a1a000f3460ca67d4d0f389276b674c70b4222c1c22c5bfbde3e51faf73e1d4e1457d17d911bd02e2e9d96571a739353a1a39d22247711eb86f688155a2726035b1db07adfebdec94af9755e8a48273d18c61d81e6f03afe27cf96adcada8b8e2422475af05eacefd52e17a3dda8c655a41d9b8300f1e6f94d59a743ec7ba5245af0517981f8d9e6f8baf8b86a0c82fbff35b84fc993861b390bdae49f3c4c73768f4106be0437f20d00e5b95a3cf611821aafbafa8c73e48c18a7012c4d20f6cbe87e71f5c6fab6e44556e79f75b643915068073f7a1d94604f7226ad60633ed37bd4ab16e4415a7b80d7903b448f9031760807a180d55a7f1854579b5272b11957aaf1612d02750c1b0ed4a59f6a0260da01bdc294e39416814845ccda753801cba9e02968325471d550cc2b2ef8c1fa0fc8700126355ab49b98e12c2b160ad383e59a00692599dc3a35e8b9b4467ad4f78591a0b8fae470077f55f1cc14fa24a682c61762b12d9f60de3c89110c2408f3a83c4a2a9c87d0f3f111a71d5a3c35fcd49da62b08414d6d34a28878cb2b1a625dae9d88dbb12b6385b1ce48e25584b561963cc230d98d1c2b6e31505279a1f0bf4dd9f44a5b50108abce5b4903110cb5afcd61c89c91f2ac4be423f7de443819a9b38cef2b501ad2a42123a943856ca3e66f327b4030242bd9639c4901443a99f54c2c08d768bd4bf755ebeca478b2aeae56c936ac081d81c97b3081a4a48d49978870f31f7f774a63f0102348cd06621a8465bc756d11f7edafaee277d33e7f2c35ac543f025e1db35138430a298f796cb30bf208294ef18e18ed8286233e477bd1e21438a8088e5b88fc664bdfefad96158f5f8db305babc207f361aa787711edcd0040de5d2b60d38652981e2f1346fcb1af0412113ce3b1340489010c7f373f1b4218e146858c017ebd9305f3524de2fcaeccdaa814c7dc88b7202fcdf6aa417745bc1ed88fe3e2bbbffdfeae08fc3196641dbc86d76238e96d608cc0444359d7de34765505d899cc0d538bf10ebcdeb8d945abe5a1ce710b676394190b80a76b7f6aa56ba74f2a7c86a6157ce8890a5dfbe0a62ae28ae7347f0bd44d3b67461b7d85a6739215ee65c9f304f1d64d34baed53a40b135f1080c599e30fd504f462631e69f743e766f7a8514ded78feabb6ca781789b2eb83ebe056286d212175c9e08c0a5f1991b14825c501f3e7b8b59e242a11768ce43902711722b383c7e61e150d4eb1dc3d7b99dd14b1565b2f98f16e59e3f6d3506d1ae3c1e4f6b310da49462744bf307a6b1d361ab45835316aa96ba83fe8e2e5d5e2d0b836b396ea62de9b5f83e1f76cc1fb7adc9c30983ee0fed148a50489cbd1435bb0d69d2d8ee98754690fdec4ad02506ebc30f255bb96c281e1dad74ed2a0ac3a477f597d961b2c7bd0627a42fc11d00d04e5b59c950ca94d00b64bc0945fb3dc5e58241b4c3c13b846b61424c6c86c751c55749fbc48a2545b6375a0b09f5ad8927359de381f0c4b1294a50d8cc73ba487915775640afe2c3cc45a7f060cb1ae44b2931ac78a2e5539b3ba79184d775a5fae5df3d433e32c312df49171bb3c7ff1206a515562446b7a5a0b399d22247ef327a5487927fccb78aee4d40fa26f7c0757bccb233d8e2af61d588da5a51f5a00f32ca59af193844e11787f86d1fc968412c1af469bc8cccbfd775580dd646523d2fb63ba43e608908e372216bac21f61bf3a359da80ccc9fb3689d5fb058f793d2bbde05e6f1339677b48a606868818db2ed04fea2c00fc730f03586e116eba4642c404df12f6a09879ecd5d620c7af27ddd8e5b72a3790c48239ab4155e147d0c26085b1068686cf17b89e8f62156670e25f1d1b0234a2c8c2dd532606c3f2b8940066deadf66f56a922b8c9cf60da212194af4316cd4536a0de98198bfc9388cc1b7c5cbdeda337e1ba003451175a7b390a11d9e4040894ad34eadf3117b6b3861d7ce0de1be0a2404a40f7de0cad44410e0f892868a392ec0090cfa482ffe87de1dba5402550fa24fb045ed2efbc9461e05b52ae48b35f65185464cf6b462e063f4f2c6063dffeb4848b50b52a0a14105f89f0aad3dac2c35466ca7af93e8723ffdf0271dfc73506cdfdd8320a28137221b7fcd608d32ac486c2909c1ac125b05ae3f11281019adde1c1329cb3df59849f502c88f1bf3b9dbe218cad2299ee2b5cdb14715068dd52809b001e98740673798fcbb4569e6b557a9f6daf4cd90b4b55f6e151cc2fd4b9646e97136b108fde318263696076ee49074466211ef7c49c13ae075e9d83845ead3cc005304d81fb57cdfe489f2594235f94478622c8bf8383ff6074a8daa64024811500fc192f8c1a925a5db63022e2821d52dc8c53e41c5134433693b7f67e0429f6d0a93422a684846bfe3e797c3dab31cbd0e6dbe76f6788711d335c3bdc070c8ad0cb4ab20c22102d82989e9baed187e4e21df4b23ad043d91383869311668ebba68721586a097fa504823f94abf8bee2c60787b4e58ed29486b2290f005ea5b32ab0df41b648a4b8a214457f8d006b68db587d26a474d55acfe7bc8bb9a795e2c101aa468001dccf1271148b7818fd7728108d3e94ab8d42ce7ea4e38e1e8429e985c08a5b5e2112f514f35ba2f6efcbc52b672a1a6a1d8bb31a3353bbd89267469059d30207dde74a498105c20be18992bc0304053b1c45aecb1e82d652fd88a57e6d875f16ef84f3e627ec84d1179fa9cf46e212ecb9728c4938b398fa82b1517b33f1940bb31e13ee69adc6d9b1ac1c4de8c40caeb788364dc5bdef3805cb6e2ceef695627b21b443c45613a8bb2f2ed5a0f2efbfc2aa0d428179a308318c8c7e5daabbcc30e7849bfcc11c8db56ea1583f0de3d322cba806c9249c5542b2c9877ede25c23475ab5727d3543e7cb7a2037b9356ac95ce4e7ba518385a5bb1a752e9abc41d73b3f36286c2f0599d3e9f23895ad4c5c4b442811bb430c470e634d0751a5c5eb0b4eba30f482f35b67acb748c7a7df179c043f661a2a1531e30fa6a7ce50f9c2162097b0b3d168abbec56b36d2889ab4a8af033097c9aa15182f3f3baf6508bb669e14dc0fc26055f4699522b318cb6fed27025105446dd3c9e62d3d796934d0fca083ef3e54123992cd5be88cb6aa4acb41ef6c20e394e220d368258ebcac7cde12b2a3231441db814d82110e9a078cd9c3afa8e7cb06bbecff84ddaba5e21d0a20f66938dbe31edf9d6f0b28b8bb04167eca3b2ffe703a230e8ddbd4bcbe576903cb061a907e2d01feb3220be336bf9640965e64a2d71ab29ed35b0bbd0db739a0c3a686f44b2d3c4538836f9bd32c4921b8867036bf9e0df731b46458c0b24100ab9daba55026c39d8ea78eab8951f7248d77b119191f91efa5d7b698de7e209089d1b452f538762a8b6936a7da646460e43d49d78424a3848790bb9c2b9bf641b1dfcdd9ab695301333bec634e610165af8860cff21e80f301cc0053cb26351fb4160aca6e99fc9c1717d635396a8052fa97029219c6570642c519ca55d40883f1a49b1b94e42d44da967bd7983474dae43a0d9c106ab6957f7bd7e98f919cd87e29f46b4394ae40724246f783dde38bf5b286fdaeca31981fc047567e7f6e353833e548ba1ee51ecb2cc6f575737568f203bbd412c8642d36a14c10347b3843afe9079b67adf8bfbd888633c531ce524aa8c423e4c106a287a733c2dc49892493e29a394ca38c289136c34fc730d6400d66e56f5befb615a3cf83910b1a35f704e7aa114a843f0bcd23fad81276504b8f31298046e11e91332dca33890239c82d924439f258e00e3f4e621b6ea4a645fa1c451a0383913cf7b9f070861a92b4b935b6a513803dd7e65ccb44ae88611f7af66ec68ce1afc575f9241afb39943550afd4e8cb67ca4e6c5cecb86de427b290579a207cc7d8252b27062dffab384ba338d4cd6fc2573f02d4a0421a51ed218b19f2f0fe3d81eb819f81b4c6b735a31edbc59ebe5eba4b081cd151ea943052f72e036b76b07a70455ea383f291b6904f1f9eab6c793b645c73635108abf378a9571fa6a505de763ec58f9195c4264ba895c09c3be0bee4644b65b029d48f3c48c16859f04251fd2bc24d5d83fede56fb05d8ea311abfb620e56b13fba59fef5f22c98189d3a94cbed22b0bf73396d9ed71a986d07bb75e56f6eb4955ac218b1243c205ddc5b6085da66fec5fbc41953b27b7b151e7317c3e6f9fa9006f0f2f89cb53092486577244abfa99065e705a39e4ae5e2f9182626c9c67fb0c3c17870e6a3a9514a789e90dfdf977865d22ce65218a910214de41a7894766e9d41b80aed92573c0c12e1da0fd1592a78bcf60f9f4fb0c5fa1cdfbc792f78f9714ce6cdd692c76e05684158b827c2fa26f03a9dcace322b61a76180d8e91063a8807731bf46b5a4f05db19373b88d85e5ca06893277c57bf2c1d781aadafe2256b599f2103d19a9ebc7c75c42aba47e25e31883f138f7e5b214a3bd32ac84c41ea1c60dd43cf2aa7402729e7f6cd68bbd47e26426a839c8aac6faef6552267d0d7894bf5e95618a6d7bdffe5c5ede31515e1b4ed325f5f1550f8886138ca405ef357bdc79bc0115462404d615762d78913c8f9292bc007c9f9abde4ff78a6cb6f0d2640ad4407602afd3825f50af05fa65db34e8c8ebe1460f776ec01e512e4b64f62b81b3367c1572b7489416368859071baafbce97d325940304d6f58029294e354f9e03f4d03a42e4cd0e30b88c61d9399fa25f076f9bf896f4733a986d1d9a63d17e26705f1a681a36bf5b9452383fa018bc48d01a3f9d8344204c989ad191c1b6ab65993d57ffca0e9e931f34883821825e8378a74e824e48ac94607602f5f2659d35d41682c275c861afa189b898101bec6e1470a82a16ace3c95a03e878d8b76c6dad9578668fea2d0952778403f082a683c5df519c0a2580eb9dde228e0284cf84c2936d3777395d49a13dc50d38bacacdf6cf2003d309196c92c17983a179777451abc874331d342e02c115ea9147a565968cd52749918105793517e9ca35af49a18626843b6f1a63fb087e3b4133f55cf2ff79ad6b113f40e04420aa1cdc447c6b3700b6041acbc2a17fa81712445a07f070db66f4edb4408a8e499fa7cd821908bab367cdbe5f235e3023c588d872f3f415ffe0c461d2faec735a339303c63e74f57088cb89775dee600eaf9a53e6c9608dc9a8467d8bb5b4eebd69c2ed440927c45337184dbc906963f627a13f13185898faf4443dd558ba30fdcdc4a6144333206c945d7db4d5d1460596c31b2c00e435d6326e95e239394fd8e9bf411cdadcf6f9899195b0898ae37cc25720ad74042788885fd66da309c6b13a0d73bc0678f0afc6b0a71fd7414e880da125f5158d23fb53567db4a42141a121e7b2326c50d3ab3ba34bcd2032ebd41237c056575d26c85c426a8ffb575054ea3173f1c6833fe8582a10a269bbaca3d082ca1a52e5553c871f0f82522deb356c416e88538dad8e1b4b912c176413573507db238d1a725533db557c2285d9304e2b7d19ff4c4d24fdb48cbd9b6f19d9194ce5c3424431c0fdd583ab25305ae6cf6b25811edf0f01f7224df67873b293930dade3e3672055224dd0f9ed455cfdaf041268c8f49a7beb51de0aff27365b215a241d71f41174257eeaafa0a68beab5a9ddab171a9db0721ac17aaa90242b6943c394feda96730f8f85312d12c27a7a88e8049486f162b3ce5b02111db0fe360ca416d27796f25d9d2e78ed1e5ebbc0a20b4083120865fe96daf9125256bcc530a291541fe87c72eab57e15c9643d2a61e4db5cebaca8f52f9a85a0c78a40ecafd1526a927f18039af82075cf1bc0bba98f7a00402c1b803f4c574f6d40db76cee91506d0da21402298b2bef6187aee3b85f7e153386580b82914a4b3c69cecdbf1a108c70174d933c9db0f54bdf8f546b514fafbf0512de6d20d780a002bda80a5726055bf8a28603219d862fd92910297e4ed0227a3437e42ae3f78e8e6e22345dcd87ea404d0e29f6818bc82dd1da4109e030465210fca758bf462a474af16fab51128c5cdd66808b9e038eed7cc7ef9956a00bb082b1c4685bd314aa0ba2492cbd7d10bea7099d18d1d6a377bbf1ea4db13656f600611c12a84cbbed41d571aeb9934036ed40536fcf813125b2ebd953e25bbd2a45cb7a24ee5f6622e8ab37465619bd178175df9b7c95ac64367378c3cd0140069f1a771f53c1aee04e1f055038c566b91b95c7ca22655c860f06904f68b7e18261c129e2dc1066f3675361e66e7d2ab46b72a4c2a377378da04199aff21e80ab70b35db6770582ba46656d2137222a01a683821b72e589c8aa7f49217a74c628dd2017a48bdf83b2f3462487b2606f2f47b1eadd7abaa23616f5adebf77a39d3d323d14938ee88ceaeae23e1b4c9b3e3ce1f472632f5b5f39d76c946a67980a135458b3ccb1c9509dad37b522981d514b41b4c131642b50067fcc118d524317d9e9bb442ce396a86e5b428b6a40b0e6e9e857065be7c103a77ddd2532889c1354383e1f9c160afe5590bf756109964587b7c9f3d92666eb9783551511848096b8d8fd30f17c9642c58cee5c0d6de55c522906e7c45d0939454bb44fbb47348db92fb76d51b2e1d7b32abbbea0d179d49ee3913f8165dd8456d2ba61a5000ca6596b3b9f3723523e0a7d82bfd26912cdcd8870dafa7bdf5b480290a8a735d157012a965415686d529745418d21602241402b778291d72f7fc183896d77ee248d3fb8b61648c9c9a8e19492e106ae93b0cfc4306970a53bdf9059f151b3001eea9ef41e1eb894adaaf54a13bc734fa0e6f6112685be9f080717db0122d99faa1fe97e05fc1c57e6cc110280d9ab0dbd8ca86243f9fbcd6dc56ad9e77625902dcbfd7b5a172cda669cae253cbb95c3c97c18ec3584f70c4491f6d34a2c55bca6d25d7c99801f94e7661c0a03320031466d9e9adba4e1981b7261dd844d109016712db57388c1d35d1b999928634f84d5ca611cac5e53d2e2039b2d2c38a0d3ca438a9502964a21ed1ea5ed8c044b3e022a4aa02858f29d365559f77471675ef0755b25c5bf04c2f9f3b1f39d920e1b498a3f92020833c2755565baf15e96b2b266a118d40b39653c63033d31ba1db30d33b5f2e9237b2156a46bbb2c74619f06619a53ea93bd56ff1fe58669fe7511c7a990ecc1a5ed2bc9e6d45f87e5196d40abc44772e37ff7f101988e8a9ebb9ad25949d694f124c02153cc6970290d864c9fc72aeff8e522298c4cd489ce25e2789c1921c1422806bb805c617c8981f9af5fe0e4e9b95e6e60fa69beb19c5fca08a0bea187eed957d3893e77b59a91fc97d7290f6c23b4e17520f9430e3a898c7259cf76caa23d06ccf16d36e0c7900dcf3f1c0c6916d425e20f7bba9b45a1d09dd87e58bb85a621ba8d74406a767ab59f97334fb7bcfe113e966f57ea87b90f6ee7bf4ab3660f2436c764f87e32bd6a4a5f978fc6e9ca350260a4c1d4d1dcbcc7c059ff8de670c2a5faf08f5cea1d9ad4f931ad45fceaab8ca38719bf8ae152e302a7e03c453bb3338d02c226c1776fbe8e696def33cc9a8c9000c6b680a5d20055aeb5f01948480f2206be62f668247ddf0c7424ca4916f324aeea057e551b0629edc70cbb75059d714fa3f5d707c4602e27bae0361062da7c6e503e62ff3c30b0d6ea39534d6c23d2f89dc49d6488392305f499865b2e861f20775641888c38bac092a1338f422c4e159c1e1473ff24bd214cd639a5fbf5823bbf3537745755101814a4cde63c0db6511d4390fce353cfa12700aeef529f39da47d3d78b77d7ffc08871e5061d166580596586a33022568e0a95cf7736f611b39b315357615d4a3f6f86211409cb0cc64ffb35760123cffa28be2fd724596afd020fd0606d159ef9d5720fefe5273afe00cc8b7ed5865a5d488980d67ef2092d5bf331f2fe24071b68d51254e57c1881dbef8b4bcddfb4553081196965fffc1b05b39562a191b41e6ad1551f9afb575c21efbe8616e516d9092005a6084a09459996d80416e9ceed1877dec58fb910045aa73407dda5e58104366d3712aa903d766c352b84e78676f30cbfd99547d47ca08c2c8728e5f2ae3df8a8312186375af03aa99dba2af73c21f87ad675dd8798a7c997a65a3ae958d01b217cc4b98bb08a3abbcb77237092036d9bc43092367d4d0aaa905cc39e28e11a72a5045123d211ca54d98e669240dff4883fa2de45daf38c752977fa811c1b55609c8309b3550a367079c1f6fc8ed12bbe2dc4c9553470a59d54d323420762f84022764669e2bc16e67d0bfbf723c270ade8cbae829256ae252aae31387d096eeb18989067ff40ecf6d9be0342960df95780ef81a84951b907fcdbd61bb4d88492c590411462e5e1639a5fa251c03212ab94e4778ebcd48ad5df206b2fb4fc4033833b9034bd6ead0673578d551691020256fac43fb95aa6ae497134ca08e7d3518057f3261387cbddbc1bbffe402c25564a7f2af46154124ac783318364a6f22f293e3a82528bf3c284babc3d064f142758ea9d291de32f62ce0008ade2de0a150a61c86b011430a144588b7ddee8ac29f9fc767a89615593c7cef9a3e02db71efb96210ffc816057bdfab5fd9edfed807ffb3cd84a99ed564ecd5acbf159a7662b9e83f401c9489b65f88d28c97b3be308c3119a80eb23877cf281eb11a6be5e765cefa15ac4bcc8dacf47d8e483877609393d19f95c9a51932adea06a1ae4cb3c0ca4867dcd8dc487acf0cc0296852ec641da9d7c90adc81ef02981c82e33da89718949f34877cbe0787bd80b8a88a08b0d6c8e100a911a79aabc524e257890db7cb927f4305ab2683ec9d378019880afa570ae15f6a52e8e170567c9a91092fbc294a7bf71f5278c5f99b46b78713fcf3b9810f421acdb2b014059e9cc5627e96660bb8ceb2dd172f412e4f6c614bbded915c71ce29d7d6eea3da7cd166caf4ce413a5f66002edf6b1d496bde3823c26117940b9bf5f4949d6ec4df19310a57bc43a6ca03b020a28f1bebdeb251d164edd02169a57247af1122ca37431883469aa901e50aee0b69745fa977c45a9477c1f45acba96be8bf42a68e4a48ac6ea275dc87d5647e2e4bb616ba6f4586aa3a8fb3deaa7b8f082cc90e104a1611654166541f14826d253082135ead870a659606b596b8adf85934b0b079d1e7a348209359f8d168b15d0ef918bb3dafe822f91a9131008bf9bc199f3a422af1388d5291f23219001a3f3d5c5e109fc8d4ad62a06c63db3ec3d1f1c59bd61ca52b8e4f8c4840570a2e9e26665b3d8541ce32b34a5b12d9c425857fa1bec75d78721537a4ef452ddc253b79c087a28c5bf338978dc89be8f76cd64a252ef0e7d46944b1d89efa1889ca5c81d67d660f50610fbe71ad304d29f313fdfd898071a4e0562cc3b3a65c72a4341a59bfd92fe192d481032e53482eff4d94565562c8f75638a1ba60594a8156699ae300377c18afbfb4f302ea6b3963704a0410b565f7c971d249255883432743e5001523f663eed6660591eaf966200fc61e1b87911d4c7522d50ec5ec51c15578cf50f497b113e1da18444d50dc7e723f0363c352ed6ca3406f959e0f064827e72f9c80f923804a3555826927e11609a0eb522669404843073b098ae0603a2041c38a2f4214289217cef2c63ae05beaa00c4b77f7620750c01f1003331775706336f448ad32d03974ea0ed155c22e2c0b50284ac48930d58be043c9628b8faf8661d782ea9437cf7dc50820019faa5d8c33e845ac65b064d007cdf0aef93ccf25126d3827b7c60497cbfaccd4530e981976044008879c184324b2281f396bea0b15df5aef0a80ab4a74398e3110515c98a8de0caba4a43eb33607c9a1bd0776134292c039741b8e986de6cb8a66aac1ae7bb9472fd97d61c6d8b84070b1ff7096d4fdc52e66d17ea857ec064a1b77821eaba5c7cf25dfe8dd9188c7a70f327a4386953898c50515c5e07b0c8d6e20e0c40e749b414ca0dc4d78f302122a0d60d41c0d7fca4c5f851b06e55b183f78e629b3fd93c1677a707fec77251ee896bc52d6e13432c95e54ac7eedab5a6d4f4bf009343e6a283a80128dc03b60d32c47e69ffbe15c3cf1539a46079ebd954b9b07ca34564f3051b25957b3fd9c146a43835ec85a59bdbece417978db00e2d86ecd767eb9db491a3f74740fa159f871790ca84094bfe32f747885cd831b693d2e63e75e963254a313fd01e86d5d29436397bf7d69a7825a8084a5d17d7fa6f130b642e0e61f86b346e15fcbdc868f4409d046959b16662eb78ad806784ba3bd0f467acc909bef00a7acd467eadebe651ced6543c8d98538731381fd0a23cf0ee97170830e471309294933439d001954d21c72ce443210703b0805e073ab3bba107a02a78b87e77fc3c2384222163c35a4c46582b2448b3c808c48c862f1a2b4ccec42968704a2a951d320da2c846537b0fa1b5d854da779109d7dff4dc555ae775322a750112438bc976a3628945153369e17c1264478f227c0020a773c6437c7999e6764911aa5d8527567ff4cbdf0143e23842f5ad3c06050cd6c4de29450a9f2e0c9d248992872fcc9ed18dff47fc32527d0b6fe889e000d54e71ce5cee2188dbf37fc98f2e3d34873d09982e4011564bac12adb29f0e5790b47d2812800808aeb89821365525dcbf8bab4ff7737a89f634a93bc80acda41e358e611a024f02399520cdcd11d4486c0908e134c2f3c7c18f93a5bda9d6c5e54118598f93131d9bf4631b0cc424b11f043741ed6204a8c51808115b9e4b4dee2d50a31608f1414c3fc4de83a5c7f1687cea2eba3b92782603bfa73a54bc78fdcc752537c60b0f5f1fbfb4e05140588636e9918dad3f48d75ce931a02f39d4fe0b39a7d0eaa3e224ef825cb7d94d25380e1fb87fc36c760f3f6e6b62e2d52d4d1682f8e6684f49df629902262fd646ba2a011e70d14f3ac26fd3767c10709ee101153500b44e422a69cf21b5381c6f4b15f724317092d9fd7af23e6e97258ee7220020fde3f01c465d26bd888663966848540372608437fcb966a8d309a7d75858d131e72f173b6b85f1a27c80a88328a1577b68d201f1a77bd0dc113b061d544978f06dfb4fe840c2d8981148d625c85dd069744b55b65908bc32a4211568bbb20af3525d106d0ac97a0ec3f7f27349087e89dc5ed7d33f3bbfb5711904b085b6cb3e3346e0c901a641874a6de568564285939a19e317c7b4aef66b744d7ab0163911b1e911d7bd2f5ccefd9fb12a298240ef6a837f32714e3709c6e08ca9216c981c9b1a582984dcf0059310c09c55cda92ef77a329a8e86fcf0f744767a6c4616726bab748c5a6fc32f1a05e45a1d84bcd20c2f0275849aa97ad4d3f0451d0ce73a04a45016f760d36643afb9eba6d021ecb6e53b912cbef11d1f590bac099b3e98e8fecf332bc009c7ce15185796d3d2f8acdcbff84a3b11cc89eec9296df8fed6a231a8f41651a673f14540c1190baf6382354de66ea56ab686d7361281573ff327442f7a085429844b800364a662ebf996a6a3b21769b9cfcbaf41806d7cadc138b57b44a2abdc60a731d9c54a6c8323fcf279c2fa13c5c8e8b97896cf3c54be90ed130ec753757511ae6917b772dafe94f521461d63cedba8242e1105f28a261632a30db863d7e29e7835f3b71168cc8405ae199b538d9e3a5d89d16fcb1a87dada4f29fbb52b717efa0930a636924b530aa4958d20941a66ef0564d55ac8f667807bee96e083e2d6bdeff0d73807fb2e08a6df6154fdc05ebf09dd2603a9a387f5d6f0d146676f5274409f10e064f8d88f54a6cfffe2e69b994d5d69757d57c921983d16a7d0806cbef21ebad0b1f139466ad8b0c0cb8a54e53c3080c616b026149cf39c3acc29e759d0fd6b54dd428ccd182614944a9b71adcdfbd3e8a95944f79b1337050feed6ce72dcd4fc6b36bac51a05083d62406d0d4e83041632a9e84d3d0ac4ff393059f7224bf0ad5a4cfcffba0ae9660ae3f350354522aa3394da4ce1383faa6723b8063c93262a593a590d76d00f38c149e69925b9ce9ab15c3b5e88f94a3980050ba82eea1d90ff8530787e72564125ad5d6c9a2347d4d1b1bf6fb712fb96bcf2c20116c762b69449118546829be48b8d980e1e04712d4c90136832f2fc03bb9ace910446bde292d37e0ba2e228b80ab527406c460004db2e108a23374d737147b1cbc22532b8cac84b1bfa9a55ad8cf29fb11e04b18f9834763279aacc000b88650c893678a5d1f4ffd844f7ae79e04a33a1db7b89e7a4cded60883703d4c430ba44c7990f59b5e0e1537afb6caadc2d649b18b19c4b236314192e3ab8c5c62f007341ce35a86d809756859fa14afa6184296f65a4c262b14b9bfef9795860986eee85d52d25ce6ad99cd48be425b91d3b6a18236b5a6b327532a26d2c2d8387a338169fb882cf145b7e8434bcb7d829f22f5e954caa9e86a1d7bf1240feced94f80e00eef078feb402f008ff05ccb20eb1a74bd61089d92b519064ff5c2176cf5be544a2bfa705ba62bafda1c7b1551cb4460bf0cc233e2570b2068cad2dbcf92b112521ee8eec2d9703da3b49fc33fab217ffbe97573271b867b7e6649fcfeab4d359ac0e8b3d37b2eb1bdf4fc38e5a8e7734bc6e282b34fd6d292528a9d92740468988f9a2be8e47a838d538a0cb08b914e8dacb50c9e996e91cd9e63aa1a8df0533a60289ac23c577340e6bb490676a1f1c23fc8ca6f918049f05425f7d0d240f1562f8e05ab891bff85ce6fe67b7f265e1903d2e44d3c9a030cf1b61d62ac6cb6ea29283319b65cd3d1ed99ee9b00fa0bcef7f83076494857a1aabb73d8c8dab2bc97452e2a10a1d158fec1bd1babf8a836dbc0924759b67a1168ddea7ad06b73e05390e8c92b79703e6def143f28365b62e3c1e0416281da2b8f3c9b3adcc2398b0a7a4a75d9617a8fa5a27a5e9123056b1adf2fee36342c98329970c01b0e9271b4012653d4355ee0552f0809c288fc352b8b71bb2b2adfb224fe3c3fee3f6c9c236781fa8f452c1a3da1aa50fd8072aec2af1fc479ead2505224c0e7cdb00cf9712e282d964a6202fb6732c77bc425620b1a36a5f60bdcab24b99a5a453c5882783a1fd8aeaab85865842dd03ee87b3b4b7713b105ea4618ac3563234e2c2d2a249f7bc63e75ab93663b324ebca3775ce4bd3c11e40754d45fa24be8fecf73240d0208a446324c7f5e86c2a00a735a9c8e21dacfa4acc9673f33bdab6cd97cae66492989a4cd1f0a2431e58098515a1068a1bb703539672337e9b7b25ec0747737630c6e2b259967efd4b77d44d2a92621a25865b3900657cab83bd399557bbe6711c0fb0acadeb930f7a42feefb9e21efded7a2329fb1a00fb0288e463824882efc21cbcf93f671aa687e990f23191ce25f9a4d0d5f7eee8292bcd4fc031a1b9cbb0774cd2b8a792bb8d81fb55485873b875183e5f91ebfe9f1de1d8da605fa615e3b372b6d5dda3e70451c252c39d4605fed1b0f3bcd993005a2c398c6d0efb2ffb421cc4424563dfb530c7aef1292627ee24226c6bdfd1ace879a94f379bce15e2bc50071bda35c0beed005b269478aa892aa8f605c5556668fa685f7212ba0523881d261005b245b6301cbb132a45b636bc5c8d50876738073908aef2e92dbe881853e0b9785630679571d900bce84b8fb6459efe8c6d6227a2f5be9b8a0e274e6a57b143f3e52a8a3a1b36916575a00c0a35e238e3ff4bedd51fc1a80c7fb01e11d04d4898ca07fa9d641695cbfc0b7bb94595b05fe9fec182ab259ed771f4027a3a892bef38142c92eff302e5b4a3ac9c1a95d339ccc253766ebb29b6595d32d1190e23cebc5338cf91b4f4dd9c92bcc677d392690519712713cac72f3df2161f4e22e4c153ec8dee26b4918bf30b507460a93cc3d6cad0c40eadf6808ba37e80d93e31904f568edde2130984a123d19d58b59f02915942a54364e8540560fa6d9aa95025d3adcad447d05a09b665f02d8c2e319ed1f66f8b2e2b11392574e8d83405cff9a54b74fd2da9ed6e0497a530078d8d7832c59e37c813f6f4da8da2cabe89ed55647de9f912af9cad9100f4f1321cb856191e57051196aa9b54c0061c834be101a4d700ec16528155c16388913860ded7c62500b6098be0589ae1ceae8de53123dae708cbd9c8a88e96472e009737a1ec6464fcc28fe1983d8abc58f5fdd9353065bdc79ff750beee71b2cd45fdd3251b223a95403726ce4635c5fd20764ebee9e2b7e725afa457513bddae2d8468a907ebe1cddf204c831422bcb07c36d046cc0512e1861adfe67af02477513763bc8bb4de4f2ae3e04a201c9af37c4b8bed3e8200297795510a0f166b4bf301b8020615120a8ea242ee1da6ab60bc6373cb32da51fb3ce051013a7e20c519cf821b7d1c36ca1a0f9854d52f799b0454b5da006bf3b83c0853995887f6443a7e545b4c29495ee0ba8bb50f403ca75b35f61bfc0948c9e033afba4edbeb576322d49035e4cb60d113b40370cc87a35328e1f26f8dc18cd70cfbbe0abf6a81f1b0ec1760e683c2bdfcfa11bd40c2eca814f4b4195819a985203b41fbc98377f1a11fe498e21485ef200af052d2d8b7c40c87c8784e0799bbbdeab5de79328f8803f8d942a24e83e190ea1a0912fc950220ec17daf0d5928d2e8e2de07f2e076f778b1ff6a955f2d1602aa5cc1dfd30c3b70d0a7f74d2826c6792a6e211535bf946a4f50c684ffa188600acf10f00886014d881610f3c6b3df2c8a5a265dd2c45a7ff7482790ad62542af58fb8b339b82fdc171b28269acb4108e886842f7121ae47cc9fc472f4536506478e182201dc3e246496513c05920a3cdf6a8c3b47d794420671e592b93cc258b73e9a5d6901607523af77118d2b7e2294e2d6ab989cc80754359177bff28ea4341483e3b57779c4b80f5a041ff07ce5fcd9bb9a2ae1b624f6aa544e11c5c55f7dd8aaf1321fd9aa92e7640038545b24f0240d7257de4f2f4b45a08cb26eac54fbe52e2a0267256c1be98f6e7a6eaa3eaa5794d21e19b3c6341bab8871dcbaa5ad1b32916f152f7d5baea0c0a9feb50745a268ddd70a3c1f73e4bd7ff1362d55147c2a3926f92a4815db9c9eb1610fd357702d3e74cfa28889a6f3f38caa7962f5670a3fecaf725d0048036cc1b83b6f84aaaf44d5e9cf6ca300d393b03c517c4c08aef9924e26e7d02e9386fcef14c4ae018c91982f1944a7e5127acb6681bf4401d975fc666314642efbb7793b047d45b3dace5cbae92cf46b1e97428653ec23c06f208f1a4ef13cf8aab69e3bbd257e4e27dba671f5e128a4b47c1e876d192209c353f721ce48a55374b98a697036105457ce1e8ff732d5d74b10d1e68235647a86ad785262ecbeac5342b20025229dc4e2735552f37da064f36a573e0913993afbb98938dd4c7473cf71b0384a630c3d4ec85d486b69525095815f4797375f97a045fdefb535c683e5ad08cfe3c47a0b75792b35b6ad427f71e0106e2229a3d83aa9f22ae155bd75f84151f5e24a0c2352dab48a3d577cf0bdeda6d7cb8712e48a1d3e25b677ea3742f15953e39ab5621f869ea1896f6536c472d79b2369e70f98ceff2065cec129d1b389417b2deb536bfb04dcd088d04189d9765182e11e4354930af9a80361d397c6cf92f6d7427f2bff27f8ba94accfaa5e84384e45954d02ddec3a6504ef07a1c45f4268660d2645b258d2734d885acde6e204a8412f7d7fe25a0c3ae57bcaa747e6c8aa9225a71fecde5da8cb98ae17192df28ae7a6db30ba2bb1fa92bc051a1995e8d1c3d98c474af51a6ef010fc9ec1956400d1b6908a5c5dcd5c408d9b3e20807497ca37247f17e1cfdcb7d8e44556b32a898eb0d2782bfbb90d68e41147ba2170536329f531930daaecd543012cc48752177aaec84110dad6809c969a0ab6968e323e658b64538ff43c5af121e4f66399092acf200a25d932ff59a43dcd2da420c5ee72d4f52ec56e1904afdd7a87efe2431635a7ebd7d81ec5cb836eff8d4b22634ebf11d1438f91bee07fb1c30031d8da24c10b1f6b66d48c638d48e67af9887f7e59985698c2c1bfe48ef0281115a153d84c6e21bcff24231662374f7202c00ac6bff57307432222bcde5d86c73597deb83cdfd73bf2f3961308d2f4eb625b46d16b88ab83264a2861f3291a4929636885286d79bf9f45395462169ff6bd53e898f54509b38cf24570c6dd89601a5028cbe528a85639bf8ff0a53995cdbf18bb8fb9d9c9abf96699cdb4e7b067f82b8e34fbb7f095c98ffca8afd10c29b1b217a6d91e8417c1f5fba6725bbe3895db33948763429a69526454262779520cf05ffd78a07fa8a8997174a8d7b2fc34d6b39c6cafed1a6032546b2f1de727b0385932851a133acbf694ec0fc4ec9565958f480b698c81431924924c8075e56a52d00327c80ec2e2193cabcc2810b7bbf0b46e5bf39b1ddf653195a5aefa3ee4081a218e582349bec13c5b46ad910a7d036f0d2c608d9ac8feab9b1ce781818404bfba61f81394d4367bc8bc61157d70ec7d878e63982905418ab0692e4c5f7b3ab76fd588231ed030e1003665fce7c9c2440c421d28381098730341048d10df3dabf079eaa304eace1e3fe012408a8f29a844371f2546350d8787b435ccd2d014aca1c78bc6cbad243829972abe2acdcea7b9a88b50a90f0c9204ca3a3f9b54305e15251d2f4e10ee00105140cae798342b731c20e6cc34bb3318e4204cdc37d1219c52bc53e32e6bf45c1d6ef7bbe8e72ef9c5aedf5f175380a7e0a7b76ee2daf2633ffb1fb5d61a2184904df6de7b932de50e8b09f50879093a76447829c34abad4df71a1b6bdfbee7c3ce553cb49a485c4d6ac23a5dd58e7d27295dfa695d2c26b52db287b55a0c24b4ba11dd5d3c70a1cc147820fcf43e0a5edcdf3f8a95d5611f2d05a92c2c753e9b1d4e381b492f4159e02b9b582f156cddd0ade4a83cd3945c05f9af5a7813e699f4b2d3ed3a8c33c092df3e47aa6b33aad33061fe11a186637994da6042b5ad57ec587a7dae4c7f311ae41a789aad475d94d66b2b48d89dcf86815c12f55871612d5e5f391119c3a0fe91647e95ca93918ef7602eebeb614c00ba73b8fa8575a3ec233e28e9eb1dae384946766867c0821bcbbce92df380d65b80bb84e8b340839c2853cf2e31ae03a2dd2904492a4ddaa77fd94ff68521ef41ed2a4bc89963d1569b187b4ec65d8652d8d8692acb29b0cfcd46828c9aa8ddb0c15a9cd4458c729d650e41ee03a2dd600c59d9556dae2df774cd354a7572c4a7e529cbeea58ad187dadf49556a230abd66318f68a5ea66f6573bc5bbfe396aa53f7fcd6b7ec2c036f33e8cc269c52b1d38b321057a71bcbb4c7caf4ad761bc1cccdac4cdfead22c3be9938267503b49bb89b2963ca8b53a6b8297e9da5bdb6dd2b6acab8defecc79cc8f7d24a3974dc7877658e77a5fc7437b66f46c7d5c48dd79f0faeb1a9e05171b7fee38a7dd837c5ddfa327d9fcdf16e05dee1906dd3759795e9cb402e7cce909c1ed7c791325c2347a8b8f559d7d5707349d769a1869a4bc475b6aa26b95f5a084228d3577e93e9cb3a7c63bfbec9f46415518a76abe227c8ca742389ff1c141fd2b2a712edc4ac4cdfea17fc68eea97832bb595606deca6e3355738d845a276d899f145c637ad73bd98dff7ae2a0be67a18c7d302f2d3ce941b6bdbcbc88ee494d569a0e8fa8eec6777b52f00c2992e12c2cc2dde9687821bd6071fb431851069a961868a8b91de35e2e5b4f0a9e01df96b48e67c4ce69125b13908267f061ded4076ea82dc136431863a4674ae5bb80e82b5031c59cbe7022909997dff42d93d12a2290c9ee00dc0cd856c6ab38d3fa2e0878ed0f190c048ce816f8800b8580b77e93b9eb2e743193e670e640518afde1e6707f78404c11ea6762a445448e4a55ee0cf9012f2f0a50c0010e308001c48861125304f8aa995c3145a0947efa1543692da0ee021953044ae9d4640ca515614a51b9d6cfdc3a2b40a51501b7e4e670b2a29fa1b9d2222287b3b9d4258199cbf67240f44dec6572e9756bfdd3b6573f5dde1a44ad94b224105d19d17d175e5a674d60fa344331c4c1b932af00557eb25d4d32c249dba619c82a98a6699ace9adc80fbc31f62f8128a6086c69a8991f605326603ee0f31fc1c55f0f1509d132287fb0333f363d4f813ac9ad42298a1b9cc7f98b9932581bc3cf19df5f6304d30e7348114dce95369c9b315cfa14e878756470d2d24c12666fa4c8c8c26b1bb5bd6701241f79ecbf4cce384702217c4cb229b9ac47d4575ce654ed932f574df6ff52ad05da7e6e9f5e445b7b0186eaebb035527a33a2250b00ffec13554d00f3815fa1938157a47b121bb56e85486acef3c2fa106d9a5e71f4e08be94b570e9c656b8945a0c844b7dcc39abae4d75cb38c508e1d48c641068157a4a9b5ec80f1f4da2d7c09079c005419dceb52e4ccfd1b44960bfee62fa7152f641698f26d14fed0c593cd38be890517aec331f41cb8e20fc657514015a0d5cc7ac8ecbea704f65ce79aa59f3d59a16a5d6034d7a344ec79a34dd12a71dd1cb534a27ba750ffa48e50fe7b4b07bdcd114fb08d5c9d8200edf11f56a1e47e34c6a524da2f4f5d9da55280d1910da40b67fcb6cd3d849e99be6d1679405f254e8619a690cfd43424fb18bf89029b9747b492e3d1cf2bc30d22dd4693dbd4e4d4e29f54c5804de5c3ae9bbb9f4959a4f85fe69d953a1879ae8a9d09b6893ce3a7f69f31f8fe65e367f63de8b59866c13510ca3306ad679012fe478417331152cb954dd668a1c7ba5300c135d27041704355f313bb56da6c8a5aa3bab88eb54a5aeb345c465ad4d245ae9167a4ba3a7d194de3d7af7281de2d2b7109c10f323ea1555a94af5e9b3e7f24e2f7a2ef0f47f2ef1f44e06b5258863d5dc210f4a2ec24316830484281a31b5a007d388424c13f2a185ea64e4dc290bb91eefd655ef66f77d7242c0fb4ec47bef04702e0e36e03e06dc17c47dcfe43e00dc67d94d64a4b216c6bdf36f9959f408e77c56e0684632f89c17979f2bc3e5e7905cbf1e5f9048111ed2e35ed60ed1c2903dcb86121d7041bc1787bcf7624e6deb89ba392e989979729fd84ddccccc3c396747fdda3dd78e63cd9d5cbb5caf2ebbb3a364a2995a109a967d4b4269c85ae7e160d527cc364e9c7a629ce959b4dbeca94367c85c8c8b111dc62b08974018df45f4e36b8c81a3edb586302699a6254d4c44da857d94c18b57d32d930a215674a4792e289f40d773c13e659f4ea6e863127dbe9160684ca0d893394db2ccc2e920ede3d1324ccbd8c7ebee49e9e3b6acb6a69ad8497ac7b6ce4e770f19dff10238d954dd69c954d9b7d397b6a72aa38c30d65c33b3331dc08b9664d32184104238279c304208219c7114a58c324231c421977a9d46a0915330890b823adc91a7e4cea55eaca9e91ecf051a792ad4df42ec64390423b46f48b432a091e7f26c0bb13513031d136d87ce90bd9f5f0cf509c873a999a7ac8881c5a50e874cf7e1034845f5782a14f55aeb057074700c768e611733a9ed15b9d4331f59e50ae31ecf856b322ac9dd5e1697fa16e3526f0875f7843c15eaa3e995ad4ebde7ac5665555cc08943aa1d911db2fe4549447ee4f832ed443b32329ae071340e9c5ef7112adfc9d82e0136c943f63ee7cbdee31ed5c99833cb442492dd92b8eeeebe1cf05b13d79500c68ddf5c892fa59452da1cf1d0be491619b2bbdd5996adb339e0379e9fe0f9f33a37d9e688b68a79685d57ce4d2a6a0c1940844e15709de8ba3bcb9672d46f73babb140bde75ccc4bc4f1a0ade567dfe5944c8cbbff7ce6326e6591ef1d2da1cf5f3b4d2dcad135a27de794c3c9dc77be76179ccc41db0a110133f644c34f15c98a0a1a1a18979114e27b92625e14cfa906dcee630ce750faa33f143b6b91dd73aba855d66bfbfc88405f8466bd287ccc5af34c93d3a19d3734316930bd1104e854f9dc91eb2cdd9189102c3f770ced7a811b1125ff39f4d7d1238566eb4a51c2578b78a79772716c8eb9889919f1a0a24b449b0c0051c439d92f23c6662a4e5414f599bc31d6aeeba24aa90a7d23a213fe57c2a6e9e873c0fcbc3b91dd30cec6906dcb35b9a5b022679c8a66f434c5f70823a0f4a5178eaee53c678ea4eccf39053ca3f1727ea79cc57ea939eb24e886278501e4578a74e9d1664a8b9eea4151ad75d67053920e16e435cf739499fece49e6bd2fbbddbb19c991a3a4146172f8b5b6badd5a2281af593e607e3ffcb06e09515c02d0c158089dd4401b01f0b00eb9e8ac766189db46bd7ae15acb0768b6aadd5568b1ebe52971b393639363936d5fa5551d70d9753b1b0bc1f2c34301ae5480d75cb54bd4a2dbb5375486aad20842548d1ee18a7b690d8fa130d2bfebaec46e34ea7d1f2cacaca8a5cc12c16cac2ac4c7737cbb402a5bc68c83f4b5978c67c979597646141b2524f69b362e9ee66e996eea9592696ee696261a1c162b195841f4e55732be6ee943912102b9f78c6f4e97d0a22247ab5a294ba8bd2aa02d1ead4c5c235e8998256f56a2e33d50d1942082194333a569e15a5ab57b8465f3e2a5e0d0bd790f45bf56a6efc3b535b5919aa31335fd75ba33159d0b2406dc3a151978f26a14f771faef07b7cb565e1191264a93de1834ea8ddd8e2d6361ad5b23c9c5b2baaaa2aeab22c2bcfd2eaa726312d7b1af5e0a4b3e724bd748ba7b2ac58c896524ae9a5692c5ca3521c1a0e8d998f43a301594633099128cb58b06fd395daadbee1d0ee75eb553b66a75544f5d2559d890987c6c2352a16295fcda558409c5285ac8644f594361dd36af66aa5babb4bcf61dd1485d123161234ce4718db96a8bfc768ab943951e6449913658d3ddd58cf3955c6c6be31927a49cd292fd337cef909e7fc46cd39678cf36c23f5b6a32cc6c738e3678c51d6cc2a227bb4a5f83e96d58f8da00fe89ad70a8e6a8abf2a05e37d9abb549c2ea5c5f864acaf4eb5eb53939f346a8a320b75cbe4b9982d8651ec5b758f700de9b196aae766ca96e267e5395f238561169bf198465f2fe1bcccaaf5ebba6c7579dd3a52bd562ad218638c54468871c80f92758a846e57d2d2ba3e4a2995e9a795b049e97ba04b3ab1293d10fb7cda0f175981507ea319258910c9a244dd234261212917a136acc88d9752f2b425b8527d2845dbce0a60f26c41d28eb0538f171149e9067aa4de4da9ea7c85c9b7950bb2156832993604a2eac330cdddf71e469908fc68d63d1a251bc61378720506af8c5eb172e14bb5ad5c4a29ccf215977abc62e5621486612b37567cc5153ca3c8159f06d9fc6024cea2cfce9a7557df9cf3692549cdf9de8cefdd94e731921bef1a47d5f3ea9ed11161dc79cc3d5807c6c94868b41b23b91047157b8a71be15aaa3cc8bc798df8fe71e71ce0dd25b9667de5aa93a1ef4899e950944d98f87d2fe71eb93ef69dddcc33ad43ad3e8439ac9611e47abedf989ca62769b17cbdb52dffae4272c8eeace3ead4c7b3a97d0273ff1e8e1ff04d79887559502c5a58fd6ca86a3baf3950a14179e9fe02778063cbd9b2c222823b9b1bbbb9b9f704ea5415ed47e40b8755ce6f279153f6ba55af5eaacc167d7ab95ce39674b47cd4967654a62d562947e7b8fdf7bd396e8e51f333b66c7dc4a4ab33bc64e7237c8dd3af190b6e74d6f9a3d379eb302d2f617fde8a7cdae6e9e33b38aa87f9885446631bb351fe33a59cc6e1311f101b9954af9de7befc9091ff7ab43ed7d4e69f110be5bfabaaff4ade273d17359992bcf4e2cbb2d5755256de97d1e5a7093794d6de93d9ecf5c8138cdd9afaab375c49c56e6e153aa533844d36d954de66d0dc5859fd4a51193dda69b792a1211752e8557c62be77465b453934ec2c046ea145267855aa5966559f3b22c4ad9baae43eb0cfa654153c8e51a78e3704056dddabebbbbbb9b7ebea76a62a25b9e8c333afa6eeaf3247237cf77dc86efd04dd38ae6e79942e014d28e7294f51ebfc7eff17bfc1ebfc7efb1c5a798628a233333b3075a5df3de0512cd996522d14f72d3c6d6ac7704d7e8b365091172e555ea7ba07cd7bc3f51762be75c1ecdf6b242bac86e22510d8c5ff6358f40d5428206008f26867d34a314bb89ee0d18769ba2074fec76131bb2b05f1ef72fcac8ad1446dadd3d3d0cfb2c0ff3207f7336cfe5a4b81c0bb3a869514a29a59452b6045fa78d4c4c9c764d9b9999395e1a2b3373f3cacacb8b356796894494d23b1a798675782db9754d4ecd9b2e5a727be7e565658544fa45229b1a7ac578ad57913a7422086fe6cc3291e82791acc9a2afec4be419f1f24eca29e4f24b615b040f4f1a89f3c96c2871a35b26750fd5e6a9d4e2c3acf79e2c83f51e175c43deb2ae495e58cfca64175ce3559627f3489c0b99f93db6cf96ac1765195e5bf2467221bd90d68bf4d1923837de4d69a5945276216f2417f23d89433fa7e71df3c82e7806851e2a2f4b4a0fac26cdad966559f559d985e42274bdd2ea5bfbc81b97853b6597142e0b7c4ce329a595d6bb801b0e87830387c3d970f0bb56b4acec42be3e8ae14ed3651738ac57d5d25092c9bccbd6e329ae5bdc8d8a419b2e08cb6e382afb8feca25ad985bca1288973a9eca24172e8864b2abcb9151b028563a577ef9d53cf7f5a7d6b111412bd6b464723e9c3bc2e59eec15ffd0b5eb7b80696754f697d5ef0e6c250bbb08ff7deb39856dfeb7e481a7a51c2bee3c2aef53d245ce39d21172d6d0936f6076fe235e1cd8defeeee8df6d5dfe8db625c7e2a2d9e73378cf3b9b1270fbf7ddc9cd9f4ae5b7fdf3e13b0e0af3f6d93168376649d1fbf02f35c9800d96d9e844cde7b87dc5262c72c24aafbdefc09d9130bb26d652008e5256937b62c24aa176f621f5bd0c776651141e1925b2fcbd2dcbdaecbc2de2bccf368d4533494c3d044cf34ebafdee83757bac5b554a77ee34dd2c248278293c4aacbebba2e6c9bbfbe4d7b7d5155d7b7fa78c77cbd32b3a8522b032fd49933cbaeeb882a0f7f695beb5c6923136f92dbf578fa94cbcac06bbdfad63a5993ae8b50b46915515fb2ecc6d242a2bab555ae4fd1c774fd621f978cef2fb846a56c7fc133e2a9bb14de5c9652cafe62023500d45063bdfb55dd4d0821105d920de19ca0fcfc24c3b76d094aadebb3db34a3e3c94f0f08a6df711ff5b7756f93a16ca94f29d8ef51946b50b24fa98d3c039ee7a7f7fedee127aef13e515611d79fa557e419f3c15fd78476bb7cafb1e3a59abbad3ded96364f69f5308fe12b16ea96e90ece73ad9555bdb37cbc0c12d2469e5155761423d7909ff8f23719bed4e9ab0c4f94ddf84579fa39e37defef4d37421b79c6342bb676706e75e51b0890cb80ebb4c8c9b93936b7eadc8a91ba05bbdb2a4dc675cfe5130f92daa354422a0ff3a44cb756aadf21733c87b4b2a5ea155ea67abdce5bdf7a2bd6b9f0cc966fe5d2062d8d3301a4096ed7a195ff3c5db9efce3585bdedb6322d66335b6a8b9d5af6b273a26c891e7bc4a2b3327dafd3697339aeaad44ed465c1486f455885444d621d76d25c0ecc8370e6e2ea08960d25982eb163b343a90d256c2c6103f3a866d95882f9d948b6a1848d259ecddbb96c6309e78212ddce8d9bcb8930220d455bc2c689c634547d137a31e13c2efa862fb4f1369488dbb38194cd8d87d82314dd0b8675228b6233fb44300eba732e29f7c08f34f75234e754606822cd3914cd0a7dece6965c07e5e3355919081d7fbad1561978a91c78e797e01ad596a8c74b59c953b6c46c97e01953daedd95cf825780684f444e3188e71391386a6a16827f555279a8b715611f2c6a47ab495c4c99cf37f11908f274368517b79efbd373fedb3598287801ab486eaaa63c69899e185e4b2484c2df2138ca45b26f64bc3ecf604126624d331f313dd62c53be62835772d4dfebd62f05570c9a4e4ed7412bed142cb489a04374612cf4822c0976f51f17d6e59b6da9c4b76cbdae2c7ca2c1a4a32ca6e38aa63f4932ca2343f59eaeebab3d8af63f05ab6749d9a0065f9090b09689f885cd578a8c51810c2d09d8f318220744bf1f4f58134cfabb35be8734b93af22c300e89e3f90e6aa568920ed5611743275a94d6d0bddea52543c11dc7f8c849f58b13abbc29c3a538b43f8c89c9375260fe123131fe1217c44c431d6737933097c8475f888cc3c5acde465f62eb3bfc73a7c8412429f01c9fd755a980188b49b8e2b0fb5f8c8333339de9544c8770e6691982c22a2140fe63d8b88eb9876317f72374277e11f64cd4d47d4fbaa3c41111101e1291bd23806f4f164d6eb890fb2d23cbcef482e8cebb458c3cdad5db17504bc59b74c0babd0ab96c6a734ca5611f49174a389365b251e4513b54afc8926d2d88a003d4c8bb4ec28dae7279ae7261ae8ceb2d131a1c3f46348737385c6cb1059bf5d43cdc23c07e3676e9cecadae2d783ee29bc23361159655c73ec7b0eb309d61d9371010184d4e5044175f8472628279387461d7f99e0c7b523c299e1493a7e5116c460708f41110100602496c4405c8032b9383b42dbb333a3010f611100c34b2c204948140d64d4037b138039d848e594f9126d831cc9a3c115e9e5899be26761bc10cc86eecf9c763b70cb33e0a320a320a320a22bb90145a369bd1615d24fa645d348964f33bae1545a2ec5366d15b728a2815616589b42dbb333a325196890ef344d63d5ae9ba1361229147748fe5c13cbf2eca3c22eb338540d84766a27fa6d004fa605384817272d2cacb8591797e896c36a14c2793c9149a40d3679a98dd5ea8cb5f5ae419565529bbc5d339e374c2940b90f0e1f20caed3420d3ad4e62a7b7daa7f3e9f4ff5cfa7faa7b4e3560e7e3e55755dd54c223f339b62ad5ebdcfe792f968db435ac9fa609f4ff5d0e7a1ea0a1da631cbfac7e43af60df4ab7a8acc747d3e17945f36f4ead7517e7255199cbe5d329a5899be21bb8d6086b37b32bb615745f908cfa86c35817aa9dde0a71df5e564a54d77b7a7adf3118fa7c23cd83dec394c57fca9be65af144eef1a7af560a7af0f81348b89d48f87eb2dcb6e15d3f3113ec247f888a7631fde7bef82d2c4e4105af09649e419cfc48462138a4d2836a1d8e430cf84faa595aa4965627299fca2aeea323953186326c7ce33a35ec913c72106f1873d9c31c6159f5cafb71bf35d87401fbe2c1b6334615a85b6b9fdde7befa395765cceb24358c157d91c0539f53ecfaa2aabaaec302fa3fec9ac2ca3fec9fea1b8faf0abe715f594f8f9565596c2aa8fdd463093d9cdb215351f84675074daedd94cc2a036ec6eabab99032dcbb2ce3956b52cceb12ccbb2a6c9aaa6eb569d769379599c1b13c9344cb3a65b293ae3115166b504c37b5d4d3a51e9289da6191dce4d229452eaa615ce393ae77fcad2fe7f68c54e3c16c824bb2cfaf19c602093cca2a28732d18927f33cfbc75e009fd089282546004e3240010820630056d3ec05100000c418a5c010a19c9884401f4f865d96a2368ab2b72506ebbdf8de93efddb27bce49ca7e9772644589f608c89b2494721bbda845f958db6e4cad572debfddad7dbc29e61575b95a5941565184b25d5c9d8e4902b1fe37c71aa26a7e88c804c37cd382335aa99736e9bb555dddab4d3dd2625575e29491a4717e9faf74edb6e2949a6c72f489051107931c403849275a0942d25948f75ec1631ebd5f58fe6c91c13b12a2b33ddf8884c131fe1237c848fbc19aca118aa93e15c7c6e92da96f5ad264739237411c6cb4ff67db20bba6137ecd449e6a4ddac539b8fed198a1180ca6e2f77fb6d945025cf360fe65c7913bbb1911bb21b34c23817e7cad0419f76e301dcde39171937b9732e0438ca9d7329c061dc3917037c74e75c4807c09d7351b900b4ab68cea99034e7540ca039a752008d009a0c6d009a732a34d66af6767b11800d80dd56006063d88d34b229763b8c13bb65d26e32f186401fdbf3a2a124c36ca52c2b13af7ca5a56a29ea4dd196f897d9271b79c6cbd1baea4839c2a9463574e74eafee46b4cd0df5a699eaa9d4e254e465afaa5274aa8f8054232b4a9647a6e88c80700d792aee163aac3c9e438b3d20bb356466666666b615f4bc5b551c5951bd149d1190917cd53b8f82c05797e90ac05752db463537ba2ce6cc3291e834f2a50504692a7a0fad5292409af7f2b2b24222fd2251c35190471fa722293a232055b5c518d55c8ad1ed2585741439650c1b2ab194524a91ddfef8c44a28e508e7d6f7deabec28c808c8950c318ddea35d5bd36c538a4e671195d018d12d0e833459641195641acf4b69a594a320ef103eca51104ea9ee24f940e33215792ad3776e74725e8cace0dce91b691a05c1d145ba089592e4522c95f0744c8329f25c5e5e565648a4ffbd225de4cac72925c98d1f05e11ad528488a0e458d6a2e9552ca51109e11290c16c01ce86f301a2826d7096b17a5924a2aa9a4dba36207a554bee9bb3e293e94e2c6243f7b554aacda9275aa4abb4d333ae4e994a7534e17a573cae953b7eb74bea24c594a299595a5205ba51d51af9f522291234fa594a1cffaeba297b624dfb4d69005514aa90455b55237b75a1425a594525217a6657f9ae89386726872aacd7fb4ea308fe935a5e0cb96e4add75f405c29b8c6f478b2e702efb1f3b624e9abb4e4ab86843feb67c5d54528da89963d15eb269a7b139b8f508a7aebd53ba7fa641151d15bab9452fe52534e33577f70d059d41fb7625d31106fad9ab5a664a1d40a83a2a09cd45b2214eba2fa030546b5aab3a5faaea1ae3e205cfbe4dbfbf17edc780ba556ebd937bde94daf288e5267c16a5d1e26fbfcb81b8c1f0c639232ba07868d012346c5f5c634b2392976cb60882c4bd3b585ee81a45d920042870ff7e528e124348ce4e86c6ec7ed0c41e3c25c8065a016641e22783e63f880a8f02613a010e80f211316989c7caa38f9c4a3bc8080bc6051018145cb20d231ace8101513a572cf855ff88557483b4c8456906d2f1df8cb4530be906d6e679b2d5b63314563d144da8aa6e2c19051c6944f18b2cded4c266c5e919d9d9d1d6793813354d72d20e09a5b724dca0c10226d0c847b308d119c1b9b259d835f6c08c1a02c6c707ffa30a9f74932a4c87381e767c43c827a99b21092f38a02dc8c68ffe04351649b33f2e8f38820c988b647b819efcfbe7c608d2fa09b1797ac5578850ad214fce31a3152396803ca03a47f16869bf1a89d3da2c27d771f9d6c7337324dd818c1373733f4630a79136fe0cd0d0d7838a10d372419cc822ce00c8921babce3bdd7ec5ebcb6995b430b49b08999e11ddcac04fe64c1c60b1b0e831736d188112336e69c73259b73ce25d9e68c349c18e6b9c8bf1bf15ce2d4a47849e106373827cab83b50b58103b5d908597643348d72908958d0e366a11930b89febb4888318ee969dfd40732bd6f39660222db9a19384910a2fe0bcd8438f1d3b9c010b254062e00fee0a10ee26a2e26ea41fdcedc5077773373db89b335273210fe690e40863f0811a7e78010c7007406c01270a353c74e2202466c60a19c4600d72c8c210a2c08798feca737140164cb8828a9c1a306108317d98e7e2b2480213867421061af0200e31fd249e4b0370e690072c6001061e57c4f46f3c9713242982199450410728d82066016668c10ac0608517841c89699a1ab0a1f298020b7d40430de620071b0401ea8006150f78140951c105e6023f5c4970411772a0c8208410562c8021b0e10311f480033a08d98153bc81055317d04007287ee7c590374494ebb61892852147ac232c794ec417fc0b23a0ebb470c313575eb7c50ad0206326c0096e3e8639b8a49e89a20f3588c3139840042531dd5ce4478c3ba02e60430b824c3d842e4c95138e0061f0890f52204245c1092c48a18ae9c4c4872d64b006eaf270dfdb62055fd09b2d64a0c5c5aedb420660b8db23139c1093c7f3aafb1e1a7e6be59efddea3a6a99d13629a64047ed93a17c47b6bbf273778af708a3312018f609a3bc5799eb2ceca591096005238e9a4df22c7c8fc4967e4182b8cd2cd2b34855ae184fc4acfb175ee21a122a5749a788a16ceaef4add14fd3ca95f255ad1ee35df50d58d6bd95a90bd051d43c45e79dfb085ce8357c6fea8f60e6be379d9a93faa43ea94f869714457d4ed4e690f023988980a31cab505db760539fa51342462744844e08f89c10af9d101d3fd996e2dfe361bad497af5193324608a7e93d7e87dd7756e92398b96da72907b67b4208fbd49b7a85da564d337bf6ebdbbbccfede2fbd57e122c699e57869638c18dba955e62d6db38aa8eeb42e68bdad0850b768ada89de27c73be4f32c6279af46467154c4f359e29cd2eb5f2360601949055eeef2eda8f217bef34de9ce83451ca73b6caf44b736fabcf2c3b3569a23e69f212c6e8966a3759750b94d24ef2512b8114627ea377b3275875eca2ce3cb4e009363195b7d0831076626a7b010b3cecc4d477e30220f888a953d59a13030e919c980a6f728cc4d4a8e343484c95556bfc041688a013536790214284c4545ab5c641a6f0e326a65278d0c3196a626abd830c68626af5052700e126a65a794092b3862038e811533124b44127a6665a0cb1424e4cf564810b3631f553b5e6bcb8998226a6829c165f6c01276687b3f9820b45980d40d440c36f48034d4d4c3da95ae32d7440849d988a52b5e69a015b75793603f85554b5e6eca0879d9a980a0346ca1e84a0849a981dcc6f8b2f906c2949dc9ce634cda9e15ffca4c1c7865372dbd81c86c1e54c935d420c48e6bc1b433127cb4f0069244f0a32f01132b4ce9c93aad7ab463dad3e0b4cd32f7b91ea75bd08774bd7e3294b630ed9e888ebf157a5286de6520d87e8f69f83da4ce5ce030d29e504a6cf1731cddca76def0ddfe3cc7d7de13403998d70b4e69c73e64a6d939fe4ccb52c3ca30ff366203318a4e4ee9b514d93e4a8a649324e31c2a74c2949ea08488ace2808cf98a0803e608f2689609a373759f328c84dd60f2699baba4b5578aa82b7e0f3d139345e562aad52578081c015229c627c36a1132f25949406a9c944f808e49c0b2f2584d7263dd122eb4ffc28a1d40236dc82d4c403d224f98034493eca4ea2fae9956c244f8aa86dccfda2ad502756b0818140b7307c40ac90f5b739ab2cc2806ff0049b98429a24af9c429ac472c736032b1c3bdd542ada6d42f17c5e2bebceb998fcc2ee5c16f26837dee46c5e5e565648a45f14e5e434e6076f6c9a34591a4a3297a3c3c2c833a669c7754d3ad922eb6f2ee746073c70f5c443ce0322815427c34db74e2554f284099d9311c94af5b07aa5dea2d349ba05b6924afe5d1a914664196495a253ed280864db5e344e1b699cf6c2088744312e83d4469450a7510d16190e918c6f1612c5b80cd228c8888a0b3b67cb7a07270bdd8b6e89d148b2ce190599e01458c029eefb2848fdd64fe1ad73ee1b05494972273bc99caa868a846d86847aca66b148b68944a225504de8609ac82df154f6f7eecea8e9188b2615b1e1226cd32d946d4e90c09f70daf8848cde15814bc838068cd38a1bb94811510c1263d1840e915bc233a285d0081769e281de3645951136f2399f8b3cebe122f7c5ccf29b78d9465a42e592385d5a6861861e1701b7623b3144acd396641bcab22454d5215b4a9b4dc88cb879cf72f78aac240fef46ba150908ea43740b5579c0e9e0a828bee421314417c5385320090dd850e2c69350272718418124346043891b57647c8da040121ab0a1c40dd2151941011b3148403c7743e2233ce4c8345d73e2234ca4d6a16e40dc6a52cc7242f0090f323e5b19275d46b790ba25f2890fea542dc39f40218b775d9dd4647cdf7b7ffc77f73ec4fbdf65bc9055ada4ec66f654445374d2da39391bda11093f587eb0fc60f9c192fd6081d60f4a29b590702b8ee507d532c7f2c3ad38961f593896898bba0d7199c66516c9c233e02bbe86442864a2e2d5f4147ca75d99f6fdb83c979fd8677dcfe9a39068cec3d0685c886252cd53ebf3232d0586283bc9e2c287404d1dfc74ab2dd17a59c712cf52ab65b12cb52508a939f9d637ba0846ca484bd1606432226522f8dca3bb65dcd8dda218f7bdf7bae70f961f2c3f587eb0fc6031695018429f8f941f2ba5b41b12d30eac91b2a1e828a69dd0a5168a62da9976604d4781c1f816822de4ce77145d73e79d9bd36e32b0c77bc6ce06c87e3c8f6e240cb66082348f0fbd6452d2504c3b4d82dbb46355d5b403806f5900be4d013cda8dfa27c6e33633d03d02b0ae55b2bf56e918768e2c8c771a31bb85328c3ee512dd3997d14f465a8ae61c0ccd39156a1d907b75cdbd6e62b7ac85dc4b64e35d6d431fd1bdac876828c940f29ecb5eff7c3cf71105d7b82edb51cc40bb73a3e019f1991619098470aebcf08c2078b9a568b32a605437a25ba205e214689715d22f12c5ea155622238ad0b8c7c76e248f15b1c52eadded2a8b3cdebaaaad8f5155fe306c4b672e38b6dd3366dd336f3d166a20e35e93d74abb6b91baa19b96feedc28764e1a060ce0a4653370c22e5dce74cc22e1727c480d2ae16e748b3ccc8e69999439ef6e9a7ea35ba08f2b35c7d3b4d24b4cb6d25f58ad3393929d243ad38d0eae11a8a42d98643dc9d4880000800001011315000030100c07c482e18838d2242da40f1480108ca24c6e5219e86192e49032c6184200c00000008000a0499800d2c3730312345a41b15206bdacb84e2b1f029f9d8e502d16e8f9e56813233a05d7e6f9c8d82ba6c3e017cb304a39141e336b391e3ddd9753d16ac99b135d53d35460e5c9d4d87b49be59f3cf10ed73be8c02e28ca0009158e6ca59f306c35e552500df3a93e1c977f46aa097b1a5348ae8a5e15e4a59949d7195efba0dd2cb502fe35239603ae269b8975ed2594ab2ef2d008884cd6708c21b7349537e83bd1c95ea4ef8508c587121c40d06bda614777b35ece57c29eb573b2c5e39897c65f0be5698e40cf11f4ba729757a4f707b57fe60cc5f42ac8155b70a1bcd4f752e784364612e9dbd2ee8d85b85346723e0c502221ecd1a6545dcc4740916040c75e432cc1fa2a228b470daca363049e2b5eda6fe879a78d3d6a3fbc1f772b2e3fb96440008a3a1e05798919dfc836ec0dbf9099c5f7304ccd142332e09710fbe421ee299b6b2a787e60fe0b62745cd66ded1553cf41f83ead56b0085dc86fccbe95a63f976d03e45f5507290051ea7a0d17d6f510b7137c179850bcf9744392e16f609f4f113e7141b2825be0f6e829c0e67d2815a1fb71f4c3ee57ef1363fec21af9ee9d7a7b6d93a30e144dd6a10435c921a13e479a70816f50b96d948fbfdd0cad29629c63fbb808a064ddbdf5f285252857c0103cf4bee02e3e05b67950c87a2b964cfebb9679fe16ae67807ac185d35eb265023b73ee520530cd95a3631b4bd85714d8561ca9b12607418649635443037306056badf3fc80a1d5e3c3c0078ba0e3d15fd830d039a2fcbbf9e7d065dc1c5f025fbb4045110b3b2e64faf077a995daa30a2f52860410493caf1dcf1a4b24c0f2fd148402567e9d823f9f392cb5db961b7ba388d1db9ca20289d2acb5e88b4ca0a881d2991ae4af7049156f359edf613591c5a28c7bec4d3b288a4d61a952cf63fda3c8104abe4893c2ceb4fc686df7cb84eca5422aee0c8fa1c05d50f2fceab26e40f8b6377951a2cb9e685760ffde5b4acf2fe30e3b506f89273b1dfdb8b2cfc03ce8bca3411943aba21d2045e22d41b7e5aa0722241431ca49a19f0a838fe78f4e4f0bf36fcacb4c416a46d7dfde6f65da9eeda75dceb65207ac21f8f8bfa61fe3b77e0a7493eb84190b88db1c52d340a04e0e25ac283a97f2c1b7da38d628323c35b96800c3baebe1380615ee94a0fc25f33e4b11332a53a01cb821c571da192c18775f599c7ac55dc161352378efe0869e2e10cffa43352cd755a583d4dd4188519a1f0495b647eed4bc186d94c371dfa188a1eb1a270d48c9c74efb4d43c21abfd81c3e981af146730ad2f8cb3bed16782e0f6608bc291bf6e8a58f35953fff4a149032b1aabd3688b405e0e5c3fa4bf3eb96b82f11fdef11189bc4e1331c4feef2b1ef1f467307b6fe095ae9b2755e5699579148032accc1eace3054df08fb56ec6b645ca7969382e45667f8d4372954896f0597ea72e32fe50145e8e18510798543664576976520555ccf44354df9154622a337427fa80c5e87a45350a18341ab2c459d9ddc171639e7d8f68c5d270a4a383210ea2eba3920708022f04a45507e3ce61341110aa9f30bb3707b0574b77bd27ac4c45ebd2c8b61a3a38b619e9b341faf3aaf399c3a9f212d899f9b7e2f534d9add4469b2d4397f9fcc2df99f8d799f5fac7bf69492a66143b80fb9616bd314cba11f48afed0edba3232e68e8d2654e0df4516a7563708b9ebbd10d82c95c8c6fb9498461f3d2788b6c8ad74f0991dece840e90c8ca46a2b7ff8d68d1d677ba7b2a66d72254b86cb1a32085e0569978c770c00683ea85c39c16c55ee0a3515d9205d1235f2e3ba75020a4da072d90618259b4d7a1e890e422efb3347f49c5e8e8239361b2477cd28fdb5f587d166b5266edd8212a32799f223b098268f14b9b7200cb1a85daa48ac8a3a71b9a75bcdd01f440feb3e3782f5ce66033418ff16bac6d312e97451f4747c996334c916b367156090431c4b8c2b2fb195f87f1e6cdc62e9c61122cecea36d525a24918ca0b7343756ba003ed99fafb9fe3cbefef7e92577c8e7a8c85d4733942deaee85588154a31fab1ab585f3c21115af0370eccc6c6fc6e146ebfb2b1f9a5e4737741cf39488af3744a43f4195bd5f81751919f4f66e505253d3e04db13c7ff0d63c163f94ba0517bbb3e38763a508fb4cadb14016e28a617072b3ae29b6a9bccb0f60f24370d93b3a0b6ee37ccbc89e3cafe9d451b4c7e7605930f74fbadc11f612e7d37f5a0db49dce6412738859d31f05f8209661263e7e54025df2030ecee406a058066334f82125c5802d9205d7354af750c9c109bfa4dc64e19751268b37bf527bc1906bfe9bc3d06bf3d8b14bf785554b3e46d68062de684c403f4adb71fe0bd69a27507af91a600a1ec38dccfae80295a8f3bfe4f707e300e7885c37ece307b775ce7f38d6a42e998411f4422ab2b2bd8c39ea86368432b41ef1e6c3b0aa2a6442b76a8afea7ff39ec1c3cb3f81e24c3ee05b46c55850828a1a8e269139d5fcf2d0ee87eadb8cbcaa3e9c67218c33bd048a66c2a3cb318ad97befab4cc05f10ee07f709c54c9f8782aafe31c65ebc5e590c4437cedec9a31d59afcec40f02ccd9f9e29125d747ed2cb669d3d5ba2414e19583b4854aaed797e51e6a543ab7fc63cb80897c30c4961ff42ca5df99d769fb69106937cf32018654bb3d24a3a8e2671fa130fa8d3ea44ee706f2f85d1319f93a5f075510c5d11793b9c88596802c48532cccc51abc5db62f942d97a18f20428fb7dd7fc9b44f85f3e026beae560246c11571579f0cb7da5dece975c8bd20fba98bd6cea45ab3e31a6abf5fd59a4f3fbae4108c0720d99b3e6b399e42d3ac95e2e52eff661b8905a17c150ba6e1f359edc0b2b2ef6e98ce0b7808329e33797f4562ba2cb85f3424afa7a1be43903d96a54700df5880870ccd76b970c9b06ae137ad74e4156fd40b53aae42cf87e9b7b1365d510b5a0b735d151e08ec28ab9c30378235f119eb2437b09e932d4e016ab76908d933da0aec442c204e436a8d48ff3521fa9a2bf3021ca717e0b019ec7fad46b3207bffa64cee6d83064633ebd431ab94adf8b14717dc2086faaf831bb0e6f25c05c811e53d1087f85c97bf250422572a9efe9fae2a2af9b709db20eec9f498f344422a77bb7f2fd0595e966f64609892e1880d675f0a0ba59006d55eb64600bdae4e503ae2a45ca9a196806256def0213130050c4d15e5a8823b9b5bc4509cef0f39c2e74cdfb5a953038f3bff437e456f8119ac619d45af52abfd48fbfc27497c1c2d73feb9c7edba0173ab8e2b47cd9665cda557b0dfacebbc4ea7032f2bf57f69694c91d64d787dfd0991956ee2be1a645e5f0b4b30148b3b01bc406b8551784a18c2efb3dc86af943fa3b40f78e046a6cecf5e0355a4ebfb90fa7b14244316d28f43c3b3ba143123281a61f822a484f79769dcbd1bd6c76e1ac8f4b5d8e2d439528d707f395c1ecc1a7715a6b431a0f6fd89b8aa0285801ac875a99989f3e2e7630c6914e8b082b4233a3636039949dc5c6cb574d3c5847f4590c821897d207a116f7e4f5c14846aa8ff8f186e47bd9a49c3d44c43515eb3f34ddbfa99bf462ed95f17a3496673bd525367fa8b70bf479ebe4b8847c4af3c799001a4304c0eb826607120e46d61bc52f1275161e2cc84fe91f48bb50ee3dee807373d20fa3a6c5a5a4b755fcf8f031d34c5af19905c962426b0f99072ac3fcaffd9b390335e242adac4fe9f60981daa5f3921fb0c8761169d05782e3343b4ab290f4289ea918a6f512f9b020e804f3a64b13b4c64db384924f591d52b773d28ac046a4b81df9d24d3ca3a23cb7499db77b4e339174cbfc628b6b46ece7eda2b026adaae926031f03d30dd64b475fa820c29cebf8f4c75bb0d94a73b0c96f8c40d15247f7491a7be9162c0aade681b0f2675951e43f1e496cca6e8ce236e1eeb535a58fc1075a399e692e36eea60eed8880765871c2b78697a233f7aa17a975df36dcb7c5bab1e905893c32a41dad72b8f09152d65e20bab56584448164c2478015161904c0191d7cced300c98b438b6f5c857c2207e066a3f5fc04fa07aff49c18e77e59248211845faaccea9263f58edfefaa4b511f98d2ac3e502315d39d0d6d67f72ccbca371c88e5f429ca63ec39548e20325d98951df6eae992fa9adc8037347c20c2aecf5dfd0f06a1f653a3fa82e318e7760eef521a67816050212cc15852623b4964d531e1318c4d4411d2468aedea76da63ed7f4a1f7dcd03028917654f9edc46e39a778ebae52991488043cfff9b716e5ae7b0a0218a41fc1d4329bc5f32af06dd2ce15ec3f1c3e6f541139986693891042874d6ad25bf9525e41d2273b1006e84514238141b1074775a74b98abdc71490c0c96f0ce7e8a938a355502dc7c29f206ee7461b7c847a87e4b5b77d77d88d1e0828f312522104725f0f311b662f0c176668966fd7c82d3ac63cb53f82902e16d3a3195e8fe41337e651e58b285ba66ad358dc4ed77a68e2ce34a06b09875abb4be97c3ef01b0e5536b8648f90d6d0081614c4328a988772313eb8ff5612170f15480b0fd826f0723aa141902173dde1128721aa7cf33453a5cfbc6ed941cd8f0b5c5d7410d76ee17a8d5112bd03bef2bb716f59bf27374d171192df29fdc65ae70874c5df4adeb7fa87f625f569c996b84e04faee6c017b8e0d927b7bf127c728aa2a6060755cb5d0e930e4350dc0fc21408da19b7130075f2a5419b86dcebdd1b1382d89b4a943bd46f0640e8d17ab7d0168b277a3d3aaa453fc3aa47c7e31a92de240718a50670e3fb01824386c019a4a04684f448b01de0901da054a557027f3851d17924a53b73edc52ad55493505e017320f83e6e57eb514b2a72cdf470d0e8ee173875194acd0ed91d65770e2172c6312a4cd7d15b20a3eab600bd928077d55aaf086caca49a0dd68a6d5b2d564aa0c38dbdb28815c3402e313f96ac8f40316b28e9d428aad7c7e44807800269b2435f3da5ec446610ae5eb39051838018fb9a7e8ec0a74bb6381171fd99de482c8e8bb5e3edb65d8f157c01e2f2965cd70c164611664a6d01d43afd9b4a1b5469c90645ea739e132281ceb3308508b039ed6e2db4f9e7e97bbd36599010c313f36c18a1bd3518489fe8ae1e28b16e980a8f786260ea37dc95c889fb59f58230482a16d738cd50b009efe79ab0b5fc2212502000e8bad67b3fcdd3926a04ada27444ae69dbc4debeff9758405f25504dc685f2a69b28b6e0050b62e6667c0c114a18acd5e0d830f3493f35ea05fccb27b7cd8332be79d87868df6fe6d6ac5f255f3a69107ccb28143ec2f2213025fb087a74412df4024e4c2fe0901f01148d6d84d96a9d4b03fd8654db33bdb15d77236ebd135b254aa86f529fc3372977d447afaf24377da1134b8a58d8a6a52c69b20f7c2c2182fba4414587e05a7cd21cd26518a018a9f7ea2a78d789abdad06c7b9bcb2a845863b23bf39b82d4ffaf616022fc3ed961f46d62ff0a575f32a915ed9f7705c22b5227ced444d5d1758c7c52f870cb6e65dee78d959aa5aa40d87bb33abf9cbdf601aad9a8336b7e922e0c3dfad325ce257842ccba8f26dab31312ea462abb91594b73595ca7bca129d9958710a7ac3f3fd121a9cb53f69c913c4876fb3f2fe92f17addbe494252198f2fdf09767feb85353ebccb79f67ad39910fc6fbe93e0c7cb73ee5500a3c81db5f208430f95512820f053ac814f2cab2cf7aae262a89db970da58d807d0d5cf5a287cabc1d4b7f3769383c4cddb6a314b69ecc2be9f35d7d305a4bd94b887dfdd121abaa897616f7e5a537461ef11b979c2823349b10db0361c86c0d0ac92ea4489b2ccc7c1522dd42463179151fbeb184ad8263283acebecc87c8f58833c1ede47913be93583a57755fd16edc611a4075f16d029b7af9e4af2550022b33236145d0ecd5aaddd409a5097626aad88a2133bf1f3ef6272979fda28c3ceb9a14919e78d9028b79e444bf07b3f615309a7d4cac2406ca7daacb996461a54f06f959d153cc60c25e46d3d09170276baf4b39d37d9a18f092b8c1ab2f99cfe9dae28c4534d36eb1b953354bc992ef2c4b501ad0512c372a0b89d3382340043118f43e502913c247a3cf02982b2ad82d1876d31edcd3fa09f9905b283119a088f0ac4ba4a5b5fe53bfefc108693d132be8234385feebff6ed6bda88ef6d1a4a4710fb9a6798240d3e33f1b385c20a221ecd9edae3e407547d2181987e22587dc4e1f6a8a4d7cef68dcaf04b0b697fd8a1068bbe667c56d10e9141ee5974215e6a08c1083546377ba5f7228f6b5121e54b7862f9af677897cd92ba8de74677a39f26b6cc83a8479e39f644258a1f808fb8ba7aad54dc90eeaaf9cc9213cd13a6c09f443e83c19bc43e82c197443e83c191c331d30524d2bf03efb7937f4afea8216701096a422c5f4cd105e286bf982bfaf0a454f1755f80653392c82371c7ba7c163418f4616ca4fd7c8d2b5e0b8d5cfab4e83d27389b3651acb6766d21b20530166f91033bf316e71779152568b555c6c4a4d0a6aa75c9cd7dcc8bde3a8c09949d0b95c2849b6c13ed9a560c7f62e04456389952d5576bd5e11ed6609a8bd8f3ebf647e19f1fce29182be444f0dec69fcfa249a98338194d9f2d5f14aa70d6df21b9d74d01a3b1e11c71368282949ac53352cd82d2a43dd679e8694edd19b5b1b77940b9c9d0bb321b8ecc01a3768b365d814848465def84783350d6ec3a1073279a687e3a01a63e6933206489a3bfb9ec7326d28dbe684ada197e82d452a984ae58ca7eae62510f0508735743adafe18d4077144cfd6957f9176ff49d8023fcdfc2b28b14cede2c456cb06787f55ec16d74c3876cac91c13bab853545e13781d1fa4130e689905739b0becc649365f083e5cbe9bd255bc3ed4b005b17ff569d6ba7dada8bd8cc6ba005737e80f5df14c4a282d7757648a3b07e40af85c732cf4b3ab5b4a2ee2d5c46c74e06bddb50dfcf5be7de0133dc7e565b21d68e8e23560f59ceed05acec5735a5950adc4ecdfabb0b2b1d1f468aed0427ce672d3e0a5c6ae31c1329ca09e4e4e6b0c50ca2135a1f30e2d5083b96c20d74d77755e20cdb04b62fdc48ae20cd2bf7307ed21f7bdf0e9602ded2cc6899dcf376b526c40e159f60ad2109f435256876f564766bb38ac6dc0446e6fc5cf83e2ff6918286461c95aa67e7dabf5ba979ddfd96338af4e739c29a2be051a7b05991e25049c9c7d155318e9c421fcec1fac42d5f971767e8f6cc39fcbe5b00620136ef80f94511af7751eb228d2e00202856f4d2913df71c2abf2b02cbabaebbe4108a4da91f1ac506d9728768ff47c0a59a735c1afab2b85b4d6ffeb4d96eb6de8349c4848a83ee7a0eeda83c12f9a0e51a6d5955295552e0688c3e5655502162c54bcb685f156777a7f095f5b0ed9a5aba2e4966ec49b6ed9329c7dd1bd33adefc20ebb6d174d9ed86126aca5162e8861ea7f21b43a53cd64c12935286c1a6d3a0edf584b278e91165c2af27c4d5cfe13247cef088fa19ebb9b50ed04c225fddc2bafa4012e7a1e30530cd95ca51d52f6513d54ee1838a5069839b2625d84043f5649f3a6af7d8def5442e3f22a6f41c1d776ce8f259e206ae69d5327c537abd30eb971eb7479f3e754f42b0b31c2710e382856b6af1c9987e3caa24a75a08d8af8799ad2882e0931abde72cd90bf8f0d4eddb138e6ca4c0de55121e1da4eae8bb616ae1ad94dc3a00de1989e995d837d5d67b839b6a0bc13384460bd7c06b0a3eaa00030fa81d8d27b4d2b617068aa1291a557f7750b6e626459caec50c25ba8b9c96f5beecc4a30bd570029621e0bb26f52245b513efc320b1ede1ac634f2ae668b9a175cde54ebbe1209ddffe70ff52d1f9cb14290b645f21799ade1c7a018eebf2e330562a6726949a215d2a532bbf5b918b52ab648cce52337f403998e82438cd2040e92dc85f363d977e8dcf2c958b263bee7dd0fe6b3cd9728c423e79388aa498be4665a7256f2bb8323143ff55532a6e9073900daea1255a6ec75a12a3a072d8bb05b311dfccb10859eda8be5868e18340c3c3846dc6cce73215a131b95602d3c3a1a7fcdd0d9129a8a396657908bff311b4d940e36e603b18eae4733e61430aff35ce431e5000456d0da32073dbd3a5c5b9a402b9a515d99291b1843edc6cc49c9e8e6cd232b8242361dd65002f0941254297863bd19a8cf32e2035559444a3dd0838f8c6585f31eb0f67fd5cc9c0f7bdc0f264bda06ffa52e00c5345309fbc5065e26b3fe190a74f106886807dca3963c0d84da793e31d630c16cdbf49b53495f996dd68e5e5d11f2027b73ae3dc8cdd33b191786b9dd4b84e4471eb4fcae60b347a0ad9ff1ba45e3e02ba8301a445724e772d51e7d5459d2538650ae414561f0a6d1f8e5eaf03ef2bad5ab0d8566e45287f81fcd9fc78c065a27a8a05000224e2dea7515e42cd053402c2fa2ed74762cc48e02b2568f24ac15ab297f4a0f5fbb2580507678b903f571e2315b450626fe4f28795c2143739e7c61a54cdc714a9a82d5f4187ff3154612a522ca7bbcd2649d92723d1141d2ab115ea2f4492ac6210e05100d510c05a18507f983ffa91c8bbd63e898d70e87d5f7f97f90c44a345339f319d56df9611eb4e7e915c893a88af4295817fe6db3c8d33f4c327dacc2c08b8ec1b9cbd3c8ed079cf6388aa815d269dc144770194a4ac0f91a2eeb59ae1caad7033151ce527bc4ac6a28834283ed82dd8fcbcc656121bbcb6e1b5aeb92188ca9adb400ca769b23485c65545ed95a55bb81561d316c1eb9f6cc5315e935defe7ae12dc33fa1495d6c01cd52bfd77c78fc681b67613be429ce6d53116922bc3d0234328b792563a74b2a614fb2421f43f3c5ffa356963199ef295415cbdf9a2811d60e93c105ed23dd070420e4b6e667409a3184532b622d27ac28498080e418835a30b5479b8fb0de499b1fc80e3f42e4b54d0bf3fd1851ec1fe92ead42d50c6ab54e781de310568ed41399df2d478643c5aa11249371596d77a86b31124ac6c944dd0a9145cd2b67ec0cfdf8e3b69a7bfdcc66512ddb7e657df1683888331d44e65ab17fddafaf5cfbb3e0362cce8bc66dfbcab67eba0536e88d5a0abdb266d2347b029cfc273afb498cf8954bd3f210acbedaf9a21ebdf72103b52a7b7102504d52dd0d20cf7944082bca52a74ff4b55907c48927b79e1e5f38d70b9de48bf0181ec28ac1b95139a3cd13dc57faaacd3c18eb45598612ae8e36153699b1b528ac8c5665b3e397c84137afbf5b479dc22d6ae84770f1b67c6c3262596178cb709cc26667f1d2caac1608772b6930cd23c3e800e084f019ddbb0d8de12532b1bc08406077a32bc451f22406e59fb055cf7dc0a16861d9dc680db19d93ac1ac0d1c74272821a1a6a53cad80c6c8e223f45b2f1e09ea79bc24b2ba4b56c8a7ea8213e341884445b15c6bfcf7e8166684a94f80b0ebdb59dae8faa5222aa6812877da3c7f4790375901c5ed0d1b152f42422e581ee58ca20c0ffddcb60c7b3795d5c989e6c647983237cddfca4fbd6f5c94fe79ac2633d15c057018713766cc4e3dc64048fe79894a30b3c0221f92d95477994c32ab724a9e1c66fa68e73433a2a89e1d08c712d56da68ff1512b5e5a36dd0c2414dbb39750751c7ab3b59902dfdde7664544d0fdb8d02d002d4917b4c39a0ff0301b600f2e0d501ec6111a7be9a7ec61bfbd5af0b8a9e9e28ac8c3d508d174a21b74f80f24708a9ce52aa104caa2e4fa38827c09908589d607e2928d8716b9675f6ff2efa00eeb1ce04719a4e7d834dd37edc8709f84aecdfe374250a82d3b488273a633f390a38a0c51f1e7d5d515b04d8095d866b8ef06e96691cd690a6825ae92dcfdda05cd5354d49f2a01c6ea20e0f28dd50b4c118fb1cf2957e00608d7ea0839ba8c27fef304fb29f36ca40695c98defae48e8ae7467566a2233fab24ad5df834bae363d6bce1b4639b3abe7e984a9a687dcc63a7987ac4d0506a516f41b2cd84bbe7c66d942f650e936242f191d79ba72cab6c7e970524c73d63ee116fad4d8b1f8658c3c48871bbbaec31d1e4059be678d3f4c8b40ef9d0e3e17776068636b7a39dbec899ced2a8773a28e91210b236b4f2be6c5368a730c016eb8cf649561b694aa72bf5f58ae1c63c798027938e3dab525819e7f44c609e1c5fe0a32f6069ce9982c973e5224f921f25388760d4ca0d8ce0386266351ca0e3c4222db7e72ea55638ad22c74a57d53ce6ba3a5f5e1a46005932785d0851ea1a6f1e537d145eaa609d0ca03530981532fd0a04a6b4309b85281156c6e700e536682fb7daa26e40533394de5fb501527aa918671ce1e3a1227b975064bf41ee59a31bcdbc37082012d470e5ebf79d9586f6a2274acf5bf8b24ce15e78395740a66f87024500d82048beeb7e720f354cbcf8941be0cc2c609d510e0ad3157120ca590ebaa437218e8dc088c4a431911caf014226ed4ee2ae1836329a9c0905cf736a96e72532009a9e612dea2f3a501a9c2064d6e1ed02464a7382bea98ad208a8193a2360aa30fadb820fc416fcff7f4782dca64ce1ea177968d1c20d0276ee573dd98f8e2528b313efb26a0c05f7df49b4229ce9dca97766166abbc567ca1ff8c73a75c54dcc3f231ca8eb99049fedb3a21a9d00ca4dc0fac401105520cf6c3364a5e223e46f7343e8e27f8f8016607185a54d8e957af3a3732c018026fb0522dbd01913cef0f0c9cd6e0a53fb63e2417420d97210f664d205e212860b38e7c8d58a8bda31226d781812307822ed8546bdb7870333d9c2383a7d624b704f3ebac798e364dd5cfed960d770fd34088628fef15362ed32bfe6dbf53d62052300c9ab35b1d73fd6aa19888d212f6aa1012eef5624e31c69c387c6be3154fe218c5903586961eeaa296f170b295e23317a1503d4adfcf20348f2317c32469765476976644e05e2a9b63be363aeda872c73205c90b7a8d312dd8fa2439ad0c4c04c77239dfbbb5fa6f311327c6d241dbbd428bc707c72e41f77ad2907c2351fd02f47029f6eba31e737d182f8f46fee73aa2f7db0e6d130745d3dcc2576e6c8bd9c37eff853a285e36d41ec7dc7f7964311f80cebcb5266db0a48a9bf2e7632aecdc86e6a04124e890d03647c9b029fb3cf4a5fcffd2e4fd379a34bcc5e2cc52f392ea8a7620acd8f4c548ed52e5d9bbf4ab4794117c60c38a30022fe932861f62a3cc432d0cea07f8c5e2524a84d37a232e870a81cc3522f29df0b94de6c83547a34f7182069419fcc6e8c5b0718dba8d8e1eecba729f4b29b58d198854d8db401cfea8905fb30883efab5863cd17430d61c3807cf4e03cd75f5cc39705436e1139a0509401349ef7e2a3560a585d8e0fde8733fc16dd98163e834cf2b4e34f151dad2f09e25fcff4439469ae678e730f2c6402762e4d184804b07c88d7df017e47957423064aef54ce6a30d2577917cca79c116997f237c82e34c1c91f152cb25e6cd8dc38fed27922ef985ac604065c5ae5bd2dbbb0513ae2b6c80f001d71ab882d24e294f55abd4b4c4fd31c8960b97cf4226d1531baa897cc01584aabbb7a85c5ec55703922db3fdd7f5dae9480501d8b66ddf6c81bb8e81be69387c1c85d61221685b82db91822d8e8532b2c77bb4bad02760bca09a9ea341e8699b22c8d1986bf1813ee90c1029613c5bc3462e42a47102a7ebe14f4100c7815f4aa70290e683a88d9f4c1040ce706487ffdfa86210373d926256ccb3cc506d44b9bfaf77d0a81ec20f87c744691588ee7ef1c42f3bd44c755d4661369b6c45f7c2cf8812bca8e69aa0d7d3f817efe793e07bf904109af190f093bfffeaba66b6f0f3b51867ed1af86f649b7eec5fd7861e1acb9660b422d91bdad796afeb441837f9d7182c8b670c4b546031591220706ca905003592156d8afa63f8c1dc1fac676ba257bd55bd3212643bb95a1162f9143c9c436be3701ab6d8bc496e0fbb9dc94828c5885e8d045aeb3884cd08a1ef822224387d02a8960d33eb872668ff53e340d99ba243ebf8db5726dfa52f3b9521d1451daeaa2cef858c1a27b5c092c2f22ef74091d58ec1ffa1968b316d5b597cf909e5a5c9b1d9eab2aaae491ad87cbeabbabe42eeaef7a48a3762abe8f13d63867405ae6ff25d6f6ef0dd729be01391a8b52f16cdacd4a5d24e9f798a5db48eae76435583b5d7e44bee70b68eaa78631fb0cdf66e21e510721bf7918d8b85c944c4746b6b4688cda0da913201f1302df90623809ec4d2e64fd50d71124f4a04e420da47b44a02ed2e1d723c0f9c7a960426cbe15662b15219f88ef463b5359f0394580d1ad666442bcc4c69d3fad2939eae3bd6ec245157118d53c6c845caac80d328412355e1c33bb8f66e4b7dfb549773cb76a260f36505222af4d58cb58b3ac47295096a29d644526b538c819a148cf6464c5d3d54e2209e5b4b688f62a8fb3a0b3922e1d3b945a5eedb3369d76d731f536107d0402f9d5f60d11ae067bd0a6df04ed9f8f8d216cd290a7b23f43e2d6524f4d17e94f7bedf803c2497fa202ddfd76b2271e9ef3b791ba4a014330465f6d9be68b70560a3c4a5c7a19d45f8c2782d61a5cdfd738eb4fa202ef37737d0c1ec1ac51afbe203940c978aba6dec1832918074ac7289bec5573ab937697f42af6cf25065fc532d983f61034dcdc45b5543cf1345f472090e212ba49f2e82ffc54f442d2629f02eef88b44c48bf18702dd69fbc61621fea404615f888584f6f007afda443d564245029d55c8ec31f9409fad5a27c2c54e8666bf571209f3f532becabe16b7ca0fb34f48ef9c36ab7d5ca3a1ef4eb5abdbf41da1351d1b39b7d3f16807995a501eda3bf6b110a5bec5739615df0078663d3e53a73efec50a1519f015b315be005f9ae2474be305a4bb1d2eef89c83c4280d5b7aa87eff4c36459684c37a1bd4a66659a5da343dd9cd9dda9507b917c3b11ed9c4411d299675e9b27134570a92b9b68ebac8c3fa0f8f166dc68193da21f3780464fd6267b634982ca6da1513fe03021b4c6eb1237235489af974a600430d3706a45c3b0e34199961d209e5442c85ac62802b516d9734571ac34c5d6afc40db8a7b88f156c329647d4d0ea01ad1d4b166cdbc04fb8de59e43e4f25577c05b6fbd28cefcaef16dab5645fe08ac572f7068ad2359fc9c3bb51886b640782344e7921bad8f9922001cf0a751fe15f7caa441948b870e2ce9fd6d6c803ff7892bffabb0c0f48854287916cbb0ae71890aceb0d73cf0b1a03af6ce96363e9ec46959a53869cb954d2fc5d068a90359cfd7fdc86c278b1d574657e0ca4685661fa81d58744301358e60c3a228626c4ac8894569892d2a8f1c71cde57a94b2a8a88cb9a2c06822539e1aad35d062096f816e3384b92b5f5433af902a2bfd7f97b02c7733c8f8f8af1e5f8d773326fc20d775ce34e9047c4915a725607157aff08f514d1184ce6928abb6b0d39fb6b569d1391fb68c83ecab34a7ebb76438db59d2ff6e567a48af888f60c27b70c4067b20f75bc7a07b5cbd1655a768de92ee5b2f5476bdd815a9736158c7c757bf4618d1d18d2efc32fda7f3d083354ccef84b7a4d1c457e9c5e9ff01fc0c2e7cab690277c5cc5e72d4ba03bae321472a89d2c54ae3cda7195a51b6168718c23ee689d4cef1d765eae266bf5426386a6af8486e9ed0b79bd66577de2b69bcf3a6b7ba12fd672773a6b6986d7fed0724e3862ce34c2ff2239903a2498ebe2e652b172a2e8a6314608ab7d5ea9137fa3223efbe7c04a4f23f0cd36a6f0529952c25951c458a83ba0b9de9f4b178258286bca029108e013362bf2312ecaa5b12a5a5c3b6a1f1f774d3dd4e54072f2f65d0e5945ff792e05173e191624b230da7a20aaaa514f8e0df5c213766980d38ed5102568916a877417dd5041c950fc5c88ee898473a10a0a342f74fa4e832326caa2a8f16a200dabd671098a36a9091d5f269ea23c23cd26702cfeab76dd86335730682f54d146dfde1c2ae92cd4863e4301edc51afa9e8d7190e5fbca1580053e714542b7248f632153d4954d3aa1c597c923b4252aa841bc45d124dc4c8d54bb48e89f3d48c649d31a1a988146fa72f4cbd7253f081336d624b14fc2b62b9cde8696bf36d480e1cf62a30424946c06aa51d93d0953aaaf543db15766020e4888014e6aed9eb87084cf80596cd3aeaaa910baf60ff55cb086c1a1a5ee91961627cc45889c3990fa842c6c08c76dcb2b9115bf0dd4c34f420ef6d927f971df43817d7dfaa2a75679756ab9a990e22f7054d1915b6ace483c12ae09b4e09964cb7724e92c928c988ceb063b1c6da6ca599e3aa1c064a6f00d6c3f39d6837e5b787400ed8fae4067552115681e3ed505d4c7fec01b0cc4aac186c962d010ba1a5c972e7c57430a228a4487497f6759152bcac87570e4644657bb559898d272fb04c9cd314c8700ad6c4917290cb58d34ba989896b5164291dba324f5af2339d7d9b825f4748446e6680f0db4e3a7d83f3175ee6a077c94801272c3452e7c679b2bc9e28a87560ed884f349675409aec70d5c53fec3f980bba6dc8e681d8d05b8c42ccae51a204e7b7cd8cf38542abe43fcd733cbc172a0473edf11aebf7be106b8c0f9e5bf52f14ff80f83a164a677c2cfb016ac30ae28eb4fc8a7a5b2401a50ac882b223f300e0b22f5fed4d99b887805e581814265d7e730bf1e57f41d3865bf842b4ae80632e004b1b8a21bde42c776efe64be3ac68d5aa043283e1390b21aa759d57d89e817e7454f95ed76bebbad9426114963eb162c92344b3041bf729bdbdcacac07e714d16b775d8839cb432bd9541e6873ee7236e47ee27bf6dd0c43cd01b40703b0b03bb243323aa34df40d213a0c3fa8caf5fbb6d3d04bec6e5ed2f1b2754e5be961b5e98c658fdea053561f7358a5367fc9f73f93161d0d49807b20100dca7da6ddde64a9e0f15e92b523f687a8745b8f11ece51d37dbd11a094008ef235c8326c7bf09e262759ce755f7936878ed6b82fa57367a53e243977509a0602b59b14786bd0f2ee2b1c909cd7bbf1f597e9fd55a8d5b6d87f07b8602006c55dc2cb3c3c03c1ada255117dadf5064f43401ece3c8c2eb69684aaf34914793ee5dbe7bf3a5875349daa6264b55f9e3f88ccfe567b68d3220942e3e3d54327fbc9329de1bde43c4f4fda2f5045f51ae7528ea79fc7be871c32a01da368e929bf31b7756ca174b7203fffbdc1cc0fa5eb7ff71f066530c5b8154af1960e66db0514d0cc0734bc5e7ff0d94ce19b97f4441d793e9eb496f480c4b29d7d2e4efb862c5ef4d877e6795e77cc39e6314a42e160ef51d3f16e90f75a9648c8a4e075135e78eb9de38db2fbff5cd203ffd4ae2304fd3777117fd4154d618579dfd2955f5c88beae3f5c26b892f6bdfd811c974c7210b4dc83260a5d4fea4566b5d6371b1fc2fea5a15c801a16fdf275e17014ba65113c77d79f7ca0ec170507da136006220a24dbc23949bd644c8a1b7d26fd40c507a0a328d8454b55596046ed3305bfc22f81ed4bc3cd88fe8b0c00265f7d2d83979bba4f33be987f2af6e6d2201521a06d3e805e1c7147237b309d9eb7ac0e9e93f0b833146d6798f3c8f2c5ec742b43a2413de78b9051f8aa22b1608f247d979663670a3e2ca54d187ece2734413d79c79e2e74bdaa9dfedfe2b9304bf46d5d3830a99edb9f9ef76a086cb06816d66abbe79bf711dff5bdb72d2fd4dd7dc2de0fe88b06dcbfb614d035904d50cc3ad799f7ddde0a75c7e081c8165c29b608d816840a5ff49e66aeb68a6f482b429358a8e00ead8364a78a441074e3183a9da92466674210cf93093c7ae62a175530abe7337c0cc21b152b199ff471071dbc1fe31d150089ff7338206fd9954711fcdfaeffdb37242fa757fbb192430e1559069b8f39dcab3945eb75b5cafc46c88fdb865366efe5fa516581bdf7301817f3f55e68c47ffa60a4ab336aa57e114bfb83d2e6fbddc5a1d9dc9e67f16a2fa66886cfb6a1b5ebb5ae8abd8a1e6824b08b3a98f9e9f8f2c2c20c8264f0a24334cf3380909082e4e9df7df4f387f4137cad1aefa28da7f78bcfe3964005225450bc522464aa646cb295ec863c1b06645cf590de5b344612639d96b73c30250e6ad691eb214b07b5859f253456c9fe757bc9f38b6d63a7d5341774dffc1d004359e88987ece05989ee9ec2b33dbd732a7ad35a267fecec16de07d321e16241ded0ba67d34b99a11bcafc577cb49ecc3d241f57eff58ee8059e57afeecfd766232a2c18cb06cf22d5323ad3708c77bc7fcabaeaf94a2ab3ef4f58d7626dd513931f69379479b74f081543ffbed38db4a61bca6c9c1cab30e5e6c07833f8067b3c92e216c23318d7cf677662da646b1ec91e9459d7367c03a59ca7f5abf317cdb67840fbb78ae39ea6920d49aa93449568a431686c9c8028ce29489ef977c7b90fd78fc58f09b7403f4c0430075998a41b7aa7d18dd335c3ca0a47d74f109bb9524aab6a57cc019ccc563bdbdc5d00ca097cda449f2e92f0459271939484aee83fd1d51f6a7519fc03945818aaf835a1ab726faed994b6b223a8341ef1d09085513872a2055b86d2eb9d20e986cc9a2724309838967271942bc78c68c6fcdb55953fd9da2e6c97d3791d3c1942967fd82424b6f3fd4a8db88c4f948f385fcc69934d1a195091a518b0c7e8fb35c60ced3fcc545edc9929c1a8ca165f59cfffd509ca85ee81d6aa3ecfda056b69b9defd0a19795dfd83c0498831763c29c8316912a55e45495cfd1d2118a2b6efc117b744ee1f29711a1c754660e570429c1829a5f8869344634a9d6b05b6c9644b61aa8e94985ea5ef59ef5a67b54f60a4c450b850a1b22391df37626ba5c8e2c0f2e9ffa14055513b47266fcbc83b94153b366864662646f9068d32575fdd73bf699ae90a7b20723672180492b2ea3525861ca0e6014cf98a8c9040a470aece911cf3192737d042ec851c9665b7f8bcd830040162c594b354234f1a1604ddf8c3105712f4703310f4bf5efed90af739c85e4bb8f55328f4cef5f9fc3bd2c11b6a751bdc96705af171f228c473e2bed244103ebc02682c633b3188389cf8b8896f0c847b2e3e460dc3aa4ca80f52812517fd9375231178dc73c78a44ba8873b038d030154c9faaecebd50f1da10f1eb7f69729554025e88dfe9c1cbd137ca9e1ea4aba164863ae4b34dce136292abb78c072ad94a57dc5beed0dd8d6401f7b527722b7175316165f47b75c0b072f16ccd768e90c8c45278e3a91000192abcffb4d21e8189852f8a3f949282b618cb34ad5253d7342799692c3daa5ad070bc006508fe1af4b74ae7bdeb856141f93e514cabc3c834c5ca1b8755e0b7d5ce1c6ad6b07df4c2a0e8982fe91c5c2a67643c3c3dd2549a573bc14c6b22f87631dc01d4d3a049bb68de86f5a04339d5a7c9b8ccd68c7013629a2f850cecfeecdda5f4ea36449785c0c2a5bf95b437e873a5ee7fe6eba6436eca1ae50c1b03f47d49325d2dc40efe8592cddb7b3e1b45e3026aed7aaf5e48da524b0ecc9ae23bdaf29edb767f5a26a331bbff04ddb492f8a64e2a83836d9ce136910aa436c85b94161d95e43e1658323f45ee2229960d7e7dbcdb3c42ca206850f93c62a62295a7c638900b107c37de0811c8840042eb0a412ebf9ddcd37589a3303a31c373e45d79aea6dc1f39aa54dea88066f3fc6dd96fdbfb359b780441d6cc55042f562ca2bfae51fc854271595351e4b8f33ff5676e362a6125ae6948933698b9b1a720353fa6efb4b1b62b3edf9da12240a9a401b47de63744d0c05f070c83f0246c98b92ba58facce6bdc873f1e23817988da1232887cb26115f65fdf03f32a551faae98d1beb2bcff64390b57d82b671887820715b043cf87728889ea8040e0cbc17f8e1f03d6c2eac5d8c1de7ef8d98e645d383be6c835f03bb300d58f102d629c47a85f8777999ce2c34da9fbeefd3659993b93fca6d8013323d4b37ef0934e7a53bed3251e78b36af1fc4d48fe3b09f6747c1cb5d60336e998aa2fc593a0a9041593b79d419f394d8eaeb4cc6981c2c7d80660134120b7091decfa710bc3ba99cb92b8f0a090e39aedfbdfd6ddef42ac24c5027b50a1c4a8c2720a8e51ef90518832ed4de913b6375c3306b80a7c065e1844f8faecb5ea5f55ff1e08977741f0e9e2fde7be3c0108e5afa93a3c70ad72163d4ea97027c3ff2aea2f120837f41f616b48473b79df8cdd0eb8892adc2f1b17f37980e326e1ddd21085693bde25b9a3ddedc0c0551af368b0b88f98893468708738500c1b15f5ace015796b38cba948e7f533bf719c3ae414f95c39a3841e5430f93524c3e38f2996fbe93ade8dee9fefe89b8e1e2576d135137f33e5d669ee9a56b9c61e05e97e61da3db18ae7bdf239fa96c8b4e114b97f02f430d02f87ff370f51fb3890ce9ffc9a58242d990a93e9584d2e98e2d9a473ea70b91fe43818552c6c2cc5c1a2b02ea354b27cf2f990ab36b0da8b51076e7c3b1639c7e81852c255b705b96df329941dc49ab8fd475f2549125ed27d70f306edd9a4eff8b79109506d9ba3709bea94fd03fa4b7924dfe3399aa09bc7342eae1109cbb5bbe4002ce11e2b38fad73f2abc6c86a7c521a1ee8cea929789d39ef76624e300ccbf422325fe99b69efb38c3fd420881c02eb3e4d43d3ccd6814721fbfdcd914a02f2de106320e7ada640e9668a54b5e32480f1a9da13411272aafcf202d6f3c96578c111cd4acc894319b27b94ae244d70ac21740f12432251071ffde208ba19e4986e01066f70ea7f67d8c8cc78c34f0378884362379fd124ce5d979152fc22b2cf4aa630d5bec3020204574dc975f7a1df3fbd243b0e934c6e535231434304ac4a291f5a2f4e725b0648b40ba8d9843cbd87c4f471980dcd35b18bf4e09c9e21852be7de289303183e7575f38444f058ecc4cc6a491cbb3bc0c2b875da9c8c05d66ddb09e4c2d8b5cf6efb1dd3dfef0768ebed33a9c4bc832b2239d0f10062cda84041b8882da16a23d1237cd4f70288638cd85330e97aaa90800b84f05eda706f012cc442f3dc3beaf8088b1f3145ab54c3f9a9558b23bd2f1ea4883bb64e8ee5f38cd25f566ff570988b7fedcf25c69701b1f48c7d96c2518754996f0fee2ea6f9971363fcf3cb7378add722801b5fb731e0a197e04203720dff274eb5ae75a8b91be936d5c2e0d1d78acc6c16dca9174097a85ec3108a03f1034f1aa45e0022651d678d0cd18a04a84d43d068ee2566f452325662d274c2e67900a85ecc0476f1f052f9fb2913a393f3f2c0a6118939084c9b9936134881acf77de67f3cc4b41210d4c984aaa7c0b5384c8ed97d9e61be5ad2df0e889c072f84ce64a5b875e32a7ee0403d843dd2f56613555f9919d3078cbad61f059770655b5644dfe3c05bbaab56b21c2a20bd15dc85fa56e6517bd343097f6bef4bf8e710adeeef020051eeeba7c10983d8e82d474774b052cc0484504a1a831a1a9888c3f7128251b95363f4b4c07d5a7e89ee555e1bf2b1c4e9c9b030715e849369e8c3956d9c654779ac06f07c1ec3879fc76a401a7f8aead108521dea63a252fc0a453387c2b2e8a5d9c267c538ace8fb66dfa9b21c6dcfce0f9cad69f2f56c2e15c35ada0937078fa86d689bf132f599c3ca34e4256875ce3745cc8bc7434581f9e66683e999369f5c75930980e367d99dc0340184c6771f4d0e5a3f9c7fa2d382716d2298c3e6029208c50b3e73b32dd4a0af8dc71ae9e85fa03c8ec81c56cf369eeb60faf7f9c04f8a053511dc943332018372729fd368840b925e73777a7f177018ae09ca095ac19eb84cc52153fb4a61384cc11059f62a2728a827c5ce390b280cce72ced14df6eb3bf2edd9892b42a384dab92a9e47f84ada74a2588d73a0e0d645885a81a08881bc6da7b89c48a16f042e18b07c12069ac97be7bd49740a5e61e918843d7ccdb41b41073670b798a61e7ec5126b60bf443b768dec77cea7f0ee89f921f4e20807f58037d0a9bacda27c10c385889bac0a0838a6ee245f69be64b956a368963d4ee034bae244579a5023b7f34a1c9eb63416204ea38a52d240a0d89d6e9ecfe38661209181a88109daf73e3e0d1107921c9f235e0c34f12db0134e090bb0b7c3f579e3e05caf931f31d3ce2b905ecb39131737f3facf5b26f4fe7e6b8c04c84a014864cac690f9189fcfd25db6f85b133717b483263b4d36e59f34b3a3d9103922c5cea5349db183e881c3ba97d0e5091b27a9eea32c62ff8bde0d526c633fd08dfe2a581d9f764ea896c77acdf4e0790d942b9aa8993ddb8e923e2d6f5c4109361aa00579472ddada7bb82390c0b8eb651ff5b1bd2604fdfe5acb6c4767ed8e057c01f34ae6772e8e8b23fab3cfc661474624fe477f4960cfeb884fa353983725b2b2b4e94af1a2c939f8f24ab8ee7ff36adcaf33b46eaeb29427c57c49ee3431a027b635433ee893538a2054db5641063858146a0f7f1ef039f70c0ecff47e31ffc53da337a302aec793ec52a2c510cbd57f2521645fb87a4fde6ec2af2958d13d462747a9a5a86fa9fa0b99fef00f829820fb3a38f2a23c4e38a468be9683ea87429b9abadc38c4427cbeff6b5ce6db791fb633f888a2ce1501360d121a3529f1e5e54070a837c9ca44efdf4667d6a7f2f235001345ca372c6eae26857ebe89973aff7b17edaa29d05af280ef3bc3bcd378f90d5cf7ada6955dc5ea75070af06b4b788d39f9b05668e3413db5eb15073d03f8afbf43871f6e0214f9b525d374b9f72c3bdeb6266c88c5c92779b2a27397014158985ccaa84069a064347df1c1846fac4d170318f5be1e5eb56cc61985bf5243c3885e1cc1449ab69f31c9c261a61dd8a2f0559c7bcf9fad0cb5ec185b3c944aeba2a9dd3b8cd83d838d3ac641599a72c89034ed58d113348ab64adb0d26554cfdd40986c7bf7e1f8ba2db85d7d5d3cdc49ac9e2f989ed13136a3f01a7ae460786347a082a347a9454141c718000c458a4c5febaa45a8ee4bc9eac82f31602a47c8c13d94be9e9cd3e741c71a6e2a90687082ff2ab3fbb5cfb09d95ed954fc2b02dbfe41ea90b06cac6a69dc218ce6c6193eabe4af365c0271ebde7746d47474c385541a698baff630a7e91322a6e425241a5686d6f0a21333fe0a7e87e1dcae29fac9545859b7e91c23ad042df0ffec48b03290a4f78f080e59e3011f13c1325dcb1af514eb19cf4159741b4befe620486377f5f8b0994d19991199a327825d5ffa319f852522c45e21683e32bae8e1304b46964562f0944755d64b45b8e8ada1ae86993fc95d9aa179bef28e74c30e2983628a85a36f4796315f514894bc0fa33971c25bc3fdef25ce4c605d72fce41993991b62f35b74d2c6c9d91c3463a35b3f384a57b6468f05c63e06b711cb34ce621d7e989ed212026c83a9d71d8fe6a211d7205f428ec50ae048e60651cb8b776b75a160b2ec371b34fca664348b5151b1b52a8fcb352f4dd2c264aa13c0f973c62a28d39e031ca875c1f0413cb27369a495baf622b519082946dd111f3256a5a4b316768a9d8da1b9e6606e279436b4db6781f676e2e51f0035944b83b796c4f4150cbd94664161873cfe8a8be4d6443f0aa7e048a08ce2f736e23df47550245c63c91bf0c590b3832c9acab6dae5ee7bf3f210175e4c9461f75c4ab93d425fc63387697c7d70ee5d9a38abbe986a676bed2fe4d2750fec5607418ebd0cd82e36d93108b9a8e55c6ae0fe34dc6bda482e31799fc2b095e332279b7dae068c8af9b01069cc3d1bcd928c1e3c815fe98e4077aefb810e66aa40c471f68375e60f8a496ba81edb5692b8575d7abc8c0b5a955dbe6ed69c7781343a891a59671f48287f466ec02e3bfdf9527290d00b46bdd9f84dbf3b703e55b97b9f72bde3b1dae0f419fa40c0468fb727e090600ed503a0162b711f23daab2d8027c75801e12355686594cb943441b088dce43c582f53177a4f119e97c02e9a3e6155204199c80832fef00b046fafc8b1bf4f56088ee6077f6e73981ad5a235adc22d79ca70b5bb6c3918956e8d27aa39cfa1876680302a58584627516896c36a2666db867f12074276f985ba1267a696603ab7aa25b2920b204c621ae740d77a143b0a95a6d264aacc87014a0610c1e92a695daedc44192b7c893b4c16aacbbb9e12c9c3f3e722d617f70f4a5af6df88003aa8f037645b70c9ad9802e788223153dbc3b115ef5ebc6eb1f2536bc8c9fc190fd565150ef4b2f46b79c4ff7aa256d8943c626631a738fa1eb6e7327a281b7a4bbb53fc340ffd8ab47298b52547a04aaee5d1106c81a9e9fb1b8ffc16d8a40d0bc83650e42485daeccdf108f363a983322a55c7a5133483af8609d56860e1ef5c78fb75e05636a90a3641366dfa081d836a88e440416cef8875504a3c5147ce98cbe05528a1e8e229b611e3bb1a28341e47a196931ae1fbdbba9a92ceca542caf4db7ad4a7dc99a58ab8aa0d24bc1429162bccdeb9cb30530b9e5cd3ad4d1618cb29fe3ac0726c71ffd4b19cbf61e1c8e3d0867340e6ba57340502207589c7dc237a0409bf1b1fc6520b372e4e6965bd5def18342a8e8c907d2abffe08e5860ba01de9a2edfed52cf2432975a3eff07bf3679b34e872138993a4e5310a2b1fe5bd48bdcc481a8bb4143389bd73a7e7816290b33b9b609301ea29380fe44ce498a671954ee342e55ddaddf8d2cb15c4a3ed82ba5c5165854dac4f7fc82f82c37cd77022438a517fd01628f515e4c872ae25d8af1d07310ae848c36651d834fe4d1fb8f68fdcd2acc2bb2fdc9bead38713e9668b520900f531cdfd272ccbbb1b72e6e677aa66bd538c4f82eb3a1d5d349febd312dc577458ff8d397c3d4883e21c8f6a30cc0918db0f457900cd49523b067973d599de240ec2cc72c0b7ecf83c1ec4e1daeae01773b839fbfa7a38d521d75e18d08799eb645917a6aa95e548e0d19e0fc516b1f962da368b9dc33585f4c6243334d721d31ec8e8066cc924152fb52297aa3099da70d27aea79f482559c47d273d07673e0be0178d90800bd57e979095688ce5380d063713cbdbcc91afd044aa169e49483034fa0b83effb162071b9575b18da5aa792310210cacd5f06b5431d8392813aaeff41b4b52c0d1b8a0fea18b84e0ef542aee8dcb2272744eb6d44849e0a1724120f39c9ce3f5e92ce72f78cd9ded2eb0368dd331e215a619e55ba41ba651779ad324bd55b3eec020df8b3c5bb073854edc765d2cb7b8626506cdec7ceaa61338df51e137d69575a91e9d3f9a3a1bec997cea751080ca195b19483ebe8ed1ecfc2c7af3ac0b33693f105a12def795cfe64f0adaf2c2a8cc04194d6c88351fe077f0d10c8af5cbe57d912203632bdbce89cba418dfe6a2876b287bc5fa8dbb21ea4bc167b19c92973eca612694972d2f9321ab8b50269c3ebef7b3310204942ab828a2087744b445c042eb69383b451454232fca11d333426ee9d234af604529e7899048f6a2490ed8a78faae832d20ce3f3b31cd1c08eb816c8654822aa6a7e53bae05db4ce2894667a07189ece585c684b0d66ea7f3ee24a565012eb132af3686c9cd777fc9deae96c5347e292e239509e9a0943ee7e2a677d0bede172fa9ecc2f940e8038c7e04583c1887021f6a8910b9bd40bc557f6c729dc6df6b170075f0e2df1a62225ee0ea55fcad3aefe7c68a56f73408fc718f0d5a28aa3ddb8252e50fbd6986788d8015e365dedb90db8873317fc472f6530aac84d869974a0cc69be77959a3529836ab2cd5ad13be0b7b99949ccdbc320cc3b07df37f784c5e57f4a1fe5f38e3758cde0743e07c7e419d97a26c9f2d0503eb98aba575e723ab0c22f97c7320c950f9f2ca8af1208814f391d1beb29463fa68e3e5fbfd035b745477ec012c51cc8269d638689392a2ba7e658bc84c5bc8d0a0841843e79795bd323fbf68259b0fdda0c5c952736c6f6ac133f5ae4d5472c9e3893ff81473ba53904506a34640e3f6b5cf32719923eb8200607aacbb821d97fc6bc2abe0956773d1f6c6713ae40cc0a806360e838d5bd9d515ad6424a6ff3fd04618fe36bbe06ff8ea461fa7589abaff81ee8d4507eb1abbaeb771043d2989acebad62c6be310eba069e57f0269b5dae895e26498b4b14bae39fab4625f31739611f7b6024a43316c223fb158db6b13b87b21166b7d5e6f7606c61a433d7d64a173c696a39ee79f5b3b777e20ee67e9f01fd73e2ce587bf0f6d52c77e4ec22b69481134d07dbd952217d3e835dd220d595cfa7c24768dd74c8a79e63391d055e1aa9e8ef04d0291fc594d8cbaa596df39fceb4bed1ea6a474f4550e15e377f9c21a6eb31a97c682377ecaa5882fee86de1b854dca73b0d9233923796d3364fc3c743ff62d94201cf0f30c5cd2370a7454d73489db1c13c76c5c0ba8d18984cc1515e005d13aa83f23a670fb62650a576f4c9c298357085dd8d321660a689aaef300024440ed6206be6c034dd1b63f2bfd5e192e592d1793308e2d64096bc57736e9b4433424fcea1120f7b0f90cc1c30d507e6215a81913301e22eb1cea93af7fee4621a287a00c2588208ec024d5196ad82f34a8317457292fda110b7fa235a7525e758365f56738fd180d5e1b48a784203e0554f30521a834e837f2bbf44a7db6b275be2e2d185d9dc7ed7cde7478ea523ec1b987edacea7de36a9728a8642729adaa35c093f22ea7aee4427e7afaefdbc27487d06fb836ca7c7944ee6d0041cfbfc9634ab6e2ade2c13a823cf00d603569a3d0c8d8b251688860e9e225e300ee3b5699d0fc69afd6e6a95292304ac4152311359429853824dc5703824eb29309d5781aba626ed73337dbe189629248a99f28482f4157be885429c89292ba5ab7b718c098b47bbd57928c8c0a1261602ea3e866d3113354b00127b2d4d123dba66bbc728f7902440a3d89e9c4a0ce39b2aadf6df50dca410d5ef42e3c19fba01650c2b1c3be5c5ada3aacaa78f06c4f06d6b4bb887818dba24ef35e48df618b74d70cd5f32216b07d68eb88c621a148fca0df027b9234fd6a8dfdd5c31ad44d622353730af211761c518058c379ee52fe5e48250e885ae5d70175d768f9b13e40e4067bd75632c2ac3dded4587d9575b98bca7506061b4d270827ac7fd859cb75b3ace21890ba35a4d0a69e4738beb255d10192f64bc13ca2042393fba8398b28c59ef80245036699da8fb95228e29f682fa549c4637b8f2276250e923faa944e10f947d7a4a821be89943fb9f1911fdc38b430a3f3077a4bbf5a8617e34f2299886a9441466cb2af85765f404c837861ddacaca14c32e31e5e5bdcaefa9186fd774bba26863fb2b8becf7253ab7ba019541520c81b154fc9fadbda940f61cf42169cb27ce500d9f0edb6f3dd8217e0af71b662e4426d2b3b0d7f37e935a94d17b8a4186ed544d6879ed22edabf946bc3a5f4535807f7670c007d52bd4f466468d947138ed1a847187adeec84108a1ece2b01e184840a2a24b07fe60cb0de83dc76a4cf99e3f4fa5b4a34b340fe9312bf305363bb8d97ed4973302585c773d97e4374d0e0d5fbf208d716a77d44d31497f5e80893fc2d044d48f91821264968f2d9f6f19a2a3be8421f4acaf6459b455e5d76610e443ca66f9fcc659b4304133d5414a8bb875452984ebbc5e703328864bea78478a5ed3897a296b0037d90d151db3f79c19c64f14adfe8539a5463e2548236085075002cfdc6a543719dcab52f9c71a67015aa0a0a295e5838965a109e79b2975158e2567c0cfd11e51fa9d3ae9aadf2e1f9bfc76a0d4968ae2ca1a2f90fcea9689578b8db443086fc17ac09e411eb0c25aca4ea9c58fbe81ae7c62763d02026f1c53048fae4135e7c500b5ebee3e599e29a735abcf5e8ae43be4878be066f9c37f96c727c65f7d525ddd273555e0abdaad4a84dde0f3c27ef099b872421b1dd1dc53879270dadb699adf06ad37c533aba059963a73dba4ac9517ee9e526a47282ce14e9de4fb2195fcdbafd5cd7be8fc008c198ea2f2c02536cbfa0c7d9a80280ac6fdf6743a28956477bdd874a1ab2ddc4a6b850ae75d5eb9a10a5a891562f62eb6d888b38cfe879a45efce135cf1397d004fc36a1a1809255c85c55140a610900de2a9898dc490df6e01c189d5dcf7338e2851455dbab25d94fe88def9aabef3334ee4a1abef1bd011b49c733da7bf1cb1111038889c82039cacea16ce858002eeed57ab682055cb78a514846c29a8387c863e49e31bc7c0aa4e1b83e52e1e40de5e3eb52fa43d61756022e82b26091c2c5171746ff28b227f119080392384a55764e35f50f1518384cdde3844d90da4b99eb4541679e6d063eebd9b7d75885fa6d73442a94be876ff3c8c45e67a70e9947993a756a445589a46a3e06bad3434a89519f71d3ea63c8694c583420b4d498e899637102b137e0e30f6c2ee779584a9655a31bbfb3838ed5f0184bcc8e1d74282245bf4eeca888ed708ecd35316a74bb29979996000f90a6e2e68d6bc75d689453be5b9d13ee6d87bc647f52fe98c51c1950dbbc015e70be8b4f80bcc82da7d5bd0dd9d29f4726a793bd6735c9e56c7f3b233e490325c5ee23a66441abc2bf3968f4e712f115a154576090055a53250d52f0d16536cedeb34e389112a25b1a0cdd0cde0eb101a37dec22546157704f411a7ed00175555aead7f0da932532adf7e8512a3dd3f5bd911eec74a1a2acf8fc699c43cbe7c54490b88d3ec4f4cfd08759d58c27b380d05f28b6d215afdc7f79a745dbe3553145e300bb017dd2f0a1d8bca99b2a939229c68cae867de88d8a17dc0d26ffd6440fb183516b384fcfdbacf27e2d0e6fd05822c886f872764ede37ac1005405f918b590dcc916089fbdbfe3266f7d0e8c453fb74308a736ca14e46e04e1645ada74309ea90cdf9e45fcc2b6b80fc9e420dc8cb90ab4f4f83655345fee478cd8b020f500be19eaa8ee3e741fadc57a06f81caa08204d5f98356eb94bfe786eec711683f89a047d04a6ea3217566f3f25efc9ffc39362e0dd5e37260139f13dceb699153cb33a16bb246ae99c2cc2ff6223b309ce204384bb946a06ff82c4ecdcf5b5ab0e8cef268af51d8d4b9a1a46565785c5095071e48fafa3875e2c297a01b04b6b8e2bb56a7bde31d60462e1e4e28e4dd360b762b694e17c96ea150339b417c6dc70166bca55cc2639a71105366537b56d93a81d1594215b46fd0d13c12b099813f5a176849660b686f5464995050581da69763eae0871cee2dd0576b62808af46b46120d2d572d049c2cc2cb90cad13860adc57c5160bf3c1265ec28f1741a5631ade49c354cb05fb8bcc2bda74e625c93a4e5a35d52f48de12b4475bd4da1e824207d520942d1a684f4ed9e6ae958faac307fcc16013c9075bdc351d7daab10d728efef4dd1675b94c4d0aab920afcf7b86461da940a8856be0485a6dc2ea3d33e246d8003948d2f0a5d439b7a8727988d3471fe89616c69bdd9ed831eb19ed9a95be1d2d77d6970ced3557cc0ccb5a0def14171f84d2e50cbd4d6c0f36782089041fbe7af430408d1579ee94a443aed3cc782a96dd1e8e6c6460b261026f4d41bba4f93a14a18af6ffe9830f2cfc341330626685c17b10c592e7e4df47027db918bfbfebbbfacccce51177392acc77ff964e1698a88166408d18600a6220f3b47a97665e2d451a2e6a65eefb6826fe9fe88d7585177357aa4cc9e27d7791bd1763f26ca6db1fb0d1b837a3304ab5965ecb647f706d71f6272977bcb7ceb8e2354db9ed5bdde25f2ae531848fd707f8f0ba7adf7f099c7a454dee7f856ea1c31b06d606ff97d6e2454cd366cd78bf04c6f22c8b29e440f9017c2d9181253d76ad9017c0c7c9f233f404c97081e913b8310e937c9eafa57d53b85d07447a81866fb17855c8ab2b8781466df59733d46a4cdb4472d77cc675bd2f074d9dc80d7b06319f14f4462c48cb6ae27dc625ea8f2e8ec99e9fa95c14a7b0619f82a1da97092b2b1527b77aaa7bf222358e4012f0cf65def2a460ce3213e77513c405587614418f68e66e824874803311df3f999ab7f404f03c9230cbb6773e8c8c4cac9ca0c603ad1c453633f902161170f784ba0f3313e311d89620cfd1fe5c73967012b81de8d1860266bc739139895e911f38ad04664f7ced96d4394630d4d28d23e40a6acfad442ef3044ea219504bf1380b1202b044e85027ba2f150b832165e90958ca59940ccd552aa7a092cc85eedf8077c908482494faa30d0da37ba05d8f12d69a0e0d1da25a451dd4720f0d25ac863a3d486be908a1d5a17e35a8aa0c89a0903af060da1efa8ec99c4065d726a22766620feebe9e9e74d7fb2ff917a279ab3e45ba2f5906f8d55d518fc00370f112a370edbf3f8713fae71fb2b2621786c335d14a49f338e75b99d6e514616244ec244a66d9e25dc3794f4b5bb52f92e42a309af3aab04ee66987a8391e746df02c286aa1005ffc7c1371e1cafc120eff56494869567b6502166b0293b089f3066603b042679cf92fffae2ac317fe16bf4b87841b23a1767558bc9ccc70c8d050ec6ddded14b9683fd28a783198366175befd99302c25a4e886b5f2de63122df975a704e1bfbf5b97696f6905cdd1b2b2035814663c59039b23ad60a8788b6816a7891e68bf12f2e299deb4b401d151425bbb06b4005d4f8c6d187399adf053163131fbd744851aa9deb41bf1b199cb9db9051d13350d9f75daf35e52bcd8a79fe505fafc960f09e54f554b3c2c55f68eeee845e9c28506c543c7fdbd95211e55300f1aa41c2f45dfbf337b2167c36313e879aa8ba21ea14e38da60284fb37e68eb58fd5b57a2ac205976344fb91e0bcf15d446bb32d85f4f70dbb6df52428289a5172134c28f8cadebe84984a4a6c82017f34a780611531e3e1a491fea603c54cac59924069ef6cfb800d201266c45eccd40e93adca972449c8a6672337b2c254f6e1e1adb857845b4d3322c6376406da41e0c174a002a13a5f0738123ea8218a1ca670d87f1658d0ac263764c48e23c3065d564619e05a68632052688c552a6f15e77cfc9f0652cd214f9c0044463460fd4b562031d842da2e40f58584614e896d0cedec8a69f4021aab53b942b259b39e8c42ee3782165f4dc43acb1332fc580fa39df89b55ea0f931238a557c5b6d88ec93291ef0eb721ac7d7bb33c74443644606699822e74b32c1de3fdf2530cf44cf1d12cdf76460be0cf8877a50c075648b7303eb9d911e959511b026ef40f213b1cf5f56fb3bdc44b66a49e4d627ce35e7e52704db1a19fac9350fdaf64c6acfde2c9b0633e8ae8b6be2ff36b27ac43cac1cb2515220e5a87738972f325824fd22e6b7f8885c69e3042b7191d61b004364d89ed3ea65a1ae340944628a3e6f90c4aa194bb33730354d7711b16ac8cd7fc94c1b6589b151e3e0d00d25346654c9103b9de31166288828e105e0ee7b56dd7121542ec7c0345e9014309f9e5f86dcf932b7892805d856171551ab8840559f3e7f0d24c63d900163cce1f65a5873bdcc6546902a1957a68bc1407b7e52e90cf120ca87e05d41831339834858ef80db09fac0f7ac672761bc048554d8f1f9d4a03020d27b1af27e3b2b2f981af778ed674edc8165745239177d9613f72d6e5d65875ad5085121fa920b6769a6824485b8a73352f602ebb4ba36c452beb37c52f0f6a8d9fef3778f531d8ca712100bd19d36bf71ba14dcf2c63060af9c3d73e58dd67da15029d97a6cbbc746a84e8600913b1320f4c86dcbf278dde947f9052c4a865af96426c0ab3629ea190de96cddf59552e86e151a200c5ce043535fe10adaee7a537a9cb3ebfeefcd0befee356719664f43f6fce44de17865fa44b430739caea90d49129b841e6b9840faaf7bf319c6b0ae631c28764c719196f3454a2619ff5db129616ccd6ea684491e83e2ecaf26d1a0c00203de21af9cef4509b79961b0c3836889ffaaa7eba7fe8588d691276461612240477e115add24f036438bbfa9d2aea987000cde69bb6a72fb2dadf4aa66ebe3892cd88c34840f1e11f6d92a55d90306dc44d5f2fa5810f5f76c848d2d07ad793b66137a1a9026e338aa0c29d87e1efa095773fc58f4324c506bcad8c3401bffab14a606f67dc5bcebe14af28a5d94c2cc2a112a4ff156a2a901394fed2889d0b1f2474792900dc0ce619cb9fc681d01065484a8e21402476a1cbb48ba9e7ff611d969f839b9259aad21b3cf08f6522a2a72fe6e246a9c41bb2c6a20f28341712ac448b9ff69a352bf0331921932c4be73e90d063f543644a96f76724abb6fc1c6ccb2a141a03b506fcb8ac4cf929ed51c084ef662903b9704100540b55a488356dc7f082e1f0975556658e008b1b15f165c7619778e5c9d8b582468fbedf8b4473b4b4f175aa128ac47f7ed8b81530b81123820f09c5524d56b03ac7826fa3df532f89b110c4fd818382b1dc74dfb5421dc351e03231a3ae21aa500a68583a769dc4c29e6254e13616fbb41c9315f02c13eaece9710cef714357ae4ae8ea14a27adb6332847ac19a906a97fe12f91d367911a4c470d6c7ab1e4778b3d915a93454a7e4e59bedee0e5e5a14dfff5a3cb6208798fe22ae54a128867ac81f9a4f452808111459881a02789f266b838cc1fe7be3ce8569d2d8385fa19449c9884342249dc2d6985199a74dfeded4ba3ad0724308d5bded47e74ddf8cb78c1d4ef631c1fb143cc168d1df724e697bb7da3b8bd3afd3dceaa3cb29d1c2aeaaea024ae9b9f529628010f8afc4130f8e3287688b8d9f2a823af8a18cdb4819bff816ed0fa95048cb5c6ea13fe848b468883c41a09f17e37cd1d018437d1df080f507c0c9822cec49734568e78bdd684bef6b8df10cde6d803aa8e45593e8bec39a1e80127a239975046f1b19c6c6702a0e8070eae103cfbd5515faca38f097102d22b09525c7a8aff0054f77eebf5fce9ba00a226cff44384d256d2cbf646760b0a421ecc25aa96d1647649435ce747df8fe1933272f44139f2d2f37cc721e013015885add011d0208d0795c3607bba3fe1127e3a603539c272d62708d52d2f37d880e3726b0ac55b7406c1aca5038d450eaffcb31c88b71e91104bfe90190e03c50dc485131f160bcf75811115f3f57a3c12c12fbcad9a25685004f81b3e4e422a6101282afc7c3028b86197dd7dea1a61b5cf1575e243dca003a50930cbc517e7d8538606233c32ba37f7d8928aeeb5d7320c237255054409c105c2f75c3c60a36d7cb7a1632b3353cfdab53d43d73773e9c36c5efc847e0a819f2a340fc9e49907797d535e31c8a651ba480af015c98600346e04a2a97aaddcc2aa47af70d8f828b86f11e1cd4712dc4c46edaada701d62dd9924d7bccdef6d304be2a85656e12e175b76a6f353d8f4c6b0a941f2351f619c5e5740e5d9aa1dccf99a2b68c5d50f3ba8d167840e3faaf3b1dc4d0ff979d8e2b090ec916845229d18725b06f597b2f2359fd06593c9057f1e8a8cc475c0e4a3fe745919b721ccca99b888e444423c3a1494559e9345a2a901ed7096db378b5127447a31c518acb7fccb20ea53b613575a3b078b9b5eaf24b045f4ab110d996eeda9ec15d1b7bf89f4d548347849549ef480ef0ce0bf7ccab20411531220a20d3d77d876e105e02cebf2f129e906106912fa1be7f020c99e30d35fcba4a8b5ad342a228531d257ee47b67bc45398437b6826913abd58048399c490e033b07a917075800359e6f8949060e180a35b7d82574152a1e9e7998391f6ddb5b157003f17ad50c40d580ae3fd5a320b30da6a9d5213ecae2fe8215f7d0a26acb85f0c772d5603438a9d0edc7cf420c67bdac978d75ae132a08e518c77315800db2fa4c52871450a2f44eb94d70eb3eba47c7a28e910077db28a2ce404a29a9130c61bdd24cfa06ab6df2d688d7bf5a5a8859098313a4f057165e8dfb73fb9abe802cc8690a9141a6e5dffb2f508720ba006915f471ec2acdce5aaac040438f094b0ac947786e4360e5135cd229fe811520863d45128bccb41413d17d32f97689a1189964a009e0caedd78e264a2341b0481e5ac9414665501bc386e657c79b371a736205b770a67ef25174176893a0c9f2709e14363252a92ec2e10918a8bdda5dd8db433e0241a3cf1062f064b534a8c2668dd5b7dd4b2c1878a9f9c510b55d2c990f819d43e438d8e03c75ad39ddab498ca308bada6d51537826ca755870444b49bda83c51b4edf6283850d3465a46b7a5918f296c0b5040934dd76874f376a83ac3bb74860ba07ac7a00b267edf2638dbb85cf293a281fe73d058af830aef9d87c0170fa790c7bec95eee2595f64471993171aba06dc0180a89a2e29ddb6674d1054186e82d20d4f8164e164b078ed49772b78080cd5b955ae49f7bcdae20e45320a4be59874e7f591a37b46303563e99274777271316c9006d44737d20811ad68ff462d7cab94318d33e936fa4cb07d1a5c3d490c72e7e411e18f0647400decf95bc540b27702313aaa60c21c28c608930ebf40d629b7608a4fdd962fe78017f0fb0bd367ff2b258af13bc4d1e0b7d625c90d0213cfeaa9331ca72a1bc70c910deb150c0f5574d10324b6c5b09156eca40582bec81027ca271e4b21d1af2a4585b337042a3ca8b8cc7da9d808051db1ee8aa1c938894e54e2d9e2563d4a59d467e2a8a357293884617148c947c58a7464ccc57470228e7deef8e70146530069aa49fa23975f9055aa6b735a27ce4bdb0783afa24a4adfa493e1543ce6a567aff2226ff3b6541860ca091171a33b7ca9f4df459e8903ff845a9fa4d87243810d57d7ffada6bb8f24d247cbb36db877df7bda28ead8721b3ddfe147185140a8e9980631625bcfd8e55893d103a32e0adbc6877e28a3aac50bc0e86c65378cd44e0d240f4b78566d92f83eb0992e984cbc4f286aaacca4d6dfae22dfbc0f543425fbc903478dbab3343fdff9a94336f154c06127f43db2c87d3b0aa52e3b20639132b56bf01d871ef98df05966afe3cb60c99d0b4c785c53480e5c6d5bd3ed563525cab0e6ef924a158f2eff7a27323fe8def7763762a41d9b92c8f5eece23cb596efb5683a44bd490cabeed49e87ffac64e245a3e310e50547fa21a2922e67ea2dcbb9df44828a9af319771338945b16816bc0cc70a2d8ebc166d4ede75ed2028af56841e943da9e4d0d5dbc7520bae4c0de828773fb53cfa4ac80d920e1ef9db2216fbe7dfd15777833e24c35e9f8a8d13bf9e11e8427e5d2c791662e4c930a6f22f54e68538cb310bf2f85de14b76f7d2072a4af3fb17c5f4d2184ab904ffaf6353615e06ecf0c4c83efa8fbf4bcc6e40328abaf1403c7885a5dab800f2083f4e7a0d415bcb8101e82050a2f3329c82a3ea96e98888330d5e9dad3926189c03e8cea2f5283ef6a17c8dd2e40c8b328a12981b867596143b095abb6203141e85c623d48a67ca8107ddb7b3278d4aa0d9e2618f7d9edcfebc594d5c8045e8e49668895f1b1f54feb519a6d3d2cccc4e8238bdc354c92db4078995de9c5ce02d49f608516c7c4b4537a2986db55369f12b72b4a5114139829fcbf10f08b777b494fbe7b5fc6c8c11f4758cec291b9905d25ed3863514fc80470f0f82f129bfe88c28fcec4333686526f389f26cea3a525fe887cea7cd5d4a70d44d076711067639138539097b5af5161f67cde416e75ce085285474e485d1d59e2d003bd3c778a78b023c7b1387097dfdbcfd6625f16e435748c521ea2a4f452de0dcc18bde63533bb837fd213265c38f953ff8fdee911abbbfdaef2df8f5de6bd9f76f56fd72f1f2398b9aecac4e32e2f79541ffdef3105fd5b296705b925af93376a68972bd882ee81e3f4ffbf60c1aeca6743664f0ee5133fb38472a6c39f6317835e28ace65e7474e99465bc0c235819ab61ae6445fffd0159f2bf11da187ed9c9cc87fe10b67319fa6bfdf630602860985ad4784a18c9a36ff3f52a61ae28b21c9feb6a81e05085eee3d8c6cb9cf893636e5d1fc44664b55f1005f35b4eb9ec3ab93c4a6c01926c85899d17a437b7c7bd745fa2d36775c80597d43845884747f52720bbd4d9ab2129b120fb5d222908af7c17c68453bef0544646bc9b52492b6d7851837ddb1982d42c6a8a0a735b8a6ad9ee4e346bec0143619dfc1194860ad9861a8d5a477558952781b84481085e6be1765160240e26137f9fc3ab7fa7f745cbd8e9f97561953a9c02f9fc2d6a3c264e2096479106d956a69088c54d11655246cd93e12d4b810c33d930d9524926309874200a913b1c2755119f3461b39fdafd86d726887a8b0218c84cd78b716a55e10ff5764990d0870a58df4a8ac99ce847e9d9b5a61d1eace785f2280f460481a9501aff644c28660c685ed057d65f7465504273d4eaa5b3864439f23b1cad7489e5dff0cd567244eb0e49c23141047e6336dd1046bef4ff17e949168a21df1453a62a2101b179e2fa1acbd5474548fab837d4704d8328ac181b4b37f4e491b9d6c6821578fab7807c8d78642eb09535ddf778dbf7e3c1a8174a7fd3c86f90547da5004f371dc794a1f6cbae88effca0a6dc590d4c3f4b31c245e40e4d401aeb20609b41528bb8af929ba5bc4003972594b65571548e264a9af1f6eeb5c8722654d6d81b4a61aca5af2c42d5db597b8126a1276dc860342662f8228c1a0cd2f13c0709c6b8ec68e46f894ef28c775ee049d4daa253d8c78c2c2f77adaa869f68bf5c2b83826c181ae59b73954f131f4c3651d7ed0bb09d51f5893565574741a1c9e64394510d9c233b5128a06a76382daca3e18b778f8be8b2febfe94ed782f5baf445c41acb8a73a65ea98d8820823beac0a50afd1252a8641fcb8800c2e0694e185069929b9f8366450920591506b440b95ff6d931b818f6c8539c125a3032e493343c194fd5a506e7de9717fe8b43888d657d63bdfcee3bd19c3d021238c803899fa60d24e33b5c8263c434c44336a63a5b11713e9d748be5db1de927c7603757e127e912abd8fd42d3f9b6ddcb4ad2327462d1964cca8bbaa28ebc296a5de7746bdf516e82bf3065dc0c165457d3845258a6d819350495b2d3e3c303961eb8ff2460d9794845e5494f731c49940134a64fd5dd0a92958eb91ca13814d0c4ed78ec0004a581231438b66aa117291228abe2f585ccbf634e63516db435671f803ac3157005de9908284cf932fe373d18a8f0e0277312d5c806b4ab5f665153399fa7e1278f3d5a8eea451ed8c5d7d85fd4da9d70af5762b763caf15a1c0e7dae15779c285ceda55081391b4e35e224862593d219ec2d1835022a713958b709ccebbef3bd89e9d6111c6fe3a2e5c8d04e9485227396fe34fbacc317cb738451562f292a12ae42b106f7cccc1369990b95e5a84dad998e1c953177a40076759502d224128c2b9ea43c984ed1dfc09b6693dc2b4f1b8e399cb3dc1ceac3dbfad642a883023cea85913966618ae1648a35d4f4b292833e63449e42401fde88ccfb66100d462c9cee7ff211da2c09d26e453b499919936ee3493627ad430b2d9667016a6e94a65cc4c46a7d90235cf4a585bd552abcc841925b3b8b30b46e3cb7649c738f38228efd0747e7ebac029992b97c52654b26fd0cccae9e896c936025c1ccb9c4005ad5213ad1afcb13e97a60681bc50c0519bcf70a1c1dd77590a2b86788459a47f16a97909f78dc73770ae5de15a8364bcbc3e35b84a64e5e26d857daf71dbdfb5a55985579027e7a793593e393a33e22aa6959641305eb1227f43937b592886c52abd4eb294dfd22475b98e3a5b431ac678a2bb10f9967a02625250431087e5d13e4333f6c00add16799dd0f2943a1a68b032e7804d4ba4cdca1c2df58d927e122987c62091c633e33f5b133dda892b25ce8bfbfbb7520e77b4b6205a5cb0e79342f928f65420329477b4eb85c1d876368726573bd84d7d16c08351f793f935eb6fcc615d6d4472e7f6285f9d1727c6ef889301f3839c179a7da1159cf1ad7d3d4d576215e8152ec6fba631113dc69c03dcb95e9f8a06ea97f596dc9ce39658803a35eeda7880deda2eca4d81560dc0b80f943842fa54e81d78b2eb8285ee472368c3908e6c29a822421d6e434c8dac773a62e8237d5a99d2dff0fb1a805a08bc23bf9c737d53199a02e7f9ab6728433720fdefef05a79659f9798e8436fc634998a6bf77a0921268dec01a29e0ad821600066705cda322b0942b6a5fde571a61e0678d80ebd0908230757f6142129ac318509f59d04041249c505055e7d62698c7006d610f7572f6cb1a025124cff694152481f4a062a7435eb76759c4c8c60b0906bb8230e1addc5c45fa645e1b0543e1711ffbbec4468b207ac1ab1a20b54ff0e79a9412044469ed2fb5e1179d92e24be3dca770398f794f6f5ede03d79a7108771b1d886fa7a997b41cfddf52237610ebe1c029a8f89bb211554a488387298a3c21fb0d40c96909558920b2d0339e9ba249019ae390ffeb99b7cdab6d33a392de967400f7f024888ecbf1115b3033c948c52663a79e840bcb3cfbb1d20d631fd4ff50db0f3a745bf8a98c6edf330c72eae6f61b93ac97b93d6f55359ddaf76d3eda3906ae0e6ce2465a0818cb55ef155389602bc8e4757075e18b7269a264b321c00ef58f274f599a77127041754133c5891c58ca66c9e671e7f465a4bf7c027dd1f34182d9fb153816570f6f40e602cb2250039e5999424c738698471cde073df46a8173569c6db0076323ee137b5164db77c1a7acc560d77c9177401520fafa9413bac9e23154b274c6023399fcb301cf901099bfe0dda78f8fc8db75765d3a857e4bfc5cb79295cb13a7a8c654250f9d6cdbc7a326bc416eca002cbceb30f4529d9dc36d7ca90788174e5ff26af9a45764c0a9655f8d8dc8458c4b2af0a92d75f33a511554ad465cb3ecdfbd45eb2a5e91a2b74f51cd9983bf92bc2d32b3d4e034bab0479304e559e908305fa7add04b986404372cb39033d9a164695ddcabc884f72108b6d30239d4f8434e53527a7aa8a0c6536b86029bffb9228473df97462d41de9d5838d02f364d2240f2ff5046d4c611860c8bffecc35ba2550a2c447d14185bc09ae28bd59f2cd1969ae4d8ae010feab22a71f5dc4d3cdc5d424ef1393799ed5ba1f23e33cc93cece99bea6fb93f950b377abee6c80505f0cfd23acbaf5117fd54e5634e5bfa0a1f1208eee8dcd096706ac748005d7c64ae940dc7d72fcb849f142ca3fe936e72c84d4d521aea97dcbba3a74a099a8a8cffe0f45b52b09391413883eb161da7c38efe86989974d059c9f842a7b144f0e168349fc571ec2553d549f0de50ff478fe5ea0a24a0f106700e8f56ce22daeed89574087f7fa6b634673eb54980022c68aed03209c423a4cb53a81baec9e4b44f91bdae3006fcf61c9d661e4d9b342cb5a614a10a2d8fefd473c3ad56da07179083be44aca65bd15d999173aa933c1daa50258991f1cab2efa04cbaaa273c5584b6113f6016b8bdf79b8ce3e62fd28d540fcee28bdc640725b7ca1448d8fcb7571e30e91f5a6ba0b23fdded28858400f370178722f5fb70538ccf202e6beb26d81293227c3b651650ba9d8f2f3d61acade1fea2463b38971bae63298a6cb2ef4e84d9c8f21b2680d495cf2dae338cfcebcb3150ca238e2fa34173d5c6409bacf91eae00dc8c4a906e06f86a41ba571961e79634d0ddf4887f9f96d291e3da9aeac649266762213a347c88db941d3519249f661915bc24e82fcb0faabb0bb5ac30f83ca7f1e826e9828e7cc1bf480e710d66cdaf124e1bf8a214556ee67518131e4e7bd54527d50c3e1535cfc449e8f5a45768845093c60e9b9628c233c92bfc01747eb181a64d165776d0b22ee13418fbb1a172986b3a20a24dbe1165a637ad3e990f32a50ca8818493c062e1eb4c40a562ace5e5657cbe31424ec248815330ea675cc0a291ccdca40fcb5831a5b7fe5f2af6a52bad90ec2113d19569fdcc3ab1f045427462610610cc211b83e8cc62a3274dda7c07faecfe5a94a41686afca89513f265207470495e741b9aeae82690ea82c3c57d854766f0b825037652e87902eb676a0cff77cce229bbf0f5f9fed23aad95b55c13677ded6cf1be087137880e06bd0cf71b8ed39ea289a2329a98f48bc4c44d4c54061da9f21dbcb5776ae3bb7a4b7c0baae9ec94ece7ec8a7a8b80a19afea7da117b641964c94b9667717175754ac80d26002f3736e1482db3fc840a5da348cffe13308c22a43f4a11dcb9d0800af0b7c20d4640ec0b6cc2342af6490cf3017a6e518a198f1f2290b11ec50db7512961467c6fa9ec01e841a5b84fd1ae0abfa5e6992eff181899e142a1662e529f7a4b96c2aa121b97540aba5ef9cdd14346ad3c166c7bd18946a2fd8a2e6feea7e5238a2821dc0059b6078120b2f8cc6dfb65de819224593a4dd40cfa811a210f8073836ae4e1da694e72e342718e7dd355256323c11ba386bacc59ca502e401e3257f9c2e060ae047dcc4ab0325a6088708a6433a7fc00e2f0beb063b7578b381001a8a711a98a05dd2b591ecb05b464a073482db1fa11b49d2c68cf71825668a3ad87e2521673128cb306a815548d5e1ecbe66c0f74e426c55e1ecb0e12b48ad5e5b1a44b4280f10aea59d290e27059595e92088cd523fa2db62275624fff44773993fcd116cbde923274c58b0ab45e32c1546c034a158e6a5b30145ea503da7996d0e7ad34be5c6d5bb8cb595a4b27a6ce13003b19ce0bd01fe258834645ce122ae22511c5161d628bdcf0f801e33eb7bbde4ff37afda35e0ad72ffe8f269670e1bee17d834a7ffce22d3bc8de64cbbda54c29a5cd0683068306346421ef166a21168a21c68f6b70842ab696953f9439628c514a295329a47260be0ec373c498715877c55239b8fa67a91cecbf37dbdc5870dc8cb9c17f3e5755d908abfdb6b586bca687e8526ef69c150041507e45a66d69c2f9f29fdc04bed56ab558469eb59b80f06b3748e56b3fd3600ac71c2a9a48434b46488db33538f252fbe79459651a354e3885a8475388adc110f08f5262526ea80f3b15b4dfde533b50bf7d9cdae19f6ac76c55ffd9aafefd981fff07aa92406307744a6e668750ca47fde6eebb6973325bb1ea550c72a8e1004328575709872f0870021cb2f0828315e011c5ea2ae1d0831516ae6931238cc023acfc35e933c3a44fac4a9f137b29a7c4a4f499c9a86931238cc02b50c6cb74333191b24c37f33134326464cfbf71291c8ce2994c866518f3fa67c2180e4de09fd96233d8a20eafd96e2fc60c931ebb3119dbdd1d6bd9b1bbbdbb1ba63bef86114497c8ed5162324ab92b252633e93353aec6a1c2f82ea5e4dc93acb56024c7c86566d8cc30cf231789612d774a96f24377f795bbcdf5203d62188661f10a0cc364c4aec062160cc33029b12f24e65fb29c207ac430b98aa9ebc36b5ac76b5631468c433df699e3ac0ef73634d98732aa8e4dfe7ecaeb788b07c4f3693048bbc03630b02768ed058bfc30293b66e6f828c6e9be7df8f00192c2e19f0f12d02984da3239ce2eb2931455c8b0218a183728e151d49095e090430d798897542e7ac9e0f0030350a81728dde0440d99b5a2640316a6a2c5450e95f9aad790c3912d9d53dd38eb423ba523575653829c4471648601c81083f8d188961aa7a89186276a0cdaa1a3e1ec2a73d5abc94436654a1c894804612938e245415eb34a35b4acdcf080238c948ce0e086071021a186b0ea55cd902a90f88113c8c30fb6a042b3ba493ff052032b66c8c28a1c9470c08245d27ca6a25543a6a255b51857a8ff8186f3851c5c23d001ae1c1274d21a657cda3851a671c08fdfa790a08cfa55fd313f7bd487ab1b9c4a617ef6d9e4388e7bcfe9e7487051ae598eece79c9aa6693eab99fdcc661735962dd89666052e622b9b73c3c9d628616b74cd987b394aedd63e7b02c2809a00cdd7be0423ba757e3f4d7ae6633f7f9a842a28a002592cf505ffe2a5fac854f91a90f50522a06e7ed38b64585307f303bbc5c2d23ef38f88e6a9fce5664212babdf64a4680e021b2ad2781f918ca53f9d7333f95fcf9cbc23e55f62c60d9639f4a3ef6cb6221fbf9f3fbc1c01030f0e243abc5ca3e955c96f6bdd7f8d70196401419b1921c3c3d0a68c8454c1e1134ec22958bee927f0c8df3d3386110f971510a09aac373baaee1efd81917a7d59d3776c7766e5eed0760c188d3eddedcedd47d77fb6777f7c605c70884b8bbb179cec8cc3122f18845cfc89c25c6e8ede8e87883ab78459432fa17af702eb6a5a9dba0968d3916a4ec97de8e8ed7fc40c71b5cc5ff188966dcd238f1080c74a87119290284225b106088224e45b21079d2220376a40b1aaca06c60ca860021635ad18e6e51036ab5c56be3820a27451732c0be90a2b58ae08ea4b80a2353617e4f5061607e12a930325ca9300f238503a62b402af51bced4144d4d7d4c7d11414da5beb86a4aa9e5434dfdae004449b16d3475b3a9dbc7eda79724eaf67448ddbedbbc58aadb838db3495f0c31c56e4e0c153930e20b220b787531c4c58bae4ccd9ea8d9cf8c0b126c7183c5f12e1543610b2432cb22801a54fff954c593106d26af0ac39c161796640386618cdd4036d920a59472092c5a920829a1c54b1750aca0b224d960050d590d65862c0d01ac494fad17528d3f6fc4f5828b11473380410e929c205a22882d3ecf2cae1ae4e83a0d72185feaac68e8344eccbe307e3a4cb5205269542f6848690c61894ee188af56ab55e428c9155c38f1e2cac11169440a58b0558c5695115de934ceb2fc38c3801ad2744982290b23598a6accc2881a93862003e84ee6933927457129129465d1b8e0500336b9f81024776c119471118286a804e362c411d762cb0e2dba48cae504397486147772692207def1d4e4353bb9b84068c7109c3a224e4a337c914a32801758d440802050545da519ba0ce0896e759566d0a244b5ba4a3334e598814969062230f0efcf565d6f6babaea7daefbad5c398e83ed7908584d846ccaf8c671e388732f3f22fe77c42f40b79a96e128c2fe4275c2ca5eaf093da8c04887d3c8277016d905543a061ece91ccec2d33898098e3cb1836e6d79c2b21fa76c19f6d987e9c4784171c207c21996a19ef63bbd2bd49199b7f21ac6da6582262ac60082d228652c21b1262645998c22d98c61034f4e46f2d46234d1260a45050d5d028409456d44201ddd8004885ed24c9c4a07b93fb953739566299aaf30169723b99117c98d932104c4d0122a538e9870a9182ba80161f63c0df2c8fefb917d3c7aaef037ab071acefa537ff007e9d8c91a769e4a61fe8e6facc5afa381b6a22520e2487b47b96b8ff25ef02f3a2f1470a0d03930dfbfc5878109b30f39f662e488607edbfcd9f3ce87931f11a5edccac82f13ffcb587e1af3dc752f3b28f44f40b7a16cc47157fdd4cd7f46b2ba09d1419541745426713db3eaf4725102f3e225ea1eb6d6d6173767936085a4c4822b3f79af92808ec6f0efe52330cc3b22fdcefc06f7ce9c110eb8ed8bf2c22d4af07a3baa7ea9fb5bf393d100790e62b4b284ab08c208a96111c69ea8fd9dfb93144897bf8de434b69410d57df030dbb97122d980d70659a8ef10f7908480fb53044c92eaf0024862f08d78486eb14841fc837d72de58361c762a93868832cd4eaba2a46432ca12449c9fd8341dad81bfe06ec092ff8172f3bba55e5d780bde1da5e07ba4a4f05fff2e5cb97da5f03168c2ab8cb4f676f646dd6a921585986d2b0d3b0659f61588c40acd1b1280463be61e9004850595789071c146009edba4a3cdca0c30250578987293c18d5f07b8ba416282dedb3e4ea4a1e1ed7d5de4041c36deae17352da750f32ce32b8cfcfcf21013a122833aaf23659a0c7d688cf41a8dd3132a7a47b60d82cb4649bf99918638c3ed1fda36ec4b634616caaf2b7ce3e9084adc19f01fa518bd2b2d758d3b44c7bceb0d8621b581c32c5e2f45377b1a07c192f624af582f2851a477bf911091071282e699cf9dd854ccbf862c47c5e44bf229814d71f7b0b68b8efad103d1d373c14741a94361290e0cc1cf584f9974dcd999cc9005b835f3ec83632b7c1179996b49961a856a2cdec51dacc50dacc309436330cb54ccb0ff3a9df1ec59307745d4f4f4f4f4f4f4f99cc27e38bf18594c27ca1cdc77ddb3741d000314954326d8d661b315a557e7cb23596ee8dcb9f93d2ae7bf94bd953f9924a83724a95ccbb9bcdcfbc179c911a1c82ae8dec896d645f346a2eb6067ff1c9de7c91696fe44b25745a098c14c6bd029aa0303fe3e5c72316274eb1a07c39e38b465acc17dad0afd5cd2902a500e8ba19f12bcc179960d080c6f7efcf8be44fda715e94050909090909098983268a6addfc4c3ec641937e554013348c46ad2a3f1a21b9965e4c325b63a590b115e4394258f2a390e74496fca8240e7d2c8a4d362858edc7dffd5412908f3d0b8e7d404d7a16c85f7eaafd88b25f96fc653dfb11ed9837a605195e0bfa91f7145846c0c2753ab51ba10723f480668b369a9d95820842d0d09f78dcc96b4ad8010dbb656f1844a914e60ed8c5485cb4af255751d1f217c3021abad04e7b933b6571269dd76aa99b406f2667a55ec8012b21d085804cda096d8dd49c9476dd3fd8af55bffa1583d7c407e235d1998aa6a061bfac6c4e347a6d0dfe4e0ba5c2688a1a7f33f21c9a45a3ae24ac3cc799bc26fe9c9476dd3f08ae564c948a100410cfe9d7cbc90926323de938adb2803abfea406aa30353e3529eb3d8a1a01da0159b6c036324f6263e17f109ce4a7ddc44ddc87368c450a1fe5da917a35517fbe411f5efcf3e9992c0b252bf291eee1909fb908b7060518a28d3aa5cb492de6bf7b51d0150824d6e1ba7fa63f4f7e132fe7cb84ac21094fbc17ddcc17d5c887dfc2020e5730a07c7d40a42eac6defc622a05d48ed3e9612e770648ccc004b57db8da334002864bf5a76385d7c2b362df2f5f074f8fd4549ae6c3473e906725bc447e10b94d44aa0c2212878e704b57ba90daccbc165a537056290456aafc1b8db33ea8f25dd8c1098b949ec2812282c68a83bd0c8e2ca502f6330d764464fe3958056eb509c12a69c815133190f27584a046d91648849478108251015d25212521185816ba94e2b81e68ff2a0931a9d8af6f18c6428da3d2fe3114e771d8dca67c2e224a3db37e98c5bd4c0ac7fc28edbec33e1b50b56a50feb2745637b06f6713e1075a7fa0d8739dcf2d240d2a69500a3528352bf46bc8462b603a66a6d0551212aad86fbcc92859888d3c466a50be00aa501551ea97c53dcae3e9d1625640e3b1044d793dbc463ee7f98842c36daa12ca12757e412410277655f9233f062168b84ffb1d7d7808fb9529283666a6d99b1eb21f7d4e4add6d36a45bb338dcddbf0f40b32bd95a93d2676262626abd8c3c92e80ae1fcffff9f3c74f0c0e44f791a696664907037604c1d9fe051860e51c60892cd33e3d91926a349a49991e1646a68e641c9b0e2bb86a780523495d93c681f3a7662f849229c4f413334b403ddb523c4483bce42317ef3056f194e345c97cf8ecbe572b9a490cf8da741973b0d6e41c3e9e2a01df6666f76d9fc4960760d13dbb922ac18f4536d1f4f8ca7458f025ac40202e35371d1def4c3c0dc97d0033ae39391c105c5d88a3e1e0eaa28d58e50fbed53ede0df9e5f48155277baeba672506fcbc2c9c582f196a5793cd4200a6c834bb027f433d1fcb888461ba59a4341d3aa54f854fecc04658287ca5f68233343433a8310ff1ef9df4f93fd7afce3f7f32384a948912d32d4a7715a1c0edebdc0daa06117b64610ae2964ebc2deecd3b08d0bec09fbfb855bc57ef1eb582816317fa78f7eaacf1f3e5ba54a75ff1117ebae52e5a8ca67cfbe3b85a3b32cee0fecb97e347aae8ee8578e4a49daf863588f9f3f9583ab0f4c642a476318b61206dd1b8975330deeeeeeee763b4de7d90467409b06411bcf46468ee338ce9b7783a1eae158f2632592df0f0858d46b38994a01e3662a076bac699a37350fb5715b2a7aa738b5198cf0054fe3540ecea3810395ca81550e47f584ea0ed55d9f7dbdbe70a91c5db9bd49e5f0caed8d4771c0487130606052746fd893c1d1e58895c66c31a8989898182d26262666c6c46431588c8c311ed3311c7302bfd8c50cf38446514336e2a1d496cd07206088edb3623cab8028a80ba7a7713819266e18b8f2689c6f4229527f884151a4d07c94204f9e2821c5b69cb9a0092b4840b97b70c5513061b27df49db3bbce93a06d65a3fb1d25879928534cf041fb2c15666ef6d2ddfbab06757a1ba73b7e389d7a7737b66f9ba0057dbd48e04e9d3a75ead4977a0f9c301a5a52d42038a345431eda6716d72747c4a7c9556471c2a589cb15c58997902926f880fa2c158ef254c3a74bd35788c0fbeb4a297d77e3faee7e3dfb294a09ba27ed966977386b0e58e065e7c60c1d68b82e1d3a74789d0e28ee2e82b8bbbbbb5f5ca675ce3f7ae156d9dd940b3c4629dbc4141d9d0f596989a61a3e85120528dddddd1d73772cce06fdf7c6bb2c8d954d83fbdd206f3bb3733132d6e43b8e8a7083b92ba0fc73ce39a70735f161cb8a4667db2fbf95ced3689075a46c273c8912585c477614bfbb45a8abe4a40b126c9c34dc3e3831738f06f97d70f5f1e17e0f1f3e5cd0d91b7eae09ede183db6edab4695d274772024ce5e70f003f9290cd86ab2c450a17b91e9888a0438cbcb8ffc18ccf04e59e8d836dd7e5f4fef7864a7bd8a250d73d46667e0c8b8286ab1d5f89c2737431f624185058517612d0fdd5f2036b4728818222e2729520690853e4c95a8aa281aeb866239818824a08945c69024915282aa65eb3cfde8c42775775f7bb068ba06b43164e51fc553d9fe09947d2753c14c5a1788009e7c46c2b3498f1a8c094ca39ee72a171f6092447955f5d7a340eadccad2a4f20d95243bea18519ba204de1691c6e41e5dff53929f51b0d12cd5fc5b430c405f2573ea2418dd3534087a2108edc5d623724e6ee2ee5638ca57030f67247e28e64ce22a8ff7f838d6152a83f867dc488e66373ce1857f06a7a31755d47b4f1f4c47f148ac79c3c3c5e3d0db644794124cfc67ddb1666af6def1e797ad9164476332fc398962512ebe1c2d7e8f9e8ae9bf3eb71a3c5850665d6c37d9cab2138d3a0fc3ace5eca28d4ff553bfcb3cf9a48fbe8691a57d983cb85246bec065b92b610996982020a8dd31f3d09e5e6666460cd38a9410d6d666823115aad5641336040436e794f31826469fe6ab55aedcdaaae68cc0f5ce980ab07e90c82814fc4cb781a3df6b71f44dd403f05ada599a56e0a0dbb89441123468e8a1c0d79e9ffd93ec8d6e0efe7c0720ed0a2096d203e9e0f8fc70edbe8a0da3bd9cf148e2835ef9be1ec695a8a086ac842362d90866090bd41816d70604fe86f4d0b7a544316a2c12d579cdc9091274f505f3c61c26408131f5e0d2ecc0be8d72a3289155ee136c5cdc1beae03f766569bc69993c77baffb81c61ccf1e11f68bbd7fcd7a57b520e857285f549957a50d7612342b92e4430d509200a19ad0cb01326308fd979ca67d7326314b46d33c63b5d7467be34e360b1115435328b122d4519efc3d07e6b51ffddb6bbfed0dba7dabd0bf793d1af427a18924a954654b65a52a5dbad544b0df9e7a305ebb521e6aa2e69ca86ddb8c3a339a935277327ad0c948a7ce18427b66764e7376dd3f08ae56298f480af56d3427a55df7afad3414aa7bd25ce9289bb398b795ea0d45f57602c6f64e1fc6c873603c877eeb35309e4e08528db37d1e6e40088c944783609bf6dded2814aa67f6bd4429c95dbaa687e74c963b35e8de46d597696ffadd488605fdfcdb8c21df0f9056cb63f3c72bbc6373de6b74f0146b65bef6bc537fec07c16ac45e9b3888b26d93d8172e5663cc767fdf3518d03ae3ea5cd1073dcca3c10e2af47ba401bf0f08743ffcb8bfa908ec77e353edf874b8c2d54cbc22290906f47f65a48c0da5734257980ebf12164b4cc2201ec64fa741ef874c413ea603f641bc4af9d9476fbf5583197ff9f2e54b10af1c0601f9e964afe3ada47f343a67fe8d95ce8e4605c90674ca84fcdd395b4d7ff71678b6c68ceacf23dc205477ff95e76c1f2b995f94b287c7c7f341a1fd27a8d28b51a5b74b489654e96d7553bb37b2ab52feacf263ca5ddce3c6dcf3afb6c68ccaafb33524144e705f0cdbb75e83fa580814a2a4fee8f7d98c6be6bf59669dab95484a438ab817b2f5fc0889939b3b78da41465da52737d84149294992ec411ba4f7424370b9719669869716297a9c148df3ae55f73abc867f7d76097c2574a7174e15fc4b5d50872ff9cb2964ca2ed5ddddf1812f35fc69d5fdb80abae8d75daa1493d1578809932f5f5adedddddd9ff6a8466d956e8f8d5a4dfbeeb92f1eca300c23e6a373c2501a44560d45436b9e0685a0daf7a980a23e5c0939aa3fb42851da2729d79e5909b51feb2fd4deb5cdb25578f36db291826a2d8b14b8fa2f14285d52529ea4a43cd12289249ee8d2c4d2d258c9501aee2bba0e349c759bb33f206ba571c23929dd710ffc81f21361cfafcad1771f6c20e857a41454f9cde840c30eabc293199c407a6bcca8b1f6f311b5e75c7d50fa7df40616e7c7829da385ac038a66a95dcd7c41ff8e42b9621f0cf10b7d364b8dcf9a4fa4b1eb86b74383d0b75016640213c8c8ec1eb19732945232b38c531e716c7759d37380809a00654f81eca380fcaa3032fae7de70f4ecb47f2b30b30068a0fb11e8df0f05546a0876529c862a55765f3c7834b83c1adce769707757fe569b06b7f31aafd9d75e40bfbf705d2b2846825acccc2c9955df2da9115d59798286da5f1bb1f7d0f323a4e74748ef7e1c04ef0a614dec8b086145212cd5673df1e5f7133beff66e2a5c5b2af69bc4e54a9dbfbf836b2583515ad92589b667ec53fdf601a558d83ef5a90f889b5fa722807d60830cce5feda7170aa9a1f6a86deaeaeeee0b3f2108e67ae033f13fb29fff237b08ec6826557e33a9f2db3ef3683a677b17ba66f6d042c37dcd6c57270e7a784a32425d2514e4b0bcdb02ca7533b804e599066bd720af409edf1f00196cc006a7084f34039bd3c2fad81a5cf931b038d10651962455dea6ba5d2a7f0536c747f658d095e734c19e2bf77eac2d53d897419ca05ca5122ace2dd719f639b22f7bc9adfcdc23f27cba867b40df091535d4910197989d6de1bdd2c44e287f051a87824ddc6ecb8f908406a8fda1571b209f90f168b0f7b5e2b1e113da3d1a0dead8b08d04ec09fd08c09e5731a0a83a27a5fc3d8f5461bfac9edec67e56ece3b9c1b59253583e494a8202a5698b1042114350ba326504fdbbbbaca40b13264aaa50a7d6feb033298defef1f90d7ac8a05195f7ecbda9517d2ee419d9d16dd8dceb4caa2a32d550175a13852523718fe905570a4a4822e5e18cc0b6630fe19ce9959b675f78f20723d7cecb7e6b01f52ca7010ab7f22848c0f50647c80a2a4944489a5244bfe5c8f80c2748416a10a0ae84f038b653f9ffd1f3e8ba5fafbecfba781c552fb53450985727f74f78695800285092682824aa8abb484117fc1822d4f9858efa44b19dd37c77f7fd0cdd05ed6c90cea1ae999f891e5ade195df8ddce539f15f04959f1da9b22fb514d82f5569d506d45daa61a8ee60e4853f7b8b880921a0404dfa81e2cf0f9bc90b1afafcecbde51ee439254041819af4641f1f0828eb57f64c2a16a26abf9f263dd8c7e79ef8fdfc3451f1c49fc0b2a2b601c58f5bcf3df60179150bfef1e30784bdefa77e59d293f1531fd0fc38bf75a075e2b2f1901050931eedb1073202d23e36f21a1ed201ea63232ed8a7da4f4581f9d8f764af7d3f4df6b517c2eaaf67fea878522f84c57d13150f50931ed473a5c04f10b3b09f8159d803a154fbc1d0335ffb7e7a780dff0496e5e3e535ec427db84da9cfc96bb2af5b5e93cd07dabe16c21ea5f1872d5419fb80fce7a7daeccb1e7b1632ec81e4671f0b1890d7ecd74b610fa528e0ef4abc865b3ee42da45ef29ceea55e1687def21a969605fd1a7aab72f7521f35c84b6dd420ff26a49b0a4d71bd76f77f47c78d0697bf30e2a8fa57c6f622e8462d647e75ec34b87fa5f2f7f2224992224746a4808179b98290b06dc9d6d86609d6060a3bbf79a8a75e5f714531c462c2a47154dc6fddfe432ec291eacd9be12f64241aff16c2c9698855a4c4941a32d2875cc4b193cd0940d3efa618bf947ecca7da3ee637a0dba8440ff6243154340300000aa314000020100c064422b150389e29d2247b14000a73864c805234944643591224318c428818420c308400600c101a1ada2600cfb45151019851a3f4ca9642e3cbc9e9e230c410bddca5922151eb6290133dc8040710390dfa9d5f2ee64468b80d7bbbf9b50c6a5f8db9db7bc1bcdb5b99a8ab1417d21efb8839dd7eef530afce31d0b0d48aba7fd31c98afc201dd4ad87cf68586ae87da0b73c0051f036bce1c9f10bb9d9ca75d0a548cfc2f04b21e143bb30cbabc158702965b9984b789d054b0fe099a6802f25f61e62abbcaea765bbdc3e6cbcc26b135de1d173f2bf57e8fc0fd30dfe28c902297d465cd2c6071cc1711a58b54da2af8dcd35eff98c8983a40eba74339db4b835795e46ed6de5bf7680a20161c46f356cd46ec6ace0e307005ee094ef35939c959e05e7502ed346a00d948eb664b84ea0fd76602d2c4df7c0c0e85432409a2e4ff07d3efac79862deae4ae6633d0f8980bedf9e1d6d4f3f185b9bec6e75b0a20ac2292149b94301ff592d1152560019f0896466a9bc403f9a3a599624420d5414ebcbb4642be8297680a4394efa251dbb909bfb856a465519e302c1d58c73c26f1cd072508acb8e4f6c0de5976b654bc022c0bf059154ec50115f6a43a7fe5d5c351cc885a1c8a568492fc6688f56678ff62d926b0cd78a9db13e0437b320f81ae1533d5d9f233600ba074ebb18800db48727f031e718641a090c8c3051827e9e649f29567b897d1d03dbb5b1bda355624c9becb19a793a07af76844b36b903f3eed7d13bc65189837eb28f5a6ff06a5ae00af533206d9a0b10e636db6ea0df0585da3706480490c8821838ff5b65cac4af862f75ab69fb17df951268f7ace8eca935978cab224883c18b96d60c48f77c51cbd60b0d162becee62040fe7e8f281813cd569aee986fa4a2abdcd66322ea8891ed04b10f5ace5e7bdad198ddafd2634dac9fcbd12c25e01de377a1280ad60b6d78717668c492518025b6363f5666e28537bc547ce8f0b61814684452c48ad1b77e60a60f81578d9710730185fcd868486b2977a5b38bab058db322207ff6c23a064ce0472356f9fb72e5d0769ce2d49b3690636f2adb9e9937e03fbeef0207049a13003c28153d0762e5eb25e380af784f8966daf63e9870d52d2787c02fac858b4c47d6105379ae144801d963e187d1cddfca6730e8bdc4a816e28752552286f7d182c57429cfdc376ef3d673a9e8e43080a275dba9512541f6c2c8bdb9bc08e030479e366609660917d9dc516f60c1694faba7be3b49eed38f89322139d6bbf14204b46ed3c8c0477000be3d3e3a8529c1983019e5b87b7362baee849a4e7bc9e5798c2979f3909288113db26d78ad91258c30585b7fd390efa1a9ae0ca5af938e648e1a06021a961ea51a796e5c29a0eda16a72c8b298aad7af5badfb3dba5fd94f5da8ee00ea751e8675a6180e552e03d9f01cd087876a7bcaa3266fc40f652ff305dc5cb87b22b7f6f32c7d0a69ed5d56baf6924935c72733ec42e7a5342072a5e0c7977645060151b4ebf484ef608807c17baae0cce493f20bd65f20f3d00cd5857f5584a05863760897937d0b04f8ea5d85cf263fe26341e0d0896fcd69d58dbb6ff00514d4cb0ad7cdeaa005bde134efe0862e7ae15dfab73e63d81905206e6363ba1a3475e15bc721bc90e92568481d88b641266488435d6d3dde216eed4fdf199359e04c5ae0dbdf5c3a50de72f2f452cba382b003b584abaab53659757b51f6ccebe345a9ee57ac148f806c0f7a4b386e32a765651a45d14b7405708a8d8c7114db2b1e13cca7e9ed21d465256c6c1a6d0143bbdb3600287fac95b9d13b4cc504ab3e4e5d97d5fd028ae8a92557592f2012afe7990db1b9ee1bca96ede4d3c4bbdec8c979ce08569112729833c93d108d91b7e0212ff9f2e484f03d297edf234fbb4f1833a5628b9a1add0ce938bf5b50b1399b8341c17ef00520c6a10bcdb878826bac303a286e00be168961dcc83115f5d1eff3521c85569ea590cb4da5eff5c2ccff80546487f15e8d6dedf2a81c60aa8e103466dc75bf52c691f731dbbeb9044ffe12da857b36d23007136e7b24f1d3db28db579c0470ca200692ac2a3bbb79abf16aa53d97c3d4b2dcff097bf68563d9dd21be546e641da3c60689db8417fa5fc6aead8d4484fea1c55ab3a2b24b851752fcd4e74ce7473d036ef03a3df503c2947de0983e82648e7c9685b37f8d246ec786157689df0362f91b6887737efb7f4acea4707cae198add6e9e176aae46c956fd416bdf44e8d4ef0c1415bd8e305e8c3b52603186061dac74f078d86f0cefad81b6524dc9e89cde71bcf73ad0db24c805349958a17922452adefce8cdddaa0eb1f553b006e6d8825a41760241c8f45e6805cf5aebb45000187fc99f2c91879aedafd7a8eaeab9db19ae57a65fb9ace508b8253a279d96fdd3dfdcc9ddfa85e0a7fb57112417d9e741625f337ce2a6569362e8f87e159952ccf799783c830d5199443e66c30e0fc95acb1b431a30ae451a56c94e98039899d9f2ea24598cc7b3f6d363a5b6248425be3c213165574a24b8c867a3dc4b43643410a826568a0e1891131b22f42006136368ad666e5d429a28e46473f157e98374559d29223b4a5314c4072e1a8b0080c1c1073a484f09504e8f30264ab5c5c1edc05d8e2f9fbcd4b86be5b6774dbf3bf3c10c51705cde0ccfe45f19c056288801e5768da31fb4d112fb4a0a827164d33526bad5c4a38d966731f36d792f3636c50267a092c5f52150b665896f1941f2d2a6aea15033805ea2465fafdfa94d2450d4acfb45e6c91bc48c2807b50da164cca7d848e8211656ab39d15febff3678c214ae11201b4beb9ae8b5f075b4e17a0b08f1ecef2dc53624743e20eaf33da156f216532f64a4dfda7e2f98be5241c96426e9a4dfc500dce2f202e276d59ed82002528555407d9a8b216d7db0e4c79621cc76579e6cc7ac2e8ddbdf2c1aaf8f263f0002b0410a50cdda8eaee3fe413c1d5ff6c8b55666e6de9947c21b952d650f112abb16af715c9b3525a59415724ac4fd808fdef64de05d91f43a10a36da60d2e847e3694f82686c174a00c84de0207f9ba02d20b1e73dd60fd645372245041c0be35f37a46cc77b83240f882950a6912e1be9de86b355c99d87d880cbeea3426c06ec937927c65f4edf5ed52dc224a3d280714ecb67490569c92db668bb78c7ce42e41e16f28089611362e5ae459d1f105d11947dee239fcc91350de0e42946e0f202308a80445749decebaa4a7c13f634acf318b3b0a74327ef2f7890a40162969c29ed09152804966498ada0e7cc6c2d3c6345647c6f8ec41093c7e30b0022ae3111e80efeae238fd5eb426c3c2cf35f81fcf370fb93155754fe8763d7d32880ce330beefeacc904feb219491e1845e96a950e2523a9e6f5229aa90132d071b8b1e9cba1e29d56fdaca15c0b940731a31176ffcf6f9c3ace50877b5f3e2eead764a2b447442af776799df1092c83f7bf9b561783321e18ff9c82fa043b7067a06a1a7cbeb3a534bf9424a8cd89ebfd375ddab26c0a932800dd0000d53d4ccafc649d8ebcce4a54fad78fbed1f4616ca5b16100def77b8107b14ab05fcfac65f23d81abd3e9a2ce180e3c10bf60352d6b1b460857c52c3210baee9715b94b39ecd3c97e95f5e3dd2d7babb1e6c4eec7fed7c6fa55d5c8b5d615314ef9e36c6c34427e5a7e8fafbc99343030489ba20af84c7c3fae2cff247a234afa1f9592ded8203578b1f80eff5c3a07be6d3c0a94131fb4c10571c8379d0d422eb743a993848abd36accd3ed0629e262279b3950f20061cf3d99d8afb7c0918cf1a045cbb793b1608205c52ab32cb6a89361319f3bbd6b1fb330595552ae19954b7fca5433bc9e413ef398e41ac00e08c220f8b58eea709e4b90f8829b4287a2c770dedff1278a6ec97d1900e3c3e84e35db91841e11c3693f44e867f51c2188968a1a79d24aa8e41ea4ec0b9e6c9080922e1b75d8d3f00dd21cb468cc23204180659cfe07812fa7b171800367e00fae60590120ffba117dbaff94556f5f7dbdf5fd126e3cb1fef00c8893c4f60138f6863fc4278f089e247add297f86dcf4aa5fb0156196639901e183074eda75efe1c40b5a87f289f2e6a21c6213a8083369a1f835b74d6c8741eff024e7f1852c0ed554881327236c7b04e18d495a8e99cb22289207518d8d36e1b0f98522f4b376f0b30cdb1bb326f77ebd949bf354d052a1e52dabb3d0846d2f12ec85e44b32afdc50b8ef43e4661ee4f8189009517b98033f8fba3e58155fa77605fef0a0a04d5ae9224236d0c5228c695a48a67eb09d0c5a998bd281da0115c50d438f19331b42e51210506418e64064d11d10700c5bf87aed1e270e2b65052e98ddb6caa101c20b1a9f57c880435a6cf0a3ffea5c83694f2bdf6caf32e183a2b344ed92d9a54fbbc2d605a78edbc3bfa02b57da010fd9725b69f3863d0a28fa9f17ae66acfe5240d072e48bef1426f375c6d6875d0ce3c044eec0daf61dcab54aa04cd44f97fba40d4d88f30ccd08f26d24db57050c640687a9a199ccb1c2fe058325a4b9b43b3860c6abb49a6f85d4f6751f25fde8eef2241c31844c4bc36a2fa1f3e5867929f37684c1d14abf247ff53b1edc8507c0ff76464b68db3c12c3b5889fcfec2a47d0a35185a9615f8c8be63c95a7aff06f8f3318e5a2141746d8d41363251c386f09f4daa7a391f2e4c1a82583f4cbc73e9d5de5271786e73b2e277bb63d4a27633c902c7810eca6c2db134463b2ef38b21749c708e2d949157d97a645b792db50f23e0969064bba67dcb2ceb7d5aae4dbd60aa3097d979d361341146a7d343b42e51dbbebaa4eadbc21ce0f2815097cb0508c9e3ed6d8fb692d466bfc1edf1b2758e11f00acb397041d9f910aa401c476738145d8e2da15821c87acc3d95589fd293beca5bb9fcd1eea4cbec0db7de335e5fb5249b89a1688d9c40dd884bda315a1e55bfcc5bfb9370a17476765596d25dda2f889b8bc66868d209fc8638e415f0cada750ea232c6a793aa64e38c0d49a7412fe9948f31f867a0e98c026ba719eecd328b73c1f954f0b63085971cc1823be1ef9148c41c38777fa21b1ed374064aac6778a8a9e3dab4755928af5025237ff98b89661366bd702e2f70be7d67a4608828d86154271f9349b02751e0030a4711abdb4799d80bc9b34527baa6a58de8a1bff5e7fcbb71c505560c6e70bcf5c58642045794b7e85fccf924caf469f2e83dbd4dce90588ead3606c12afd46c1b6587ac8aecef66136370eaac6fbf9142d344448c9fa7f29a44320d8f3655847c06feef6d65a2a26bdc25cb2081eb7f98bb3f9feab89c6bee2c457c315e5d4b0c80c5e94b94ce295a31990d6a18b9b4fa550c82f1490a437ce3c90f3ddaca51e892411f7a4eb3648cd332b2f81a68b7a60a87879997d748887d5dd3a6a6578a98e8bc3eeb78b9e40544a4dfbbe2903ec7bc879f51b00e33e5b59defa5adc3aa39e668aadfc08b42daa5209dab4b9dba5d98c6a3f12e00a3c5b455d1506e110050a15f135e06790fb49cc5259a9eb616e600c4d27a279d6ec272f4b73b5a4f8165c2350523a1e0aa69ffff57e54147216c7ee780a0072513c430a29a1ec5ca7cf4ebb8e4595162380a0f77861316828ca47374cd61e3393b36e40f7c9e5111ac710ccf0bd6b0815061820adee301768752f0a759cb219343416fabc042357fd143dda81774a865dba41904349eda712121c5ded7ef5fcd131952c3d016775cb5b61593625aba23d041568d31cc7d4132f8fd644770a4a5fc9deb28d073f225a8ee7534701ba3d240c7a6605cbfd8155b27fc6799a4e1239e29cf110db55ce5e20b38c33b3adb49987dac8a5b877f7d067cbf721d7073879347270a528733971e87ef318d3386de7a77a87a212648f2fd6778de2988508bd9f4f2d99ef9a3f461229aa1d3aeb46581ec0f2ffe2ceb08878dd9c599a5b25fd13a57e698c332135f3707b5b9d64a0da08eae159bc9d605b683f6802bcfef7fab771cad584cae63fdbf9521db0e066432d8e7f17685e5f6fe92abe10fdde2151d0d2321c2a8148d10ff4138f63fb9b1c862c5f2afa6221472d14db06f9770028c9007c426e18658c24f6f4cbd06fbd1a5182c920c6cc4a2a2631fa4ddabd21e786ccb3215e87f24dc2c3159c693afca6416cb3168fb200029b728a23549635c8a8943b69ea89a113f57a448ac548c1dfbc747c8b75d888451a660a47e1c9278fbc5dbab6e843918d788c66ec634af895925d9522e9a060463197d5d80613841827bcc22d714c8643ddd12991da40d766314511d420743b59187815e1b06e63caacb6f673a944b781ae9d95cce0a684611d70e86428c421fa6134e00efab882b4e6e468b2018f6d59be986a198225ad01d20ae21a42d6006f5cd7ae06f67edd443201c274e99e0099bbc47c022ba01c6d8969872a5183c58397558e008ada6ee4ea3e4b80d9285eb390111fbd2a773d5e487d08a0748b5848240edae13de2db1dd3f0a7d01d8a962217a92003db2046e706ad27962f9f8fbc141aef1caaad7de40778b8b757bca3c4d340befdd50aed43b7ac2e1613bb337982884b47f75ba9ea8fdcb1968f939b2ab90f8ddedbbe17d3f345dca2921695425eba9d61b48702f17395e32d24263299ea9a35de8c49fd6796ce5342f1a2d26300d90dc789e3679f4109e7d0fa9963be5cb048786ff5559c5596e040290f1eb6ad1f4df3f93a01dbcf4855514919c2cfb174cc4f03a1aa3dc2a1dede485b16ac3a9749b9420761dd2d70ceaf04f92daff4be6f03834cc0eff37b38177a171804000d5573e2b4ce5998fb296070879f9f4571e61c2ed78598559c5b217c3c7866ce52e5998655c679f43ecfbcd055ac48a81af2ba7d151d295eeb933caa58c9f508c9f86ed675ba20b5cc9e7352208f939a103ca2f164e658dd01fcca7524e5e963ec9ae2517be1f69ba3ccbe8df1f65e565197ff9552f1428b0ee1445f01177f28af03ca6f3434c120f04154990a0f91f1090e9a323f0bae498b91dca3f14eeca78f210d96303343ef4235037418a9f7fdd72cccfe850a6e4c47ca23e4eeb9b6cee1f83647781cce9277b4f41dc6b7793e2b27c4c8c186688da8d4ee1f81f44245a10577440406ed496edfb96ca23c636f75489334a81ea58c438270d60dc8b61b5fa2f0dc756fe9593e623bfcb8ccd5516c906f9672c51cd31cc452606ac662ac1fe2d65c18798f40895d1016b98bad17745099d8645c16016153c11673d1cc2d05d083b08061e66a5a25ca6c07bf782200aa82f36d6e282f4d6bb036938e819df0247060f27a08e80b0fb048c7aea39ef25874af96b7cca606731d2315ef4ffae78280016124b092bf0e00c6d94c98ffc9a9ee11b02bfa403c2a9896375a501464eefe342d519541cb33d807b0ac8036d5f6ccf85f09cd8f79ade7570b7bce9641edd35654c38c3cec3b67022c40152d6d00e8d340025f95bd64b0132ab2925c7929cc692b5fec458a2fdc3817b24876958cfcac8cd4d3afcf79de99652ae9719763e3c4ca6947711581a25062f8bab47dfc0b20c171db507f67df9a22f28ac5ef24552535891fe7fbea4ef15242b2c4ef31df3494dcd720c088bd2cca466198a44f03c2f4dcd9a782b3434cb044e5aaa0f7512ef3dd32d9537e098b55dd33c18de4f24f73f7af927bba3c7915e1fc93a38a5b9f9e993c1c9a17b257b86bf3077a547bdc40fc9474384b0b50d912523add5ecb54ab1fc1e48b7154a381f5203b700616d3a34796103d0c49eaa2864ec9e4b7203bb0a3a100bba733d18e1155619b09619ce756db14e6384d23c2b4764a401285b2cf1bfb32888a1077d0de9f047a9cad26f6fdda22c65b947c521f247367586ba46ce3a5c1ca6091c677e56a5e0b9af919179a6cf9366722dc779ad4458d5adf40f6f0671a3f78d54a2631a6abaa489f9603a82f4b31bf70738f1e64bace59ceae66640f46b6e3f25dc051177594cddb84063883d6c6e3ab2aa3e2f5bddb894a16b25c95970a630e6f2fe42b5c1f11901273876310693ca6e19ed77602a3842f1b2069c01bcb13b16ed80d0342bf3de0b05662dcd10da1f5092a38c5894639a0db6a0e62058b00c53abd56811f7cecf3927fbaac1804853a478ee0d1b52121110e52ab8f2ba79771c864de8a3526f5430ba4ee8decdb81d60bdea0181b56d91a6ffbbb4bf0442f1926b3e8e1e15e71005e8f1a20e27e35ea76cc162497604dfd9d0ed3b542ba8ead33dcd7b830e3ce1b12fd1f0353f1f45eea6d62a423502b9b364ffa6b92dc05bfd3ab1e0deaa79a2e42ecffc9d57647f1f3e01ff4a3a7499f8d863ec7a0593a0ea4d94efdfc5fbc38c996df201f174f0b32ff6e8af22446eb9c2002d4a7b30ac8c6c5d77052bba7ca078af639a39f8a470870e57bacdf51018363cff4233633e5490930d98ae9471750e915d0a456e7f60a3f6c646933333c5eec11aa345457c8517bbd13acadd4bc2773c03ec7652e80425c68e8ab97a357ee35312ed0474bdb5a1539cb22d8e10a87c70eefd8b9f26994e8b3a14fadf39450459e4ad2136e58c5bdd44a861db1d8892c10e1523774f8321dca50264f6fb5fc4578418546975e68332dc42d884e5965be52bc45272020dde40fa65a0f67add422dd70d25d3ed54a14c5618b7ad30beaeaf6d54b9cc6631ba176a3e7ae4c6d4f70a8e92f2b40493da63b7c37a98861d90f29a462a30f5e2c4a7ab9699b0d104b1aa8279e87831e51ee3bfafc1d3358cb9ea7e3694269954ec3e8705d42703d664722059c47c1bf837894ed15e247e23dd745e3d8397cb728204482bdb5c614de4692adef7110af2958dde7144e0d5fa696fed12354895d931765794e2e4c16eb89b5b2265444bd152914f62cea7c9330dcb37f9a0e32aa0a9ff651562924fee39bc24eef1e5189f7bf3efb3fa6ff27c0009890f2db33c298a22c0cf419e576a058afefc0a8fb3f9078cf344c58f8b063a683f833d2d2fbb057f0982b88a87d2b3155cc32223d1893dda8a207f46777e33bae1f58b6973325fb613fe57e8001bff08f219cdb20a9314de5bbea6225b1e781aaf796cef47c3e826c414ca71f0f85da08b7fdce7fcd7613823103f6a17f4a38a87e144163fd8aa5bfb6230ee684210bd70f3867ee4154505b74f00c62aba397d81e42bb492dbbe697de556dd1d33df7de63ad51e02ef888121854a850f93d9a222629591e19ffe309bdb1b9b9fd3eb25dc1df0e8491c19cd6ff40f2b2da332896c368eaaf581c83e8bffbe8416c86c6a6c34de30966865e927895d4b46b931f826cc11b94828737435dafa4f9558dd97b6fb0dbdeb7b71397c106de0b04edc022cf3dafde623e9f5481a6b7502697268fd49fbaa4fdce873991b553d36616614ad0a0ed37805522e4af6a952c397cb54294f4480fd3a06655c15d88f1f8c2612bc696f874a128f4ff25468d726ece0a80468e08a2bb16f9ec681365690b7461db8ffb4445adddfe973b19bad28fef28d812069e3c9a07c36698ac665c9a4f77c57fb7670f9279a8b6fb030739b53fadd4622d9323fe0254f06bb74c23982b0e2525db8b8d0a370cbb062570a2aa64c15210ba0bf5a19052dcb86ec6cd2936e927a013a45fb6914a3318c59041ef1d5ac6aa08b39c0d5bfb5cbc28ac0e7da844b0ccf03db957900f1952c60ccb413f53d801c15bec5d7678fc9f496bbf36cd20cd8068dba111307d93428b1ce410b3e2cdfdf5493d43a0b3a6881b0ee411a0b16e3a4e65350cdbbec972903ef25d18c8470b3b594044766593d887b211aebff1cfe9e2f022a56ec4fd6573a04938c9cc554569f3e8e8d391a8defc44bb3098a63dcc9595f43d73da9e0f41554fc9d7ddc23f1ca6bdfc60cd411ba7d8bc2c4ad05beb66e6eac530df71d59287b7ab247e52d74d27a61ce98a4aed40ade9eaafdc282a8e967e24043f299b5ef2705b5ca5c1b34707c75976d2b0a18b785b3388f8b6ca340a7d3d8a5f7ca1e40e7a370d48066eb8a0a36ec4fd854aabe6bdc8f7431bf22cf31fdc79192a4003ef0bf40c2c3f4bea46609293d7b0e3eb8065a5d3cef62c4dcf053f12c1a857e65bd9458605bbaf99a93b9d987accd003841a32cb60351735979967cfade3c2a8a69aea7e9a0788b2e779272ce4e359d2e5359086f79aa56417b722f375b3477af4d2fa2df811a15ca84e2a2bb0445a452e70df980a8746d658a442415ade9985ee34159d30fbe95817cd58b9b27c87a077b011e5e8b68602533556d20e4d9d057a46a697d79882911c99949fb1d659f521dc3263bcf4453dab3ff94c7d8b12bd81d17e22a144ddfa6b250d6efd11fc906b511fb5541083bff7c71929575ecd4d830a8eebf03a136ece40351c6b89c2ad14dd6413031c7ec8c5407604f4c3821b032b280914ee9be88a57ca953ba77e3c83884e912425ad2d8a15a73b8307a72e696634bfc6b74c8da6498bc9153e8aa15383694d045f325b0765c1ab48ee88558366e18d46010a3bfd5d7595f9944e2e0f39b2e02a2190069f3396bc04d66ea8ee1aa719047a175048150cc4ed6b8745d147f1b6dd266e896c5ea1e93b357087837864f3e88fdc98d4ece64b14a19736483e664f945cb0e38eaf0cb50af122620833064ff7fb307b6f06a494eb6056b4ab3ab84dd8698672deb25c390b0171d19ee6623e4f47b0a9e64b8e36fb3b1abc2210fdba6f3c94138f66720a85be5e025eeacfcb80d75eaa9918bbebd914f998b9c3ef581b465445cec8541b96ec4d9299156730a0bca221fb38421ad31a90bdee4b4a215fe8171f531900830a7209a245b3a6b8d02854410b06289263718fc6d08a492ee5b6a506985ed8138b7e00000c6f4b7ded35e6fcea4f614b8916161a588355a063061c242c0a5cac779cf927a3f88ded44efd55f142eb8be316649e3b7fdb029a23d638abb0f68358877423c6caed110e57a76a051c287f51b87000acad0344270991de63991ab3ade4738ecdf6bbf48eac914086397b7032f61f483d83f535ec19a4e4a5eb3bf6dcd1efc4ac50de291e9a8d2d3f99f6b812b855ef641ad9ac86a9ac6336929de869c9ca0b6e2bbccd1204dbcdbc8dfa470f4742d783a92513f4a0cfd22731ef3b0afd14fbf05b5329be949ee4e3fd5d76c59b5804127cc1ef2c7ddeba106b33858f2f62305dc5b75951c395f3bf0f1cd4d54251db70a39fc6b631065f70d83454f6b5517ea49055b491cb0ca561b16d0ad241057f02007426e8148d9d4669cc47f38e1948816e8457152d3b59a74b315772f5a11ae842ecd978b150c20f336f6226bf0367513ac98201ba2465b9e4745740f3e46a920a75cc052b69d0d41a42c46695c76210d748f57e889dd77c0706ccb7d629a946a12c7c21d560b273618333411114ac4e933bae43c063e86672463710b8b57b155f96b2a8fe0b7614e0f6cc8bc9d76a268328a94b5afdfd134d42c35e6e35c506cf7fecd518e311063c929cee93f701844925b80503fd58513058e7308ba981b98fe5e23ded26918d81137923b5dda76430e5eb6e7cbf53161a160770f28601873fc0c73dd7b843142ca56ea388d8673a8ce160f4908c60ef0b49d2489f225c84f3c208c0a8e719e201801c19205156fbcf6926a8d60cf69d1bd28f65f80893a0535b4023c25d713ce9be17e4c839505320c18c2835237fdb6cc5e124c766e659a17637c6dad1bac49ad6919c8b7a1318c687513f876386da7e77a99c001be4499642d9922ebd3a302cbaee4cf7f5a9bd322e5c5ac58ab0fd250d4ce4987bb213ab5c3e75f07fe0d540b6ebaa8b0f92bc6d84770def2ce2c1c905e4e545118fd15383b8c7ace311869db2e6d3ea488509ac157328547c8cd2e50c88fccc0340c00948899be0306ee576999e31c0607ce173cea4853fb765f444a0d753e47f63d7ea6be648c1bfcc60308abe7d862c68a1a90a87bec1b8b15ebe16128d346b6dfb03a1a7ba90477a3d81a3bf434a4790a5137e1c7ed64304d75e026467be2423121e955a87dfe82fedce59aa263baa83a1da5f83c2dcb51292e5fad1cf24a93472c58160964f139e3242db3f75ffe4eda4825c1bde6ebd7aa2b503bbaad18072ef1aee2932cb7c7d854b2f4499ecac1a921e40c8ffb8e6fff2450d2e3430eb9018f8c69f2dc707513cf522d5734d2acb91f150a9ea542e5daed3449809f8e76723154e3d0c76ee4a656d07df3bb452bfd872a270db53e5439706fb6ff2dd88c435bb113ca57f2ffe70d9fdcfdc41340ad89803bbaec959dfe9a32d8138f176793b6040cd4c54aea0a34525f18efc6523faa6b3ec7e4b39eb491441f6ca53e69613d3418002d92f9ece907b50f1a8e3fe1c77368d05887293770bc692b02640d9e6f36d47c00217f4d199fadf75066f2fac4bd3a836de016d9b669cbb31c41d05a5c8a00b9d69dd8d1c94ac1092b891b7aaaf62c2e7d22766841a3bebdf390fa669791f24559dedff72c32fcc9c97bb9b8b13445a2d4f76c6971daa55c2284c41851629b88fb113f138a6a5ff521b34a845eed5b2904e8f5ee2a2fffa07007ad637fa7519ca5a315b536c909aced28a720d6f2ad62ea4c7b48399d27a32a20bce288e8ea3228ce97967043e7e1e06295159ab85d7d805e01e86f312834bbdffa02a5ff2601a7b2459ea9f030eff49df6d3962580917863f2c89e32c66d68b940c424f894113aeebb6d3e857c71540ae5c9a0dd96c491c43295946ad467902f196103d54a33523529cbabb8b82c04a875546749a7a975320d0c201ee704dcb30e91890573fe6348a0d0f37f79badebef3cd1306fc4dbb7db1744aa045eceb66fa66bb2e171efacb4e1f0758fc77b6aadc819b1a7d94d389600384b3eb4946870b5f9680c28e6734d8111ab4dcab7e56511d375aec6001fa7dcea48c3236dbbb80836ab9b6b5b75f4ec4eebe3d2e8d64c5ead9e6c0e6d98c89f911de90ff118bb6e2039f0dbb55bc820fbf9b436071ae80ae64edf5d8cb85fdb7b7a0e21f02d24df941da8c5010b5a27db34681e704391d3b4d771d7ccb471a303a258955cf4cca0146459c903d21854a91373d06101419ee3b52976496710448db0ed5f4b557f670ed2cf5a763b790e18ee88a8e2aa86e03a07d564a394ca4d8c98bcb2f2c48b9903d9344c4432a9dca485a15277155bd38a0aa3dd34f3b5ec24580b8cea5c45b2469ab8ba6cfefe3e7e639082aec95ca0ecd616ab9774b83a099c0632b4fdc2a23210486dd1b6889c4825e3285e0672f88f93a3481ed4e0545b85fc4ca31a2c584a2b4b0fc27dafdc337ef604c5dccebf69acfd0763c6d1b26c665c0616dee4dee5d2d7b80cd332cf84586382824072d7c3a9a71cf02f3406b7f11cc2004822701108c4636aebf80d2f07e55b38331b4e496fc05eb00ae9370d33720da213a1dde48dc65c94eab29da2268a1eee83c5d6801fbf24152fec6033c4fc55bd14b56223d5c48da0e78559c6f6bb31bd2fed400bfd6da3311e96839a62a3527a9e1f616124751451bf8e77561693f0da1f0bd30dc005aadeec77d4070778f469ea4ba277d38c871a2b8312f4100bdc2dd7831bff46ad639741fb1eeb69bd2130c3a20afc0ea4867f04c80d112909834799749b25333a238a434fe1b9a947110d260bcbd01e80b69dfd408101180aa984e0a5f99f2f6e240e3e8584ab53766170d13100b7c95960d0cd37ba3c85cf48055414c57ff8f94df64caa0d624d36eac6d059f11ef76cff51755531ae9dc235fe0b3a55e3a852e73481159dfb1471ef6ea06f97cdbf859045ff4dd7a89c2014b4d4925b96179739f0724fcb445bc3296afdb13024f9eb54be0712671d94acbddd88a4d27850b90c1ac76a4c255a2aa611ca2d16c58a383f73f86c62b61ef2be35864f9bcaf1a08b9b89a7605b93e6edd011875e7ca6587ef6cd2305199a5c4030c6341e559ff7d4cc87a87085e69dc70c230ca46327e718189dcdee089f7f72581b61e314c3b376c3072d8e75de464420a86f7d64d8245916e3d71fa7b1021f1bb1815baf4f4ceb1eb87a8dc136f17a8130228f559f4637a94295e9d951f1e3ac48c1a9cdd9c26bd407b6ca7efb78206285b7b76eaf6518d2decee7d6895b3d28e6e2873a896b0bdb5b18aa822e68aa9d91f1f5a70c489d5e3b7dd6815adb6482a3e8861cfc9610c1569f967607e0b4685861e6cab162d4ccc78072c72fc86e6325a66168442461eb5dabe01979f07bfaf80db00b0109274506f0476588f7c150d3c6a377c2aff2e08a7b316efc89ddde2b8bbdf26ed0876f478fe224aaf9ff74e094834deb59bcd3bea1e64c7bffd439d85ba11a930c5d53843701821a772254eca30a85cd43052341e72d19c7ce1a6fb91ca23fd3001ab526c1b9c0ed518857d18bbc1e2caa0b0b3a35e6a7853ef283f2ae9a2572d1ceca57b3169313aab34cd6a8d920c2625458cac4d57df04e48bfc16f8b974e178909535e26b965ae2ca251c881a995c6fb6206cee30a49dfb6e91ef822780527430c5ade57c0572e37f43d6a86d6a62211061c1b27cfd824320963cea0bfdc0e6123ef780afe27f4c0b5a3472cae09a9d1bfd5855ca6c6b0e214f2a2dd0104b027b518356adfb12dbfda7772f26ce6f9c98f0e9d8ef19fa4ff34677a3d233c41c0fa424bfa7e34261c39156d7b7328100db7fd1f3242db62172e3371b2735ca7d4e67d2f3b98e04a0fd74b9fc3f555e3ccbd3d024bb482758fd9acf44c8d88bf73105031e162bdb9ef7c2f9c08ae7f771c32bd945a0550e17c00f8c66835e9d7970f9850eafd31107efc955143254bcc9a67ba36013176080185504228c705d0a4e6c7c8f2c1792c79163206efa5deab2ab97b2986c4d8248e0b1368ef585981b11045edb837d014d0110c64d9eb5d79cde7192ad70d86c1460063f80abbd69c944ec1ac1758e2bd239bc49429300054f4bb0368d0cb30213b36d14c4386892fe4d10f19d30e075074f12a507714a853bbadf4f633ee90dfe85fd7939185afc14ce7621cc87e178ac6b9622e61fc09d29554bb5186f23447d8a35e9d36f64b2a0390381677a4dabc577b6dfcd942772eec29d51e1ff351e4cb792a234694da0a8fa4804d0892f4ef24cfadcb93c767484cb52b912a7d4eb8417e19406fa99414cdb410836a53841fe26ef0fc68933af2a73170c7fed35108f01157df71dd1156ccaf6f7540613e8af39196dda838705d60f1ed1a1bd8ad7178cdfe6e9ddbafc2e61650aaec18fcec6193ef860ba31bc6af99761f5dcde3972e45ac3efb17952a1d7be1792b06dc11928f60b54d856538f41ddb6d57e24f53c19983b8518c97e321720cbacda5702f6cb194e83d0a5a3d2342d262767bb78535b96134196546d064c927bb91b50e7e708f01948ba39158744333d441b68cbcb14416788596ce5a2307638d3b4ee925298dfac9d96475727a8e26a7b950ca53242e88bef7dc34be4461c703953cc3c2fece071e8d8bd5a74ede4b271735394725c28f45a3671dfc136fbbd19b37e834663a272b8421fbfc15fbcef3d18a2cc8bb4e9d9e8cad0f9cc75450148377a698bb949a43fa5067270e6c3f55a5f79da08b0c4a9d0fe4445f82b37497837ead2dece261244d4535e765c6f9ba336fdecbd48c75966d0ebd207289dd62fbd4f0e531fa24bfc4791d8ed5174367a2e16a43840c6aad41e3b93e09691f9923283d1115a2b451efe8a527e8f89b1401dde5161eb0a87f676894ba5145f7bc8b2a2a08e69b940d3b9ddafa9659ff272874f1562a2619bd04f93fde11c9648779c592dea291be88ae8da90c6cba989d911d0ab05250118e56e56395f634c2e4d45bd78d936dbd70100252e56966554caa6e51021d605bd76edffd8f61fcd03606f2318f9a408d03a2605117e2591a326f317dff9872b30104391a0a4ad3f54cfa769f4974168cba0f82ba17041a8135633708a68b665e357e0ad0cd7b1caf45e84ab0f1736336d08a7808109210fc5ad9814c1796bd82db351a3e704b5f123fe9f8f9d30d5685d16f01ea9ad21ca79c214d0bccc97cd9556e2e8d94295bee1131524afcd01346b482f17e3be3be5a9d02c7d4dd9b9fbfd6edd86f65da6905c3f45425b8582ae89049297d46363b7f0a544f80f14905fb306bc7fee6ac69ae40e1b24d9811f9661cec821e7df5ef5110f88d312ac5339a50a1c4e35251d94e0befb8967fa2b906744b832845ece13c1f8e630e1c281789b60ef09e9c5fcdef4e543f042d3c058b5a066772b240b5d1506d45adb3e8293d7b247ef76b1372fcb1ae35a16e24884204d62882f78d7bff054301027a5c6bf0cede50987f0b8e7b1fd76fcf72a8055e76bc52bc814310eb2c9f999e1aedb3766178cb05e81ca38dec64576cc80228e00f1422688ae1e7f56c4f6718692f1d7309f99cb2215b997012bed94b2f08d6a9f4b47e77452d50f50557b6e63d4f524439d35f7a054332857595f77b1ef290b42d4c31c1d2d039c69592b8c671bf57275e5bd72fdb56129954bd5fffedbd64402d9cf0e23060656f70b0d0a31e8ffaed2aa6c2a1482662c8f46b11d682a1835811d0095f85b0f3d30bac7b6784c122056d0058ef4406852339d8141a44eb7a01e4b50240b0b16812758d7e2ebf621afd8508bf8f427f4deb467574e1e9faea1cfb38ad45f19932eea0d97cfe312ba4295a37efdd62152ac47677754d0fd71ca9f355274246f366a1e88cbca7970d74e65d9b7ab5c1aa2ee4e950486a4f71ef0f992546bd326236bd1ef11a692f364788936bbd5a039b8e11df8b082a80737d102d88a92b79a8a151c155a0ca04d2baecdc0102699bb10e6b6d978714ad7d07e092e3a5768598071c76e04e187e285c6fc08caf8563864e91b4cfc884e532966049efb50323ebc8984d8a1170f37ff312af579f545ead37cc8eef25a52796100ada781da94c7b4d5a0a8bfdc1d6f7d0abebf4af09c70a1f9e22c4953a05fcfde3b16e7394e2a0ceca462ced744272ebf47d16d6063e87f730a673312fe1f10412eeb2a74f33410c6302bb4c1b86c2ff362fec2ba113538167cef340bf120a35ca65f74bb98ccc92f6e60efaaadd78fd5ac41432769266e7d7591269e4da2aec889a4a51cfb69ed5f66dab4d38ce39d8b380f7d09d17aaf2d36f0b264225e733581848528bd415474cf066a03503ce3e72bdacd10bb94a5ca304b455c1f0317c3fdb9ef75d7857cc4797b1b37cb05a32d05b4a75bf579ff595025390933fba4df0c9c4e6f3e9aaa423a7233167d7d7037cdab0f6a872c4cca02e35669bb252485dc0fc066d5d13b038ea9a4eab91cc5cd1db21615d00ff7f742a6b8a0df1712a3a74eeb38da4dea4a9104f95f3fe8291e355ac9f4355d4426fa57ac340ec7860043d027ce62a152d54d94e5ed143590344f664f1c0dbdc7b9ad532c277d76fabd94ff85b7f5feecfc3ff9fbcc84c294b073903ed3d098c2cdff1b0f2ee17f474af20bbca5084ddfe2301f0b7152df30e6ed3d7f2bd1c49bb320050c40f7c4edf9666fa385b458bf0a6a55b34d45f03fef9f72c2dfba39890428084c51a675c2507dbd760ac82d69119bda9fb7a35d321ee3bf12c589ec01ed324e05f8fb22f0271660972e363313a355f469b380e495de7dd510fc099ad40ebb169dc1a2112be88a0693cf86dbf47e9b81d2444dd845ebcd236e35e9833c08ea364c4c96456e44108264cba85fed342afc882eec2a273e9b83c65c97a8bba1f51ee2f4aef6f07a96c79933ca373ad08592b8f3f8fae7dc427b363bd8974e287cfc41c8aa62f666bb993d92da350fc1a0284524364fec375f98b9a757959ec0934a34bcedac2181d2ce390f79635b6fe0d3a2f65e471f65e1a99a702a5572b3ed3bb71cccea078c5aa020ca489d505e833c853dd155989f9dbd5619f621c579412d7641ebee51c2c548886a8a9ad18a9b4d4eb102e780effe4f8c28ee094a08a60fe4b35be16eb9c17dddfa30c24e98124429bdc06a4ed71919ffecdec856cba0ab2130739d9ae3267be008f4189a38a1d5370496e39c27d8ba01e975b55e9cc7518654c1867193d074a24b9416df456504780acafaf9a033ead10aa18bbc673d4a562199d32c08f0932fec0c81b941d8dbca840abe1babc68698ca2456953b45abbf823aa18a85a35e66eb7307c9cd4f29c29c4ca55f7f9588a885b963415adb22d0f033fb980398171b15afd2c52a8aab933c2805e32837eef01dba94012a18901e9ab0bdcca10eec7da348bd220fdafd8f35ff27f4a41deb57d04cf8b7fc12b4492f401b96724808ac44f263c40bdd650e945862a2ee90f556bfdfc540c4d43e49581460b48ec146ee9676156feda2a23390a03ec5afc990409e48bde24d00e1167e04f8a2c204211599d378c41cf0dcab8364ea75885520ab190cff3b324c7deaf16b367c17862338a1a4955f87da0a1b5da4ec1cb545d060458dd60fce281012f13e719dbc4dac4ca7f052881123ad6d4136d22e2a4225d85bb8333e9d1d124eb6b15870f57f63664961bb2ec86cc7967940f8c92d04191f2f9ea3ec4f963038bb4c36245e089815874517aa53cb20c050d6cbe57380a65d74b3ed83778602f3e53190c7b21819cb1f2168aaf869a5c57a997d9d7cb2b4df98b2cdba73d1d7742bcecb5beb234b00b6a79415ac596378e61a5953c801c87f467669b355b31bd347aadd7f4f1984ddf9d1de95bab69f2393dc4526db9d8feb911a9dfa805b462cdd825a8eb15864bed323e516fa2581880094463fca5e5f43467cbf4102874c0f228bcd3009dd05c77860c34767e7b038b35034bd705967ea781efa816e9aa97c7e93b4bc933ae9d418ceae0cf2cf42e9ca82b4a8b87045d11dd2c244d9eac00ad4897b9b83cbffaf46f91ee00092e0e6cc7cb533ff384374e05609e9192176f7665f938007fefa9750ecc4ec186d6fbb355a974dda7d9d97d1e9d7ae666b5020951f2d56f9885b9cdffb2f1b59d39743aca9ca6cf1988369e5aa6a0f065b1b7b9a1a81e4f0b77c88a87d709f94af9f15ee23b1c25a28cfc944f4b3dbab1f336883d86380bceef246126f0228e6ea8aa1be83992b91d59e2188a3c0ff1ea6a68b0b12480bd52720e6f2e681cae1ad9b98fcd03eb5d2eeac5888d50a71aee9f0387e09b1467ac24b69ff0364f3ee7e531d0f846cf37a4e36d98e1a7b608feaadce873cb6ef24852cb88043934c3bcf28c6488edee100de2c38af1f8b795f207a57fc5be0f8f81bb0326b8c3f738bed181126f103f20dc8c0f4a7a796fe89858e1da184e7676a2a38f639eddfb9b628e8cca1eb313b10946cd78b4c102aeef53e503119cd142093924846a51e32adf82e51019d4b27598c28b9fad71e743d7d2356dc984a544a7e32a79f4664a7a367933fcc25e818369f672734d7083dc68c3ce667afc8f179fc3876d49948142ce28a279a32a48241ee59ef957c14ddb92d327130f14c315945690c21021db1b571411cd8ae6ae61b042c046020301e89080f673ee0eaf721152d4386ec1de5f08ff8893f0b6bf3a665050703231bbbb9e516f057b56040e1e68e139d38c7c00321b648ee97ea8cd62c2263cef0f4da533140f399631fe3b8b081252414d8a7af9ee09989d1845f4c719c0f031a18f96ec086c1b0383b143e7336a910ca8183b112cca18f2f0e35da8d8382f89ef22a47070cf204c7a981b4288f697d4be99e7e6658652ea6ce4d0f91b2827e0ef18fa6531fb66ed5991218f0fae7fc27967abef1868c4aba354227d4cd408b1fa01921f6650983ea133ebe354c19e694450ae93f9b2ca7297bb4e736d8fc3b50868eaef9bd86ee2ef58ccee62917978b3d4d00dda33a215dfba9328c029b31f5f4e8df522ad3cc211e7e0a988c803ffbc812ea5f0173a2a4f907742e408498191b5a6c767cb377870df4755281d5a3ff99ecac9841baf88b09b45ed6ce5c976e4ef7c7f56c162b9098620b922a20c464d1cbe20d34e637f8624deb2269184e9bcd345b88c40dfa5918a5e4798206cb82739e0b1c4e9cb4106039e3b1017129288eb15a09aa50cc0375c12e31fe941cb6918121a2a1fa188ee45caa997066e256c02c333b3edaaac90b8199ce11c06dc62f18a3a51042957146f2cdeb179c8171f73f41a2694d6e65a79c41a2333e3686486137b1906099395164c3d845c06a30d2d3ea12eff1a6da440f7dd34ff895753bccf3130122cfd22873a4daeeb90732cb568ef8771f321d1c6086049765efe875f10bb7839d43b7dc974a4e3ea6b063d171a9128e4e983633dd20f04e2cd64584654b45e938d7ea7f08e527e2bf0f0a5b462db45d89adb7fc61c553ace880d2ca49fd869164402117ac28dc87c5868bab71fc203b84f8210798e72de57ea7de9585ee7f910f4e9ad053bde203cf43640496f3c73443fe4a8915d2196053ac175500e940f8e8dd487e84b5bd9e107b8ab4ad30ef8d69632cdf039e433032622b0c835fefafa45d4f0c41b2233e474107d20b50485da81339fbd20911d59cc7d9dd4bc2e0b47f3b1b3ad42374c0ad08d40626c076a1ab66491377d3627a17b984ac04d13aeab1bd4e43040060903d391c4fef60a458438d7c2fe63ae70b38fb8840c2329f2fa43aa03e562df0e1629abe281f63cc7c2d8a5ac7b075a3c2f162aeb086d9e54b0c5ef6134a5b4db468dc77fc1103a92570c44e648ed4131cc630c4f03e4b2f9b7511420cb4c00a0689964a070dfde5cf9a82357cd17fd6f574e4a26db6d8d8a78142fd45b5575a21447925d7a6256415202a0ccec04bac5a174a843b202522cbaf25dd125310a691bf6c04f83ae6c306b34e697b8337761eb6eff4347c62e9d035e1aa30be9b033ba311bf85cb1494fb70c6f23fe780f72db9c6c8b4f8c8093c516fb6ccb982db2eef75239e5579868dd55643c2ebe773d84b4c3b53feecd86b8ae70ed562cd9e7b4339fda497f2dd35cc8c30014654864a1f5c7568bb31f1c20ed38c947d63a1b34706d01dca23543b96eecd5038aec014d893afdb0179a73e5d7ce0287213013b71d6a2b2d3d3329482c9903bfb397f17d6b511895c0311840c6a055b445ea38c1154184fc64d8596a1db590d56b5b888fb9ddff83c083977a57560821c66414ec6de11f15a98a0b3d853df49d1c8a56026226e7e1e9bb3c3b716dfb864773c06cbd39c5009d5abaf5509955651b9f246d50f64f422887de398f92aa79f5428d4be9006bca81856210b7781ae9bc5ac9cf2de3a22b4197e57a2fe3619569283b606b446cf0422d314058f148917e9dad43265a326e590a0451a8a35dd9443269d858a0666f34150da644c64d8e4745211d013009fe4f04dfecfd7447e695f12454ef5b7c9f3350ff8ed0761ad73cb4871e2d22e6e749ceedd5321ce6012cb316ebaa05c9dbf9d5234a75315ed5df280c1ca916e121cdc236871361097716fa3b35a660fd5729bedbd139ce34db8ab29c177fc656fcff5af6d21c7c4995cbe4e99c75a567332bd842c339971b63faca21959943f7d2ce2e374286bb8d06a5582151850c71fcb441b50bbd256f34a23fa2283909e6f34559605293d39015f52c82df23ed39f8c37e6c15c94554f3a5e7d4c593e806aea1ebbcdd266584dd9148645c521261dd70a108b051d936a9207bb082cad684bcf30d9f36355c13169c3c38a279e2e257dce65d7f8c60ea11daedbbec5ef894adf4044260f15a2a9e4a725805d866121f171f384e666ac0169d0e0d5afa616e58f48c693263559aabfe6ab7dcd9632b481c40770b702346b6148b74b6324cad32b362398b7dafbdbc8633364186bdc20d0881ad338f73b1644a663a7f3c290a1f93f7e0adfeaa1ffb3efcc3c20006b3a4b31cd1f5c3c01db6d2ad1f418e6e78a400cc1e7b2cca06f8a391b1dd1d9bd35de692a06888d1dfab601c37808991d79a6cc5e87bf936bfc090865ec7afd4dd335e745523f8803abb9623d54a3a666d00d9b0353910b30f8afcb836ff7c5ec5247425984460b62f64caa234755d85970bed17782fc7c089a19a544a880b1eebc14c363ee50a9114844eb83cbc19fa662c39d58ef1bd82f1ac7ec5b29feaa1ccd3fcf197647048c8c9791f38d39c58d454cd7b694fe84eb67c3357336af946ea23b48f154c5512c21b227e63bcfeaaa04ae87e6027a380a5b706a22cf853a51cee12fe72e7ca7ec50de09676fd36926998d16d33f539dc1fb59e43a824411497b2ceecd1aef0615a597ccf57709ed27e7ae885e49a9ae992c9b5aa05165db4b1315bb997b4906519bddf1c8976ecfa6af7c9b7895272c2c0972203eb4d96247568634aaa85fa93e304e375341dad0e19d94eba0e1774f4095f3fd82a187049ed8a52ab1ad9422dbf3d9b6a70ff0dc527b19dce49b7ec49a0b0ccdee5d529a4062de19ee1bbd980e6adf3122d599344ee2371b4d7f15435a527b09d4ea3a1f8ac5d5b2df9a01390486a5df421a7405f806e4de1de8f46cc9e137a1b0b7c83ac53e4205ce2e7206cc2bdf39b2164c3734dadcbf2b669fc5b15bdcf4ddba31871963fe457857ddbb8ff884d7b0399ef0c604df356decc4cd68717bc29fb78fc66a11b367c1efdbca11f4bae259a1da674f5be6580650ed7b5455aa49f3ff325f9b370acd31bd359165fdbeaa13f31a64340ae631efcad27eda700bb479aada4654d2a88d7884547ed4fdb5849e2e5974fd7a768d6a740140efb3b52115675ed730c5f8110a6ddb6b884370eff78261cf9ff8ea048b47e2765d21126777f38f549c67b10e06cd870b4475aa9394c50913435a7bbde803d26413ba908cec5550b3f0e1d341e7bc88c15eb715925e66b3840b8d2dfa65045699c60b7c4265fcfe59e7868e6958174d90177620f6e7429744d95f4310320332dde683bc5576d9917c72a4441db07827895c9a80f83b5c9bcdddf1529cad3d7b6d385da6ced6ca54c41058c45fd624ca45acea8d4e8d397a9a743ec6b38943659431c2204d7d180684136a4a3d61b446400d9de162ad632f6cd01b48ee56e0e3b8043fc1a37f38115268ec2e3712c7a263e27853b6bcf28006b4b2f05d8e945242de1f9ac69dc96b5ecb6a9b6cce2cb9e61f4647f9065079f8eeb26b1842f4f26ff31aec8c9d601e2f3d42346b2e39ea550ac438a2292fc4b852f1ae43c67b25e30f101f8baf9b6004e0604e3dd88f067c0fc3f2f39d960f1f20ed173674409b6597102a9f6e14363f963d7ba694e21e574b397d501c89d7cdc1f17017300a9605e662759916c8c513243f786bdc6c5204578db07ca09f7a6e7ddb5418a5838a4d07a3f3b98d9a127cb75865edced017030ccf23633533455c63ca68b6dc2b046797c117199f4e8bcb78bf24c970e1d95f25bb29436478e14b1d6f353603f5a1c09302dc040ec7051fd5d2b67b6cd6a2ce3404f7c8cbb61b1a4a4fb27722638ac0de2f52f20839cd40b3de6c194cfda7bd0927c9e03beaf13d8912f0ebd7323831102f814646ae0df83f72b5f92ca8b0a7d021975bba41e1a6d6aed817e976c5c7f1409fff3f569fd91be85c415e1233be46844f648ade9e60a445b7c6d9819d9272ba4547a88299e87fc0ca23f61050bfabc553cab042091b71d813459328534bdea20cf55de19499be78a1febd20ca5220d90ba80724205c2c79ed0648b31e4cb357f3083fa2d40baba705261d07e24f0bd49c01862379b867cab68c5e68189aca39acaa01de8a864375e2b4d643b1dd73f92a55d94232e36b7525cd921f989896137314a742c402658590ca1f0a0e8233745b5e90ce628aa07c5a839a1ea6cf5dcc0191129022a06384e9852b5d289094baad6e35af4bc7a79237e26b34095e11491c9dae86468baaeb2026433efa38bb1f884db536bfb337e3fb9c42bbf53653fd3fabb9a3a1192f0ddfb08c1dc13283062428c3a3acd7921d1bfe0b6f3680bbd727feea3a06b1d00fbfd81df8670de150805510ccee254adaddb9eeec820375158e95d349fcdb57d77e37e4bf8f220cc7789e8532f33a24195e72af28cb4dfcdc7f96ae906317410faa957a77335271f67a11f06c64853ae19f0eeffd284e11a3f514a12ba681028b22825913816cdf8eb59b488ef06bcb118444a79a668ec6d396171eb9ef03adc121b9cd3436c92c4334e4d811fa59e2882eee1264f48c86a20db05fe693b61a38a0f410cef60bee734978f2bf38972c021f04e14b240ac9fde60399aa406a1245f39b5ebdf993fb3bb137f2c3941327e7b91b54d9ed6b1c59c95eb0d8dbb473b1442af4afa8689b7c958dc4579ecb2cf36253635fac682f28801f37312373147c8e7779b52d9f63bbb70ba58372cbe43411137285a3e783f150ed9b68ad4ecca9123cdb5c1e59551637ef484616d76ddc85151c5a912a0bbfe7e7c98e98b0293f86c5b4709fd1db455d6afd9aaf89c8b1fee09ccfea4daa25df9816a3a06ce1f80a24bfd1eb9f039112771467575622a342819afa2460a1b0d6f80d6ec42ca711d76cefea5fedef7727f85ccc09486a58b542f52e823ac2f34922bcb3135cae97bf69cda06853935ad638d55046894c0dad57c8f44807ea144a074e2fea4a0b8c8cc2dd3ebaf9f4fb863c1de4cb0dd7203adfab18cda6e65c0da14d9b9d0e1b0e6b7e483c19aae9ebdc9caced6937cebd1e2e512479c062a11acf774e7bb8a379535eb9505f4b5e16e2ada1ce92a2e8927067448e958180bc76652dcd4975ed48bb37d425d0811d8f41b25fa6ea00c45dddd20db14b7cf3c0d10b38a6ffcfa429bde32419a6bb69fbcb70fe5f800ed529241314479b428dd0549fafdf3a55ddce521ec03939955a18cb8cda2f3550b4f3d9b53d8e8a9c22150335973beb48fd8d26a367290cabee0094be097d3410a44fb28c9a21b164bad8ebfc8045bd09eec244d3ca8e49cf60f0d8a9e8d05811d07b2cffd7533e5e0e8f2c4631802dd9cdcee2736a142c7198337cb6e919eaf5b8337efc1449a1266d3369992436fb8ab8225c76594dd5e5ebdb0b5962bd9ef4f9e3718ecc1cdeb9f53b494b5497188efd2aadd2dd8fb192bc5bf14b1de22dc4341ad54312fb0eed0f902fce205d55055fefa45cdff123d7e2bade04c8853db7f4195c8c10804eb691d704b96dc6cb93259a23ae23e1c5f1a76ae9fa9de69996040c94646b9c9579f414b935d84d6bc0f70ff037780dbf987c23faa0535b898b4642d5145ae1b472ed5b9f57ed04ad3bdbceb2665a8a3fe6bd527d34913685aa5640361f52e00364de62be7c500b1adcaa92310b880306f5c376d7ae82f1b050342f28a2b54e28e491142604a43c98ab360ff070e6f457e51a37de4537e097b0e3ea2ac501a63f8cf97f3d6db198f673c10e29d21c688c2898b4488903c6f5ac3c4737b1d5004d137a86bb15c0fcd090ba5e42219d026f8d8f2848d31d0f90dac423cb75ba7ecd3700d6065c7b81d237c277d8062459df83d1eab50ba865b15c117983f8e9c40212d6de1e76b189b0ee108691d763d20229c0051247c30dddbdb3a14e4d69e09f914384fd5550b55de90a502db8f81df3d0bf33e8148e0a265373f93b1af792de3b69363f11912d170b07665bc849aba574dafeaed60a471255597ea544f6d5172aa43d3333d9f1bb68f9d8eedad3b741a2bd21f0ce6ab06e6c947b91a090b85c764231a898e7c315a9e1768b9eca83827b92aae43a771b7d1d66c8e844d36540d67adb50e19a89c9184ce7c7ecf955e7caf1f454d75044957d54d498daeeb0dcaa3ab0a5e6f103f6027b0c8e21e30b7279f06c19cc071b98746b8d2c0e149461372b430b0d3286262156154ed6916f0c8a4820dc8562399e971d4a12e89690dab4c8d28b30be170f9c8a534cd91d4277492fa45996083b9333e9078508c3ccb690c3fd423d7fe2bf594c8489e44fc929831bc199a219406885ed5f00b362aa15cc7604a17a38e85dc3dfd092c68038516f208fc452fb4fa88c9176d2af8557dd242c7682faa8da1e730168afdfcce77760de5780615726b025590d92da3051193d013699a5203571153e4be4ef16717c6367b61fba8014a301210526258d14ed6b73d60a365c5338bc60486c92a44ce103f55260c9896a069dcbfeb0e3834e41e494cc4870481e025d732e05faf0936884af1e69632bc16adc7ab70a7f955f7f82adccdf3e84e4249cb3559a1ea8dab4f732a3ead954279720de66a152dd7e6ac718381f84c28b42e64625a040b6de681fa543084d732d64a774653e33588cc9c8e92788725bc928bf4a90d122c4fda2046def513f6aed96d24c363b31deeaa2cd0bbc2167315301aea8a320778ac59a6a9a866a0638f6b2c8192d2d19a5d957bf8cf51036553ac474634416e14bd66324d9e6db380c7b057ba5813acc65b48ce8abcbff74ad0e233938cfcab854af10e0873a8710a5924ba4bcd07b5f1b90a7f8608608215062251e48cdd1f3eddcbb7280ee1bbb873418a12688109dd342dca4a07739e5999988e8883d13f5fc8d3b1dd2203092a449ba500cb1e25a554558d81b0393fc46d260452ee645fdd18eb3c7b0c68dbb87034c38feb61d85b762e9f87362208afec3748291114f64d3fe7a06b7608a0668c95249a704ce32d88c3886c76b025e4e3b11aafc2c40fabe81eeaa142cbfc79506bf292848d26c5e3b8c034e9af8980fe60761cba49c8d53c8c5f3d691a35178c40929a58c0d32889ad674f36d3234154e44f33907290c90e67076c4e8ca7e7dd1250c9e2e1eb010840bd276c6ee25e3da6eae7d8e1910a4ac7a42841ac1545b3d5c144488e3277f99fc201f129bd3aae73bd3c85c32a327f013e3b6ce40ade0819eb831147800b5745047a04b2cb07ea5e461e95d7dae88c499aed0488a1e563fab5da784f92fb4751da51349df5320a9b459125efa30e5ebe0efa55e7152583ab3dfee44685da5a8cc04cb91cb817cf3ee65e22762dbb152cf4dac949c56d4857616d74877b790f203f576995f85e31eb8c94910113758fb0bf671002fff176e06c4a3e2a58bcadb903932b727ab31e3482a386a774d6af6b778b552e07c6d9660d3b416e1c9134f9441b6e1b43a30ccf40a611b868808bb9c5447720081ed9a43df1485b5182e9071175e05cd65352936e2eb53ed2267e83b7e964d6eabeeec399c6432f30104b1e09bfee48ca514cb383164318ef063c8b881b35921c0135a310ca4f22284b9d0aeae16b1c9ee16094eb5e5e827442296f7c533e65235b1e511b31dda7646a4fd2cc1d1b67aa716c32bd896979f4d1fac53426d34d45a756ca25c1b518fda847a511fc3c9ecb3a9b42e2eadacc210eb8599e19f4f7b71e05d21e576a73ab0fdb4b829ea4a3853ccbcaa897b277cb0bca209963dd77517e430be48aaf020228cbc70d6c42e926bc850bd313ba0f424619ec67d990cb5442a7a206aabad19580e64ae12a219e0ca366872c991e41b408d628ef858c208d6796989061538f736ea05dd600ef095cb8007e170176a6e3edb78b7df27f077cfaf778e219d27712610e2151542d873817cf377b25d266fd765de3b05891f83808c194bad871ff5d45514c4fa0db8cc5d87d5c6402e4c2c163e2709475783a8056e3182549847f899c41c6ff5f3d442a536488b12f76e84bfcd1ec95405c08a73c7183189d17a1f125915473b3a9998480d7256d0298de110854bdc8a498258f9a03fba0c802ca2ba1e8e3501c871c4a4a950314006d923ab12a39c5178bc049f02283dd1035382e900d8adc81581cfbe1b4dfb75853c010a30c3257eae070c0979180961061806d0918cf31643ec2d77a29679caaef3ea9854ea33fd34426e8abde615240e4cc01a3e8d4800837f290f31def003304643b8642447915c5a66adc13263b727f2a8b82eb516d0a4f4d16cc305648d9cdaba7e3a5a1d3e9a96e5bad58275cd3bf81d1f91f8c35bc1813e8a94531dfb3aef671bc5624212697c1d25ead2da6e586968fcbf5b9d7fb3337a9cee9400a5732ab2e353e42438e4a44e818b7c48891aad87199c76e57b38213e1739a0d46ee5987504810d96eb5a42a589e717cf832e2a675aa776471aa3e4d6625c12affdb37eeb0711babcceef5a72e38f4848e6b650e7eb20dbe6197bd18eb2def3e16d0ceeb0bdca7cefe194f6884e907172b2f1eac18ac62970b06f62d56a2e169cf025c13ca1c27dbbd109cb2f0862b9394a981ea112c46d054a8ac48587dd59b92c145da9d122203ee412871598332254ce4300794d219981f13db341aef8c36167da1fb862583088ed70d89e03c38e4e65fb44ebd12046ba4e5df539b5c887801f22c47e611b477d61c55b2ddd94557ec9fc865137c2b0fa1959d6e2ad0cfe4213932858a6cc4941509f2f20193f7a940768f59e400f4231657471762e4217a1253a47bb7ba45e0859c082553995305ea5937d36047e1040e5be54c21c578315b553fb39729932c57ec87abb44a538dd2e9a14229a96f0c866eba8a520e0c84ed6cfa1e57e8f81a8db4a70b4bdaa495dfa1c976e8b5c92f2e59ed30d0bcf266809abc7857e795951c06d80deec776d4067d1553fbb6a4c696e46ef9b54b54bde4e9bf90bb68a952c89d99cc7cfcb00b050be0c9a476993c2c49e4ae5a54505ac4c3104e6fb995cdaa591658c4ce02b5921d121247224a14591598328279848a05daefc1a5290e0741df15efeca455b86bd5e615f233006b94086960c088b3c70bd6f972c89f3bc2e79fb93368a3e7579bb074880d0b51cba2a4658cf83c6a6fdd6d3fd54974297738b2d58be18c9e120174661d656a45fc2ef974def5c93a7b2db5e24481fb46771bdb1b8c1e1d858b84bbd984ac13cab19dd8846c14c643a83721093c112dacbaf87a74ab723909423cbff584dc07fbad16f981dd60d81178bd76d23566bbf766e77518cdd350a0199ebb6bc32dfc695e379843f0dd301d3d046e35a51fd7ed9ba4fe1e9b9bb5689899ebff137120af01a2d1dc68e08bf4e179c9418883ab82112a553cd506e339a1b48782c8891071796755dc713eea0c20a3855a69a2cd55aeb93ef5d442b139f8e69dd1f2dc78a4ceee2b0c9cf0df3ae1e9bb61ec21ffaa567fcb8d50dc174e343eb0ca3718312bf16888edd414a3ddb1d644087ed395d10767031ed792e0cd591a55028828a257d4abc7656ca2fe3ea0dc6ac609176ceb92fd629c28ee0179723ab8d174c821cda48a91a6c317e178626e53f16fb20b2742b8dd73800df074756eadda83af3009abcc19eff36b80488a527ae8bf5719a12c3b0bd4e5a98b5dfc04ccc31c23df32888ec1fc197d26f2e484c5519114dee59529425b481906e0cf3239858a986c0d9e93cbf28773a4e1f133444ca12b9b934ba6515239551f4dca89652285c2993d0f4360547b30f20e64548a4ebf2ca44dc0b481faeb59ea5a6111907b4ed2d1fe84148189c8b1a8d84a4cee667e06b1e2d2ff677de30e70923ef554f175e9011a373be6e99d8a8299c13db923e3f2f234d8477eacd8ac47a9c56b97478431c0f7993065177a1a1522cfb1c88c1e47b9a0d8b4eea8b9a75d35432957c5fc4f9a5b15d6b68d0af483c50d3e97809f782f9bdfc025c575ad7759546656dd39357d050de280852c6a4d3f57e037866f9838ae595f637d4bb5fed90a38fee1c4fc12baf6db24e83f6b11756a203904db334b6df046451ff267de3bf2138b61bdc5269b538a7e9895e59411a84bf147ae9e98cfd0cd221f1c40e9fa7383372a6f3ca5f88d43eba34290879dc7a9f69cfe3cc2459b819b61c627243bf58aa5c6a7267ac78cf042f46853d9b06c963057574fc01a8db60a3dd886596705cd280d543274e3b8f8e96623c1ac5c866ee533667f6497c312ad9ea725ab6f53478579fd871235a96181e1193637de84e80dbfc105e7572cee58b0188d7f1f6ef51f24e8dbcacc0232833af1324339273ae3eec10f8b0cde8cdeffc02c572ec49010786319d463ef2513f320146103ba3b82745c308eba9a0ea58266fa7dc08955740b5c605360b57c5f66d484a679b773ec9080c49501be08c87ebe00cd3cc310ec0ae14caa4340134e3a5eef9b4c21c36e17e94fcf1adce9e476981795bd72a191e668c416d2f6eeb6e59652a62465070740072e07dc0bd93a300572e1e675c7820a9d5bdeadd42b485ba43cdd82ee47b89784eea7d7fdf723755048f7938988119095cc0947cf74aae15538971cd6420eeb7b2794d3ef9813d40cf8a98b8a5cd9d6a71df5baec791c28a43eed7674617361f251c3c6b2a4c54e9a2e29b0ea59df48848afc5bb693c3dac9c9a9072ff221284f5072e64bba27a8b09d5af4204a1d095585448c88a8fed5bd9cadbe71f8ea42229a9733ecb416815aac34208f1f0e7b5195eaf10761fd2afc593deb71c8fa9fd56af5abb0c8ccaf1e874054cf0a8918adc21f11bf2a0c419553b0e3708ad11ef49d195d56a864e2985505c6de6254ff2510cb8d64b26e92a6d3ac67b319d5e32d79227dface5c25c03b64c6913bf325173c36d2152cc1489f1476c8dc60e44e3f09f43da99346bb81bda5b33ff6960fec2d43e099bd1f7b4be88386df8e077c3b663e410acc7ad58f8dd43497a8dd36afbf11bc67f2d175a9cf7e273bcff3b13d15f2fd043b157a4beef7b8e46e2cb9bf91c03413ad7f0a5d216f2473cea80b55d4471794b885d39e9528da09a8ad74522389c136a42e22f0d8484923914ab57aeba386af1f8d444b809b16cea816bbdffe8065142bc98f51921f230f4722d60799628c688236fb2912b29e1516f99945b4223faa67fdcccf204da4a22b27122ba4edc17cfcab1f659415385ce118831d366b4d4b93c98fbcc621817d24d67e8eb65a92a4cff6d652912d14965a1acea4a31a064f30004699fd14c8811df772468374cf853f463ff73b4ecefc7e2a1cef0d2507ba30806176286228b8be9cd95a6e6ad16e16c98643e05196152061f6adb5d6edbfdc9398f80226db2ddb9f5062748fcd91fdb9c5beb4d6328151629dca23fb938afd293f13e48e0d9f6a9db485912dd26802bfe8b3dec88c035fd47bfa9ef7ac9008ebbdf0e7fbd41399f93ef53faa13f2ffb4de7b16f8d33debbb1939e03a0626a2f91fef67be051a01e99ef53cbc87267cf92b9933a3d77d2034effdf673d6fe3fadeffe8608f09ee681784ff3fe3f461e02e9bef53fded3fc8fd116026985f77f68ae7d1a5ab017befc573ff3ddccea675e4030eba9ea59e05773813a2ad44c1fc7182c6746f4f3fee52110eebdd4e3f881c493c91cd61cf8833a288158f6a7e028ba9864b2679e91ad2b145d4b29e58b0a9e7276695a7eb100a7e020a5e38c1aea9e9f4535e35c629ab59e13706ebeca27305db8508a23539dce01ff7ff53466cc7819353443381c314e38ba38d77fe128aac21a8e327f854953fa1196118eef5f138e622b1c63b5a69921d6100e41f07e27794f083812e64f461613bb7c275d1b7a0e0040d9b4ec34da2686aebb4d7bb282c060b87bf794ee02edee6edadd9476373872b9a7cf321f8539d9d32d09dc6b4b8f05842be12f5dadeeb59dbadcab7d4aeb04ed579a6f485d2d23309db2c55a3f2d787e8e9672a72f0d3fe70d2cc051eb53aef56f0872ad4a3674c9f5ab7cc09b7190476f8a1a92100293e96fb8a3a8354a84926ca5b243aade106292e9d35aab8e1836685143982a5c706156eb0fb9d62f5799ab0cb629d36f117ed46a24268628a2706109125866b516e54ac549ae55860f4383951c198431e7c40cb6e97aa93db208da9466004388f67042b24c5a70a62a044393a002212a65c9dcbe6cf9c2a54eab4403112f6c36c460c06022d7748928c05e9659b278829100e36499258ba83c62f10ba727b4e5cab20516368f3fa5dca19fa3ed14789c436456b42642f6bceabb4f2993974c2514322d273c00859fe0cca950f6e7a3e33ef5db4b0fb73c9ceaa1bba2543887b660224666aa4fed806b1e9f0b7fa8c2c0e31ca20d557082a13a4818ad4d010593964c8fc8f4471911f4e7049a4095fe03ba47cab7639280c9c2e9697ecef5ad222929bb97dd6f985ce9095276e9495b949e38653adbc79c20384cf2a0603b09da4cbe6829207bf27c7f14b05fcfc969d1715accd101d71f71b2879fd3a23f8665ff6d0478944e3eba7e8c752e830a5dfea20c4a64f7d9b783c69716dbdb3e2549292500b2a5d6565ba96f1b121dc8a38b07198490ed1e065078c4221358059afb45e802dcefcfe19fe2c8602f13ebedaf17bba1a45f0fbad1c740fba04438ff948729d057e1e62977eaf75824fbb70d652cf4b721d1fd5a6b7783a0088f3733d92a53a6d054c184a60a2636d890840d33ccf032a74ed1841f1434814e2174ce06cc16a5218ec5feb5041e63fff37380346cfe30014ea5607c4808f12121440baa0d50903aca1160a2d86dabb556bbd99e5270dd68583efd1b06aee1ab7fe60e63e10d392738f64474cace19043583dc45e089007beae7e7108147093f80cc94edbad40c21070f1083ad0dc4116d282be18710f865503850ae6c99504a41c19f9b5639a7a4f425f09dcb90c8becde958770cbb2b05430d47fc426db7dd22d28be12f29ed6ed99422e1eea8975d38b4c56e2a84a951151b266fc3e4cbbe9b531a7ad98244f75e4b3d69690949949f63ca29a7cc3165eb88f6e30d30bfeed4dda5b4a981c17db395baa49c51c3454e96b5c67a6d6d12d34b73299de15adea52cdcd195a7faa8089f8a8ae0addcdd6ba5b5d64a3b9c929293f24ab939b53555069d73f21081ce10bd6d72a6f794524a29a5f2f502e1480d1b192175e10eb9dcefdee1f8fa1173987c569eb456bbdd71ec57e5afcb0e4af9d4e5e3ebe6da79524a29274d4a19a502a594524aa9d7b5524a29a5d4fb542b3cf37d18a86cac5c18a75836683c0ef42eee928b044770aca19452997f83b97524a594524aed6d5fdde01aacd68c67696ad4dc8a5975061c5399db6a64cc70d9bc6478121c0130c3456dbaecdfdd6c90fd3b9c9ef18d5073879edc49659f8181cb868deb06a76ce4fe8e86c7b97eec1feb3bed9ae9775d336db029a5e1b5b9a95123fb77e1cdcdab064e8b8667737f0700ef29695455cd8df355708ac320a536b36bbe8865595d4d4777772ef07c1fa59452ca9aeab5e54949b3497a299da192b2384aa9ace102bb7baae54eb5dbbdabcdd6ceabb4aabcefb374deacc32465e7eea56ef348c9b1ee8c47371a8f4a4a6dabd64cf95eb3943b94565aa90c8f4aea140a6af4dae5dde6a975ba3ba6b9bb53bfb91100986aab9d9133f65f5f0f6c29ad332d1a302d9a992ff6267405405691810c6a6bdd2a4e8d960694564ab3b0f645ed97d60be31b3b43ab9db9c27ea9f7d28e56cb62b1baae7bd985b24c9932b6be36d059bd6e7476543796565ba68cad3602b8e1d9dc8875ee9303ae034a847670894c892385060e0f2c0d1c0f78aaad0183f3e2e9a1d5bfbe2c0daf96563b64da4a3ff09922c5cde3a4fb767476a7d56e38346eb27c7a656d819c84d940a062cefaf007c3968c97326a70c277587f4d173c7ffc57e80a4000bcdb354d19c8c820834f47fd1a35d4abb5bfd91a2d0dea8b34d4bab442e3d3e12ab0228c7e58b45e18c33cd8a7637b1b1b78392d977f5eac4c242c66789b6c6021a12934a9e0a821f349cd04d54766470644f4be173f1daa176ddcc0deeabf1e7d801956d779988ad7f94af50d994fbcce0415c8c88070a9af0d7456af1b9d1dd58d8ea7a323bb4957d66e98306f34ce104e269b4835cfa46945c804a1c9ad5e304436b9e4709c5b643602b8e1d9dc8875310e3a0eb80e3af0a4f4b9dd003c297d068063436a1107081a383cb034703ce0a9b6c609e7c5e3d1ec49ad25266ff5822ac5faea9161b9483497c93e1df46545b2cb200cd9b639adb488a58f9037fa93903bf5034f4a1f1f4f4a9f248c5d5eeb8dcfeb66a63978824e0eb23f75f798f489c91d9f14088cf1c54e0bb3c316960d3c44d1f4d0d492e202950f543a1fb2b84b5d23aa218fddd460f21cfb293799281fb8c8aca7cd63e38a79f5d28725eeeb28f900743be83a294de48e0c2e410f2f5c2994789062edd74f8725c00e284852da61092d1b614daf88be3a8871b5e8a04408997e875d2efa2e7a058728a40ce145174e66f4c5e6a9405cb152e562af66091e4516f20eb976453a28c1c9f4ef3645173599fe96938229327d6a37a5211e32a55fb9e4e420e50465327ddafda083031513e4e042a6ef395ea26891691352e0f18a3a50d8cc8cc04d578c1607585b68a0c4e0021e9a8122f4c28a5e160f4c544d2c612a4e3e033411b5520af29c545328e94690f44141810ba9273c2859b8c07550bab80618c1500a4a18306c475c1420254c51624862002786a2387161a916258a0b062041541422036ca145a1c2845e2e3480134b51987819c53aa6650b316cb8803924b028304cc1ad2cb33cf9a08114d826cb2c4f48884cf08c2cb33c49914197272d32e0e2290ae604af90d4a8823f2736ca60d6908d21f08a080d76c038a9c613be4c3570c03335510b5e6599258c164c61c0a8410553174416b08d013606e69e9c00df285b4809e307518c510d1bb097651631546a1c65119374a41613b3c5d6a493392924302bcbaf5f18065f292fe82370bf510a5aa048ee14787bfbeaf7d519039d4722790b715a746f5197c5c4609fc205c4eda2058a5db885f108c214b1618c714be90616dcf09447fcb48ad32d98eb965d0577376f5c33601fa9c733a6e011a7bebf9b9e6cd93e02cdfea927f8738e965212a9beb08884a285312420a168610c5af6fb94e3387ffad465a8c54240e58e87f52be55e8242dc3bd9bd48909ffa1da9972ab8a42f4122cf41dfee0d456858e7f841ca0f600b12794f411cdde37d7f2ce6b21c6070151ef2289b7498b8269d2b0afcae16a58bc0c3257379cffd9bb70db4a0dc3102cb5047eec8afdd5feacd5d0098b08ef4997912f1a72111fa5eb885dfe22482df3f4ad5a9c019e5b04afb81c4b1c3eae3f7503aacfaff784f7f23a13ff5fd2922aaf0e77bff2dcc6971ae5ef58f55ac577d10d6ab56ff839f151209b2fa51fd785618fb9574588ed81830f4ab53a039a32c14a1e14f051c169ee0b0eafd98d7f6347c6d21fd1ffc1ed2573da534a4d1b0c8cf1c72587dffafbe9c85627b30df7b2b68ad8f630c7d7f2449e87b9fc4ffa3ef3d10ffef5b416daaf0b5d5d716fec0db130caec0090536cd61f53f1003e5fa9c18dc42f5c330b8fe6c9a4935e9db42da6227bfaefb0de43e9532d21f391b7881c7f76a10264196c9aa26ecca4d690946269d4128773f16c99386eb8144e40b8018439b11790551153902055ea2663fdeab422233cb0e3800cd8a7020050c41b38e6a0ffae5ac88e442862a42332fec0f9433d79125a425ba24c70b4573b2d2626f791d61e9deed515e774322ef91bcc76ba4f99130f34c016d246107da88c2374273e98ffb7602c3a979c6761abdc8a19a67742427538b1a3b6a49f38ccd25b7182ec678ffd5c3bff63bd27b91910374a11f8563913cbfa1644079826d942f28c3d85244cb0e1a41e28129cae40df49745cd33bfc7b778911fcdcde57228177605d1bc47ce80f2903bbd8ed80277c2118b39798f87c31ffc1f0e5ff3677e5e53f541583346e16bb258af7a396b16e84e0ee5b0ef3d509643d603112322ac377afdf03e0172a6fa9aa74335e0091f58019e54cdab7e944d56d0680eebd5ff1801c1ef7dff83c339e4b0a079e4b0315ff89a0b50bdf740beaf098918f9f41088eabdf766dec88c45f35f8df747e6ccfb1f9a703239ac5f334ce187745802e4cc0487f51fa9be6936318da999504d5fcdffb0bcc7615007d15ef3ab79ef83d4d484aff935ac67853fdf7be16b862f04785ff341bcaf097f8c80cc7ccdffbcfc695eb23e48cd4cf872ef83cc3ccd4cf8f29ff19ee669bcd6d3d474d00254bea505ba934371636067f2a5169de64beee454d4475d94db432ceed44950ee1464f5a3fad53b943b2d60155220a73477f2aa38aacd3ea7e9984b290f94339af43ad2d1dc21e9eebed7e5925e28a5f5012dbabbb7839fc3eea639e48efc98ecc529bb3bec965da48d7877347436c7032692e6a18f3f2665b247ce205ad417292515593e0a0e90208490a70e52ac40210cd5202ba58aec3fa5587fea604576b17e92303ec78657c8b54c762bddddaa6e7882dca92fbde46a240716f8a3c97348183dfac0989412e63547f697b0fa29b0eb8fecdd7fc20b914db7392f40261ab4e840c68b306ea8412cb14d7aa82f7ece5b31b48527bc58b550c33cc1b48277d1c2e54038c3ad6d58704ba48262b663b3d25a690d16f097a59210458c21c4942d4244b13428e91c2658760068be27440bb93eed5ae9bf8e00aa1845193d512962d43f2f9411982deef9a51941e132bb7928eddcfd7a97a500168a64f76e760a159986f386e3ddc2117b8bbea82c042656b64d22348f540a6229cf20bce45492d29424733ad7c4a9e3155869bd7a3dfabfb3877bf94f2499c8ad9e5ee656cfd4864cee06b2c78625348c3e0d65c3c601f08c2040691ecf80a4995640fa804e7d053a54c20f1e4bc6abc210155aa443c20a04812f7d2f2e2a32a514082fd1a73fc42954ade17e377b7bf9e9f0d1dbc66ba7bd96c6451e71be933623df9f136886938684472fdfd4bd9f0af1a4f590835d54ffd6d0fefd8e227de41d7bc98ffdc40b7b68389f72bd3fadf4fd09a57f7669f55d5cb66071519953eea7eed3200273299003abc0f38fa84dbe3f6fdfa121fcf23bf768173cb7344f33d5ecbd6f5fcb3ce21291efbd6f8ff09422af0b04d8887b276dfbbbddedd29f9f2c5f51facc21b973ffbe0264cf8442c29898b46891573ef8401441d0c686c5baf77edf7b7f9cb4fcd7730d6dddb5928142fa90c3a40fcd15cc60aeb5d65a9fbb628bc0435ee7dd6ae55ef0e762c04745cde3ea82bd86a5fe0643f64c2aa496fa4c4c5ab4c82b1f7c208a20686353b93c674deed4af453e44471f9252e4ceec41faf4d76642dea85f8b724861ca9d8aa536538bf53beca5166b95d4932e0bba1916a64c64cf0974932dcd5b30ff205b876cbb06dc2b8041a579f05bd6db6f26b2c785ba903ddee430e943b34dd9af35acfe3e0b9c605ab48f417b05db22b037794f7dfb2e54e59d496a81d2619b17712f7031e0f9d43c76c4f387a25412b875381572c73a90f441923e7e8473c081b43890088236360e2404044e026f368923913bf6534dc224789c4dfdf3de24202bdb5a3b9de40e7d9aef4f02a3e030fbf762ecae1702ead30f529f86488064b3995772672d18c038bfc8f6bbc8f683d0a709914c27c9817d25773613a2e0d65a2640b98588b2fdb63fc14481ddc99b869ab2c5d247faf474923bf6b3ece91a562061f69998b468b922af7cf0812882a08d0d8b85b36dfb6058fb36083c7ad9be0365fbd6eb99a00cae0041d320041843d90801f6c068f0846590400c836fa8d0f0430f5e7e1012bbf80106b10abc7a0116633420c236592a159d40dc01d764a954c484175434d001b3b2542ae2c1865091932745405ac4d8a0c953161867a944d4e5044426d8801605080606930649b863a286126c97304289e8034c4440d8d082b72c958878f0614eff6eab57473a6520696459abc9f293e5947f6c88dcc98084f573b1828c269cbdae7379333a9547af87adfd6ca5ab16f67ce5ad4a2ddeb6b0042379ba2a75f9f43a4ecea7a33e6bce1c8b43591c48a704e1d69aa0a6bc413d29592d55cd274ef73c2a6578d2a3299b1befbe365b43c16d9b7f73f391306987636978423a413940381d9d8e3f59432303261e0600e87e8f36a98c192e9b9b215907fe8694fef0b1e6914a4a185dc9743f980274c515474041c152a58244169dd36ab7f196b0b91893f993c78e7443cafbde5bc311d370e6de5bfc28b7f42479073ef83043972fb490c169e61ea81285cdeeee2d031e316e9c7657f3c8dcb31ebabd79db7bd2f3878c6a356d4e9f41d3c1f1a64079f8903d43648f321b903d3c96e691251a1275df605765e50196d0b0f9dc93710ee540bda039e49ec354c90c85f69caf21dcca5269862a799c41b98416271641997beefdab7fb7c999021d227de66637ba6ddb6fdbc681258077ca56cda0a01fd913812119909e0464cf1087bd181ce571b3d611ab40f9b5825c0a747a9dfb5bc2b7433e51f70d689ed8090d6891c70c2b37067fc63a416207d528d149efe92b5f94116a7532bbbabb7177775f8735a6d9eb6eefeeeef65abcdeddded6058da4694de48efc9512980cd49353d214ea204a29add45b4a995c1aa294e21a9e2f7f8eb329cba7b44b8bf273b4982b78caaf1183e5d734a5d49b524a699d492df250f711e0299b5221f49fe60ddb2f3b245264e645ba6750e041b55aedffbf56abd5fe3f6989e9ffbf56abfdff77d004a189f4b964ba073b3d413575116e72393d54510bcdf0ffff1f4c172e5b269625115ca97708171dfd44ba40ddd3b55aade8a8a8a88b7e740d045dab0975a935d56ab55aad56abd56a35ef367501438518649eaaa8d56ab55aad56ab79d01fe9ec745aed76b9546767f267e77d95e33e15bd57b5f26da3ab5837f32cd9e4a2d39680986a28788f283a2c0578662061f55960fd16f22ad21b8a0cc8f6eb513beccda709824b9ffbf26717f9134b6c224d2bcdc3bdfc7ea6a79b9ac9752fc62ed72ff912153ca548ce225a4fa12cb97f9c4eb2fcc9448230b1cc1bfd92e674c57c856aaadb50ba85baa4165241518193502fa95a28053912d63d0b94123697963a550bf92a945d03f2fd2976affa7a7c4e85aacf87ab64119e3fd4505aeceea1bb86b6e22aea1bb078ae610bf50bf5ea1b81fa1337fc277b0f9617f39e6d06b56a0cb0102247510b5ce00596284f3851c4154e30e960adb5b6061f5cf08219c22ca104083ac9cb096e1212a37ac55092263b34f1a4cb17274e6c5189b842cabdc208870eb2b8e00b185b64698111f5083741e1c15eb1c24cd312375ce1845382c3115a94184af08014461d02045c962748ee155090d45a6b2d81184b0606ebe58a1a85972c668a349822040690c00287114c11a6c9163329a7796e9e33c9262182cad01412601ebae01b9e10a203415d82a3329f3d8284e65176c90e45a61ae571d226d05c32859c8825398fd34af6a67214953d004d8628ba72e5ca0c2cccfc633c4e9072e4055f4039e1441033ff19348b3cc9aa8e58248bad73e0459dd8d6850a76d8c24b91951cb4f8a18bebc08154134a6e70128515485e70e1160890baf9f385807bbbe97dbf79de77537f5f08d86c98fa1b522e05e688b5d31008f75dd87e432e1c63a1c3a6ffcf7dfbc3fd06e4be0d8170bf85f8b3c02c937f8ce8ffd84ffd6cdfbd7cff5e08b09f7a20f653a10864fb2e7c8771e06bfe1b995d30f5f7132067ae90080e2be46cfbf153b65333fbdbd726a6a47207676fc0097247be6784e7c75a942f71748fe01987dc915fbfc0217da8b4b96a0007e562381a70824c862079b6cfb593390c662061dcadc9a59e2c3dc81c09997ad0352a1a19302053ee3db04648a63f45ee7b0399faf07cbfbf03784e4d231407acc608ee4d11ab570f87f4a13e3bd596872cb616ac68e10bfd23a4ca6502b422b43801921034e952a72c81258621ea0b2e822f5c1430d45a6b350192db4215a0285a40e22f7c0ece52e9a8288f1fbb5e0800093cbefccf1175ac064af866a97434248e81b72c958e6a68131c31b9329924b11c1df132c6568778ca5d964a4378916189b5d6da301f688d59d244eb8b314b527004e83b526bad958b0e1c145c8078a116610598302ab8428c099284f1c3064518474a66b8ae219686b052a543681e2ee575aa6f05f290b0fb52ca5b94650f7658d3af3209bb72c9089228e3910273ae091cf852bc2fbf1ed4f3f6f8debb59db63ae902b5117b61ad65a74a28eccfed441c47aa7bf43e6eda6783856ddbece3cae3ccf48f6c231a65219c92a8cf11bc9d88570bf92592564f51cf72bafae563be4abbe7d16a8d330fb1514b9d48b62f35831d63cdc5bee6e7fef9c77daaeed169dfd252592f288979440ca7666bd68cac9f62f879f7e3a2c70819953bf43666ebbb5077f5c0a9c92b2147024a9a3af7a5538aa8c641a8e3ade774672178eb1affbcf48fe7aacc6ef57a191bc0a475927a4be4a0a517d55a55eb5a382ae56ef90d95efbd95a6b3710079939ef9d3766a1aab413cf5a67752ab3ba2783a58023b516a50bc7f2c764217c9db34e20e89c73fad359ebac4dab774e3178fc1ca7230e32b5d6d818f27ee02bc834ba3fdf2bad94d254caa6524f53a930265bede05d1f62caa72395024900755cf04542cd1ee851fb77bbee479c5a4cfdbdf47e4e419939b0befcb617f5177d9a690c31269c20bc796013d162313451327b05f929229d88528299117d2f7c059929303935cde67b2ee6a6eb240a542a0bdccaf2bbf995febf101aa02fa8b46df844b5643403000010042315000028100c87442271402498e8c22a7b14000b778e3e76582c1a089324c7511404418c318618400820001162184254441c0020516c50695ce0c5a843790fb716cc6ea340f88e8727bec67ff44a591a7d4a03d016515bb9882701a944cf604088a9ac816dfba7aebf6559b1951f11acd87833f99578132e8b05894b0396bc3c14fda0d81c251ae4becb11b87d6df5b3b22abcb0321eda997ccf0553aecfa2271e5203fbbb049c307d2ddd77ffdd1754039b8b1d2194851b34e639d94b16d7181c34417a142328fc0043766f574ed165c011b932275e5aa5044c2139dae091585a8e578a2d1336986dcef41d8a343a2909289e66c6fb733806b46ba64a4123c90e2d87cd49fc6e03bb9a0322f89e04b7144b6a4664b0e235b373cc9dbcc5d5e9d5fdb4c8ed074022da12347a37f75f2637d809ff5aaa49c1207e4dc0ff36febd6f733da9323a466254a02390fdc1ab8de241832d2227f81dc8894000d566a6afda11e08d31ac4a34960b097c22b653a056ec1c9659af2a2c92d5f952168d6d5986da6a861728ed695f7c6c98bcca5dc585b20d0518e6d7556507c6ccb4a8aa2567ab12d6b5f718f3e062835b4ed068940d137ff25650e8471bd865f927fa139ddf988d7bf7af073e58f322439d70bf6c7bf81e675361d6008a9efe03c9fedd952df8c548de5ae9927cff1c0ede0e57e01cc7b5c0299081af12e7d99a239d46af049179cd0789f2245cb38d45bf2015786f07f8e1a444d1c025635f92dbaa09a50a1de1fdeaf02ea3ad1850150a7045f6e050e9a2af0c47432503e80f843ffd477f8bce44b7a47a9ec4daced89ff4d5e0b1d45d5fc0c678f68eacf4b061d677700538d4a1cf10b87e09d69bf802f9217aecc4edf23d0ace0ceaaac108501a41eb2d5ddc178b53990499e3bd7b804cf315ddc373618138c396eb1b57f2049a2483ff4f90f0322dd830fd256ff00c58a483c6fca47dc0dd3bae6e76f484a42bfe1f7a8d396f725d1c9a65527795a846f3d77abd5516e4b1c1648c87d555170d21316494f28c21cc8b32eba08697c5b3e01b72b4be9a80c61682a1861b4266e0a026a09947b0c78eed58bd7b674232d34f3ee7f09b099ec69e57efc7cd1c6a0907d1d0b3299df18240ac9ed79905926e52d70c2b08e8326c3dfcc503b8f21d7e589eb1260be009d1fce33223a67972f8dac80c55953eb04bfdd6b53088b008f77a3471df79b12c164b189474cf3efd82833d3db6ed2baccfd79e45b364011f2ad740de9a8407745e2a8d7de6218527b48a82ad9e5979aadf0ad542059db4e4eb07f06d3b5a0f0311f719d8e85d7f1080d21523f8b68c310950d4300d1814d8d7b32127e42d4b5553eac0874ad205acbfd234958e4a54458423f57cba213eea0326806bfd7a5852a4fbbad310b100e89ac16834a27c0019f7406e91b94b91acd056b1e086f0326a5e1fb51a64bbeaa56bafaed72b597588ecc825c9f9f1d18b473276ebdea571e43284a417ffad8d58c788d207cdded3c885732d27e8ba41f2c5a5d03f1765913064f5e9dbd73d28da823dbdf12dc756b1106e6793d369c239adfb7dd33f786536e4189798ec9f9ca3cc61a638e4a5ca04083d6548ef7bb3e1bab03d208abb117d6662c2e16ed826661bd35e3aae3426342d13bb8e7c569d7d719931ec6d173374fb16bcecf9b05870168fe1508c6eda0ae5867260ed3cbeaa26c4815302c657c4915a5af1328cdcde240804e073031cba83425810c6b8013a379c53b30b17e3527a427b35937213b184d4132e9948f5438601f71afc250e2c874f691eafb9a1a88252063870888bc49ccd9e60c85ba3651afc0b875d9e0f69f5910d01be54d96a5bc92d9dbe7c20450de810987c49b1ec4a39c71e988f3981762514d65a19446c0ed02a648abf4b43e7910c4ee677dba24382c020ccfe9f8c90c81c12fb4c9a675a66fe31cda40caf4f4698734d3d8d1041ab89e910b5c700634d049f665aeb12d6fe1c771995dd38518b11a01f0fa0a3ebd4437b8733e2628aff07aca3fa00d3b69c9fbcd178bae094903a04265bbc15b8b67701065c920096c17a21aa166da90b79884decf2d5da173f54201b7919997b71bb9ab38089fdfebe4cf5c88ad97802c2ca559d966c49b1caa2065e3bd18cc909659a7da867a78ab4566445521db5e200f5698186f40151831a1404ba072a1cdf1ba4319e93458fa4db89c9679a2b4bbd895ba381cf36372b3dc9bb3500e6dd863033007b6874ab93f0e2d795e650d0d768e9eb827b654bc44a5cd6b404ad7d646d3ca8917eb37868c8174fff3eb4372d147d9d1ff188e24667fa9ee0968a7366c10e2c4cbef1adc26085982e28947bbb1c907cbf8363e5a3c4a5daddcf20f8e96edd4c5060ce4b4d5102f4dbe2cfdc6b523f559a4b7b4df7592db24bb0fe1038d4813adcaa8b42055120979520b5ca66ce2dfb229f4be442cd08941a1591ad89622a79db854716fe4a94c5faf8e129cf94de03152c15338c9c68a0790bde558927e4fb6f28b26fd68c55a61a1a8906bd6302deedc17b2b996b196186e54a13cd3d7c2c407a9e284ec8dc8add9b6fd1049892eaa0451f25be43898ef8d567697653862cc6bc1628fa5672906520457d2bc90a4df8c8ba0d041aad13904562a33b797b1bf06b63a33bb5d9602cab57fa57b4c9c729ec3691d09be1f09415dad8cf364190135a7898d270a88c1c200671b8e1c767971b892a02dca774301a69c277232c08c7739d1a0ea04b05e44f67a84c08d1900afa40c3b704c9b44bbef45b57eec3fe374b339c86451ce7300f0d58c0c92cfc22850e9c33d3806099e4dea1b02589b7d8a2713799084703ca7f4e5ac476d70fdc7a6979f264d810747871fca9237808392b5bb4ca721f4c99a7a82070c65e7794c4f35bc4bd1aa66fe42e76a6de91544f5e6a63d7d77c9e190d0f5c00c91a5bfe856dd3770c5e8b44cc5a6b145eb348b578e9e2c74c6b9abf4e4c67c473c8e91b57f595d54aa527c8cf69020f5c195dc4f3cb351a8ff6c5aa761bc46aa706c7a7d915363791a760476223edf30a6ce021b82aa8a7c3aa20603d009d37640a3d91e0e26b0c0eac4d5165ac679aa2884cec34761f38dbdd1dd2ec38dc3a31b4fb88812546426777cd3f8d24dd4840b301bd1f0f7945cdc8efbe6fbaf30fcb3f6033def57b0520255f1126e4605fb55d2544a1d3df0f77c64eef6a49dc46d89373c6989547b44d06925186815edd9cfb0197be2352f67b36f602d480af3c620ed5a206320a36b1529ce2d9b5038061bf04e5de27b0eda846f6623c4a221fd76652cd83572c9f2391b4600e92d9e3729e0c005af50d79013f851d7c98144ec7d9733a329861b046ae76e4d228129d8d46fcfeeea1580586971509264008285f2bf81c6dff8e9b3efba76dfd97bfa8fa2400e1b00a77a75e2278d42852a52d3037e8897d437e1afad513ba13b24821b0f604640821f003706f93e6f1936c0802995ec54d54ed25b18e5a9e4877c72b7e3f9b2e61f8041f3cdbdb019f7f89a0e1af3fbbf76140ce2bd03b89e16b36e0d52b1704d020a129bb7339b01ac4398c614162c0be8b55792b8cba87f93bb602ef2ae0f499b9191132c82edaf336c43a81e142947d037e217cbd06c9256fb496e75d4cb0a48cedd2cd3ce05691a418996e99ef4734b59626ff43a8677616ff544f830985dc9ff8e4668a5ccda95efe497fce8b890172f6ed0d254119ae31f69affe67fe8251055c84bcc7c1cefdfc6a007dc9049518ae28c4e559c733f96acb5e5ce305cff268f0e234be81045a5e9ec4530f2ebd060c813ccc892bf88d395782fcbbea4ecf2f9e95f91d090f47b2c9d1ca48058de4fdcf60ce8ade0916b2cfe757f6feb9d2bbd9f2f66990ff89d46eb16f31827665765909e4c5d2dc21d2706cc273b9e69fca00db21273ed7c06effd3a3de8ebe8506f5c5626f40927f7b3061172ea62c176c449fcbd5c9f41e30c4f8818017b9ef54531cf731415c7e875f283e601d9d3234f4fec46018ab325e6e9fb0dd8bd58f741a7f7832f8bdb81eff1b8e30bb2d955ac7c7643ea6d1a99779963d6a4d811629cc1975b20f9af9eac32c67eb9b67ee3e18d48ba7c6d8cf7ffdfeee7fba579caf0ccaa756587dd14e6aa8e49e8d79062aefa82e723228a2aac0da806056f60346e1502c376fbc55e1c7223245e9af5b09312f819981dacfbc1105996e88f27b6d2a260f2b6ab12d9b3386f80d78b0951874f8cf63133dc63bb2ebbcd76fc2e8805d8592c5685ca52edbd920df6a4b53718ed2d791f9005e00d1ef679edcd3ebf1719f808bc9c700ec06ace9efbc31d32fe3d033560c3a88cef583e0e2ca13b574c13fb9b297b9d74effd5a4640313abc947bcb48235e330b49da7720cf9fcccd159616af63ab41f5bd0478a8a602d9bc9345c76a1f2c768d0070bc2df3b9762e401c97e8bb2f1d1c6d19058897f729c16fc1cd33a788d110cef8c3e94e29b425e7496b49b33c89321d1774aed8e672925c966d7903d63a11b64323fd284267a03a1d5fa3925e58e785e51b5fd4f6fdad90eb72e7815b31d30f266b8c2f81da0d6983ab8c9e22618eadae426de7031deda7ca17804ef99e46544911d6a769f6ac7b23e441bc96319da1c8323726704782029267afd29635a373c2f97ff12136392efd0528bd44f635503d2b6a7aa3dcb7de011973d9547c7339209d46e40af776701ef86499c7628ba36146d1e12433b552fe630ac42bbe30669a6e871ef3d47e93023ee883ff5300c865707eec9ee9a041f818cd4188046798c741828b83c06ba351ac3a22b6c5d6e905d38ae6e37c40f0eab05871c69e0fcd2a190eb4311cc53219b458caef1b33cf7a71a8e80fba422cc9bf201ee431f7a0b6e77f411ca25b0cb22cdec8c9042bf84d26ef4de0256ec37278ac8722e894c203f5feb8ca2797a02f9d3845afbc130b3d9ec75f427cdd27f7d0449b07dd2a95f73c79ce9038fb03d2af94f4745114b63693ed184d4443adc1c26e7d5f5dc574b4ad1ef626146834229e5c9ed63185b7ee020c950d1ca715589b0a95cb61a2637b8bda10120a0335f616e821e4c2882b6a2bd8fa25bfa88924d14d7e2292e54eb4f7233ace3da17fa6f32ecdf3bf3a4794438f3b9f832e73723d0d393d5596eb48501bfcd3280e11c7b70a73b08927a43c4f28ae722771f07780a46e16bab5c3c937e23d8b55397514cb3a5cce89459f55b72b4088b91caab967ce8fb007747b3f38a3377c599d1fbfda1d4a7ae65002face8fdaca2b083553e87de3f5896e02805156e0d71ee5944fcc450d3938016013a8d0d709d97fa9948ef9207cf2e620eb3f2bedb7529e3901a75719cc88517f5102c4614d6253d2bd9d7f3ef1a96687742503a0360a71dc85094c76be8cda0590c17f0f5856b6c8e9c04d9816f8fa53b6c8394a6c2933e093dfc453042707d7f22f6aa79bc4e0d6fe2cdb17eef233ebdeb8570bbf30a952a69d90df002c1a0480005cb8a6a12713e7de42142ff5848b3fb7ca268e3e2a3b68876b6393064d894b7326f428824b0042c324be77ff4145704f90978a4a6277695ee7ac18805e2ed13220491286fd1039bf11f3b1610a83beb79f175b014af4ffe380c305f36abd6dad40e8e3339f4190bad78145bff7d0ab424c4e3724da2d3e20512c82eeb3075a5f26fed40f12974b1644aafa62b1e97558fc072255533367b0746defebbaf63cd091f31e5b1f0a80e5c22c4c34e1ad68eb0da9b6e09e9df2e4b0e440f8f10812b210395bc14e20474f06d54d54ad78ff27565432b1ac53dd1d64e3d7e9ab7f50875a3c330e759e76a60d351619070e8a1437715316f07157f0333351c899deb20820ea568cb675b71c669325247b479216461feabc23f2797848c42651ab3f4976d9de701e71d5df03180524ae6cb38a5c900f8924638d9271e1d4aee733217a2220062ef1ac5a6bbc00a09b7bcef4cb41146fd734fe0021f35c9287fadb76cde9ecbf00a4f8639982f1e20e837f12e970b55d1111850b10aa575306cc6c88937e41ec789a0f81da85df593a2c7ee0d7185a37efc0d726ef8a8daaa73a9f1b88eda2da7a2624a503349499692a1686c99990affad100801a63f31e2500cb32ed3edd8d85746625b5d94d4225971d6647cdbe4bd87d8bee7d788c2e16218e7a432810dcd67bc6a09266c149f3522282231aa885afc0dc742dbf2a0a645f40e10621d709016b6dc03aa38218ff47f8449dac78300e3abdc899cc161122be8f82e7d86395571c8db99ddfd8a92c2efcd3c0e9c084420cc8b2aa7d4846800d08002259b3ac371590cf50b80d50abae4dadc004c6fc387daf60d0f73e51a4ebbdebd725381e06c13d8afaad461d4bdac6dfb347e95fed32dd84707d07f32ca6244b938a2df10461f6158af9b71d3a2c966fd4e73dea272950b851a9846c02d36ca02d0b4c621e92e8d7300e05c198977b57e34aa1f504a2cca1f7609b776c55828c90c35a604d1f1af9e1e7815fd1dc88852026cd817d3d94a7aae6155ff30f6903703fc50f9ff9eb9f32fe84aae19c40179be7c0e0d927fcceee2312394cab7500696461cd20543469c71922e4c15f3a2840d5407946cb9c13cca6667b9bba34ef5563a2fd26b26adcee9eaad40c506deb068c87c28a25ce4ac7193fb1981c7e03c56ccd12bf8caafe96371002092766ba901f13c105ddae20b71281e304e62c78ef5ff360869eb60b84e979baf76ded16721e89990fef9757c959cf270fdc9007a92d0fe5860f744b21dede2b55f9f9facbed30acb4f27b4623add5a6a1bd44b4074fd531a3e8219129ecc41d81e185dc3b0467fb2b3193cbf141a8f30baccbd51f155ca065344ce171ace7d4a80585a11a6c073a9e3282a338de6fd909d7a9acd2c9685806d11f6b7a7ec843690d08de4758ca4c016eba7dc9475158dd36f00990189bd5b07a54f0256b2b59f1f41a361447127df7aa29af93fbbb1ca757b2b221c4ff0b4c464656ba560250a491a1fddf9344815e6eb19820539aeb0d3aa57ad05424a6be231b5624c67f5a3d28fc18442dd08512d909714dc8874a1c284a204882677679e184128b933d9e96c3a196bc83fc902ed313405ad257de8eaf0c1ef734314afa34d22b70319cdc419cebc698275ad5a7e0f7d0e3ea6634d65714e8183f24c12e8eed247cc5246843acba50287daac8dca394037d93e7e675890364fade86dcf4d2f603b7ed025608e698181ade8fe74a183a460f203a03301d1b6d687b5cd9af18f1266299f29bbebd3d393958679948eff17f5d158dd1457c15827d625e69fb0d0994f255bec67998c9ed902f3154110514b6f2984cf68f5cb7832b6808238148056ba51412fb86afb38d65140d111bf3e1a0d6b5febb1cf09dee7ff98f06b893b65b05a6871d696b1708848668c8621a0858a0aea2316c3a20e1e9346114f45f2c7ab3ba6529b167b7dcbb1f478316de045103127f2830ecd763d92e2372dc5be9d1daba5e0b5dbec18763e8256adcd88c32cf1623a6f54e8bcb99bad1a52ad75c164fdde84c114f6257022b386c038193941422130a3cfdfeb95c416c53b25ebe54777de417e7a79865af4a207329b3f84f366d0a115a3919490b917ef14941df2c6f27cd04f0aa8106e628e92c76b386abf9a35580c325f239808d13633d2359c95f935634f16862110ff3dbb64eda2e78e8b3408b41df9a142ab8e066798910c42a2e2c86f778a7c4c39fe0536876d398fb901f780516ee6f9ef91c001a22391747873aa6dd654e70d66690494da4be7e5a218b911f7c3464ffff6ab5c1ab906105991e9bedc147b01a25e2358f3e3eaa9f7e2cc1d9d06870495f6b00a236a19e17dc205baece553bc2f94b87286bdb113a4d907e11fb802b7fe0dc3748c6ec3ad468e4b3ed691b59714e2f8d78834f7b986706e245d9c5cb9c345d7bb1f6a1137232585459feff0a33b540bbe0f55bb801b70a63ee3423c03db16171fb19821ea326dfe2b6b058de401699069f75d3126a97a81111305bd5b336deb48c95a6c1d60130176523c1a6c1cc658912e047c54912e0da4c0514ba39f9e0cc0d06cb97dd6c367b37d6e4b20fd160b7ab8bf4bdd8fd4ac9920bc6742307d7814a0d64e44f9b0270d492111e8cfc43606b7f509b96ef2e9b1afea5e5b380a4cdbac54b35c5d77e0a53649601884b4e9dcc7265c8e498f944bd43268364c9c57306939b6bc52edec8054d740555c151c6b366a55014353356c4151efe858e441b2afb29f095e4aeeca0a9ac9c91e50b5616c75e515fb0bf67b528f3938124b02d10729b82cb0f88bec6836f3e3548a278eaaf5e6adce52bb018bc2404dcdbe27e4ffbebb16e9d44cbeeddea399ee4662674a28a2ed795576d3dcabc62044de2618792823058d130f0f1509dba8dc43108731970f7dbd05fa5ff17b93b6ce01e5141f63c36b34d11cc29939564804b9cf98fa249f296e16bbef9e1a791500b98c12d3713c4ac9a4cc2366519616a1fa155cd1146e33d8d35c111406c1e900cdfeda1e2bda1602fdb3479910d1254cce02482bdac45fe2188feecdf23492ab460f7765237debf4fa8f7440004671866716f439c3939aee079ae57790828fa349bb9d263a6fc53de1aa65f7c8343b6522fc59a19dea1efc54fd92e99485c3eaa8bc392d5adbe0d2c742cc64d08d007bdd0274dfbd7e4e16846dd8fbc017e984929526eda1fe97c23e71d8381d45648df37bf40dc991d9685a022fcb2430f73a8d1b755e18dc7a335c21f25552c127023ff180623d74fb8dd498c7c0c53986b17384ca4e0b1a67b96217030aa7a24d6c39dc41cb2103491bc06e0cbf638e0967e4d8961d5c5371bf354eb9a6a7529db08ae3ecd5bf60a94215cac55d92a107263f92e2aa3d2f82b120e70737471560f7b132f5d6195b82ceb476ec9a9f86afcdde12742402a5c9b227e582e934e83f8e04cc31d5072c195b491a00ec454738a82b297e36e1b334539516d6f86356708a0ad91bd17a3e2861af30ca145f18c7c98b646ad40de9478993f9918c256e14240ec424ff2200c2c2e9ee77c057e3cedbc9139e2b92032df6e4764cfc00cb65aae672077e2949452beb6341536ef657f79690eaac86aa564a4ee53279d7d0d1bed754bcbc7a245f24a7af98b0d3441b6a1916bf360ebf583cb3968289a564e3bee2055ff209388f81058b852e52243c1c49d7bb9553d1a7b649ce25e6c4a9c335932c39808f37026ea32410be16d7f918bf9d65221268f8996c39bfde4374b02342db7ac237feef2f9ce33994f69f633361f40cd70a9e6550e6c6c6336706df0950c26c55525a9f44bab34ea4a8afd1217c9e9d685badc00a47e5b009e6b15417f3c4591815b1640ce00f2774ba163c94449cef009505adc4ba48b53c434ba1431f1f818916286484b6ccdeb4f76c9259676a760d0f2e9c4695fecdbcd208b0e41e48ed125ea7a28d9be7c7e9565f5e8c921f2bce7c1fd8b03702d2831c039864015159fe7ea8654fde8f5fbce48815bf8955762b616da05f0e0887f2bb9f543611ccf635d94c3a751da38183f71316d079c35b84d7a9e628466c05a04bdc88d3979fdc3fd67c08e578541e6bade0e3161a7a8670a9d294c18f38dff319506a02f9bc8ad80b10d6cce1fb2e68f083b03d83cc02ed8640bd439808fa0e902e9b1bdb51c9db84a41534b246d4e5c3ca981264787129b1164a8d6456cd57d4187f155311b785b7f2de477dd1147a6c114e445221a6ce380101048e1501d44f5898136505f6841ce181730c0e68bda980f9804f9de6be6d8c04235a470124c35c3275b6b78017d61b3f6b8d436161f09091c9ae7db61feec1a27c02bbef8b141dfb41c8e1e268841a225e1fdc6237f6fc61461c18aa4161d06920ffe6744c87bb25d75a6308343d378e13eb44ee71f45f81b16a12a7f16725efc8cb585028e814547180d708f0a2c98d102e0f5a93cd99ac08949ecfc28accfe672a8b9dc0b44fcbad8fd1d461c76a4785d57f3b04ad5f994c4f8a3f60829484df216ec4399961eedee8111bb59b041e9232cb2e5993bd508f2dd3f02caedecf428c0189316ba87f4fa5819c0a9da6a8396dcf9874c9bc4455af3a5263274cea2697faf1f10784c703a3a8079ccc0f26eba12497080e1e73aeb9a8f995a63cfe5a93ead4e1d7ed9921c0c21b78af7ed9d9d516ac8d00726dc9d5c7922829ff2654d64ac3df22dc3e9f27f78b38f3f6a80d63e1ddfeeb59c42768c893168edc5435f85e6a135ca7173c5eb892df138fe7099c7c8829fe128ef49ab75ebc582d3fec530e87b688670d2b3f51a59b437289555cd744da04f10a7100f8080d8d13915d5029a6771b63da6340bc2c2b6cf1e7d87b1df146480e60fd6c53be531c02d7f31a4c50820e5319c61b4931c81b57f89064d8a1f5351c85386f431286892426ea9e95c099215f68c23817d38315e580e39936975036569855f2024f0f339652b4c51e85c75740ae3d9fcb882437dcec022489ba24b4e6993678fd4c85cb6493e69b4ac192fd4f77afdd3e09c51749d9b93a75d90c5ece0c6bd9eaa3a37a5612c74ba301f3eff94add6417bd2204c220f90dfd1c927d606604f87ca2dea73556214ddea13d917414d96099315a11c3d302b2b70961aa432ee50dc165bbba49cf4481907e71258e3e5a0734d19c2f008206abe48c8ba99067c19fadaae9a0899c96abd2ba3ab69162c91a215d5da5881e614f751c69a74c5d8ec0541a44321a03bf1bd64ee391915cd5038d361e37ab2792ac553080363067d2aca54e2722fc544d63423332d8bf308cc08e3d08c33f425ce8e346d51f7e7d94c3cc1d1b7fa20bc3b0b457824f69f89454ab1e1384c73e0e64fd7801919cef697dd5c24df6385aa71062d7e3a04952949e71af3804bd0b3d400cd8cf99a4d4460f3a05c4942d15477e4143f86439ee2be46286be9d90229b1d7881036e0aa80270a7e47164d5b8fea73ffa9d7657d06ce2b364414c833e08604ae54943f49c1c4f7c89eb0bad3a643f4a7bbbd4814726d02b7bc29f6731ed9445a2060d0938178914b0beae8f7e099838356247ea574a3f9f003858424155aa5ec5f318ac715bd218531d2626e1eae94eb99eeb1660916db862ed52f8fbcb132f1c00e3c1376729e78b6e1d22f022392c56cb67974b5fb413a1cb33ca5dad651c35be0ac24951fbaf1c6a8b0e93eb9714428b36af1a3c5f6995523ff86ca18ada0e1b6cff976b0cbbdae3162c284d0474c76219d3142c86f1a0f83351ba91fc487d863d747aaf3b0da2d72cec230043712e2913822629c2d91c62a5338c173fd7228d4769866bf789bc1e85307bc81fa78b8c26c24921b415ef83f2ab4875353c289c387a9e613003ceca0ce96edc0430c8f7db88a81a27067ae909a2b2cd2c8b292459df237653802698c8097552afb80ca8ce6c782812248c4bba259aed93063395176eba28cc17072f5e74c42e4ea9081ee104b169026353b9b976470f4c72e6fddd8f1e26616b5677e805d3d2ac71d51f895544efb86f0793ff0c8b699ea4626fbb2336039e2fc34ca11fbf1aea1ff22461fd60bf7d86bcd6f8f9ae30059de7583b8205e240f05a6f43b61d1a23545c3d531801e60f5c0794345072ea829a7f5084003caa1404b90c9ed84876b9c9a7499308a3e0241a4715f779a33cd5f302756b22df7357fb1a26bab7bf02d2aeea1db427a784fe1a9d878806a85b1fa13fb44416ebdd04746a4245ac7b06423ad9b21e193d5bb0ebd73db5fe8db522014398110173f5bb90341e0d9d38ac4f0990d553be5215d6da88cc15fb5145562bd38466a40dc320aa757493a26e4bd8cc12c5200f4c601ec99795c9210c916bb8941452bfca5eab46cfa7f1f174f4044db3d25c38b3349c5675e004c4d29afccbaf0a363e9610678b55dff82947071db794d94d186aedb80f6ae695c2194f3c7cae24d635670899d996977f0b4e3d4ef54190f2e9eabecb611d8e7836551dbc35009415cbba97e50b5a864094a55b5f20bb49b14d7e15f64cdb496bd219f8267b205aa024299fc8a5df6a7f3f7e94a12bb25854e64b6e281aa943854dc3c31a41a6450c3e1207acf8af9259f792fa99350769d58b6df6dbd1522e7596b6677b66a3f76be6f030e06acecf5f68d8a40c61d20a2bb01c22d8006f467573cc7b600f9bf859aadcf1efbaf2efe1590c3541f316428d05647046be359a52d6328fecd3f17c93f8f6847b2314e552497b4d47dfa1d5c91966a806d1636ae7df95be10bb5dc3d0c5b3aa1157770cd26017947593dd2910fbee83996c68d2ae693c959c7a732c8f3d5e9298123b66665c4cc6e24cf14022179dca80aa8292124653762758480566c448f9079840b2df1d0b4a2b50f644264db1291326804cd95baa7bd0cfa24e649777d366b895cafd16a72b0208812025fa3752c8e5bc6818f5c8ebda7fdd367d7bcf3558e3c7e05de9430f1e5672e579c507279ade88a4b255c3e593f410732bdacd11f0d6ee8464b71c0023ef79ea2a7a406dd5e1be32e97c074e302f73b5d99661b0c36efeb5c721d8297b8b55f92ffe75640d633ccd3a44a2c29f639a6361e462db691cf6b32444d6fbb6c88dc21d63b8db2cda2f012b7b46bbe2b253f4a63e8d0f5c2f637fad48f901167ef8aed89c56b4e7bd5f515c605b809ff0f41fa93b1eb003b85f02ae24deb775aff054ae1a8ef034aded829c7a1f7ccb718d58ff522da9f9393dbd1f130bb1dd5cdb6ee61ac3822826f9f61a678d6f207f3edf9538b5b9014848674b5071b07307b715b5b6f93c871ff56b835457f63366df690e1f806609675b9b6db0af3571b0f04420ca7bff53cb0fb062a0c609fc715ebcad55fcd9497e4e898693c690d7153509f252df84b7e9df9533c8d022876f93c39e017aaf07be45c268241c50b613a830b550dd77e7c9180ee43bdaa5916cd223c1566e96044b96e8053556bcdbe2a5c3b903eea33055eb817d7c36aae87d795f168247f29f19c195617b2e40cd6179ec4bbc577f86cd74d46fc1dea01a4f31f0cf07a7851a87fe99d472bf65330d376ea7cd3a0fe1071380d19a87dfe20e16e7399a10ef84f1560b71b9edc08d4e2d22a6f78a89e383b1edec4140b84191317eeb39055ebe31782fec44a591466b45583cec81642b3a89a6c32881be04c743bd5483b7d9757e0cbc73c3eea1d1155f35bef6af62a5a15b48e58589cf7502dc6432c0da755dbb264a596e9ff0ea95e14e0b0562b6367b27442107acd36a6b2ada732efbb8346349a3cc2b148f8e5894cddd2dd0fa54087e800e8bcb067228008870660f41e9614fc98335cbaf334c659e3fc72b442c32f9f9e23840b860f11fa5bdc2bc99beeecb108f33be8fc359cfb807a56b5b661fa08b1cfd2ad182b3eb3d0f32cf68dc0ba23bff5e92fbcb72b133c1497be6a76886014ef1fdd09dcdd1d58094a7ca599d2c0fd99e351d9695d3dc6462e97fce7b823665d7c94140b893c51efc054de99e2fbd28fdd14b80515a91c98254199a9724b94c61a4d07c3cb19c8ebabe8e3d1fe79488b4652dfe9c0a491f67094c6b0602ac27d3e8e59a6bae0f71147c1d6da1bdbf6cb046d5f7d33d07d225e9cde1d3c43c479ec8428192e2563bee28ff769ab87badaea5a9cb2478527cca698fae8bbf59ac49fb63b13e712d1bc2591f2ab0abdaa9d3e11d44521deb07cd5705d453de9339dc37128e7ed3a52a8e8f8ae270ff56b3e9aa27b596aad85546b9f393cd7e05cf82d89f46848855f0d46a375b08c35fa8b37023f32bb39202bc853543d2b26be7e00dc077438d16d4adb5ca5d8cc13ebc0edabedeaab6fb9229a9cb80e6a4ca9af6df21142f93cd4773821d122a916530cc504f05a0e789dc6b5c4d45c0c3c587944c4178e3685b2c496e4f523d2499a8ae5741501b48646f4bc853c7d6ea48356c717311763a3b16e1e8119f78e78835681cfcc4a1d88308e58659ced19cdcaf467c6383ed4f522cd1292b6d920e4a3e8890df8d0a0beb3729cb54ff02580747172ed7e1a816d35123e32e336c26cb58e608705a78c6346d54f105dd93b8849f1a13da4733d8a46a65b54d2af3399994de7fdd3a6f05446c6a8ddc0e9908c15dc15cacfe11061ac56d0501bd066dd121a212dbc5dc880b641977c325fdfc57cc020f8ca1555e45de688208b5d3533425e706c52bed8dc4a47d5dd7c4bdab9348f25d3cc78f00d0dd9dcc4eba2c731b3f2a7f822858930eb8f8a5b24c8ae664f3b736fa65a9d7cb44e9d7f287492555acfd877544a9b8e83dfd1c2c53a7eb1f48fdb7adf90333a803b247492f5212cb69552b7c4e185fe58c45a12eeb2975309a4cebbad6fdcbe1b25d57aefe23ac25cf7464591e8abb495b4a31bb4ba73d06e290a70055cbda55a452ad41dd15add8b1ce9f8b1158bbf5a5d183bd5280393ccd0e3a42d1779d5113d01cb1539dad3aa061b9e30093babc42a2127fe57d97063f7798610b747f02c495b55f1f4a03a38c706466cdaf789eebf82cca093071bea8499e16fa2c9b57c964415419fa240267b33a50031f358b83de58360e209e0bfb43fc69979a47a799f8a5ba91165129a9f4f59e2d30d61fd6787640ae1cead847455f7d5bc35eb7f29ca7441ad3964ea300e5ef3ebba2bac00ad87d1886c8cc178bf4a13493b29332a54fd01f60405551afc63ec6f9fdff2b4a75c95d80d8d12210192192c6ba568b2d93069b8a8956c10b867d5e442e179426a2b88b213bba8cc037687aa499e0aece384522a29388718309f40d5f82942e2a0f337296f3328f75551eb46ae97225411cba07b7e4892d3331b9a570f154ee6cd99293d4e7d8525187915f47af3c6b395a6270a009b0b258fe00cb0dad6f668d1b71a335a551ec3530321c3ab8a04085574af56c120880866cb76a6587993ef5350a9642e810d2f4f32cd927575943b6834ea1785424080958a12a918d49d2a6be5c1a84d987a5fb3aaf54d8c51aa439b29aeb7d901c9d0107f42bfd1833a87713aaa22570a4f5f96103419c0771f667c33e5c4d444c38406c5562f7b2c145ef83ed86bf8913f5f070f3e0d047c5fd9106249524af2ce9f384926023a33fdf978a4c6df9a03533b524166c5c83334bf89bf4937ff87a76981b750070583c7919014c5835284a422a9cb317cc2fd629746718f3726df44b1c9198542d805a578327e6f1fd548ec43063922866ab83303c1ff9275319f10729824c1fc63a4a9d9b0c89040c238079202a7d0ffb2b69250761152e280c439a2248a70ea2cf3a5a03139a7509e1e3f7f59b602039686a616b4f5f9205e13084a0715fc243b0b8f36239c78bcb3f5fa7867569fc3fdf250e8246dfd16d9601105480788348641dbda7250e6dd392e8bace96ae0ec21c21ed4497eea5da02408dc13a9481db8e0f166cf55f9f01bdea053be32c85d1fa2fe860ee6aa0b9eebb58d8812cff4b624c8a3c1dd43d41e7dae38e94ff6df8298a7fc060102b0fc378415461f26c79721b8faa27f2bffc059f603501228034c3430c865ca3def301c5462462777aaaecb279418d7a7b7d688cc9574cc3ca50094ce3e4393100c51ba8d026eb1888cc988c6d6cf36d056b4e181798d92b630d43b25cf3f02195aa0d4883828a997c9616601e8cceecbff89af12881a496fce5ca5c218c76cd7511add428eba094f587f0fd928198e559c1583e3cff159b1f98af8d2851ee7191ed3bcbabf45348451c26d16910c2932fed577670a07b4308c53ea14ccda7d415d5498c8ace6a6460bad15ac5dcf1967edd2f14610e57630a089742f8a9b13e549ede8ec60dcc72d5360c1dcb25d3ebca23e3467b8830cb08ebfdc9d125fa27c2e588d08f20d2a06632d94224626f56c524e9368bac3a26b52c9a6855d47d72bf04ec0044f52131a431468949f7251b3589fc7549a89b931e9df73ca42375802ee658763eeb567e0698187ebe5f7c15843b7ef6c2f9b375341ef43cf77afd621ba74be86555d996fe834d8c11c1c753d0789ba37669a0996e68b59e35ddf31edf2b04c571cf2433c5727a0b32b1d159f77c60e29e556bb81bdfa3b6b3346f88a0d55a532364d5fecba74da018e715d91c9128acae6558a70269fc85e1d904b20b0aef87fb5b1566f1123053c9dbe3db0d4933a239d53170007ee0119558b90cf63d961f5c15c6992ef22c95b022134317106c7d0c56a1036ac8e167b40d2a44a4c62cdadd5d3c77b74a513b7401f98c682b8b020215be21cd5111ef0ae699946c75f7bd5577ae152761ad8ba03fc9f2f097e265a379b954e2fca6575a2e60391e2e84fd160447724e743ba2571b85126a1c76fe514a1c3de9a781b2da681954eb25662ac8715f2e5069ee3752e78185ef019f9cdd6da7c7dc8a6723ec04ff8f543f441d2661d9a808dd914b8cf7530e1d6de2d74c6a9de31cc5036250388ee29851f0e85112066f01a04fe1ee405592049484b0793105390ab6a5dc7168210d89269914eca8fbfea500c077f28a69ce1a366a38fad2093d0cb276f721ffe4c59c1ba6126fa8ee071af515b3de22bc96b8dcf6a4ade847a01d7e4135292718a2d2e0add0384d7a79d3e56cfd25428d885e55ffae1f5556d0ee2f5587346c220e8003bf1a7916dc3ca915a4c8e71b2bc18b5edb5e934e8625bd7c7c29189d842c36baca0a7c06b4c1ba8f1a3579306f4caf559f016df8a5c5ae96a902a7c040f91e3c5fb9848df1702bf09e365c663061b02fd199f3d162d24164533affc4c8f6f8bd8bdf3b6c901e2178e43a27301a1c5902a1eaf1fb4f85e50cb5a2c85b4c02d20a4a5bb47b118072765f140e32963e40f5557b9222666ce8641b8bba8e5c592efdf3d38c801c5aaeba71183ee74c05d48de3da28b21bf531eac910008b6a7c3baba9e60560f036c32c16e6bfd7deb9d16896f5369d2579dea22aa2186a3869be52b3f636f34ce97c0de6d9601f8bc195c59288260b8228852f382aee59d7b668a1b763ddc3134f99113ba7afaeeefaf674025afb1db183dce84c915782a9228ca94834e4cc1f265b5fedb66504aa60d1cd82d548a830e8dea02aca52ec47fed7cd5d296e693c9f7ecfc67a80cb721aa7419db0ecdc2c08d6ec1401428972bdccc593043a5aac9bf824b70ca074d7f63aacf9ba6e72c9a248f78753b509971f0aaf4e79f386e7c1811e8699d46217d87a31d101becc98f8f8c757e510dcf7826f05b43f969dc401b814051845c36cff8c00fd1331ef31efb95bfa324c0cca391282cf75258585e3346cd91b21fe5da95ab2caf814a6784396b7469f4acbcb3603d5af5bcfb15c84f3bf4c6754d404feae786d2f176fe7783effc714a0f7910cba6f00bf6bc4ca2da359ed3734c5f28aa257b84e3a0776281ce2d7902a65c96457c311c89041abf41fc61ecf7647f0712bed908daa0021b0700b1b0c68bd720bef443c118f0baf120fe7c5fa213b5d23c017cff4692b9dd95df857295628c29cdad5c08cba7ce5ba53514834d74a4b660f9f1fbccca1e83e10e89ec0578c0959668fb436e51189053dc331d42a1607c679e4090c6017192103872cfb02f300d47343d7ea10117fea905532dd6b61630d189c501325e06b64d53602cad5aa31e1fbc82c2c8682fa16dc798b0c73216b402a849b564a524e27e620e0855102f3b91be8434700030511c12e84a21d75dd77f95f2f05e1de40da2578d4875106cc53a0864cd573bdac96f65b36950b00ab1cb04bb2e6edeb1f6c4ba90ee0cf09bc347dd955379566a24c742f3ce626edf6fb38ef185be73ece603ca1d3466676a39d692c74f04436c495d269b866c09281724f914bfcf6d0d5be93159e8abc18a72206403c0b50e5f271d7d66a047584f1039157516e222941ec506620025c6981f4a6585424d87751a7eb762dce3a2ea1207608e229a3c72abdfeb845a2660b28ad6393c294f5bdd3ba0527022580a06847c71c7d98f16454a88799e90a43006d69ddcbafdc6a5eca74524bff1002a2ca910682fcf5d991e3173ef3fe1fdbbdde64fe6b2a70ceacfccfde4aae81e146ae88426669d7a754ef29fb52315b25c48e2d84d65d9bc3109bf3c852af1a87189891c9e3260989ce8920dec1fcc835c959d088fa1b5483dec5a5fb397cd273d77942f7389a656c00fec3284ca0fddedb3ad337e8476c8b0eddcef591187079ba00a7a14e1405ffce9f50580e6aae652b7f12c1fc4d514c239bf2a95559663cc0fc85be6c58b023bac754404064b778205e52d894c70d6b31377ad734e5081d885060aca23e4dccd3403c9d535b8557279fed82c690aa12818c3edbb64742ad8f092b6046be6148ccaf2e0363c7d85330eee4afa7237802a6820221863e51e3fd6f9f5c3989db6ebebad5017e3490e1b5a9cba19b40dc91b49f9e914a4ee728e6a3213f33e1c94be3eaa61781603e32ba41ad8a4c5afd5cb9a9f3579e4eccb60e4f8a7739b1432a81a63fc3b5258f0e791d65c7cc97c94bbece688256fe0bfcca66596bc717d75338d89925da126fc4b43e28b0c02d6999af0c197f7ad3d15c4738aba0092ae1952f60891813147fc45c8589f74aecbba9a0827c72bf14fc06253b1971f276c75eb4c1536cc634eb220177d8035cca3cab29519b34cb2d55100f7498bc1f79da8533100e7cb7e9a87b35b5099955e5b47300d6a5fbc880aa020b7096a66bfd34ec19ac5467fdb43552b501ff4a4de5548a9385aa40b3edbcddd0c6fe3702c6137d40cc2dc28974692ea53048e60ba53e777e7ef3f4ab010c57b996431925994d498b6d43c15960162296a1ccd0a4499506644b98dec60f25e7e3ddf029ff067f1b499d4537ee4627537e3b3adc02a3f46cb0588341f116ab5d428cfcd860aa4c1e0d8ab668e0e01471d9597e7e0c7674e242dff7bf02a161d79303d0267d6ffe279dae4e8d0a9be9d9e6c057c8678b79b8afa40e9ea24387e2a217f6c2c4a1e94933567a43242d3a9856c3eb5a175d445abc9a8d09a7b5c586b22d8d0f535a88b6c64b46b4c2f7206ae474fe843962056d41c58a70fa86dbc83c875a6ec6f9cd9a879084ed19a3999aff795a996c9c084b75b33e88f5f374fa92e1e03193c6e45a02ffac065d5ae1bd7d62232c4dca862914b2aeb724f14f498f11c528cdb68b16bc46482c5e5b3d13634d3ec8e67c3e552b4664fbc3cfc123082dab5e560c1aff0aebcb9f4b6ec8dcf5b1b231baa59c32b5578ccc54b109e43e7160ea7207805dd334c63cf758e04362c69d093c1b68c6ac2de3bd707cfcd8da8066765ea5bdea941139c0db092dfffea54808cef8a232694177ab17d7f5c97289389347b83cb33dc72ef157b298766295c4111f8ff25f8f1b38ba00af6074eaee86b17e192db2932c3165eddaa1ee46d355232ee844456497ba8e103a5f6e611e7fe72f900798b31747a88eb6800d09bbf09d34bad628dc7a1b00e1f81ad874619b5f1a8852aa0aef05fbb5b5990e672ab6332d7a0fa638c14a0a0ee024549459dad99a125d15af537c977802974e4944d1000633b8fb48cff0945154ee69047c5c1050e0aac8a581f89c3447aa458d114fb42239cfbd132abdffb87b08e1e15f3354e4944f12f4e85037394897ca3d0a512183ffca8cbf1092e98d3abcff28f118a3a72a0c4f6ee83db674a0647e513684846c39db1c6d4f061eb3b6a147be006d31a3eae30bd86082595a0f136d6993e0cc071c81c9b9112528383107648ce3aaddb6e7be075ba1f4012c601a514368008dd85b2ea506d8719b805112227ff171b2a6c4aeb290d42437939b2442592cdfe67a48ea5227388026a0b21283a8f870281d2fe97973738efb8c499aaf3ded77fb2819a82efade8eba95330e93e2405fb3b11cb012bbbe3a2197220eb55d9de7d63af674dbc1064eb63ea6facc21cb214a1fdf9d8e96f05c7946005ff67294a825168fc654335ba99bb381fcc4216b76da9ecf19989e7097df11dd7e6a4915717d7f8b2d924c35506e02318ee603c74061c9fcc813ca58c1af09d21c4ba754f020bc4609b728edcf2d7c2e4a3248dd5405719c9e98df80bbb63c40c8e8bb99c233688a6dda36e873189e228c9574d37099dbe2dbb8f318b86af32aae615e67e46d290b256fd26a398d4fca71691f8ca390b9c7af221f079b2e02e1072bea6b05be4bcf656704ff2a980bef27498ebccfe51e289a86fb44a365419be647ebd08c180e2d256aadab69c3080a499f9030dd9b96f0779117d964c0c8d0936c88f132cfeee15365592e46daa6f1f5c884264af51f876c169010a69dae6fbc3382f048de9bf61338b73f4ab5f02d2ca32f84f91c45a7ef25a34a9d4daf98d36a26a1592cfc366999369f75d1fda2262695fff49a071a14afa7bbbe72385e9be60a9b21df9e09f435e88c09b615185c564375cb2d1433dda29d0a9cd5eca59376dbe4ee6b4f467e2296a18aa4f81ac02213296990b283a1d36c579e8dfb8f846e4330d139c598fd94cfa777f3081f2bfca1cda029e63da1b946f213f9da70cc6e29afe479444673a73ba945a1802fd87f7f299165abd1499c79da2684f26c901677ea1e453cf7a39e774b128905eee31cc996dbcc17c040c9359a9f1bac8dd063436c783b225e722f0312bf93730a1f5cfe16de425ed030b6065e85f8fdd6d7734ec1981d52205fffe8daf6d5224b8137bc05981ea3109d4fca1be0c810e99307cfb8a4e131f1ab357b785ae0425e8868d71724b79f5a71cfe61b8cfdd9327de5a1431b1bc77fdc804339982b96e026d4ce8ad1f53cc98001121e2f5225b4fb84a22469deab4fd2780d8e76b3703d467a465634aa08f79f01e34000bcf3f011b009c28b42c75a45111ca824441c147a89a7cdde1b242cac3e6443f8c2dbe8a20151f4962d6f62364420d65fff06d5f034d2da4b2746818b286b09ea7bed83fc13b256318e407c81384a7063382ca1f8bf6bc4e48517fe08c6297b06d0cbd6019414690a29fd30cd9c4342854e4b67f347c861beedf4b02b3d424cc0ac0a4b78410952cf11b4f2166f90f053cbf38fd104af7161ee364fefd5211423bbab5449cc5e7f035f2a2b1bbb313e110d0ee4d1b6b58d44c1c3de90c0fd0beb54ad1dc064b6b787c8ecce420137a3ed3417039de9765813397e3cd8bdb4d0ff0a2b7d9a6ca0031f1950fd96f9012f35f4973175c656443217d9fc8dabcbb458d9f112ec68a7987a40ca812b17fc5bd67f6ca70d89df2fd88d6cbe9f44f2f924f355860541ae6f936a94a6fc65b636c0516b10c5029573b44d9a529926d1ee84abaf68daacb8d4af046de4cb1747fb7a8b9d23052118b534a81f5bf6078bac85b7666cdd3e3f5294c198a43cf0e9edc3279d288bbafca0caa7ba44c50d432ffa00869c0d3e1a859c73bef7c9142888e02a6188e5aef0f762b117949480a72aef49fd642f2a7a2bcccd4a20a0080b249aa1608392fdcb81c6249d32d21e83c2d04c65f5542243e052756976bf7243471efcbace1a614f4ea7ed4de64a7ccf7a5900212a51db03b34be2f41b563ddd989fd079a732d01569dbf802727ab65aec2fcac2df7a1b7cfd05749930a77644d4d55c136fe5aa47a2dee5d9004a373c0136c9e2385c666fd415dc1a4e4d3f0061d313db364f6294f60f2f0ee974bb3662bf87ed336634c4531f8b3d051a9fce19790eb924b833ebe8c045ad312ee7825df5b41d45516708d2f9ed2831f2ca1739cdccffa17a318e66b94011c975c1721d8b63593596450b23d0e9b6cfcbb9ea1c1285abb561b1cfbb96417c8b4f922f6ab7f2b0ebd44b9c3ea59178d252b384387f44bbe22e05b00ca713dc5e67367f98fb8cea641c7e803d7493737022d1ac1da6b41f48839342eabb19687401211ce6f77d45871e878f9d2c18b1b837d4611640dba82e3fcb827ea1bbc432541546c2bda81a01879bb2fcff748711895c0ce8d47338c150faec01c85fd8f7b8707f37707474b9fc378479f3f5b93e593102acce44de8a84fb2c62c350716904f1ebe596ff6874ce5cc800a16f6af99dde74494af689128fa7b4b0ea2d2b986504cdc4f64ee8a74462ac3b51824b59751aa3191bbb0e70af3a16a865604df4356e8b3157aa03b55f192b45f72d95fc552c96e033a827c6f5937da44d6b16c78c5d43824603f495ce7d309ac420e9061dab768eafa78c0f7bc4cf3e57ef391bfdbf7e74aaeabaa204e8152e0061a6c2a71be0e8aefa0fc6aade9e994bb5439b7ba8de53456d61a84d1d59c867e50ea969df1cebd011d801e9a3139ed3bc2d0524f99c2234461e22545a408d539650b381defaa4f9a35106aaf89c038acce3623a0e9d0eeb3d3471246e03720a60b6c748f3368de922502319eb716bd260d74cd03a364c97f76bcb97c70018dfe9e26f6982703eb3e3ce831deb0a2dacc92b5d8576e358da850fb49e084a202fdf5d97f57dd4d145e48068c85a0fa46ab1836e560827aa32802c61544700fb0e0a97206ee0dfbd650645e99c0a686fc7b5f2d9fd95f693ff61dd34ff208cd4f3c26eed4a6267dbf574c3f60e4aa680e741271bdc522d21eaa5ac46fdce781b6c9a380766e3ec3372c75f2c57014a42735d20ebdc007bc41dcbcc3a808ddbdd24f99af727c1e42b24d9d9505b3973164e6068ffdd8d172f8b1204a558a34d38a743e5cb88797a53bc4de440feec468754caf0e2c341679e6840dcb677103d87f343db996a5996a20e60af6355c6392af3b5c90ab0fb180c9a20960d0e07efe1df80c242382306ef787514f4c186bdb9a514f76cb587dd556e793f36878c9c6e725db870ec5e7c5d06244cc3e4e6d1c64bde84779756cddd44f66391dd7077618d51985ca45cb8010681b75d7112f4e423a217bbc403c3e4432225afb5356ab59f57109fb3ebdcc919dba08b8e073123a789de7669b91a50dbdbc5b866b23c783198f4ba3b555c71f2ec457c5974f358a687724f111532c3d9ecf3c947ba5c1476720ff0fcea5ee031a0f282ef3e319450b873705f0a12d108442166e4494f4d82f2d89c3288c7b7ac5177037682bf9d0e0f404071f1f2dd1848fc8df11f88a1f5be4b4fc098161b2233d17c92acb861e5e78b83d2add5c58dfe591193fcf52413ef7bc7fc3102feb4c87d8f0ccdf64177626125f357470ee2766f9f806e8ca4bd79e2311136ccc045bad10ab2d691de1cc6399433e4b1f77b2eb47f3f39d32a98f2c96a93b0303c96fd8a2d8ac0a37825f07da91a8d6a7a270f0d43717402620713050032c60267abfd294e53a0a9150eb65445455e32e6ed4dbbae7858be4d8841e28ae254101843e3f887e369e7668ac383c38924ea08a1eea05051ac7d754e87324c60e805bc12766e826a6c7082d65a9e4be20c7ebb4511f6a642345129e55156d5d57442846aa5c83387d82cbcd64664cb737a171cb1b6ce97b174e37166aaded1ef2358695be1c94ccdb91495f6692e1114ea21bc920e6380fd997057788767c30cfbef53b81a9f2289eca0a4804b048f7150b844cc93e49c3911e6257f9a3ac2ffbd669a9064ff28556c04215c11ee35d110c7b555b0ddddf12078f2ec44d6d95e3d0686098a6848f3c6a78b55845443ba6b16ddfeef16f8c6015328ed8d5c29ec088ecca5dea9d0cd9b68fa4200671e553c2955620e651ed6fb4efef1846a70878639ca22622116f5fb8b7617b348dd54b60fe3901027444abc18337eec0936a3a880b01a74cd97ee2be11d8972df3af7b2f2d384b22fc6ff64264f698a3d0ead5aa4e63d913c93f13bedb0ba8a5f7af021e9db01fc76c5e4fe944ab6b7964baeebdb6dd3c056e20b70615296a5061126ee1c6f4947ce69e08c4b99c4aeaed95e91b99aa0336c2ead865cd1e507aba16d2f88fc695a47ddf946fe0c19f511602d98ffe48b51ffd11f3353dcd139bedd76969ce674cc201d681c2ebb7fc5ca21e4bc53864de4ef0f96c8c1513cd01e26982aea0bec34c8791f0677bad5ef617643321e164506ee4c16cea4a2070bf64b42002c34866ab19360ddc3557576f9add31d6feff031b49eb6e490922dca732e7a368e6b131910092f574b5573760ec0cf788a16a40dcb9bcb91e2e1bf455e3e310563d0fc8997bf67b76f03b109e46e862bc27da116018ca32887896bda74a05ea5c111daee99100b11665670fa740cddd8278ca3f9eaa110385b60f11db22479dc7c6cc85421681278dd2200b23cbfa8fe618d275137688d335111b2823082cb66c37f76104be175c918f9fdcd86cfc34cd8065524ddc8e741195f7a9785b8b791d996dde34e1ca109887ee18def52ecfb4df4b759483442f4d0112a357e35321dd1f325e5e30eb3c97d8230d1e84c5ac2295f54f12608c78391ae7a69f18c647cafbc20832684d3c06f12e8443291b8072c4c99e1b8cfc1720bd5e19beaff324ae5f874cd0af84ce694d00dd84bf894122bf6a53199d2ea8057db37e14e9a057be7a7dc82a36a8b50bb9243e9c61b9843cb8867c50ef273781088640da142595f80997db8b485275eae9ca772ded5f9fd685ff701d2ed3314de89634274ffb921639ae2b3b2e6dffd395ba0d77c0722060bd2d94ca323b937e2b8056ac6dcf2afa5ff00be75fb102e9e7c947066001e80c7a519c3503a727a3df72ca339f8db338a7a6e2b974d68b5e4a2f75cd283571ed90916b2c619b09e3c9f4102e74fc9b479171f05676c35ee8b2dcac462c8c223fa371d7b3c1fdbe4b04d20ec20075bf13ce68a7d1fe73526ea8cd829f32d79e0b1eea0ec7b7afa64fc135f9c8cc002d2e2a90351102231ec9a2962fb96d8d8f4891e2246ff9130197047cad5adca757c74437d21bc9c805a837a4b62636b5149e15c02e26ebf6ac42383ca48cb489a5c08820deb1bf9a309e0111b3d42ea3cdf09b581346ea12c69a80cf47e7b427fe0d5c641ed1e23538b315cd23d267931875064a8f537965db436f47269a60d6b6e1464596d9179e3c82ef4851c9738c34062613ded7d4cb8298c60c9f7a96f4b109bd75dbb9e2620a1a9e8954c3c68034ff67fedb3adcea225a5d96a5e03dbc81934f53406b997f454af585772711a7b3cc9d3535f66c80d2c12cc3e3d1813975321fde4493058b784769d137e9ce2a4a66e511f2c327e93334e40cc3ea1fc6f03c2aea330ceb0dc3e340073948f5d991cbdb0d2715dde35a11949f013bbf3d4d90749db3b2849907eb584edf1cbd951787be304de7989053b9e384ef79278f749267ab4c62c946a260eb9e66fa223992b44e564ec5c45edc0355c3c0370a9524e4429ed68c90cc30d123bf40994bd14c25a754510940cf2ee9c14630de2b52efb7a3b8c7781f2ec77c2193a8a0941e37eca682b2fd337c74a4bb12a0f1477758d9759f3f07424488568e1ae9c706c6b08ba1b8b868a9146b9667f85e41b9b7664fb433d04d67ff32bcc966df0d2044ffe35ef26bc05ce25917c1b492822e9d4de0064696d9df6bd8b280e1dfb7610715349ed3ff83da7639edb2896ddb0ac3be364a6369e6449a0ec7fa322a11b6d66a83c205378391d746bf7e12bbb1c7aa988fe8c08be9a0d793ef08a8f3ba56f32589d7a2303d665c4568616a7e477a2975a1fecc8ae785e61ef148056da1dcbaae4f2bdb96d37bbe87894292dde0eec39c426a342190168a17698ca7ad319cee9ee5f26a0c174174588ba1b61959206ed371367f505759d6481572db2376a285d09ad2f92378797e10881072dc8463f4a2e00d85778c46642a0895f65b071b1803995420fbc871ad2a698552a936d99e7ee844b306c450cb021fd073532159225a4ce29179c47c506b9429cb28dca138eed324291084cbabc2c4b3c724d1977d463dcfdd439435132eff08401140358b5e7abcf00decccd5df4012ac2530a2990c6455664eb910c589bd951c91a8c30c7eeddd785993d835d1000f87fa1f046c26fe7a034b14f9beb1b1e89d68662092e629c149e423a359f7659696f27457496e3e2289c1d84321e371dcad42e5ea4cef279ab217b78dd2df8eaa6586f08b01a9ac77b200206e079f6ce107464f3568d586e9375f2d383a182132f99ac15ed8c2d15b75cc4e6c8c3148e6e3699c12251a83eea859cbb988ae03ddadcbce8b49d04372a319a446eefa6ee2da51c6342519363918aa982b18bc5700a4a5f8a5c1a77f38bdfc67fdb089df5380de99ad73fd168234569e22957c80e214b52f698cc912a0fa817c4ac45cf732251e4ac8180c015ddf2728f271a0bb63b40ede29c23487e5dd5d7f3b75836693ac86ce070aa6def697a6b13062f5e6d6a0822a5143a14b28e60ad60ac30236ef71e96dcde405d68b3071941b79c0f70cae1905a78e7ef9c73c4c167743621afff5112adb2488ab866e35af5acbd6bb9e0fd2f8174feb0e3e3dca055ba9429705d6c668d8ed95c88561c50b84e8fe5c69720d7eed3a83237963f461e8627a0b1ff92d492842afb7a14eb436686ce71e43df5b7fbe7c4948ceefd6dda4509c836d46d619f25690c2b8cc1f2a69a20456607c8da030ae6f9508d85508c22eebec9e79505504022674a2a9ffa6bb1e54724326aa91d41883c53648b077be2e9e5864a4d5b5bf29bbb283db8a055020c877aa42b08a957206b8b547f34b5b692aeed043340a3332b229d6824575088f84851072afbb648f8a0ed3ab031f3134107ca614880eab57acb8ff718a2d23b6b9b0354f76a1479fe00fc676a4659bbb04e03bebc62c60ad9f56b6d4b5e4c959e430947d35a1f75638c200c71644cb4485941e2756dc00f9df4ea80f8ab3f86af5a7eba9481116bca9b7d24b74cebae16955fd8ec94c1d3a2d957684188824674aada8da4f87a5d10683a8860a50e1e9834c6ff5544dae0ab7be4280a713ace8cf28b405f8ec4269767a74649e12ef07b5a19050d218c530d6c29ea1b18d2133cdab7f0fa43661851b6da737116e2dbc401d208b2453be61488c657218efc64e520466f1c21892710ebad3dd9b8983ae22c92ab7004ecfbd06933ad5d31d3094b0131825b10499aac357cf4908b374efd10c81b5fc939b6c5a3107a2ad3b5f4084ba4cdc24d13e43c57e2ede44e60a3bab70e71f709c4c0a8dabd805adc12c73a9b01e9ba9f4aebbfb478677b6f6460bb135297dce487435f47bea5e4532e3182be4d2ddd3f2f06440a93005e4abd6009eaf521242c6fdcd1f2eebb3d5dc853f591835907b414ad9b16287f084e8fb174dc7a696d70fcf195d4a511c921dc79e00659595b6550edff40083121a4c8ab8ab7875be3813e1c34e63877092b656882cc976f995a34cf117cc30e30844707e4164eb91bdff4c166e262b7dc128b2e87620fe26d3fca32a9bad41a95d1df3ce2881bf832416b5d6692a128c3dfa0a1b31136f669b4dee3ecd1a7363749172cffe0787cb6870a47e700f2259d43515406836181ad5776a8ff3cf5915910f586ad919cf875cfc3d35a3f814e21c9f4d921956a29a1ecbcb9033b54a817eb0725fb3931db55a027a8fadd840dd7e0e0c691a67e4c1adb61efacca5ce07ea28bfcfe5112d0e6c896e5687b236ae1f0c949f0c89ed968f07fe49297b2680fe27428de7411d3932f50a040d2dd4bd89d172d49ff3fbf993857eb30d0e19763662ee4f6add3b37e273ce65ca70b2693b88e508b53031ac7e5290d114abdd77432c2f8a7636ea85a2061337365374d623ce9812dea8d0fafc9a660aed715a386562846ed94a55f3868e0a151c186032f68fe65d038118f1b94e1f8fc3ce80f73af3ea65834d8556f459e9929086e65b19a44cd97081fd46071e82c841a3dcf2d76c1b23160f54ab5cd0912bfa3a23af989069580d7429db033ccbd7d5a3f4fb66f258b8dfabf16d52cbf992472b16cb8a193456e3c322d5f736984f7daa2fd5b0e63d3c191796954326321b8e94766e9644a5f79b3ca394bc1801a8abbf45241e6a3b0c36dc59030af7e385591bff1a54e6b622f888d034051a56c7c3d650ae8431f405021ab8949c8e750bfcfed5b3f1bf58e174e9e43f295198121a2fafe3eef746207baa44d77e3eac97d47a901e7cdb769e9144942a282cb7682351a744af2b8ed865d21ac51a3c331b63ea5ea00b35610ca36a66e9cfaa87dcb546d6e31684bf197221d16141a81455d64e628a8677efd0479cc0c47ba845def81d7e79d86864652099ac6b02ea28c40d10aa366aec23a5326b71b5e1fd66d78b813324008538efe2fff289e51c461a7cb218a7c80f1ba0869f89334564c112251219d7d849c944d57614ae5bb442268e24c9bc069058122b911e703b2c24c3e24dd65bc8bb350eaa8aadfeada52920d779f13e11b2687d73ab80044965e09268789a6c090969da3b0cada66c38bbf1995368660b35a83d7ae06a7b843ce2bece287ff51a02abdc6b378b9d65199ef0a72d20219495fad061224d241165a544a7d7df22322786e74f59199b682bb014a98c170b1a39e52e9ee645184637613838b0bcee7f792d0e03dbf492390f1790c107a2174864ccc009e34995b029074e0da5cb58f52282adbb03e9028c0abd48f6d5b7c9f01f98617ada1e93d0d45a79a2718f3630cb1e3c51846d0612e8c08560b304fcf4031ae96694572056b24a07159d088bb37ab4d38d0835da51aa8c37e30f5a145d4c1f95f6dc3df41cdc7f89ef4d0f2b5a280998d1ba9eb8412d2862063671b8617458bcf0b2f0f1ec2812f7f2761e7dfffffffd4ff6265bee2d654a29052f0909092809ce0c050a43f9ccf8d1c44ddcf4352971539307e1f0516742ac3d6a5dbd42724b7dd9fc3ca8fbeffba17383d983d887bd51c70d75eab85023759a5d17e2f0e1e0416ce54aa7ea1eb041aa30871d390e0a6a3f053d67ffcb2891992425e1cde6264dd3b4ef4971d4cb90a3529b73e0d8337e4ca0107165e241dd771772538bad43863772541f018ddcc568d62eb1ee9ba29a9aa2a2fa888df6a14bd045f40aa494b2e3af518110203cedf9c9987f64f899305e3d5445e1ad90c4a9cb4c9849163281ea611f1c877084a96cd42d1ac2b6d57449ed1f4965186973936ef5373b912d6cd01622691af600e23853bc71998a780884890e15deb8501cf3e9130927c0a30f5b1c72802465044a9850b932f11828bc02ff0e063434a82b266aefa0f6e43250672797b75f9ed05f000b51a52376c09efe3eea3e2266ef9fb94e0cade383c7beb277cfe33a23d5afff0e9d11eb80e13430e71b9c4c46dc83ad48791ba31fbd1a43e37c37bffbe272409de73136a845b01d436aa2d322d890b29d76f2203e8a2151a49c6e9edff9ca2a223b5fa444f3f0ecfc0fcfb42cdbf99dffd1debefa1d2794f27213c2d884decb6f0731d3afbff7dc04695e32135be11edc85d9c94a759b1f3d1e48ddc426ec02d6bc0b1b74816df917e9d80eac2316557301998f79ffde52cb473794783c3ff2110f0fe223a46ec23d3ac63aba466a529d06dc15c720d6b907af8e7ef758cb239a221ec4aabdfd349ff0003ecf67df4478be48892cdc164130ccbec33103b59f871ef96ad7d91d3ee1e19fc7860f79c2a29eff4109d80b75e67b106aa05977f5fbde2a88664f5bf43e87178a586887e71b7cc2f319f8c4033c3c3c3cdfe11310f0b0f33c08ad6a084f58d4423b3fb3aa31e11111c3802fe048a3e24153537c446b67db59366512de8a65124edf18b1947e32cc69f2be7e75de56bf9cfd59fb6ff8851e73ee5e1385c73e4516575cb982a972303844fce910c77149781c87f38437e79cf3bd07daf371e1ae8be33ec5fdc671e17844acdc770dc4f5d4719d3490882b8d3571dded5f778f66d6b1dddd9e725f9d205efbbd7638f650a9171cd7dd3dbbbbbbbb7fdcdd6def6e6fdeedac05dc7577777757ce4ca3a9996974cb34ba750bd26dbec875edb7c1d1aca7af48a5964d2a25d5b2d9867b1bee6db8b7118458d6d3e7ba66f681d9febb1b11397f58477f90ae1b4d6faba3b5acb8f087cae933ebfef17136862e071151e1bbe1e9f413e9d40bb4ce6696cd9612bc5977c90a2c5450c192153c5802133db0024a18fe194b4e9bee54ae7a93744529cfa49487aee88af2d015a53c5c4ab2945366736bee91fdbae473727e952bf0ef6b85ea95ab0ca0ca5f06d23ced3dee91bd7c0d7cb1454dd334eda56a487b5074e2699ffd1845f65ef5e4d4fe47b714c3355442fa7f34ab8064ffeb929f8121eb604de1f5e414eb90bf2579628b5286485e5213545ac1e447e218c21be9d10976000589082780810410d60b2a595260858923a27cb1031e1891a9209531690172d5a985b15a9af2059309aad49e9e7c8a55952750a8f2fbc91be9aaab02085594f09c78fc19674870ce3a42c2ca61d3ce5e526609ca30c489976551781e645dfefead0d2a00b7fc671886ef0faa3f0c3cfe1838d411c22f9f439db9350b695a74cf62e08d2c030c91610daf9f25a5f8f9fea19c3635ad8a25740828a92a8c84d08670da88c888d0429b9060c6b42a6210845400115332294700e19222796b992081028a60f2a2601a9c24555125081a58f0e4ada50ff3c34fffb444e07575ab40614104d4173e8881020ea0b4a0700694127c48c38725218c98c263eed147abf6be1d3e2cd981ded7a2949ffd942c9d78f2b746e1c9ae6357bbe31eeb7d2357395d7ede1a9ef410c90c8e81100294cabf2f50d58f2ac74ea344a5cb4a6e97fe3a92fde55355551f1d5413eb9c90a830421dc594142a3fd4714775679f3ae006f81085038fdd3d03def2e7a4e598f66662726a402ca5c494b4af557b47b5796ef1cbbbcccbbbee2f594a29a53fbbcbe94bf8944b48ba03954ae876ce5156ded1d7dd7d6ed7ed673ea1a2f639e62d89f1111bcd1a780cfeb64422146a9af1f33da97ec8321f7461fc90c4053318021b48b0f1c4162ef025234820fc208d0dc30a2c54502e62204608360c2a94e820a2a80111413461c51629e613543cc992bc20aac9921c3861841c2009c9d45da2e2045418415997ec52aeaffb0fbbfbe24ccdc9a5e694bed3478b3b7d939967ef99d30f86393316acf4f9b548a9d47c6a99bb53f7fc05e27f546366667a84d37eaa25993d70f70b7082f0f8a1b668f16056ee53cdd9c185bd9d7c7f22d520c1db976afcd775b787fc662224b23e89e92c8085e6b3d00c8f3aa1f93a4160aa3f4cf5704c529f7c6086d45beeee04b1723034cbb22ca39452174cf1831a34955fa3495514c10a57f9b31774107e90aafc13092a5e9040c2a0326f7142e5a5f27753073989caef7d4a4cb85229156b4e9516540e42f570fc9a9fd54032955fb008c112a1a5e2a3eed2882bdd3d0c222485a12eb8a89d85d3c3f1abac22bcad445cc765ea2534163ff6c5aafc3204d9170aebe2e7df262a767fc0b053f97128eaa0258201c870aa2cb20462142af38e0dc76d5a29a1b0af7d16703b65f33c4de129970bed7aa8bd7358973fcbe51ea52c5072994b1135449f2975dc2ce28e9fce615ffcfe1c730846731e86d7bf54c695daaa20371e185eff4ef94d4260d2fd532e9722597cab8ff53378fde1f91cfe04d90d77f8ec87496818d2a2ff0c59a101dc86212d36ebb4dca1c8779ae8209ea45dca12abeeee51aaf7cb65225c8a649101240da4e3d3891cd471b354c7411d774bf528ef70e4a3f61f4a0668a0558281d2376178232321212121f506161529215f7e92da034c93d0f65c4759ddacdab1fa4383194461243662a7f6b3d20a414a93d52285c6af528e924003a1f954cb32b044a7a713b51f072ee30ac99be1c849d38974007054e0c9dfd1416c44b776ecf0f161b1562b24a4f1e767859eb61a74eb679b81f7c3492218346852e43b25fa026fa49e5786a7d33a5078d5078f1778238fca38108166566c70813ea7d2c7f3993eddd215e73b28579cd3bb5d91f967bef2ce8afcddb6f85795c70fc2aabf6ae367086e7f59e342559875f9b30e73577f1fa52ed5ff874850007649698b231dc5a4388247f5e75e985e411d3b987ded7bdf60e99ccbcc54db750791cc5dba31f306aeb7e6fc49837431b8658d3f9472f85e1adeaf385948f0ba21b4940fb2094b5a1c5551a3cb4c428225253021e1042dda50c284c2092d2fe20c1b6981b0c61426a2a8c0093880811a501c21460649218f5220b865ddb4c06b4e22c47df645a9df3e891038c45beea4d39fd2e94f7d12a1a26d2bcab62442dbe72d29bc19c6accbe927e4de7b19702a9e0c9d0af94984f8a5b7fc930811f156f69fb7fc95b0818b94b0617e1616cd90c32cd4b1613eff0c89784b4847fefc2a3cf0c75b2ea512549f413f713390992a85711b55d5811ca8937d12213ff256160e31ea286fd14f228484236909a698d00a29c14f7f8557c286feedc322ef3bea578876a863c3f6fd423a94071dbd6a8067ba4c7e5405a5a19199c42d7370e3c3dfeff6d53bdfbcaf9d97cf6a480fd3d81e9cc43ae4b7e49dffd13796ec8093665b37b887f6f2c66fa0b7e28d1b52c78d1bcf205df1c68760b7e28d1b3f321255bebcf1db3be8db167e6f7f23e4a31691963012d3be364a299ddbb67d2f1fe6c79eeafe3d4339ccaf6b1f78836907eccaf2c6cbf0e5f7dc06eebce83fc6e0f871ab39defd7380384016c89f55bef1db86cfe0b8559877d5d08d23620dbf55423c90c5194c2085cf71770394350c290cd34ef4f1ce55439de893fd8f097ec4e1286dce29a17ce328979a1af33b6683c6872aa79cd27307bddef847b51d1e77779e2b4c13e4df9161864fa337f0805ba90c2e77c04feb181cc51df0d33cb06b71d5373031adfe7b8e763facba626228bf116e9449fc49c2d37e14abfc61256cd00c0bc5c43d6e60daca5032dcc03d90b00ef9854c4c5ac73c2a21abffe8af7e5534d0ff543330c8460cf5c37a16615ee4466b78e34679686407362ada818d36b063207701398a8bfc22483c1b703ceb7f3497c4f484373253124331ed0b2ccaf13afcac67a1acbb01f67c08fe6f20f845907096653e335343f33b7f464ce13371fc80be1fbfc7ce8fac9aa5006edbf2df0177758caeeaffb5aa8b200aa0d370870682edd0ff5441e8f3f71f1ba578c0aeeef473be2807eb7960d1ff582c168bc562e560e5c0f139fc7b5efe4bf0250f0c4ec334790dfcf9687a402f07b8dee209e98a86b09d704c5257211bb528657e5d270cc7233caa7f4ec838e11716edd0a227026f6428661e3e70f0f8f1791f8e276fe48e7117495ff33c1c373734a43720f736e098a3662111d780e357679e061cbfce844c0c9594c450ab4ff4f91199f5e327036f6c26947a1eeba9b34050a6004419b2775d53aa5c2c90b048da97d6473eb4230dcf816c6c5f3eb89192edc147ac43be6cb922d3d03de297c2470e76c7806ce5a4265a9453fee769cf494ef695bdfc1e9c6f633ceba16a39384cbf37fb1f9e038e3d95be0d387e5b48c4ffa37b3ea9276435a7a38537a5140f62b54f95b3d2df2143d1fd354d63f99016ddb5a11d2dca9faeb9bb873f5be53bd287513d3a3b365fd26cd3380c9a492dd39848c76ab93e4052feffac06daf9d5cbf769209d97dbd39ac7f9554991b23ae44f1b2d6782f46dc0ec73b80de4ca03eee2e01c58ad9353d37eaa86e6c3681c27b32c530dd1988c66dc38e0c8aa33f593079cbfaa4dc3c18356dd62a9fd9864f5dca986769e53694f44fe88e3959f06e7573c5b0f76621d47583943783684445a944f1f07dc791e50e7370e6c8b9df8e504ae727670f1564fe5ea6f562b1cd8487e28b32ccbb44cd3f833d6f8358d5552ee93012a0fa932540efd351b671bed1d84edaa866cfe47f37378e4a7fa3bc80fa312e2cf6f03f2fb88dfc762411d83693f323bf1b8789a114e72a285fc1ba83dceaf6a933a60afc0de01fba88d5a94eb922fd286ff9ee7a438e1f4c0e3183bedcb5f7e0fd8f32cb474c594aa42dda5154851b9addb01717e05faeb8039dc64555936e7bbf365b2cf62f3733ef54c56cee7fce855999c9f116bced36cdbca79991cd8cd4b1b4e4543f60cdafcaa82646ff33f5a03190a47d1ba09a640afad0292fab9deca09c7ef8bc0d7e12f4a22d4f33cf43c0b656fd3036adf420c0ad13e87172a7fcf73cf3bf8c4033d3d3d3dcf604f58d442e0ab6af8079f9be0290d84f3f2198a9ced2585e12a6f8f6d75f07b9f037e20b860e76d215d1787e31766fc37515a942f6d42761272528bf23d203cd6d802aa1afe8bacec9798bf87bfba813776ac911a481ef991d956edc11ab0e61d1c8754c93d0d38f654999f01c7afca844e440c038e34f5a563dc3b0c3f5a943fa481fc191c7f5b4d49234be5a74758425653d230a332cb9a00af0784e28d0dc55b3f2dca90478b92478bf25755030bf5bc149ddea1c435cf6c0cdef632b353daa66c383938211b754e185bc22276c596ca2a205d659ea1b8ff088e58aafeebb4c9cc99f7fe6ba06d669b993321cf99f7dfb6779006fc544333cfcd8032328fa45dc667405957b56d1f64fef632a091e9acc463a175550ca9f79e9b41e6b7e75a45c3cccb84d1a5cafccc732fdd107dc9fe12a4f9560991f9992a622ca937ea56f1a24b65150d313133e10e560dd53cf7a3e5fcf6b481669e478b3099df3126a93ed4f0c69f6a0426138e49aacc1bf991097db4e8dcecb6f7a09c9cf047ba8536e07c4ef8ab67214ed6e0502465e35024bbe4c436b0e655aa20335ff332a08f167f1a0816c3ccd73ce772e7737e2392b3b393f3ae1ada5435c83af3379f6364e66b5e82453aaf63130a89f91c5ea8db1725115a3d0fab3066cbf92de765c0271ec8c9c9c9f94d27272cc2791d24b26e7f13fe34d04c081b3949a68aa126dcf1f38337728c97c87cccc77ccdc7bc3fd79c7fdf8facbaad827e7ce647afdbd374d00f6ff96fbf3d6f0f1c5887ff168e5e8bdfe69f8dcd8dccff6822fe3fef8f03ccfbab54bfc950c65f158587f3dc736c5f32ef3ddf0703859301b932946d731970ebb675db16ea6c32bf2dfe688ec96ca11099f0090f398ff31b119c2f52629bf99c9c997094f96d7b9c4d666395ccacab4200ceefb66270de655ee61b88261cb91ad95edac8af79ce8dd8fcf7d90e329ffd28f3313fee506dfe03479bcf6a6ade553170ddb6c604a9091db0dec27919d026ac0147991f699d792eeb6a40f93423d79b99979151e2c1e3e74746e6c7af9bcc8f5fdddcabf9ec6dfe47df803a78bd9101475a657edbfe876b5566bf81317f038e3bd49ab789a9b951d1c01ced6ac0ad34b44e70e498ffe13bd418702b0ee88123ad5be8fd0f9fdac3c4469a3a3214aa316d3101de7a8f92670403f2bf6eb8aa21bbad9543926d1a94825b16b7a5a0042f539c52cffb3ef932e54ab7e422b74899e5eb0ca5d3944e9eb73a54cd19c6d055a98e1fe52d8b7d71d9987642e08d1b535454038d2f53b234d0f86969a07163aa3d76571a287b62f251c4a819aa8de8981326df82249bd857ffec0bcac95bfd947adef7fd8fe2d6a5c5dea2dadbd868a1eef3e8a0d4c684d5d1bf5bd4133ce9d44057f665d4405b7770f124d4cf23f7a02dca5bfd0fe54bf2984c244a3deffbfe9d6a3fb7eafe76344de8b180fc37c5bf0522bc19760eb620024f470759b587c1544d8ba241be16eac0d57584f0ccc0a548b8ca306aebe2a481b693500dd42e7582c8ea2fab87230f955f9e6c946852a71195e792cad3a8b2ec52f925970ef22950b349f5291ea4925d5cc988698937d59e46ccde5289246d4a9bbcd5b4c9839e9a9a68136daa4dab872a25dad48637f2539353652b4e3c51a88164f7d41bdf206da5b3ca999fa90a4a5b825b964ef6fe498424a89365ae436937dfb351aaf245529d3fb27cd449b5ec4a9d2f756ae8976f43bf0c8b9490df9f8536f06f61913f7fd315ae5ff66c9dce699cfefaa66dbaa6bb1ba669beb3de3ad55c77adea97e6df51dba7368bbf797e67e6fb69ea841dc30947efe36791b986959861f8a90bc78f0bc74f85a398312b51de58d2ea8423ddcf09470f271cbffd2f1cff261cc51fa7ba5f53d4e2fe0ca8e44f1ee4635b1b138e9e172ac184235585e3b79f0a9fb670e45179763c6969387e9b71d4e7ad56d5aed439e182b4a7bf7150aa53bfaa2156b7b4dfc06596377abd55b603c1f9737dc415c5d868a4557214b7bb9ee4634be14029a17400bc99a4c2913a73a92c95a5b2549651aa75777777a7f8658adbb66da3f9946af324ea4d5a8c62448b1eb6931671a078631f31a9de40485bab9e38e19653e0ad15509c547f19c340befa56d92d10e13943e1b6527fee2e4005b8adce5e554f5587ee45d60eaac2b4aa838caa7b8191b158e5cd6181e7deb5db17112b551db2ca441cabb0172ad3e0c52a7f17846395b35851c6165354e134c594de6f75ccf046f1f3544e58562d761005a255baf2c21bc5da3cc43a859452765048291bc8a594325c4e825b393993383314fe2c41b946b8ed9b167838bb5c6a531651a0c8236994e549090a6fe24ca2b858699212a597f491962c549438899b7489aaeb5477fcf0f30e6d28a9d4556a719f1966c7cfba606ab4f0ba37088e1a2dbcfda902c2e14ae9d239ced65b9bfcb232548bfb315ab89549b2499728272b4d4871245fb2450b1315279dd4b12e579ca634c1463012d491cd42061c33bc6d91856386275b5cf6177e0e351970db6266f9d2259581d7ff5503b9bae4cf26540cbefa28a954964aa354aa4fbc9c107851bc7eaef25bdc178cab873eab7e1fe22a5fd65042cda50b78231542142a8b5027242317eafabd1ed491a6f60f03e1858bc39d91b4e2806cdd17d755128208f56b71eb2a095184fac1248cdba83bd7c5ebe290430242d65d1a820f940d982d57aa4a8bd1db252d4cf503de2e699952f7bdee929626aa6a5ffb75a3b1e2cfebeeee761590cca0d2dedd2e8174b8b29a823e5a641e73abd7f3b36d7bf6766f21799bb144b62a866c6edbf6ccdffdd9d63c5ae4267125b81567055e566f3450fbd75715e466e4d4151ce3e12d5ecac3a5cf9e3b3f6564c183673b319596c9679095131b4827487728d6fd4f12fa78a27d87eff3f2c9d127a43d4df7ff4085b4e7ae8f635d387a14d6c7d18f90f6fce00e407b6625d57d1819fde009bdb000e077615be0ffe11352c2c31f2db6774b2e8389b68294272ddf500401aaf2b3b80ca4ca3e95df3d26f73625b8e94ae4b2ac331f16173ed78c460b3d18161b48cc5695b3a7bf0dd45ef7aa81565e85611fd5a3f9eaaeb81f9f25d6293baa1ae232eeb79c4c35a48519a53506ae0dd31da65823c193a31f5cc8074d48891c64edd8910f42b481bcbbb329a56caedcdd9bfdc0c1ecacb265f6655916ee77077252ba37e7a3d56ce53ebbfdb33a5d35b47582dad66db3f74bcf54190fa6bed4ee70eb56372584558b528aedc3ca2ab7fb6ae7491b48ae9cd23d7bd726336bd9d7dd8d18522ed7ace1f1eec32491695cf71b854152a5675a8aeb568479f985d9a1c2bcbc84635195a9aeadeac027319c16e92fa970d795fa1457e9ceaa2031af814f62429a6eb1af74b986cfcc27959abb160f975da35a66c8eb9a5f7d672699596acb9ae653669a9639e179443eb9be2b3aa774399dd022ad780c6e2baa72c8b60ef0949ad4b215674a07b0579c9916b2104443f2533b32cd69449f9fde8d61607efb1770577c81d9647d79f9ed25942d6ee1762ad949e66457aebb751aa4924abd279ec6592853d02e8986fce7d3ba99f4a9fbacee4e93bdee2fdbff684d0b97abd3cee2f187da04b9669dacfb72594e4a29cd6896895e5716b981e68622abefa3ccda4b3976ab59e972911b5a24227be0655cb783ebc6d5cb8f3430d91371a7f09f488b383fc8324ddb3939aacd15a57ce75f51be0682caef330bbb06da90dae0fab90685c00ced48c36306c2758802631ca1e145e5f746fab4051043489a4ab324e5fbf134ead9ccb299c2104f4d68e1d2930b6a963dcdb3b2503c42cc5b9b83079e5677e989c9138fd65d7a427a3afaa8189c94babf340426754324fdb520ad90a1c4b63e4aeb05174d57f895eacb65471db70b0b7564231e3275e4265a38195577271b388d01060bc05063290c2a6218218695165c811a03690c294b6364c182022c3ec0f224d9f8f934279c0f599665d90a7c985d202105d2873356f0124402b6d8c1144b68d962898dd9832ed030a2ebe20c1904f1a403277cc185ca954bc8c1199feb8b20787c51459defb3088d104fa4c106133c28411421c9832526b2a480f036005ec0a0cef9d40ba73abd061a797ecbc593359c40a50d17b0e08a286e708457c45bfced130b31d3acbaf081a6caa75e0aaafcaf8b23bcb6041e1f7fd44a0e9614a1c9833cf6ac5071842c82948c045e84a869e58a10bd058a9c568850e7e4a57286940851bab54d50283c2a5f166fa55b5b47b16a44b8d2add57858e16202056c69e298f8f85341044870e2815753b70a12889043081e4ddd2a4868238527783275ab4015b1c5f3ea56814a4205243c9bba55a09c48c251168e36d0ee4eb99276f777f713ac245169beb06e953482f018143985952080becaf9ca853ab27cc61d3ebc1b9b65b700403a2f6123b46052fd084b5a3061c907598c60698b2b517680c552d3117c588ad20265898b2e4b3f687a028436765f9303759f7a0e7e021bae00c2084f0841064ba060022107180801064f28d9c11231777767ef27ea589cb999b9a04e46537706132a68a209f729f5f424babb3bbf3b3716777777ee31a4bbbbbb73636131199e497777777632ba63edeeeece3f72952419eeeeeecefd947999bbbbbbb337167777e777e726a3c570f666777777eedaa18e907ef9cd8e458ed14c6e021f00d14c58b25041d1b4250b2c47685a38220b348ed020d1a8e623e6880a0c8ef8b842fd8a91f6d405c9080b946c09131b40c1b263c6c90766ecb822fbcacfcf2b9c94317184169c2406d44684932ba0b04829a744c2fc058371449447c42f852e66e0c591ef8a966dcb931b68c142fd4877a4f3b94182cb123e57322fbb728575e8a93924e12da8c00041451954904c0ef2138cb043243d91f4184c2f4da62481090b1289448ef15333832e68f073659d382e74c0649db0f0a05e83c838b1a2081e481c898fb16259414412ab7d67e993f3baf63cc2922112e62b417e0ae9e124dc9ff3eeb7ba5b802b929e01d7aa96cf2a24bebe2a863252eaee5ab7ad92322cafc1254a149410c75d837c4512672409ed165cb4408a10e74a9a100613a11e609a8a580444f08249a8879ea4c98458194a423d30b310dc72309d8163513582a4ab94a10e5d8d64e1760db8658dac04a9f6732fa04fb73e160aab56fd4b0f1cabe3a543d104de56191aa13c84b755d0b852fd99634cb1260fe21897e618972eefda208e3df9a0b168f4732c8d6d6d9555352cd30f269efc6d823128a02835d2902a20502b14e1414dfe4c6e4a8e9a5889518ca981baef29b6f34fe6a7b1ca1c638a516951c95127c18561e49852ed576a208d4a03f9c7a05a037d939c83f3e52526a67350a3a99c0de7600ac7c14ec74155652d1ccbd262c782b0313354313cf1782f61b8dd8aea493918199b1c1e8e3d79100cdc77dd4f57a7027df625bf5f876b7b541874e8f87170fbf96129030c04e851a7eca01fdeea1ce4aafb9299191a9a2536526c6c6e6e8e7096c0c1c9c901627583d5cece8e6a28e5cf4d9e4c535982fcf3532ee5d6e01f0487b891831b2c160b05265010455185292a743e5a3ca8475d1d66703e9d8f6a48ea00e2ef9896da9307937442878e9f9f12b0a003163a1654433eb1206c10c7a0bcf5145bc22e618376ebb65a50d2826a8895ac88f0465612eb3a72ec8963593a8895c4941cc5b6d46625310f3aaafd635d4d04c1108e5d1d57c92b22a80b520d718c4b8b354835432a1c532f3ff32d16c531a8a6e71897ee59462584fbee635441e677e1287f863a466648b7452da8a35ab64ab1a8065a185aecef5440becad0a6053eba3ae3820bfbdaea23e48dab34e4070c36c4a0fa09a87d71c7a55e542f324625c47fbe2743ca4a90b8f6d105c7ffb5b8a4c509f3b79e9c224afdbae0bc1b5329ed53dfa9972afa23f7dbcf7129493f05d2163b74da1d1a91df8554be67dbcb37229fa6a47cfa0deaa0d5961ae552dd214db19462b917eea6b6712a9898999aa6a01ab685648716611f0e4131fce392bf2135ec36e5e8403118a98eeb86e873373743eccbbb2fc78a1ac87f6a6fa4a808c9b64313c996524a0f2606a42faa0e4c894016c80631605b41439c01433c08b62d1cc6f0a0dc45a08c064e4c7184ca7fa31ae2a5eeb9a8a3e361705ab75a655a7c299e9d8eeb7878523ed85628a26763aafece5907ad76b4c52692d57f2553d9fdbf00814603c1182e641686345fccafbb71e3c68b239dd4e958ac1c2b48da71e0d869121bec720c29c263ff1cacc5c3e15540629cbc71a1c623ab9a3d677cb4c41f899452ce0e0520bc8eb1f68cc4466ccc190d278aaaa199f14b0ec7917f59cb40e92ffd0325bb8208542891dee7d1789e2b498c2580a18329a260d22cc836c0860b90886289255af083904b41a35377498b1234b8c815aca0026ffcea2fec0bf6420cef3f807dc10600c3fbbbb02f980b42efef635f301f3fdebfc7be603d06f0feaf7dc15e3ede5f00fb8209e005f4f283f6050b5a97ff8ef707da170c685dfe2cbc7f00f6050bc0bafc7fde7fc7be603bd6e5bfc20bd682cffbb3b02f180b29bc3f00f6050380389ff5f375ec0ba6635dfee1fbafb02fd80aebf2fff777ed0be65a973fcffbfbec0be6b32effd5fbabb02f980aebf2cf79ff14f6054b615dfedffbb7f6056bedbab49779ed51d8170c8575f97bef9f635fb01cebf27f797f1cfb82e158977ff7feac7dc158ebfa1bfb82dd58973f7dff705fb0705dfed9fb83fb8281ebf2eff7ff7dc19e58e02bf016086770fc0c7af9821101e97dc18864f8ecf705230af2f4f705232ac0a77e7bc188847cf7dcfb67fb821119e05f7e5f30a219de7b98178c88e6b77dc1880ef0365ff3fea97dc18868f8ef6f5e30a2219ff338efdfed0b4654c3af5e07468480e7f99df77fd9178c2801ffdff3fe30fb8211d9f0e183efefed0b4694e3597fe3fd63f6052352c08b8fc2fbcbec0b46b4804fe15bef3fb32f181103dee755787f9a7dc18888fc0aef7aff9a7dc1881af03fafe3fd6df6052372c0b3f00078ff9b7dc1881ef03bbe0518118f07fa00bc3fcebe604437fceb05f0fe39fb821141e07d7c8ff7d7d9178c28023f8077e1fd57fb821149e07ffc0befbfb32f18519117fafafe3cfb82114de0617802bc7fcfbe604414f8187ee861bf2e7ea20a843f2800f6ac8b7f0220cfbaf88b803bebe29700180150675dfc100073d6c57f0388b32efe078037ebe2770068b32efe068035ebe22702d2ac8b9f01e0ccbaf81700caac8b5f0160ccbaf86d0061d6c59f00f0655dfc080055ebe2af011c0272ebe2a7014cad8bff00e0b62efe19406d5dfc0600b375f10b010bf041405f17bf0c60af8b1f08180319957fe885ca0f0396ca4f8001547ea1312a7f75a1f2ffb852f95f18801895df851e95df471895bfc7abf2bfc0a8fc021040e5e701eeba824220a7ca1f0070d7b5036c21a8f2b3e045e5070050e5ffe9a2f2eb00775d2b80bb2e17b8ebf2015500f953f841e56fb150f9452e2a3f0ae0ae2b07b8ebc2118e9eeb06b8eb0ac15d1708be0f2a7f0fc8cfa345e5df59e9e46051f971c0ef8aca7f9342e5b7b1a2f2d7b4765d33618cb7c4ff52a5f2ab72ecba381c1b48a9a8fcda8dca9f4dc1df2b0558d1c8525b485ba95c79090d2a756bff04fae5e98c2c6a4edda53396a8bce1d45709e9a91c7e8ff3e4350b96089113cb51f2a4d440dafbacfe924a3765e164e5e94af54d4b6d0f37264a3def7b9ffe3e4aa78731f4fcaf4b675ca9feefc4240a977538aa746a71eba26d51393b24f8be2d6a6e514d9452598ea234715c9840d13d1941c9625f502f46e0d07fa1b28b67f4ed6f514c4c4c4c4c01a3daa884cdf66441ec10d1080000004100c3140020200c0a084422a16028186ac2b07d14000b869e4876589889c32088519431061942080284106008010081a99a1a00f9eefc6488e4606f630c03b257007b312e2ae872714fe80b28e7b11eb8ee7de9fb51e6850079288c27a492b5b8cc4e0ca9bb0b089bf9989a40cb37aedd12c2b831f3c51c25944241c49080b87349c3b3196f2756f4dcb3d198e2ca92cdcb0142ec2e6d9a85122e32f3b3add43d7208e66a346218d43b459036438066a326c6bac0197ec1a6520c2e8177586b9871c38a360c139b33ce1da8864f61120b5b3b2150ee3873c08193f2949d54853ac1095ba41d9b08498ec8f5c1727ac9b53ed393922d0d19372d94e093d1a003614dee8426b5a2ee7a151f27bbd18d3abe4f18e3775fdab7e6c3dc5ab68d610427098dfca8290f6bf87df1fcb8c19f3f5470c50ab5092b3b91e26fa60887da360a52be693d1415a9a9b4578876e59f20d6152da1bb68e17b847b02ea233e1e05fc33147ad4899561697016d0ae5421736d6450c4788038dbb671df1df7d0d45864706ea12ba9637766a89a6a626c072680ae5847f7d2207541c40690e54b32c3cc0b56d824ac5852bb0c6dd0736b673ff1903c942b182afb33ac6d1d7227518eaec7d4f2b6ab2111680e17dbaf76c521d58dc44ed52898414bc34162d8b886d5c17be1d1913a10845f76c69d02de0d37eaddc0b966968e51ece602eb2b6e5dbff1c89d1626cbc02416219ac459e80dff86eeb4ea4f6f02732c4450bb31ad8aa620a833d6f18fc5c2978f15c36c5d7352b7a285ca6c824933d01bdbc28bafe835b5eaee12535a7d930c75ec6812c69bad96de61e4e0e1b876a6dc03d3df6eca4951ba959ebc6004dbdd9e79529d42d151f3ea4da13185da3ededf38b31a153b2c55a8484415f44dea024194b51e77933876df34bd09b06f0266e5f9cd888db8d8d18c699f7cef8b5f12a243ab83e1628118d96012cbac3b7782f5f40b66e9ce58a3252305acb9042a757bac77fcb6d75c5614eebb15e9259538483d592525bd64ca04f641689c43cfe2d1bf972a108a94a555e3fce690c34696ead7ea767bcd61f08ca4236318919832d3828f02b1e4949dfab3a0afaff772198230a1883356505b8c45802ffe1a54d5ec1362a726f07c98c54585572940f8c54fec247823cea4e16379e0e25f19ac40c7eb86c431d1572332914274e5def29ca91e70cbfd20513ca6683ad34c2ab64ada6bc3620a8784f67158cb47fc9a3f3aecc7bfdb47d4d2c5644e5225f7222dedf70bae09f1c2fc1432f051c83cc8c3dfad0f45f5387988768efa311aa88f10c9fc5a2ef726851c20bc7d2e4405d39d057c3428967017d04d98284bb7992f1d85ca071555b65112092d52c7015e4ea5d53aa95f27498b5e20bc9be0d88f82633bbe908aab84ec82d91f496ca4ab16bcd68b6f144e1c46158a7a2d738b6dd9b98489e7b655454444b1f9b044b46977e7d1ad5f477e067e20dd9a6c3d0e364303e33c65a092e9b6f6ebec860a55f0f9de0ce7ad3c54d0f9513bbc754574b23e9f157b04780733720161060a77eef886c4380d5a3f33540ecc2dec2015f41035ce00ceef0460bacb043f6b0f7c27b84403513f0761dab0b0231a8b42dc3889983274a0b525c4c89a73b6982dc600d5be0f354aee607f9493b4f784887eae02474533726db297d12ea77290102158c9c490613faf2a861e75309ed024bbe9b9c708a79da204ef695ff0df6a74b70c0871ffd65672ac0b7eb68920e9c031a3f461bb613aba7b1a2fa8b41d178002e5b3ccedca14dd865f281f9078b7f50a41029688ea389303ba059932d58a0663e663b275a8e13b1df3f734a14bb94ba7c33799d75beaca92a1d490897e38b2cfd64b4c0b788475362577926fe0921033a0949b9a22bd037894db162657dae80072abbe851d93bfc3e781b07cbc2a50271165bfc27848db09f3f223c43227d07a412d214578360a672b0f9b31c2d5ff4f74c88baa2ab6f141d1521525f4e08e9bbfa201c89bbcdaf1495af381bf3069f145271671a8191b17422b12292a9a961087402df67bf614660199c9896d6f5775e7c567213d4c8ae730cc42221055ff0f402c902a7e6c875859e566414a9f56b5a1745a81122f0dc94265205b401c0ad2b8ecbe818f4854ef8ab42a0b53f3cdb4a84f755b75126cad72242d20e5c6337928e939902c24af9872da09704a499f320f751f0b2fbd832427214c3cc2118de8bc28425fe1f2d1690077c6a9a7f59ca0a2bf3172667840627f53dfea699579ff59f7928f3cb946c22cea9afbb0032008baa6b79ff8a00c3667ccbd3fa1c8282c180202ae064f858e3156109c5c79c3778102f78806289ae434d4485e0b5f18a4f82bfb40c60191411f01d0631d3dc190ece090fae56035c442ead1f6edd0803ee7b953be3077b134dd02c65c163b7cfabe11b0569d68b37dcfe6a7e7699d667d91793561df202c12728d18db0161e2bcf0e4dbdc8ad74542cd6c3f0811f2b1a6f2a40ff9c8073ef5299ff98cf1b1b468aa088745c1f8ce38da69915dd4134eefef453338ede853eef7f2941f48609b454c5cfbef3eaf2ea21db2db879d8f3c39f2b4c2cde497c9b1e84f28535a747aad42ebb249aa31c0cf213141734a510a31e002355e1d03902fae08c22ec0859df5a90c8073145ce05762c85fc8c7cde1aa96b76eb26fd81eb773abd2523acb6dfce923ec8f9bd96a727931cdb5b724e6266825226795d48c2d19d543270c2ce777f91524644dc32433de2f6153f60fcde82e95c01c379056fd61f34ca02cffa4a67381aa0f09d244b816e08168f404fc930f2c1f9783abf0d22a948bdeb9b8376189f24e6331993837c1b232eb140589a9429fa5e4928e627a964c2c97ef85d6d8a3dba18156d06b42cf0345ee8e120062bbe6888c552adce5bd291efc0618875532201727df082ca2faa11dd46262c9a2070ee5de89189b5fa9a6d55bdad20d79369f620b1da05ccb366b3a37aa27dedf55a4b4c6b25305015b29b0323438fa08b07666eff1ce21d398e0e83e00cd09196ccd0ee46de74a5f1cb01059ac03a3c9a4a082b65a3c8988b9eda1108ec9403b9c5de265d8efcf13a387866484fac43407a92385077f9b643d9a386149b17b7a477750ba42c7c2a9a77b3e4b79e61569902ed7d4173cdfe2b54a31dd6b819805667954e5a4751965ac08bf9e713af8aa85f2d6f8ac1b78bb0b1ba07cd5bf08527f1a4d294733337e618f0bb259f96bd7f2ee4a32a4e4295146287d4664c82e39a09588b403a7178b9aa8b91e14ba3511707660c2c703e306c29d199f0d9d953e5ff625c53fc03eda24dc81d0b30d537178b1908e52b171b9009f3652a26dafc8a5cad5ef8a7f1be640c8037b92294ce3a51dee1eba66188313b5ce45d8c357697b607e24e153a4d91788afda8fcbc09c134498c4e589928bec3770c5a6ecdfdb99bc73cdf0a911f4ecb0e75e5338b93557f2f16b1987ac160e414943238aafd7bfd936b1792adb65449ad6ac6bac2bd8fd289a0211dd0b4ad78ce3258a55d24560973b48cf55e5051b32432411b9d0d4b0e67e4ed9f9937aff6861c7d2d0352edd2cdd760ccabce0e70198442eca8df812948d8481bc978264c5aa9f26f5c01f5aac7d9cc563c09d4e1b70bce7c3b1dd66e1c26163b609890aa0f800ee322bc22f85875be551597deb6eb7094ec0a4eeb1945136be237bd1e009d463c8ee43488352c73cdd96773a7dac0f8bea55cba42d0794b64a73d2e980816cfbaed781c6aa06b51f659dae1c81ed85d36e153b57da9885a525ee9120489b23c276bea0e895b697f72c31ed88575475858a9e42afa2afa1d7d053e8d442676106e7c8ce4888eeb66149ed97d5def5d62b09c077714ab6dfacad816f1a1af166b6b2014583d5bd93f5339524035472d1d75871a9e68b6a83a67598284b1a2d176ccd7d9f5456268e12e4a6adc6789792ca3b01f29e3dc857202a600d6d55de5c07273a86788e8ee1edf582cb9fbde07caa546e9005ac7f1c9138edd6cde523194bb7fe258c6fd7f4a6ea0d409ddf62d6a53ff0b4c264b89dd520ebb6dcb16594959b863d38ae118b060b7cf4c1e62949e437cfc78e46c6ef89393f728459fa8ae08e4acbe62651f2c31c513b49db553df2185d9f12e23d6127341507a0a3dfe6e74889c072681955c5cbd2e4b0ea8e0430ba57e159d157ed6a156f3ea0d2edbbb2ad3181b4dcdbaddc3870acb39f0cd9c57024c4870ce01f883ca9b2315b980860476fd4dd4cdc5284bec02e60e95ac48fbd56ba5e71c16b9364bab8ff688f16d61625e28b77db4f7f17612f7b76e3798f95c03e7fdb8a4cad466a36e2f0a3f52cd2251e51432f6a69521846afa678f18f0b8e9b6923b4bb1188d79885401e82fa8090cd7d53d2a6144687172a51e6572f10cd5d1b0a13c8b9e2be11aed72f650471983eba2f1535dc0b3d3841947673bebc42caf1a57723072c3f0a6fb4e8ca8ead49941efa7f452b5128c1d8a4bd58be2174a2349c0b0d4811bc5c51b1898b932ec9d5e91f43e6be20130cdd2a1a675084e75c10e0ab6b8c2c1f6320bed5bc0ca2ae320472303e345f7d0639511a2df7f264cbe7d245bcbc99f16ba304817cb57daeaf0d8508a4e8f431f7b351465902aa68fa5c8c919d158e6a1410bcc3cc8ff50773182a89cf92e5dad4bd7ef09b1b875d11bd1dcff16f2c3b5907dc0918b1a86e341d3cbd05ee0ada485d2cae7e3a288b879e77c1db3cd04210bb2c88f55864b6304f44879f79473446bfcbdb62ecf68020e12d51b7e2398502e8f3f006454534f913778b33cfb280ff6af11da4a0190a74ff92c27e8a93da8379e5c5c9a0b9c617aa27d7590946cef564bffffd3df3daf8689e29810456a6243810e2f2421cf3c708223711eb42710fbdad89b38cf2ebdc78dc3d188c833fe8b4626611c9c373a4b3c08bbc0b51e2bc13a76047a8f26bee0332f72d3889a34d1969809c242d351106c2b97b1b23cc816693fbff5e52b49ddbe2abc5793a96f369318ac083a04b383fa7148e83e71a0fe972a368d910649f01642683b900a7d4abb50013c72ac97b46d3611471f6a87d3ed7b7204c68729557d883469f62a96e911e89752c798a0f2853ea596b982b4ae8dd6d92cf90ad3cfc796d05965b52a91f2e65a1167079043ea34ee9ddd40a19d7e7dfdef008e5a3b0745bbe6439af15b14b07872aed34d68bad17eb2c3f4fe27223222870c5f48a7f0066cbf9136387d53d7f6ce32b8afe50e4fad9a09c812ab3f0770e1e01fd465b212956ad9325abdcf3278a81633b8fdef3841db66daa5d6eb4a26a418697355f3cc448bbe0bb88c6d37b2c31f0aeae963eb307395c065d7321c459dc167b20b9882f47e5c56b8b84f6a8659c993b13b5a5c0bf7cf641ebdfe3a0fafbbc07bc76636ea1c0579078bb17eec81cf79402e009d68279e7506d4d13dc0556c98dfb0ccf3c855cf16ac55626658f47055e77125a488234a347f862ac785f936de0c2b22459cf21cb8b26d8da80ab62aebbaeffa78a291f15562c46bcf813fd75ec18f7a7db6044e4f70b4eb3707de20844f64408e98b385adb9a131800987ecda89bcecaa57ce810c9e6aad1575fc8296bab0e82e36b5ce272f8a36288a8b36603e951befa3c4578390ba6dedc086f83a2fd7181c8c3b3f17384b050b49a0ee78f606c83280616e6a0e7d01e8ed0d48312662f6a6933e2ff3bf89b2d7fb194ee0f1c374ab69334e65ef795d9d483ae21a57115084892259cfaa7a995e7ce72d8869667ca217b9399047bee81d797f5f5b4d657372620d8ea2f08460e0c1f57c4c78c182b517e6ac1fca82f6f5e736feae807622129a7f861df57a09c9b5bcd35a866bf7ba81f2080f8db1456d397d0ece0d0cd29825a5c344ca7ae9436abd65e2f0c290580ffde2ac9a7339faef3a930fa94703e1df9b91f4708eb086f1e44fdef492ea567e4f360596f88547a110caf8be7614b347c9f11a1b503794bb29ba52cf13e4094075ab0609e0922a7a35540baf0fc774517278eb801e7616336123d375bc427dca20a6673344a030dad468aae6260323a6ba75065b192d897c8049da355bae2c6e72a98c6d6a637c26009113c63a9390fd61cec4f583d2141a9654b2f176a6c257885d3a3d0044db333eb8f8a5f60f680fdce7e72e702ac750ce9b221047c73de797910218ca882e3a21c5b78fdbf7254d489a291d089a001e171d6e1fbc2897b33f193e3463ccbfef423bbc8cc2156d37e6fc46577a1132a1bc2147c55d8b320fb3c9093a92af11a55370945e2168345c6173c748868676e4f359c901e68214bb43c883d94cd7e793cabf9469c024c5ce585c0085f6734adc0eac894f07303377bf4a1fb731db83346691a8e100eaae3ff91a7862f5805ec70a94f268f6c4b4b942e933ecbf6406988f7acb525320cbb83f08dd27df781403de7095d192e9833f77f49ef347b4e57d79862a600c790a5ee528edb0dcd28be1b930e9aa98a96e3527adf0afbcaf029dd8d9909650ffff17a6ed426861c53d0901ca0f56fd995fc04008a7eb5a290c5e235a4de9f5cc5a7767743eaebc0b36cec8ccb52c71ef68625c43253dfd6cd2a12170cca252f22b3ea541cb23bddf0db0065a3fbf3527d97e009a4d3f4693b3e5afeca161e0b8dca863691ac7364ff9bdbef4820134278bdb1287da0da96987f3028e000bd93145b071a3582fb980d2d11b57b5a7f063523dc6dc418188916a990f023b01ec20d1ab4d1a31d2be8c791276c027eebaebfc6e8c83154e4e34618e63e71bf851bd8091b8e20f6aacbf9031069ea5ba6e9fb3574ab74a3c760d72340d2004040516f60e1c4fe96b7b61dc0a665013c2259e69954130e24a600814d502fccf0656d7d697d818afd17ab08558deeb858a242f58851649c3644b12144d55cd3cbec6a5cf4a6098aefcdbcd0bb6df5252a9a5e5faf3e9695d7cebf410327438924e63f3670631bdd0e432434e0959037c2f8a085447e563b3bc141bfc14b3d95f66cc0cf1ac0f1d7658426e743742504d482462496d63d8c567a5ca3473b1ae488ac243d002540ca44d1c8817fd53b41231731fbd8f1c36eca96727aa78b6c256dc5182638b420e7787dbc626d2b03493a9d27ff280718d0f376bbf4ac6e576101885938487dd9a3be01d7b92e1559aa9e2c2d53f02f91cbfa691a0009618a2297326c819b2c350636d6938ef01404a53c90b4a13d05a548a402c87d8b4d4aeed19f0405989cadf35f52538fb08f31e29f30a1f2e17a226628b4c57a0fcece4bb0caca823db2ea24306f25a54cb41831502f5d94c510dfb8c1313ba0063b85282bc10edbb50401ddc3b3a3b78799412f20d3af55ff7e300c06d37a8469944c8c79f4187411f24dae4ea3736ce0ce41049476a877264d28e853add467063c518011a45e89baa3d09ebb847059b9a0c68148071253fd7a0844b3bc830f27b3a937e3ac187cd8e8f3d9b3fc559d6105d540dacfd4c69bdce8180a8ef7211e3c2731cf0a6ea0d29f3df8ce2089551465a32a1fec8e9724caf9f6b5960f9729b56d51c26c0e3e64321589bd428e8c7338b346e4ac22bb360fcb9b89cca0be226ba870e6c32146304991433a06a222bba8e49882772dfe9541997fcfaff30468faadef75533e3845f7cdc7970e458855567e68bbd289b5349ab79cd19567fc0baa93894b2d18c4188e445c66d1d323de802cb1a11a251acd0d9498e2dd446fc4d0c3032b15ff8c1301fa474c37f7a646c9fb12883b3450af86a2132bc6de7f53fb7c01f90797ee618ec40642e1386c2a33ae710d2ed616c613b77393be485e45a6bd065a55ba4b0bd7febacafa65477fafc0855ce0cb18124666cae0b747b40243f07f6c4b6b26e82563d7528a60fb4158d90d82cb7f13fa94e5c4c7a7f3fbf7ee5ca1bb43f4aefae641446f00a160fe3a54671802e122e632010f52208b5529271b60780c328eaa1e11dcba426f9abef358139aadad7193bcbaf2216e9a8679489d75c6ccb5766e7355a5144c388eeb661d3a1b138623075a7ee66800709e73bbcb430d2ba48830cd610c225f002e4bba40621c78b67dbce47bb925eb7d9258d548bb593c4a2cb1d0d1034e53d4e79f0979b90b795defe507eaf8a6c6f9347a97a86c1fa5035042ac1abacba4341db92d34b8b80cd509ad49feb9c9bd6e4bab7db0086288d704be611d42ec8b1db752cb0b84fcb012f0080f68cbd8b8fd6c008c7aaab58195b592725b2bdf0faa4fec9a63ba0bd26d171deac847696d21ebfab550aca17c8c3b5f64982c980c5cb230a0ffa18bd6e545e63dbe763b1f8a748a9676e61565b1e0870538a29d9a8bfa545879e246441969c4b8935e6614b1bdbf9b2d45e388413cfaea829489c4b3c8b83462c5c2e2b6d4d35aa9e8bc5baa7436fa5e8a9e88637e4022c06f06bc5106e48834a5f8b07400c5ebbec14d4817d86add15679174dc482a01463ad6387765bc3b23d5dac8b73578e6f88714638316d26c6c540c8eee972d4a22acd04483ed7d9ece3c62fb5b26596004fda624c29ea860bb85ebc04677dd4b37b3e9befb1c8bad691081b349b2695c2d6ccf483824651b259604bebfebaef1b5710226c97dcf9e0803fcec037709fd757545eef2ebc770698043b3bb7fb08b120876bb88e4cd6d678dafb77e31f606a0dec0b4d71635500deb1650f55eca1baac3961b0a85cb556d80466369c2952ab1d9af45676986df38c404ab0304d4950f0e7469758ea3f4ecae494704e07f55ee1c0e5f74ed884d1a6020efddf5ada633448315490f69801958faffd25bc17e1d8e00702d1909e01b6e929a79626b5e0f25d62b3b4a41ecaace592cd78b07d17c818d903e64d50ff820dcf75528e8773b7f77c27c2feb6fd4937be255664ed8e79cdc1e4d50de3ad3a456356b6b550d66c17bf9a01a5cc836b08dd355efd7914c136a56b960754f56021268a710b282c072a260f5cc2c506b3d223465acaf051036f1962ca6ecd3b769c40233ea1388c8e27e6fbff44390389a7af60c7c71f252f993f11aab8b1e74818753eff1c0966c55a6fcf94ac237d3469258e753b08f6de14361d6ce50e58d27634ed840f71496cea0793b80a128414c4fbbfdde27ffa2897c94d63edf2fff9d097c4cb3bddeb5ef7bccff66824fd3b6d7fbe6ff34d9f83038ed2b5c8e81d8156c1687d78e18db2f055087f08043287082b4b061b5bd211d224a1d4eedd93f867b88c25fff88dccf87bdc74cc690d08fa162d9b86ab650cd8a6ae295561a750aa2a13070de131b7a55913ec3dff0f4188bf5748954bea5c7e4eacf81bc17a6a184ac632bea9420901a2d108d1a380d6cbfae33d0342e108d1928512e9f7469a0695c20180df06dcf58d73d1080327a3e3291d77ed418a963466fabe6b6d758370e63f6fe3dec28ad829ebac279bd8c6d838de95e91f98e020ecee5719a5246ba97b8f7572dae58fd0b6993affcdd6a43e983f20c54533237d132dc12ee4f8c8838502e70080346b24d697d433ef0b55d11624f2e4cb651eab2c1fc2896ef6ec0010ba55a8e820383439e3c5f42c6a43dd4362c87a3cf1e50bd6bcb3e9194e6f164c1642129d684a497584ad71ba09d1dd0635a7260ee724078c2b558bc5be82d2b4fbd86175717a993dd06fd12db08085ed7467c0bcd872cdadab301ff6e27b9101f2a6ff4d8ba8704312e2095f504569e87822476261e61e809778629cdb5dc55219a2946ea6374db0376b3f2904cc5dd2b501d07ab80087841334518dfc75f985c4a834ba4dd6e863e4345e3ed98341e5fa42f858b3294dcb80bad954b8aa8c0fe13c8c8f704239f5d4df293ea6c28240240bc637b9070d9bc2d7d02e32cd697d1d8521d3d2e34ce46e7003c6ab88e7a6af2a4021ee0c984c62ed3b927ffbfb26e8c8d6971193d2f1c5c01adc17e631cb359c73015735a3eae6f2c519198ad9f42f9a1217a964898cf6f7ce9c45534497db944d5e99358580c2d5b566e2cec050a3c48bbb12c1a8ca3e9235e13d1444a80031b9d362b287a3143e6c86eeff4eebf207160a949dfbf273bb6a794cc701077326da9c3e2fbc208d63348daea79e08a8a040b10d38cc7da4ac492b6e893325bc6b0d534dcc3a4a6188c6eb580b53f505fdd798893acffd0b1deab8767c53cda342641ebf82986382ab531eeb875fac03c1e8f4418d170eeaa7df3247727f04c7c3dfb3598a3c33e50ded0bfeeda9c0b0f87a0bf7d06ef03c474ba2048c0a2353b7c85b00ea7937bfc48d442c7b85f3e65037fcf2415c7d38d447983fde0323c115b45020b2800c2f1b14df26f9f93c250f0fee7c367c9fa13984509933d653f685a040d3ebbd8f0f91e6ab89c003b579254c758270fb2e7984c0d1c0239059a8e74dfadef16ef2864d250b6d7e33c902b11491d77ca0ae35b33e0a07762ad06037a1244103429a3c19c023c42c5c0412101076d6988d04ae24d6129fc7301cb7021fda98d6733f9d6d968d4ef1c53554ed63959b5c0dbbc4f4430a9a1a288bfd6a98319824ea338414b42bebb84a2a42dedba3793234f828336d83c8c991d386084a60598a4ef671d412e5343367a5daadbf539272c0c67f323e6aab78d833b378144ccce6264d4ba0f5f65343588ad626f51f6360087e93cc8e82986456f20173ab4fe75cfe6ee70c9178ed24c21561a7415c89cecbf6e3d16a51063a45aef0cf534dc7ce32ac8de356cc528a5aa0fb6ee16542842adb549669c141a50ab2d4008777827f1cb8a4c106e3e4a37eac07689cc40192e910d71d5d96b911abb75033b6b91a8ad824ce7c6331af35805076d3fb0aeb20da0432deaa61779b3fa0d5eada633e1ab410bc4e8ac05a809e4e4b90f3005ac1fc6e0d34e9154ebb6bfa300022b28ed9be72e3105bc3640b19595810076f3aab0314817979526feb6eebf7868aca4ad49b1649a85f228a1a7804f22551fe9da5510095888600f7d6abc7303420398f1682242ce20f2b7dfbffd5eb6c215bafa7ba553f71866b0eb4e47d257a1810166960450917f769c6582443126c0241185f7372e1cdb77ee061c6c2ac43a0344b65f6387e83084712137e20fc2a233360ddb18b0cfd10acbcd5ad41080985d9b5cab7965c2ab27a3f0529d9dd6d07c1d6bf0d0be707b5d69c683b6eeb6e6a9e8515e42a6efb75a62fab2361f235fde9080fc7d07895a7d65edf77d9f1fe52ce12c9de1a0570ad7716088da90b9fe7a8527999325dc9bfaa9078197efa95af252bd39ef924e96d1ca8e0fe7cff1d12270d396d09d1b160e9560181973cc70561c65eb6cf8ffe3ad236ee7061c4f02cc90089e10906300fe4c173cca890436a8ae6899cb0eeaa6c99a0fab25716e367ea814df9f2ab2ca5abadf652d70cc591416564165e9860da457d370fbe2f69d219ba14f6d74e2c4cb42892c0853e1dbb028cb49694f4f633eebfc65a14fd4374d5a34733e288e3ab314e922e33104f9bce6d137edf119de8ad5fe4bfcc22b3dbe2d78f515b9f098a768d2c032bb5041026579e46e422dd6640d27d802f2b92bc125b9df5b2bb9fa36a640290f1d4caf2fac484cc06236dd14edc48ae1c028f7a0513cb8c233a0b153021607eaca4c94290d816884008f728fa85c6a321b595b8b4fee7701465e0a887b34ce401ba93d93866cd9ba41c12c99b1ad32d7919df403ee2cd9e4d40491a4c55eefaf5108299b0ccba5c24e16eff6cd88c545eb8166c8374c73588d8104aef40806354900dbbf26c25ab866665f424995a0fd5cc1b83ea7ed4b9c24426875f90e66e8763a71a01e15c60261a70648579dc3bc55228bc8c31903350a34c1fb31e389824bdde9e6fd45e876931f54e988aadb434e8553a6fec51d1d14df848f5b39525bbffeb45ff408293a6fe090607702248804fb3b8400f2ea2ca89af7211fcae41bf2676c57a254770fc9db0cfdb4551fd382cc2628738f898aca16996fbcc76380bfa17438fc9bbe21123c81d5e0fef80c11398cf91c94905d6a8a2a1ded801294f22383fff60e2556c549845dd1848b96539a212f87941a02d6e15c16f6ebf063f91ba840c4372152220b171057ca18ac6e01805915087965ec6c621196193a9a1e285012e516de7da1591166c516f4d79e720d1fb2578fcf25941304843cec79647009dfafdcd3be4d29b1b2ea47c4b8181d8a641f01297f052c66c56f9dc7a0ae8a8547dd9efbf6ad1c9f356986143b7ed6504582b7010423805e114f7142bf1351a616184518425104a67591dc9a5b04de73d42d29021b34230f542f38442d10a1fd4b8b9c080290bb801f7a4689d90cd6d851aebd95457c39e807a13caf6ded826a85a815ddbec8100fc9228e3ee8588126510eaf84bad0015083c719c3956636e2d921b4f2df56f48b029de0b48ac5b236228fe083e031fe0f925b77bd26c6a6053d0077be8c6658728823a3c2b925ad82a628f658fa5f44b3ef8b478b8d51c85152c9e2587ac7f9a7be4b101a953900cbe45b246b0e34cb082187843a752793cb002710253fb2f7aa45e8da27edcb3ddb313f7b094c9f54055d2b61f501351a5ab2aadb0b61cc80ef314ff4ab783a0ae16ec626265eadf8c5d71f89a7b2a936209dd8a3e5779903480b159f6ab0451580983fd2e747f12d1c00943483d09296da2690cd5bc545f152fbcc022861901a4da55b5c74bf25bb09feea946bb3bf94c7b7a96c5e554963638e40e088649163bd41b05988490131af951b80ac287f9aec86ca7a8faaaf0e88ee6ae090972ab5d8b3134aed521416ff4fd5d64871494439f1009b53c67e7f9181dbd68d047b05e64c0b5a89446eb7471a00abc6c6db9c0cd4f8c10b8d1fe93c5da0d2f0c9a59ce4f5bacb0a6840fa2a669ce1425d6b7f3577b6fdbdf667a98581e89de94c9cac8139eda2ef1d769421fc202d869e6edfbe74a0629158034f404437731a2144f4e03293122851c5941a96f9da3a57e59aaada82cf5ae4e13266b0db0b2704ce674a03cc99b0829bd35d88577db9ffa83b2d461955140624d8cee3723c44ec857eafce821e9d5585f6c414d0f90103cd1aac26e5b0b08c69d0068e5b59df96efb50fe27f9ea3aaf8ef4366f8c884d573b9e7dc819239dafc88a3911cd3294966490a404cfea21b803953fa5398b60796bbd86a070dbf95b2b32b75d43485c8c0b8e7cc67cc4d6ba6cc4ff5989701e00d5968a4856bcac825b8b51e9533d53f583990c3ebdbc70f0404a5c05fc604ab73b245eefc092023d94a49998255c4c165313985e2651f1d8cd9789b77cd9ec772ff2e047c46aa9819c5ab461cb052f1e04f5e8764b08dc882b70d2c90241527f53de8520f1b451df0a5de2b1ac3fc70d58d9de121a887ed0be9f4181363c6ed3d346a863cc3faec6b19e476d29e134b7485430c7bbec1949173d0dc1500c2c8df96c029a073ffbc2e9654b9785beb439d203f0b96abd44a63c551a581acc088cf3d18d8e010e72c18a0a8a238a865b74fd47940afeb524338775c8ad9f0bc41c15d5fc473d6331dc94462db9443c20eada804fa2dc046ab03c1d8a7472e72574f924b621099fcd72407464f46100c1aaf40ef39ae23eb92ce237d2ec071ae441c2c7f43e8cf9dc4cdfa1b16336c57a53d474cffb718e75941df08670953fbeb05dddf38fcc6432f2dff5ac80f736077746014b75f77f1188a117bc5c42a69d59b33f76761e6bd636ac5467b723eaddae97917767a9b4764afd5ae239f9d65e9a5ce982e6357564dc40fae15b92764b568aca946ec6685957f99140b098d067c8149877d329fed61511c444c684c67664ff362fa85b76b99fa97cd155e3e94e0ebcf8b22805e28cbdd8b3c8da2d47106e2cda75a6fae759f71e6b6c33dbae16a6f3c947123311dfa6717883eb2e38f24867825917cc6aab32c8a65881e22c628a0d51d2f6aa66897b70e4afa400389ad59c31c530f7153e5a69a6fe296117d8d9d60775020f4234d1be5f5fb11ad42fd944ada0be201e1c1f9d32d42c4c9c496f5d3e10afc540f02913590433038fff6f909f74ef61af49719e4e273a0cfed51127490d9c8831cf2e251ae621c9b3cc53e8192f969047d187e9225b439058ef584b3ec937d948644022d099179b862fcacdbf88cb4c385baec8d5470ae8a43e8b2d04f843cb829c759f153ff2f2962ce4b48d0ca6f8c3187436daaac1f8e763b3acb93c5d36b28ed82571a1dc23e18d28a75fbbd163c5ae51e9ab74736fede2b11c86ea792d4b8efef8d806ed0723ba9b9471a527a34ab574044a22ae4e5e23d36a213f221ae8f0f7c3993b5c3999c2b7b859e0386e4065195d9a1816ad0905950254be89e7b597604c60b385e1d40832e8b90d5782d6bd600de17050476f1492aaad58c33d5c34686b9fc297aef57d8d7a29affcd6801e8539980ab61dfc8cd6474e18ac0123c6794459210dc763d8d939564974fdc4e1e48de9e6a72059717bfdf76939bc52c2ce104d519e4be91691e8377f29ef45c7d4ffe8edd2d9673cb86eb25619bbc9e40df2d2dd678427283b4e69b4c43ac9c8905b8ba26113031ff381999cd07eef14b27e39cdb7c614bb39a7e1e2a7e831f4af16da5dd432424bf35aad0a28da8de7039738c735a39c71f0d3a02a754b3501563311d701d5224692fb40fd43466b81d53748a6f55ae58cd1165ae2016523d31a5a7242e8ed5a11f162ae0ebd0f94940eee4a909620d4154121c507b01d38a9093cbfc3d1324407c5eb5927f4b349ace513f906fa2af708d88f9dff4ce2e78e4fbd4a9d7ec3e2510e63eaa1227e220f6b9ecf112188bd95d213b5e19e6d9fdf6be208c62a989a31dce9c06cfe66e2db644597fca6763d424ee4896f7d69772295ce5413d14525c0b9929e7f8c39b3c742f8d2bc5796080dce4187661fbe67215c35a0a610fb03da8374b0ee051102b9a3b72ec66798750f3c7bd6c874da0eeb750085235baf4a645800c479a2347ac2db015a27e236485496027de882f1a8b8a32150bcc60280231d6522e1d20651d591f0d4cc1c0ce23bb0194a414f4186120d5bc6de6d8140c1e2a8b297624fe124c18056b02c346988291be19e089615a37e2a1b21b1ca01e6c1694345f49196f3dde3785cfd9876f8bd8b5566bf0dc185cd26a5b1faaa41d8e248cf76aa3ab737bd5d1d83182d7456b747cbead6c4494aaaf93848c4d7a80695d5ef46ce766a58fc5ab40b44f5ed5d46d83a280a243f1a93bd0938acb7c0a5b2d1e26770c65bfd2e2a149c30e4fed0ae649fc5fbe306a77fb243e2b01a399639eaa23922e23ea13990daa8922522c6c3e94ac880d8707606971fccdd7aff6bc30fec947b141df86c838c9c2392b68d30ac6d8d3bb8e0a86b319d0d2502ba6832efcc8a5600cd1586d31bcba10ee66080482abd4fb46ea4d3678279c018a7399db99f81233c39e5f0723cd8897ccf467c89260c326b9c41e77136af26a3b972cc61874bf8c4fa6cf02c11df424022a0f00776400c49c1b0ff8c630361b9e3067239549e250dad5be14361bc20242bc0c7886190eddaf7e3e3a9d63ce6734457035d02ca859beb04cd0841361c4b39a754a9d33376287d90647e625951b1eac1c5e64b520509b9dac34e5fd657322142fa119fe1e7eaa9dac1a44e9daf93cfee98e6398c1f4366f82c4899f97c3d4f1b8b627244bcf13c62399a1387a8bdb82c350258edcb5509b7db00ef35082bc9c1cebfcba4d5aa3ef6c79442b4ba809915b16bd346708c16a02a95b220dea6abd819ef265d0e83cef9ecfca8e5b6a6467c9b167d18524e2bbd37ae05fb700aeb5a7f74f5d9bda2f1d686b3a54a254926db7b17427f85de99e442425fd723a0d5f8359d604533f0bbf99a0762767fd89941ddd922539469150aa4753110acd9bd52c35672de04d291c1dd17a0c4d01086ac0afd947be46c289eb8480c0f9ce12f16b9ee07b50d135fb404270aab9db50cb3a73bbbc1df7f4b29db6b432dcb6e3c1b1d261f091e5eb83ad5fa6703565567b9143a2d0fcb79a1835801ca29764144a769da946381d69d642d555e26c064ece57ecd1d6c11f049a8215fe35545e453868bfb9158f693c03f737840bda1e72d66fbbababfc4a3b0280634c47279a531fed08030e50186f01b0d37590781ac41eedf8c50ed160954606e26334f8bb34f8afb3e953390b213b55e6c436f557667f7e87d0e53f09d0a51a2efe167df0b362f9ecf0b4587864b594739c805c61428054fc40127b4a89661979808036c7a8794548fcf7ba76c9d6dea1990fcc159177ce34ce0fa9ec2b6598cfbb2c8325a2c49321714be67ace643539e70efa3998992c2f235c4de5c6e6be17dc873345f5ab9f75d6891de7dea3e3c7d89dd2d13d6d03ddf6ee1fd6763cb8a4014623ca256af7e7f9dcaecaa487e82cf163191d5d54322ac0673119351ca4380ea6d909dcdc5e6953246b0bcdae7a38a2ccb5ecba7521fc310656e6359d155cd6d976689036dad860a40a57f2be2c6e1928871182acb859667c3edc337d3a1a25ffb5f5f0a732c4a2f0adeded62abe871816273726c26d89226a3fe4c612ba633c329a9f491ca92faacfbe3d5a5d399a7e0dbc03af97d0408725fe74458e2a2db05d05c96b03e33d064ee358ba03e3df399962640acb927cfdeffe993716d7e125269755ab1bf500ae9624dd1c881bd8b74bf0b0c49959460af1adc7da2206d70dd8dfdac74415e3fd1011d9dae8b89ff0e04ff1344b303aad860aa2d248b78749548d467b78509e4f92c3e61a2480a36486c41fc9c3491cf10418d6fc9e82c97ea2e9819bd63c79b0614c82a4c1ff79746ceb4b65fea2baa6f7686a7b9389dd0ec2647332a50de73ac11c459aea451b949579cef6fe9af50ff37c36fc8f61ab484d6907c34fb994e3f918e54264e03064761fa1e317abd33d2b74684a310fa876e97630a6667f9e23ea211e4b1c6a494d5a74aee9a7bba607d657d74f96b735e3b0f5e21f920bbffe78866833c079e538ed7f96624d4bf98b94a9c9b549321ebf313e745555bb4d53b4e7cb48fee313b72e64b6acd240d2277fa2e695b538e76ce6cfbcddd61ffec232977ccbabe80df1c229ce2189856afee5741cd37dc6b6df01599f1ae6fb789f14e9ea2b3871271f96141b69cf7aa061dcc0d2b46eb76eb46e71980f9f0d53484a1fe71f77dbc9a09476abec4e3ca089196f10850298cb63502ae7878718503bb50bac7ce8586a7ce6e712e63f225a52cb6e6012da9a3aa43b4c30ad819c30d789abcb9177a902bc9a40c9c079ce4db71c2dc429f3196468a3a7f13570ac5b7e7d63cb1992d602c7822ce274d23c17de3656245b06c6e4733f265ed5f361a9431d276f45f8dfa79953be544377398733a8b3e8f2904452a6b159cc54082a3907786326d1eb864b0bc618e5ba79ea8df84e37a42ca9aaad10ee11249b1a759817247da7351d3c9c7b640560c619f23b30ff08e68b9b18ae2d94053f5e9309478f00a222c973ec9279ab53c7bdc16eecb89f53e3aa9cb38444403ef52d35c69be5a828417190d2fc315e30299b6991193736754659db25f16763ed5e8c37b0c0ceff456c5570a2a578741565b825d17486694306f2b6848069c7f3c19ffae0ae87664bb0bc9c22c23257741b33c5b8186fa8a03125861777e034ddb2e8b2424f73cc9725b118dcfb0065ab64f47061b7d7f45323b8d55aaf60e193cce5c104249b33513480b0f65bb87803a4131f839917c738295de37cfa3b1548e584f4198c055ecf23b2c837321d9c861d1ec439d0a0b9b85d8a268251248f1c329dccbc1c2f0272abc01f145e784d1ac05c1461688a5e2ca8a14ae494fbd187f847c62446fa52a20c97be9415c4beaa69a7b8d17f145fceb25db941fa401173bf618cfa47366b00249b193202eab9f1e80339d2159859333634f1c1a667265a6653b75356c8cb6f4400a6b884e60ec1dafb7aaa79f09320b3053e8d3559b0d68e7cc4d785febb8c0cf02eb979aac9e07f12566fcc2a9cc8423c1bb77f95c4af60bb1fe945222d1207a64a44c07bf8561c7122faf80dc0c12d1dcd9d99beb7b2cf80dce237424df2f13e63f1671430d224003877c21cc7b4e25e5501c36fa1d11056b5b866a958f3b9e20b43f03d5047c11547958962f6e96447e5d1308d720fa774ff67314f8cf7c973667e5c673fa08f6960d3f243d74f777ad2036253e2d492ade5322851df4407180a00189368b1b858417c7207a728befefdae6bed13226e41506232d28e1bfceeefa4949b4dadb720cc85424259ad5e858d8f267c43c23c660fdfe4566745b6addb15be8c03240ccf353dcf0fabf7b7947200e9a52dd02fec5a454a152f00b10e24aa89810ea6707ee057d41c883a5893dc933a704e1134cfdb2768f3131234582a2a0975357c0e1a5c058a6114e0ae5b06ae7c1267ec5da3da86ff8ac861ccc7791ae7cd5fba1fea287c2137456a51030fd60f62db750358c84bb708195afb69116ed09ae13f963bccda18a2dbc52403ee6fd48f4f3403619a30305de273ee783dcf033d69b5d2daa58fb56832d3f28ac9d869357489602e0e52dc5804bc5dc684abd43efd682e71759858a0020c05c8b5188538817cba910d721867af7dde666e9a1b6209a54fb5d8c3f4656a96c96ae3830ef2a2c41b8da17e75e60a8745f52bc5934eeff30685508be9ac185c7d9c4979de4b33cfb266993eb579f21eaa0018725473368a422f0272d8f090c677c8099493c16915ab8f45e7f88370afa20bc714c21b6d028e51b34b3607ebbb00005c5d4157a7b1f4908a9487937e2de75b33805eeb6817b144d4c3b9626f313cacff3f7971a439a41df490b29e3ecb84461b3b8fed352aef0880cb1b16cafd3d618d5e63fa4f09e7cab2f5399cdabadaee92e29e144bb0b62416b958cc0ac4dfa72dfcac18c97cbb75069d0f38149f81a7be1a98a713ab792b4eaacdbc7d6ecfd19d6f722386246f1e08d84b97fe53724865afd5408f2248b721480a9663fa88a55418b77df3e1cf26d4f9cc126fbc937992084745797e5a44d326d393dae0e221bb09a19c4aa23cf8a30a8d30b54e4fdf678cb82df834773af527fdeaa13df84fa2308fd1196985623f755911e1e0853ea2d9abd7d469d82a8cb63836592715f5d8520755b9832430a508fb476aca2d9da8c1bb0c30fa3981b39e7834a456e13de0f19d37c91a241125f35a34077f4b328638098294d7303092435529df441595886c160ff2f558b5e0c342b514f32736a6079c11eaec23e986f50ec9ac4d70f91a71c5587758089e17ca857412b5832f899641d6aedf3209cba44b3082a7ed574b3a5dfbf5369ba002554fdcdffc5b1f1507e686fe3d06d6271f780781a386f472400fdd5abbe9ba9747e9322c562b18333406326806f2dd31636407a19246d2f219e56435c7a72bd52d4ce3aef8002f82e000e5ba7694a17744ace5c107913036adfe8237d697354714d5519b848ef6e39b13a9886f333fc674d44ca5f130d57c2798dd83a30c5f3336d995cfeb58a4af7570a735fea9d66e846693ea2c75a102b63a0ae7b953c51cadb05fcabdf84d4520b692a74b840d10b099f327fd283559775c30cdb82fa93f2762f95ab04584ed72f7d829971a3cb955de0a06046020828b9d32d6de7130ba5068031eb9f055ecb91fee8c58eed8e9f9cb88356450c620ae8eda543c53666c267d839389c32b7caf1e9dff661cd3b842b6e8b64af819de752954e643369b669861a0d29bc5d8242a9caa64d9ffe1bea94687fb2169b6d3a82219d95652f8e78f95ac2b41a57b2c9a5512e7322b526640345b9200ee8f54ea0a91278ab849e943ebb390e30b95f1c6ab3f0c3a9eb7df16b3d6d570e1f99c7e10ec65fb2edf08a97cfe6acfdf911ce92127d0eb97d7050100cbc2a853b91281f56da2a38b8ea14f4f9fc3439ede7d2c1dfec4cc9e5320c4ef15aa351b02bdca1831248d2042bff9656cd2dcd4e619fd11a66586a88d9fc3447f437094efaba76577e1eab4e17c4a56860c9f989925b8d6e052f294a2c8e4a0d4bb535b82aaebd60e5f55279a1d314ad4b18d9b10387bc3694eba406d60f2085258c387575991f709dfba278ec8db674d2e9ea12fc2bd0c30d742977bfbee9920c24e452f4d45169ad7996779d2b2a956a3caa932b94452f3894a59da1c15c3bcf77ec6353ca6e3381eb121492cabe9156b8586d8b5582b33bc53b03c8c6821626f0185873fbc58bbdfb31072a9e4f410fe1dd8d542f735eadd3981eaf29d6ac3dd1323010c1b94e258d27c215c088933f81352bac5decdbafeb3abb243100f18c609e140d776682e93b57efc5db13c1df64417fe89c6b7704b08fb51eeaaf66830c823e02938845456ec538a1c0ebf8f91e6fb31596c57c49140c6370d7c153563b69f935d05ac705b60ffac7108d45f33ba436f4beb6f436563d80341fcfc3cac5e266cf8d564b9a1d1c897947115b09968a23d4e30b707d0f195c3060d6b71d4b367bfb7656258a767185f9f7f69d5bd825384d7132cb5b87fbfe28bffae397fe5c4ef46051e00dd07a9b72cd06f573dacc0b426b42981049a287dd7bd0b1d41988cc027699e6ab550e8fcb445759a09890773e4fc8541236f1bdeedcc7932b8944e19235aa3c65956fbcc78ec85c6b0fafc92d61fab74eb0b2d2ddce333f4289e91791a55070cb537a64b55f53cd558af5dcce24ae30b68b73e6429842836f4b35f9eec1f0df48beeb6fdf5ff79508e830683c8007c230a51dcbce0ce2c22291080850ae5a9a1349436b0ee3ecfea689bd1b3c16993fafe59a850a2c034f02a9a71f66873529b74ca318c8834986125f6795bff498e8a91e726fff3ef0aa3ef3b5fd028d23ccbf3e07a125f8537812b694e828edfd95878b0606db73339a4979422b5d64f42a01a68e1b8a16641203f01443b33fbac2a062a9f0b65270cb3e106c84f502b823cbaec2dc26ed0a8b698afb39c685ecea60e51c35794af2092ef085c1df74ccc3cfe188c4466b7a37e0f1d310e1b438e10b2c7eac248594d3a8adf9c1a68796375ce682d452d32f7091dbdad105a5581c8364013d542e0b3a0e528987c3c8d1d8fdb4693f216eb4ffd5db84e4be09f79b95f1d6bf87b529fbeb3d1327ee4560823ff7fa6c4b1202ba1e4e4794ec472e7bac40a610c451814a77b2d950146816617410804133272a09902bdc75aca7378202e100fe28eedcf19d1d13477206a85dec5cb72b95bea22189ce04608a39f02a6b45dfd3e68b8532076d7e7f6a90de6fbb242999b890612b057398dc0271a9d0be9c0ef2c88129d0fdfaecaa383f197bd731ba37ea5738ced3abe820f9e804b1b588368dbed309e1a62c0a5a3c71af07cc2039b7f183fe40351d6d82a0785e085ad31593ef885a53358b990c4cc2c650462a05a3e963a63ff2b5decd164022e07046205611a485f3704ad821078410afa13af1205963767094a23233c585473f1002b79e8c1f22d12f241c48078826c36a492b0438a81c414541668aabc1c3066eb36da4a77bf7234336dddc5402a03bf0fe3503de1faacbbedc358ce719212712d5dae67c294508a4cf7092648e738be41a9c4a48eda2e4f386c187c851255390630029e21a92caad7d54c9cf048138355a38408d1397e6eb4e35e259c95e271b815d682e7f2afe741052d1b70c0876809277e067c62211a0f3a2921b2312e4bcf4f7aa44d0b57ec3e1c8fdad372e13a94aa9366747c52554bf7427f72c586b8191145480a7cbafa2c47862783a41a0fb2ccb1f0036d0e9faf5e5631a35668beab75c36fd5d6e827f05a6a1e5c714f3c493870194d35993218a55790d5af757520d484c97bd9640bd7422bb1d42cb2bbace5c6f5fda69840d95b4d4e097e657fcded22a9d297fd92507d8c0ec7cd9371b704133acd1db8bc6c3e1ab79f15e985a12349ed035bd02ea14fb539fe51233565156a9bd6df98626cedfbc934b9257554472cb75e6b35c986a20b89d41e4039d0f2615345015fd335187337d48d43a2b32094087f2466356aa54b0099104f0a95f92591cae96be5761dacc8c28909ea7b0296ee4dd651d9557177c6fbac2b549f9904303f652d8e808b85051b3078e68c524dcb64f4b61805f697948f24076667af59af26696e0e4cb59581497f9828fe61a48e547c60f782a4fc67e84fc9ac9784d42beb0881345060d544fe5fc3cb0d2846e732b3240caffcc60ea17429cfc9277e6515170afc4b9ac4b4f9c93f81b8e556af197bd5bb16186b690bc9f1cba7d2a683ea5deab15d10ba4d09c66fbea91a03c2242a4e1a0733c2e029695a8fd10b18ae74ef255c72a1a9ee9fd161d3677f3bc4a2ce9afb6233e0b9f9aa8b98325776be92498eebf9b2e4e443bca3af48c4c57ec280a68872e52edc9e76caffbc2d55b7033161617880cebb31241eeac80211643066b0cc04e1724e48dbb60d545db168a55e778f902971ec6de93db1eb56600532ac31f28fe4133ee902d2a88c2947907d036080d4da0d35ec811ea84148a98645f7ff9716672dae14c0c0561d895b5c17f13ca97eb0120987c4c9dc08d0e10bc86194f71e7ff8b5df8b82253d08de99f58bde72b765908d4ba5cf493dc15783cc46e435e4bd14a02de3bb1425861674c903a3e884e8d703294f68ed54e7e3ea95038b2cde679e91d7b25c33007a41acaf75ea2f0dd5f75ff8393fdfde6bbe300d66a95618ee036f975ac47e8c9f4313544e0f3d2a4287b82b1473368304d21d1914f5a92e2a94a3382fb9d29008950c7f70a5106f36c910d7d2acda31404a656d92f8186f27fea35910a43e22fb2377254bef888ad473091d7b51150ac99efba4b1b6c0c7889c86c27c9cead524481344710a20e5beb252cc380479677690bd9d633141a8dcb3b9285304f43d390b62e37018900e3b512871303f60e8d9cd1fb6c054b902417934e01f3660b0cc8886ceeba4d13cd7b6bc7951553ae520068b110bc1cce3711aeb01750c8c1f31de6ac7280320135f0fb5197c40f603a46648e7325d45e3df7e21cfda9d63226d3d5cc2d810556150fcd89d32747e1913d0237eafa57ab7646a15e1d0f7e928f19cdabaf10c089226bf661f41da524553c29379b763949f8585561392d604de93a0194701a7ad4824779aab316b452c3d61245832b28f67b7116d684d5ee495a49b4676028abb3cd18a0d1a44946aa0764ab0d592f2967c8090f4c7b3ea737598e7771a59d6fb5527eede2bbccab8231304b8eece0870966648aae87450c12a457ca021e76a7c0419551e9a6da4b6cb07fa53cebfab1d381b577207849e829fc85b26aef139d3f02ff6a55276b53ff6826125340c15fddf869757b7585e72b217e43af610c81729876da5020a81615e65d4b392e02228d2d498c301d7be3350919efc6283a51ec580136abd7373844676835c2acb575b4de25ea6cbc245bb31f394a93e39cb53c4cb9312431f58c4b12cf24b3e399844f02935c1b5cf0747ef74256084628427e5dacd6a29898b1d746b20bdb7e6cbd32a00d5f02139d3b679195c22f74a63dae4774b4def34752af12978848b33aa92f47632375db472f05732adfbb2ae4520e6246081dc2c79275daee00d32d8bb1176d7ddff20700b2ce913e8df552e4c8610ccaf03bcc734c04bc3888264fddad1985a9cf389d56df72e3335e08b265773dbb273742697d6ca04f3c1e7411aebe8d56b428ef06e3324026ef17147e11dc17f68635cde19596806996c501f4b60b3f6fcdf898db9086ccbcc8e37d87040534d3ce3276956c96184a04f2026327c26ac9b083ad4fa37749affce8c434f65de20c83ef62926750628674c9c2d8d32fc49e3bb0461f0486580582636ca890fa84f202e097926584d4e496a7f3da8f5a34dfa565df0236b5a8aaa117f773d2dc3899d9382e9bf5cf07c429431fc1392ff3460ea28353cf8b53ae210911b72b0f70ae623fcfda213b4d5ec7de833dd1b9c8af60948a422b7a9c1ced2a3151c3a4ecf657f91ecf41bf5a825611c50f691d1ad49444dc6a8496de21b2568062f6dc6f7dc9662eb9d3302d316282b22483012d595be867f40f69feb582b32339b2b97590a509722cee918e409e8daa367d5851d12e23264d3f6d073d81c25bca2c2f94993046b571b3a636ca93cee0db21a27a90836831d103b22f772a06ff5b61e4e074f00c0cfb8fb15f108585960cff591eb750f91365622f60a6788b460b253117e2a5055228272f06c436e470d56ba961789dbd2ace13c127e3ab9e600728ed4acc51c0e04ac2dce67312a7cc8903b5c0eb3da16b12a7f1dc40d53f6242491a940f0990cf035cf4113c12edeaa9533426627fa8207a78af9751f09e4c9c6fd8bb039bff2382a027b890b7ca3dae0ed1755c202088e88e1ce8384a66e6a9a4b46261e0de6f02b91e7e38a373aad6ec7dc9b08cd31b0bca184963eccfdc0f5f321c9a6760b933a73cc00cd59c16ef1b35506899fe830f02f00a2894948c36f934d6a7ce046558d0fc992182c9ef8322cf96d38da0e2cb9561306a7b8c48ac0f55a54d48aca0ef32a8dfbde94b278398bc645e2379dd76ac6981b2a4dcbb994c313d167d6468a27a8aec462031529d543d7be62aac314d036c2dc252b905344a8e278ee4ee181f3097f7edc64d289b148e67b3e3593ba59eb2adb7ed728d464a9cd06de9a410c2d2fc9bf31dd4897ebc9ead42ae4a8d201cbb50d207a7429acfa30347616bca4f497715a2523bbe2f1cc1ec0631885ee1352fd4e5885a511f85674a423598de4df2edc6b9e956f1ed77426b83825d444e461ec128b58d25176648b07f82e6269aade0b346da6781fde324a19c029e543fdc5382f8557046ec60be8e9fce64b36d20f9180f1ffb07bd2878f522628e6fe996ebd7cae35a713813d0b2a6e49b4b0d2fada5f9a6645342ac876b2aca5ea064fd13d9652b007a365370ff13f0bbbe7dd010fd7b294d091ef0de48e30193faf6d0c55e54d3b9adc1310ca826e64261755e4d5034e56a1f0d1aa13fe38c5af61105430fb29a09188e6bd23ab2b66819a5c20155f7d1fa011de8d6240e76808a8888ceac6e5bd67035e9711a26a117d67c5c14762f3f94c1e0ded36adadf680b3a5b28325331c9ffde477699d0f03dfb813176b8c4e7627959d1d877e474a738993f46137ba1a297b2534e75e4f908256ceb87792aff2a1f11ffe99ec0eff0e2aa0e4262715de4b79833d3eaf5226abc5846bd6cf061bfd9583b216cf248abaf1fd6070913a3f707ebb26ab86cb0b130871b2f616e8f5141378eaee594765b8cf649f80af3fab7be17aeeb753aa64ef8ec49cd32fc20dd5d1dd96c27d3cdbb581712da6fce8f987986f0deb441f3ee751339d439cfefff96560b11842f861d5354ef28079106adf7eb8088560e4a6b22074a849a588d5c75042203972e32b2da77bac9585fb23d7db17d42a57bc60b7d08054d953e64554de39a04b9c24d7945ef22d7cad9ddb5856b03aa400feed5aabf09d1bd60d06f089516383c311f7f78ac4c6acdb68c419e039a6150bec2e09046c2e4995f5bce65dd36209c3f4e2cc8054292f9f009e5f2288da554c560d8d024430d22d8b762a16d92cc457243442adf25d219a0fc89b78a293fdefeccc77ebd918629a947aceb601de8258fd35ac83b5fe228a0f328dad21b3e75efaade1081ec3b5a89e615f2489d72261356f43faeb0c0e02484623fae1d293cf55210752c0ddc40265ec0bf90613c142577b0c4e5faac573d715287bd2c21454401ceecb45722f1c144530374ea460de1e6fe0c3c3f2b3c9af1ecae2f2cbc3f5ecc10060a080a26bbeb23e1aed0ddea806df98cfb8fca05e9257e1139cbe7210e427d6af98b5ff0494bd4edffa01ce8d848efc1e34498b8a9e38e8c0d05e87fdecd368ef87d9845d2b0b34706cbd67c2331d22548aa410e88f2a7596d4e2df9d59d165b2bf4fa973386d8a6a7d15144cccc38a436554e0399a74a854e375d2faae9c987d99cb436e7da3ba0bba64821e390098571d8c2fa65af64681e570432bcee2003ca1c0b2d0115e87648948fd30b6ec6edc1c05bceb6d4154fca13da8b4df9208be4c1f33933efb2556f88715ff7facd00044defd8cb25a65c1abbeeb4656146fab35f054ce56ec50f1ba0c512c52aa02e5688c5069e12fd97d802895d459cdfa4c06c71552f75c1fef78f1b2e5911ec819a004ce2ad3c5548b49d52e09e9dba0907351bc4ba62911eff30bd56a95358cd3fa08f3e9201e85069855657eaf5834d5d52544248e08d387d6f75c58d4c02e2c021044fdacbe06eb5750c4fa1d18631af2d6ed49d79098774d4f6ce2a6e44d3d27709e05331b8de89c57a76cc1c0336b35a70cc4ccf58047d204dc7a456bfba0041eaa8e35992ece03f2982f97fd0c5d640d1c550c4f8b8ca071dab771364b32407a967741b9ad744972005361f29fad3d36a36e7b7f476bb8b6a6db48e02beba9fd4b2415b71216ce091ec825515da0251456ca44f4a2cfbc00edd6507a2f44890ddfa91d96c234117f87014388b171ada3bbec39e8109a96cb0a1bbd6c9e2bb6223b64762e0be0961b7e1805e82993ff9dd181f5d13e5ecf52d67a763cea9859603f27b97bc54ce62abdd9d1e18a107147be07c8b53ef2488c208ba23abd504c51ee8796b24ccda856bcdc24cd2a82524bf032d74b502d14588dd3b4ff3b7e1bba334393ed6984406714eb0a67192dbee2ba5783f6b2c20ccc7f4d0f6651059c548f99e65287da537d0a7ef056d5be81031ccc2229290053dd106232a517d40013143ed9c40a191dd8dcacdbcd7a85c0ff0342ec32f8e2f65a8ad45ae53bbe4500cb9ff783cee8bef7060d1c202354082fc8ef0e8fda0b09f29144210ab08386d30d85872c603442a19fcea13d58b25af1abc43e1440c2c1671eaee9c9b8fdfcfe78e896a0d3a08917826db2d815eaf9751caee6230466f25fe00ec3f7aa2ea033698f68b830e702c170068b51c3b80270ed68831eafe352a5bc4d5f0608808bf2300d4517f620976234e846852c629bf5c1f0e836280f836d336e0e674de2d8e953ca58ec744fa4ce0f6166a6519d8881c37d3ad4babda1f1f06dc81746a0ba281d85ff7815c1d35d417fc7a2997f8120d1df0ef67dd5ad22ad6668aec5ad5a2d240254a74faf79d9f90ddec61d0f426bb1828868af387edf13a3e87e0409092e6564f80370591b80a078a2479aa6024a7223417141dfe94ac6b0ba87632773762db9676db70b390f0a2df2f63501c39195c3ac463f1aaeb0e3c05111217505fe3d4077b33904d06fdf348aabef16d160fe25bec9db8b6501ae4cd50fc9d038904b932fba76e68eac89a9e675226da5bf35e16426a86ba2398cdb5df39730c6697d5b0062cc12f256bd78e83b295be22181ec09280621f256113204f8e34c49acb713f850b5649eeb55d9580aca660db1ce28743ca196f7b26afc5b7b6b6a67ed140e4713c37b3197010166d05a53d27f5ad16b86491753d3e2f391a48359c400a1fb9a7403735323392e539aaca2e5cebb545a5a32017433245e5de1248a1d6955e09edd3114a9a459482f356280aa2bb98fae329aa57f85ab969fc55c11798291d3cf59b994dc555557b5795c9b32ca1b9442d99c19210aeec001daa39d94893d6a1e29a831f34a613c8da73600255999931d4c7390e64ad457f9b602e980400572e39cd1b88e5c9f052fd930db7b13e5016718384b370cf7edd755ea75a73c736436a19d03215fca359f663c7af0f16818115b9033cecb7072ea4560f21de6d44a0ec1abd6d9f3af81404a78d6bb666b5912105d921fc65febcc9f272db2d30002b616d477eb18e714866b6e41890aee1f3b4061dfe4b3d23905a67a98a882833acf6261ae426d0b4344795e1748248bc3e89ad48af708e13caba08cdee3d3b94dbdface923b309005973861868e7c9295c34521e0906ad854e2a1419a4e5b10278da3345fe65ee728c81a61dd348e2e3604680285d2402085b52009a98e1d4e61f75e17eef53aaf407bff9bed5b07b891a7030d7eec01a3c3289d749a6adec6f78b930b702fbd3a8bb0c081cb2e11cefa88c0c0c3e7bf036aaa59cfe626990dfd3e85ecff37a0786e25060b91933a266451ca90d03c73b174c7f00641e2999a7034344cee08fa7d44043dc7db3676a00a5dd586c4e6fddaaef302db3694e511aaf3302ab443525587ead793bf474c7d3b6e4a3192351e83275319fa6cd042f962cf82aadfded240c0aa2dd9da2a41d60c8ae75441575a7d1e638a797aa512c20ec2acd380c3e77d321660ef8eacfa32a4514a28fb601e8f87f357eb7d82cd167c45c4969a3af2a0b6055d046d866c5d9ad8920eb90e64d7a2d4734713c0915f12444d9782e768aadebabe43373fc139fbd605c4dcc64ed934c2e1130b2ee25bba24fcdb1d1f5559a55543bec4c60fa68ca079ddc53e9dd7e4f447052dcefc61ac20f8c0da7de6dffd62328740522d9e5e7e8cfb119f86009c0a0c72dec3a757ca64c64495d9d16a1550a3d74bfac7aab8221bfb6163d45b0a53009fba81f259d09fcff4a9ee3f30b4303afd87a23789bf1a63ecaae08578d09190ba5f68de5efe8c920a922c40373c139ad601506b2f4bc60e2129b78826b17ac5d954de1ac14f899a603bcfa2ca18742b3d84a66e23da145b4a3c42ff5c773a62226c8d6a8caeb50a208908222b2f601ef3c37599a48fd505bab6b90436c5cb2189641dc542603de80c1bee938e68a0c28b48e6857c5daa05ce25c4c84b589b3137b280c1df8adb5f4417a497947f05217dc888f684f6873793d12caf19dbed38cc967e408b900d239694a982ec62b7abccaefeed39feeab13aff90550e70e6e5b97c2f664e8b09d49df95b3729ac67ae5086f03261c99ce9893e7b7c1972822bb50a06395a4319405ec18a632b53ba958dc67ede821bb0ab7d7b36afc78a9722eb3cf92ce6619701f55333e845b22969d4a896b8fc739ff61ab78b66242f3041400e8045eabfbc66ef5c4082b2c0990b31073c1332480aad092714d30d082c5ca6a74b9128c413c873726cd010ca3ad4dccd442fb8551afc2fae2ddb82a5622677245814dd6c37342af69c895da809f94c4db3ebe4232ee2198ff218b17b8fb9cfb36747aafddcc32a02d91ce5baac5fec92107d7f2a891baa738867a0987a92f8c2407c8f54d74a05e5310a2da05643be465f374e90f8324638d86e7aad04f8e78e58532eb0b1baa849183849482f33c2660a9de2f5e50b245b417ba2ea706a8ce83c55543fecfaa8816301d98f187670088c9d9566cacc2941a490b470867085ee2161786d1177d07f673e2c0a8651d868a4ba2b976e0f2f8dd06f2281c2291b6a92844ac498f929687965d36d896adb294ce9c643d0435c05979d56d159ada4a441f6101b69b8bdbc114102f63bac7ab7ae99d62f3237d4fc4fefa46e8e44b0f64c847cde1ccd209f135b8c391c4a441bd2c2813888f62b903e46e6d6ea1f835818cbef94196323d420f5a89ba871b54982115415735db870d14297cf51d844e6c059e8fe85d6edeb533217611da6b19487b3aa91d41a006447654a4eac0a0f286982eaa3f11d1ef4c4309ab532bdd7202a4fe76543a2c481d450055fceb279621e445a4324b4a30cb73bdc817d6e160dcd84ce914226b992d106e291267e4824eee77c0a9d465bcc8caddcacc50b1b2642c8448e5a8a0f826ee8300e2e0890ba7c84500c22a0380a81c759852983bc24683ab87be4814ef38e2ed55a3e00400c3b505dd96238d178af20e18b40a622bf6974d7a3a95c01157dff989f8d2e2c9e75312064cbb8963ef7f34ecc400730d482c3e4a9aea1ba95ac8dfd0a264c43694b03542e04e9034f85736c5a14a6741115453c244c33d87d9829f102fd9f2db3546e166b3fdb1ac0d4e7f49e0e7cdc3e97e9b0a9d2b968ebc04edb20bfba1da7b56e33d8a51345864119e91be84653069dd51201cf312abf895271e2a37311dfb3a9d16df93f669992c6dc4b208a62254e84a5b65377632bf89ff7083db0ff0c332e9064eaa61b39512645774d0e229333b48aa845b4f4eb8f93b8e69b360db8639100db2b50ea1d4b0c76b8b33c4b620cb528162665dbb716581c7759d6c9d2cd8d631b594a474a9e21bc65c048c9f6716cd4b9aaae4472329f49a7b9bf8c5b7dabb606cf6e47930f0df472f604a2a62e03118d07ddc31746b9aed06d79387de62e286343f00bb5fd56bb02373d25c62eb8b9cd082689b50bcd40aae1b9782406b789695b073c271d4ee38b94de5c7d178b8ffeff91d46eea483089603f37804bf83962e656543c2324689a71d63423f88fc2dca29e319045b90c6050f19b74da2b9845723bbf2924208d8b65874a13aab6805db757a398497e9a31ce5f011d3076e49c6a271ae7dd147418b061602753ca51e6a2f7f4e366c2ff4b992719d174bf3c21dc1b286800a6724b51b1097d1912d3ca829da55ea9850e2fc19aab11bd571fb743fed9285790b98402741110da9e3899321f0647c243d5cc2c7605286a4475108446b0d46c2aa985409b083117aaec0fad22d080401735358cd4554decc2d09f7640b8ca7a719db6b129f72d298b6381d24f5ec83e4e9e861300fb2d6c00f34fd4d78b619383106c3aa5093274bebdfc230b8255bf44c49c6952e1414594f66af4361cf24e1734a3e36553297e296496af7d8db57c5af27243b959d51f20c7eb4654a0ea2593852adb900b5c90c17d03aa8bd8e6b9954ed47653882157d694853eea42c8cf59535e1a81f7c0567c45441d5a9b4fdd7bcc2ffee32106a88bf1921d66970313ee34652c7b2db0c41fdeae14d3c0b93832d497bb572bc004cbccbbbe704d10f2c7018fbc2aa4b7ff151e6eebd78d87c010c88c11c2e799e9273ecebcd3b2b4d7eb3be350bc15a8ee8d648cc49115a99c775ad63349caca8e261316b716a94b99dcc9b00a72dcb9cd8448e470cf5d90e9c5a6883390eaa4bdd84baa353b949145c98e6ec8b979ae700ebb7c70b0a2f9a86cd3d94bc6188fe4719afcd7394afae850b8b8a57c1ed6ae883a520084b317667408b69296bf68a92c51e0b00910f11c1a066361ab7cc40d01d83cdcfbad8efa6400c7b2b7df9cb84b6049808a74d860d18fa8f347274977067aac57790114c6ab32bc74e8039b06ef158cc5ab529e8b57b9147575f9885846d0b365ebd0e54b3d60171f0bfe7ffc2445f5c610b8d00cbce00e62b43e14dc55d2a32d63f795746c0d9c84a09d19b0720b6522e490447cb42b4619c3fc12d125e0484836180364b16ef6dea3403c74d9f92d93028102134dbfecc0f17c46a4d6317c5cfa69ff395aa84bef9d4ca8e4a81fe5b9e387e8663083d098f69e666294620842b804e0b10d7e5f0fdb2c30e7b19c5d7ce9b0bd54f5f5c4ef25fd8e83d22dd6201cf5008a0957ef1610e8868eff4b34382ffd76a5ad167718a0970451fba13a108728a21e040e8ddb9159e2a4d66990f6de00e9f4a49da729bc994f32fd21ad4379c602b13edc5ece29cd88c161fb53e7b5bf221fbfd1544b408cdfb9b69738c0f444c38af947f04eef7224ae440e85ce81132ed20fd6a08b7e7c7a22d5d731145bc1af3cba061f15d7ae04e76aa435bbf6d5a20cd051450cb00d054926f86e832992c800f82a449879454bf11d111e40907e146d6827956a0d0b635c054e844b43b8b8155af0a47b7c1b92f9c177f588afa6a15e7f2c318bea4268bbc11af70604ec9be9f6165d827ad68d513cfdeb919beebfa6f2b92d8692d4abd2c7e1c109e6c2513504cbbb6cadb2dd4f45504b77f8fa26581d6a7f5d18250f8289d705dc1d8bc2d46c8c8b56a3b6ec5ba60bfe37b8cfdab5e744706f4fadc4814ebcca050dce2a784d8fcc6c994d4854f977394212aaa46f915d9825b331f921ec956e6e5a679d0b0e4a4a8e3b80ba385d93016fcb835f415f4ae21fa8685c8df21ccd8c4ad0d20dc4cebad442240f415d6839c597d10f8117d059e0533017288bd86258f90a03710924b1558970333af678dce4bf782cddc68e6c849612a8cf84d7ee199579676266a383fa729b69c1bb3fcc99e42506f55293193accfea1ca602199655b13b20cea5849144a48da0d2bb7bee180f0043134c7496603a65a4863ee49c0d77b724096229b309eb6101274202e2accda05f0c0c3b034b533330c6ce3b3149bec663e2ff0ae78e2264739ed3f79bd4c06cfc0f54cd25f655cc5b857b92be8cc846f037fc398a656843de93789529a672d354ef5053ca722c71a628119cdbc89539adbc011256f4ef4013751bf94622331b18755b188e32079e0059158dbf2fca29fa918ef5096a93e27ea1f9e7b9586fb0db0e4ce103fa71927c5a30523a4a1b557ba611d5181049f87b5d3578efeb15ed62555c45c95e739dc48d5d89b1a19346eee9649720ce20be71ddf7cecec471e3790bac553678cbb848f0a2321d57063ac65bad11fd0fea13d44d12222989482291bd690760084e07b0073203ca3f72503621eac4f840c0f5af379d7e867b4ecfa8437fe6d613874d1a6b4a8fcc7d55fa56383125ac63d7755dd5d0dbaf32f63663d6630880bbbaf247949a2e65159675adb56fb19bdc7f89497bcdbe0848763d92f65fe7eeb57addae6bd332eb177fd8d192f0b2d30b76e207f8e362e211f4b1711fe311d1043bc62b95abdcd33c8d5ee2499df89c0d2c184a1d4b7b9e3427b566fea491a86661463b9256423ac16234e0e803a683e455310fd8ea0f9fa828ca96ef590587e2cfeaa3ac2af0a5a2c71b04499ae9ef859d197961cbaf681bb1b634e92f866dfa6d03024aa53e6f67db7fbfb36ddb7e1b4a42b76cda4c992933fdd7ed4c675baddb5002bcaa3c0b226902acb7d62f86bdb5d6643299be6867d96fdbdffc75fb9a724f19f8b587d956b4b79c846e0dfb2cdffc25d998cdd56399fe35bf7e90846e5d61e79c55aa6acecf3dbab027dc39d20eb955b9cf97347b8e30aebbb95ba77c2eb54a1527f6c42e480487e4c79f28a0860c410d77ce96b342183d9797c72eacdcf4da2aab8fd10cb3f7fbea150e81e14f5da137037d78402f7e0761849ed72a6bf4aa2a75304429c03ef62d60df02b744ca92eb97d4c72e0da50e108c5eaa8a1504160a61bb7d485eeae89fba62c04203e0eda6674c8c7388e34044fa56ab351d0825c4e8b3aa755befd65b5e1c41ce96357abfb3054bfd59f875cb4737d473c884518694f51656cbb2f4071f47f4e1d5e73edcc1e8b6657dc51b96bb68647d9dd6b2acafea4c22ddc15495e538ea43fdc1b774a53f77aaa30a28a594d2ea734faf023a84d0e974a702858218ba6c8f12dfa330c07a9475fa8c925616ad547644e05aa5cec49565d518e3ccb9d785510cc32686490cc3302c6298631886c11a42955583449905b99910d72a8d6adad4344d46d7340de20bc3988d0eaf0b5bb7d6420cb3f6664259a669dbb64d6bc7971046cf9ac3abf9b6c19979c41056964bd5d0b48728c8cf77fc32b05d6f189b4c27df3dddcc8c27b42324ee1cc8d309e108899b833cf06b788c31c618b5bbfc19a7749f4b61be54b992f4c481982fb0d003949a77ce2b39135ed9baeb6c6c6c6c6ca2436d03521fbb00906a3acddc1e39a7ac8935355e032fed83e1749a99e1381a9a1a35f0c6aa689de130e73457484d07d40175b1734a4353a3464d4dd779363637372854aa67e762685574deb821a35f35375046ef6cf00dbe12dab0216dd88836bcda6442379910148ee3407983729c6843dec09104ae40638ef9835be24ef5ecd41c570e1682bd317f292171dbab23f3349d6d07e6c163c29b96f15c1e8b5d3c3cd5e27158a37d45fb5e871107077338703e5b08b5ce9183f32204f403c210421041870ecfe33a211d6ec70e1e3bdbcece8eb6b3b3b3b363632313ba2184c3a3f615ddb89109898f234772dfc91ca1fc5c177de7e0703964f41c392aed33a7391c73721cd29cf5a633a11c5cce0de17a16e48a0075441dae83e66428cc9d5474284f803d4f4767c70e1e3b3f42266421beaa954a553435654c792a958226ed2bd2e1e9e87411da910989bb0708f2b83c78ec509e29a3f3c0aa7d4b3c2f13e256277ed43a3b78ecf08c20a343c8b343661d3dc6e831422b7ff59bd0dda7363f222866f3d8b163270bc22313a23c3221d72828f8bb0e631129401581e107459e11a4ec4164fbc748d4790a0001c884049009c11eefa9da5734c2087284e8b0070a85d4beb1a3fe260081262c8c6e44c6996c77aa69a414d248e98c926a0de91473ce49941373a0110906d260a7f657043f3772a31d03f6f73d938804c03be0f8dfc78813638c313a86151cb2ed1923b7a5ac01f5e855abf5b562dc75305715dd441ec444d8092184dc7dea5cdcaa28a5d4a97b2c62d239e5c3af43f2ab6ac99773da9b1e2f289d93cea9ef3c83fec420c5bf0ef94f2d44df7277cb5d4f49abdc3306ab307f5214a8be728931a4958ba4f690fc1bebc72b095b0f777c5a7754e2db7591d4aef4d07c9737d88ffe0d135809dd951ea25aeaa1f83ec3d2972caa940942861bb24c1720e1e1080e2e3e40022f5d0081cb126362161d30e388279acc3082262418244a62e0a5035998a84103369c71045a31a241135f9e30592209337e904598264884e942c90c2906121b4c0cd185164590994195320a10850c252652c2fcd0c5bc922305195e6c20081f34d1032ae6480f61c0600b285ad0031371cb912d3e948004484835f8820c19908104298d1ea028230c2c494954073598628a256a80d2039931908821a3054300010aa32f35d030530922c8a032c4132c64446024c34042811684e940131fc22c9d012684230f0021cb18198ac082e6e0c8ac02c9092ef0618b3159a0e10130a81847ca78312687107421811552fcc0831eca50d9e28b333e3066098914400105151a7ca1e18b1c03893efa000f9618d3449829611cb1028e61860e70b86203165ba8902408c3064e0091a481249070010e8a8408c1126472c032441857c0e8c10f860801164258018510f308119668a207625809410c6e9853e091a9a50b078a30438a1f68918418f40bdc12850cb4b84203661c81c313934a1a5794a0063878f24412497c8104822993c609c68ce0cb1667502fb48031c313204ce0050751ba4092c2165d6cd081862852ac50a5235420819409242d30b1850a25c48069a265a6801a01c41622e822461844d01b1c992f80687c6982e54910aabc400772043f88c00654b4c00a0e5ba2e002890c63c268228827be58c2872d907c51c20834c290c18a3368a05e60900ca2f040822bccc0e1055a403252762042074fdca0c50cb309241698e2041fd042891b82b0c2060f38e38805629630a3075e680d669214e3e3012caa4841175e84d1046a11812aaca46183204ee00507ba1c296201106980b105193a5cc981d200491454ac30da010f1ae08013740631d00004305ca0000733a2805e661648341bcc20450b30a688810697b802314a3ce921cc0d42d02d47240f31474c3027619eecf81188f8e323c6075a01e365c7773137a72c29fa7895e84379b0e3fb951c36ddc1beb82cd378043525c09f2bf952f46ef08071fc3ff635f237778d370229c6ecbbebe6ed7e6642366f6176f3a7fca5f6e96f7296bf157676cadd679928b5b38f9910f69f27d5f8a4d3d1c912f87327950cb8c6bf67416abec607c90aa3973d8ef74eff383e6234faab1be37e467f7e0c247f6ee898e1682875362db5335dbddf6c31a569ab54e9d93769ff883ab08a8d118b91d43f5ec7bf7127db7abfb9827dc79bea3f586503f9d20fff127d28fd1ca3edc41dc7cc97c2c09f4cf912944792ab9faa554b4a86061a68f0bfe63412e44646bea05de92fc85a7de7db9e31188707f8835a64f6e3ba6ac5b8ebfe33b8af8d999023f1439628367d0c6a195221f14396a44d6b75749d4201eae931e14dcb2e10166310474675fb58f153138cdb6b6fb529b0efeddefb4375fb6aefb5d785b3bd32cc6a1b5c98fe946127d3fd7ab7ec3bc3aa1635acfd479fa86947f7de7befbc990d2e2c135a68616484e52f67635fa3cf497f7557bc4df0f49346e3199a65cf68d9333371e6bf222af42b38a33fece929fb36e5df387f38f6fd4dcb593ee2a9093108adb5461b5fa6ea4e297dabb2acaa48ce105007faa81fdfc6c6042f6c19718e990026ed6cdbf3e3c76967d3b2aba4b28ab01859b08108b344f73697aac9356832cc33d955057d4929a592beeb9ee8590f923a99f0965d8b51ea4facfcf564dde34fa24f7d8933b5397fecd9137f8252330aa5d78362a27814291d8a01dad388cbf9fe010155203cf9032552f4a9a46c229bc826724acf8532f341d18b3ef2639229e22cd0d81f8dae2a897434f6855536d4017fd5dbc7feabd087c7587f75d39aceb47fb00aa55a89dcf46da5844aecf38954cece0edeb4eca6a20d49db6e94e6bb1b98053f2adf649bef724d9e791aeeb5c76ff115b072fac32793ddeae55996657fbb1aefbad310d6e81aba66dbe38f3f61968d31bc7dbf313621faf8e3a4fdf2a7fef7fb7b47bad8b24a4846f5dd49f4e2e32ff873244792187c17945c3fd951cc4cbc5d2cccb9aeeba29452eb9a3e57a67f5dd7755d9665ed58182db263557fe5b96bbe95b66103feeac4138a65f1a665106e1d1d1cdda6bf0eebecf8f6b2177677f6ad738b9d1ccfc39b96edd8186d88db9b18d9d37f75c67f382fc1f4f4b993def8b7b75b166d599f3fddb608ecb2bfa2287b28fb216b6b96bdbdaeebd22eec56edf433f1badfeb2f7bfb417263d7ea0f76b96e3fbcdf4bf06d6b6233b7aca75996595d62972e996bf9da7a5d6da30b2875826203287655a16f3f888691b5828454a7acb08e38dbb9ed9d9bde5d7f443b1bff7d7b73c5827d63ec462f76545bfbd6d3fc153931e9aa318d556ce6b4693965d921eb33df9b56e464574af5e6dc78fde7f4f70259a0ce71883afe34436aed3b86d94c04bb14814a50c9a1d276a0ee081503fcb90f108d1d7d9cd817e4cf49fe489de851fa0d903f3fe857ab8ad3afc3315be2aad6f710faf5c3080c922a92dadf0ad8dbafd25fead2f067ef5bce74fe746cacba2e4d94b3aba34d8ba436d54377a691fd4d5dde4e4eb5d65afb35887dec83e2951fab3999f6d95c4e4311100c981e860c98be060762d24b1c88e9fd6bc41bccde94ddca4bdb2d7fddf79f8e1dbf7e50bc377b98ddec7abfd7fb755defba48ce1eb2bad3385a89dc4355d71b3d9ca26558014365432b631ced6e432b6178b095c43d4497ecfa18e68e25895b49c5f2f5156696c6dfc470ccc61ec3bece8de9fa7e6928a7d4fa44a20fce1ce74ddbb7b6be045e637c35a6ab43d7f9f0671ff5634d4718c40509680fa5cecf20cb940122e5f31fe265caec21a222ae89fca1c0102475744aeac82aedf7dc00266d7a85942c530648a6c542f7a3eba856bde97b0f8aa345c5b3735b6b71b6cdc9d976d3b2ebcd222cfa0e8a78d82da59f43297efa3350fe70dc9f3ecb50d9cf3c0472d24b5099462960db6666def4309cf457f73dfdf60d98d1a687d9111666fea497c02a75502b68da3b10fca8161c087e07f2612d053564c8fed5307a1d04a2d5e8ddaff9734ef4b0d4a9aaf937ce237eecf40001459d1f55918df10808298c5fa94fe833dfbfca775da55f5c8ae042c49e1f87442e457079b267915b5d8e31613552814dbfd60af557814df557813d047fd2eb3fa81dd312de2118e052041727732b997beaa8efe5520a5ba38f125a2bfdfdf6b1d0a36f037d6af4a27dfa1df4713d7d6bb3fc18f6f13be8436eecafdc411e0c67571fa1577dfc0cea602c75aabf740779aaf9357fdcd657578da315ee64dbcddbf53fe2d88ef9176dd75f7d0a03ecfb8ab8225225e697ce96f988efcf8fe60f196387434d9dcda638e83bfc0972273b48fed4a50fe370ff00d4891f97e24ffd751b092edd80392a9f1f595d58e8357f3dbbdebb2fb72fcebe39fbeeecfbd04b80dee36a9b4d99c6efd94353c61868918d3108ea55911d2058d29e32fbaa7ddc5cfb8b3dcdfe16524a7736f6f52bac6aec87b2afb6b705c06f59c11e33219f655b5dcf8454ffed6c0f8abe594b29a5d4dddd35f6d907492dfba699fe7636fdc135fee0bfd4c24cc80ef601d285ca9a73caf8517a9c310a71c2aededddd7166c09f57e9cf4a0404747a82458716649f69ee37cbde5ffe9c2a9abfd4dea6eb6ffbfb35b9060dd6327ab5ab65d909706b7a9e4cb46e5a6541e4cf24d5964f935465ca94a95ba45160d93ef6f7d29b7d633e5950fd97fa56d8b362d8c2ae6a55f267554d19b140daf32bfbf153a832feb9422985a8953f98a4e5e200891fb69450e290b976717dd79f7cfa36cbd7d72cf74df6dadb2b13d22eedf2781dd92ef616dbaedd58f6f5c8d5b2b77f3d561fbb6c9642d1ce78e2ae1f5f72ee9c007ec88203cd2ba41894b6f555cf18eceed42184103e9c4257fe9ec89db8932ddd0996f9d39a724a6e4ba93f4b3ef6d8755ddcf5f2edcc84a4f4cafa2a3e5137777443767029824b94524cd70594caf17672e4ad79a6653ae5ae6fe5c5393b2998049403276e3bef964d6ffaed31beb2148ab6696b307ad7d34a7bc8331fee9f3fafbd30fa571577974f63956d0352749154b1d839abdcaeb1733bcb72db78b1b86fb2bfd7da6bb32cfbbe70df6cafefb7d033a1cb923f6fb8bb7c1a7386e05029e38b0a0afd59299d4bd38bd8bcdc9f385d010da3870202fee6290575fc973adc45cb6e129654df924f89e694ef50cca5c3132cf214e914f1e33b3440922d9fce4dbf4ef74ca88a2f33d1dd3152ea35fac49f918a20ec0ef4240584b7c3e3e04c20993157aa16dc450a47f33635ffe10db91f1f38ffbd0f8eff727c6efce7f9d8f0f15f6a93f01fd01f798f12508ff2d13dd41f4dbe79541ec0771967d764283e5fc3fee4018c1166cb975c0daacb10cbb6e1aedb6578c5e608cc4a972115b562dc75ff51ee141809fcd5db6558a52607e049c841de4716f2353200fe270b3d4e4efd08f9c6f7641c2f808cfa2e432936978f709fa4ee1e6f6fe4111e47ee795416001553173813a2b12e244319244779030000a1548690077a3d2cea863702576f97e10b6480058d11f8abf8d264eebb0ca3e09ee66d6cb8e7381bee761926e54cc1dfecb20178e872016cba7499009c179adbe51fdb5e81bfd9e5126ac5b8ebbedf9e974a755efc6420df651fbb264339c0d3fc7c8d03bc09f9e7ed4f36e10f90a3bc0700d2d5f0b0cb24d828e3f4f183628c31c6184f32292949fb1a244934c4f8f8a2143d2f38d9bb401e2d5227cb95ec4e64a70279b4d871e6e3cc7f914b0277f9a72607d4edb20f764597078071d1e59eec5f5023f3fc4fde8f93771e95773c8eacf35d16c0b6c947509fa46e1e6f3d5e2e43086b32843b43b8c3c3eab143271f8190c7480c465be7adbc34b9a6e66dd4bc083535b3cb01202133f1307100f149f857c2dfec3200382df876394526ea24e16feadbe51e9c14396e974718d3651e202575e37cd0c3e8e1b88142039b508f7a9ae7dea9a03ebebd910971a8f7501c0ad3649bdce51d9312feaa08b7cb3c1e6254f6c9f931972eefb071464dd6f135b2f73f39e8bbacb3ad4d969b8b3128bba703d3641b32b5cbdeded2c58bd29718268e893a34206335c1dfdc71bbac236b419745e87208d6043cb7cb395b086a72085f23e7fc4f2ec0e3e41c7f23e74765111e47d60f9409f05dceb1b90cc5000678287f88483fc238e9e8e82893f025641f06c84c7ebe8607f2a3a5c02772f44bd857a91c513942bdcd8b40f3dc5b2f13b2d129146d08211a9506f60821e7e402641873e89c6d641f99840ca30e01725cea94701712f28ff7914b781bf9df06d5657d9552b7cbb9ea80bf0a80dbe5d7b2a58b8f4c4206ca2ec665b07bc40f923f36cca872254bd4898f83237b97e879c9aed4651c1b33a2cb37503de8b95db6b19fcb507c7c7e7072cf002e0e6a7b54ced91598f3b95d46d964a75293bd4af42a19e06f76f96673198acfd7644fca5e253b8d8fa692e2fec6e6f43701f93354e213aff245208e4c2327b3e35f2f351379141fcb1f66d4a926f2285be2e003d296a7a29ea8821166694ce1a5ac17766528d80f134e889ec49e524a31aa6d76c01fc678d04257b042ca0b51ca082c44ba1c5ea864a4e3f0822c2ebc07c2d09903936de32f5982e1143938b1bfbba39414fa70337607b168f8ef5f1915c4fef5715f9f659402f0df4761ba05540bdbbfb5305b80c6850cae040474420a2c044d89492bafcca4262fa015b000da3ffe05f9037f85e853450cfa1416b629d1c7faf8275c81572ad00f79cd8b5ed575bd705d4a1ba752de77152e2d0002712a4878bbf4bd9db0c5f8180f49f71ee194c14058941fa720825dd522123dba2fe56cc72943e528c658f557d5917429ef9d4424104fa6e094c147181416bacf0b6b8555b6a29765458b69d9a11069d179a5624aa6a495722c4e2eb6edb2b0edb3b7d9b6592793c5f055ad4bd334ad5a18ce9f3fc9b4ecca6a666536bb19ceb62ccbb2ccd2d6c3fa4b6dfe24ea443762c7d732a49457bbced2d5aa1615d2487fbe67fec6dbf64b6dfb0169f64d883f2e7cd5277bfb37b20d1c8ee66d7297617d5b6766ac9a1a341cde5c309994f0df87d643ed743a65990e7aabeb8e968e8e96e2d1921b19c9997884d5ac565c6b05dab5d628d68575524a5f51ae2cde188f8ee2516675448a4947b5bad157603086611886bab1e930a3a5a4384d3e20a57af294d5ac2e549fbfddc88d6a8862845305555512f6769c28874a49a717c9d94352d7eb2e830d4af287685403e6a287f990b2f43fbc292a7fc6915ffefcd31e3b7a07218ebf6f3f3b8e6cca1e7573e2a287bab9c16167ef4a5fc6ec48b323102612c986a92a33cd859d6938af945856e997bc6a8555b6cd43f2e7e7568c16b6ac9a1f7af531a62f3d87c86da94091c4c0202c9fbe3771a320729bc0ba419c49b5a6c41f0752ab13d1a3bec5b7446f8a11d047dc4044ef6807ccd5ffb0172f4b4a5e941cc90b4e5735aa732324952b55934ac2a95143662643af8c3f85d4a0017620f3514ea267e56ac99c713a207a2c78730758e6d0e03ed4d0e2f5442fce1d60a951190b90e7caf6db4490092bd4f0823fa8e4829c3ef4ff5fce6a42f4cac0df4bcf8464cc9965e01863ec2612d6d34bb8fe8b615bab3fab8a12bc8f5c9f046fe83dbef70b277afed8855595aed14b5082f711ff24d84bf0aff9bd878c1173c1f2adafe40c5275c35bd66412d630849694f2f3f0756537d72a3a05014e10f0e747f15f70ab7a3742637f750355f94a4a5dba402db54a596b4de29bbe53aa5128e8214b13d144d1f34ff544cfdf03d890f9c5b0e5c3f0dd0f03177861cbff70f67cf9f2cadff5f5ad4f6eebb3b6f51f8e6519b176d543d6e70f495165af28f0bd37a386dcecefc38dfd25effd7bfd7529662de6ee1935c4fffa4b0f5dae3fd410b92f6d7da67fd56b7d51b9ed3f5fc2e8535541719b3b16e176d17477f23bc659ab1860df9dd481690499b3aa26c8137fa681e15bb9304cd74b88dcf5bfd63a2bc9559dc6950a70bc4c948e3ccf842b4c9ec0401bfb947b54b06f1b23d0864040de8ed6dfff07bb482995e613f80baf9eb63f99b2c72eec8d0a78e5aa441e992d5fd62c5a556cd2c2209b69ff75d8572630aa7ed1ae4bb0ab2a1c24ed6f2b2f1d117affabfaea11df56ad2c9d84eeaad2a82173573f77a551d517ed4a9aa8c636f08728eac80f6242068e91df754bd004a5c90ae2334e8d023ff6d2eaa21731fb1447c88d5e9cd782f46de5740aa13f67177db069adfe7ca3866c77ecad22387b08c782546afa714e3b5fc87ca1ca55747ab155f96bfcd7c33b495984bac936b9abc10eb05d7f5516d837edb1e7d7d05f0f0e68467fdda95613ceaec5a84bd9337f3ec6b5fde7799ef6f171601830b6d01e422bb6a6e1145ad52c4dd31e4aed09007f807e401ffe9aa601c91f1f03ea646000fde748408e84bbee3d0f2905b4c3401e1b4db0fc48136d0c7270c54a1949fb53fa7120622cd5d002fff5d6d78f5fef95b1871b7ec4aca1f9f3ba3c436168be1cf26c5af353520790528efef75fb73b1b9a69b4faad0aecbbb24c58cbaec5ae8a54c99f52dbb6ec2d8b94dbb66d407b4b6d58fef89498923f15f0b2457eb0e2aed3df7b29280668cb16ec5db4681460ff20e9ef4a385e680cf9c79f6f59b7aa1c8582fc0f921615e3c4b6d9d08a182d56651109817b88481691f38a9842d42e54a99e05384c7221b53dd79e9f702935addf58ef4f5d000442c48958fda0777d1b46ee6e79162fc1dd5d471b46d4a9ae286d1245208417e12cf01e368cac1feac3a969fa23c2b099da1ff6433a1b811afb548a2815c4faaaf5148498fe52d0339dec9fb2b7699e55d70ad999a960952953a68b3a7115020ba10f9a65fb4b27b6bf10b7f24892fa4aac4f808c53d704500d210fa5b9fa2b53c15f3e66c4dad588b52f25c3cb7677f9f43d53c13315dc359e720bf74be812eb2d4705630f3314b09dc1c03d0462ca309c9e72798994164e6ffad44e459f4ca36af4a2ebfa033b03957ddf042e34157d3ea20b81cc3c0ca6e79e9e7ee6e90ba9fe7a9aaf8710d2ba29b5374c600ceb88ecac8361060629dbc3c0bd897bd3b3c069d36f9a85199d653bef299bae7d135054a72e30f884a01c3a60cc5941c41bebadeabcbc04140ab24c19e9310cd6706e8771155777568fd07b58d1670583af84e0b437e2eed6ebbaae58aff40266072f607630da514341b1b0952923b7f5371b71abadb9bd4e6d0c2c25be5bbf7080ba14e7e1bf1dbeac2afb002a239611e99e87c059b763d1726256915f6613e93edfca34c3cc1a328dc8c801998618dba4628819620b104cb144c548ee6049054488913421bc4288a5a32962f4306f589268c0a008b7d86cbde3b0fde368e5555565966559395525bbeaa165d5b7189df5a77f16a4c26e5996cc558e87392bc3ac5b68699f910f87cb84dc5dad2bf3b72324a4da15f2f8756596f541715a9635bfaa2a2d334f4fc86642e49e9047e6af0add29f3778570853c75b31bb66ddb766ddbb6d56db3b66aa373935bdc7ceba12589515828a79c724a09a3124cacc0ee417624ac2884b0a3049a907ad47a208f9d99901f417c4ba9a1f65279d4244621843da7860a42bfb9e1a441023838314e4a679269c54208218410429f16662d66ede4ec09fd5a95fb5b1e71a2ac2aab56abaa2a286755559555ab5511e14c5ab9af555b4eab5955ab56abf20a7ea5616559b55e17d600cfbb989d93e60f0bd19cd13c2177ed866ddbd44429eba729ee13ce39a7432ef037b53cb21ea594525ed100cf23d275d67e73cf39bdf2ceb2e6acaa5a41f841b128494cbab88b5d183666f81ca574469c1c58777777d71046f85083b156021b67430821c4d9fe9002a6d40fd8e3eda43e1d5c08db2bae77c7d66cb14ce796dbdbe9e142a8a263621633995278f372204ffd4c77367794dbca9b6977c7dfdeacbdf6f7b34c639a96f9be7342c8536145afc82b433420a89431b32102a4e2a8f44e164435656648002000000043140000200c08868442c168381c1266557714800b7e8a4280661fc99328885118a48c31c6184200000000006064a6888300b99eb317ce9b79d38f19cad04833b1c8ff42131b0f32e2999964f16c2d046d4001a38a3cc705b6129d10be10723156099b1938d7682629f39bb180e7a4e2d2c460f8679ab9eb21c7e75aa9be2a3f29444e4abc12c4357b7388ca019d6f0536016a4a6b6a2df0c952d5a04727c83bca2fd4a46791d9583fc0bef459748f5aad60cbd08f630ecd1bf3e78deb71fdb9b88a488d7e56a2c31190e808912d857b89b2e5e7d88d13b607671d0055a047d0112ab65471546478f726fce304b44115771db376c2d161300de24a17c947635f8a08372eea69f7d63874235ba36f11928c8102e2d83a0b319f9605352a11ff330506663565ea2b0683e34b8480c03cc22410f2d752dffdc6a6e75f2e6f0acaa709710f641e7ac302d9b58d64e7809009f169525f499a0da11d9b2ebb1ed9804287635a3f1c7fd481be2d9f546e5f1eeed2beb031d6df1e17c534c4397884520f796cefa41de72489ac8634ded1b5de383636fb96fdb0e943039b398741ec865e9bab63873014c78a6c603fdca883ac74969aa6a9d9aa8ecc840eb18fc64f4cf1ca82469952bc2518cb80b12c5570c69b39e85843c18d4e585596eed365c90fe498579799d9993620c0be9121b5367b38a0d0c3cb5acd341f42a2ac1ce50681c68b60529cea113c53de7244aa31abf5f90f9768e2fee773a0046135783a1854059838adfb0205d481ef69461b784496b4b469c571a4a847451729075da4a38519b77ef07792f65f062481d79fcd0e6703ef3f37c09551b96d0539db6e832cb1a875d2e681078783515811b70ad77d8d1c89cf7c22937c662279d179915282dfd73d2a3b7cf2202943bbb2cb2d4b13f2c5c28ea5780ec033a514246f39863c5c889e1318a98d2dafadfde32a76da48dc84f0bcc13cb4493feb4847a22f2efba30b6cc1a396d9d191460f314011d98a13f76eeff5602776c6f393fe2e448e64b5a75daf31342dc435ad5cf831aedcc2d644f924464787187b896bf375023fc83fde3f1ac645db2f41ffbf09793fb3cfa13b42e9ab0613846b824c7fe87e11b8dc976e4e808d1f310cb5cc9e4bd099dc2dd6c06940dc3c1135fe64fd42cc3d414af1711f4c34796904ed35fbdcfa61bfc93bfea10bfce4db3894c6441230c26656b60c249f3234bae73084f2544aa2db933c51a9e337513258dfa1885f5b5bed36ffbbc3a1a7e7016dcbddc085219ad490efb794af754e34e9ad505975b89d5fac7fdd887ab1b4f8c858c755db5901678ca35840a0b4119e2298d90d5393773172fbb46fd5a961e0d688d0cec930db2eca6a999b6e071aff0beb04c9ee8c0c95f3c12e13583607e022c73030038396d0e61a889e38bd82727b4a2abf82ac1534fa47d426f855a50ec8e4e2f12a1be4f82ce814035c0d9b00139bb218e4da797e182b160f42a3f7fb8c20078fba474de297aace375ceb374e32a84ac996179336d731afd90a967f98c835f1e143de1950dd86014171af2ede66eca02362cc1ea2846a090c39b9403360a76b24283074e9a305f78815c42882e18fe63cd3603647af0bcc5c1e49ce8af8cf5e9cc29fb524a9fb6e77d5ad42601d58caeee6a8a032ce33af7fc2af9e3ff3aad83fbeec2a253ae6dd00c5a444f39b125891f9f81be0596e0ca29a5e9e95435912a4390fc43289bf5c651cc768ebcac84453f2e569ce56dca979aa0c914f794878f3a62793b09037c8f0658015a81bdbd83e4578e37efbf969f1f71b770750dbb8f8b92c23d1226c15ce474955c8a92c7717c31513180820ecd1ec0d0ab7877d414dea391fa0aa6571ddb028d2725bdbc5958877aa48b31983b30f7d51d3057e84db622004103688962f0acebc063ea55b806c152345e113a8fd096f542a52b1960f9c9f573a08aa1d73c2656778a9c46df9ef4924c5241a6f9575a5929fa0c09dce942cc689dd8712d33a3dcecd4a6294e70a0b718872d5d055c893ace9e74008cc0ed83dfb78020b32bec9ef40f9312bc424b0cd0772891755d5a5f789cfa31d18a39210624270c0857d85b2d561c35eced9e04186a19499dca5c2f7cee1b4ce2c1aa34269bb43f8872143c18010635beec9acb090f30051de30754d3f0e70bda9b70e090dbf10d02a1c7f87089890d07b41056c2e479760010c25e9d9e62725b9cbd0dce946b56b63a577e44c0c64f7206fdc814cea47594d266ab37e84946bf68585542299070162c63e4509677e0c98a657df302f302e024c32be17f36483ff47a3c53f933550398108b945cc74b75abaa3cbf72c5a99a5a29c1d57a49246667e07d462b05820099f7d848fa7df70dfb4ef5a8f4a5a3dfdeeb72041b424d620f94a2146e79f187527959348c3c09d41d7521253eb3ccdf8b1a7fd8197393eb51e5d3ac04c6f35a66a584b3a33edb06bb365e1d271761049e28a04105ba24588ee70c2501b55507d45dd9361ed1cd26738746092115dbc9e52783e22590543ef18ab2b15ba1038bb0da8329f99b7f505a6ca8c99f0774f1ed00ad4a120a05295abd4408bab8c22a04956d845c58de8fcb3e42d46657d10a96026dee86c581157c78ce6fe1af30ccc2c4209d449ca8ed025b0b2a8828a945d3b900da61609c544e00af4a0bc2fd827862831347c7983c0b6acb92d0a1a6badf0ed93b7fb35a9a54c916bac9767a5bb71088229472fef590d51dcf300154363d3e6f33a833838d8c0c5e05866571441ba315915654d5a51374998c18d2ded47066e692b6ec8aa9c6226f6b9effe8709b402ef81f9251552bc7f7d59498d17df6c8642fa5422ef28569e8909afc4484b1ee3c83149ffc643cf156f24e1662a998da5dbd83b63a15ff86e8dae5e0785f67c39c0daf205cbcbdab1ee3ad37ae23eb915083bcb36e4ab4add7f87fbd8b71946f600a6d4c15037a06bbf43c00b6813888eaf5af34d9372e6578394ba6a64ea6bef3f9acabd656a7ca6f238775e2895965412c414f7b75fc8790ab7a9f3ed5a2c8945523b866653396dcec6bced98392eedef3e3cd05bdec2b655577d1c431e6061be6cf25fa43591aed0a4db74db8fce72deeb19d48a7d12eaf49db8f5f983d23369a8fa23a3231fd638dc21f2d2fe3ec882b6a660085d3bf9810e5573212b061a03d0dd510882336cf1c57474cb50d0b29e912cf67edf09817affeba1cf1349cd885e5bebea78c52b8fabdb94173621213cd04802a45466a12d5c880f21365115fa914a46d68ecbe500aa537b818d82ba52567b1693ff9af1e5e152645a3131b42bbe5bb9eddba2dbcb67c62df50caa35210c1946c439ba983a79b2a947d5481b646ac4e25ffbdf552f94a9a67d78d9012e7c2489e89d861a81362ce9832a7315f1c17cc2154816d4c606d58538f178d0f528d3f70c47c9654b586895304d42c6d77a800d2e0734b0b9d184b89328d207056a4aa52828b35f72b626fe6e626331b166665148dcea3e85f6721e9206d083212db6773841ca4fb34a442cae70e4927d3053187a3761842005ece952c91b224f74399dc3895b2e7cc32079b63a73d256b987a4001df2606a269115481a06b8286947efe218a7940cf896e017c2c8eca100f805dbe6afb6962205fbc03ed798c61f8a42f0d423bf2145f8c70f32b3bae9e46735e195a4327cfd05712b6221138d9cf86a76bae042b4a5960e389bede0d80d5341f6c129fc9318b13308a012c77fc6ad0f0d95853e0a77b262c970e43631ba11465658b176e0e6450d70a08754bbd8b4c17396aa1f0d25ec1fa5d591a37d72fd9eca151a8d4b8801c78ac45912942d8c9e5a4b46f030f055fde15f343d7d1e6fe103e639254b342ecba69e5048ab0d605b88f7d32df24ef3869de4cb0268a0e4fc9b544e651846934d8703235f9d604e4cdfd15e6b4552e15f44265cf4229449ca9430523fd125da087ebbacfce07959863acb9a9f946c4f5b91106167cb1f4aed74661a4d46c48d5da407058b6b02e64568fe1cc45433f51a7a74615a83ca4ff58e4f070796cb4ce4c553b88a59205c1517d1c72e607e47e09a4d009ad335a5bb846d843e41ea5c4f0cb9f1c36de3dad3c67b9fc335a37e9a711222997e0d20c6e13b19f04a08718b8bb8c77da05e25088d777661c588beebaa10ebbf6e49807b008215a6aee5031f8613a160f99f6310140c9b29794b6b8ce91b8d4cff4ade7a970193267470b55853777f6fdd953d74ecff7d8a001f26bca7761041c83f35ce36697f865811ac6b5aca776b035307a75432d9a3b72a17fafe854d44aba7fd5243203faea55ad071e492b27403a554d725bcf3aaa2905dcb9d1bef3cf64685d80fce310de97b92be5bc2c3d55a51d4973fd60e5e9c3600f6cae970c2f58830511fd5ceb40ba50a0e270286e6067a65d21e7638e193afe5b3a99b59f42a9546245ae2dc99773b291b7d85fc537bdc3ccb8c9ce9825ad4f8a18c2eccf386f476d9702e623ba28083251ef712fa083ed3598b4c90182d71612d14465182f8b0fd49048a27a5e091747e47682cd8e6197fe0366e25378ff345d531e34ea3b761aa39b545cab93d7d040c2a1e254e1d9e3a68906140dc237eca1aeba020c6ab7ead9295ba8509164571b5ffb10c1fb6e3aa333140f870f647125b07c5d17a18e801c8aa3d6044ebe3718f2c41daed1568968ca540a3d78733678246244a2b841949669e0d2751322adda0e6a0ab14785bbccd4151e89dc61111695fbd254b1d5a33b146c9742f811e2e30ae5e63b370ac12a2f51aea517b17c23adfd0b20c0bf6911b6dfc23b3709d2f3a59c59f1b6dbf40920d05432013844032d413c07543425033e30ee4551583b62f611913e2e1de56c4cf1f6ed9f5aef7bfe7bbdfff3d3b25de134d3e1bf7e79df8433831ca605975b997e295c38692004043d0c0fda08e9269abd52aad7ae03256eb66053fee125cfabb04131bc56aa00ebe350d23629807ab4e9e93e5afcb42fcb72f5c0c545e2935ef17d650ddca3b1719cc9118fcfd62dfac7664cdfb0dfcf43403b69de86320102014c4fb0391134d6463c8045df4c8f118411a308a2fb4fcb97190a35d6a83c199195ee22dfa342ba317817db3e4c6595828065c4c700e9e3d37fcfc888d7ad85a30108e776347a70386a9281160f82c1faaecabd40d6c02a81b2345c122dc7644cadbc3cf8a23114216312da2e0a3a3274f0d942a73e553cef868a0dab8a4cb1795d54ad5aeea151b603f65af270c2ce2dfba6725056b667f2e2745661415e6a5a26b69037f9527bf9e5da708e688c5be8a6cece7e94ae555095ea2dad49c0f30628ff1c6edc840b1c5a4b3f857b8a573e35de19717efc83796a945825b099d376eac2cb9dcf76a4c687396ad4fe18d260db60a976ab88ef6080b33941433723fb3d94971571a61804115efae603ae575b619e005e72f04722f589b30c64777dcf4d79763fe9c06500464a26aedefd2075f0c007e64fd8b3200c634b369cd22a3dc916e7f2ee4791474a33106c0c8fca1b4d70fa81e42ff989e520362f7b0ea08bc9a0ac1f420e0ed93f06b2a99efaee2893e45452fdb27d1c8df09ae6daf40936a894bd4880d933b9648ddc14084b5a4b3c3845c1b9381b831329ef1445e1b13bf46df20888263197b88666f2c0443e08aa31ef3289e3764efa6ffd91ccfb9abb5e4ceac0a71ff0637368a71e6d315c7260f435dead76b200ee3db34d02c05a8c01a231d4833b4800748787a364938383374a19c602ebffe0e4c8d497e682708440d0c114aa444f93c83e03b2a3ddb3d387319482fc62a35bb93242726aea90882c53aebf01fe8191d93280eb6ca556aa07b20e088b78a880c24bafede68da0b3f9da7e5e91b32f19b32d839d2036f1373512b90e7d20384f1300c8d23835382d70ab7a1b49b048f96c62fce14e5caba34df71d8591580454c4931acc9e1019b6f512848ba03fac040fa15eb0fb372369828e71b5e5763dc6921b32904060ae63f7c2032d783465e2c1ce4d013c10d5c4ca996033c374527560dc0981bf81a2688abda9421d62e30c371a99871544cd46fbb822c2c444b5f10a8096e92f74d12bc56c28eec9fa581c4e97062e0de451bd807edfd68f5b234f880a90b46841b08fc2cc4924204e8eeb1015df476961d99cc607b1cb1271f2c55f1c3d22208306cf99f3bbbdf8c29cf04e3c57cb047346c9273645e07e726be99d0f7206bca2a0ba926f227573628e295b2dbb725d939932429412b5d9868c73579eb1f24e3da71b8a47baa6cff968723b0c330ec95019cf7c1268c658d2975af238653650ca84f80dc5c86969ce1fce802c5f84e63158ce087ac4053b31d11374fccdbcfc6b0a4693293693908683faf26ee7f000ba79551fdcb208adb2be718f16ea802b1845462cadc7be00c8905a4cbdf8b07f2a8b900e0b9807660e760b25fbaf890b772cca97150b3b518616ccb0d02361d86341226fc53a51787348aa1d75771a7de437cca05c38f75608b73c71f1112d81e90b457a3b61544d7a928310091871ba15ce31815084dd0e590bd1d192b1a29bc187623768576eac95178635e9ebe757c92518a368fe143d9bb6920278dc3360568a352359977ccd59112072c3a91857464c4952a885f30ad5cae12a4100173cb1600b552a63dc0883f5422e26cd4ac6bd044d1e63b1ca9c6d1471c60bc40040771e74fb11a4b23f2d4988069c9bd276e7773b437f4f69fdeefae14a5a9924e28a063d97b42fd843e81ae8c34557a988ec6a210719cf8ffdb5a9f9e918d6694e64fccdb915fcf98b62af8b40b79c6cbc2bdaaa6e4fb1c8a957c0efc09c61ba19093b07a1516a96e858fba65a05e7cf16a70f117cb6487704a865c6bf6edd606cb5eb6344be7f72a2ccfe9fae24ec93b1b3e5810146af75e0653b343b2a8897fd398041c9eb9f2f537a96e27446ce9efe98e1bd72439299cfc46d5da6bd520aecb4bbae01212b0c7672a600ea6295f4db58a5b9c363d3ff7349a0317f5986a70e9bc52f0edfadc2a0f61c0596d3c50413f50f56c4e4c51849ef403496a1f22c474d0812a73b586921840095c093351bfd29d4bdaaa840c94052670729b7b40064a28609ce26f3362c4b603d6360be79ec1b67847b69510db70378f9230d845b106f5550753ec23b87c8591766494d8fb0552a5e4a437bc62cea06ad937d72aed82a790a9d03a589ced49829fa30dab9a35e96e6ad6fa1e9f8f9f021fc7ccb921ea6ee43700092038f24a1e337dba370d90b869ff2ae9d307f92d1fb7be35ba88efb60f0bc7b32986fae188ae5c990c747092ac447850645950ff40acf7b93f4210d0090f33b9e946821a1d5c3fa50f20d2e443d8933ba42b6be7d9b8a6d3858467c05e47570f207ffe1546bb581f7fb3e790d838adca144639bbed26a8e629ba878ced081b9aca610b25a2f001626ebc0eef8823620ef356405cb6ef2dda04dcbb9968dfde6a8d82d50ae1b6d153c28bd3d4580ee532e802c3ff26f42013bad61da5c54ab3ba5a6d42ca5e482813c52473c6913d35f3e376ec87015947394d749c4b9c952ec50a00d13668b62656a7cf703b2a5ed0e628dc196e80e35b317e8d1b07edf7cc1f17f6403319c6115b3a28e01c6757dd4509495c6d60b61d83fe3e921ba351caf987b34bbdc5eddadc457f8dc06de9f2a19f8971bb7517f2459166947dcc8d516e5897956f5bb0b02d13686d46bc44459cd770a20d7e7c120ef074212e6a1b6d41d62af94aeb428f4f4659960c88ecabe4ce91cc9744b8caa596e6808e89c9264577dedc235dfc2e66b8ded6c14aac8e166e0a37ecfd4b9b83b12a52256a4322b04359070c7b87ec62bbf0153ae810ff32777f3e6fc552af48fe6da06b8ab2ec8370322dcd5bfced48497122a8462e0d97c7d401267cc8fa43df07e3c35bd7d7db9afe734bae24029ee0f27a69cc7dd5b41dd0904c73dd33898f49ce93ce0985b73cc1db77e66f112ac2db6c3cca9b49dbeb188fbf2c66ce01665839d984c87793e28c1199f2ce136b82f3af6e4de777e1e3600ee06ef1b56497220ecd0edf2ee279fc5bf24ec9dc835c4d55adbac878ad7485e442dc44776bb797ad79ff97aa0b763a72cd57f766af9ff69ebff227ef453661417b9d33c53f39f7265c8db667a99ce3c5bfb46e3df1fc0bf70b88cf2fdb59950ecd9eb1b7d0043be8a7d956d175e6116aed345f9ed2ca638f9d27bf2558aa7d8a7b840ad65f87d4d5c0481553dc9f81ba16ed72fbd7d053a7b5897a6f46d1f94e27a675be71ebd5635f1962c2a6027a97c44fc68488a1c14dc378bd1cc3d1b43757f4ae40b2c9bb5853f0a0efacb75b13c8aa0400672655c46f05012db3faeebfd2e088db1b738af8d0bf1fffeb628fc38a694b0fcb91cc544b7c32a4f60e024dc5540bbfb81024ef48f48392ad571e17da4eb69201c47cf5c37b87df77f9865eabdcb2d44c70049b4faf25d220b5eb80ec617041750e855f4e6687628bcfbb9e2d741d68b61ec99b0101206dbaac3821073092c0e1e00ddc3457048335214471d16b07da708ccd965efd74ae15c5135d172c4d36f9b1be0c47b9698abfe904614cf3a451c92d1ae2b0d5a405c8a86f2ffc54b82689b2b0bc7f06df474055da040295a87235304419a86fc42f93c77d8fa26f1913df8d8885a5ccda66f2a322958d80fea2a472f6a333c8ab66761b5e7d776527224c0185361e0ff97d92a8c89641190a0d6e2bf100e1d733154fefc33f8e0ceb84d06c8d5388c831a512508c03593a29fc360c5332222cb47d3f76395a54c96585b46014d66816d6302c31f03888f08e6fef60e99c347083d686c53e66014b2d7f3aa6a40faa7066cb0941e9fb0fc1e45105d6661cff1df877487dd0405b62e07650c704de170a2c4350780848148270b78d4ee5dbaccfb8a3239655fc7b1f97c5e4d79314af7156063ed6abd8eb567db3d7bc64e0efecb94ee6bd6a8da21c8824d6a8199b6c3d3043b27e8322ccf665126cf77e56be0f99b3e618cc69211fe20144a515b5f5aa9f992b7ffb08b36fd95e7aa3948024d7d424042529062b4fa592370b355b3a743591f9916b06aab3c03760093de1c6d4aa12e17cbded4f4ad9969d1eac0f06d09fb04e4c3868323b3d3b247a144d795a936f5a9fd5ef8e0cdce71d4b68891fe4ae046f192f3a241aaaf4ac6a8c16624679701a439ccd1c58a24fb31045d3f84914bc5aed122851dc2ec9874e6d3c0f0ce4a1a4bc05a9b532161dffb13fe440406735406507259db663d2d1fbed42a564a445eacf62a7cbebe791c0723e718177c0eba5d032e1e7a681ef1293f6f9f9f9fb9e74bbbc24eb88a70576d1a05913d5606564a3214d94198050946bb09dfb8657a872c2274d67b2b9446c74a6e9932d4f91a1ae45108d83d450ee37037e0413f360a6660bfb34fbc11f74b4cb2c08007cb5f64975889cdd83df3af1c046ca98eeee5a6d61757272902b513c50c2660b4522956a22c7ca859b1f2f19563ebcca604d34ca5a279fe6fb14338d2cebaa445b95c36a16132f750a5b38c7119fa504d877dc61f228076e56deb965cd3c1e0a119211cd4046c72f6467375d0697a549ca60617b29d60f5f5492e6d24b1dfc7659ac44e24f69f49758507994ee1cd5c3203c2ef441026c3e04a4e54c4bd04c8b18c8f294d41142f20a1372e27e07c48897ec7b87c69ad28608c46eab274141075611c9d38140c4c60f4567a4a086028eb46c3780ae886344e649a076edea083c1ea3f66a7a9bdb8e21aaeba4947bd3cbc2dde0764b28a744d622873cda3f88b65a454288633935a1addb590eda51d04548d0cfe976f2a8bab00c66f16775034b13ed9e9aa509a3ab4524c5fb58b305782cd345a4eaebd31076fb6fdace4916229def1c7cb8029da8eb939d63f4fad2000152b351edf71249f0b2d17eaa263f137a89ad82c5a94a9a388441bbf20f4f2a40694d54b500853cfb16d1982e2c94ba1e7b2e1c0365cf71c696b4d6c848b5e3240fb7a071f52a9ed934c56d2479fe0abd077eacd60bd4dbf16d451b6842411c9fdd3374184ea5215e4ad16c20da9cff2a005f3036617eeb33b50718bbccb46648ba5e2f6a16914d6f9ad2867f612b81d50cb9e5d63acc04891900a0e311ca91a9ea5d606838622777f96688a41a97c7fd095d36030ea05c5840a13ac48c9a6d45dcea7a1af0349f8981ac4b6e17c616e70d19f8a5e73454c56f498c8f2a29552c826c0066228a5008ad43bcf2395da2980ab23dcd8b49732ffbc7773004e1466c8b189040c3e7f24bcdf8ab8452df50699f1457bd4988ce941aa1e09d2cc8d30c99d8cf6ac6554f2a53f00842131021c14ba6ad314ddd5c912f12d7173401e16f04c0580875b3e2f45851d03f7296ca22ac7a4aefe6e919b5ae5c49f7c0d394b66b17f73ef2ca732b269e4c17324fad68ce4e6d4cf5339f92c9ec33a61afc76a573073b9c661845a32b661e746474c17fa574f194da3a76733f944acb529b7d0255ca7ae62d01b9b7405cf08e6d87f48277b60830e4fc70f893a247a080211ebe3f12e8e3465f4970f8a25adfd4531368b847d81a59200693ab4bf6bba2157ea81f2fa4a4aa8b672282035ff49e7511867a59b150da0cdd51e9ff4f52d4efbfd24b85086ef4994d6f0a495c1493bf36e8bc3a1a818f0cfdee61b623c248884acbfdf6c4500c074121a98e1682b0f7a1b366a72e5ba45101bbe0175e32e8b134bc5ed847435a7d06ce14023d7dcd0bbf2cb716b88404a286476b8419e888ee6beb0df853519b89ca7ff6c0366936caf064b1c5f1b2f8c09457d92ae1a8146142001bc60dfc05023f9127aba0f7a2b8dfe5e64ee53f29c31e3893a12ada442423e190911514cd040f2493563837a0aba541012651739564d2cc57b86a75661aac4a202eda38991f8e126cf74099a4222ed67d22b3e8a06baaac3b294d91379deb6fb5ec77c179b72631cd62460902ac8f34eaff4c40c199c0f6e75e68638724bded50f8aecd1183f4ff6d92455a4009857d2b41778faea65b53a570489eecf07d94b68013c3b296a59ccc39d12e420e02668a4873157e32de3645bfcd77663916e80bb8c92e8751f4a03a071f14efa11c01e42332838c8bd8d3c3aa7f10fa705f2ed537867f7101c6f82257a0b7552da28b4473ce54408706adc18fbbf7049ca5b4b511f4d1bd43438c1d6abb963db66cc378caad73431b1e2de595eda73bcaf5e2ff872fa6313c2bf673621b85ea366e315ca38520d318445b05b7c4585f40dd4beda6ff7847d50b6cc0b575d391bf8f083c291d288568bbccc0da89a001d5038900a4ca35f3978b4dfd4e064cc0945b2532c3e799ce52e36b46ee4cbd6f36b1a1a6da315d56a3b8948e87b6340333117ba62e80db55389517e5eff846210f3b77159c687b3f68bceb817181695fd23fe7470d719566c0c62e5cd69141c97c4f538d56420bda604900f48525179c7d4cd514206515d5722697c14c80674af95f37f6acffa2317ac376533e787db1009c49bb48f6acee3ac2370b2780630aa56862cec984b12d12abff05637521266f7da6ff97781e1c869f7298e320c31c30b296c064aeb5356b6d7d5ffec855bf7eea41e4bf1936b5de845e2975dd16a0dbfd44d16e8354ec76341f35ed6d7f795e4739b9eb7c8a8864d390286deedb7ebf8f97fa790713599f9890a2034044a77dbed0aa84b375589e644650632983e7a4e5d035c56ff29f2ce79181ef32bb67c888c23d85c9c360328cf5126c309aaa97f041f04bdd4c3be3efce04eba510afd55f7e9c2293300cbcf32d1d082dfbb7953b114517b3d2d16fb6f3a4b31295a96fef3606c1468a376fe36ec12cd93771112feb1a81588e040faced0ce8f08f3b3931211b40556d5e4b279831a0c0db6d3f612cb82e252c0487c6a115aa42983a06b41c2e8d05c44b5943163e80b2cfe5e70fbba3aa8633cdf268bc435cc422be60673819c46d75897712e1bf83ae2fe7c282eabc82e3a3588ff984eb3ee7afd448e789758a6f6776c4a27eb335c529d164741b4cd6a2a4c669fb64b7be6a7b83684be167ce376914fbeccbcc322577810241b0aae424b64580d86e1632be5d05d3a6b7ddecf61649b06b3ca4e44b0126fd6d1b87882acc47b88a7f95748efa7480c80c0a2f11a03d19d5e34b9d4aa29e482164df30021f1e272f7972b44aadaf7b9297af02f96a4eaf303a743829c675c4b71d777f6f0f4632914e31edef7a3226fc32af38816dd7e971f5c4a32170517f4941d766176a1014161807efc3a8a23e5e42a1d6eb8b1714835eed4de404eeca1349e3f077e09da66f80edfa9d8b82b15acc897ca200ec1d68948387bfbdc01e44b4e13a768dbb7e3102b7aaf6369f5f280a2d45e14bc35638d994c44c7c71b0006ab9097a1db7ce419150debfe9798ba93364c2dcfb0f909f71855f232360e6be38bc597790cda887e00278f5792fb53cc61773040782e728d0be49021ff68ba4b69f574b9d7d4c6184564d08850dc2788a90d14755538412278b9fc8b340a11ca64ab3edb14d95701c206ae1ee36d5d621e744d4394a99eb0002bc498d67e81faed6dc91c69f8f5089a68c8b01551edd80acf0437a7e2b6936387179b38e1fcc1d4ea94515abe0d3fded7b171ba5a72fea1fe1a1201947383f525cd41639ef787c5bcd22bff0982949d6ea7c152a990b94ba60c4fa0ea054102d1ebcae1d0da2af52b94424e229e2a69ccef43a11ce71ce7f8b19e05eeb1e03438afff407d122c93d430d8d15a5dc3c8d269fe104ffa09d0c7d6a1640ffd7aadcba9e5897d966f91efd580ee2a3757ea53955a5509b3d65bae22210e0c0bf9520f11295a53a4f573197218f5cb167b5cbc71b8ea2a3c3e3103dea21c81f6c0237150b7b75db481321fa39c116886ef957f7b328ae5f74bbddca7b6f5344fd09b1db1c69f820d503f49e6a7cb8810ea6ad87ff290511670a4738b6a0203b9eecdb116d4d80646f747c8d97ba311c0764581fdf98ac28c76210d4593d81b3d23be4a37ddbddee67e5470bacff2761551f00ef5d47aca184cbcecf6296137e0e37df8b38bb9187a6345336572a1cc34096771ae6a8698a68e5d18ae3d965cb178f347f606939b260e2acede0644bbee10189ef89abe5b0d7fca0d4020ee4dc8338d09b4d6e17ed2aa611683e6f5420f1da711cc5f5c9c86423e28253eda9832dd8db6233bd6e4e7c468f6cd7c1c03f8d53889e816f44629373553a1cdab5fdc48851c0d0b5c778784fa7e5c85a3e7e72d91815f6ae51423bb985ba596b1dd28ea32d9a9d344300bf54e75bd76bcc1d3762b57bd9ea6b90afc445764abd8fa4f3adeac6a892ccc91b7ae8b56ef599b0df8bb4e384470be7ca10f866194b55b90d12451406af1d447b14c8762f36b13e5ed937500a9463462e16ee2be628df16d1ff7855a8274010fc8e577c65a7e8cd6f2b66a4a2c848b5b6ef99ca7967ad81ebbb46ac16fffb7b235b008b1f89e20e396955e050c60086021833c3fce2d4e36b95a4d8faa574d76c6fd147a1e04702d3da269938fbc92185de559ee53dd0db0fb96eac950f01b39454317cf454fbc75ebd1d7190f5bdb394bf997386fe177aabb6f60b4d9a5b99d464f8c8b10fadd7fd029483e1562dd22da7874694d392103f92dd7ddbd4d89e804e6133495d48b0a240c97b9978362fcdb18ceeb8f39c64d4bfe4c58e6f7c7b69d73ca406102966e160743cd8be15649d6408e4aef42162eac6fffc0de44425cf5debe7de5534a3154e50e3a6a789d965242217c8b464480a4ac41050e96404913441f9b13611192773423fafdbaf6885252aa3b019bf35f325d3a5ee30295fa524e52ceb100b5df4bdc087e6ee439b2b2186581e0c7049abbd701e89fe00bda5c7c2795d30026b24a464b5e654bdb507e9c9c482a6654e759c998c8206b71f0a9627f59b0344f532792b55cb50f4f796c2b345b33940be328a27e1bccd1298d3ea569848652542a314acd926cc951e4229d1ddc4b2f728c58248f6258b153986ab68417fb008fd6d3a58e3a8d47249d5b055cac118afd30adba1a9e04107d04b5534c5cb77565ae04958ad52a709af8af0cff5ee8b715ce97c08f821627b17537d6008684f31de2a6c09c05655ed87d315a36394669f10dfb07d09f1008fd87721af0aaec924d82c8d246d20ef3f158aea12687d5c5f4d6cc1e4ecfab57b42e610cf870a9433781468f94fe50171b35da3fa1cf93e5a3ab3e8faebddfb1d566becaa841e0980581981fa32c9f8e6db4dd169d66052cb9d4925373727953a7fb8f19b14d0bbe703c93588e3ff35054398b27302b087d03fae6d92bcd29596840ce942a8edab1fcf4be58f3e3db8b52d91b243a8620390ae971908242bf7fbd5360faeb19c70baca3e96f3a8497acc08cf6df4992888edc6c67244a065169c118df4dcc6be1d44068c39e26693fb64f8df61f56c6c079e551f4ba93fe60842be5f61e015a21e43884f6caee37f962d3540c8d0bba43112d012f6d567f98d9fccdf602d123a249270ce2f59eae36b2a1f6bfcfbca9d9f7f44b13f70612366125fd13472b1a63708cd7477e3eea693d7e4eab06f3b971e2e519b2220189c406680668f9e4be10558ff819b58bc65fdc7587de86440f1623c3bc5baee3efed42207283b67a2e2f0f4f705de34ffdcff4fc5903d9aa358c3af4ded11743fbe071db7bd98bb9ce40f9d18300be2af0bc2341437e4b240728bfc42d66fea9e9ca77024c2bb671dd142047885293192ff78f48fa582763f3428cab6d6fe9770e90833b1480c914f88d0d77ed73aa3cabd85e101a9d2e8f109ef134893b4c840c15c06298c97d590831b28be4cf20710f89d3bbb012ac1459616d50b2b4c221b7f363283ddaecc9690e66a419c52fd5e84c228681300844ba7925655bf98d010006feb986361864ac1cbedc63a4139ad3a031f8181a0981e6a182dc7ab6cff79496b8aa4aaecb2b209f2ade21197413dbe7c7fb9a5eff83807657af64416300935704677b44408a6415d523fd027dcdee8a19bf969c0cebb8a776348c7bb627022a84d873bda054c5a23c1ecf3224ab2719fbb9740e6fda9626cf229d4ee323809d66c30e1fc9a3bb683c0494ef7f95fb2b060258a7218056d589be600bd8fe28a151581751915c782674e6ffb623294023b30bfe1d5f9ed3ea15c0d94fe2527148a4b1fc5458522480363b82d0aa9334a239eaebffa5eee83c0175c09fadf69180ebbda91cfb6d3c67effd3a7db74d6f53fac42699e8ed4024b1e85e3ec528ce53c772d9e483b249c64757696a3a389ab7562028a671ec60ec8329305ec4a2456d9eed294f0146aece6a59b79713c3db73ea49bcf0722ff33eb034147ffc5a175387e5032a4451d9947ed8652fc01f44e7f967de58c7c94f525712e92286d57397a9b19f63419089c836b68ae9e0004c2eda55dd4c82c4859d3ece3e6ade01a78b614ac3fa4ce12fefb52a0f552221b57a08184f021f71456ce7cb3371d6d94cb69e04f2ade483c7091360807caeff8e204a482e4181d2011410c78ebbeff126703844f5ad32e4aeca8b4eb4e3f305b8106bda8bc873773b81e7941168834604a42571596ea245852fab2bb293819247f52353bd9ab2450510f710eb540cfee3b50952eac8f0ccd3ded4089d4e2603691e8658832932946beb7c4a02d4db161cb8752858a2ef236c78f807a5cebc38b05435b6b3c5616304469a9c9fd245612bdd066308232e09a94485708497472ef2498e0e875d6707a68e35ab7f4a22633cd4b8f69f1128ffdca5c0624375e6e8003c0191e48d08f0cb659b0d08f7ab0950aa1e20c897c561a85630a3e2b18f8d37bb1f2a756ff8c927198d2d24584099fbbdaabfaac20274306087bc629269af20666dd7f3524c1bdfb8a1e49541d1df30b53459ce64e6cff92050837fd4fd93cab47c80e3b431b941267eb4cec6720534528d3628a6360f53144610fc4aed0fe1f931a7ecc9a39c2cb7ca603651d14c1c2aca9bf0c61069486d708ae2882f931f39ce619a5e70cc87e6368cf38ed97131c1145c8492ddef5676bd56a0cf659716638c7e7e8df1f1ea5047a45f8e5bb1b6045085504f13f518d410c7892e1a36909c9034fbaf63db1e0896337418c11c686d71d142bf4b445ceea2daaa91fc95fa69610f6a1950f02ac5db4582b9e472fc78f5eb896880b4a74e762342b0145927d6b5f9a6e43c6683226958b02e0fbbec4f0f3c96353da961fcfd1c39f9c368c2b0cc4797cca794037f9bc06da7db7bac34d203a9c06788649db16d1bff2077ecc6220bca84de9b2c183159f0ee2b5280ba525bfff3df986157965284d1890b3287ddbc664736a5ca7eb25e2afee50a940fb7e3a882586f6677914fa48ed865154753c7c524d63b785d374d4f53bac436b9c1760f55723be6d3f9d46604cdf32bf76160427b338587d8c0f46e06d367530f2ab4bf0b2e69b446f177bc75da7185d47f5dcf6ddee7350275e65fe0d8470bc5623a3ad6a0d0902bc21b06dd8e2a3abf905cd410de32925bc716a3d86267c4c04115a21a0d20dbaa7d3e96df04d8c7598f0e8fa15d2b742657e1b6fcd4fe14d2fc39f9aa6219ded46e4d3fd876e02e4aa7e41f148cb7181b6168123b44401b1188c7ebe7271d44ecf7c42c8b6eb529970e9557c202b4a553411367ddc656f85a06cb8214a4b20f7e934128af6dba4fcb8c23dd2a5bb15385262aee30602d13390e5f7af58d0bdb01366b3675cdde503164b9a75975b1d96addf1bf02bf2be712c259b7f3c498e77cf011663fe9e65bc98eed0a458490d2e621fe1cd87008a190b09c2f50e68e5167de078331734608aa5a3efe994d0b832370f99bf3e3a67a894707f98a2faec0dd8cd48a24d98f059a23774f793bd33bb9f79972a0059bb340a6489185a0db6c74e76dfc0e08e0621e56143b0e0f1a6c2298c1d76a3114510528f2e08d901b68d07772067170a05718fde10d2476743bc06d8e34a317ab2f4bfa927a1507e1ea18ac89de1e33d2c3f6da054d2c3ffe0291dda8851f1019d864128ced7bada9239d9f0afcd9b753e2c442eca4bd223bdcc490b42dee88b6f63edf4c94a4505bf8da54c080402746b217832c831e3d587e0256be3a82a3a9c2906f22298e1e395d5cb4131c5ba91657135918464e1e18386f3cac20591d5f9b8d1f0e89904fd163f3fcb631a1cd77bc082e6599a3f2a4f4c8e26954c7600a3c504fc05f7d167290b1774faaf86c05394b553805b7c4d70865c09230a43aafecfb1272fd4284419521b5319b03a598629a005e26c8fae3a9eeea510c3d761b0f53cfe1b4061d42431f48c474b70475b4abff4b99820163596fb38a4d8af197ba6832b606c1c939608cf1db118e3cea5670e4d1e0fb2471cdae58a626f4038a684686c68ae64a9f2bd74759f0f6f91ae0add030864421f0afc8def56f58f3131ebb02e99bd18327ceb0c3e7ca22c08296a5318ba31a0868a3e80bd4ef9ef8f5a63f2951efc71bce656b1f891b766f0adf619698aff31d5d11f02ce4e93022296c7abba33da3e83aecd24d791d24642ce0f88fb6e8c8286ffb887d22452b4fc262d5a6e915c72cd94c424cc98771194ba74587d8984f42dd0f5d2cc1a7e4b32d7ab0893de63ba3a20e54b266352b800dc2de80fbdf33598a68cb54ab9f38722c78ae0b425ea6ff031fd51e7fd0b27dab233ebe41e1b892f606ceabe49ba1cbc6edc9d2fb98f7b57e78297d3d6e086af4466bedacb4ca09776a12ec2b6949265a49bd4b6ddce37d9bb20aaf44b7e1284f9c4f5276ff8aacb3453fc2d2ba51c9a8e0c50e4fdc278ea5d839140e527de30726e8089e3c0c5c9f7d4b3887ecc5618b53da00b078a5e2bcb0c50884356eeb5594a1c595c8f74fcce6632446fab8dbd306c6d2aa59f99a01a35126919bcae6cd7a825a2cda0b3c2e542f41155747998f13e54c38f75d9e281205c4796e2ed31a08154c99653bafaf9d7502c69dbd0d3c3a740f3ddcd3b3e5f2786ce0c42f10d8e951c2d6e255c02157a94093cf4c4619b6651c44f3046cd0a2bd31ce0bd40f8cb2151dab3e13ff7acf1d2c3b82198abfbb861422461d3066cafbfe77cc489a52d3981f62a5c766ff6c2e29f3349c48c1c0ce748e2255e85d70c4161cad2c8acd3528889158ca05248b5e30d933badb774cc585c0a187a4b5424c9b15a322e0c5a6320ddd445eab8881fc7e34a7d751bf20a0a4f8fa6c05b55ac0e7eba2cfea298f5b3939383c24ead1a2c83bb5346958a5fce57c0e299b4fa4fa5601c06b96cba86185dd0bc115e215c7c7c9fc1d78d242f5cba099283619e7cf8aff8ee5f82ddcbbca6d33b869528fa4e3e598ca5d14b6251ae4ee0dfdab080d825abd6d08b91c28cc34cad010a3e6867c87c0cd14d00f2546d75e027765f88ac9e3dbde2858920ec6c6575ea103006f19955c3ac3cb5a8d41d33b8475bf17f05853d1b8c09a1b25784a66307fe7289388ab84bc438a46262f1aece39e3ca57bb1b2abf550d063334a1546812ea9d6c8a205e0e45b6ed2f6e115819a419c9578b1f2d1afe888c4b79b666937ec56d2b3144bd26adf100e41172bdaa1eedb2df12ad8006a66659574c006c344702e24b54fe09a3fd7d2a8a87ee3c55161467c80e26554455d2a0f0e4a4910c4bdecca2485707658734a6fd64483de1a901caa2984be1d108d66a9b5d04f6c2326b6cf8511ffd7a049959e3383604068a3e375f950850e356f5d2573e5f77a4ba2c04d04ea1b20eda36b88ea044f3706abbd37840b5f3787ef14094179129b54c283393fac80db3fd317a2b8f553f6459a7419d8b158589f04c5c70260255a1425be6a2de5dacd88961fd5ce8f49f278627b4831eeb3a6d735688b7a34124f935e03c60714db3ae3fcfdad8c8dc6ba4ac3cb206fbb7c4cd9310bfc0786f1b5e815b5360c49ee7e8d26f933823f05675343d7b9d53e90602b2aac9034a94c55c2e3210a6a9b6344e6d2ee78dc77e31fe38ce639f862654488cf0bb548eb32fc4be9631583c6440edc3327843cd80c96d27cca7cce4094cb60e3a701f845b02e9c91fd189dd9ea29c9e1e832d15084b59fb9bb9b213a02b70cd81c4328748b9a8893cd0bb1f845845ee25b9c1854f0166512cad266bf874687e298b8f42785c2ea1014d25e3b6565a9116941be22b8cf919327970bd956cdcd3dfd226c80e573079116ca1b223adca1264ab3962ddb96d0693942c0f33dedb2c9a662b9e3d0577c2f5b220892cf439be7c91b9fc89c270bd4f781bda9c1b97a8d5e650afa681abd0bbbd8c4c49641c0912a1a558a51243ff8a13fd7a1e7d0090ecca7eca19726b082de1cbf65a747b14a77889e1e998d769fa068f6341dfc433f85a41a086898f67007a9579ba3c29b5a899892221078faae6fcea923cf067a7d4484c1e2a412fd7ca898cb1eac519c3e00cbd8721b4ebb99dee40857a36c4db9ca470f830d13b5893788356ad0da70be61b0cee21197c6e74555fb5dfda90320a4284ab13daf28b1517552f50773421aaea469b2233b54bb2e7a78f526cfcc56c4d6040394b9ff8fdd1640d3b2bdfa797e66c22d2b29a155a2c25037ca0961dde875ee5f547602c66c609e8f6e53138cf986d80c78d0a5e8117ac743617797dd99970920daf81ab550f6e0a4f1caa0fbe1ac3f52733169a8c3a4bb8d684958a8c5029be4e821443a8f4145a04993ac4e20948642a2f35b6bd54d477f704a8ff3c7417b142b2b2e8591a6c0cec5c2de996ccb2c042d81e137e5b8c945e4cc536e678b11cd5c32be80e86d2f1ef04c897dca657ab54e4e4defdc425742c0dfef9383cb34c07b4ee36c5bdbeea8b84b0d25f58a3593b61d9a80eac846d89e50d9518ceec09ebfe11ac102547149125790c512d72ad759a36de382e7843f62758ea980317358daccb191184678632b5b5f1bac28e31ba1ac35605bdecbd9940ef1c7f252e73736057c43dc3cf543f284a4252f5fec770561443c5da8fe24e2481dca7fd0d38770cdc8299ea63a3066033626d8ad96b5393541a5b873aab6b5d53f8e911d02469ffa36e8c19df658716d0bf2500f3513811b7613eeb49488d02bb490858e2e017b17c4156be46083bb2b4e77d57cd1a716d0c249cc14ee368cc830c5698020ba66d958a59d0c1ace285f2072852158d12a856e9f31df085ba114c9a721e81447a0e2b93744e94117352b29283f8b541a1bd92f38221b339b3cefe0855819315b104d4cce7cc5881ba176991c26f5b3fb845416b30e7a3b2bdc139da18904c0161c42ee339cddaed9e88933bbf4c101925da4cad0ff333ee312e0decd57242d5f2e6655aa4b6afbe36b2ac9914e7bec2dbe0f8cc107113b5576f3c8a4ff7431110e59b26352e81c378118fbd9f70c1da569b62c64c8c1fe47da2deb45d10192a234d5089fc44406ad98596d915e87864573f2ff3f8484888c8fc5bda034b998a2b71450891b70c2257aff1a81f5ccc61aed5099702ec7b60f12985228763492c18322534ab3afb11105febb0a3f394a5b1e19540ec401127a76b16707273e39e80888231e373ec35a71c711626beb21d2bb07e413926454ad3d4cd8dc9381fff122a73fdb05d54b0b02e4aacf22321e2ff19a7e6bb9015f6293708a953d7c16b245a55e17c946cf08476fe4a0899f6dab3c14f7a6b284cea90375b9f3b7724dc781498297a398cbe274eebb29077002e863af2cba0c82e31562ac05ad5dc2c86331251ed000a1c830984e3c777df04c6dc178ad935e7683a4ad43294d01f6edb9e1797fa15c9191f88f6185992dec6ba7ff961be0c6400014790b17d50850406bcb019b0d0eb628f40a3b4366d68d7fb4d0cc4aa706bfc3c8f4030915b645af3957f1b469189de2cecb86f5236e225db0696937b7d3d56216548358530a994fa7fdf2276ecbebdcc42493b4fcd2fa5c71d9d6f166aa0b225b37e8031f67da4fc222164e2ba52deba42aba3566fe4a6105214376d70d1751f284ca131d213cd879792fd7ff1f2e9b1aa9f896b8924ef7a8e45dcd8fd97b9e92032850cbc3ff76d330dc40b45d46bb3543bf69f051c598e303b568a0e1e53a2c5542e8ca32f4336011e36a9805ea5fd446744a4e5ea5f55bb7a773899e43ea13aaedc9b53bc1a1073c3fa01dd01da8830228726146c8de594f386a4c920009602807bce027bbab820c95335269406bf82e703fac04638089b38fb446ea734d1d178fde7f1c4e940d2b52a8e68145445cc38fd55968e9fa3d8feb9b676d080750fa7b698947971088f12aad99ddcf41f1a5c93ab9ac679edfe8850c6ea425ac39c72ce2b5b814bb3adfb2f347c3e54fe9adccb4411f083338eddfde64ba2012d45a5058ff1255163dd71e4fa4ae4da46faaba14297ea9efd2e6baa41a1edb9508e3b67e3bc6e8975c0a29a2fb28fba630cad14773154d2b1abba50cbf7ea2108d8e0a8ed1593cb19c506dcb42ee260114e6bdf8821146933cf7f5e3272b13dafda40c0169a98218d85eb79672bb1b2e1cd0853262e944ebd01d21602514a05e342248d4b08e22eb43f8546b4dc430cf8a8450393ecb1a31b9d0dd8a2a61da570f94897f5beba7a462c9f5367805e043f235782e7e537c70eb4531f3d3c3f1a39be4fdaed6ead26b1ed761e539c2230655b14eebc7541b234a14370994e7d6c78af4d41ba467807ce2218c4d82fc1087a332d35d99b1176ef3696835e2e40cb4c6b7ebe60edb414cc331e8be095a3ded7e6bf81c5d0dcedda76d04c2aa7a1a0688db4fa6aa98f3c45d61210d974c0046fcb5d11904094c6700b73d1c48cc20eef16ca8dbbc0181e88d532d18ce1a2f74b5296c1c58ce32491535290fe453d41413d9449d0cc5fcaa79b88d8423569cccfbafa3f502d44b056b689911612580699422ccc95022fd7ab5919c285284bfecbff3a04e50b7614d971b542b4c4dff36a5de5de251090899c8a99b8b2ae10121dc2224a5ae9e0e84338545802c060681e0ce5893465ee1d2f38a883f85865d4b43c2a240dde6857ed476803c60dbf578d8dd1fd2f32172a9d02b3f94c95ae5f2cdf2b065452615b8d343d91a915516b930730a58147428dfa48064e4a99fe91d550bdd03812a92333e4b3a78649d1f69b484fcca9a5371c6367682e864194a4ca01d90eb79ceb9a35e34e9a58adbc528268d2e1580fc197a89b6b03086588fd187cc073898f437bbc669cb128e7b7cdfff8544cf517f2b833cbe1233a9aedc78a7b9f446d3530d1decceb6e3c1e8f5d6419cf025d289d75badd0f3e18987558f50b526cafbaf35a6d913aea65b16db180a0cde4c0ba6b463730267efd591483f515730fdd8c224950870f1049d3a0e0f1a4c4d2efc3f125466c1b318c394d0f134d919c289fb21801438fb85ee4eac5a561d20cddb2c176733a4b502370d1ea802202948f44349da347a7280f466c96c5242756b710a85d2e88f0251ef70031e3e85342a0a960badb1a693dedda6da76e02d2c0993235a9367a14e38902903663f6b3bc7033108de9c170f68418c40a3f3b9c90254f7f4372858715776d3905f7e2cc4db9dc83f2c9242f4d73e5cca68273ea8a5e9048cfb50091944f7987974b0d8d9a89fa6c8e81456c4f5e1240e889f36a26f526b753ff549a7b483c486407d719eee4855cf8e3d6da9b7c4ad92e2cac7612ef1ff41f5264711a8a797d6d424921319638aa1652a77a4c109a607182f9a0b22b2baff5bde75c20b990697cd60068bedf5463181ca2f820fba50599e12eaa09f97cea7f7768f1526c4dd39c5158167182c1fe9b82373865a29ae35e1d76956ba68ee21428da18121c268ddc9d38a2364d695a13250d139ac47f5619fb8afcf04fda0ad034cd5971e1d06038e18f431e4c07c3953f792d40e26c10cdfa523be712bbb263cf089c353df708a60d1e13ca5ad964ddc07e979b15b208471676a0bccce2238b2d5e6710a175d31eec4688a80c66056ff03fcd10fc64244551556fc2059a7c5058d4124fd39a2cb493a13309f1e49d3200f6e0436e0fca00fab147443b7fde8158bff75a24eb7c0acfb4cd047acbab1735b61a0eb6d61fd81f9e606a0cbf94a624f30649adacf1c742fafc048c25c8e1c08543b105bd7f576b6d38834673d34abd69007f76c311c4e902bf80464f914ae5acea4a4fdf20c1a67a41e6153e3c87ce422ad85c3595663c108bbac3323f791d275387bcfab21666c357078436d9a9f9ed2208320f0cb6322c32125ea1673579a3eaa76a8f0e726f9bf2c0bea7abf25aeef491fe22e674ecbce9a7831453771a1614cb67187d419a1ecbf8ffb5c108b085d29867d7e9846d4eb6653d4d99dffe46352533ea85e793fd39386e312ce8fb5cfda468903db37e52c3f3281d889acd0f2df8afcd19bc29b4d8aec5f156941432d679804700c38d9c07941fd17d5b43139f192effe95247eba61647b77caac4fa21584ef124b0bc24dbcac1adb18bbbe264dabaf339650dfd09f5c0f647915c15be98b165a8007e345274b0ae5a72883141e56722762750415c47d6f36ce0b0da24ceb7274ca6e1be5ce260322b2a698ba737bf7050fbebabf344a09f1cbdce722e7f566af567dccb030fcd51c9f667ce744f900b22ff84fbd3e87dac73e0035517eb2668a14726758cbd9710c944b9d51502442be1db150591dbe1b078e3d7dc020030b32feeb27c281600c928b3a36fb04ed2d077d4dab56e97e9c2707a14553b158d1692640ee8345c3fb2fea77d25e4a10ff8c2cf10df133e888d6e9f23f14d0aaa2bb7731cd578bc5bf541232736022dd9d0c7530b13059465556cdd0e5a4fcbf2bb92021e21ce9f4da86dc3a93f3eaca39dec791e11a7e6ca4f37594f36b1619221e90b7df84ddc509b920a610b61095788677c95f09b4867f988e7e3b10405f19ec6f21aa120c26cba98f05faa090a50d14288d5c6542fedc914db46f093dd0df6c61bdcfc5dea94ec72e647af7951e8e884701024296417074d71daf1f269bb7f6f077f76a3bd82f272c6849719cf20ef8b0bf9bbf56f7ce92ca2c17f0a217082387668bcfb0cf272312155d8a28a45d3fc9edd1e733fdfe1168a96657cce44b4f891be357f75dc98f826b4d4590925efaac589ded1edde8a25c4021928b612cbfd7c1bf46a7ac8f8813818112eee90017cd8a2d46436b6ff39e169a8869f69773f653ecc2032f57f0725745c9b986ee452a7f064284628f32599d3a88c429bd34edd4a489b5334f1f4dcfea84badfdf5311ed0cfb48d2bf4a9535b3c4a5b648e8085896783845bcd0f53bb3eee625082e383883a4d0ea9b3912c088eebde91def124e4cc76f808787156a62fd041cccd82c79c2d1ba2401661d22787125542e19655782b635b1a6f99cf2503d7f5fda5ea6e2a73b6bd5d291fd5e916418aa11fa78c2c68cb6036805224e562df0ac5ea32d3972450f5bddde9f68b7087328f846fb8b6f81b2ffc41fa3e1ff6e3e52a2ba4fc56eb8904b832a4dd51a31799e4aed56acde29f080e65f6388dafc04e0929ef018234fe9183c41b0d41694070ee547e18e77758a574b98518e1b5fc037d11a307ffc2af06c03dd84d68cafd7129c1dc855b0d829fc163d15bfa82fb13a929e3223e52b0a8b46e29e0a57618064d62d5bb301258ff190132728490fbb4aa5a45e741eead75352ad2bce7dcce531e20b9e5cd2c3390f1d2517139fff5d6b422e383ce9457571c8a1732fc160e02aa97816230adae105acac57345bb50959dc3f1635194c467068c4c05d4aee649594727181f8d8492e73862f5cd477f5e1823532ea548c301e85d372a72c968620546494e902e25d7922ba2029b7177cb8296b58e8dfcd16662e0d878c5b33c769df460822a6dc6e9a63612c2d439aee9bcb48bd332f99d1df7314a01f25e35cde0d85e89150e2039b0ad69fda9642c5095cc09f7d8ed98bd01caaac4bdbdb18e4c8deb0ad1b632198053546057cacdeee19b503e455d424c111852c65b1cd87dc726b9c5e6218ce5340d446150fd3e2317479120f978ee9bf60420033c040dc3d667e92885c6ddf33763c4dd15164c7803158194ce2194e88e94be3548225ddc1bf29625cc0eaf498d97ea822cf1dd7e31e67ba4d51a94dc327d85c4cfbf17340b8d99b73248d3c3cdc82442b338271a0fa01897b4489cc5a2defc5b8bcc00842c9cb252a60a03588e1294016b3da55fe866df3509cc94dccf0a410ce99e40afa883971bef29944c619217975196ac4a78166343d899bc0e76f87842695320178999c0ebd8a241f39fefafc583f2255973ce4c27776c36dd1bd7d78d1105686950561e9dca5d172ef96908c6ad7ec6f563fe314eead35309ccc54f983a0b06aa39f68ff588a982c3e1b23b03d4a7732a8324e2ee50ff43f98b3158373e9b470794c9b9bc1704340c05ed490fd03050e0a1f91a2d4831d4db04ae16db5b0117f6e5d81a463f52230ff8bf52e6797281089b4015a381fcdf019783c291b55965fee711f1437089a28d338722e070e8e3e71909b1196ace829e45fad7365dd8270f4d4e6a327725a1635b33b8c4a6734da08322cd7397cc6749342883c7ff136f0932286e41a5120fb12c2d1d18b23500c608bda0029e41bc0d6a038452a12d34641c28c6d42193ba12bebea464eb1fc5a00ba6df921f415ada821f2c65d87a254dda1f4a0fb3c9448e3e8fc4fa2e8f39ad8d20a91f7bb65f7c4ef62417feb761913fd68860b20978da8e4133a3ceb95e2388ad1a5bd10388a795d9ae8ed3cb979763f42b912a411cbaf22842607bd27417fb7a200e9f61133d1b4402183267f99bc0369f42580d9e90819d762e5d5b5ad0182cc1acd4f243f5fa1de20bcb921d5845667c453ad3649be0316e54d17adc98bea1bd4fc46a303c2ad18d1a81f721512be64fbadd6ae367c52d6c91f663cbc41ff79aef786ca552ba820e01280617a83059db9430f2cf0e9550dbc890f0af1309cead5e0f209b0a329b5f315a0c8df0408780d40f1c876dd6d7d973ad67818a0734da102ac34aea624e3bd6a54815e895cfe6f32a9f460d07b186c59cbe7b0ffa7b91aad31ba977733b4f2fc3bdb758fcb9c31eda20f69c346368bf1713100dad1932d0cbea5cd8ec141e68b5dd5e8c5d1205eb830dc4a78fabc0b80ab1698ce5fbc7e96abbb2a629b74db6488ff4559050c79b54da564e5305e21b10f799cf54b5e347b432ac60021ba63e53d0ada9e384dc2cac71afc203e2f180773c5a510030765143a7319a2bcd86513238227c10ab23ad11fa8f5628e27a555d548309017f61a685ba17ea7f5add012607a1e9ba3ce8f4999a6f12526628f7ae8ce0e709867e7c550d026ba9728a9d71a31cc110cc202d9dd4a61f091cb7852730fcb24b491e079198a4913b26c2b0f32deba055cbaba8b184b8f4f5a90900e8942e464ae1e00b52ac29f3e40b7704d9d074ed8dc368e08d46954a53ce64e0f3d99daeecf4af88acbfccb9a7441958494d18cd0e3f3f2bb704815b1c595937852a7a26c058665297d195f41b1cf3fa17a3e7f66213741450116e043025b6a56c1fb837c641b00cfc259b17f47b367be1fb6949582a0a0f8631f2bb1fa63f3b6f089b3fae8acf289451ec8bc2f7c832b5c29e415efe905d3aa3527b5761b4473b480e00e244d990100e3e88632491ee5d7bbdfcd69adc481a15098e42cf36694f5efe0882b005c5a41903f0f7f905296c1d3787e00282f164c2aaab966a788770263f095adfb748fc416d63043e45fe3aebf159352364de4c8e6275039b1d6a2e124280a03f5e6d26529d855f7aec10f8ce40afa49054606028dc465c9af853b3145dd61d875a0958a521a18a55f6d286e60d160b83f2cdc27b4235e650714ad9c7d00e0c347e8071a1927b880596e745354b28e82ffacfffc3a59a006e236586cbc74f1fe1d3cbece560e7ed8fd1fbf98f0313d3c1c0d9d34115e38d07e831a8f87f01b695cfa39ec8c3ca4908de6abad71b9e3b9c9c24e05358589708d413934ab1bac654ed9e05c96f3ee89b8070c022d6d61a6f7c55592e8c7565323c1bb0fc3e30781b485090d0757decc9ac2b3d09a1894aa213b74d18b588cf2f935528e8053a94f940fed7a03042a109eb702513fd9b57d467c1a27f27d9586d09c337e34fff214cd8cdc6449dc220d581ae25e2990d8e407a66026338b64a1842d57c292d466e305ecbb87580584136b28b3b8b95f573e723498e2a4b4313e0fc3754fc9b31bdbe7e1237f30aba8b6c72b66f3cd4bcbe2e01ed602c88bf20da00410fee28c37def869f5d06563cb232bd8d245f06db57deed11d1f59849f6b2ec8cb075e4b9c76c48b8ed47a1818884d306f2734aa8389c5cd28fa0dde97c13cec3c18cfad30cccb11ed9e213c79b95124ff1297b308cde9ab4ca097a0fad72e480b95d06784e572752ec1d31e0297326dad5725a8c0de32ccb4ba5d6f2af6f0e0aad26e0df52a25eb01b054c8e62e68081bb606633821f73d84c2d02df1dbbe98a22d453113448873f34e875d329e502a743acdd0e6f3f492f5874dab25d3c9a13b9237ffcaafbed1e071443c3b9eaee983586663964538018b2e12053a147584fb5fdbf5e51bea540c912f75cc332acb2884c1a3cd192b5026abe19e01724d8c9429b6450c14f4dbe59556c33d4e324345c14af86cf84c17bc26d1971752687f1f535065cdca38b7ca3a9b9ff6986b4f4335f0d1bdcaf6bba96482bb56384b727f5ea82b01ab96f86ce556012c03f06e4fac0e551a417dd43e9488869d55929df97404c29da98ac4c1d4952b0b25294a3ad89faad5438026399620ce36103497885a595b3bd4a0d526a04480dddcb233f07464ccd0b1e362a01365516deecde5ee9e28b329e2179504515b1930b6107a437a3d65a866f206708c879d88682c74b673f2f1dd99d24908c68604ffd81056a82a2b315b8abdc828a735b3e5a6e3dfe66e1917a628017a0f2856fe3002574b3c5d46d5f5b48a971a9063f1ae592d4f456f7411544c103d578b3d82be3a40c90fabead4c326b2e359ef86d07bcb7fa31ffa43b6bc96dc750aa05c4e7b3b4a4448457d806f9c8df1a5b0f53833425ae384e48bf565ebb45d119414456350dc8bf033dcc31663b2e0fdf523d8406bafa2010b2002b82823f15af74a3ad7c4c3e87fa4e03b63ba54465d5af2ee74988cbb32ee29365ff2083e21d85da37efc84024e6b51b541f8103a3f4eab92172f527d12360c74691acd3e77f1106849b51ff7f19a932900b531201875b719acc448b6b792741020ab055262581e78213a028d13c3d901123aa2c8ab098271374096ce0cd3ce108ee55f5e145c1f2a60157851359b7e5308ef576f08e3e9227233e868220ccfdf6ede96b6344f3541a141d1044791d8f08c30773a50df496cd3c4ef93e014870475091b88e36373e984e89994c5641430ba57bd7553582bc42ab21b88e179354b733c2e6ccac454b283ef2f350e02b0cd887fcb8bd9e8cdc70ccbe8024598fc03ead9a6019e866a41590211ea6a1efedbb79433e6020d83d6f690624e7ed401d6e90384a1e85d993cea9f83c199e16e462be0f383150186cb789dbe08f7999985d028b0b328708fb49bac9309f99266a1977b450abf80e5e59990f0690ebe02916cb5b2817982e40bb403afa0cba3a58f81ec7827f61484fdcf27d1169e597f2252c2188ddb984c8f4f0e8514f1fd90aff02e878401efe45b92dfb6ad4a317454776067fba0ed215a01e9238790280f33bc782272746de898dcdd85436a30814318ea9410618f35cc4877c7cad245548eee207d8225919594a024b8afba397e6d689bf4c15e87501e923e810d234799b415bc869a13fe4b1fa4deb99234ac7f561894783a90e447654df6faf1d98a9877f61a148aa1b2ba37d2624babcc86cd63497393681e70c67bcc959407ccc0600dc1f20f8770953a4f87a539206941ca30f24cb7e9d6062f8ebac08d840688818dc5ca9f98054a25af61310817e05765198eeb76e7c86487bca056f582fd1a39671f70657f1f59ba5622000e5443f6a3c442eb4315b6787451321f0e5efca5d19bdf9d0897b2e71d4c2a7095c2a13bd7591e1f55e7614d9deda69170ed7d1111f2c6216d41e8ed050bee96c7cfdac1318086c2915b955bec2821441269a2ed0cb5b114a82a416a5e61e2a3aac6a16af8a4854c242cde70c27ac553f4e939288e5e85c7a1ff319e5ad040750a69ecd917cc87418fea409359f757f46a28160fa2b457e99fdfdfa8ea7070adfac3eb5edd733c55e929ac8e364470da2ce336b8db6f55f3276d9da172c8a1f397bf59640307c2679060b7a183a6c1f4f1a5ecd19bc74c82cb7883ac9baad7a4c11cae903186766c33632bd780681f34bc6991c0233310b9e8ef70f6a946bd1186ad330b1668bebdd19cf5861fb06f4078f5fdc8df7d4893f208ed8be7a0050a5fd52f76337581ad61b9a967105e5cea8336efb52cdb3127e0d3152d4d1df52a94cf12a23076da06c5f63c9e725a5c23fdbced24fc4c8af680a56196dac59d8f2095124c0ee825b54ffc3348b5587e075a6fd6aad72b19a40aee821deb17be888fb4c0b3eedc467dc76481e01a506422bc1f2d977a98160923d3a75300934132e2184b80ba5d93b93fa5fac96db9eb4e43057bed1654203c2f3f2baf095fd335e970772e52e69418d363974d82829674ba0a3c064fa407695fcf82f1e691b6297df41d5312b8e67adbf4b2404e9b66e747cd3aa25abb9243111ee1f82fe560dbb4bbce29d9689f5f6da7e59f96dc046a7f7d85633b7210e684a767bfadc5ccd200f5649d0985229296971d5acad8fffdc2c1afe5b8627a8437e16f754dcda395d1f49ab7189988e3bd9fd26618659a50ac43b1bae60fc304e0c02ae1868cf7834bdaee9a5c7fd0cc6096eea06d17e85ec997dab35352eb77af98236451ca72018a5aab42ae11b02fe99ed0bab03222c82022e1bc0b2b0267f7572e3f8c4f6038e82642b023feafb046d7fd3c116be8fd7da587881c0ffc5ee8796613ddf4f82df91398587a629961ffd65da28963ed2eb25fe33962bb4b86b97849f434d415a21b473e6f0c9257ece9bd99ccc9cbc2c04da63a093a1cd6840617407ee5ad5a84dd4296d09ed2c52ac376e4de4303a4e3bc4aed34aa1aab836386f7a6658c3494b9f2dced9d9a96186b89c889d20a985266bb8aeed13de92a03e903cf33d54597003e49fb427172f988ce4aefff334f84f213eb76a2ca398b8c1135fa2d70f74f5e52a695f9d974c3dd69f8c473f5094dad7fc92e092cb212374182639e71cf99ad2fd4c9b97dacfe3e367ac6c5438de6169c7cd4dec5551bfb1e06be18cfa45b146b08bd3f47a6f105e54496f5301cc358aebb03d6ea5f7919a293cc41fa6c4305a75379eeee9080c2b402cfdeded5802e1c7d0e3dae484279350e1fadb6206e12b8c2be0041666ccfa3a927bc0267371cc849cacefc73b5ad89295708113ec15cf83efc0e76ee14d252ce68b2072b25b8814c2533c9b387e344dbaa3aed98fa6e886061ba3afaa42c566ea84e61d7ee0a9e1c3dc5eb74a5d818ac0356675715d48b9492ea2f7f18be5317227d7e0bd8163eb2cd7cf018379712ba3e23d47afb61c270c7c4cd25321da3669df270526dcff1a489fe1db9f313f8abf0e3dec5e7ebf5b01693499b011fa95eee93e3b471506597d4f4ff37e0d2bc9f1dcfe9b259baacc350f4d6ed4bf7b891f6d5500441bb3aa3a9fe7c8ba49faece7be1489c463664797ce8e279a4464162c22706888345828fe810774e4f6b38885810a1146e84e845bdac08a3eb5f35fb9916c73b0b1ef4e3e4f4948d3272169977558048fd795f11c6519b8b63e2d05d354d24fed29f7be3fe302cda5e24c7a52ea7d27b4f7988375d48b66d4dd0fa5da3efc27b562ac95d895ce51ffa7715566af1cd69f7336572ba4a7c5280a15f3373635aae65f5201b6b676c86dc124d85f164dc759da2d5fa4efbbbc5dd1c4f91413854a15876e3f748d2715f98068581421b25f8ae29ad78c69c4416895bdca8c46e03aca97be88e2ae480449c2640df963708162b07b6200297c07c29f051de67d817d3988db1a879ac6167ddadc78b4e61b831095e4e309a7082784d6afff3ea16ae3f35cfd7ff1288067ebfcb284b4f07c61e6feb52750c79a069a27398f4759838e6117290eb37fdce596cce00814bdf6fbdcce3af8466de3fb9da66b9d2cf9e09bc2983c0071f28ce3c67513f8ce561ac395ae40a1d7740c6545c667f94ab2301b361dd6925445b4c558d02a2b7503d6b185a5026001f7ea6fcde53a88e6f9be701b5280b5fa57a98abbc6578cbbbbfe9183ffa71d3de546698ff0a3dd8c85e19de7ec210443d5e84c0f774fb699c25ee8c20210c51c28f001037289f6d27f312f5f45fea8006b110f44c100fed60034a299e4b9b35f216d905418041a35b26140e196fd72a06c77f61cfce704164598b54f27b9959723aba4b8b2205a086141877b223f1cdbaf25f172565ff9b7cf077cad9163b10a1b68809ffa3fde074fe796546bdd39fd8138c5c8ebb503d69595cd968995113450f87beac4c6fe2b66251029a43adc74ccb5d5fe21993315e3434ed13f2d8e81ed7dd60e76fe52f347feb704f036470c720c28b965fc656808c2393c0b6a1b517977d54d17dadf34befb4fa25ea6c7cc0a12a4cc00aae7a174b443e483ea686f414049286f627a3aca5589e935fee235c3518f8f0c7c20d2319e8667dabdac051178d4a4cbfe77ae67cd2ab45aee7dd200ea90920efd30717f8bc367b01ffaa0a6bd3c0d8b8bf8550dd81ecd80ab3d1f4a248d2a527386223317471fca78cf6b619ce7e36f052882ec1dcd68b212727de8dbf39bc9c3ed0226a910bbc6ffe5097b28a229220a6382841d653707fbe69e03f6bbf280dc353d887b88a093c403629fbebf695022c720887bfa65772233ba09f782c8942a90cb1dfbd635c59390297111e4e646334a669c5e573f2d1ea120eceadc5da012b584acd9237927a702ea2f97d172d4f8fd0f900e6a1874800bed1ddfd771d644e99ee54c58c5e32b500b8787e6fde59eca28b8c114d14f834c74a1f8546fc3d3bc745ab9b423b369353d0b73a2b9c9c1ba3839b3b91a557fa7173fd8ee029698c59f0de3bfdd4fe0f70714aad4d743b6657f1ef7fe7f1e794d2b33d068a89a04d8051c858f90b14f647c5b1214e25ee8b68ebc1ca8edb6e386dd18ce9b45fae10e1111317114aebf4051acc57b53fb8812c68a8d00deb63f130f05896aaa38bbcf6f563aae9004a3eacf21874f74a4120aee8380ad2e459c28a3ada04ca520391d832e2d83e5de943b110679f3a096407dd3de0ddf802b0d98d3c1d82ddfbff4e7f3dfaf23a888e3b917ec717028395ce7372a8f05382201ba57d1fdc37126be791e2f22feb0777d4663a940561a2c457d575fa57cd5596c99abb3825d187dcc7e42cce4e7f2f644b6636cefb479f7f9012b7f0622ce5fc033c7fe846f076808a11918afb3e4355d223abb820c741f03375303b94613a16f926beb660ff832f46db3677982ac8967af136b65a8f5dad46a506485e3cf24ba69791961704161eed3413c376975940a190d2152e5b7f3cc7660bd0667adb8063a90d72da36ea8017647547bf07ae857f5b36d55e23656493ce74b4059a409a71b054a8bed97acc3966334cac1f98706240204f14b433e3d261a7b1d08a6d73309bc9fae4a3451991395ba6e565816f8ce4e752c7072d440a41fe121295398de1760865b178e10515e3cc3608139eb711df2fc9543a2287791342a4ee66ce97d4967d1b200f549fdb3a43479195541896cb4bf5aee709e8116c93d40eb0ff3b53519bdd9818bdecf69e7ce0013a4115715ea341b746a4c0260d3faedca6b0b89a3a55c7080054764d4e7147c5193e900b7400735e040c24e0f2023266b9f33d4d9d4a037b742a07ed223ff4640d512125c2484caf71e5439097ec2555377a3780e788f266d9058c33919017843aec43710e98f204fceafa42f6c1d528df1e2ce2084958060c6f078a4db164b9a4eb7c3e2ca2bb35ffdf49b0df7740f21f0c80e5ec59367c2d524524d09f32529eb4d3af8dc0f08e65b2277c3f2fabe23f3d347d8e3598a110d1c66860b2147e1e851a07270c4b052460c32cf4a3381f13e79b39ffe91962da126af94c9bff80196f0fef0a525eb4ec9f2b63b76bc2ebd3df1e4a3467fc88c12cdb62a24ff01b80eed8303564a02463c0989b52f669f70c37045823d470b8602fad1a40681e7b23effc6241df80b61be2369bf1580f260a414b5c87343b26e8c16fd510187904bf8fad3f5b1bc2ce905416e0aee0f64988f6269250882cf9dc832ef83b4cd21e035f7ed89c11d351c9a2971f5517c515e88eefa5aa76d65b90c5752550bf9bdfd0e3ba634129c3c5af487f11fcd480d82db932caf0fe43defa070a5a4a099ee2ee033054002cf5a4cfa7c97ee8eb9afddfc0dcbefe3a8f56a9ca3027669060524e79a6ba13c3eedc87cfb051346b8146fda85329ff6765a983fe848ddeae3048aa8911f322580e5a7dc36fec224d47f15a324e43fd4a091c691c28f79828c20461c14040ad48a9059305816a503f2460d49daa6f7c11696a91b2ace1fdec5ca0e4acd0c517debf1827a4a560c79d233ee01188823eb83f6e31fbf2120e805054e012fb5b9acd453c1ec3e0a9eb42c2fd3a71e37b3d6882464efbdb7dc524a99520a8b082f0924096ce3f2fc31b04de9b7ff0e70f90bc03f7adcd45c8699a91835a697edb1593aed47abe99f085a208c03472317d2f3921b3f15395a4d0f84505115284d8458b2fabc6b6a22c4922ba4be4b57eada51f1539ef76f32d5486986d5ee5372499fe3ee84d25a4da67fcf4947619af8a4e824eb2d7f01763cc4ee6b14dcf859eb8b17b232277cd1aca2254a04ab387b6444a21b185525ba0437c227c2aa91c58151d3464ebb1fb7286732a83097871899263e864512f5e3160fdd4d24953f3b69647b0bb0ca83a898c528dcf848fa870f3407c6f1c18403e3c88f5f01b6a11fff48ffd070ae8f6f3da83a0263e81f9aa5ed237299fdb88b25da48025e82bdf8f448fc4863afe0466ec59f338051a7d5712fc608551bd3f931c79d71fef5eccd39d342340c596760589504a2f8474ffb4892823dda477c2c7ef4e6523c299aeaab3577592d05553d3c444444695c807dc48f11034c13ffe32624619a780315d5488f7bf157c840cd3ec5d33f7ada477c9389bbe809e34699e44d5a4e44a41425da673dd9fb50b9381ecdcacfdeb9202de9921d4c7630e91d4baabfe60e37e8313a6c814629c76fe6523691e88d3e3f66dac7052ea55fdac1a4b27b2ef6f3b9f4b5b8757d7dc7924a5feba6ef14c228e3c31bede773c568dd7a2f97872e9699a664bf24977eff88c1ab24fbd5113733837b300693bbf7fd70778037da1c6d54fe90cc5c223272965984069fe5535aab49723ef25316f56b2b577e3de54a2b526c2f91d85bfaeda5cbcbef2aa5ce887bf2b7aea7bb0148922c27b9674e015c6c270de49efc1b3ba8d98fbe81b6e81e67d93dadc76db8bff131ff5118d5cb5fcc46476d2fb76deb71373bbfeb46b40d712eb9d0d8f60ff9b1c42a0a51f1a9cdbb17fbfd42ec42edc267c906e055fff0cbb4bb18e5d1f81ee338d3f4e7a1d806e2a061fe75d94fba8feb2fdb3ea4c52105a5683cc331fe57af9cec45469960a49561e662988d8e9a636818e39c6aa8cf3d03483ffafed6ba5652259d6a7079ee8bc0fce874aac1e5b9e781f9d1f3fdb970dbece95443e9b9e789f9d1dbc02b2641949efb20627ed45c90530da577f922257bdabe74d28ac4d8d3668b909494eabf3c4ffd17eb43457b1f97afaf591e5289b91d262ed6870a8fcb97de06ae2fcf3e80eb5dbef4fe3563b8d8afdf6342e8676fe4eb217e3d3414961196e632073d7a7818bd26f486a2853806b7213dbfce0c6e537a664e7d8c35f22fcf8d9cd47594daf5144a6bcdf8923aee8ede2c7bbea2b615292addec575df8650d5cc76dfae9477b3ad5b03df745467f03b3ff6e341eb7f93ed3b4ec6f80c0cfe93182047e0faeba2114037c1d0dcb201c753f2bee7f5640dc8395079eed39bbd9c02b1db8d91c1f007c5e610e2d1595dda3d67fba64cca5131a1fa065646ea497c43ecbaeebea2e4a4d1ea40db1c3184e5b2adbf90c2fef27949ea394de5c8a7d89d48d3a4dcbb22cf34dcbbe3b2dc3fec22c4b8f269899f9e740154a6b894f4eea4e3e7a9ed1e85fac0fcc8fdee7e549ffb3d26a77f25277727f97fa31d607e6ebfbbc7ce98847edec8e1849a24e08388126a1ee94591e785e1ee679b081573cd8007f87ec6de095cbc3581f9f9f950e1456cd66f918cb2b98b7a71d983f65d6065ec1d8d4cac57ed7e7c08924759dbe5f3ebdd4f1aa76bc2219711b6ae3cf41c85e45738a452a85d16b4af786c20a2b806c5a86d16b426f247c8c1bcbc8fc78771037373032333d6a6a328c5e53baf715db651b8625faa70ca334cba80ccc81af33037c1ef833f0bd0ca3b445c506ee132efccdaf0c5719d79ff20cd72c62422b92a71d86fea477f9d38ecb9f6ce855e94f455625fb3fab51fd6a3fd2e8e75e2fb3a4d27f7e8c310c7b86b0ca0863f04415517c981bcb9847be6919f642ae2d5bb6b461c31f39055c9ba9d248230e3448c37d1b3532a1c243df1d601bac2bf935eea8fd66bebfac0bed3c11f981757ba0173e7f462e7cc242725e14cb8000c9b0ebc7bdc1000244757343afd93df03c39af99197a4d197fdc3b8ad643d492c633d767d9c32c756fe0f82ccb328f8bd13c26bdc6dce8bb23d92fb5731996f83f6ff4fdf57af8a774a07f8fc949cdcda8f99db94392bab687a8eb2b0ba1761bb98ca2d55c9e7fa9a82a7bff1e32c398dd8b3fc81fe87dc557493f320e7c5a03d9356b3e1b172e818728855dfd7ab8d1e7c69883da3f82bf74bbaedf5ca575596cfb23a8d35205f81a1ae754524ba7bb41a51fd4b1419dc1a57db40f7f26ea217aa21d9d19dca6a18ef6e1ef704bce0d4ced238afafdf7a0dec0f3c1ee3bbb8da8c76d3ec67283c9543dc6de41c9ad37c4a836fe31b84d9b4c2c1d45d4af9a58cbb2c630da5996cdcf4e32649f39d76599f6cc651af69d61df18f69de6f33dc1e73bbf7f7e6f9bd6652719605b1ffc9da35b859164c4fe90c48e59ea4efba56e64c991be1b6354fc517c2ec6974c13396b993ac22b47fa909b3b8e2285a8332ea4d18778e10ffe23bd8928d643ac228a262e57a106d1355dae420da1abcd8d8aad52b799736a327e47db9f034f50a532cb94aa3019a6653d1cd146a4116544ac1e5f613a355373a660ed69222d510888a2101951374ac9d0b40c4208bfd4d58e348241240bf58854fffa7e97eeeb9503b9209ac6715cf771cc6d9ab669ad75d3ab75f7f5fb302f0e3ecc8bc3f51a65ce24e3dee6d20b8eefdb2503b45f920b3fd2adc298e6d53db7e95cbfd60da7c5410608c60584fa994c8f6ddd551847f56bf412aa9af6d95fdda7b19643ea6acfdca5659ed1ab65dd977d0ea98b437c987539a4e86557517825a8661442088f5c11218cdf18c42086c53859e8daaea750c5fdf526a822fd75913a18ca6d5e8d4bd858857d9c5807e328f818db4a837a59ca2b2c5169d462a4f3e1351f9e88f80cfca2664f214c72a1fde80ce0d7f06f866118b6759122b9f39b5252f4895009245ae7cba9e5f2155f984194032713b58c101a11bdce06f5bbb9fe566aaabbbb7b8ada2374990bf2566ebc1ed7ad855951112c9229698b8c7c84ddd9a0fa5ba9fe1f17b5cdf7503c0f591f640955a1d5242474bf2b0b64614ea06ab2202a42b9110b9a72b5645e7c980b6efcefcab2a36981a80883fa4d4dcb8d3f599355194ed68dbf234ac52edfc089d01d627141f108dd3ca8a87ff9d2c0f7cfcb57d608e2c6e7314585cf438a28acd13dd078e693555a1746eea018d48f85fa892a04b1b4206c412bb55fc0838aca97af44410dac9abcd48dac0285a75c58c19310b0d0a47a603e6149994058e124f017ac2c454d60c284279a587914ae7f8de222faa9824514222d2d289cb8c2440635c0019117b3ce14c040e3410aaa002209fa53646967a235c512ba5e6e4d31c6c52eb7a638632ee1964401cae2074638e2889567e00b368a5a555650831c1011456bb68c52469413aabcdc12a2410b51849c702688105b422a9861e880460f5cc084222ba2285d6e0941112a020241ce979fb4d5e553f9358b269c70a5fc198f16492a09130d88e81a0c31c485156674b9e58328acc009723425891478a1c5d009b24c2088b450e2074b10f14bb2c052ad27435c78b9f5a404330373085f620330890c942e7c53ca035888f0f324063e40620c278e085263b25670834b0bf9de9c5860190209126230c61a5b8c20011663e0e00acb77b0022e8eb82e27818b9607d0b8dae5561223b8dfa7a4179ab713ef543a33ec2469263ced7ce3369f0a08dc72bf4dc53852de7a250a625c1cb77a97ddee76e8eb4c033f36a1ea406666da1d8bb40bcb5ef38dcab95ddedc15aa5cb4bf6ee6d2c138ea9ad736394d9bdc77525cdea53341d4f59adc3431aa76dd8cb445bddc26fbeb395bddbbae87a155e15ff1da6a37ca344d7bcda52bfda8ab245bca3ea3ddd7d787783f1dae666d0ff1cacf58e8d22b73c9a28114d757dcc69d57d362db42125d8fb7ba0d13d10008d7b3d0d80b26975b5d10e072ab0b2b3709f44117f785cb2d1f44b9def6d98cc63b2e8344bd5e931886595ae5c605e1614acba8f32737445eec35312a256dc184228e0b82618df110ef11243dbc9ef934db360c8b379bd0b351f6b48cfad55c49ad8431044693fbe3728b4807d7c3b2e36661e53211164358586935717d5c6ef580755b5f64b9dcd222a8a585932bc513222b54b4b0b4a6bc6cda18350ca1fbc2e556184eee4d182c30b4c46ced3736a216308a89a2a0445554a22b626eefb8deed002304444caec96db8d583347a6006902cba3352ccffa2890b80cbad2f987891c665e172cb0b2d2ebb4d0a5b771fc52e04eec428655a7344cd6e6efa0271ef86c76fb0ed65b820482ec65650d3e5bacb7a7c3b172b3d89e3b867ae318c7bf7b8cff127759fcc2565309051b1efdf3ccfbd1e34602f93511257847d003e54faa998d83d17b82018666a1c9c69078b5aff8b97f437489f034725faa4cced4d7f4f8f7b2cc3dd998c2646cd300c4686de7284edb84d66638744cd7ec4da9e3ef6c17041280b580f0cc7eef180e168c85e875b3d381a6e68b820a4ef3b391ab2b7372f7041483fbb9fcb6e43b296723464efbd4cfb7976db60ed788897c3be2dcf65a6c1be2fb533f0509da7dcc51e062c286b815ddc90ecdb520cc37e623c37dc10eceed434e99e59f36c2b526e3c2da37e3757cec76c534a7fa326f784f808a1769895916f4fa0d27bfd8672f98bcb2d2eaab804b8182764be96795e4d0dd6a3e6eb713d0ec37e731b18c69118751bd2f76f15a62717a473903ad2b7979e392f35fd18a71f439b922aa97e8d89e962ca9db9dce24204976296ed377aca29a0ca50a737eb4edcf315261405d1ec4906ece963f6c4bd3d8d9eef0e90f41a471fa3db69f4a3cf3a24a30eb39799863ed67d29ecfb3199d49d76c6bd1bdde584c56245b1cababe64308acda069d42dd63031f9268ce2abc3c51346f1fd523a5c848069f83601aed61aa715f5b071d7a02f5fab8fd9cfbb18d0165d5c9acbad212d9752a1e532110d9e98d6b032a58563ebcea75926c72c05d2217194fcee8eb8279f0764c02b81189151b8f275a070bfed7e3c04733f1ef28e6ef176908cfe00714fbee7d5d4f4a87949d57817e769cdcdefb6c0b443e2cdecc634da0f89b7ed28fbfeaf9b1bb245f7346d76231b8d44b20f8485961c0dfdec5e4c74d3cc8ccc0ccccf984c2d34c60d696cbbb98921062031b8e4c47013b99b1e82ddccc29045fd642ec66dabca588692b80db786547025bf9becf4e830ce842ac85e3ee16adaa6a3c3c3f333f04851fb4bcfdc901dcbe39eff484ec7bdc805e9b1359765432c3704dee6283b8e4c4c83bd10ee694cf7c120e9993d86eddcfee6b9d9f76b5927432404e532d11466dc7a99688a1f5cc62244e6621b0c523037a4da9feb0f611827db8128f9dd3b5095044ec93458866118cfed1bae7342faa74c872475373bd37ddb4bf7f5f880dcbc97bac9d1d951f18ab254cc758ee55e8d769ecb9739a2cadcdeb62d320efb371df9b8a75ddb75514a63865858a8c609d13ef3ba947bf27574a0b6b4a9aede6064324ee9fd4d2c73b7d7816930f5291629a5305968a42f94d2178a75497cba6b56a963a6d9dc2ba27e48646ea5dab6e1c802d4dcce713a9c2c09e2b849c905e976db4cdc93929503ca57596e531b4afb111d90b88d3b71c20549c283ea0f01b7d97a6e30e2dbb68dbe63b8197e6ee7b8cde8e5c7e03693dbb01386b59452a68a6c6580e8ca211fb7e116104eae7c20a8dcaf9bf8fac9958fb5c7dbf2c1930bc3e5561845578b1c91fad4a9b3cbb847b226a6c9b2ecb70e89877141bacb76ae7fc605f1a75dbc3cd63b24a9cb29ba318e0fc72a258c922fdb7e321c1723ff8b49e29efcce89ea2f547207cc40cb1642b707781b0890107045a5b2986480b52a314e015a6700310d544df93e3ff0184e3061ade2fbfc302d15f83e3fec006a22c54a5a2afe3f9c90e0240aab68a9f4ff00bba7bbbbbb9bbb25d3305b33d0284263e85ebe8246153097afa041e52a216a30714a64326e702ff6304dfc1e8269e23ccd06729b18372b56b86a004f3aca57a144acb287b8f163931bb5dc6fbb3d04e3cc797ba02ad54050c580f9f282ed610c685517d1a8f8b451f1dbfbacd5a1c3e57e0de4d240d9fd98a853b8299864ab6a1c151f4866858b8a1df5e9b86dc3eed9fbd11437451d5a82151d2c97c8029b3aba74a3971df71adc5c84b809aabc99fe3143fb881fdd541fa6bc51c9ce10493656fbf9e5712fba1ced304d7c6e8afaf2a4d7a24296444a4d6a924f44462f474f6464a52881f997973d2fef43455a986abfd14bfb1d692ec8e8e50b67bf929c82c0ca932f6af6f1055a7c0d08e3909e5f875f98e8729c139e7bf0fb53504260c6174330e2076aace07f12f5a331a298c62f147d9900d195f642dcaca85ccfa50845bdfea3345c598681a88078afeb3dcb32bfae8797e501cbb21f825c9d0f7e31c62c8c9b7b1d8ca3b668cd6235fb88b3df79c8fc877b79f7d34b86a88078331ffc660c337b2f8e645cc805d9a1b708f99f7b7549ee9675356754aa79ffdcee7a80dde44b72e35f5434fefe1d30a8dcddcfedab76221dff41a02a56b447f85b33619c7e93cc9bd6e034cc30fda076c15ad498f821c0aa9fe123ee39048c98a0f6efc06afc68e0be76b5fbe8b23a308d3c03890b8d647152effc9ca2a09f73e67c966b8822212834212673ce391909ac1b4ca9c22ac210583810ac882d253cb04407a49452b2e042621221a594920821823099fc1586c604a65d6e2da16289152d084153de4c1ce932839bab73d30e4302872e5fc07ae372134b5b7ad069b55a2d1aa79cf26504f5e316141d776fb54c5e8eec3c881a39a1b5d3cbe436476f416186f0d84eafe8b4af1ea259d0dec1851f04a1402743bd03cd6196805e58e1f5c743bd1399a81f0f0545c9d181aaeb31acbbb9baa11eeaa1dee954efd01d541fdf990981cc936ec26468c883a8388b70b0019310a310810842601d6c00160d018628832068af92f04fd22ff767ff588beb115a71dd9db280b852807e35ad8ca3dcb4c5c4ee43d0428b085fe8dbbf75ed4c4878c1002ffc0a9a8fb84b957697339c5e466e1eb033840bffc3b94270e749c5d2156239c2de11a8a233720eaa2a1ed94280050b96200e64a1290e409287c1e9bb3c2149bbcb091c1a01bd2f6aa47fb4b310ee13d158a224d1f2b11653d5125f9878423e8d523fd6f24510be0c909a9afbb1961b8ce4403a618cd02e1731b61e333230148608cce5089cc9d832f68b0e3e2ebaf0a111f814c6ce2463cbd84d90b15fa8162ddc6be92088824f9b35a596244ed4efdf29a5f0af7f9d2450e55df65dac549ac4b3e2a5303d519bd0a2458b162d579a802549171742f8b109beac8d10cd43a935fd27a07f6054a856415a523fd306eaaa53b7673810b921f4d239a590decd795d7f79b2ebe109cd34a99fc908f5335d77db6148d4bf9f77eb90c4620713a66a6194f34a82d218e5bc28966914ab584629866159a66d1bc78d46a49ac154227db58d1bf568b4916238eeb71189546ba9e4e2f2f2020313c3059111a2a670828a0387c9044d2693a9965c60626e4478c36fdce81ba5928bcbcb0b0c4ccc8d1b26538e6b4aea5293ea1b3f986e4c381c070c4c4ccc8d1b3870984c3972e8d0b163c78d1c384c394c39749cfa9443c74966470a9d5d6106002cb4e0a15ce0d1a3c7d643cbb01e3d7a6c3aea0623733ac9ec484185ffaeb3768515666600c0020b2db4e07928940b2ef020f1e0c163c483070f1e3c78c8ec4861c78e1454f8ef3a0bbdad9d2315989dd9f9bfeb989979054e86a8020b2d78330480112c1851ddb6d082e7a1502ef0e841e303a3970f1f53fa88d07db40f1f3e5868c1f35028175c7081078f1eb2478f165af03c940b3c7ad0a0e49cd2615c30419927d5ad0b1c91be3d54b0525fa83406a18931c2b8bd20534475eb23150001c0d030d0d0d0f464a1a57604d0c44e53ea2f4e48b576f0f3217c1d05b043085d4c103d14fbb8ac89c7b46cda0183eaf282faf1d0ced0d0d05016cda1472882ee7403bc4cd47ccf347acdb75150cc8d989f4153b02a49146c23ffc6cbcf1903f3da283ee98b8c1e3e09be125237e1dec9dfad40d4dcf120889a2e7467cfefc4a8cdf1475f04fe8b55427af823ab840a0fc9d257c2308524ea07d52fbd074155a985588539d1f532ae1a9182b05041198c93cd3bdfb340d5280860f0c0abf028a8b7c0b55c9aa983e3a70e5425390255386c433171769b1ed3311c754d0451f349dd7cd9589ce5ded472a73371d4f4a02677ce6c0605050505ddb933fa2015044d79a9228a4934499fe32f5d5771b1429a329af34b9dd441fd3cc8a3b8cd97bae236e1361f5fa9122528ca6a742364481097c6c3863e83e80d44314ae97f24034c21dcdda1b7684d027ff83b3d146015cc04d826fe02d440b1d70fb91a78355f8b3fa80b80377e0ff6a633b88ce8232957bcaccfcfea212a7632399dcc41a5514606be3ff48f1f87a40fe66b0f5f4a7dcd1d0c5631f7847f869771e79c139226e9739c3422b19063c1e17090a036fc18db4ec76243afea9b60ec72523b5055faf93f02aa71554f9973d6e0ab6a5b0b44cdce594f1c08aabcca8c325f9b92cd06a276e55560155877ea8477be5b619c22a88a6fe241c5825671a7c6927d24a53404898afcacba055156b8379f347fd4cd975bd4cfabdc6e5d2b6ef3e1b8f3e7d756dcf9f3bd8a3fb7c324cec7c13393bad684c65544612da6ba4d216610f793b935f08a096ac70b2adff95feac6eb239f986f7c24442e50a2b8aa003db46125b2da38c2fdda89e6644b116df3c16c3d4666701b6825063789fbc9780aaa4ca6f89aa9b6a03244c123d0623fffea4e6e79307bf213cff51462d863cf2b2a4fa8ee04edce1188e215c686ecc4a81db12098213333d727b5bb9b075a4e0c53d2bf28d54107a8468d393ab80d5f2918041c85d99c2c4e90a81bbdd839295e161887e03e10830b4c8c1f18218740c500aa720835a057d890c17db97c850d297774f90a1b4a18418b9bec4c321d17449d7115fd7adcf8f165fe8377dac89e64f624c3bb83cf9d94ba0d845235339e4926422168d7f5542c503108e96bbfbd4eab590db400d6d095fa555335fdbbfdbc5a83c6f0bb9b7b98377b0cc3be31cf7e0713f54b5d8ca35c71ef9cf5f961c57d8e675d0f06395035ceea78a0726bda689d00d1d9d318994da5eb73520b600da5628c99fde0cbd3c8fa6ccdd1906d4fe9e8475d6454fc4c067a3f9f9b3d5b0fc69a5e4a0b0002e6e3a108bbcc651513ae818529a7f90f4fcb8fefe3a7d89dda9e6a80ef5f24beb44aac1225fed002271afa29e9a1dd81a8f927cdbb93664f0c900f5fd3ac0f0bfe0ff195a4a43d69f37f569b3d6996071ef9dbfc9fd5cf8acad0cb27fd977a5e916c0e0e2719fae989483ffdb6bc92160d2a27227de9f7a5f644447bfad4e2405fb35709159ef8a45772d2de2a89f674fd4923d9d365315b44c6f7374285f6c045d7536b011f08e143fbc3e8e147ab0344cd6738e1977d0ff05e492ef673aa21f28c3ebeb4cd8a3edba44ada096cdb411035e7d7a021834ef4e5a9066a79e49f78e25fb6ab448e75bf168aa27e9095e33a6a8450b975f97e70680a54f52a0668c40e75997250bfd8795abc15523cb1c216bb16b819cc6cb16301005bec00a095a07e94852d7633a4245ad862b74266a57e3176b6f542282b958b6e527f1b680713ea4763a782f6248a4381414d1cd58437c1638bdd8e2b3b29177d618b9d8c1604510d3482fa511f5bec4edd35907b4fdc6b92da62a7e3ceae9d74fdc45103a0976d2088f214aeb4295cc805811c6f95d413d032cd4308614321681042c834d43d18a78410324fe9ef90f414fecd5950598a00ca08a10cf54233cac0fae95fe1cd1ea39cd7d636878e085c18c39ecc21810bbf3df93206870bbf2b506fec16a31c07cc3108eb978fd3598860ea7672351216da404848a1ad0d40b91f674983d7b8bddd0b060927eef5f17a0ac4bd9c105da6a93a228b4d76a3e988a10bb3d48f7d84509531704be2800563a8e1a40652ca78c14a422c57c600ca957fd9082f74d03ae204373ebd62239ab1e2c65cf9b4827165cb881604910f85145bdbcc98429314dcf8728bfad53bd345577e943b50a144171f8001129240c51cc2054f5c90c78d50dc184d26d642064a6c37625f98da848171c42cc2093542965611683c2122cb101ae04887208a39d064060d40d1c0086628424803538c98818829401b116f6ad305e594725e39c38b39e7dc1202477114da84a3e496275cba90e58c222967248104356bc2fd6a3555982b50d0407d31a668c1779b1391f9e3c49dcf949a482e9f753a5c10051fcb16edf7e180848beecbe700affc972e076c2bba97d5b1a4c2fbc5e81c14a8327dcc8dff2a0e1badd501deecbf1b1c94dea875c441b9f061daa81f5774e18f9ec3c238d9c3e7cc701b9787cf157164b88d6ba5aebe465f7244b08ff2230dd84986f8d8c7c7e1e5a37cb12719220ef24b2f5febad748a5648e9e5cba7f44b5db51f0feea8e3a12f673fbad9af9ab6a2cb5a6f10420cc67252380a72562e8c10890a3f76f386b6606e6ae5b2501ac08c4a9d97e7cb37995e4e8c820f63fc669c1e1a4aa9fe1fe58860d76d756bea72300d7cd9e948d598dca66e9c05f5fbd44d0e8cd2f6d0d9f5cf201bb186eec052fd53eeeeee5f5cf525821604868a6a6aa3eae0bfa30815a3d79411fa476b819fab83ef284265efafd4abc0a1bf01f601ff45d32e3f64557740882ea264546a5ffe6b630cbb54aa948c6114fc43328e0a17c20ba1055282cbead800023f16b2018a0f3fe38244185a2be300c13832e3867037da8c4a85973929d9cbeb7229d910fa57f7f98df2e3d55bb48b034c03bfb92119957a5d08c4ae99465a23eec5ef537c5e65ef2ca86e84280650b2930cf179e2cf3dfd4cc3256a0ee318b98171e2c36f00ff507259f500154ca6528961b43e4c036b9ca480b88d3b61038cbae3dfe3957d270b1620687606b59f4f1e1f5a254a564aa215b2e255bf9351bb655ba8e3a2b2ad1d79a87340c6500ef291c02d5baeb4523cb2582b0821dcb265e55bb6a8608d1ca4c86732ae14fe359aebe118e0459ac96032c84083e4af792785452507ffa692833fbb3d11e9ebfdec3fdd16883aa51b5f23c9e719165c22e5b4532d7d7ada81d60451713eb5a79db9a24fbf57d4e12ba1d683a808df2f7b0351d1e1f717f16739e40eab10c10c6a53aa5258f2990196158b48cb8af6546415fbfbfbe10f90f084751b0a1ad30cd1a8db44d88d80160eb2dc1c97afb0c1ba4360528426975b4320e372d1e5161945b74504a13b4b77be469acf3ebda2fed12dad19a36234da229ee7d8bb5dd2abeca36522c40156d803f52a7b13f6d967ba41035a1d889ad9d78c7105d9a11f29fc48bf5f89126a7b6ac66022fafe45a875ab258916fad7f350eb4385c7bfffb2ed478022d03750901decb52c5a9f5e65dc8e0645a3cfd9688b5809a201f58abe6619a278a0f56122c40156f4817a757db44b7a757d0d1995fef5f1a137478fa31ddabbd5a27fd9d30eed55967daf32eb43c5065e3151424549665b6360581a0b44c167ff22bd62615da06e50e7e5161144440c0115911842689d9999bd2111124a184550462a0b8d67be54b4ff10de80c6348a20d283a296106d04418408c804d41aa24882c0d426178470c6d0d51982e8679ec0c48d975b432cb9dfa7588fb385c6333836f84acbae8ecbb0e6d0f12e36bbbd020f6a578710be91d9f5ecb8c745468ada4a670f59da9caf75bde209ffe32e43adf3558a21b7a062d6870a8ff6d9fb68f2a177d2c8e415e43c2baa7dbfd8d14752b677ae48af36ebf3c34ab3b36b2b3d05a2e0639d110aa9b568d4af592c0a37eb2c6d0438b6ff622ebffc41ff9b5c4b9bbd69cde2308dffe7c275fe991856997ef20fb9b10d86b33d7f896db4cc740056298027c09effd55120870ab00f7f78861ad79f70dbfb180ad75f07721bd75b88eb708b3567d4ecd93d6e48f6dab5c50ede03f00eaeeb34ca3921432e4ec80e8d456e988d8614512087f6e17f791a5a9e70fd75988debef5bb2e06b00ce0895c2fdeca400d115f7b2930254571ceb466b290bea47a9e6970d349ee12e8fa3e27677e20a4854f8adc58f85e4e7b88c3d76c73d2e6af721349ef99ca734b3ec295da59b7094f7138ef215a8807d785b7fbe5c109bebdbe569e116f5f3a12952aef721efc26da277e143dd41f01f541e5a87fee14a149181eebef19477468eb837c56de077fcfe243a40d5cc525d4d5a0a520a1da6aab8d7c409fecf8516f62a767de7f714f7fa79056d80ab695700aa2d5d8ad1ec2b01b48a95c8821b072251e5f54fa42083fab5909523f0fd752000a7e00d3318eef90d91bed31eb9611a2f2280d70057974d419415586ab75a0868470e2a7f771087304608234c615829478e48c1a85f4f99d21e1dbe13582ea0ae7b8eeb5fe93a659c78994bdcd59c91011e3a08104ce3ef4c7383f3fd9b4c371815f5bbbb3bfe8fec2b0dcd538c43baf33dc639ed29cae799ab537c25d7f34b5b43ff7c1e2554a2e5e1bf5e49ffbc94cc1d58d47e36991c06acb5e646a0ea9a454a8c1cb9809309b08a7610377e43a14d8b2660e4889306e2099742f1208a7d84d11c88da99445ad48f8b8cb8cd478f40c06dba280235bfe61bc7385ff64def97fd67d967178bf1bd0dc6a4c5987dcc3629a1d45ed3ec69085fcdd69c51a9f63f37a319ed7a80f7b25d8415282cb8b204145ba4b18a9e8a25d0e0ddfa87774b54e9efb90725848e43fc1901bca07aeec1a4b5a834ae9574410c15d1000000004314000020100a86c421b1602c1e95cbc2de0114000a8ba0447e529849a32008529842c610600c2080803100032033325b05e52e2b6ac05478bf273b1cc876a547901d81ba146452eef7bc0a948c6ed2dea62f4c9784d52eed030910924114fa59e0de1db03f7be46813a14b0483308478a33da0abdd88f57483bd0929767d894aa9d6a74997a1d1efdf20adb8322ff4b9f2db32957ea26f2727881446ec4a0366981f409e52a16cba27c834a97f45c2e8d2266ddfe35961756aafff73427e714d0be498bc34156c2712b34a8c19376272f61ccbb04846d1069abe353cffe41950d37bd97b971f906e73df893077e4af3278a08a15d69543ee8a40d25557e39c1447ae6f27d6fa8623ded381c5db8e2645ed5362123465a0da19a66d735cd0901b33250d21d3a40c601e869e3a9b7e400274800cf624a71aa248b5af2ddda3104e8262d332a7d4534f1d695510b5435dfecd623eec499ca60a19a64b673854e7e8b8351a8c1ecb49f3ec57b9cfcf6218b3142dc652067ee3018ee3f99e4bef8a6bd3149fd698085d9b26191563177429c4204ba735ae485996d66c534f02d8bea5a5f99ebf4df263bed1a7ea3db6ddcb779e5035886f68f9ad01e7e6a411591c11a285e9d386e9b95fe3849b3b131296689b60c7047fd6af67300e35afd4afdc8b1af0cc57d479aaa5bcc3b0495a6ebb1a4dee9fbba915a5e6b9a7b7a83e7ca942115f6a4c7636452c6cce9b2d3121d0873852869c1acea97138132297234ad9f71a7036ed93dce4a3d06876107bdddea8a2497b7a103032e60360b535ed44e2f434a3f286c8db3040a7724415e7855431fd9d2550691537de0b6b9ac00cf6060fe1dd1914837aff900d09e14cbf9b34ce17ad80f44f4cc1c7bfecd97eb4ccf1e734863287b6749e0a597b3217d375eea163f35075453223b0ad0463ef194305c5e3ae9d5d9bfb431a0b34fc277c567413af7ad0516f20f7526b81b41c14f0285746e9244f91818bdf417f1fd299aaa46dce45ec861de8b6ecc22c1f9bb604676a3429910131b10bb7fb15b3b27f806bcdd4dc9918ab7e5c3bcc653d35625686ca8a00dae6407f0e3abf8444079703b0dc69b0aebec2138ed0e46f218f23ed8fe9337ccc1d3174d3f22d25120b373ebfb55aaa7927d9e457a984be67937bd4400b158c16a7582a3285187737220644d1674744542e2491a3f12e3e2c208b0c3e6a31cdfdb8db67d6d421b0db3c3561a3deaa9b3300382a6375739025324c9f99e14007fa8255f8a775a6cd7b7ba1ed1430fd3e025ffae9914960d95dc21732ccbe6af91a7d969d7422103823d92b876a54989366723d29155b1b652a4d0713868417e99da2230a84b62b9ffa95df646bf920c6f5a5a209df264518f62e82fd04b877ec21c699d05a8fedd529a529a45777e211905de71d67dad532308ec29c450f9704cfef7205cc1d5b01fabdb8235f93c6b229c1b0ecd0316a9a37c2b468962deb93a00266bd779b973fb622e9455b014f10e126fde906222f62ed50787f916944f8b3c326ebb16f97c21ef00c3a881b683c196137cd1dd2181a9b61d00cade4d67256ab6c36811520dc8e8f598eac6b96bfa24807d0a526715456ad9dd5392e45d278fa52db44729ca53d27f8c746fdd628e7680b1ac639c0b4479ea1832dddffe9310f7cafcde26b1ce83ac0aba9fefde4f20bdd4a697e5ad76b4658292129e9aa3cf3a3c5e64808cd35d65d1ff1831043dfbf4e30da8f62e853e320e2ebaf6b4074bdadb306c5de1c6450baf472f59d1f579acf9cb60c940737e52f9529d9d54980e634f15b3732c56dc00204014a399d294e26a748e514f939650c25c9e4833f71bd3294c0602053a530d90abefd540f85f5e707211f476e865f51315c05ff6706376ac6b493cfbca1cd613bb8704c1c043003fc100f0b0c98d6ebde89a300ac9ff3cb4d9e179da4e307e3564725bfa5470c94d164049b5828ff385689b94f3f892c0f8a4de70594968634ea62f95b603392e10f934458131cc2829fb71b91d5ab7cc5ba8fdb090bcd6462d6d6b7112c209022d00c703b61ca3f70769d25dd7ec93c91aa1a3aa08b1fc2618dcf4bfcc7614dd6968549b6da939444e589d17260dca0d9defb984bd573771752bbdcbdeb245fa129d25aabf4e755a6d2ffe4a6070ead45120df5eb2ecee3b1116a37fd61d0bde355392efaed4f917cc8029f80aca4a44bab6f04824e7de31648a6a7f1bf33665d0fc8547a8d0cd6d5ce61e82974da717122f615cef89cda9cd04100a3f4fa2fc88e870b2d10d7a4ae535e15adaf48b6941a754789547fa6a793c16fcf49acc54866c396dd637c756f06d458caaf97b08192b89a957564ace6fff982c7a17a2e31c0e4393eaa2db87b007be308595948fcc286d4990731ef073d9cb7a3785dc08ee79f883b352a2f98d000978449cf4f78fd59966e09315f00af3fc7f9b408bbfd74fc03ae091c8e1ac83859f9efa1d1f78f1e10db9accba489de6a94231165a627439d5e81455eb98cfd404f86fe605e103c650133029087cbd2aef653dce4d82f41264787f5bc94d02006231e10dfb31802e9d52fb3d96277c0add8219480b9ec5f6b7a21d674f51b46d88d0354a802d758d6d430bdc4d9050da6b49b7f4b30ac0b552c13985651d3339aa0c309281050b4ac8cfea394236cc0da609e7e904b2159195e69959fe560b265d7682d212ed3065409136028c1381a0a6296e0b1c7c812f55d46b07b0fea6a3c346c3c451c03b24b260f8fef63aa97325c0246fd5ef1209533ef63d32cfcb5606d063cbed33632929786041d91eb23c1b6ce4f8538f013c0cd045ac3a21e793ff965ec171bd0a0537e42b9642b34c221238dac2a9bb72073ef45cf5f82a757abd14608ea9922b491ad78ba3bb9fc7405c6077e5c40193d65d9d5ff7e750ee19d2e361655c4e5dcdc4e5f09357e400891556e5ecb61c11823acb329a7ae7e864e5c6c8c23d75f74b9b5ab47844fd31d2aa0307b2dbee95a1d4c53cd2df7de0159c3283ed866c9232018ee686577dd0adadac3724742074a9a5f37a41fab171400a989cfd0c878270414f62927f78d20875e2028ce5acd0a66db899a666ba5aa361de96b9da3f26c5baa4cecb276a4e5b79978f005b3c25b44f3cb2edaca9b53fb08fc9e1084eab2d037ceca42956fec090a18a154383bed00d6a7d03ccde11317107e09c2351e99af5eea103af304365ec0f94b65a30f7d228c5bf72a36afd4c24c1efc99419f3a0c0fe5db33dd2358f60f78a87c52402324e1dac9722d94b5dc62f7543d5a09e03dc74cb3ddabe0228c297a720f54a740a663202a68d0e04d1367602598a391c92ff047c8d34f4446f95dc3bb60e0e5b86a49ce615ff5df72bb576a04f11d13b706aac6f23f4c81e540d31e5c0cbed861ed42e1b2a948a2111fe46a8535d1f168f9de235ca0c1807d8cc2dd870b0705520a0fc511a2fe5d442972e5d88a1ff98e7965ac31ca22d3ad1ab59b99b12703389017d6c1ccfe2df1abfa3da2c94e9e106b9bd9b64d112e9a703b389c8b3c9170ffd7ba022d5255ba58b040b37b066a79a934e97b8c14e99cc02c64d5de8051d715edc14114f4821039c4a7103750fab7d994471a32b9934018ed0c73b1637cbdbfa5ac8276e26179f5a8826018534ef1b93aaf04b00ea485c9d93accbeb24b6854fa6703077bfafdc655c67cc7565c94a39ce94922c5d64e5ba933bbdc1727df3818fda7d1cca32445f04abcf1be64a10f3d70a47026c02201fa35cec8c5692b88ea2ebcb178a51568d406e51b3a418c592a246fad460028ac0519be451405a592bdd065125d37818f459c1679adefb20963759cda88b6f52afdc628c9f1bdda3471d5b02344d492da4a48a83befa10ef1b8944782701b3c16360662ebbdaa6564130847ac848941251c5b41301c42a43d12e0c6f3defb328aaf8287fc051846758c866c5420f50325ce3a81b8c038780f6f81f11b83250b1ed750f0d2732f332abab1f854f780c34ec4af3db42b048ad3377828785e680d43e4da611d579e97de4b8b2c7c7f8e4caefea89e321b5fe868c617e68dc3ad178bacdac9c04a71a9f4f6e0005d1755752d04bb84a239b5ff2cd50865596899ca1cf3e5fe2238ad0d87e60f9316e150c8d873b08f7a08cc86576b2c79d5acc09b8adb4009c6528a54041815ad12a0042d1a7f5e3dda474554576152339e9ad1faf866494b9dadb37ced17c7c4d0ed7c8ada71d3202246697637015ce7c71d3bd6f1325abf119eaf2fef79e2c15c9fa7325a1a638fbd5028136684fc7e76092aa31c3f3550255fc22c9f1c89817236b743bfb573262098a462b22a4596de23e096f79956d744e9026aecdd18fe05af37ca753c6136534ab3de739f19054798923a4bedc1984f5e92e18a090ade04e7c8e8d41171e32d38e41514000bf47d3b57a52a07405eda8a9ae0bb5f2f329467fb89c10164176e2df92ac84c578c384f9bed282e0b07e3e5a46505e2d0f8e6ef0afda3d054e7e86908b1be913253285f1115b4e9931aa0f7c11823936d2cac2d05857e0b4bc0f558f2523a93f02dca963fb21a3eac080c54cda404946b72f727cc8fc6990d624b86f2d92ebc50d73eff95e6f3d6b9774a943e9c42f027669b6198a80fac4bcbb12b6df2fa333fa408dcbaef4016c3860b18274903728e23079b04d026968e5a3a07282190b052f0fcfc130e0d433a8eb485c318bf20e6d88ab6205521991bbe3adb22fe3c67eb88676c5fad8b9b3eb4a4fd405bdeade12df9fe364edc2114449d2620257ae4c46d534e2a41e5d09707e814995c550c7038d47c7249db13c408c308f782bf85f55d97f897e37a61e813f28e73824720d7517e9a17cd5320f8edb31ed5980ce787d3bc4c490b1067caf6c20448176731f37f323cc8a1457c04f7f26043a3f9d1b8d814e3c62b7c454b36d158da504addd16639b109602714fa48cbd1059970e4b7a2d4cd4e07ea37dfdf7373e1a18fc6a9d5b31497adb38cdf641a8837ae31fa397d79a303731f4ead154cc48aca45f0bffcd6d64684dd7dbaf5c7b87b6a04aaf049400d48c1a297bdf2621252653aa9e6638a24d39c06fbcf75ec2f87d72b15614142d422db44cfeeb8a15c73890a5b597664220c451cb0524579543bff334149951337c918623c4bd5a4b4f2327843ce48cfd78e24b716f18e2dea98361256ef990714f5db2f767dc5343ffb88ebe8f7861d07c9bbcdb843b66bc696174c421a27bb6e98b1b114fb515bec98854ac57d9040de57e57130816135731b67295d3923796e16aad075132da0947464dd314b4b2646d4e2ff48089b4fea90f0461d6b4cbfd8100e64523a02773ce0a3fab64e6955b2dc76ed4eb52e7231cb328c714bbe4ffc9caec215593343167eca95d80de326b113b0f1352add7b31d634103b7100469be7264a03e31a9e18a44eb0432510b6ebc53bad80323f531a8108c7accc67da2c573fce609ee4bd78cce03f17c6e24a31f37f2594b5d1ba0d4b573b1a4341635da765363f2840c748850c44c5abe535e0cafafa284cdd83facf9ea77ab4312dcfbe7079430c314c669efc11fa9730d3cafdb5a700bf5c1dc58443b76d2f1ef700f9935a57321f382e44e6b92c3b700ccfa43e6b9c4caafe48b19672062a126e26f3092168983a888cceb61799fdcf46b207075d13fdce31ac66a0905b6bc7fe3626d19a51b461b3a6887be4c55ef37f25a431daaedbdba49a0b14e9bc0ce4314c2dde4a2f7e8db506d6655a851ee2f2c350a78a5046b25dbfc326643802370ece24a4e445d7deb158c71a9bcbb02e6e180c00fb9eb3859c676d9bfb5e6415e93505454adaed082eb2810f364a619b37a69daad3431e0cc185d5fd28294b4e01ceda81d819f527f032771e97324cc8c94387aa6b67617ad4cfacf611eca215866d6355b14a6013cb2dea0c203d685bb19a6ee84517863e21c9c4ffbe2eada1ddad0430cffc8b49a6f118ebc3cf73bf846dca495129d3be32f45be73d2b126c500fd6e4fac65e5f9c7ae21d922970e43fdb2c32d507c99e60b2d67a5efcc1125302bf2dfc6217123bfe65005d9055ca9659c0fe162ac480015502b28d1fa26fa605e6a1c409096c70e0027cd9d214c8d5c82b8ebcb06cc80c05ad7e8e18cef5974e9f07032f889f8656e7a8187146f73911058b549f4d673f378e07be6dcf456db0f6168fd51fb2916d6081703634833e860f601737315c6ba830b14a3212b7e30d3b7d9b7dd7ab340037b2a9a60715e175acd103c6067dce1cb6e65127357410a9f6bc4dab2980c3bb30a911623d193d1be1162243e4447ad47455db0e3eb7e036f1ee08bc825ff2f43deed1791a4c9484525e3f4feeb73cf85a3cab289544c588f452e74beb10ab51bde1d8cdf162cbe246fd0a4958dd75713cccb3ccf6027f71144df0263c46fd652f25a15c81fec0739798a593dcdddd56db573a0aebc1556b45da8f826de5bcbf40b3760f6c8ae86c870c034fd72dc27333da0c62e04b858c1b0bc2e04896a7edb1bc9716f065f681e97724d7b653f8952b3ef9dab0fcdc95709064aa66041c19153ddb10183132ef5700c1068ec7669da674b56f3ee2111fb33e8d02ee44f8fa49353011cb0ecb865e52d0116480b9673d063f9ca93d33cc429259b33dc6ceac1d09e8bd113e80957871b327b9da37c9f1a6eb120b46ff416836669400c587eb62c3762c35744c22494c6c97c0447ca67b6e60c7ce7aa7444046e40849b290bc8b37208c395251a26e843977eb2fdf64a9cff1aad991be60d5a17d1f74248813c77863f37420065dc4886ff338bd69cdb46346e2d5107b014f6ab990e7169760cb9b9aa42a19f6428ec8fc640a8978b1564427dfae96f63602dd2646a14efb2b1bc191d364bb0c8ef68f1301de8e2edd98260a2a98034a2606dc383fb36e208524e19d799c18a913ab05ddce6e89915e29a26df4be867b87379d22af9b0b60d54a7799782ca38a1f390f5da577087def7a70b9b501729ca66a08e7770fa695aa0e0789415d7e6dc901ca6b75d769a52a86bb42d8de711fb3b83c4931f07638bccbc113c2b5de1126a45d0ef455427a22d79c5c22876cec885b88e80e40f6dbe3cb852057e0610bee421d5b02ff0f956a1a027ce331d581190187118a7988e594c16429df372c76704bd81951bcbc5e109fc56336b71d4caf960808092b06d3bf7301b4839954998e16f5924996e79cb5290c81b96a34d6e067f894cadafcbd2e2ca672cf96352e6064ab72cd3c7be6aaf35a9896b76ce81ec1afa927a30c2562bf7982aae4fde3ee893ae1b137d9c01cf19621788ceda0073592ea93b9dbcd4eb9f8697bd6716c6b3a9cb9efe994f1924f8306c34680f71042b0804f4dc0f600721efdea709803520bb08bea44d79e0aae25534d2e58ab3e564ed356b5a7c4bae03fb3e8683e0e507b37f7e9f1008ff0384017ea1ff45f2d46b725c10ac1b457e8c0dbc245d3b630eeaa26e7177c92c0d3b02c7a364e3ff33bb0db938f51de7e8e5933ad8ccb64613246d12899651599b2d35dda96e99a179bb4b15acb1a3508d1f7b556408691c5f169123abd9c66237443d96f9d4d148bf5c199cf283b5db45b0e7183e884c43865c7f3567d2f7e937198ef87a6266697e3daee88a68e2d374eb7b4636acc77b9e7a8f3cdb7a42fb43b1d531b2011d8aab383c5ed3def1059c01245a681efb10486e94570191376bdc20d663188519153dd063dfab66bbd551373c0672a3d9487d029634cabc5876413b4c7785c51e8ae70dc11f050e3b1a64d02a683094124bf966628b8e068390a815f7f79358729a59ce44697a9c11d2a43a5cf1572287d3835614391608cbefedccfcc730b17c489ce03c5d9954f2f7a24a11236a346feb1d3f3250204fa0eed4e8e8f45bc8e8ba9b10aaf9cb8c086137a75589328d3657bcd94055b85de9971b430508f1f824e7b8fd1e969caa6acc22aa23fb2e904044239d1779a0bccc0f6525acb21e760b624ffe5a645388865b7c5db599d6096723f1e0a11a8109a734ee1f21e84058bce70a939426bee69a1026111c802593595c890746b195c18cd42acf43986405f82389520df7eb85605b81c3d5b4516d771f22a6a89824fb651eba0a60b624c77ccd50a1249b6ad692183f25917c96e447cb60122b5b911c774692130347d8deddf74d9281a088b929047ffd07fddc94f6f59c8a249a7d0137f038bc0c7ddb825a6351636ab377a54217c148ea145fc2545f779b4181b8403fa22a92389685c9a8542682534c26bf4a1847962bfddd31fabba703c94619ad48e4c982d5e71643cef7e1259a7109d9e5a7d281a0ebc4347120a4244192985f3a5ca245e0f8ae86700ed946f00a02599e9bdd1d5dd3be72c471b0f3074a15461616ad8f8048508354b727220b2959104d84b80185b159302858c7a139d2b17d0a32c9594f4207ade5b51ca0e30eb67400cc8e85b50a5f28feef9b5be983bb277e40c09fa40520957205cbd7f3e993383b518d2eac89c107aa03299086ab82c94002590122d423526b08d0e6fdae4b4b2c7ed2b83da3428fcd8984030eb5bfa2a093a9203b6954ea25cc151d73c182564d38022c89a43f236e86995f82b923dc5ebdb0402826c969d42fca782b95fe01ed3379a50239446922317e67563af0033351bf28416815746cebc787ba436d66ba23a3d0308a2346ee384b94bb48ba865127928b9d220b48c97efdca93fae75e1433df6f9802bcd50becb760302e69177285054448c61ee5f76b9d570f9066220534a11d86deeb35f7ee18b9d82a82e06721c1dff1fe039cf0faad120edb8aeddaae051777bf11aa57be06d74615d0772cd1783234d8fdadb3743057f6422e6ad6125ed2292834d149219f63e05f7cf258f0c8f5e5148409116ab83d7f731cf02a553f81887b3903eb4fab00aeebeca8cd29366ecb3c91580ea15adb83aa02e02e07184df61ef5f15985e1ac6db9d976616ac85673fb471c2eab814a2d23c4f83549c1ce7c0d8dfbe67789fa2b0b09ddbb01ec673e709059306244e9db32a7cd0f4f9f2cd4658c68a1fdd8e6010e1d026bce66ab451045e18f861c0735c3a60d6f2bb220111e90223f5125166e66282b72db65c4f4f581eaaa7f2b79fa23e0a33bfb27f561346031fb0812a2a1c6155af99e406a80c33baaff903441c69d96f0a3904c56c7e35ea3901fe440d13c6716121fa25d7e49448a88157eba6f650d50e02ad89d0e8dc02ab765488e049a1e701afb1061de9b4390aa12428020f21bd9a0a23f1b73bcd8de32a4d076c4ed6d0edc60c52fbc1cb0c4cc7850f1d10755faed489edc0b925868d0981540afbe5ba5487bc28d6ded347b0d0a0137d4732e5cf996161685018e3c7d2b900330f2667e9a82bbd074b4ce64bb85e6f2f3e28785e6b0e2b99a4785c3bf403eca880bc787965b5079e63f5025e4c4b3cb3ca4d7dd53b4c30c0322b1370ce06fcf188aa477bdda064c09cd63ccca775558d45c9d127542f3b63134b80f82a53b75091b826dd0d446feaf9e97eafbdff7b7eabd33366b5310103c652141d439f577892f5d0eb154eecc3ade4eb8ad9c26eb314d8ea205e12d17077b752e21292bd69a9e3a5ba9b2a786956328eac0d35432dcab5b009d03d35f7a34fe39c42fa9e49d5763a734e8b0d3f9539d1bd0c3288bd6c26c4d6a7c202286ccf43145d921d206c7206234557603067703d32c5257bef912083ccfbaac09bb3846417adb8dd823a047e19bb001633d6591ff6565caea25db2121e10ab7a7b25ce6b28b05b821eb41b5d1ad819fe036fa53e8817df628b4b7681810fa8dd9f2eeb6197ee37f12dabe44791c2063ce607c874e79683fd441243d54144fd2a40475b772154dbb2be2330df1fff6db04ffb82c39c8bce50ccead992c56c6f09dc36197221b7fef7ea2bea65b7206237d32430578188a4ca203f7b1ba94b012d16bcb1485d754912f53749c8d760aecc70fd7f8a35b346a6efb530aca2d1a7bfafc3404170650429b07f09f72140ae078798680edd8071c8995d0cb9796a691907975b6ba8168d7e9973c287397dfa401e8ad47bade0eeab76fa47a102e707c147d960aa870f99f494c4143e571ccbdf32f79cf6134187b3170f5618ce44d5be290d31d9a19738319a00e66d451987c1acb5bf2445fb02b5771193aecdcb086e1281e6c15c8c13c7f8b9bddf936d8ba73c41335b3807f493a4e581e8b7f8b066c6405e0562d0f9737014be10faf2b3f36dd0fac95ff0022083e00425cb96bef9b6477dca946fe1466c367662aac4e83c2708b9ebc283e4de28d92316d62d335ea005fd531c96dc34d10e36af8e8b3d8b6a7912a30377c8ef8d0b7fa914b95ade0855ef8457a313ad35bac31ad48059abda2ffb4895598d1ebc2bf805627f193a4b2bc61f2280fb75aa655b0e427a14a0e79abd5ff2344c24478885eedfffd461216c5627c268089e8b5a16a0e5a41027543e0fa31480c212ec46cda9dd4797edad82c9fbf385825214d9331362824742a45970dfe90b3a21d9ea124ec478f86011689493cfec428f9e78477c42340c207ccfd9088861f9897815185850117b8fa94e9ca4b8684746816948d47485dd40708bba1c218e0da74801c05b190e0412c20eded8216d4871d6a4b869e65a1ae2462f1ff983a71b757c8c42a8dd375130c6a32121d22bda395dceadca9d2bbbf0677efb43fa0ded84bb0f40aba920317f0436b35f96c0d76ac9be17bb87886d4c745cfd416c4e1b10aa370ee30174a760ea796a2d265af60eae5f6a6245bf28a8c3be279d4a1781e7e1893158f133ebf1d0d7460817f465765a0e545c8d61b0d2547cc023e62ad2ee741798cdaa218bd753b6595c6076c8d1f0da89589ec7b07f3671d098a0479b70040fbf3437cbbb81b60433434f2795328a0e15f58aa42a61654e29332cfe10d6f25efc1b5d40c0520b94344d9e463a501b9393e6cec1fef15a55807dc181bd0b75faf9d0324c9c216ff41813fd617de51c93583ec0123982bf6c1749db84152357d2ea545087d48b89a033f4f54a8372d4ab56ce3b1ac450d81f69053ef372d379a1c7a0540ac7a24114711e08f85ead3870bd92d92db746df7cd7ba24bc6ac19eb8e1a08caffdeca19502f4c90337077113f211db1055bc0dde8fbd8d5be02aed29fc06962e91aac9dd3d8858374f40dc06db90f9a9e588e6f6fd90e20b9063e57183cb544e726ec5a94a02839308859f2cc3a609639647c80e81b0ce86376bb8b1085080f705295010f1d3e3912634f6b603d158acba7abf7a9f815e919988e16aa9020e927da69bd4adeea9afc705d5735e81e5adbf47643e88f9629a46b3971299bedbf2e05d1c0ec7d4e7c3219b256500df450d9042c2b2db7fbcaca9fbf282514d673b03514b03c706504d281c1fb9af02419aae94e1dae558341596867ad56a2e0d3a98169499b61e9c095be1ca01b09be153adc014935b2ad29b40ccb45f52c2aa089d9288f5409038acb2094078045267b96bc6596e498ff730a34f6a4fa6e2c3828de795c649a2866029e009382fec8a0d92e90da0cda6c2b84992ff75dc7be6b8e5f39be7c1ad5c4fd4ac5704bde5a2a0ed77edb3cc47779770ff44ac4f7be53e739bd0799a420f46217d1e6a9fcf876111c796708b2a508b37088aaa3975ca087a05ae7c259fcec9d83c3153d1f8f5eea9f17a8d6412950c45c04e91c8738793e8e21832d7a2a2b52755452dd17b53b1520d6868a7119f94b56ec3bf9ec27754e1f8ec30ee56eca4bdc4009a60f05d3618ebec78d266e4da293bbec28b05999fa9cbc8abf8c2a95634d1996c1745d8654e0580d41d48765eef94679790861f22f428872c64180283038dc0c741573e28686907140df7d5e299e6c5b8a2ef05ac1ffd1950ec858bf4780ccca2bdf06b42c20c51df415976a08fdb1dedb0fd7d021e3e1c4b2dcc5d5b5d77a24e671260a4237b29981e0a5c43558a1b56e636f97ff5169966a2ef2282a1ee075b6e00431a936e130e9a820a8d9ae34191240e7102eb753c8263f83afcfe1624b4065d3cdebf60c476c1b852155f6f8fcc3b2bafc64d9382d1cbd11d6e59c36f1272adc52ce743aa3c3d9c96b99be2f071e2704b32291bd74a80c2ca9ea9a26e25475cd658f864678efffe1ff456d9276370dc58315333e6096c1f3ffc30a1c4159a64c8591a4b997c24c3a32ce7c1bdfe84eaec3a07d0b7b6fbb8e374864cb49bfc3ade10d9babc5fafe88279ba5ddb805325d88d7bda0e4f229cf694e2a2187f894df0fe1465bfa997d8e10c66a35ccac58dedf0d3a0010b24e6a2c12867e83edcad13cf7663ce119bb8d48c0c977e3319fce73a6d3fb6e9b10a7fd9893e3d5d236c9cb03b0b0ed3680404075212903fbb664189ab5b9d3c5489349dd64a81dba502dffb2f9ffe2e171b36f98eabe52934f7d58d4a893da075e9cfb61d7f80d53668f764bbdbb2a4b0822df2df6beb583810601d36bc0c180e3683e3cbee52cb4ded0e4587c4e86d9237709c9e2c93c93e133136c4e3188bf25b8665368e489bd87c0e407faf38ed0f8f526d25d073aee60fd01071defb843bc933eaa99d4c1a5bff5a650ec5143ecd050a360f9e75cddb7f711fbba8ffbd2df4e509f0d2614d0ef85602262ee633725de6d102101ef6d8080c0fcbea500ea1fee0f981b53ffd6f77b735b2a5522b212d201f8a7fbd79081ce748a4f95779db0aef77eedc3afe6fba12fe1a6414c2f0ee5133726827bd1dd2b788f759cddf5b215878a2cb3aad2d420a6574352d733d389c02d19788e4bb9958bde5cb86dfcad33c24d195d56728b15614ea4a4553e290606bc8f0103ff3eef8b7dabbf1cdeb0db1273f37de761261bd966393628017534dccc4f3204588bfbc37760dc4a263944fc2b35a787acc4f6c6e912c458a08ce238ca2e104b6f8d7e7d43dc03bf26e3ace8ea7170e18a55256cd694ab3f32a3d838351ec47add1443284f26f0f13656efdeb73ebb211cb726b28c5f1c6519193212b8dbc8042873525d136da06596a3f4cfed931766ee502e6a1968b5ad5fae3e23f41aa4eb59ddbdaf57b75606eac5d2d8cc14adc5be57b75738d49248e2e611e8e5d88034b2aff775df9f7d36f132dc2cd385089ac4e61c331d30d995144211ff08eb37b85f8aba618c6d3102971f07c0dda6646de24e3f92fa323a029611f0b4f9a5ccd6e14c1429e4e10948eeae99f1b40e460a17e29cad00a11d88f1a4fe7055e0131a7b2091cebd4d2cf1e0c08264fe128cfa2f95a2910b37d4ac2c86ea313e45e02bd49226c3ca7c0e8e3573b788deb087f94337b62c134b12a3291de3d35848974f950ec4d77822c8d0fbacfb3f814104fae5c3c46bdf0907617a07deb27bafb0cf0e82f2a103c0daa6897e9e017d988b103842dd3c258b4b5e7a99f77ce3da4985507132212e56558983ea53e23c52ddb4fb149daf24cdee054df3e3f3863e40450f2ff1d892306f934d98e029fa929fbc799010883e7f5968d932bae83613f008043a3abee220b67315e0bb8c6a89941de6d4b90e1664d88cbbf4c9a78eabe9ee1809fcd8b87cd61e8caa50b663e6b308921fe509f29bde6deeabf577cff906883ec57eddca4d29d5bc4ac010ac5ce2a14ece0fb834e5e18b7b26ac6bd475119cf9f0421625071817b146c5a0e751b952fac022192f78050370ea198513055291067c37b37dc3b51042815482f41dd007a5b7c80fff57c941e1d7b1f01f0b196257e536541f3a29a0723e718a19a188aabe6a20d630a466cac365d1f95be9484035d100b48b29e141afa16df78d8e3f0f1c9e534489be19d2ee6389811e86207e601fe5afa1824d730b7665c0ba55958d68f7af6a7eab9ed802cbc32163a2418fef7276d2eed8210729d0829d63b1e5a5222f55dd2b392a8da58b34754f3606cc2b723a5a3ece8f6389baf21c95d91d4743e1666643ccc15969fd5141714b30a9a73d647f7dd4b4b6ea2380b9b2a33cfe202e2ce94f615782dfe4f4a61c0868be12d6debaf37318f32d3c59812542576e32ce58b4aab773f2d24ea282acd03fed7e727bb407b2da611f8c1f59ab4ac8cb515a1cd96a39871e5bf0cd7237665f2b6e7e3a805839e3bbf2aac6808e8b0434eea516d7255366e13754cd3cb6439b6dd03a5d1f62aec269ac1cf670d4bfcbc297d4418e823e8d63f2a256a142a31c327f3e6ee40368f465e07255484f8068a1a46f5401c78a17b8a4c0c39402de3cbd71cb294f91c5a3d578fbed0207a363d35a8bf7fd5ebee5f7f3f5737adcd4c7d390e04a24b87137b36a74dd91ec62d197570a677cb4bf4b626056b51ac28632302761c27f6e8f4814ba7f88ff15fabd97bb8a4f9872cacc1e970f12b118f235aabd43fa8d5a5fcd509abb03d46c26b300ad24488c9291aa43a086e0180947840f0f5775e8c9b4e5af33e4897857875e22c35de80b0d249c0faa929f73ab4541fffa518549d436de8caf339b1f3a6456b35eb199cb6a05c82fa9e96b09a68f930e635e204ee9038e280094a783348feba6158fd8b41e3f8c66ea32e071e066c0b9d048caeccf5be097367484985fc5a843d5b0f001cec072df05745d47957a68a1121b0a7f94ff3d732f865b7789400641ff9a545f2cff2a750601a287c880f6b7276af38c4c7582acf5f41461df3fd5d2295e864e97a1533bd725654f94291737eeab7629ba4750f9118e8b131fd0682a253097fd78fe4267b9662cd7acbeebc82318abb7886688a835b18fc85b4ec91ab0207a4afa13f6ad192c09808fa5ceda217b17b0c8d5e7e20ccc7088ded78678f66300a2a982ea80f0534290694b16ca53e9b8e389480e7c2e2d733dc9e8a2378b7a3157a0bae8233885003ad7ebbd501fc96aa7cfdc08fe688c06dd261dac840704b43542b9e5389422846d5d27d1950c6a40e800f03b07900fd9c6564cd312480bc880d428ba8fe4812475a41c3f301b4af8f19ac43d371687a11cda8fdc87d4868f43c9991e94a9b53c51fd67b82d559c24451933eb22e9dede2e0946d9c61cebf2bfe09d94eee6f6e410c595d2e770fab529b2a2f9607b9b2280e320d7d6b5ca1cd6dcf9850aa300c5714f4dfcb6320332552bb45667b9aaad4118faad946cb3bfa1c3daee34b99756c7eb681d116e418fc697e0eda2d872b2962701caba7942467c097f3bba2068860181e3f935d04292c19816a4d46bb1dbeccede72b1b00474b1de160731dbb3029763efaf1dc8c5c6a2ea43e15118896686563705eb7c364d9d800912f1447dbe0a5ffd5fd92234eeb1e312132d7f1c7cbc80065252137e7f842598d870d800fe2c540285449058c09ed2f1b7f5171e8ad80d18000caf9840ed78eef4e07914efc0b3829fac53f21ce40d17bbd8c2ffc9ace978909d5dc244d55fe03bdc9e1844696b4f4d801ae426b0ce8a86ad3e16ad7fb324039be10bd8162d933b09ae8d9520be283a500dc9353fe8898b70bde56d449fc091389ae4cbe96d2c5ca8aa791f838d40dcce19b56ef6c92d88d79b0c9e7ae87de26d47cb540487b80ec513c7793d904038891068f0cf1698819376fa8b352ce56adbec09293568bdb4e099a58c716ae617b69c78c731a7038166a43fca05c2eaef19caa4c425053f957a47826175372a125220fdd58ab6f0d02b12da8b56926e04ec4a8edfe9fed0321aadbdd501a94019c2bb96fdea3ba2645d1197841b2add2fc5d5c837b3334714ebfdd60ea9b6d021fe7fd468cf58edd8c6ed1a90222c9874f3de69a26b74d322bbc8942abdd1fa622934526a0746b69b2483032bc7a789fb33bc94d826ae63769222b50a0a67be7f804ed819eb556fb35bd260cb2857a50cef32164bc6c414a78a1504bf3bd08059109cf57e9611f55934e1179387266f1ec47477506e326f7dab99261d568f659379fbee0cf38ae81c0cf988e250466cd033a70de8dcfbefa0c58c2ef707448cf1aef0b7be9415b08e1df101fcbe2b49d1ba5e1c22db2983ed9aae7a6502af143a479cad1c3221750fe03ebc24057c111f856b36dcc3b01f127bc283c69a54ea4b423767d06d7a535f4c51d648eaaf655343005c03e4713733f39715467c129436c627b526306527dc8d965421ee4921310441ebcf07dcee60d92a87e0f3c11f1d436681e176f8e81a56f78b20e07608a114653800c8241194c14217bedb270d0a85e61eb993a29576cf77cdaa2c79a4c9b5026e1172d0a30a9c57bd3236a770190846d0be519b60d058300a18481c07cc0d342691179e2ca010aead2ea8dabbb103a2a14b3fc8aea0f9944f75cc2bc48cecbcd0d254f89fd473f2f19ba1c40cd0e2d26a1b6d98fc92e3d585aab68d2b38ff988f7a16c9064f9bc968137ed1c73452c009a228b8ac0d224352ca052617734cd24c610cb1492250854adcf81469a4701f8d0d02efc3e944dff76e86efeedb3d8c8e9124b42fe6c81a946841abc3490341ba4ddebc39dde83466e3918398f393d66f256664bd89cb8b57eb423e425035bcbb8486a8715445c80fcd6c8dbd335465fd4530abb41f6105525ab9d83cfe30324fece74f59123eee3fef4f25fb25a9189124b55256fabe9365ef435e16b48bb78e1b75fa6a0ee1e4c383514446b4d72e95c2e36555a3cad7001210e4fe7a67c36cd58cde12a657b1402ead77dcc32db34c1dd34a3628c9b518e5c2695c437d9d3e60792960a7ed673411feaa3cd07209dde7e6c0156283eef6dda6bb9aad4c57dd05c1792c6b607825ab0b47e4a9e0c267748328477920056abde478941f12b0a8a38fe9bb5f695b84b82e80f24a74f13e96b1a8e0b255022170b2eafe70d3ce53f65c9afc4fb8348b90598a9617b66591634b525657e3e24b08710c06c5969bfc6ce0d24ade764b4c2d857ef0db3e50b5196f1c5f1a827ceb5c7cfbce58ab28d37ab1dc9ef3a0c8ecf252b806c684fd4f75b18279bf812e51e4350b4069c9e2ae22dd8f181cad06258a8c4543d88f35bcb540c95e13347d560a0ab67a5a1c135ccc23c490c7fa3c3e934020ab6b3652fb1c6b5c819c514b582ad5a66c4194ee2a5f4edde7d824fb48e8371ca629667b84219cc30b0fff6759aac367349d83ef960ab6865acb24a6a7cc7eca1e8452012f01dd20d2b467ad84f033c7881c262fbdf4e60c071c112a9962102c8002d3aee0bf2378ca56f129da8bbc9c049330010936b92dc63e9f5a21b998630eb5882e2b8bd01517c0daaea8af9aa2b2e54dfb53ad7852c95c3b32a3a46cfb7cae9fd5340afb0171f63ae53744da451796bc30f3aba41f2bb99bcca0099fad28ab7a813f54677c639b4bc962b5a9b77b24e0852a29e4a0b720a926bae75202b9f2a44d811e7c823b77d8219fab97015e10d8e549aec085aa7927df32b558c02fc4db9c5c7b81f5f3d9a29663c3cdaac81f0f1c167405ef2e8e2abba60a463ddf80d1337c55d133038429ab6217a81908f5b9b9b23517b964a37fe090ef1efec62ea9bf2a14fc7e18ef3165dedd63d3f1bb2c64b925b6bbf51fadd04de5836e74ceb5cd57e2cc8a741b6d6e24145e159d1a852235366816b938492f085d98bedfa8a955d58c7cd73d9ae548a7e5ce6d0c8b7291548280e3263408800231299049872da1e42116a7bfb7afe14097301e2d0926116728eef9db938d209d888921b8aaa12993f6570e728081aeb50bda2c6ffb5fe9e9d98e33bc7df9d952a43e140d6a3b06e76220b769733d9b81bc6c2c7b74e81c33fa1b31e0dede5df22b7c189e1848f704dc7051eb83334187b2112a43bc0628f802356719b0186d1caf84238b2af6cf719e61a27f640b3ecb06d33611f929f6145050c43ef8718cb099ee9ce4ce008bdfaeac68b59ebf36024393e5cf679ed5271f8d53539eb40c316ede5009962324cb1fd7a8beefc1ed507d97174cd7e7cc5a839f133dc90d0fb171a8200c3fb463651dd033dab012b34415ed13a3afcfc25ddaa462d1420c6a86410be35776359938bf3ba99cd195eaaef4709754cd6000c7f0e0ab62f49a92117f2ba9c603ea143f24ddf45069b263db0fdfa2f09697ecdcb2da448f9d53e0c029e068e539b8c2dfaf76d382f9ea8d95304f57c5f0171224e85262052032ea7c2f1a0083bc4822480eb55e2e09b504a5a8bff361e3378c38fe8c20151cc774a8af9558cfcde866f858e6459fd3608b1e13db70ad36cce5298769b27a8bbece2c99dc99f91089a506e36e42d6baa71d396735f3cb2abb0b3d6250b83c0528c2b412ba00be3c95ce0c83e50ef944f05dcb454bbf7c60ab8ecc2ae452d0beb89693385bda25e7f87e19a0cade78ebc2ccc1416076092a42edc1b52ad8f1bfeea5ffc8f91a245cfba7bc607184bdfee0033361d118fe7371b061395de9c712bcc649b4a95dbec8a29ed3339f83d5418484730916d243f7a3237476e588dc8a5f6c72e2d4aa88b29159d5fef08291ef07444d588221a63af7992fac83ca35409e09c5bc23a72ad641221eaaa098c90cf6383dd0219c6fa8a6efc24cead13fe4999642619c61f7858b484264fa17ae8da2ad05e86f1a50b537f1fc47ed14579342f1db00b1b85d2cb9cb5b53590a0878cd7423866961ad5b776c6828fb819d78f5a2ff62792a53e8ce6d2ecc94a3a1cfa635b8bf9a5a3fb9e61888299662ae648047151c46e303264c517b5447c3a095479edcb64c65b76cc7c5d98b6d191797d9e9605b5b4606f379761fc669ecb196aa76c9165a8e80e2c021b36605729d9243c2b6f0419fb9ee1a905cf82adee5bf298d297156f1e6d943dc70a3fc40c0e07c59e9a9866f6b3119c7549b05bfc0c1bd3e652f4751b8d56cdd52060976b4db19fa827e895efb7698ecabbd038c7456e29de81e1e061f65075f4c8244b8d4c004943b266402deab8b2921e55c63c5fd751782cd0201c78070b68a5ae6a2b21d9fcfe3ec6ed896f4f3386c4365fefde1b6de95fd9c185238b206ef6e10ea6dbeb4e1f9ec4686d6c5270dbcdef08ed73bd33053e2f84df5ce6bbe5d4678c07df817f9e0f9a9baf629483209dfda20577bc5192c09c89238e8c98437e22a4b46692f003028a86f8fbd06f251d6ffc0e9207daa01de51c1563843888744a73d8b61a505fc576d26265e4c5b5d232b2308676189173f20d26127276e98d40b9a3ede0050b2d7c381b7c157555bb16cb36a9498630a6aea33564bb99bee57596f0a92de5f3da3f8801801161375ac220c96acd41537540fc6397098681da7fc45d7b1cb940959f229320d70ac517ead4c59077e46e9abc8acb386008f2474e8adfc9162cce5f7375c3e001e84f69498b4c7b67b64e62996e033e9ab04d7b439a04e8ede2510431b08729ece639c07ec3c9e5d7dc4ec82668e08e1b7f5d6489bd065e8fba125f505301951b0a985c7f66cff44cb49c7b62346c0301fad1a61c6db6f5b09ef16ecabb60a83a24398412f43843ca833f1f363f847155d5daa59c17728f8146b315d12ca8c238eed165b0fb01141b9bd65f0e6730a32b3b6aef82aeb4be9f14b993e7041b327fe9f6142ac7fd3654c271005873a6a41d4a404bfbe05b77c3d94abb1003e7a9daa5f6e52bbdba3eadc26aaa4fa43be755a6634d5cbbc6bcc87668b4245d8076af5defee1593d2e71e0ae0b269b18ce6051a6758669b4eb5a6276d280d7f870245c3b185c92166d43d6f026050714332d109fac8ffd3ea3ca511740b6d1d0e4d47ca048761a79946aaf5177c8335c3a622133500d192f412973a8160e464548a00e3261c8ab390fb2474ea18dd716bd9b062261e7e8f6e99249642e9aa618289a3fd9f6d1b925067b0580ef27587ef3fb9f23db0bd91b3c166d1be5e962b9320261cdb07a8f9df29fc698078a46b9c200ad9cfa94077537d8b1aba332f3c361bd0f8fed83762cec4838dfb2d861f48f1dc41636994641975336f0a5e667964951c40aa070a4812bf1fdbb01d8d0b73b37c3886837a06522a61d8f396e4509eeb0d238bfacf05ee7afec6b713a22be268a8944d7f0f0a44e3480e922bc0dadf40caa44542c5a2759e4331a975ab10f9094f1b56bf23cbf8d15386ff9d0c5c47c6e0cae4ffe5046c4da0ed542ebd3f8869fc872b1b71a866add2f050d7eab7d293eca5a86b072db2db7283091c218eb969220fd5a3f3d18253a23c0852fba7520593d00fbb7b03a783bbfacfea0b50c41d4549a5c3582f0299f0d5bfd0f8724f06fee71eaacc90724271b09b482a3ac4ab3f14b5c4b356a0b87bb83c55bb812130ac77fff131eed246bbb5575c355c241787232eb2c85c09079d01d20f1dc3f04f58df90becabe2ae8e4dcbb450b3e207d64be969a4fd136a2f8a051240f38a6f65210a14e3ce9eb575fb0774b838a8341fe90959a29a80a892f5e86a819bcc41120162f2248a13da26ef90785f240bd398039063e7d748fd124885cd07ba3536280ba47baff810a84a828e146b5219bfd08dd867ac61d67a3a85ae181f3be78e15d4eb74737bf7f79c168cd836de2c8bfb2e21d4fcb049bfcc594607c425de3ac4bb1459e07dc1e28f1e02e547007825fa1328034e7c2e18d03be8b713b4f21a24cd132e14ed7cce02537fcfe7696eb7b95b8ae96c0b6a8e003c9948517fb617be3ea21f5678336078c62c68b460a88541e235890a90b7282ebeac89c44b700d3f1513da84e1c9b8fa5819f5e6dc916acca2723db81fb1557659c8ccbe2b9e35b997347c4b5dd168d0ef4113a60c1e1216114d7d60585ad20f651c76abaf908d6da96c20017f26f6702ae133d2cc70bc65c43bf1e65f697d2e916007cc0c68de275055c71b0b4a35dfd21fb7d64633a04db23f49d20bc194fa4f72c4a5e08be701d0f325a0b1340e3ace5c979606571cfe042cffa80ca1eb922d362d1b968f8750f1b95d200c45e9ddf396b9f5563d7b47a1f1fe7db27d31bb6f415b9de9f32950ff0b443e35bb8d974d1fbe2029657c4334db586e8c440021b1119a8c87a3430e6e8d05978a21ffbb56347c176834fb1eec0da0a6807743e814e575f3a1173d6e11409af62e7ed7d160ea76499b3849dd551ba9ebab0a1e4f7e89da279e443c112569c9ef0848e9b88ff4b72ffef58c9eebbf0073fa80e4f38892c6fddf1e9ed68a44ec631e06fbc12cc8106d7ce3c89172f5fc37e65608550fbee699a7e788cb95e0fae5c8a36ac4f47a6207fd557fdaf3cf465ddf79da157652a67562f66683668990200c75138a4a210e439aa8a249e804423807e69890ac91c33febcbb36a947dd85a2106f22361e9d93bdf7a259a9c8f9b4e0818e432ebf4ff1290b31e282237ccf5cb17f42b8cc57b9e2d73db46c8d8175dcac384283f3d841c802af8ab43051b9c9ffac2148e8da667ca6a2b10ba1ff67ee4062bfc5fa66ae5d7a542dc197bd3bac25055683d719bbec07689aaa7099c3c00558745e7c1cfa938e05ceba3d5ee9cf2e0b7048750045011b9133d8d63ab2fcc076c41fa26bce3caabfcb5949af058951d9ac53c22c838d68a4221c28b3180d38f1c8b46f3cd4d9f3eac2b941dc718d40916f8311380703518ed721c1f042d7d18268f6e2239166001ad003339fbea7c3fe9c9a203a50e3433c8fe633879918975a8cc8a5774411bd959f69d573b791a2360bdb8c1ec23f7d5e0427b7e6780199c81530dc8d21c12f14a024f03e220a49b4204c88012c48397306736c16394d090c096fc0d75f5f1347e128077abdc0002016900d5fd6ff70e360b6ffcc1f2bf3c03b6d5e1496cc58e8ad0d6e162847c1eb282b5909620187e6800a636038096b4d4cd9f097b8d2903e531bf674e9fb159e34b1b1050521639f9d50f083fa5128c1ea11cff78f022f2b9c6729a1732e93258cab6f88502f6d7a222ca9ff41cb81703726a7f250193ce455ca70f3f918388562e19ae35e374d6aebd4763d226caa9bb28915577eafecde1ba5f28b47caeff0fa4ac42608066a6ecf646ea4231001d0386743d6462769f81f71967deba1117282c90958d42bc335215a451f3fda094e5c6a927a10c10312188944cf17a4706c05ed4e344c37d3f3e0b50e6f3abf3138f3b5e2401c84e10de31f09754988793241dbc580e656837604d156e3d5abd96dbd549e9d31c04b4ec33de7418904545294910ed2f60c70d3aaf74325f38e853b9a4beb41ee442ba49525fee31c2d4e4f2acd9913a7d584a13e6d793818b80202ed4571a626e7bc3e4e9fdcd3f44521049f63bad551b6c733f37545abb4dc07668a2f51e99f35d4338de4638588c64d0a17619c96f6920884441bf7d563c744681108fa96223862ae7bdc4dbf46eb1c4adaa31a40ec489ebed66921e3d51de1a31dc6d9aab594dcf94eedee1ec71ca4196edc8a7674ec33129bd56a616339bb502113e16c4e888b5ea4aba1c5520051dba1b438f691deacf598c3c9151c28df5961fb637feea239dd576582a441787b5f364c864fca9c4aa1679fef747ae3f488fc032921a30be9ec244585a2564eb7065fc6150d1b621b4026013e5abf5d057b53190f08bf10723373487a637355cecb13dfb6e29313157f248c62d3dc2308daf3f254f06f3f70238e671e7d9440d80e6a8da5ab67db447d51c519121fa412fa23d011afff5f8c5c0be0fab2b2da751d0d16adaa32cad97e7b753a7ba0111ce340114b38145c66af14b10f34d600a5782e15790f72b7abe4b214d040f843e9472679fdc9df40679c7da2177a6b9cbd087dac25433e9e7e549df68eda3221fea3f30d081914b7c9a204986c846db6ee2941661e60874f8b752afa147b6440c53965c1aa75d3dd457c9afd7dc687829e85c384efce6a6b0988789320372eddad3e957c20b7c03ea4e777219d0b636442f2477f060dd6385946d2df75bb47cf4f7afaeecf1e57b74ed179e9aa025a29e435fe1e7f7b7c3e8aad1e88c6d608bbb49bf0c07457f9820d8c9efde01ef6e8e72a8441f6f4b98e02c9a527ba270bb0f922788b11bfdf93fae5645dac0a6352bf39ef36729244a0245acf3ebedaa4089bef9a624ab56d18f03e602f773d8e6280ba3635f55db76e6a00fe2bf95e564bd954f1403dd3be0cd9ec81f185298d9ca704de1ea97f7694a973b1f5899bd67f12efe210fdc0a670cd4689cd43ed8e56383b01b84074205f22f47b8933f5954018ac8d98e61c6573372978f177938c1e446a80284e0c28d23103adfaff4b27d24329d1d685ad791b957dbfb0fdce9d208adeb927d9874aef367c0004c13ae0a86fc3fa18497c5fd8ef2d92efc176feb21ddddb02510e9888070ae8a517ddbc7742bee4b3a1a03a03caffdea17fb3c886de783adbd0750e388d207babb1e7de82bd80ce0bed9e949fae977dabbfe6614a4e881d0a51289672a5ead9a75dd32238660e11b59c81e48df6a45407582d593f8b3f34f79fe26aa040c5ce6849287c31384326c94edbb2744472929b3412b4b084b996b987afcb18c24b8ae4e0550b5c0eda1110111a011ea270c18852e70fbc38b29722e43fb0db79bd7736b1f0fc519e3ce6da2acb6f297bf610f9a7bdc60bc318ca762af290043af03e799962b1018f497b132476b87515dbebb7eb14cb1c28e587c58cbce3d33d66e9c90ed6b768795c83542308246582d4333971824364a40ec7c3990e73bc0b9f5d4ae59145dec0809fadc3e0160475703aeca8a9088530bb7c95d4aff4b6ed6c9d421240b7b815248c823f03397c85e2a41c29a94eb3eae0232027c3d82c9265dc480afa612fd78159eff068e81da3acd5a3aecc341431e9d86a610f2bb8e056707cb3b9f044aea4178e78bc7f0d062b51a77b41e6932f133d11075b4861ec78b9c2ed8994e2921597a07a488468ce35fa15ee4a5115a5197f62a66329a7aed8962d0e4fca1e4e3e7f92286fb70cc29f96fe414f43975e9f1379323f2cdabd545c5e6b6391afd1f2b30ea39c5affb62ee9f89ef15277f7dfa2ad35921f0b3d2b7b87df6922f8dc7c4524ed7bf955a554bd5ab27b7e761078daed1dc56bff2b50643c44ee6a54dfb705469fcc90078438e330365e89996268fa47d9792841c7b09f9df2ec14074af02cb26b708c85943cd94364167e06231e4ba6c31e46213787ccb67e6fe47cc5e3b6bdae95d6741660898ca966bfdc8b434b673385ee702dc321c93e04563cf5cf29e95e190411f7f1519b0a3ce6339759d0753b56028f702dd83be8c7db0893ab19dd503967060607b7425838271edb2cd2dc74dc530760483186771ffd0c9bec7bb086c4f420bed030b5424c1f781f3f9582156f54cd54a8eea0371bf2a39fbb647a9be623314c706390d2f1cf636b883c543c08ac7ba16aa77ad9a4667b93cbd15769a0cea2c7e92d600cce287f8b73c11497ae1f04b54a482e9a7573cf022c468a2a8b5b02a714cff14ac4c7bbf127c0fcfd47f23f367226806faaf7682b73b178e13429154edb5e544c88fe63f4305ff4baf5e7b95d0e53c4885053be32053ae00db3f4f052d4a30b05486a1dba7c1ae2aab3bf215078dc9a632442f09d8b08e9b7f5106eb72db85f72d7c493624505abd09c32e6cd49689cd0273510ff736057bb1d80471be62c7fc1507c11ffe57ee1147eb8d3e2cc2dfb8a3b6006d9fdf8bf67270535d3ea01fbc055c7c3640ffdce3a9cf878b0b057517d7fa73ce7f752a731676979663b6321f93e6c32cb615ef3c3b2892e16a83b1dc250f8902d28ff9163647a1b2531d843be84368ddd9ae942aa1e1a912462172cca85e91c173703a90ac2d0a65266a1828639a47313bc239eabe79412b44e96743a004299cc418c7a733d5c1d5940475d0898c96274632f5811cdcefdf03ec5b1b852b89a350eee2b0eba0ba749e872bcee48b8991781ed4cc5cdc4c5c7942ff8429f3cb818307b3e9e359cd3dadcc8dbcb3e0de0dc05b0bb814d065ceda06f2d07149510131107202279bdc7e3bd2677d51211f5865da3c003ba4948e5160db9404d879ef3d19f6b420c1613f10a2dd068d0424b18b7dcc36535aebf215aba4ec8246fb4c833dce17c83a06d4a1d00960bb1c51af5d1c57e5fe237a8d665197b2e42c956f8a1b262871ca2e791d1a93918ac3450284f2b143197a36e303a9bf624a6d445384457a4cd8bcbecc86e83af04a31bc05723f1e6cc7728360f2905e2586fe02952dc6489e33859abb869df56b6d6a46ed518a7bc9d221e58ee918444c0168e74ba8ea96415cd1390a56837a2b41a6a975af0c830bfca70a4f25c644a5c83016fc3b945f40b67c40d4328fba9aa4e6b26acfb27bc3c9ebcd8efa820d133cfb260101ea5b83ae634200bec90366eab172bb63cf01838b26d2004aee5829c1fcc1269863bf9ac7836d1f74f9e9ec631a9513d2012752c610043966be300618ed5aaae82b05e2ab33c1e0297fec247169bce5b62ffac3614721f4818abd7943eb6e6064fe2eddaf09c2f31572e26ae93bb008b7ae3518346548de1db3676bacd119f3c2d2c9d35e3794bc0d95d788b785627f69534b48c82b073d39ec9621821f0221cfb998d3fd1329e0586fa1909553da42c83af706365a35db5cffaad4e830bb5c71f77ce9920a4c7029a406f6bc022fd1a3faf7784e6987b8927d4e5e382254cb38320fc2b8f2a7491b29a5f33eca1e84fc073936b6b53356a808934396c738ba00034b8786e6a8868512d614efe9b33c8682ae3e56efc4832a1b4cdc178b0f865e3eba0e0721ce80209ee56f076ea7b622c39981a0243e5cd7e378d8832637f13891a76ab7fe9d8a5774e5bae530dc96bb60eb81ca340177e51a562a3657b6d51286eaf050dffa9845889ec3a9bed78fae9c0dca40e52b31e04cd752aa97c6779f0990afc40e679fa3d10ede239c3203bf17464b0366172fcc7647c72ac7097f79a822194c0e52fec351e9664096ab892be59ef697786a5d9e1d91c768037d13b4a289767bd53f241c499a13f13baed4b7a09f44ae5b8f2d4c74f1e259f9fcfb987e33772e1f0424e9d0470486df547e10617e8a08f1fdc641a83eb4aedbff2a2f9f4aadf9f6d1503719b862958c42b4f1218674b47929b136aaf84cff9de88cd10954e2ef9481715f941bc38a8db262635971e359a3b1acdd6856d148d63696151bcdaa1bc39a8d67e546b0cac659531b07b152091549c06cac0213b78e07aef383d4f5e0753e409d1e5cd783d6f9c07572d0ba1fbc0e7cc05be707af7bf8ecd479e30e1231514c69cada36dbfd902c8f19a6143d8bea66a014fd10a01debdd63e222e0f2b33930c137ba7be763440d00c7b83d756c89cc02a6060450c696804245d50ea43794cf10b03db03f21ea3c7a1b122e464cadf9ff1400fdbcdf1c9e4f7f043b4da142a898c56af9454af79d857782f7636e58b4c8b5d41536c325002a5be2f4f864bfccf4ca293b152b5c1a72cc1344e64996d167ff1cd4d774134e2176a01ea4f9f678f3e5ccbd83e3343782d9f1c87dd193828aca06ad874e712f65d9629f929ca9d8fbb16ef6b4d4cf2b12e84e91ba20281cc94adfe7db2bbd21509e6e482e004853fbe03f5a0e422173e95133eaca3d979fa0336f0477d478651a9a9011f48aa8b053e1a00a1cb6cade011b03225cee9cc0227675e31d3e16e0fa102c2568f577d0a1613cc4643c24d0417a7e0b52f1d038a361f0722c10271326eebcde6236144bd1f5518c3dea46eedc3869e90942de91ca9bc2ed7ff2428c6e79380a3b1abfeae5dc9a358887f90cb10188e164e4148d8b97594a01b85f6815e2a483f19577335e4d91bf6f784e9e0ed0d97dad7bcd7f0ea53afb6be6717f8e79b6e86161245a8be2811a339dfcac83db3d8e4720baff9e0d5936b79c75713ce2c68b5ef389cfe8fd9d683dc112fbfeb2fa5415c74e265e4e9fee8663093eb4b48bd4f681800a6d6191d17351c52af7ffc8d2164001a9c4b20d9be4781ccfcb0a8ef2a54725d3b9e00568bc4a818cd38dd5005abf2ddc3ea2b79378aa0f48424ad9e0e7a09c8064d2a20c111afa95d31ba7113aaccc9067e6c8549d640dd307d79c53727e6e1ba9b854a3204901ec76db04eee47e17540c092c4eb500ad60e228b8a0c6d5c8c0ee9b59526b2128e1c33253624181c06b51f0a09fad74efdba1c2121eea0239cc40e9130028bedc45ec61d975ec288fd0e7d2eb5af9274a0d6f91227d9b1ccab9d0154845b639b524ee5b5a91b2eaedfba29c82af83a616b9d31e6d8efc9dfa4d2513e8ded4da8b17248e2e08687ba6189f3a96ad847a26de7f3659b89c77fb972868e1fb547e080edb9c42edbbec1f422f7d46eab45d1ab5713b516ad95a22875906ee8c2857806c4ee33823fefcd92df0cb8658ebd950de6b58be2a1cf4fe9aa827f06622307924c1bb4b1b3b59e7b8d5ddfec7322387820884cfd860174813567db4bd4027a91a49a7801504782f37806426f0e19f74a4f59c2004627c1df0ee635b2fa6b20f3a82dd761ebad1272e58cb1d82f7965c320a0306b3d767ece37077d5cbba293078d32f357f0eb0b78ed9cdcd921b5346ed56fbc43e18918e8c772875ac17d3b30658a5c90c0621b22d2e4ae82b8648c146bcf836673f5eea9ba5896051416d84268f7c3a5f941d615f3107d763a26d914c1b6f04e59fd8b3e88967ad9f94fd6f455cb8b571ef473f5ac9a1e44b760e4f1ff15ad2ade6ee0104a53a98af813119d4aac13624fd6a466195bf6505f6941f25d7730bcc2e08bd0c15343665c2fb2fbcefedeb848994b6b0a927819873583a27c61a18cf353aadd06d2426825469a1b4c5fa3851e66d82aaa6783efd75b88738e2ca7156e209f8d75355572d5a65dcc617122f112493ed94f263033ca4e908ae38046c3d8f2bcce0ff1ae2c1b26db62f4db4e2dfe08cfde841dfd2ded1a96b6c15ba2ddd00ec5f49d6ae60496bb8b36806469ed07a2391d2d692bf3bafe08aaa1b2a5053eddd499dcdc9b323ce93997db5123135a8bde95285883f3e3d0c5ed3856a52fb79ef0cdd06914d52fc9c1c00bfc16a9e9248b2ee7f85ef7a1c1775a02010043139f828239b722178474a97876c431302dd573bc85f357eedbe6d71a89c2d07ff155c62688582d18cd591e351b3faa04b17b142f16ade4a93bc6c1f01c2b542baa8d92b59e11a88d34d0b6f47a7665e9174f7bce57e62ef90f7c939f2a87266f01b400788defaffd5aa46631ef391bcfa234ed8179a9a4b8a74d19bfbfb87a122abee14fc047e65700e2a3c0c3c3185edcbe20c65318278bc1b90824ab1de9a32e1f805afb2c00c0a156c5fed2dc98601534e145903518c896071d02e65a9f7e9453e39d1914a78d11dc2ff8390272e0225a32f75dfc239f938ca5ca29e568e99ab18a301071436ed07f672e29ea893fc1aa78368964a99b69a4820fa1ecd2b5e983c4b1685dce83d01b301c3a146ba672a763dcaacc96bc7be4ee0d54dbb35bb0c4a23f5be655dbc8e61ab15f9cce82e750673df208d78ac87fe3a149802951309f5e7b247ac1eae45d263e8905bba61a51ab771d43c2d23ffc00bb0a4fe9a8b39083b31dae12da68c408b61a00328687fa847cd8afb8eacd465fbf054acb2c674c2c244ad50b4af5afc9c1f666eb836545297a7060bfa7da8bb58300e76e4d31e5224361e4b5e45ea3b3ea172586f46a95901462ee15ba77bbcaa867aebb39b656523f6ddeaa9b6a9dcff8c12433380fa617996c1774ee499986c7e6b141e37f1d095f2e102cbdc4b06b655d5e03de7990250d3f478879ed20eaf74d9285be921c1f3472befd87120eb9a968e5c6c3bb772ec329a369376795351a89aa0d294f16eebf4bb50418448a7156dc385a5d5f5f8abdbea74f13ab2681ad49d4a7bb4d4d406c0f2fc3f0993b498cefd1991b1f75c0858f96c2e49d0124aa0e2ddbfd5b0c5995db19e23d02a222db6978cd045304c266c623fb5e4311df671f70dbacc03fa16ba58007701f6374fce0ca1d8772065779e287442a30ee2bad4e088c503d85d2a4e71d2d666d170dfeaa851a38188c399344ff04fc168f902e9bb9237e159d3b48b5c5f609f147826967f05526b550f06d5c2cdb200126a350ea6b54c84bb185876399602e85fe9a3124f04dedc454e2e8ffdac819505c6d02ac0949ef4f739ddc36733a6f7520846d4dd4aa8e618977a2343b2253d0f7e617bddc79f12b0404b6f6ff839bfcb00498ba49afc0b3f7c4001da0e7556418c0bb4d5cb3771c88573a6a4c8457abfeccec09215eea152c4831c1a024f20897660fad3ea0d2f678e9dd897f73fe8ce398babf8a87a00650a148de1e07dd59ca1433c15ce95cc18e65dd55a26f0776e51225ab8584abf80752f44ee6e67c046a10e37360e196fede0fe1038b5d2b6d1243d8a5a6a5b8168deeb0204172cd553ff26861d5187dd0b1848a192f7efc1e73453ba348e7f788f76449696fba56f674bf9d59818f58251325a4c5f10aa2812544c2fc806a14b370b9c446abf86adc2c98dd83ece888a78a8ec7407895cdd33d8cea1b9cae92e7f9f4e8fec81232064bb7ad8c37f6258d6dfd0a760ea4f4a695d734fb841b760789b1ad3c8e7934e5b36cca5bdd53dfacd927a3abad36fd7122722d0c614e4d282b711281a1ea444275157265c6b7714f6276876c5d34c8ff3ee5243f800eef3a1132cd46067b39caffaea48409536fdf4b09acc148472185d6ce77904be2a7f25e24f500527b43beb0f0efa1a4bfacc3c92bfc63a8b3f9c06759b2fca2297b65984dfd4ea47c62db8a08395a99193686375e2f4ab81257828e1069e95f4fa12261ae6dfe55896bf1c4321eb401a6945c98e234b312fddbc527e367ec353fa9facefaa4af46d8d4eb4376dcb97dbb74f90efdac7656938555eb49b47ee78eb81456501ca11d6df07c29f19acf71c75d9868d3f6a346642aee4d1701963eefec5ca446370d11d7eccdc098220779e359418cde4527a0115c1b0b9d3d22800da87bca60184d04a21ca40015b01a21813115420377200e3284805600befbbf0506e61c9408ecac7873d75c36d222463fed21a0935694e159c31293b6adac5e28375b6e8d8f8ddfdd317467251da698b8429c019584e7e1e3c7b62b590d1ef9396658ea5ce3080697b2d0352da18366454938e8e7b6967489d75e70628bf7e02cc134956693a4e575156549ba2d2607108bb34fe78186fe4723941d11132a799e7b6a6e3042dbd6070a80ee9d048e7ddaf19ac4bccc37984909e4801772b7cd1491def5bbbe905e4424bd8e6bcc19a2d7e7cedb793d75efdf173b0b17ab4944fd7cd7143fb786b67a3d1cf298177c0cb21e82c95fd0ed039786c5240eb9f2b504e5262eac612f9872fd781eab41004a72efe87ea9dd45fdc17085569afdae8fa4fdd1cecb5e8685b544831171aa89cdfe478ecf517f6e464fde1987cb019fa3ef4e83f6a9909f7f879723085aea16a374a2f0ffac47c5980711b12f8676fea2e752ae33252f626df3419e7e358a62054335d447329bc43860836af0f8b7154d2c5dc0ad05d2af46092d0959208cdb6d5bd24554fb94bc2a9dc4960d70bdd673d6e03d6d1d4964735fec078158822b7cfe44e99ff5e8249854b11a047c18971a49cce6b061069b02de621dd49060e9d8ef2749dfbc1a3d10ae775dc7d9ace45612950fff995d29b26de8c6bf1196d4bb52e9126201d4878155131f45d15c2d58207387d6fa28ab1772a5aed866f4bb46bc1f16894589191d423379b27b37a5ab4f389a49a72f728264c92eefd8c04afa85e8d1ceca4eebd7a1664b48ba68ae99cf01ad4b861cc533b83bf6d4d679bf2e113030fb2be0079832fcd115a8324bd1a7fc6a9b45723520371f3595b21c95c067040465a69e7387bf96265d710d6cdb4e089e35add86284b09d46b6f3a5d4f7c7aca86ec6913dac00b17b00975ad8b34c07a2deba83bd630542f280b22d768e9fcf7c2a4a3a8b86da35da3e3836a0b71281fb687de0e6abb54440b77b599e58f4eb163433985b5543dc9dd784d428a667a5787a3f0a11c931011e137c607dc3fa9f04ad95f6279ba63957cd576add37bb27cc52c0c127e25f7ad67ea4db2bd43fa52ac3582e159d726c0d432201ddee8e36eee44a53ffac797a95d683a9b42488f2c1896a773af7aed21c287b678045a99f16f724b3dcbbed1ea075726b40069a31a8bccf178e12cdb11dc3aa47f494ec00015095e3b0c586b79c46f0ad7ce1dc362f518d8ec4c628270417c565b6d3f996acb7fbfad6f81739681280f7d3f2e1ab93fad83c74e070dc4e099c6db654e9a0f7811ffbac96e853990481c833092b788cc624b0ecc0557a529b771573f664216a4462a5af7e7fe9f904ea42112e75f4fbab958a2439ae0b624141d1eb5a4e5cee7a38934fc4b9483c8a1adbe2bb51933a21adc42b1b0245cb42825fb5c6000574f7d0f86d41695d05aec3f6308fbd3b3de47f17e23446f2914cb83c462437a373c8522be3422a96532bb254e3fb80dc74cfa5f5af7075db85e8868f85b72e5a9c114e8bcc5af3d9680992312a0cb90fef5e29c0faa58fc72c973c56e8e24169171399cee3359587e7ca71a225d0932131587174f4419ad995d433e24abb8b156eeb38de9515448ca6a820aa9beca337b53cd666b6503e5b01b08925dae3d91ed9f878895ec6097885c2e795acb11913d93a80ad2ac4e2fa2a3ca209197db4887a4909664feddd79f97454ed22c4a02d9ce0181c4d466acb828b794773819e80f5837f6fb957202ab6410559a32a97643e40b927981b7578af8f4799f19178353ed0a3b41067743e46ce76ce3befb6bee86c7d80f32595c76e7ea33d4ec87d8b19c6fa9b1a2d3f69858db9490e2400ccb71d7fa66cff7c1e669711abb1bdfde74c14fac9d04a126caf9bb5ab5a84110d5ae16007d46b15fcdb46c0046a1cd50048dbed5effe502b5858fce71fb8d55686e6e59ae21f0477f687e6f33a4df7e6805a1e4176049bc7f297544ea8aa4915b5a6730e8b2c3b2d200ff0b2054baa6d26cb46cf87ddae15aaab8cadd5c1e2d835adc7c0fcd81e42a6a72698d10d889395e4d6deba38a0bc77ff9442122033e0e247bfdb225b825f2e1891dc57b958e57a83b61083da7d1829d235e5983cdb253ae86b32bc4998c9b00a664e471163d80cd352c8066528ff34c1943a4a07322bc8176b3a9946988d3f494fe5769b9d97ebb447a4b5f60ca80073ddb6e324b8fc73c959131f1b5823bfbb85c27c4ec7a25e4f846b7e09afc532386c77ef36a5ec108e9a999c0c768acb0b653be3451f989dc2b83e0dff812bb86fcdd4050be19bf70b5ea9553e10b134f642f1b6ef2fc1ef0aae8dd16782ccec29588fb37b4fcc3c58b6ca06a948dcd88198ded0ad67cb45410e06554f9856e51880f1911bf3abe0c4c27663c7f8480754587990f23e01ff25920c8996177d652a2b8900a8c28d61f7ec89caadbd843d1ba7792b878a7dd119ecf37845ec9267988c0f3947af2ff6e118355ec3aa62bf6878c5bb41015b3491e361c151fe2bd4ba6162b93872f4cbacc439507509a418f1fd05a9aa2e77d5fecab00554874638061349471f2a0b9bf4058dadf5fdc8aa236e62641a90c2e5ee315c377f9f52d83811a97721bda5ea6a326d397cdbd17be3a81e4371ab8e59dcadbbc9ac94da1c50e4b605d5431338ce34ed9aefe851cf15f8e029c702dc1ba355f5deb0068dba91eb2c3054b42ded2ac2ba15bdb3baa42259ee1cc50844f020022deb1c72c3e7233dae06c19eb4dc0623c3b9fc9b05e6c4cb1f4cdaa33bfd033fd46904275c5bb873064f6cee7a422bc8569699f39adb506341c6f0fee328f35d9a966e9320e4ecdecb3045ad2ae246714b103951e55031a610bd89be241bd8921cec4307d29da4e6e03e89087a28d7af8a104ec1c0795db59ed657c459ad73a005ef9759977a213b95ccf20959895148d969c988825a75e0da30e990573797d1a16adbc6372ba1ea149dd10078a1cf817db91b32639e9b7e4e0d3d8080818c1dc44ac37572aa5308ebbbb9f804c85136290c71bbb7c17123bd4e8c8922294f4a664ab15241ca9dbc1fb0e06850c7d4208f48d1b3b3baef8bc2a6cab91ee60a7a8e9424968532d1af40e6c872afcc7ab75fc08e27784f13dd4f729502566a138a723137cf026ccf275cbd4c05bd7f5ec14e24ea7fc0679e5acaa1d7b01a783325b9b1c5cdc12e30045a1c4a8b5328376b252d93c700544bf93c224d4a29661e074e05b80c997ccc3f869b27287a575ff300205abf720dda05442179b15cc62f4c1e7099b664b095743ac5d4e0953298ff7526950c1a3c728750fb93a77abf94e66defd6d214ae0bdb7a0fd7085bfb850bee5407f3c9dc1a973cd8a0607edc5e042939c59c49009bd84e2e5886427c7b26864e1e51e53b7a2be111be3855370a067c22f7a65af10c5c7307db899944755676ea0fc52a3511c8c302bf6c2342369bef5b265b11fec35b850233673cd70a0491b91a777a3ef3d634db61532158695964da4958c9b01b66e08b0dd44eb26c744535939c9865445c80b2f6421efe5d75299afd016c8bc60aa1783bfc0a356ac17477b2a85da4622d3cbbd496feb5089a7ec7b81db171f97277dc24ba4fd3fc464d603c1f49ec032d08e742accd7542671d5914c163af8208d1aabc614d264623ee8ca4c44359921a77f9ba92928d607815b8c6461f983db426d1d0616b5b333ae1dcd392977ecf181d5258d5550d4b6b39bfdf14596b19bb7b6d77283fcae385eb5f1e2363cd66987f2774e6a3daa64230328d1a8ad6a8accc6095110dc05d4a52b4cf7d8d6aa7e849c51ba043e0a823f57295f66caf769555c3e24df49850f388912941b94648012d5be687e576d3f5bb586c56654f748b48b26195e3f52f18088fb0beb82affb87d7aa0df53fa49ca086edaa16b4614b543d3d6652bfec05f654725d0b9124c03d278efbee4e6ecb80875e511a58ecdfd7128ce61744963c6e8f07ac783cea320dadf749066dbcc391fac9a9bdb651ff26838c9747b7cd4876ee717610ee7865ffb4f6cc853bbb5f6db5fc7f0b89d4af15500f2db06518de6939a10af925da60cd3b235f128bd1aab5e070d1b7643cc7e2b885be673d3c39ef359c6bac70c3104a3b5f03f3121537c78fd7a8aa6a4cb3fa77af704301b0c87f2fa5224f87711772e62e2e9747e52b7edbed1d8849e03facdde91592090ba9216032cb8aa12f76621d24bcbe42f40247e91562ede15ca60e0e73cf36e34eeec7d0b972db486382914e00c06b540a96a4a1a15754213f8e9aeafbf78ae17fca807ffbad584ce691ddd116db74608a126b4b2bb7b07280925093009878f64c89faf29e986c70870a36f874d293c75f83515bdc9e1e9000e7f0570780e85c363130e2fc4e4f0480270f82543874761c9e189821c8270d8a1cf811c5e861f87aff201d209494a9492f0806489c861cf7d1c7af097971e58f038fcfce8613db487df083f720880f31c3ea6913f5fbe3efc76aee3d0bbcee12597095485c69c1c5ecbd20978890471e4a60568a5c75a784e6e5280bec94d50b4d2634f7e23372140b372d301b4d2632a7c959b0aa0b9dc64a4951e23facd4d457a2be0262ce040501e04010f3ac08914e0468c7e24173d96b50d4977412731403bb9f4177b082edc85ddc48501e80fc8052231fd0de0db85105cb811222e0429e0420b88b5a0939c72dc8fc845d82100fdedc75010daa1a9e0a024a7adbf1d990206a03f146e08f73bbef7d69f0917da424f7242417fae8b1082fe727e11427892d30efded0bc004fd091d85c75c3872895c2341416e420b408f692a5c0b26c472dc480b381ed482b621c9e94f720aa23f13de447fb10fe024402181059d9400edd4d2dffd072c18394c53b92c10799213131602a03f120ec402d01116989c080b1f0480c883b0a0bf20076ae1421040c2139de484a33fd881682af73bf2c308d049582204c87fe0273f9ee404a43f58d6c2d07f682a9789fe7edc25b0030101e92fe84234150e48c8939c86f4d7fa124d05ffd0df8d7fa0a9e01f1f007d48c8979c04168280e80ff600682af7c87d0284c89320fa0b3a094f8290f0242798fe700ee4c9cd8d3c810179921311fd053913fd91f0007cc80186aca0930ca09db0feeec7e33e3495bbc29127399db082fe863c4853e1562012e463051efae37e64051e4a8e9cc80afa23f2a027ff29c010157492938dfe4ef85ed74712fd057d08921f1f9a0a7ee9cfc77b682af8f524a720fd21c99ef4d0542ea7bf1e370992070505fd04fde027e9a1bfd579f4e0f1242724fa0b7a929f23f990158c9c707d5c252a10d19f910f5181c89027399da03f9b2b51c187fe587fa9e0e3f524a723fa23f213f437e44a5e82510929e8240268274d7ff73a9acadd4d295c9e148ca460444b21058d3ba2a96429e8efc88da8f022994ab69b8a4a20d29fbe0e4de5f2e828c11629c2a3a960ad3f9eebd054b036a2bf9aa97075ae692ad7d61bd1543823468a1829f224271d3afabbb73a749ee454f567e4b5c8ed4bd054b2dd9402bd3c97a3c21dd11f7d099a0a77a4842739a9f4a73a47c4a33fee5a53c1db8624271efd24a7a9bf239ffa2be1f442b3c90004800293000c2d394149122426e4111ecb247c67a00b65213f9247780999841bc9402f92859c481ee14332095af2205ff0411948c3ff64215a02215ff041f2085a2ac917bc0879e72164efb00c0290ec13634efe23effc83ecdd47ee79650f64ccc97be49df3c8de79320074ce5566cc894e72ba863bb47422efe826a2eb648f4877398ebcdd9553cfc9d97172f7d46fe48c95b7e39cfa2a6757e50ecba6a5969466cab4dc225fd066bb021ab648b36bce48a0c4022936cd330b0d5034d833db1d408006061269861b2b0390fa059b1095741312923f14683bec0bbab7d6c3251f30952f68067fd08c9671eade9b5f73f6744d3f037fd04c14970b6321f9032bb53686242fc8577dde00a65f50845d1152cceaba6378010afc052dc52520848290148989813c3546f8bdb0a504a955fe1092af0d52f943deec801ffb87760ff5845b9b457bcacc9eca1e94e5a0fa2053b21d81b9a98439828881bfd4a68f6bb6fc96af6ce71e10d1ec18e26e9ac0b65dca6ddb9036ddb659bfd42715e035e814358928d6180a5f767d9124b33f986606f8bb512aa828a5690618459f484a67a500d296b3d62921851b9452eb5c00471d0586cfb2419ecf428966d494d97996edc44bc9a4e4de235fe9710f7a1ceeec42d2831ee7b81e3d1ef34e8f2cdbe9d123fb0ef723eea8c175e7316731c6b8f3e57ce736eeec1c764a72ce390046edcf4a972d25e731ea2931c61863ddf9dc3bfaab42b9d89d9093f3c5ed8212235f59b7f32c6f3b99ae54c897dca94b70c8ce65a6eb17f9c2227f2a14cf79e83800bcebe438941cbeea3bba3e411f8946be7ee0b2736c789e9d9d9d9d9d9d9dd73452e0fdd5331b03816f7cdffa3bfeb9f60ede3bf9dfdd3bfad3baaa4103f757d15435f2e78cfc992ad04e908a334e789eebb8771c5dec7b2ef00936fa3b9975a83594af9defe84a867cc119a94a3fc121fa3baf3c348ffe5c00d03af487f74a477f7773ab1c38f42757aae3ad755dca75eb82bf8894f445fe7c53693fc91ff8e40509b67c85923fdff2144afee0b83c15237f762e4fa326508fcb53a50924cbdcdcb8f987f78d9b9bdfc854e97a87fe5cad7f5309c7639764e7b0a34ad04747850257285d01b0843f59e6c975f91e19caec3b5996c1b98efe2ece3dfd619ce728f345de8bb1cbf5bf5e30d817b423ec7068fde5dcba32dc391fc5c91fbd915a608837b2120c9407d7a78f93576518625797b8fdd5a5a56f9a994a53c96acda30bfea81255923f4b6ab63c851b045ac86c18bb376960798bc42375aec02f36ba2ecd6a96d5d71398db10ca026178d8c00221d8df6f012b78883ca899b7c0123d7c07353c3019ac70c8384f63e424a464245314d04d0c7e8b2d180c16020c0683c160416021c0603098bc71ef4ddd7befbd5ca4f04e209b9818f8a548908bb1fcf9512395d3470a31126675030c03fd91baf7a6eebdf75eeea6eebdf7b2388ec25b44ead01d1e3e71fe7407f2f011a2037de25d96c3ab584b1583e7689c18771004304c1783d3255f31062790168313281583f7d4c01d776f90345832cd73624ccdb4ffba344e182c082c04181c026130d85dd96b56579140aceaa81d63d448afb592dea803d35257e75e1daf6b5ecccad5f8aaf47256522ee3a0bea01fb0204499e783abf38ce7d2ecf5c5f3ea51af65b97368b7f66a2909fba0c72bfbd537bbbeb4529adddfab8704cdd4eba5c15ef5be226dddea7a65daccd125a95aa5b5d2d77dbd347d656e9787069db2fe7eb384d43f1d7c09947d2645ed6618a31986d938a29860a8992a023c85fd9285a065032d6e9cc0dfcd1004db4b93e1533ed205914bc041261ba1b9c88aa399881accd13c643765d764d37da6f458762d27ddf058a6951ebbba6a164733097bc7061996c803f4d1b265cb1c76803ef2ad2a7006caf121ff08c1e1c6bc82ec1c8ee61176d0c01fcd22d01c420e8e66187ef2389a4168858066203b8dcf179b067f540747f30f56991d1ccd1f442e5aced1ece30648981182b504d30d24da28022bd910a1b45a228a85c40d2a2598f0129d1250ab2d565880e34246b505aa025b5354b7050515d09ad0705bc4e021c504d4b685064b978bb6a58d1e889a404a82071fca1041c46529892474205ac24c1250e84094849a24ae50524a02044a444c984922cc0f444d5c72d0783f2c1de3964b2703d2019e293c2df98a3aa49981926ad75a5b2e18b561176994f100e9062f2c69d6e841039cddd0a305b8ab800722c02c0cf808036b4df4e0a00a2270b621183442e0031a5bd88059649090907e40a38c1eec5cdab08b36c6ecd71768a4410234d668c9c10e5128a17936c4062184104208218410420821841042324284f1a201df15f830836dc07c8044163d5650851558db100a8926a640c28331f0dd100a09227ac4e06c43282480f0629352ca29a58c5766526ee84329fda69c73d25aa994724a29ad94126fb9240a4a4965a694ca2ba794320535958b0a0c2184104269ed9c41a6a5f605a370a296ffece79cf39bb79452fa694b27a573ce7fd99cb37ed63ae7dc2a47d3e06c2aa7def47b0d01ebfee69c733e421ff8cd39a79caf5965a5130758d21aebbdf495c4a5524e6ae9cd6c5d73e9a51ad2bbe6d24b335bcfd09bd9bac6d6ec525bb333d5da9aada1d7d635975e7ae60cbd6b2ebd34b3f50cbd99ad6b6e5cc9ae94935abc532f6c5a6d16630d5b461abbd45a6d4699905e88823489b42012e88b16a01fb01e286f8f84e5095165d2c2c46491b0543da318242c99ec329753a9a65f28e87649ee6f778284d9ddeea2180245a6881441905ad3bd1913d2182631910b8d484abc540ba1cfbc4d9a8b71b4914b47648184d2768408761602117e80f6c7ed6a64d7236e30220c1641881140ec29718c1016f4579669b892704a08e12319f8c988a495b5d6c2664f7cace2880d5ce18123a8382266add2b6d65a6b6d50d06b7f2da62c76e02f36e5590113103d6bf0878384d9456c20db6a8004f3bb94527a420c3050031a41d040882e4a88c97410812f6c43c51a5bdbf2fc8837ec2f4811f8655b11ec0f8eb19ab59dfd6177aca5d6c62d2dd8918a6db394d9568b6d7fb1eb2f180cb67d90d01222226eb0ed9f403962db4779c1896d2f0306ae40d262859219db42282268c0a4650c9722b874b9b22d84ca0017f6622f62b6b50fb2d65a6b7b64e1c0bd9d076012a679f1210363260566071ba6cbf562881a0629c352460e148bc572831fe6089e6a96a40bc826504f7488206213305db238819b8c008d7429fd8075288049d8186a18e1011835baa8a106163d30c0aa0dc1a8d1850f23f0b6211835c078f980ef8660d4308303141217c8d9100c1b5b94b0fd6bc5bb6950a2440d0b54755cc76198109109a2cc182595627e3b929b3eca5e77b6b4cdf96d9bd56e75d65a6bfdab6e72665de04fa7428922034483f47a990934ed0f50505faa8e1ff0847042a8a5c45077f698c2b4f6846b179a98ce16a4b2473e35c67ce268ac8184a54b9d99cd7296d15c245f2ca4745c8a4af0009882aa81e16397c472306315a0062ffa090cb56f179217f38040ad0c7fec06c47d7e3e19c357e5bed12f587be27927962e775838252ff504f89387eae2cb9697f67572437461c134a286e002239a2070902a03268d96c833bb21b9ab974a5d0e490b16c102a1845862df6cb92447d8f2d5a60ea14fe5eeb32cd24aebebddb093ef5e9044c70f986e4be40fd556432d44f312263045a26b75419f3a035c796409f3f5d556faeb512d1443227bea5363e827b00b0ef194c46d2b19ae0e6838d82e1b0eb5d65a71b85d34e104c5e102693629a5843e74d2092f6574c176e50f84b0a0bf30b51267905286b1d18927d0937618a38f59529b669a3e92f747f38b503bc618e352f67b6917a3d0de69aa539a462c942e118d80116ac9eca187b14d6608a554c9cec84a4ce84911282fd4280c0485444949f7d99388b6db0c6348287dead2141b78098cc92529ef0b6212c6e6375967ada78f59c4e244c6a6ebb387c296b4826c7d1ae36a4d2a8416586ed8794eece78de636bfcd6f9e91fc7699394f49dc5edc72c3edc566f6e2535e6ccb5ebcd985a49682cd9eec3113ddec37cb32884642b1411e2fc2a889ea679452a289346c90e766cf3aedc5ec475ed41e91ea29c098eadd8faa38191d6557fdc8a9283beabaa77495edddab7057dd4a0cc660ec1ec6b6d82591303c8049dc148624f644a90bc978b3176b517ee23951a9b25ffb5de9a3ecde8bda48fbf58c52d9bbd76d4bcdab5c5a50c6068e422505c1a691524a29ad144a144a29a5dacca44501a5748b0d0689b4a1b673d8c01149860914af1ad883da73729f7ad17d4a577182af72e474a43a3db2bf87faa83ef5953e9a3b5be0d5aba84e6f57ba4ad59e13d5e9bd4d17a9b4b73dd1d7a752afaa2b94fad41ebc7609460536f8f0840e19f822765f349f7dce5bed44b347fb4b0d342640230c24ccd02296a9741558ff647595a96151474e45a9cf1fd17b2d2a0663993e4a69cf89fdbd077591fd7d51fdfc07d33ebb14622c3b9c324e95071f4351f6d4bbc77db3077fe4b4fd28f5ec474e9d2ea2bf3fca9efacc1ed41ea73df84d17ddd31f51ed412d03d2ecd17428616ff336ed39493dbb51ea993efa117dea463706b5a6ec4252bbd7525a5cb249d4ad84edda29942812804bb5ca5a291492b5d6babdd65a5fb3a7ea084d69eb6b985a19207f3eefcbf142945a6bbd078740b873c021f465e8195bd40287c82a1026f100a39658041c120f213c03a11816ece18bc0a86311d0876660533172cc025f458cb1650c228b0cb6bc05e08b7e0661c6968f715f1d97a00fb5400db67c84102ecdb02712d22c6385495a3a2595b27e61938254920542e3820637a8ae6c61c5997a450d2ee98cae0733d6e4d0829a43adb5561dd81c542c11a8ba484d81820c18d134031bd460072796a05e10517be0806a731b4205f1441a323d5833704825692b488091c314293893431850c82eaeb85c9c01020b986146cd813e08a61fa99b1d90b698316306f26c6e674ebeb07cb9cac03c32e6e00d4d087da2a69312d9aefc81504a2965bdf9fbb561841d84a2505c00cdea05a00b019248427290fcf9a07c8970f856ecf0ae7d781da1c3bf051e067c012f177e2800f6c307d97148857cc18770f822120eff04e8f02c08393c94270c48c0a144913f5f130b4f1d3621e0d961910f3924f2a0c320ff6152618b017e806f874d2b3c75f83515e0d9e157a484c34732f2e723a2e5927cc11b397cfc42fe7c418a1c3611bd0bb596912f49867cd517409f7a9d31f017a3caecfa28a64c7d952f1813c9c817c021364731d58ab59672d9edb35b2dc3feee69d7823e57c7a5ae49a6a5dcdb674357dbfbec6aef664deceff1afc6f28573fce14f71ed7b3b2538f8114b37021438fb1797602a95c588c56adae52e6ec121705b6db16427e4ec387f566f5b8ae3ba221ccfa1ab38d9c3fd45d9df1457a577ca989a65bc21fb794d4a8fe178130e3d0395426892147243adec35529ccd53cde56c9e69e4eb862ad51b7942654185c6d96dfbc23d51a8ce091597d516ccc4d26283e50610ada51b1e709472925c9ccd1509c71a39d4786974bcec50a1a3cb0e343f93cd6832005086e70b1e647a8cf120ea25a6078b0f9ca05c7e9cf1e16c9e6640d802630a418b085846e06ca64004e16c9e68e4ebe69b10cee6f9245f37f7d10dd93cfb06c4470f10204080bc80dcb46ed8701b10203e3e5aea0201726f06044896d90a8290114408418810988f8f8f90880195a6bfc9ad9c314b6391af8a5bae1db18c9005c3583d527d646aed2cbb37cb62a09bde52aca97c6dd9cd0f80adadfa223457f4aac8d55b6a7596c5b8713b460d73b4e02f46c52f7ef922ab8f59764fafbecf3ebba1d68e5f84dd907d2a73406b47fd4dd9574f69ed98e9f845fe7cb41b8a5fa895765a0a63a6fa64d0da3bfad1fd4531bbe6c8f214d8bef1cb06a486fa4a757d9623d1ce6e65c36e5edd85eae805880f187f3a9773bd76762ecb2df9aacf9104cefe71bb56224bb2f80241807b491623803e707f5b48de7b513856ca6eefe918357beaeb925e8a51f255731481618cd5323beacfb5a18f1af88b513baa4645b5a40757789c00771b42618145191f5c808a170a7085c203344d6460c134051630b0580a8389ef2d9376564a2b9d954a2979a49411afe6953f334a29a994f272369093569b5ff313c279b3219c509b32c29b26ab66361a279a29291a0821a438986dc468a6d2362a3d234633319aa9b48d4acfa06a6cb322118295ceee55ede479e6b287e59bbb2648d7b8eebd37621a35b95811850ffb834f104a0c0a9ca298d00006d03cc1c36cf292e68a2536231ac8ac5146cd0d67ac985cae5043857ca0a905300853060c282618b7032ce056adbde280167b75ba5a750087bdbaaa4bb282647457acb1bb735d920e7eb9820a1d5a3069a82b7088612079e1e588e8851559d02ff78c0d60993744696ddc0d285dc08b316b54c181304e40c5ec54625b0e6cb16d6a5b6badad628b12363de5a1034f154d6c7b2d151cb0d65a6be9611535dcab04864958e67401cac53e09319d18a14622c91eb8bf8ba948c1929323a27898c1131531d062861717164a2a8049188f1164a0870bae1b82d98193193b90c247961d6851811d98000736c2e031c271411457ccac59139b37aab7d393a79e8c41d86403c71c5faf18c5e002671b424df18507434031850a5c504a0821a432d628bb9871ce99d93a29a5349b76d639699c73427875d8a69c137eca0c3999e79748e78c311ecacb28c116e99c93b26084c1e61ad8a54c770a241ec86092228d1cc6589a820725319ea600a20230589ac2081ec6809ac2e90632a26a193268c0e4400d17186f08260d1df6771cd2c08018692cb1031ce016b918bb18a59c52c6edf5028128683e79f5f888446e76fd5dd3734a2112ba604bf92412612182208fef16391a6f2bc7c2b3a8c50d053b5c601965193c378c0c782106195e908f686478216628398a9297d4a7f6b844b36060cfc33875f001dfc727e910e2834f332249b821d004726965308d724e55d376d919c9d8762dc7a739b9cc1c9880f69cb71b45b24408004a48d769bba9018ea773de9c118b04c9d79c954a092f9c3d70aad9828a405953546ed5d4da2a9b3ec218dbb2d67cec7a043cb6ba2b48fe84005fa99ccaf52134c15be60440583369a780ba014e6d0825451a3e8690e28c1460ec6e432829a2aeb665f4a9d4a65d0c8120dc5259769ba57eb56b7a48ee2f6e6fbb67543f9f42d5d333aaa74fa16a7b6f964d2b76862967187b6b96e1cc663c5b4afc7876566bfd3785a7d6ecf6fe8b99f6ae1e82f2f54de1d936d6db5cab151ba70e5bab556374b58838216a2bf5dda44024983e9e9eba4610a3f6b25bed65ba08ce9e59290f4f4f4f7018f18293524a338dab9e93ca182319b576a952d62e55c71438be4a69350c744f3d05b6e52c82ca1b1b3480e1c3e0cb426b0d1c2fa7f0ec18e3149e3dcf13a714f16903f7e9be60eb42afb843b463522960e9b63293598aabf976d92929678c12069b3adff13cf2e7b6648fdc1c2e93590d60f9ec35f37cc600d77f98eeda35a148481084792a9b64b2ce6d730432ae7602d011bee6ab6c4277f6fbabb1968f99eeb867e4991ab73c805fd76d60082505d37e9a3fd58b33a4e8210db659e3c504c6673e4872c1fefec396367cd0805b5b6489c2cb52144cecef4544e103de36848a62a9e5a262a622a5f44a09e79c73ce39e79494d26cd23969add44e29e5945266f2be6094c6ef7b5dae9b35b0f8e037ac5f26ad36bba9596bb5f225ef8f311827ca7fb5555fabdef5663745b53937eebe6ed00666760c7233b17bea267bfaddd45aebeb9ba7347f5987ba5258bbd93247a98b7694d65af5a5db96c6255f50c5adba24f05072f3aa99bfc9755be0c8d9dca8ad16a594bb9956df0843d549c9cd0be16d3229dc340a21b4787230c0f1355575f637e7cce8cdaebdf207de4a7180a5a473ceade3b0d8b009f8e5527265e008e19cab2cd35524105b21b1c6524b2b0dd4d635965a5a69a0b6ae5943abad960c5bd7d0325450c69a32ce9401633c53c6cc4119136a4d171c37395a5bb5569bdda036752e417e7435501d2c9ab05bb2cbe2a214a4c496ff766cf81d48246e8920c821104875a82ec378263b60e7248ba57494d9338613714b4c8a4848a94deb5210c5002865b48debd2f2a203c5a170bb255b077b71af1712a15be7352d15bb244d97e7aa150eee82b9588129dcb0eebee068c375e1c2850b17c8850b9c40383155c76d5a54266f78c117632c9960192424a41b9aa0816b820ce73406eb092b5450a4e0c6179dc4b277ec8fdbdd6119bb3bedba64815b74832203504f8800a7f9b89db556e044164e423441868b04504d54e00699a14698113c098162424c1c038a1928818118bc98c0024b21934503496819620359d81c3c80a4451f3a5cd26c7afabb04984d8f97b0c1a63c6c8051118c0ecfa64d6b36e5e2b4e95d9fafcd8383314a9c804c53139b5eea005320586bd3245ab029941248b82e165603aee86a0527581981034427040e2f749cb1c9a085458e0dba2daea441a3b855dc242db57ad84a80a30dac04ce96ae0d17746d58e1eac1b526cbb6869a6e0d3170769093a56bc30cbb83141b2758ad41828b46b5668bba0136d4746bba986bd4c099a00d1b562658b3858574c5f502098acc0b1acae03594e8aee83ac688d41536c4d8aea051c65e69b9726c08e5010f4499800607f07737f528de7e105949795c3b50603829fe04d20e537fb2e93fd134edddbfdea5bdab6f2c51460aae6042aae2a4e9abedb38bb9632e45027be8efdd6c96dd669aa76a29b019b5411e3c81644c27661ef9caced26480716c18e80e627d09910bbf68d3de3dbd8c3ff29c5c6de419d9cf6b8fba48fb165347927b11eb9cb6124be922ed5d113ed71dffeaa26df39cb05e5fc47ad5de2dd2eeddec292df0b32cfbcd32d6b58d9b167792ce98bd5eead888754d7b29dd2435fe91bdda357d548fb5778db6b3b41f79f746dab973fa086bef6a8f88c5fa1136d2479c3ed2ced2de2d621deb2a4ef7ea22acd266cabbe7aef69cb058f89c3eda8eb7eddebd11fe9177b551f7edddb7145665a20c65f664d7f20bac55f62ecb328c6941b3278b58e42b68674b4c536a4d1be4d956abcbaf3a1b1b9bd56ae5c47b77239823078e635d25e7385ec57b8e5b8949d8c91f79efa0777ca39cab728e737ed4ddf3debdc87b77a98b72367c2bab17b5be7afceac6b1ae62f31bafd2bd752b31a9d25558c7afb2baca4a0c77c72fea8e2f8f6daed255726ef32adebb5b896115cc717c231c57e9a39ce3c8398e1f79cf7118935d124f7b91ebaa17e9b46ef491cd6f9ca5bbb7eea55e74f3d58bd4c70c7a4ca27dbcbf70cb6faae7dc087f471fe91c3f47e7f847aeab72b2c77de730e6658fd39e13d7736ea4f31d9de7e8fc4875d7a63d4d3b715d7503b8b4a7dd4aec06d0d19e86bde39cbbeed2559c8a5cf77415a873e81ced11b9726238349e3d32277b9a2b7b5afcedbf6718cb89da8b5bd7dd888573d63b9cb37e74e369ddcdbb1be1b08ef38e759c1fad7e93d24e6ebeba016eb497ba95d50d80a359bfd15560dd9c1e91cdbda2ee30c624de7eebb2973a8cd9642f05e4053d4ef51f8c2a923d267adb2cc53ebf90a1e46c639927ba70810e9e32ed31a1b7b75a4ad534c6b2a2c9951a2222e882572f3902cf675789dcf544f2e7a2205ff0dbf69b67fe22929ddbf6cd6e5151db3fd8e6dab66ddb52598bb0bd7d9e955d13c8357bb61c047decef9c73cb36752d9522ca02a77e9ba4e007a1b8772b9b1cb77803db1cdf86e3baaee362ace3ce3a8c6d94734d208eebba23d6b9c318c775b7d968c7499b89595fddc8c6e62b7de4347fb4ba11be8d66d91cff88f5d5eaac17adcefad445f8361792b24b01c66e646f7e35e75cadceba951b87bf916d329c3dacbca93a4e952f9e3d54c70af0fcf743bfbbc10e4ac2a8e9a106259a6a30c40e492d08030653982fa830636fb03a2d68c286304a6394400aab0318558c1b5610b5c54c0d586ae982dbc2cb952bb6060f2805f50086085090051a25184115b11578e0863ddf72cdc3a0b2508305582e13e6e2ea722faf27a8a079844e912ff839e18c9762ec72fd305850107d3a3229a5b4a5c33329a57386286f1adbd4b822f88b4bccc7bcbe809171e67e3a1066308319dc6aadb5d65a2d7de40e63174e7f3344cd3066b395da0f8aa182c6d7b64f368535b395521d635652334869b3c43441676064bcbef818971809effa8179a0b4caa0517391b09274c5a5a02818547c8a5d2297c804d1ecbab4e76193d8658628a6e8c40c63b643910f0a92d0ce9066cf25f055efc248544c970be309e565cf29e1ebc99e8f51863d9fb62836388120661d679bbc719f6bab609324ffa26cd5b56c73b86995feb46df3450922e7375025e0b259cff1b2332a809be7bce626dc59df62d62eb3bd0e13e09bef759800cfdb6b9afeac703902d31b8ad175d80920eaefe611a05b4a8edbfbcb62e521eddc6db04d86d0475bb1ec6a65594aa46c36e3d8617baa57aced3677dc37eedd6137b45dcbdf0c43da59f66a18ae24d25c71336c284aa4ac0eb7262db4812f6c7889a4bfbb39dd1d4b24782eaf20d02449ebddeaac434fc926a5dedac36f96bd4dc45ad4f4070314a41d03dd10b0f138373404ac705e8e81f5ce1b92c27dbbd552585f5d062dcb2d7f43aac3a8bf21d576a1b9cdb0a36c2599d35f448236f90171d35f44da753b772119f12a07cd9774c1107713d5211c426f1f5065775f6596a6b79a9eb03a2b4b4969ebc5572fde3ea5691886b6738f7a6875d6ed3f2b48dbea08d06df5d097b6d552bac32ee7782a47a4cdda3547a42de5e639cfa199b09e73d90ddd3ca789eada6dd6f2eab06372f39c9c9c7d31d055f68658bf39eb25c002c505991d332e764d6e76d4526c0452dde0ec7532de2a8abfc16e88fbea36774d589f8da68756d71a2b472fb3dee5cff5c9b0216641c882c75c406d94bbc9ac1bfd459b3f6ce1d6c9707299953f2b4838ee23d2bc4b37c9defde6f84ede6e471638daa7e2eebac76fac5fad752931d0ddfa77e3f0f6563f40eeb8d29fdcab6f5cfea06c2e8a2a43f1b27795cd0b5bbbbd97a1fc91e190b5ba71564b47206e9ae48b9bdbdc856412ee5df6ecf190ea9e3debabcf72df70b657e5d50ae7eeaa5577782e7fac7faa73d7b2c7d24338f7549e6a75f89caf3cd5f16d6eb377e3ddedb79c7c739cdc3ac769f9e639593bce8da6a9f4d0cd3d8ebbea387b9c1e627d75789ce377d95bbd3bf7cd266fc7695d95bfb8556f1d276fbfb1d19f95bd1d6e57e5ef861eb2b9b7fa567a8875acfad665acca71af6e337e97ed6d32ebb01b6261fd5959e91828d7e5edb06312c28e31c429735b1d01b9777400efba430b1c0fe7032c7c96e385663774b3e373e40e33738bdcfe5a0f889b76f837e32ea5303563fc1253293d3f9d9de97ba189e92facdcec4ed8ae6961da94ac54a7dd085d707c2a03c2f12c098eacc304584a03b27fc39de9b8458b7c55fa6d76f84d38eeb5c7b93d0ece715c38872e1d97628775131c1db1e4fc669c692109378e2cc9903d395a96e1e814570bfa581d976aa10ea894ec98a972182a44230000000000d315002018100c09450271300e26b2aae90e14000e7796406c5099cc634990c4300a82206300208600630820840064942aa20301946a70aa9e4126bc9c963159da9b7e6a1782514880c1f32f774369bf5052c700b13e82cb399fa52af277b9f0d0be18b0876bd0d1747de2522fd6b50b1a412694da9fa8f606ba690ea60c289d1859eb9757a711b4702dc182bd32a8bf5bd29f58d408607197163c02efa19be661da82597e4de90c15f52dd204b4dccbb155ba44abc55220889d5250d440e75fd6a27da565ef7305e2b31502c2c19f7d8e10781857526cbfaac8d603e3898e6577258a12a014bf51ec1e95ca69561c00a6ddc5a608be431d301860c8ed0e9787ed2406c2825bd58d10a623630f58985a1801676ad2893ee0aa3da594a1d5b4b63cb02e826708cd6cc5497ba94dd1aca2a9dd5dc3dd3ebba452329d7322174473a262d4a6f510c7818802106581ca7f830da512e18d059778e5591a15aaba37ea057c195b8ab37314dfd9e7993adb4ab593cd51fa3c57d402a5b839d5d9a2492cc4404247883acc4420601484f3c3e32509d7f4d2c15b8db31be32fde779d62291ad12f7c08e24d5c84601ac8688684c6819f30fa0b6392a7619a5db3e8078d97710c0c3f517aaa1510eb405bf551329e288bc2ff9056fa88ca11fb7dab724506fdd8fe764cae53dbd5447e6898da82e3324e87191b76b3fc09ad65d42f271fab29f0b484c439502f24c003fc647a0bcd03911591df42bd02406a38b8924d50c05a00f190840ea00711de30518268952e84556ec3413eb6469482923289a966e568a7dc7aeeefdf159baad3f4b8d5e50bde370db8674310eb77e59379d9a708bb8de482ce0fe90e0eb41e9f366bb18a15b55730320d591aada7180f4708bae3668f6e0678307549de09f455d8c0b6cf12b9aef5a36b5c8e461425e7cbc1261836083b22a328e9ccd70fe11aa5da44b57014ebf535d503372bfe085f122aa26f168e462e048cae005da3d801e6ecdc0fb443e894dad8b204ee41b52f491bf611459364f61598c5ed6f56646f12232159dd37a3f51e093754c77ff125fb62f8a9a21c86073ed636a8306528554bca2a25db1278d603e83e8a7aec0f352028a6792710d15705a2807129821033548d75ec52cbbf0c7ccb466dd056e40db05d28c9066c9710da27c56ab21b26747771cb5fd6ec1d4afd5824a310322a27c516079ad0322ba7f33d30d6d017b8c47e0c635085a7904eef619e2333c063995a1f648d4af8bfed8937aadd61cfc061820cffc6434bb763c34a9469f74464d9b11014c0d82b88f6c3275a18af3fa2b053eccbc81f34f059cf05e5fa10286f6b4eb64fc53d442c55ac87a72a648b153e835fb66d132b61ad1adae757066402db534052afde3751c88106a10cadcb854350d5eb2d0a31d335feffcc303e46f324740124ff816f40f4aa2d945578ddda388c22a4bde6fe2942a09285cfac04877883264e0de93c8f505097a28be9c0ff28e7fc4ac6c8de4b23292e46474a3888090fa88f0a431e962ca8fc4829dff3d90755f0dba504acb0ff256e4643d7c8a090b63591f5d81e4bfd6b04b0041be29c93317113bbcfbb55815384933a3291b2f08297ed41297e88123c8732329ddbfc545915e56a7550fe97e658ced39b28d4099f935b00b0f5e443b20b0a2cc3f68fb1b4e3f42936b35be4836c0877ab9486a0f5081f347c59d72f0940ec17b1deb8107098a7392149972c8dacab1655b33e7f99a6adb094374b432c0d9298a45e82d9906443b380d9063ac1eb9a80363b5814adcbca7232f47d5cc19b98fc726f8ae8f807244683456a59b43994f645e2bd28e0f4ad7926517f646f4998c70d332c7a56300922c1a5e3797e0729fb9d1f8aff46695713a835c773c1451123e07a2a04b04f943c88c9ee0b687c1d6aeef18e71e4a3338505bf8109201099ad46bbf7c2837dda08a6ab02508bf0b3d5a1ae930a8ef2c208a23340ed3ad630e46967a73cb17c45e148f5c1c9a9cf4065f9ec912ac3f9ee6d11830f61eef026e2365957a02f820415dafe6ae30738c188b31204a109a36d95b03b2215fab322183f36f5934ba754137d1b0ef3c4afa1758f90fde37819d434b84c74011bf0d6f5883406a5742946e507d3c0e5bf204f09860bd9d2f2ce7326f982be1383b0d8438443745c9eaf514c7ac95db1d521151327b6c8d50063ba8a5882fc7372186824a45a886b788000e2db420d801a31c8fa1d76bdf45f84ed82478a347920e89bf93e1e4fd84ae03e90aa43c32a7c4da37295fdb19269b69b50c0fd179dfbb1f403f33e297b3bb1df44318c470ba591bb154592d76eef54e5b91e54aca063f1505188fefac26c045bd6d53a82b32c5d48e6995950a0a5161dc71ae261c9772400637b1f6bd1d3a8c2c58516d98523e787c62e072c7f421e206552147ba2a8286f120fc768f8504ddd869b109e1929ce61a2c7315011398dc6b07cf5f9f80647806d0d56fc90fcbd6967940dee1e67a841c703847d26dfc27babc58244805092230346d4b6c81c9086625d49577f793a64fda86db5fabc144d5e47de5513b60eeccdee0159c5786939a7224193acaebdcdb915b3ee50f2c71720b3158c7fd940343018f99931b24577086451b9856323104308868701b0ea87945cba099cb6e789a1fe861cb7a2336cef279581d2e13c1b889714b908074e2028857e04be0ba074e77f7edfc0d4022476e3c2034199af049d206ea9da807521d3741495685d4d77ce4dea8ce1854cfa64592bbce8da72af921d075f4fae73a15beb57742f29773af7c84e298ce4496ff7ad524ad47b96d3d46057b4db5d3de2bdcfae76123ad6456fe4feff92161ec38e7d4374d572448732fdc518acc302e148ce9b98951f47d092036369e59f3264b60ee3423cdba8bd1c846425ba95cbeea80b7e3e296b0badd524bb8dc4152fc7fa8e9f95434e31c54367177a2960930e2259e490e9b3b03298787912c0303d3b43585bd119e2686ab6899081d3b6bfcf5bd4d23a10d760879f7d574848956e50e411c831b2e5e698828b9b6171471dbbcc1f9f03ab5c84d9d5f7760b13797d82e5755b71f7017e7d60118f914b1926e8ef11c59286b602ac20fadb597cd132fc4c40c4594d3b7acc7e809402db5066463e7e5d48c3d8bc678e8519807a0a8bce6ad127a1e24d452d4ca096b373390265b2954b8ccc8d4b974fc361668f8b47580d2ea00c879dc83e700c917592d8b91167bb59981a29817f69b9ee6a806ff97ca8c821180d09468c0106221275b05042773577f7eb8fef0f3fbf7d489b68645de51e027a1a9b1c9ccf2616f613e654ecc0082bc844467a52a42f2c00c1ca99479ca6fc297dc4a235140cf204ed843676cac14cc7e770000d6fdc65404f03142fc37d715e945950341ad1c755fbbbff3702e311cb9450f23aef20dcedde5e52e6f02e4bd2fbe7fb0c6882ae633e0626e0f7838fee265aba10dae4817d1eb425c0d77b1a57846c2cb6c15bc93833a7cc250ee94debe58db9a8c0a6d83a014bac2555eef64801b548eb46f7ba60d2993688d86fa79cabecaa0721be4eb4f521df625cf7fc864c188afe2baafcd7e99e3e17730515795485e99253cb35f7b10075c878086ea5b58931bc33e6260535fcf1cea904697353ee7a66969854768685b721c4c67e97f950891cf6631f34016c8004ffa45ff2561350747fec1604d219e3b26e26de4a9882b55494af9706cad4a88f73e1f9574cfcb85db1e183137ccafa14d378701222c9be12f621639bc9480cdea0707a13208a91ac6b208c9d1f20981a3163c3da617b87db3a526af0625acd3331a73e0960c6110ac8fa81d80baa82b6b5480ca1353523f79f5f314ea0a8334e2814371860230a1ed33d3a98efb10b4a7211e67aa1c5b9908ba1ca54c57c877c42d66992ca5e601bb3ad8c34c9d8305b6554552cf3de2f6f15a1ade069c88f83355cf4a839a04d83c11e395888b6275f71518ff1a0679768233cf483ddb2bb3d3e326e6166defd884c6c16b208ed2eeb989469529213ce01b111826905af023fb7fd6146df1277b309dc28e102203045ce2080d9cd81677fe30b1731997effb4fe988f0b34f96c8920c117e18485cd32eaaa74c1c1f84dbeea2cf5096d66ab9cfde0e65cb24c82554e9165476e9d4b935570cb0adf7fe7c15951bc03c26e12295d8da204220424782647902575b6560e172535669f034f5664e8d9389e9d93f58bfeb290c4b5d39084f924bdb2456a75b7dd6a568be9531f0a69d372011842c20567a6f1ac496c63ae4f9b48bb1d285a412afc67284a350d5bfe18eb049f43268b998e6980155e8f231c49a5bd4e8d4e8aea4b2ea83d252c156b08c973bffc8b4498bc9ab33bd5b0fdd317e160b031bc5753b6950367b082867da2d11a5d34c5355dd642993c54b5feacc3b1448cc72f0ad4df1159ea12335115e7bafb71bb0d76af44c9bedc2070fcbbfb36fcb88ab86684929e1053925736ad2e88011c731b293e0fa73c202fab60adec36dae4cbf0252d6c46ea76cbf8d2f59122ba5c01057595955ae2d9acc87ab821c423460fb5be1034fe9aeec9561adc80d95387d82b0e924c6ac4af58f31a20541537ea762b2fab57422b684ca95204188d823d2f7c9c900e0ea445863343847e43520938bab887d81538e8eb936bc4aae9b8a9be5f18cec1ad2cf1a767b2030d5f1d85bdcfce8bb6d21806c9e7f0b9f77b86be6f633d91ae2ece2e826ac4c4e31b875e25e476b2ea89894b6805d349bb99d41c1108cc84af010bea728dc5768a9dc8a3d8f977299e0f76b3ee3bb3d4bd727c569fbd855e410b93475dab51d61e42a40753011e3190d58b53e4f93b6d64fc384e2ecd106f16fc0afcdfdd70849d953a037b60e9eafaed1768446fb468287d11f4bb42601f8e3e41841ac58826c6189acda3fd0a70c9d7792f368791418c8e39e4e26eefadf0ed092784ceef4799be02370b075032c3179aa66558db0a2397d5c8a87b50ac49661d149e32ebeda5b0edc419afa4c63ba19e6f71eb3a1c2d2b41d5a4e357eca4c3974bf00fd300ff66722210ebaa53fc0bbb239ebc3b778e603c36337ef5deba04ca0c9d388a917907ef7539abd1cc32f9cc4f364449ba01e3b7c11dec29590ead6254c078bc35cc368b88a9074775575c4b084718b99af999e57599e1c1f619a740200aa6caadff73508544da8729054cda08f6c93ec723ef55e7a737fc8d35c10d6d8ba7930755c01014d1a930faf638b151c41a4d93a982645264c9c6d1a5dbe08b4d937bb900e7acb4ed2560731fe6bda0f946cadcbf4708fc2e0a2b097b0f259ccbe97460416baf5dae28bce08d04050bfa6cf886cd577e3604a42af3c3507587519e0ba5dc98eae3c1e5e95925a5de75320d65ddf7163de8d203d00d12cb197c4e1093638564893641620fb3bffdefc88119cb59b7a4790a5cfaa07e76f1d71716da62bd8c250e082ea9470df3d462259532917fc00b5fa8d9f9eefc917de2483cdf2fb5f6a34c0fcc3bd0030b7fb0661486fb452f6c508376b55ea3c0512aabe3cd9e37676dad4f1119aa4cb12574eaddb5ab31a4f0caa7bb268493cbdaa9e4ccca2e1082565e55b6a3d7cb33282a6a14e64b95a7c4272c5a6e43b1213e360e6ed236d447082d97eafa04a58645371a6990a78a2fda40f12ad972e1e5d6aa9f0cd3498b93096ac25c89594dd6f262e006f185e3a0e811ce53433dc0cdfb887b3856044e7f3e105b0f046f564fb1c9cd8820c381dc287e714e0cf0e14289fa7db38944ac40bb9a15b285aff73643e5a83edceb5cbeda46b44503638bd7fdced406e75a00f1012f6742c4395dbb78d0b62073c117525475de6c41a9979a659ff6062790c6cc20101e43f59026320631cc26855fdd56bcfb43692f9e85e9390b9e10d720b74c523304a049f6d5a25595cbf820c52a41c5b683af0081a627181712e3597b6d002a123d6a054bfa532eaddaa1f9c2fa43297d1db09da529ba77277c7fec848d095121bcc1fe0ea2e4d71b7804deeb1194f65b3a3defbdeb6ba0e8d7be27454f985105883d9f19c009c25ed2f558c8552055d4fc4e7beb8673e5feb4354df06238859f31b5cd5ca2c1985e9d7dfd6ea5e4cc78b32783196a30052813c453d402707c9dd481d22d66a6c454cd65959d106b3961c1fc04838219b66f3a9c0d745895f849a2d68e8bdd2768ab90099289e4ff875b174dd9d0aab3844220aed91f4ce8c8165a5cdf54cdd40ff0b4b683708b18ea4264ec849a159cf2500a62320c49be9d0908afc423d8ccf40f6f16927c7c9e029ca1e27ca8963c20a8f15eb27cdc542102bde92dc18bcab89fd3cd6689f698741cb659681d73beef9a44d50cabcd6ebdb92a88f164b6352d93b05d236848089ee5f1a07598f33279907e2c1b9fc55bb73df30945321dad79a141916b23296d07b9d2170dddccf9fa146534333a9985739dd620fb3bb3c88c2822f7bf4133e9fcb6cb960605fcc06cde5e5b94186d045bdccd248845f7298358e49832b4a3ce2c0238747aca0e883ae39af6f4963ab029ef3d78814e6dff8220f5ba41be765470fecd3209ba44955d00eccd9fb924ac175967c264c6a445f8fc5ef76651d571fc7e4ff731f0a564b408835144d87b883d7aa3f24750404c06d78c9f065b57a7033331883a26fa1d3de69aa65f453f6f732b623b23a21d6cf1fd9a35ed77b144ccc2cb0ae39a7b744f8eb4856303818e5c82d2018bf22a3cf078e359753930dea267f48e4e9d3db1d5002706c724e494e462ad471f7eccc734b7dd02a384c4f402b662ff33c35be93c06889897d57657e7977dfce32a0f6d02ce3adc77b34af9f22263ab404f88d1d9573e6a1f1b1607927e81fd79a4d1e8ddbf3a2c8e1a92f8141a22c1a22554bc88ba5be93af5a54ab608d1fd8cf2ef04404b5f8aff50c82779c3e0e68150535ce4e04bd97aa28541bee0e214e14a204708356bc113cda25fee0b1e50b9c00645de6cf6ae8c113225a3274a6303b34aa7408b8e5c5e35c5cc80f10f24cdaa6c87f18b4774405a24e301b712408287d9e48a72daa726d863790fb46dc3543c8b7cb7a3a1394b70afe84867141a6594d2d36040908abb65d6eb539929de81e27217e49450411fba87c8a175df0beb3f81c64559f45532bae17626adfa837056872bc8f51628726817274d0c17b6703aa0c559397133aed1a7b12bb1d6029112b79cf5a26529069098aef22f0331c814159d4bae0cc91db603c5bb0e3481b479f951c67a06f075937d19a4e2d4cfc7fc2e604f59c383a88f1775fbae93a4c22c529a4581a0ceec498a782cd6bc6991f4c08133f7b14a866a7b13861ca448d738ab9a8e60a2015dbc2db192097bb1d87df887f85cfc2d462cb6e1c54ca4090374bb12e91d331c83da119cc96c8dabdd859546c3c69be55a6dd44924ddc6941e219f2880595749d9d70f9ead23a6838d09a5c48cfb800aefa1d9978105d344d13eab180a0e578018e103145a6445950fa86728cdc7c59ad19a7030f34b25f27f7fa0651f8b667db49140dc1596c6fb969a6fd2b99d1ca04e3b380172b77aa7262d428127937d0d8dee9f40ca82450a11a197bf7c4b5db0b1bac56f7f28d629cde4694893d7ae88b29a01bd2a2944a8afd05fbc0adcd3c32e58047433ac6449c48f360bd922d28f180b8c88629b92a95bb77501769136a98b3f2a12eeee8e53804cd74351a6b2264c2444a240b25c9330580d820ef0cbd726f6e1749f6faf279e37d1663976312a4b485f1e18abe117d2ecdf162a1d116b18975bc27b6e806df24cbc0618574485beab41b8dabcfa9e0530fe93d20147ea837038e13072a496be7c23ef60a074574888d979b6a690a4cdd5b0f67921f20177ad9b782d86ac95c2b4acd297fd34d84e81be44750f0503ae406e6d909e9d0f46a9009484942124af377600b4a3037f68948f14240406a38f501aa1257f4936d3a0736288938b80a2110c6a2ad1ffe29445959a62ccfa95f719235e4cd94c83a863f75098cfee97cf6c7d5b0dad3759b35db5ed8128cd0322919e2fab0b7d08ecc302708b99df12232d1c824344fe3670a0c21534f89e0bdd2f7740dc2ec794de2728657cf141e0456cbe33026be29b8e544f6a4e655bc6506cf9e9540d80a3b92bfc78729e5523c8f06d4a942d72fb1bf7cd3451a01c0904e7980ee45956d23cfae32144941d336cd397e37d509e6124216742ec8548210b9a6c9cece18671639c6d704f63bb6565f7690f7678721a690af9fc2987d0548595fe30dcecabf6192d83c78f8189260caee6bb691d9e5e1c388a1a4089baf06861450ecbcfb02efdf7cbf0a8ceca30eb394388959f2f2375cf46470a9dd6671e306563d697e89c7b427bfa7897273292062565a4b0a3f85fe76a015cf2ed6e4e33417aec98284e41dd54f517c69324cd5892b9cf478a8440b876e37d2b6bdf2d6833e91cc56071aff6dd82038f0a88bc643f7a64bc074518f8e200719813b7a096f4e435f632ac612c6657a4ebe74d23aadc5b5134dd160a474a1a0034352b1daedddd909917d152ceb277cfa11a74973f15c2803abc6d71fe45cf1a879adc8824246f99a8c3e00db7b29c5e1b3058b7878fa9db805fe1da0f2aab3e50f687b5aac7023f69c779d41110cdfb54beaad3db9b7e139be161eb7b381debd203c96d3b71dd5820e229067e67bc44cee6f5416fe2043f70a46a9ce7a88a4a41b037f501374cb9968144aa8606ab69096c65d3b6a6d6d9f9bc845e2d09011a0961b64011f35a737b46b4679b68599013b56e54b5e277eaa384b819ffdebce902a64b8d002c094124e91e1f17d4b2bc8c20d23510b6c9817e45ba782a94284683fd4dc18e474dbb636981adead3251354106dfa25c1ce77da0c0130ad39f1977d592629e1125846d6a56336a105d8fb58d1ceb641ae013772cd3ecaf0b48acbc4bd3ff9b70565709f5989960397a587c4812e8cee5d01e27ece34f5b16bc86c37312ef8cd7307aef4e6b6b66ba1fed527dfd5ae2d9c77f3ae61c6378422f422e9ccc85541904d391dc4fb7a0153baf24700fb94965adf575e1596c694a3d3a381c6d2db755f630c490017e9a4ddbc505b0bae695a6d42dd2a492782cb59c5d5bf5922af9b5f3202120c53b3eea8c9f2021b0dcc3c943e1ddd0e843a7d9a8ee0ced8dc87a668f6deebfa164db991e1b032fb462585933b6751032b651960d0f2d026eaf07565ffa40cb8b25ed15fe84544923eb111e29d2efaffdbabfee193a5dcbcc5c30def70bd7f585d37cadf6f2a8897b0a5ace33f8302b64b284f547a59ea976592065a23280b35f2780231becc3198b035913e200e6e78f38a3ef9ae646877463bb09c326537937639be1320652dbbc72cf2b645055171bc9601cbdcbaa70eadbf64c54fde5f78c42f17d03351f529b2f3cc55b1bb9466889013c6074210694ba8c3944ddfa8722929e668ab163625f7cf03d3a61078fa779879d71879d9f35c1829c26e55e83adf9cb255ff4bb0ff7fa237899ddbeb2b71fdf93fe59de5f6418793464477730cf1faeff90781cd1ae17df7ed625dc71e3b4256547fd8586a819b0f26dc7f49adbe7f09fd7260259d5845b50c43071a2b2346410b054df91ca0b1ef371adba99a9d556be71a95ad49980d26185a8c6ae7808a999a34bb9375b423557648d6ec92d1a68e8aeb3152257fd3573477ef62471562ccc4474707c056d612e2f1863fffd44a0685990876b1a7c1a6c68fe27a1127102413b432700ea794187c6f042718ff6f6c0d5a532514fb5c83aeae80856d64f0d5c169e4119e56cff1e2f878855bf37df1a5fc22cc0c6c98a933efb111054a7538bc1c32976eaff63040876a34e60eb67e442f4c126442abdbc764ca1673e9a8a45a766e22c30224a2c41bc9a86b10d6d741837914eadb275ada129bf185b0b9c46eb35afaa91f133d5428d1fc0d251ea7e8e1e9db869301e22078810d9a459a8974cb822724f588298a4b0b25c69fd6888c80b94df1086714c726af358b756f1fc6360b469571aea4556fd16e8a2760a2507d92a05d2eb7f6e35e9ff1ded52175d7b163de694a05307c59ab70071fc791c81f0b8ff979e164e3d0e1bd3fae6d44677e4024273174119f23bc87abf1189d5a34ce780d754415e295301f711fdb51c80dac45cbb17f40095220a97a113c50aea2e979e044aa17161b2ae9289f408cea251fc0a36f84174c70dac9d7d4aadc4c3ed722e7c2ab3362e7e02751cc9f7145af7821fa032d5b3aba9e7fc3fa4e358501eda77ef6138ae9ff0e0d1cf1bfa184a350a85b5fe1e75293357819acdd3afd62137e1ff59077f0301d32eb45ce0f98d46a4d46727b091a64f9a798093b97b92eab9c249b00ad3674e842c58fa9329f1378297a8a75f1f41961ba3d9ebaa2ac65354d92af723edb1140eeb2b557117f04bff12fee3807f9545998edb4cd24ae37ffddda6a15eb750be48630bf0147a7c0f44ed909d96bddf04658fe009600344284124d14327f0724732cd16d26d9c4271653faf0646318bd4c9198fd8a7014341a0a8bb02aca93268aac4b99da82c7e5587e2cd51c45214cb7229cf9b43ebd20c72bfb14f73978d1a42c23e4a19434d1052cfadd44c3b45122ae519d2023d8bc19f61fed2f1cae851b94ee122aed9c09884b283524967ef9e0f80fda203827de0562e198c2e1f0936a1d0dc14c53e4a64e2de08e5cd384e32caf568e4954579c32a2fc83b6aad4adaa702b0e8d64ff2ba18973ee6d49a35f3c805393b71a1c8a3429d2ffdb623b166801409c82f00374482053462dd309db084a50b73f5ee42de462c1d4faa12d3fac9ea167dcc4b7803e2315bcca7a75154f5fe1802291b5eeb94647c580b297d485e8cf17d47b74dc55632fe149cc098dafa4a8c577c0d713db689610726ef5bb95f3e4ab1efff63516dd143f51ae6766f91c826df31f9a80e75b5ee97bea1e04d80677ea5ab55e08fe736d5854746185d1adc0ad50f3867058a3439d6d38618d85018251831d37007818056263c73993f21dad787ed20e203e3673dff800071902fdcc2089529557c86a5b842e7ef5c7f84022a1149122a8e17a059c551802f07cc64bfb93b69b116c247f26b3e6bc5107af75af33383b87313d3ebe6753b6d0682f12a4ad0b79b4e23a8760a8bf7d03fb4cc6e65c954fd578471313115ce7e2d22898892ea363828f9f1a5980509bc78ce5d32e048a0d86533c881edb05847e2e922fb12283e738f69c17d79229f848e923062247ab94b8d17ed6012d258695917cbcdd64a1913aace0b10a61c6c947798f42ec81d84f2860390d48ae9bcc269998f2c79e66ec5bea917a849283c3c705851b90043dc9c784b8e5762c99b03c4696258c194991e64ea43ae20dc996f1f86f9db8e670e56054d126a679c15eb911fc048700299b6ef609c9719e946381850d8129079a058ceb6252aa2322b2dc797415e196663f7cd40c5edb42583769dd8a547a4a6cf0a1d7f169796506bb6cc9eb1e6ab4badeb91d4335e6a9f680cdc4060b9e2fbdb87fe950ccb19928ca1742f99aacbb63c2c0a64d7698f654745d34155ccfa44c18c3a841390b6d38c97c0c2cd0510582ecc6bc3ad17b26ca34a2fb215b1a6e112ed79508be134e09ecf97a84ea5fd9eea2c610e4aa977da1cd9011207630af99bc0ebfb13da475e51a8b92bd80ca80017c1adb0b53d66d009cf97e8598139cc072c423441a75501f6f45ad4c1afdc6b7c18ede322a5adecca571632a7ca23e259d0b3aa7f207305f23a17e45d4a20deb6e684c9dde60ed83b6e121ce158c704ac6ee642644befb05667a4b5fc4c01dd37e4d476adfb6ebaa138e93648f17f90c963a989aae13e793d29e1488050d44ad528692c5ef8444193c4687d984ea17efedf49cd452264ff6916c5c33a5306323adc1f4859fc30df9c4a09dff6eae7240a7ed4d6885fcb63ee56e975eb90915dbe883501308aa36b0eaefed2da7d5f5e7f10e37c544df6f643a7611c9947346b59d71cc60c46a1d5d6c41d450dbb410cbf79f17e202c43788d89b275d838352962450fad12b2348404315c347c994688b95544b9f85be0cb3ebea3d961b99681a5b9bd13c329aa23d0a6d2ca80551323394e1fc76ef27787d0eb4b89606161cdb68e5f538a41c7528bd9b525624db2efa1dd18be7f14e7e858cab45b2306e726789d1776b30347fe90d023495b923b74d674739702c9b9901cbcb9a4e7d3fbde930d6ef6c1775597df630baaf859171dc51d1d93fad28e8a452ff234801a9ed7d2cc2b1684453bb99527263b9a8c11d654ebaa5c4d098099e49332a29dfb6113293932dc50d822bd19af295c1fa8265262dfa0a5973e750e33ecd037f8c76538ad373ad9f63b9e40e4dff4b85e67f79660e27ed1e45d10d710ff4b92e0e0a887bc72c915e5fdbe6a5f2648ac95f163a8edd061bad1be3234b27308c611b91043d99acb0f64ef6f6ad04d56bbc4e779248775e1bc128fe0118e33d3d6a44c0efa759e7d857a77b3436f6f03d455a29286c20e6d9162cab9a5a0d24d1c31fc7db8964be9f4fb6d7435a0f2d165ba0f50010af81e0527a6170e738ac0d3f3c1ffc77b2cd750627a768cd1200c86597fc8d7671a41f1fe151ea7437534874612c25cbe5a29bf7192c5670b807de735659a91990d10938e0cb80e9a3ec6d5f53401f3670b9f3c2cc31ed0e6cc1311a0eea6989f92c9e8ce89ae97073a0e84194827076ed2e7e2e540485e5c5cd3c68ba16aab9b28346116a97cf101c815257cb0012250658c798a256a8e8144d3eb0da89423721cb0a25132217968207a076f8e0d40dc0607358a35963e6e74ca502187012cd8405efe5070064d9f403f79cf56f66a0c0225a7c7a1fa97d0ef6563bb6a42564e43539fc60006a717829a05547a3b5af02c43cfbd40f196ccd5c9e4f037042c46439e805dbeffaa7ad0b5d3018f2f3dc72ec8521e1c5fb974b90503d5c76b1a80404695f710028f69e1f87d34deda7b7b28058a70ad53a5f74eb429c44a52f046500f5fc9eba780256c6961d14835dfaab413b21fde04a2800dfe89096bbc878648bb6880680d9047479687fceb201b1d57c0fba429aa967c96bef8e6f0ad7136670656078e3ee3fbb4d65cb0af1d4f2a0a1b85fe8e0862267f748390d8d45eb1d16e0fa3631fbe0933de0894d3c9697e5ada267cb8c050bf80906ee0cf5fafe4517cb801a1951b17088ec2512afa0f4be5ace3ae61c343c627f78e354bb97cd0629d1a6e924571008a63440fad7d5f69cb6378a2f3f456d2ec4f617f5e8ba8504dd783b650c988e5b49585048602970a1b5642a00089405d71ca4f7b40f617bbf36185784fe04ec4d9b5a83ec0fc96e1c76868e8ffae0e7532d6b7402e5864dc77eadbe9275e403bbea8ada205f2550c50880e9b0342851c5c53007b7d8520847fa489c60b3541aa4c35a7856af6496392486cb14bf83a271a3b693f742bf102c2635b03c601cd23efa33646f5fd9b46fa5e44204727ccbb221934309c111ff8758719693b6ba2e89d12a3ab6dbc7b243f4bf6c8cbbb5d257d00e40f3e1fa92a9b8e9e46493256de71d1c9163922ab0b2a7d0a9f5cc9a8ae335d000918099ca029229a66de2aa651815c8428bfa1b94782f799057549f606fb8ad3d58cf1a37065da603094eea4653bc593ba402fbd7b20a3366c246981378dd291aa7b44442e78bfc2084ffdf38ee82b8d612c7f020babeac14ce5e3ba1fb707dc997652417564188382db5c25be645a6d41b16e817488c1a5120fd866885048788d075226599c81c1df626c59c5add1e5fc1962baf239237f6430c0fcbf97ca33111ab99144d798e18e664d5f8e852eb48d85396620b0f19cbfd12e0d867aec51d53cb01a81382e184fe12b8e90f7ba568dde8721f7abfc30ff322cf097b48b8eb1ebfd004fa5aa9600184ba35118a8cad1620626be68c7bb9592970a2785759d6e86b80a0d39ba9ae6823d4c810a286bc913df00452d023c6e0eb2f04453fd514b413683505c662c005bfdd9be192be185add7af4524807145492000d2ba8426b0bfd8f5de09834e0ea1f36cc59e79388f0c9b695fc273858ae101d3bfd2464bb53854f9a82418dc01f374dbdfbc5655b938c3da8dd6a0a0ef05a0bdf496a7d24a919181709c5ea58313e4f5aa1c1cdfcc38d146b4d784bc92e95c47a132b7023b613795646873fdbce42803e05d0cd01a9456a3144b18020e76e185d232c6edac45aa67b135c050526101ef051fbfca73b21c0a02cc5a9516dd0e04ea10ba00e3fa2e674c251450122b9f63a24cd1f262344f9350647719dd8fa4c24eccbaef7c4768a043cae22a5bf7a659198f14ad7cb7533818cac48bb6fab60a25723aae79572e14d3ce7bb56f96c38ccd7074a86c5743ee6ce79036e66c7dcf7e119d5cb789e85bb10dcab84b35900e4d40d66c1c34c4f2f821968b3923fd314bdeb55be17aca04245380daf4d6624866311133b40e4993951ab5630f9940e8ae0502f8d62678ed5278773c3faf6fdf7721aa02af6bd7a7f8cf2f71adf29087aa8b6b77715765826b9f7cfa73934d1c16c50e8f9101de132a073a7b3fdb97ba216314bef4a53bb8dd17bb72050d5e4a7951a176a57d6a9b4eb16bf4aa854210b7496ab3a461c29ca6bed739e81f59e071b53447c40be7ac99bb6d166de1f99e073f392a193bdaa7eba1ef3f7f5bbe1c9e92e063a8a816e8ea3f3ff2e70ae67c4dedde7c332d6b01fa00b3e8c7b168791d82b8be9a15d6118cee92786ac1743914515100db77d168a298b3034ea20e4a4d5d024a470048598a83695829585f56d995eaf536ee0dd0089fb9c28184563dc83f1436837b83d7cc3729c3e9e10f26fab415d501835712bc23eda7a28c49a63a5d07d202aa2f6fdb1da32dc38bd24110eb63c8102e11d5ed1280b7ec3f741b7da61e7eddd1b044a973750132aee7b96c2d839e0d3d41d506bc4e2cac0d001170cfab6e2aa593b166b19b9140a4f472f02a51d0b38e31165294dc7a274ea029eec584048b2332d551be042a60c59cc91149f59b46162de580a2dd2e2e7d225f60a215f7b96b89968390c1496c075d1015fd598842b8612607d07212a77294fcb0a3ff122eff95e06a5a208bfd4a6a91622d34518b43f5c869335d82009033422fa0383c9a47c21a2f00a80dd96aa3545e1928fc0e47935e2305da3e61e48ca4d281fbb197c0b91c43cc1696ad70d51197847dad972cf9238f10ae36552ea649d176f99f987554654a6b24eeb74515a2e909a2718594f4a3b47b385aadc00ea8c6ea49e2c2f67233f66e14014592f92cb2851836052b393df3df7690ff668a34144b2fe60917fd8ac04d177c3669d12cff0be796aec9e94ccac0f0e024803def499f42c8edcca491055984141164ad60c442a9e344b62ed9d559095745ba141602fad45d1b0562d185e6bb8421ea34106620010d574da18ccfae55d55f6a596afff2b474b8ad57793be1d888fe579e4eb6047f7356e68ee30c442403bba346bcb94d66620b8d24ab0382e818fc5de51b503c6f8485959aef311a511dccb861f546a9f074dc0ed2028b02a44805efd094051c4a1e01d221b7bbac524a60cac58667888073bf6c4803e037cacbd4736bb1759db6fe3ba8d48298e8adee76750e04a2a4cc2c3bb2292f3122468a9b4774e4e91d088fb524ebfd01d29e094cde2009fc7ad0ea755dc1048f53dedb49bcb3d8a128f61c8f6ee56d8ec2355bc5af5e0672a9a0f41349dc3a8abea87c0d9deb8693180736adcdd35e07e75c98941db24d6fcf50bfa9a76055313fd48a8b6f5e2ff3312b9c492c18ce7a02f6cae990e7705512441fd1ce32c511127be3b5c6041b76d3bdb196affeb68a85dd6ae813cfc7ffdd8488ca98227914a2bfd21edd4e80e091bc8c3258540f3a9055edc0321476f940732778d9108ed69971c0c79a603cc46a5530199d638aacf3edd41c8d3714e6d4489f31aaabf52fa0a0d64743e87ee2e5d8f9b1b56365dda61731c39f528f477c2d232d62c25ea7bca7eff94333e9cc22a9c127b9d5e46fda01fe421fe41d7fa742c5a847bd351e9681830a2cfc31ef7dfda2d8583bfa70189164754ed51e284893471c2c27277a98016d5a330930fa3247ceddd90474f273a09f331eb38d09bd3152b1c44f5fba780c90be1682f4315836691597fb9f43e8b3b35240b80bee139034eaf42672b3b19014954e1f04ce9552d915dff0a9492a37f6f5337d4df34fe112d808bdf25ebc349c6f39ecc1cb74af5d09c193d11b3c798f07254258fa60231e6994b609c0a8688d707ed82bbc1f2c86ccfe380785bab3864d9de6eecb703f2119491ac75ece3b445323b0eb24c81b6890d5212664a8eebf31639d2250ba96994685805762541f83e458e8f92d603c08ca8c17ed410f4807b06a68ca6243052dba3d3e6a6e4d7ac1160f9e83a6c4a9653e718fd8a0d5d0200db4f5695ed3ce9fe5d74f104b509e917a9f537191e36ed33b7b42100bf2eea794d8289967b95e7e37d79be102ad82ae718160f1afa88c76ae2b6a617f7e93bc7db88e16793d2ae0b400860a8fc6eda1952c21931799da4d7475828ac4e29daef9c261d69c4e04aecb4340959db8705445ec0069dec6130f1d18a097102dab338325abc82800d14818e9924d351f44f95773d04f1646dbffc889a88bcd28467d6eff739443b8823d9a4cfee693a96fdc15da5a0c17566ff6dd7400ef44e48088d52ffca79ce68e2e23d73458f65bf9467956c74bd504377b207b4e605bbda5c8e6750d7b5d7dff79339e0895e314a3b7bf063d8aed54dbac394b6efdaf477768559e5241082529a7a1208e2fd5ba55d85e992ce90d1c5e44a2b47a97ee4887b4ca778d408fe721d0f9354bd53cf17c969f398a76e721a2f92e0b68e4fb10891dcf060102b484532149295345eefbd4759c0db7d8cf3dc88c6fc34e8193e93abba1622f89840f2d21f72ba4ca447e0b6f73222a32e56b5193ae4f2d2771120d0228072d7218ce77ee8197fc01397ed084346eac458ede2413e411fbc95b91e78da772091de8592d8b4356d43e81d1aa8651e463c219a120e154abab04ae4568b463f104b57296a9bfb39100d49c28cf1c719806fe0d79ee3d5e32619b9fb41c510fe83ec729f218884e7bd181eea68f415fa688c4bb37316878ac5c4c0d666a5d8135a3d6c827a00e2b9433325ceb6d7c0696d8feb1c950fb150ed54a0dda88a563a84df2b5e37ea0b678cab460c84411f1538d0dd65a3ec5de4cf0c3ca8b487da1445c6e1a02b97982a719828c486b34c59ec0c16f140e44813e2c38e1da7f54dcc7c878281ab1492167021ffe40a9b5c56715f051bf525becc89bd97e543a5120f916aed56d69019d8c1d703d1a19ae38f1791c4da632f9d0632d000ebae327488bf46391d0abc3c1e98131e95823c083dae1a30860761062cd2d3cb9c60463cbcb67953f019077f52419e385f410119636220194d6d1c48cdbb04d933a1e591ee8f3df70d33a92841a57383610298606a421e121aec7e35f1222585328614fadf7674414e635b36459bac721c5660a70d9293f4fd64d9ab6265e7a4de49c205353c8f20327dfc910d531231da5a562a7e28ae1192545238bfad2e616d6892532dbc09034984b5905956b2f2794bbeedfc3678764ec104f8507feffc79df90c00d614914a394954f45575f67fe4d6a2f823bd476d5ce8d252b55175f698d725bada9424d08b47413abf077ec37c03dcb1da1303b8d97e9f1a1c227a51101f6d75921294074fd37370a0d65541fb9fa4977de1d542b98d94742d08211b97a483ff63e4976a36f51ddd197a4e865ec7c3bece84d88fb3659873e5a2cb75a18288204276614bf7b516fefc2300761dbf29007e1dd84db6a51d5d02dd5ffe26010f2ccf569eef88e331d4d6e9b1b6d3be695215ab6540b5335691721decb8bc1301c0e4eb547d008dfb916c49debf5e3d5c77a5b97cf756459c5bcde33af75a1c9f5b432ee3a79e77d36bc140971d256e6a211d5fa17644a06cbd00e7ab791aeb804920d8410b5b79b4e90cb72a6b29aca3cef5576e268f812d75bc973f604e2b6b4e5327194ce84fb7b284d560589a67898df7d06f950f1300de90fd34dc1d247abc374610ae63c971482350d986ce88f111571a4165661b91800b72be620aad9fefc82310034053811a08f0f0aed56a4d8f9206f11514d1f0ac16db842515c04fc15be54b047ff9dcad7be144dc765ff566908f0044293ae69f991733cbe38652dc66fcc9413af71f4924e36add19a907756c38b3b5b801a561953994a3befbc082d13941a26ccd8e97b8fbe9d9edc7f1e827b0ae786657d97e9fd106d8f91e6897ce945c81932437b85ad516698b6b1447bd8a5298cb87cbe8616e69017a2314f53ba2af6c6ecc00fcf9a0da605a1dd220f483d14593ff533bb2e92cee4746b4a833330c9cd3bc648982e176d41d0e965ec1318ea6809e1c64c63417f87983714d25950d26e0e56fdbb8550baade8448fa485cb2d804b12016be1d9af962fc6f715af9cd141fc0b58f3912f599ee1f8de42053688b0274cfdc207c413296a3a768088e1f4e0bf6e45a89133932e978dbf5e3a42f71781a845be8b484f40114be285a63da57b6e9815bfb9062dd707f8e1716f7b527550b807bf6285b2707e9df9de14134906eaa0e7409bf4043f20671926abfc3d6b417d748df1cc1be5c688fcc91349a224196e9421aaeea1bc67845c58843e1a92e37fc903ef76e52f1b59018f1e4b1178ca28170a622bad08664350191cf3f90500244dd754327407f8ed1b3629dc3e8c7a2ef525899a8ce551972cb290ce4b0006b9d45e44375039b0c194d1ae29381500e3e8c51d3fa971bf48daf18c8e8b9d0763b8f141961e80aa2f1ca4ea20fafba97909c669fa4fa9db27cece60092e3ac5c7349985d925baf86db82c86295973d183cea0611eb57b4f9170d6a4a8f066230dbbe15ef97d14f22308496b62b16423af6c4e3d32d97539c01735a25548d0ddb49119a6989196bc81f372e2ede51187d0f1d1c7789f744de4a87de6d0112206268057e7fb982681acdeb6511bf2da70ec3a391f1542228c3a23b9025c158b83e00f03da1bb9e3ec6c2fe0989c519a9e9e21b4f0d8e07d2367d1d5aedafb38e87be4f808da21ff13417f8be908181af67ab3b51c8c121e8174e13af04fe85b93d4651f541f6db745b3718883726a25cf9a0d9283d1e1e8d598d121859e8b509da3e64aa798ad086e29028806c2de4438d3775a060943d0af375bab9284e3c3a131d981c8cc3785faeae24622e5f87b06fc83119f3f728400fc7d790e70572dfe909d11013218ec3e5ba52884bed9803797f11496292f73d795ee0206ed23225b5af1a9e8c722cb081cbc97a68a450d455e382fd2755aff05da5bab498c90abba44b3f03fe9257a4cbbee1291429d986f105d3d8aa736bdc624444740e3c744576994fbdbc7a1bf949ba99eb113cb3d90ecada91d6b758fbf07fe79c56ced06962a3d51181d8e47ff0a66bea5d4fa7f3d4d9a7681b99b497e57af3c437def6360a44f3ab9c8b7d3ba0f21d73eb093336846324a120e47f6537f532a8e7a328f53a9a8be269318f31ceaa25304a0c4db6e0ceda2d2dbc504cd174ba6f864510e628557f13643afd593c2e382339ce3c6fa24c00221250cf1a9e441a35af53ea6de4d014e37a7cf5484ac45156453a4c5565e74369115fd505e2ff7fe00a296712612d3e9714a6a38dc4bef3c308f0a62f5095588d2957f362fe5f440d820d94040e628174b28450f781771ab234b03a282a0b93b325fb5ab4456a32e071ed83b539efc35413e5734fb527a73e4e094407fb8e1dfe2cb5d7e6e1e4335291f5fce22afd1661185211a5fb0f6497f331f7ae3b061313dc274763de4ed1e7b9feee9adc1d319fb2874ac162d99cb4ca56852017cdff765021a388689ce8ac35b771b5acedc6f12e8d377a3fb10c126e1277a503af594d6976c6d010158ab9d8023ad011742b7e035bf9c29d113912b514ac01eef3d90a27845a4094ccf63c8cce7c3abd5aea13aa45a18bdc4aeb6fb72e489b2285cb736f556e712a6e1088f977bd27c953385a4f1db7cb1a9dfe14774f3b4dde20bae242d388c90b4ea63fd515330ada29a4b0968873d14cd98d0cc88d20a67f0444dd8e9486b0a957cad909885705deb791089b40e5e8dc4f21d830e9aac746c093d2f1923db53ee725f22da3ed90e0121ccd0a42e169586e0fca06c3e4e9fe7dea2c232496c625f6011b3b6bf8e21d2bb61aaf28f7fa3afc03b7e15e5bc40f858d723d1367e65871d8b25b8b917d8009084e69032c0a618e7b5b218928b01027b4cc8786dd2f4d6a846cd8e22f585d985e0b8300710396beeaad4cf252a27576f1459bbc3e4eb7c5f48cc44c1d5c1adc721c538c18a432293a310e9eaea5a1d0736bef4bb64c6dec56fcc2a0d75ea141722fd2f8f9ef1e15b70ba4d678fdb167356613e4e264181fef776b60a605a39629ba7c8675c456fcb211203b34ea72deba70129d399bc749c0411c7722cffaf43359c113454c07c5dbfe846660e374bdf84f022919ac66f9b73c3defb6db5849235548dbd857cea9e3005a31a13ec81c7a08e14398ee23f54beff73d8a8eaa0d7a658ea1142b219033070b373bfab879f5e5ee9a9dbdee9060d68f5e04742a65365c23685db4d5ada6785ccf70c96c185eaa177e407236e27bb4d29b011a76be2f323601103524a6678d4f88df32e5855b3fbe59f0b24ec285e85576fc6538a7e9cc75534e70816766729c05198fddb6e9d6c248d4cbb5492f10f8df502029027823f808dbe6b42df07953648d789633607a5f1d9eb5e97c54abb1c8f74f1fc14279eedf9fa96a2090083751e866af3c9ee394fbc464bb5a5922e65722ab1523502c0e88077f7700861148da0410fabbaf00ada3c88eec1de7667cf5be321f77838ba587be1414e9ff540ee4b1829d38d06930369fe3b3cb206157aacdbc26e446bc21a14d49034627bb476c9b620091431ea590dda99280725277e81532655e7f1e910470eb26b33373e3ea08d0c76b4424ecb0d79d78734df5934b548574b5944310697556b7bac23071ad838482192c44f7cf829c1a449d4d4661ca3a6862350ba1078e63619c5198378cc83fc1df7168f3d4b775a923c1287dccb4b8dd6280c5294ba3e59dc04adb14f461c1e5b7de9ea9aecd4550b85b681b66c6b637d8a607142f2d69ed1c25d6ae0c5525b48a63a13ad597303b451be63ba66eedc75ccccd40765fd9fa853a81e63e83292c2de8c6d6f415d8e6bb7d6c4bc070eb9a456a2cb5a6f59c532aab890a2f9dba868a1125817231eaff7e59268376eb51a2dc920d182d176897d5847b49c217f0322e22c0aa6ce63d67110bd3944a982125e1216505da7bd61cf036a548197034a7cc58612ea387a6a9dc9fbe14908908f40c06775da13e384ac2ad72e404435b387fe52237082e8e708d87f29def0e6f44f89e9eb85f3560d5661139281699a8f970408881320c1402bff78c3569c9484e13398030a464d8d764f6f70d137c341d5fd111397a9cc0deb4fff2a253a6148fe01ee9676ff1ad3db52c073537e72b869c4d247ee53461987b433d50c176b1b26bf032b8384944290d5fd8f6c7e9c1e90b91b1c15bda0eaec5dd6aa08b9ec32852c60ad47c4a7c846d6954e88da3586bcc26f1271e90888266f54b54e3bfe31c02a88a50aecdca378cd81c02189c8d46436ebb4cc9695347d33536bacb80d6666b0cef3f3a3a16a4b631d197ba1212aa92164d61aaf3bfafa3bf7e691dc2363f121db81e95c49c8f84ba8e26f54ebb543bc7fcc78bfd3e83d550b30b7e55f90b1864aea21c4babebeb3a09013c37a02b193d15697104f8bc56fe431f655eff81170712f0f269812600215c68cc1ea446548186c3c86c43504cbec79a8a53aece6c9d683f5de3c5fd588ae52096e3d9c1efab578fa0aa902257546d73420bf6b84954d7b8d9997e9f204279dca44742bdbe9e2ec542e69ea41927bea276dc091dc6409ce3dbcc514944961ca08e5be73918f6431acbedbbe3ee1443345a5142eac0cd457fa51671150092f3f9137f83866abe5e8a28079b36e8cf042c122d723f56d7808ecf26b1e2c3ac53a12c3247b13a13d5c0790baaa883e8e696b799b23ba58ead308890676b35c466106442e3201418618589c805b7fc2fc2a6b023d8471dcd6c083c3627c90964ef5313771e414cf48cf4afe6b6508a99202bc95eae717769e52ed24452c267b6a789329e351aade4756027b14949a248aedc23aebc17afea089924e913c6830cceff3a670460ba0fd0d1cc6b96e0cb33ea64ceb5b3ade67303547b7330c0d9ceeb6b2f71e6d60cdb2081982483567da1a1a7f0a413b4c6e720035550eaf902f34594addac78826b09b7196952eb72d924782296790d6734e0e3c29b414224564ecf307fbc8466b70ff638238977e6b2b1bd0b60dd0f1949adbaedce51d778e3a92296f96cbc047007c457de78093d0c6072d4e61430cc89167fc073d2dec81923acec6e5a896ece6307b7c4a62c542b4995f3e16a81c6fcc70b58c596c3e1879fed05c1e3639ae61504c48ecaea18e2f255ff09ce0568f1ad9ed298022b27980fa9da7b5d2a84e4be56dbc6f3813bb443892bfb698da902a9d7b3f85a86b9e6f34172e6be038704ad825e8636cf341e9a5b1c34d53b7636be62e2cc3168fdcf99c6d84544fc280918134a5a6b35fc3b817da4dd28711ccba44864cc4f74445278c841be91d4f8f8a6594a5104199e97575938dfe086bfa4616b742409f06f7df7da00ca54b986fbd20bed4bc881050d7b5c62a9633a778d15ff959b2832aa5c5565a55c5d456193ff6befde5f7be58d0054210fd9579ca64ab835cb1784ef6028df3a65c236c56ab5e960e1d8b1c7f2503f5cc5ac4b25e58ad55b452917a9f62d98e94d1fd58e2eb2a52f9269c14826cfd2e983657f83c1536b25cffe5e5c753aaf6a6ceea7c94302949ed7fdaee5feacc2f5d4a5659dd673d9fb45b948e8c36a65b1aa1f2b90c94a973938bc185a87d8563f9a9b3beceef98d3b4013b31f4fd9c92c6cb61bc65617aaecd4f5ad3dbd49bf93520a77cd506e41c28226aa6dc16f4c25bacd16bc889161b9ec39446b1e88e9d65b1dec8d575c568511d748d1b2e3e977b3fc91f2fc1190f0473601628284d2814dd681197b9a5244f379ef286722203997978cf5f830391ad1acaca6b910b28a3a894f807d02b30bdbc8a2bfa649b1a0b10956c055311facdd3d5ade6339834c085543ca553d7008103f43b58cc96a4211f35f0de33033d0e73d8c490e83334d4e7c8b0a8dd8bf686f4cd26e79d191a6675ed85f72c52b4a86ebda19cd6ed01692fa9263ae453462e331961b2a862ddb0b79a3341cc727fa27d4202a0bc98cbbe698848b58a0ea195ebcbcd2b58df7e628b69615ccde5aaa14f8eb9869769614c64e55355e7a64fccd48cedfa2076831a0c0420d736ad59ed31a65e55e15255729e2eacf88ec62f0cb69476ecec16a814f705452c5ad6902f72f8e3559a838fafb4acf35d5edb502b9e5f2c5c1517b616a1eb76c0f9d6240b76bdc5ebb0ea3b40145eae000fa9d42412fd3788d9e7817185b930a6eef4895c61c37fe7cc2e3f8c71e5575cd44662e08381f314e3251e35a24a680b97af984818d3eb8871967ff84c0409c89a1c70511bc3455379204aa6f2764eef76652335ad9a6d1632d782d3b955081dc72f9e2e0a8bdc2a42c95954d9f7935d0598f104aac431b09d73b58cac47e2a36e4e207fab3773390cd83e50fd37c0bcfb587ac99b6f30375e980a840260b203b79a7afa81823cf02287609d4fcfa146e6ac642b4158149f7a4cca0557a69d3ff5f733edf3e06849a0845742e0e11432d494b6bd65f3689b2a67d531ab7c5e82fb70ad2340e30f2c49a264496d0759224323f63a1895a43fa3c44c83073d943648fd414b3f74f10e46f770adb19f5470b7da00abf5ced32b191e3decac62d7a97463ca5244b40f915955df7fb20f662431676038156331062ee94e1347c5ca0f181a65049218917bc12405e33cd2fe3ee1f468e0726b0c409af029bf214d61dc0c0cdc26955765397c77a12336224854e61959e735b0be7b501e4271f0f19b2d6b570a8cfed4093062293ec7c2c18e22c7cadfb4e2fd62f5179b32e60a376cab08319b6a348108d2a9deabbfc07b9526d75a32ad05085d9c3a9b9f47e6e0410948793a11c96bfad2820c6dbf540a60620bc0efc338dc9522be3e8574c422f7b0f4bc128fcd30dc95fda6a4a73a5c2887620e5192a54339703d79b369117d603c9e1a83d924215c7aa3eb12bed2a3bab202ec14067168507376f9fa2dc4cd435fe57559a934d0d50e8574f5b811ea881ab579de04b4d46a55539326847741eb13dc4b400b5cdc55367a0dbd46060ace696a280f638c7956480fda1a8bd91dc95607f2bc00c2247b9a5eaf478eca8d1d8bbb312f9de172b5b6f120ff914c48eb7de349a497f4461a02f9b5e5eb43457bd2240ba523ca04570121b817d007aeeba11c17e756ddf3364bff5da359806c705dbdf8cee8b7e48dd417426de632d27fd140a53aeaa62293c5dccc2d8b6f4a9ac9b0734216294d209a58a788fa18c75ebfdee7a92268f108b12f4c1b83c9d5be8fa4e346877075b41d51a7c6c5e9c548b83740995a87fca255ee89ace12a6493c9486eb7dccc9b02d67f53b846bb05afc56b1a064d86080258d6175492516995a19a634c37707721209125d6362cdd600adfea90bad40b0eb3ec7f8d9eae2488f5a86915a29d3447e7597478530a2f086ceb2d4f582cd676467804fe98e280c381dcb2932889578ce6a8990d57710ad256a6c8398ee412649c14fc7fdd1a0f3a848d482d2e3b770e00d5eed8f26a12f044b8e57578687d4c93bfb88df125fc104de081b3a5b801e26e2edfb4da4e3257fd7d10515a2ef164ff78c627ba5380f704c3bc0c093d9b32cf024487e491f4d2beb110b8aeb8ce70db4006c240088f4d1a60e414a84d851501418b21eb542bee74d24fd3a6b7524b19491ca8724ecee9ba9b96b3f26d3e4776ffb93148c45d493a789d83de6bebee24461de41e017039d1980b67eca5c8489f14a4a75d2221bdb6d479b6bc9814e5b312d2aa485700190a83e0df213fafff6e158e50cb05c570f29026ad700ab9508df1c1c10abd505952ca0e6c0c6bfe4ae3b9ad7d5110f11325650a41cc050c28e90ce815c0ef7fb4b1e327a5cc2fe24afdf46e4db41517350372b77381c69b8d00a98b4ccc451814feefa516035f0b2a91bde09db77ca8057d4266b2951775955dd0541cb34d74a4db39d35de5f9291f4655c29a7babac2dacdad8cd37033b9cae554f05da38a321b205ec0a0805b85edf40966b9acf362ebb585c58bf467e4d5025f30dc1d52b997acaf80358711f41c4ee3da5c7df582e4d87029229471c5d06ac69cb5f138558ad1a91b2c7e5d5308856642827eb4799233ac8a58048dd2ee3a254a5a13250328a35ad75c505aa6ed342d53d0ae11b2e91b32f97aab90b0318a6be6742987eae3fda663b44812b8c924db6c539a63e74c1573b22b4b52dbb1d3a6e64c2a07248e0af5c7f446470615049e9f6cfadb2989eca66c4bd6877cdc4286f4d0eee92ec82a8185af5203ee8368730dbb0b9257afeca0a34aeed888672ca74b2900ff8204b8cad939d07df4c215732b11d4fddbf0fcecd2f41bc3028674bad903e4b0e2e362dbfbcbe471771de895363937a63cb5086678e3594e7c09cc29ae4bfaf2137e086d5d4c28a20aff0e24777f5c5aa0587a5a3e82fc009a9a873a1f3a06fba77772716228f1c7c5abd2a1cf38de455ae8bc2c5b08e9fd2e469c4ed41bc27de3caa3f82ce00d8f2d76089ba66d4f00d13d0c5c57287aa6b9fa1f758f8b76caa09b662964fbd719488ef500197caff04c751384a5a83e4efc7693bc727862c04231f9d1ae15b38e70cc710ce6d4521bf6e4e7b89c0b369aeb0c8c6afe240b22d814b5738936b6495f33b5f5cd4327e264a1acc9f7d2080038694af34dfeb4a69146e8b872917c6b5ad755e4338b3a191fd6ab53f465e571d18f5e8cc22f4633afb92d01a8408247e5449f3a41eb66c959bbb9ef416a0a790503f534801a95e41dcf8284b2ed1d91f2a4bf174d264a33ac7d82b188006c8510b0e394fe60b84f352c8f67029bdd15829bb65413f0a650ce13170c7a52d61ce26cb36679ddcecbfd34ca9a934bc5a801ffaa8b62f78a2add22ce17015ad01b06cfbd1c5c1c00ee4b7129ed0a142394784535ebb24f2028d9632d3743c63429e5aa5ec2f0bf5939632a569cc31faec9c0f962678c64615f7eede107911a7feec98884f94160e2527541c728e620f708b647d54d96e3c4e55e85807783df46bf7b5a9640f71c8ca1b7f35d369817625ec824efb6f64aaeb98a11f3d5197ceaf3f2129720536761d3ef4e0aa6e9cd6201b5393e55ea6570849cef792461ea65303edd41e202d279555107e2a8a81e135be8acef324c421d19335d1f63f7b612623b46a182a1d38e9f8e0475800e010f586ced1dc8dbb49751023ba70c27e4a7ac8c6cfc045b65fe54d7842b9eec6768d024670413c49eda57d780e3a745bf8fed7490fe974737a943cdbb9a1c7e309fc31148f1bd0aab135e5adf6e856af3383e1d7113491751638caf37f2ea9de3917e680c77fd255c9d938a0080bc9560bd889ffdecaaaffe432e4bc8adf53b0f99015756ce4c5e435094c5742e751526a59c212e9042967502a53cac0e541ac50fef424e7b19bdfde79c4d13d53e467892c91edf86c69446a5940ce242a0bdb726ca17945544e3979aa8591a090c27534cffe9a5aae4bf50b39ce66468bdfc7601e2ef9d47ea32f63e3a91c22fa4960332565401c977c98674d675c281ad91842aade990dd7325360aba6a77b60ccdf76beea285c15a22233906ce039b524d730af213b3c5139355e8572ec045d2392c05f0a1c82440b3df97b515d912de448646f8bc63a710dd7c05481eae14e120a25ce9490f7000d134fa9cf3e5904c7203137e5c6b7999d073950166a931c06454014f521c3c5280395cdf99786d0877f51f800b92774853c0f30059c8423771ea84e06bfba1610257ca8c989aa7ecd5e73def82b538d2986bacb6b0f458c067934cd29ad551ea422742c3e12924a7fbcf1165beaaa039cb5fbf55eb05c819ce95f3f4ec4b11d2c3655b856b53e106607f9e9c8cdefdcc88cece54b01c5f81b46146cd146b7826c7d9ad93fb9c186cb46bb7e258e5e83a905348171b0b0824e7aa121c53384844e542dbca334b7310436c0fe4d1365a2301e78f79c900881e124fe20130319dbce3d4c68b73290677495eedba5caa53f8ce246a400412f5f1f4be8b6f13f6e575c1571d5ff6c274a8064591eec224ca81146ad772d3603b10db186d22c6839597d89e6436fb28c4d8677010e7458a65651f928567d4dad0d28e1c4728f056dcdf30cea688d929f439986247e173c685394b07bf4b8d26e916874af3e0a6e650a39e2a3fe4a9816016d5aef84e3cd99ce6c1e93d7d56238f53ad78dc0f4c386a5c21d45b243902607a9cf730e4183518bd2d968ac6ca82b2fb67dfb2f46bde8478e5c10a5cd7167221e392f8478ffac9787408f35c64a3009e904186b1179b94d571a5e01d790dc684eec09828914f9c05facc660b003d1b98b9301d722f6198d040377f0b3e8000081676ccc0add35383e5b2adf3d3f8a461109e678e97697666712eac072090eb94511f3f97e905186cd32eaae0e6f196db5c1123437884480b2ccdbb95694bdc0feaf1f2391f020c9457b02d1939c83b386759c47316d2cdbe8c765f2e25df05995f8a3eae348d3c5609642417015d966aa991974ea66ff3176d08d00849d88808a21028a8b6ea396daa24d99330cd1d3f390db7ca46c0b810be0420ac4957a0e14a453225320501ca580345cb71b3b567b3e487cd764cf441fec6246e89b129004182b374ea4df53bdd15f4227dc88ae00c8216c184d75ea8aa4a5d604b0c4403ef80f42c7152bff9ba9a22d30e34487bf0033c21a29d6407154ad64e87e13cd4e2c0c6d0ee0d59421f678252840cc19723ab54803f1adb66eb9aa56465c079887d09101e62b23c4e513242dba9994dbeb241ca16fa8888617b6cac2258d507a4bbd8e428dbb8ec71d341c186257cc8c0a657392a239c9e2c15771d14e83fb8818757704d07e07887e106b37587cdf9dc269a007a31d5fab398ccd4a09549ab9718156a892aa82a26327347da40a065ea5838f4b208fcaa462f29f718d981253f769071ded21064702f49697ecee70965466d56f6c4e961b2317f379f657ef561a6d86b05535726e79cb89a6582ba228f29402913164dea1580003ea5c17e0bcf44a26d21dc189f1b5996351ebe9a9ba9eb08675d167208e1f7b8f3c655b05a67468ea6c7d8612ba0b7a7cdbcecf735568394b404c0266f480d1800e7935342272d346405ed2077d4b16529302cf50be2c4150a112bf770068914ed6ee2cb73b4ce68c80e087a31dcf01f53857ee3f04bd3d6385bb3fe4eb47aa682a9633f299ab45504fa30bba25a20181ea50365fee81889443650d29450a0329046b6fd2852c97fd42a8d6288a74733fb5daa8a5734901a1e8b0e40602a430d721efe6d486786ce9b85da93d80e8c0c12dcc87f72017ed466d1482344eebdb72444ee2da54c3205c10a8a091d0a1308cba1b64dc68eb0d714614bf5ab5e0e1d6dc5835a870e09fab0e97dfbee9de3d9e45bc132489ce3d1114e7ff1e30f3cbf6f228fe7348ed55471ba8b0716decca75e101c250c8f1d613e9bf0c40b7a03968518443cc142842221b0603efca00584161fb21bb020818926c876c08204ce5dd4576ee07e61b1015bdcaf971280d00de1d323134b99ea51a7582855d0475d3ae9bb4164e7c60e2ca3b1ffe43ff91f6a471dbd02d4e69c6d4347af002f4ee3a04038effb86fee6e104b1aa94ac815299e4df8c73f6c0b1c8e284216459c214ed20adcaa6ac3d928512848a7670430dbe60507c5005113ae860adb5292f70b6b8b1e8a2c5931f9e40825112580f561c618c0c0c1d72d87a10460cb8f8c1890d37f01026c9078717d20b0b24271886611804a6c082071f2051f485123e8412b0c801ab825a11461068b0c10b0f7c40a58b2864c000dbc1042fa31f54f811f358688ac30eb04ad3a04783fb25c50e1842083d5ea14165ce89655224ad70bfa4d062d24aa552c954a158cf81af570babfc8395d3b66d474cc22affe2b61991b84b8da37fdb5880f13b8f368ebe24b0fd7a15d5c0fd7a49c14f8750bd94e2c704710047588afb2545ab251936e84816fc19c5129cec203d4631466683fb15450fa2c892443d5284a31d18010544c85206abb528acb8200b31b4a0610c1aa81811a910e560ce395357c06c81c208cbb75108a9748840c83be79499084dc909657b944c53652050d1875e06fbe1959e11986300810ae718700f7c7989a832c5aec1fbf4522b36abf2e71bd65885e3c5994e5e3a70b53419a3d795a42cda0ca0d71357b61cbd9ef882014df11b29425ec54aabd5e1e4e81568e9538161bb10c202a0e8af4087e025136ec0b1044b107e94525a81a7115172f4e6681cf355c0b3051c6dfa663ee7cd1c3f5234d5e444707cf8cd99b95e20220752be1062092928c0747882f900290618866158bca1e70d3ef002888d0745d002872776488253ce2909a0c3133e5a580cf7cb892710c087971361d897135f946007a9c54910aa1356387105899a52428c51ba7faa7eea2e31eca252cf11720501fdc4169824a4a32d46af22a221215710d08fb7c024211d6d317a15110d09b982807e602b499118601812a407cf0ecef72a4be30d6d9f2224c51525080ad0931f9fd90a03e64b9217a42e475cb66831caf2ba526485a8cad0142129ae284150809ec81fe9235b6130982f495e90ba1ce1c8c5bf2d5a8cb2bcae442b18be4cac2273641cf20f36a055a9e7c0c7b056a95494cec7eed481339940972b68d950f6370ea861d80fb3a8ab2ab61fd288f147e3801826bf3bdfc02adbdd0dbd3b5651a9e774e350bbe9dd6982edf78657409fc6d1bf42c2c6306cfc9294f26569963efb5aea52e931997d2c956ec81c52874ddff44b69a5c280b319638c47266e2fc678638c5147e3d0c2c647c2aeaad8f89d819f4ffdc78170673103b2f1e96331fbf9547ed693bd7c2a367751cc30ece6ecbd2ccbaebb32170aea9bbe34c677ea39100ad1385429f78edd1da571c0d9ded0c334170cdfc6dbf48dce417333f5af1b626424f12fb61552cae8d0217c6c4305dc10e78831c6d88ee35f3f0d42ad34424f7637f41a6608a3c396d808c4f16626d82f74e853d8c611e3f412c4be144208a97cf83146195d89fa594a8fa671cc19259533f60d7a851b8d634e2d621fb10845504d1822125eb1ddf276479af50eeef9a7083d6b658c56222cd5186354417728638cd325ab0eee170f5e30f5dec6bdf8589884e49f8dbb5f292a65b15008ba7ed502d4bef49ce784fb2ecbb86f4c9534617141b8cb7de699ae474d5ce9b94c268bf52007f227d8ff86e14ffcc77390fc5d2e21fef96b5e10ff6acd1238ceb3d38b306dfd80f1561218a4a4a38c3e7dfaddf46b2398443d4aa9c948c42068235cbab9c91026d246f46256544158f82ecd4d3ba8f33bde28638c32668865308319cc604e49cc331235ffba6191c7de4e187032e45f3b0cfe3915eb5ce94df533cee6f937717f1edba1e4246e9e38afbc99480a639e39a9fd6da3b54b395b7677cbee38e38c33ce39e7752fa2e085ad31d41b05a41217f221d991e5ae78033f85969d0f1f7ef11c5984096a7002777f1912496929ff2004821204e4aef81e04545d5268201272f9073f432ad65d10097ae9241c5da631e2cca49765524a8a69190644624d85b9749ffdc5a750662944c77cc8d938e263790458becbc611fd739904991cdc638cd15d42f7ee6e82c148c559d3e590869e02f78b065ce0eaf0ab77a418ca17f2414b4a8fbdbc13a09fbd054a8ffd122a42b02ffd92ece92fb9b01597d09ba16083758c29814ce800c40f2bfb2aa9bfcf9f71f698b3747bbc9218af0a089b2110108540f1e6aa58603304a28fbdd35f82fd5c42450866fa55fc25f4aee21582bde9b1a75fba4bec53ec210a4eb858a5a75fa262601727de380b99a784092c3a4a428845456585092c42c207894505aa9480503c080bbd4277bd826b627705e75dc1f916c03e7b0bcca793e54a661006146fbc95c4bfde02817a2b02b56861f8ace935cb814c4bb877b7673de7a440d8ecda60118a63d21b8391a3fe61de0e367f37b20da689db8e4c857d8ac2ed471388336c8ada36d555f686e794aae9bbe3f4ad058861961dd91eb44ac76d9af7e8b0e96df60d91fdd1f7cc945d29318d086dc77322c690a27cdf7c4079537e63a39277020725580d840314d6b6040e9660d5504103ab9e208955e32809563f4ed4a971031a585dc50962c0eae7893a359a3062d598020c568d2935b0fa87449d1a47f8b0da280545ac0a861358fd45a2ce0c0e5ab07a0c560f01074fb03a0b568b210410c2b0fae11735b01a0e5181c2ea87425147c715376cc16aec0a56aad503ab1f16451d069c008a17ac6e2e4760351080c06ae824071eb4b01640469820b0ba817004563f4c8a3adb1090b8c2ea8760a2ce0c2bac2d095b7c60f5fb4fd4b1f185d50cd0a18b1eb0bab9d081d5efaea8d34f6869620b963ff1a18b18b01a5e8185cb15ac7e278a3afdc4d018276075562507ac7e7f459d28041a7c7e585dc760758d82d5375a2005ab511083d5ef495127e78814b0fa1d4cd4a1b185d5363becf06383430c58fd3128ea94acb04e5a94f8c26acd05464060592b395ec1dd6ad5d062e598c5b672d412434551abd58223b072ec82f918b5688062c4cad1cb5cc2a8d5e202042bc72f9468a8d54a4207568e61729c50d46ab5845859fa401a865a3e43706165f964fad06ab55e50042b4b2825318c5a2d22a22cc1a8d5ba8207569652a018b2610b2c7260653905cb51ab25e50656965588306ab55e70042b4b2b520747add6105958595e916206ac2cb35001d46a4d798295a5162cad566b4a1656965c8060d46a4d9982956517295cadd6942fac2cbd4cd751ab65840f2bcb2f9507ad56ab0824585986d14ad06ab582d0032b4f9f1db45aad2d64b0f27c5284abd532e28795271428865a2d2a62b0f28c82671449ad16951e58794ac15d049ce794c8c59b121105064ab8683084137e68a1e086ee07a024474a10a3862dc2d0a2450e48c440e1435f09e20b134e98f8011440604f4a0003ac8a124cb181e27e31618299c54998cc09866118564312a4644e5c590d4a603e3870e2653a6902f37182c5886c87ac86221f319ca8c009941a56e083854fd0124e843003ccc70a4c8ae924cb747202d30ab61ab0d87c98e0848b520d5e309f273024d42838273d30dd60d2a20626947cbe2825ed207d8c40392182fa184d2758780d48d2490d309f32680d5a322735d48004adc2e704196a092c2b4a90c29ad86ea0e2414703550fba329ea072123334c1dd2ae6e177f7f5bbdb737aedebed89790ecb486c68da69d358d0fe64e266688257a8efb1df3dc779317772777eccf7982e1288ebd7fa266f65bfbe49061f9a0fee63bed63aa36e35dee4c1168ed15a78f3f29199af41e36df733347e06024d5adb68fccccc8c47bf763450dbf7a3be7e4dea6b6f3dba4a205dcc57cd43691705adcd3694d679784b79a798467d95b79bdc6bfdf579220daf717b9ef5aa97a1501268bcd2ea6bf5b91b33540e50a8dafde9f439a6c66bd7decb4570e5acbdb948cd6ff6e65ae365319f9d22865dd73dcab35ce5b6cf9eabd63eea3b7b5731319d8dd96e8fc6797b24367077572894e5663c279379ab1e8d536f3fe6372f23b181eb5d71cfd53ae3ae667cf6b5a3a152a95434ee6a46ad3277d575d90ad5dce937191999e7644ed9766580486c60ae7bae7b8ecb2bd423b1814fbfea90d8c0a9bbdab2df32eeb355f7dc67dff6ae50b747e355ea7b621ef5a998477d4ccca3503116d5bfdd1e8d4fd77e67bf7b6bbfb3dde59ef372132b57546ee308198d1da9ccb22c9b32fbeac9995189bdc96b20d208c43e661018f612a9c2cd07166594df110806047b1963bcd9b1863bf8c1f027180c1f8b3ee6bb40b38b4120d35b09e978e3f21e8138462410c326d4593ff328eef0b67137f372924c71a642666e8d6c7bcdeb18176e5ea279a8d7bc7e2de6ceb83083aa713bde7030cb31cca83f83665e04647ec6631e90eeb7afd0ab368a4a6fa3c8bfd69b9b208998fee6ad7cdc9e52c43d343e2b7de5beeb7adbb29ff1ead665decc0cdd4cf79907b9ef381fdd73d9e28c898ffa5a7df8dbc60477db37ae5f2fc775377fb769db6b703b3dfc158faec77ee32311c2c6a1799ce7318ec5dc9c3df7ad3dfc2c4391163e126b66840946b1e038f38c408ccab8ef47fd8c77243e5cf9b83ddc779fdd1eedb7245d9793601822f5aebb5948d73d4fecbc0c5bd87ef5320cb82d77336cd9af50832dcbd16c07ea65f5f2aba2f5ecb79b5da8f1db6f31a713fccdd3eefc2ce2d376a7e6f59c2e1288b35fa14ede0af5d99fbe5e197c74cf3deafad85efb2cbb4822ceec0fce6e0ffc7ed8c21a677b647eab3f63860b333366cc98e1d1cf4af565bed2e7b22cbb19e2da5e861477ab9ff92cfb1a5e6f40b6ea75f576bf09e12ae4bcecfb67b84a5da8b1c9788d6be94b5fc38b1b10fadc975ca871b99bc84526b2373d8f73dcc3af35e67b8e445ce37af5b2ee6b78b546eddf8a6054cc968f64db8eed396f957d97f1787685d4d8be765ec6715cee3ea37efbeaad3ad40af5fd32df7d7dce5b718ffa98cfbcd5e9ed675f65bcd4b6552ff5325e4dd5cc6ddb738ff2561beafb538f7a6b7ffb1ae3699ff2627ee5cda43c2d06e5c9004f57c8ea57ddd7f899cfdd1552e36736eb75bff2666a78339f79dd5b2feb4d488deee6263337dbb4ed884724e2ec22716c4309ac09eb0fdf48f64fb1d62a4ae501bff4b5e4d54e48295b21f6d27853aa9f6d6ff2b8bb7d7ccdeb29bde94f57156f4cda735ef6dc9b5cb0b7238d3726edf6946af7b5a4752e9cdebee6753727c10f79e2f6d27ee769dfdd476d092e7d73df516e2e9c6ec79b8ebb99892dddedf4ad952a8f92e7037ec97a27ea82fd936f3c4a3f6b12acbdbda5ef2fd19c04e7fe23910eb14bb0212a0ff826887de6955694529e689ade95b4a7268ee39ef332cd4df0f63909debc9ef9be65bf651a7dd373f44d4f4ddac5b6db33bf6eb908cef47b66dbb671a026d99163c0d8e72218bb997e73df83bde9b96bfa8e01675fbf1d7bb941a0095e75a6ef4ccf5d53fbb7dd9b99e04bc4e9a58e3d5d7afb21967510f7b6a374794cf8990ff8f3963c8857a65ff9b8d8c49e2d51b7a75f79401ff4e13b2e79f9c31a863f4d25ada4693717f91efaf135edb5dbfed5eea10fff74fa7af2b20a9fe8d7a89dbc06d2bdfd9277bab9082e55d5e312f7f2e310acbdfdb8eda8df7d8c77ba9989bd72f3e19876d7f431a51ae371f5e2ca83be0ff854f3aac903a27dcd9007fd69fa7ab3e91e91a5928f1e4cf001302feeeedeb50df6ad7910620d279401e2f852cacffec88633d99836bf62a68961d83542b33b03f63360d894917a0c43a876dd104a2803c4fef1b13fb2612cd29f5e9c7f8d6417890772130c679833cc4772c2f3ce00ffc826af1107c3c33ef6d682ec913e9f05bf0d53c8472046e23fbd8629e0e8829a24a043f718a59c13c3324a4bfe512ad15dde4ca394d11d4aad524a391ffacc4d5a18c2f70f2787841f3ddec0813b50d4b699c1ca145c7ab9054a165cfad209c3557c8927865b32188768290e15c5a1a11987e250f63568b08da314fffca97fdefed92d72cb9d9fe116eca85780d86112eec691a11658bef4f6a48704627a73fba77dd98d4798946a8306b604553318d2b6adca703fd5fc53f506e5b7bb175092a8e76834bd83fb5337b04fffc3c154985723088bfdbc99f6883a4d55f87495746f6f4b2e36f76b02bd820c1070fc20bd02f522bea3e0c5f2f817ff42b1f187449d8e415e9166c8e67e0d7905c1b4927743a757a08ec10d48f6f2d634c1e62638cb6872608564d96d9b1e4651d5793bb17ae9e75f5954c1a67f6c2ad53719cd943e9f4aa58770276e3b7947339db49d9d5ec171e95f88c173e2973e01ad43e34de96d13a552a9542addf0aff4d0abf793a7496347ed665aa2115e161e9bea4308e3f6a3093679d2bf4ea202de6067bc7400d4ada3d34d5c83de0f65613f3d21de6261df5e66821b89c4f3573ea00b4f29c4b2b039e52502c3bc8e37f1a64b8c3addf166d88addb171b401ac804f98e8934e13db13bf33ee221d8630c49006dcb73d24b2850ef767f92b1f262caf912b300ece5d06ceb1210bacabc0426323b00cdc6d44766f2c50ed8533cd6b074838ab6854dac9f60a2aff3aa8de48c10e58c01becc45db443165f149c35ec7d053eb1f0e59d510bc0c58d1e0a40040bbfb5da36392bc231c26d488e4038027928f0f78490d2c60f16c3cfaa9fa803595a8df30a8e39d8c679fecca9ddf9439b5a193f3e1d22a9a492528993c373b2a7b452fa254c353b7fe68fcd8ab227f03d258000d0c5ca79058531ec7c41f8f13dc720a04f02277e4ae5396129009ed8f70fca1f2fc31ffb36b039fec420cff1279e13dfdf15853c67e58de1ff60f8ffe339fefd8a51081a57d81efdaf69349988afa5accd9b70b08fbb0847881a7c7181e784dde9d18f43a343759fc63fcf47bc4a1370de91b2fd5f08ebb9ecbcaf9ac2e66f35bf1ff03b6e780eec07ecdf63becb2fdaf9f58d63c9e2c252088c8b0984e5f4a1819d3f77b600f0c4629fa7f7640a24dc2f2c493970bfb02881bd28ea74f424cff9e239d8536aad4af59f7dc99330c6f82c60eed1344deef14449e341b192c6e873ba43b1be333d27fbb983f30c4df24ee6e520b2133774d8c05624e227bd817d9c77200b6cb6d8230f2a9511a4607f9a96299bf96d039bba0afe190faa3f4d0a435e09c8a6704bf1830d70c0f862849f1d0ca8c7173b3a3a0b3684fd69686cbadbc6a87245a11f8233b0425f7441cc2dc1660c0828eac496c4801a03c2802612306582c568c0fed15f13a971fc7847d88c8393031ba24540571c1afa420bf64f55e9621176e872211f7215fcbd1b6c76210b7d0862e853a80c611f5a41c83f77a1298dc387a20f6c8e4371c8e3908d2245684a169b5bb168b6aa0f95f9363c0bfbb611b28408c75fc5c07a44fc359fc8c23b44deffc1c691cd2aa3e789371907e70b2bd83f878a46472aeae0dcffc1661cac81f7c17616341ae8151a0f11e239f05a213635a968a9c0b8b0bf121ff9363c8b3e0621ccd8c4300cc3ee4ce22a804912755a9c183653087443f5b1e16790abe02fbde97215fceb0d6c76232ec95aac1575e43bd21637b253c8735e5f68f11c0f8a32c5b93061734cc2fed5b760376a1cfe52e6f862e1169cdd08c98de4c55aa61cd86c53d95114b66dca46f3a1225f95020c6cee97e5c2661ba5b0d94ee1fd1042082184dd72f6ecd9b33b1e51c28521e7c3a3961747d83f55e408f491574629a38c52ce38b18a6118065d49112fc93dd247b08beb7ceba936212da24256db7047b0340cce30c8837e9e508d0642894285f5f716ce306805bfbf0a2f84b03fcf8e0bfbebf0e207b3dc85c0b9afc8ff6c3a1fd82d7ed34714c926a97a00e65b7ed33c55fcf80d069c80f19c24f8e0238e6790b874c2400d40b5f2c40a352712c28d31d0b2e2397d8597271870f2c573561870f225eaa0be7bd95ff2eb8eac8b78a20e8e6f363552d8fc7f6505406c38be6f3e6a188c79c4255fb00e143086cd4712c2fd42e27271bf9088f0c72094e765e9674b64e1e72560b07fe45183b1db8d63debc2409e725449888bfe21005fc3a0d540034c2a25aa9c23305431d104ac110429d4fde07f0d44461db891b965094420bb500c718a54c410a9e7640eba85a075509f46bb0bc9988bf70bcb4461296c61b252c8da276d47126e8c0e5d55852f033c452d2647c91c31616930d08b6a0f809623181b003b56d4cc0840c8e581546814511ab8d78a084114b053c60e16209c065049722569d5ddc40882156c54cb04412ab662ae04110ab9b8002c688d542b0c16788d55f04e1082256ad5af4025084a41b92584a04d022a208442c260bf029c20fabb1f0c20623d6f7050a767f1bcfa1c1fed45ff3af2d27e5552202564361042d7e586de3d1871a1920060f492c252a5811c6118b09aab5c5ae8448251e049630462c29fd8bb025bc42861118575094167c253d7a83a10130a4b67130411404ce1667095f46081d32a1b60d2d515a8247248636efe0ece8f01f9e03b1a79023855ec133d3f7a75cb31ff4e4767568b7fdc6e10374f88d27a015d0381e4feb04ec65af5083fbb71da58f1efd92e7a3e1675007bc3ba723acebf09b1d1a50b025d4b6f96edadd6472ffee99be898ffb73e933897dfaac61449f4253e9a6fc939a855f189ba9dfc08fa988fdaa1a47b4c266faddf0bfb468e2a07ae907ce340e7fb8b1007deb4ef9d7051715f42256b547d7a9ef697ed3ad00c8650c9c1d6741f8b0858ba3219c298e9716a17295ab5ce54fe3153aec57173dc0ac975b64caed11f6039d8a4aa148e44352e89314abe1353affef31f9ec43ef51a9f9f69c9a87b0a606974a5a95534c2e29bd42728a7f9e9442fec1cb43c2d2974a39353fdf8f681d2ba7749caa79a5506e304afc9b9fc4bf195f06c1d02b34d6817d53f3b34b566a3ea7bce6b367993e64e3fd08147eba3fbd816d024f14bae0599ace05db76d8a8f16abeb71d36fed53cf4e4973c16e04b1ef03188829721aeb936fed53c8fd7ac6116b96e1c352fb51a221f721b5658d3ffc0ade636163ff38691c34ed6c6dbf879e239415127c99c70e298b88753382821b5ee39350fe1eae1b683e6874005603fdf850c748ee9e767ea7db618d5f0516ff2627ff0318f0686ff1d84355e8e2f74d212c6e3317daac18d855a9a621a1212f2a12a442f37f22d6ec5b3b8162f9a9e93b10fe2dfec998209492121cc83b8cbc67536de8643b75274c58750f81a358ff6d8781920aef9209e93e10fcfa1475a2ad745f1cf6a5306c9201b33b0335ef5331f5f0a094921cfc15ec7fbf9b268d69f2fab78ce7605cfcf320b9e443d1febac149242728a1c62c1b124a2c9a7f1d987e8cb8b04d2fbc33f1b354f1040838d5d3cc7c6cf8f4851e73f3718e9aa79d37ccf931b0268b05906f504f16ffe11887ff4c8284151c75d320837f42af3a14729b1e9e62518fb98e4dffcf7e217bf99d9e7f6dcf322cfadf837df5d13859b29ae71570d2f435ce3b18d4786b11bc348b1f0572ff333de87b46cf19c159793ed52cfe334980fcda743f2ae543ff3b9c1dc0008d702f73e047f760b1c1288e5cdda97bed478c653d1f088f89fe1c9789e0c30c6cb5ae643783eca93211b1267d723eafc9ddca659f8df377de502c8416d1b79b2fe4de95311b083cd90c8debe90c8b30f3de939ab7879c4b76f79649915a845eb582f30169180319965df798d136fb0a7168b5f33af6b11cdf4321eec9bf8292fc683561a75ca2ea47a94274f39822296619909c7bfcce413630cb55608f405679969ca2eaf2c528be422bb74af2b451c07adbd48208fcc73b02dba544aab86f2a015ffb0c77995ca62367c605ffbcc7f8eecc582fc27eac43cf60ec573a2788efcec841d798e7dd4634018f3208c611fe36547d4b60c83b3f7417a82b39f5070f676fbd104a3ecad5de7446cf0ca08c4a74722717c231d8ea887017591407cba19da7bc3bf18fcc39e03fe61db860027367b286ce5348c728d655f3b9e7c3c27d3bab6297af705ffb0a71a3d71dfafa32f3843220989b85849ca6d74c57330ac888655cc84758fa5fc9b10a848d4517552c47060c0bef4190da1b044cbaaba5ff58924f19cf8f57dd4afb5d65ac3e0fa4d1fd6d7d115dc5e70bfb2d8822bdce6170c3f460d7a595ec1b008e248875863187d2638bb32a9be464fa9571657f0fc1dcf51f1788e7ffd1e5555359aaa5458efe15fedbe81945ef93d6afdb6a9b707b6b0252e300327607051c51554848912813862055bbad8628b11c48041104198c11170f02148881daaf051c1ccc28814203184215cc9d283186300318f3065052a3f4f4822cc183208c2420339ace04b0f2508eaa10a96021370f92268e04cb1e822bfd287d269d432460361c4a8ff0c4db089b5d05af0bcab161cf3386e8241cf72e79c73ce3c21c5f36146fbaee4cb246cc00afb3766d89c73ce09348e7e991c6c8c3d5b7ad1b7146d79d11d5e5b68c1bd4516dc2d01937c93d79b04a8cc62fc58a2b494655966929b04fc8bab52896634e062bcaa69b6b5afdad7faa79b93606fab45f0f6f6330ff59d1773aa7faa9f699bb6755e6e125304f7ccd3e87e86c6cf68cf3df184c6411a35be964a5f4fbf51ede6ed2bd7695fbdda6d0fe16b5f3da871a7aafd8909e65ef37c701f03bfbb99d6705caddc37d6b6ef3cce9ba1098ef99819dcc77c6f5a0ff68dbf4298bfc678272fd73fc9a03a542a95abed6eae1f83656ca5b1b57e198665d8845de68427e88261199c451815a51a9e0076d12ab53f60aa2382e1cdf32b0438ede38cf7d4335ed75d8d4b693210aa64361a3366d7fd065f06de4c24055393d31ece7ad2b4874422707af8dc77deeaf4f5bb6fd7b4d5e9725fbf3e470477b0a630f7f0747bb6ef52b8bb4452d86e3eb6db2a1a57ce9832a914d4b40e7615c22e057f42a85d6ec26e42082184282f13c1288a632c8e89f91a73baa70ff51c17f3a76fcad9989f93a6f0bc99480ae56522a98efb69396ea6344efbe9715caac3f3bbc97199480a6b29f88021449a0834006207a926020d80a08208462919341428385ec16928500e10a1409161a0a86cae58e9bfa3809e9ed669321675b279a1fbc8c5a44cf6a22e774f73ce79bbdae1a8a2a15aea53aa97b1aa4f69dfbda6c99c7ed6ee352436b47a35cd7efdd879b9ebba17509db742fde93b4a354d5ba16efdd39fbe3ef7272fbff0029e9afdaebb446cb8db23a379446cba4bc4065b1999542ad5a53aad6adfa53aadaafe55afa2e9ba17e87ff5476783e21ef539aa5da5ba99ce90b9a993bdf99ffb19af370834e11ede6c83b99b89d8745df7bd751de7e5176c1e431a0c6930f7f6db7354d86bff50a9d4ccc39f79eba1663e26e6a2ec678feaba1770ccc7c8e7bc8c7acdcb2f74977be8698ff24e27cfb166d3a3bd041a732fd8f080386b3df51df7d6a3b14d2662038548f53b48e0e265b383042ea4a0368bcddff78551a1e70fdba1cc44d052e7b7077fe51f83707c611eaf3202e1f6a3f8ddb862db8e9ed3ddfdca7ee9d0fd219c4d6210c4ad1939b09926486e2d10a778800a683ca1d7dc10ac651b9a54d6f0b77f98b3f67f088637737f02623f88d6533ffec7496562a6bdfd55894ff1e5576dc710863fc385e1cb7cad70d6c80186cd45f0f63602333ef5aab740e055f5d4b75f3dece373430061ee51588668e1f9337cc4fcecde7e05f290831ce42007b99f0fb98ff172fd917aed2377737cb1f36bb511e87ebef6a7b7945abbeaae8fd3d56e1e82e773f5b31e10a7b2fd989be38be5b8db937aedf6a09e7b1eafdef4b2bd443e9c7a5444cd78f8dccd7d258d7b2462998733785cc6cb4cf0b4d23f0e73af453c2390ebdbe7aef4efe625986adbb6c3f44022de2260aff48ffb7ab59be11f893be66b0fad7fde839dd087faa64fc2404c587beb0dc1311fb71da6b6c11c4d4e61eedb5f7a2af5b6dbe9db3d4f4c7d90edff69a0642490d46dffe03545c0f4d56463eccb08944c9ec5e202171ce1fa36428ff3f210cc7d47934c8cea3feaccd030c9a86e7eccd1b83350375b999b8ab9f99f279e3c2410c73ce765d467eff6ebd7efcd879579ce47cabe7c6e0886da43eff4315ef78e4f5e465d5a737dedf49c37c487c675cf133b2f33c1f56a57fe87b7979b29fee4264343b0840800220b128edf48208e3e64acb0f99f86d34082111536f70bfe8e0e1e8feeaf84a3ce6df1bbd83d957aebedae92be4d58a62bb59b8fc4a01804c4bb441e2250e4e19b51a21882168c833b8a214809426afb90d5316f9f55a800ee4d5b3ee2700b6c6bc184bbede7c642f7f4e186e439d86f2fb71d5b103679361fe79c318495dc4ff9dbb7a4bf3ddc1e6e011500f1b67ddc766c4d7694baae7b16fcbbcdabcf794db0c435cb3cacfa54a27e663fc76664b1b4c78f209e331fbe0e16f59aeb344b7a2688e49f8ccf04671e12962859706309a20b5e006e2c41bc009f1e7af9886f9edc76188165e0550bf1edb310dfcaac04fb5b8ffbdebc0976ef8863eec22d600a5a15ac3d4fdcfc88ac127fde8629cc9743fe491f700b9882fcb943b5b63d932acdbe09ae52cb3e635a9db89a30186b0eff6edc10e0868773d338fc336f003005ff8a6d3c20748f374e4f320cfb52c55a0bf1b3cf10630fe3bde11f8df76b1c3c7c45b388aa695aadb53ed5a6cc5efb1a3dcd93d9c758327daeaf51fab546198e64b8745be832307d235d06ceb2c46e8271082c3070cdd77cb09f0f1f43081bba3c1b9e05855e76c8c6095df1ca30f11109b1d80c5d2f03c42e1d58f80cf879d67cfff7b781d3e61bba54345909be242fd8ff864dd9e4584db12f9b2d24427e5e6343a737a85654060e4e5216ec9f2388902e3aea9bde2524e47209b9a440178ecbe58a49415651d8243c858868ad120c12f2cf8d8ca04ba607d005849424c465250616167e4c7a127bbc7afc78c0d10fd5878af419b5ee1e416c606310608f30ba60ff1f3f640f56f3cfa6781e8025684a1ff578ede8c83255a881cd36017de34f593b8dc38da4c80115acc900230e6ca62d486db05118f44d5f4d1e863ffefdfc98624d0aac114fbc811f05fb13ec57608741a28e6d1fb48ee91d628161d185ad7ec1332c77c9a8a5fa968c116cc1a4cc08368ca01bd9435711ccb3e159548a24b1f1163902049b3ba9086c9e8f3aa55799262cd18bdd8e37f9db51a4099a8b0a55183e54a3a13bfe2561338d5b6cf7079f8c2a5d38fb910c024202fdd01f22ea47447e9421911ff99117ecef5ddc0b9574c88f2891fcf123223ff2a3272da5ab8bfa72c7e3c934efb8c7d71be1fc38f39ab08ab2ec2ae97893a3500c6ac28a40334dd878e54b62c1ae2c8a3744576cde21a23f06e5080494654747961865099b77e27b32409c77faeb5ec2365204f2276ca6389a3472c7be4082a14a10215d62908b0ceb4f630516c628d115831ac74c1376826142e3f0998414dd592e5bf18a85374b2224dc464141f3c8bf56bffc7bfcc05a3d5430ae42eb09d6679eb0f0711cccab83b0bfc4c971251e6d61f3e73f5bd5874abf0dcf2abde69f7b3c5e0f256ca6168a221838c00323f6b1deff82fc117cb0bf0dcf03b4dc20826a7ea38ab7fba879e2d314451f30700073ec042d4ac148c2fe5acb070a1582dda14f6c862df8b303ed1476b6eabbe969bd41a2e9be406fbb7fd65f94f0cc9dbb53bbb0721a1164084c0f2ed8ff07929730ad658b43a65716a424239b61bcc2e608b47df7a7ec43d14571910a84bd077424ec434343eefae2d50822253dc6c1acb77e1cc883a0f52050bc29e2b697ab8bcd146b911221e43f2fa129561c0a7c1b9e657a77a1a12c363798f8039bfd487beeb3bfb68fe1155f18c78289ade8f0667be1960c07c38144b6e1e6258937fe9a67823ff1860b9b29a65a12f58c09306058e9a29ce8f57062b7c4de1277b06048699982fec91dd8577545f6937039d0904b4a15a3e8452b6c7697bba45004fac1591205cd90416404042a8c3081135b8001f4431345c020c51006fb13f9b2a323862265d84cb514582f5d5aa6a0f4b420db02a42d43bcbcb0ff0b54a3e932c43f57c11f853036d3a8c5669ad48497216fad228c9708ec9b35b10d88ee8e2a15fc197498e7f3b90afe363c8b4a868fe3dfd71e4e7ffefae2dcc0f95c857e1eafd16b6bbd0461ff94a4f24694c1c1e6ef7e9fc5e68ff6f7e9b8b9af7cd8480b0fca947ec1fe5a9047f129290bbff2d830f66a1cfe364a80b9a20e46146fdca3640f92ed4921a2a82387a4101196539e0c2baf0c722914848786a43c9248f4775446b082fd69bea688a894ad7585452d11d10c0000003314000020140c094422c168241a536565fb14000c93a248745418894990c32084900100100208000400000088c8dccc00bcbdf5763e6c71bb01c0cee3087158de58af47fafc668d52d7008b50c11dcace6ef541765362c9b20775709e16b79d1e2c8ca5fd113d585aec72495f95a9b562d016689e6e6d922c58f60627a7e5170064bc88a92bc017595dde71fa0805c6b5fb5338e888b1ce8242a363b4dae59a0cfe25656837fa81860b6dd383116b0544ae81af24ce2a5bafdc4a8308f5741483cc46319365a504c6be0e5bc65d12d3daf2fef471af7f1d91f59de9ef45639e56ab34d38417e9494cb508995c9744f9449a6a3909af86e09a94982a0690e6e8c28aecbde3bebd5e8a9f54cf5e83dd01c61ec3403c0ffb3dd6b1f750f5ee416ece557ddd8b94243f0ce583c9acad7be96f3249ed9cde7e4707c222cec723d0bb89738586c6904206add0bd82ba833c64027d57388382da5eaa8340c150adb0b3b07b53699df40684ea7d299eabbe986c4f7d87a0c0d05cfd008b8dfd5d85f6025c6f6ceec522e21b48633100332bdbe1040cc59c693db8a589a7dde0c756affd0bc1a6fe293560497f05b6f2870427302c2298b9c9dff58a90c1563f85691b21146af1fb6b6c2f7efd8b5ffc321fdbef62171035ff6cc51c694ac1af619b8a96c0c9d84000fe920376504d4acee79413ba024f8d9cb34fbcf2da288c33154ff8e28d18176376dd4c91a0c3e4969c85ceb9476e2574164263d7395f3a9fa3a7686eb5a79cada1dfdb82fffc84143d670e47677262eaff857ff6532badcede4f8f3ebc0f8aff899d6f4b46d11c8a819d0b96ed2a7898ad8b98a6f6965b06fe988f04efaa17a1520d020ef7cd69d983ee35621a4ae0d0f997742e366d3ec9c6179193ee51c54aff24ad0479472076105a47773dce41488748fb927472b8eeb82ceb988796236ab76dd152ce500cf95dd060a41556d6e03ff9323729cca2f563c671790b4c4335e868ba18d520c042d23214509e5718dd5e9a6a6b566be52c1852624b4e9a721df7cf094106ed7919aff9f1f071f06bc886188a6388081bdeec4f752ebeb2879cff93dc511e1fc92ad38a3511a483f971918cde9ce732846bc7cfb927631b868a5571f61539dbb0e13f71470ae4ccf9b0769b06edf3311b561bd0f337c9982c39a77680f6fd7483b10eaf55a387369f519d6ea5769b0fa9a5b6b0216d737e565404e89a7a8625a9343e25f753b87941f2bf64f38148b0bc512cafd94440e136ff4c6901e8a8ee9e1fa149503e02fb40d9b1476046497a64fea740bf5c9bffb48fa9756d1e32328ccf4af0be8cae66c24fe16e3ec865fc28df801d155298633e82815a526e2f779ffa687a0ee25a9f2f15c9ac3c0e7a65266f3467a6292e638f1ed0745895ba109b088a6074749acffad589c30ac95b854add954e3dd31e384c874ce359f09ea159a19feac963e71c660c392a1873510eea586f5504ccbb0b53fa2c2ab43d3cfd85a7aaeeab5785cf7715c6534b1c23af9b221e53910aa53b475a07810bb32476b1e6001b5817cb5c9b17c719a6736118962623323c10e32a486a24fb9cffd444c3397fca3a52cd70f0bea66e38cd39b1fbb6e22acfabcc9ba169e7e45f97be0e9711d0337f89ef6340cd72ab1e21e29e81a0feeda5064feb16dfb9bd6b128c405ac79fa01c6955df03f79cfea36f52f7079ee5b4d1ec9d50f149f1c0f6dd86658f832d60e088d234e3860a66423b3b9be9cb60d3d3a2451371c8bcbd32bde333731b619d906a8d341f009362dab908ec4980607b93d3a94001305c7d0bd5822e959bbcd2868fb30ff3b0dc88b74dc559a8f328fec37a1f39bbd06da88a34b31fb030b1e5cffe26e22af74b5d1e45a689c9811d4c58aec15de935c61d40463f52c8bf9b1330fd0eac0cdcb096a69c0766b745a810a546f79e91cb3ec8fcdebc8985e2f7bd05cfd34ffa1b62dd312df30fdaebddefe3d495d032dfc4496398498b61ae80bc23e89e1fcb58aee1052ad20d5aa1cbd0993abb70fb21fbb735605ade496bc58b80d1963aa41803105e44a49e782b48602fb8a26697874799b3c4a71bcd2988d8187643241c341a6166f07156f5b41303f713d488e69abe29b2d413bf940df1e7f56c14a7959dacb56df8c9b85dfd5e4b14cee8cf8a565d3f9fa11d3bb9457a3a782f12227d3ecec7b87b706c9135bad7415c47a48bfc682326992b439349301585ac1023033cbaf14586a7dffa9d1d17bdb975cd7517aa8c37410cde23c208f28783a0972997a22f00fea8051609f5ad58e1d8b47ad8ddfe1c64ecd3a6a119e75e2a1004b24f15a613991bf90411eb3461a34ee642dd4a4b9ed255e4ba05c64ae9c8368c2ab8c2c39bc6c44bca8aade4cce4235c2391de27b35db5d113c27251a382e4336fe64804d8ad5b13f161233d29a034d2257cea0ea8664e0d4b7662530a2d4a582a7de90239e8486faf5dd8453ef9911ba36eaad8631a5131319769c4b0473e57bb92252b6490c1cac908bb23adc8a9e830e91abf3a24576d7ebe5d8213d8ff942ac1c5c074a675942782b2ddaa1cd08605b3952af210c2069706d119893499d79bcdf9018dc59d72754d0e398e86f3015909e70d13423543ef2782ac923817c9059df621323f33ea39e7e5b3c4c1f614dceb990ca6ba30722ad6a7e183dff41f72ada08cb49426c0531f8c37a211b13d1b5ce424212267ab333a4fd7c9819b40bcda9d15516624df0a46ad4ce98e0e5eb5c8dc636fd0d2d58ca86c3ed73cae0d307d72b8d703922c848148d41040c6e4e7a1f48192f34f5ae45f79215c30df061682a07c3fe089f2b3b6754cf4635c5d0cc6c70501098278e8de5d29c8f0c6381827bdca12bbf22d84217eff3f7404ba81ae8afdcb818c64d9bcdcadd521281e284adbf2cfd9904696189b8e11a370e9c2fcdec7b08287d4a26ebb8f0b349121f2b9165ab0602a2a0ddd4bea3ba918c5381042bbba75d0cab849d66b1851677f2d80ec9ddb9d3ea4980451ca5baf77e003fb8a0177d2a4505e439f5cafd53ff5dd2073e1150bbf84366d79bde689588fee9ac52ce67a67446aadf09bc379cb106d1ad60b902956fbcdf3537e9309dab6be41b135e86034955f0a108bc753fdb584fbe2dc2fda70029cada1a8dea300b986ab35f26a03ca6853829c84cc864ca023c3d26752c1a2db0ad89a52ad9e3932a3f0a7bcfb5faf5c7b9a7772b708dbc86b2f6a66e626975a836d85f676f12e4c2227ed68311f29d812191224f0517526739bbd30cbef7de5570093dded20bfa76daef648e194abccde1b8f0eeb6ca48371f62f25929023190d0981f7dfa946767405ac0cd840bef0dff841cbfec4fbee1913b65405b751e6c103957595c9b2024437de8685632aefd073825c6127d4bfabebb3fd24dd3337302a9e1d7e48a7968fefa6b015bea15f37216868eccb46c37faa5b790bcad875b6c98d3dbd9fea891a711a50f77f2399c0d73ec528490bc1894b0eef3582d5f9f12c76f80e10ace64b4fb189b09d1f3f8b397d0ba0dcdad2f84c257ebe2313d3b7e9b9d2e98cb80ab06d8a60738f2ac258a52747b5fd0050167f5efb745223e512fa089e6d2736205c34939f8592a478e94feeddae146de36c96159713b85f8fe6eb75611f3a376884071362c27414d16bce21c536c7b2d54e2cb5d5c94401091f497d088d88202bf9409590b3f67545145e7816932e7e7afe262efa42c874d4c136b94fd46ec2d565ca8acea621ed3843e4d59bc831c4d1d554254fcd417ea6d0224f7ae40fc02d4fa587f37bfc605eccd990f8d1f80e95a4f83a15f4d466f92c87b1372befcf7ff9bc278038f2aa00aef575bd28045660ed57fa98d93bada5a14562397179cbbb048769f7bf888d8a8b7fc6266551d8c396b35b82a09c8514edb8854076b8b7c82f29a46b59812da87560ea2b934f9cb062b0390be579df51869a11aed68f06cf40fefb0d1e9db875a9edf413178213c8e717a8967780e5fad8be9c27926ad3438e85bf8d98780fbdb35937a0b571d0b755f010c483c97a16fa67730cff49e55b38bd615560da418c0c73fde0cd06657b1b78e2b86ac45efe18ac892bfe01d0b2829039f75b80f66c172ed913235c221dd7b2ce73546a9adbad89667b466603168f529acb08d2b6b12bef714af6437797072a58277e402ea2c9eb7a64c4ae5912053ecde75f5497957eb98f90a0fde91251e4dcc12f089d4366610ceb8178baa6ed38ee129f83955949ff37ea7dae43456ae2d875a9b26a37ee2f69a4c97c3fd42873c5ab026d9dac78af3337a77ea0b9a094913b0ebb9c840379795e061cd88b5ad0229745c40b9cd824967801d4b75f53b1c6574b57fddee2d9209c9646391f643c939b48fc4c2ed03753fae15da873183239cc943d60dd7e61e1446f4b66b653b7cff29f481b0398b82c434e919aab04f2fd21f67c097f71fc3651b5cf772545614f3860ef8251b76da66e435a68c1a913b511d39a9c2f141d4c9f44dade3c4f0cfd2c4bba2443248d45bf242836440dace00c92c106c5597cede299364e5248cda8a02c2179dfe03365ece914bb80090b7e3367a80c1b9452014cf7dc5a1171cc0af17df33f618557aeb01ef14a7d529cf190da28fddd1270d7f1bc4773cfe665b1f1d9020c4a35e32c55df1d934b98c8879ac9d51943632a67b0b84c77791f813833f906deabee4894c1793655dda5fdb72ff2778aa0828ffad5c6b2d03eab0210fc18f385bd319d0b8a4df5d6af1907a2030e497c9e104110740d0423410c5c126983222aee559ca093b5063894843abf799b46d943b2018431edf113a6e6b9d47c30aab91e2760ef7be455040c9596e9637a9d061dc1bef7ac06a4bf5c0c05155a5bc5532863557ba890bdd350f3b81406a042de67f8b320cfd50d4a1f6e1c478ec2ddfe7e48f3e1f222d4ca2313b09b39b4cc57256056d8e760bee7cab961b9f4ca7fcb20148cd7dc0e39a87e6169ccfab6b10537702ecd7e60c503edaf671d5796319db236c3b9432b92b621053912bf51fae699800d6045c8a5c0569c5664c6959a02cd172dc55e5e0a7c29fcdb450d10658528b28e9089972695584dee4c89f1602616bc765c57aae40662de3f6a216e8218678c9c6f90718cbfb40625b2f377c262e862a71af1afb431405ffee10f17d790b14c686fde64bca4455eb755a360353a81c1c3f3700b7c40bcea213a0a2aadad071fe16a37ad5e8aa28295b4aeaac4891f26fb2d1f2266da1f73181df6ad8cd76c2d89512e12cd2a498dfb0539f8146072f58376138006177472f901a3a5e885b5da22c7798e0f82b50fc3ecf53486e84f3a499d3444211220ddf027e64418f8b1d603227ab351e564a486f436cfc6e5f4ca98c73670dacea3042df1a217343a6d97d8693a6b115b9a7810af3b4432b8b167804a005135895d1bbace946ba8bbc199f6b0dc2c333ecd2e054db3b4bccb5f0a380a776c05ca1dfd8bcf55984a9785581c2bfd0ea79666325038ad436b5a627dac2281defd6a352ea7bf1848033b59653e9986eeb4b173e72edaf015fe94d1406bed8af9b3185a865c34005460c67463b42ba4143181da0d584219a0fb6d0c2b44d3a469deb7e3e45793663c837ab29514723eed0c88d734ed1f3960a04536a3b023b5021db3f3b05acc76b3194242a157414c37920258153cc9aa3cb1a9209224ae19f7d222015d37313c76662a8ded9bbfcdd564ded0c693d2b7f637ec601481ab1bd8a30158782528c3ee190db110d5244089ea11d5b34e3fb196a644ec97acf61598ff7e57dbae3ad27c79be08f85e0b4add6e2d845863c3a4e5e361ce03f3f60b32d08d97d19efb6f12aa4b6608aa2bde3353591ea0e855429ce829d50d9fe7dd02c5d4544472976b9db3b89073241d899236e1c7f021f2a31f5b1bb927d6097b502c62ddbe9f438f32302f572774ecdcf25a5e62bb7f72762781c8e28f32d056642de8fe2168be17e77b96c26bb44da5088487794177ec8fd1e3fb03ae797705db034ddaa6c421e090be3f82f805f5a721520e54922cfc4584c2da675761f0b26c800dce8528ef89ab60d8e6c962edc2e8ad3b455051ac350795f55b49e896243569540e38620f58ae9318c82513425a4e5a0b00c880c8df865bdcdabec578c65d31a7766013a319adcd5e08a7c393744c04124ec55506e093e8a10bc81a1022c500660c2b6e11372a852aaa75eec54f694ca101136df74a591fd3076d63eb7068186711e3fa62100f8cd583044dff7717b4382a46fa60ec84ebd6de0e5ab58759a5dae8e6dd82be47991b72e33195b76a49ce5549be04329ce0a5edb4f4a441f481a1fb6630befcddb44646bc7ceeb49e0c1051c04fbf9d1afa108e789b22ab61284549c6d3b43fc8abdb64d5ebdfd64b1e2239513ffbe136deea5e41cb046ce5bcceb09d949d60cb224562673e000e60f28f86ff0f64a4eddcce9b1a63e58a0d2dcb24f89df13202f5baadf62964d6fa3a6bd092b9b516efc9c2361d403355beb7a7e7606420f968f991fb279cf536cdab4310324fe01685f5cddf0882e10a6b5d463668a61ab986bf8d5cd9876a11f6f290dea7f2980ce94485ab250dabe997c02a473e399000cfabe4ae5eff783f6d281e4f57e909983c250cd3d1722b0d6c915d326b4ad57744ab6fa07adb81b578de5fb8d3cbaefaa4525a8e84a9900f2ee7a4ac2f2fea36137964de3ef2a524a30aea830f4680ac29618ec860567873e82c30bb54cda869e676758b27a360bfc99bba472b505dacae2a0c2b882413f8d737fc882b57f46e09950adec77578cbfc8c4a9facc280644d60b5d49be8ffe70479d610f50422f813aff62050c56b53f3dd860dc6ee6d37aca382b673f09aacb9a195ff80ccf6fabf93b6ea79212a0dc37c4c7db5cf95978d429977b152adf80ca9956d07cf80fd6fd3a69762ae4fd39d382e540e837d5d019ce8299178db971b7dbadf2b5a39fa342607eee133fd1ed9dd0414efe3c47acadee50ba6205f69ef82524c4cbe793cd73bfc6628b010a9344d626f85076b62e41afa88b7811101f6bc02d89171c79145ed83bc87328ae8886ef772a31fa4270d487c97f6290dce582056b4e3da0b60e666a9f9251ae843591c79cf7f4d519e76baf6b7171a90326ded473e6465785bb40eaec41834773adfc90ce8ae00d76ab1da3f16433b84799c26d1b9090e65ec19bc7dfae3d403b1a8f3592b02196102399580887b4117cf8a324fcc894e4fa4adbb83737a66e5157d0e130254fc2c0d673bc48b34fe46af4cfcd35f8ea6f8981a7afc32f3c592dd2a37cb879a89a1b5a54d112b29fa26e5ebc95d10bf443a691e3c1d91040fb3e5477f8a8559acd64c14d970bb17bf02b04184d426a068df17a5e31605307bdd85f1542510732b20ff8dcf39427563d9bd147c5b2e0acb2e8535a6ffeb11680671d62126e5baf9784993ad0f50d23f98aa56755585898e16d2623230f584acee1b1ce551b3e32e007a2124c4afca4970c48f646725aa770f7976c07d0a817a2d7d778fc4f5261374483b5cb6eb46ce340b68cbed4ad9ad300a86d5ae59da4132340a3514911f7deb56f4fcbfc88c940eae3893ddac04d0990c616f49d262b7bee75c2269121d9ff9bed49af63703d6238d4263f59163e2fdac8cf598e628f80b320517ed7ef93036e27b3526daa65530063bb707cb7eb5a018f28151b090f053804486272ac002764ca34e2a907dd091755b4823136a525b5c78e90f95b171cf8890524394912bcf068447ad868f0d4444d1474e4e3bcae03968dbd503c16797d0542aafcbdfc5201e26465560c2bdcebd33d0eff34536670327786342855c6c64e7d224eb00091a5fe8f9631c4f575754657f51eb2a4ef2ee780a0c2ffb13400af9484d3b86642649d00740a26fd0c0ad8d9c26f56f9b83bafa44f4fb7e0f6d93bd4b7d034f95636ca01566e60fd584c4fd6676afb592e8cc86f6c016c4b6d2c31ebb4d99a524dd063a7791de64f796854799a825ebeb537c10c91b49023c77594625d2d36d3e06365977e2863f29f8f1e6254879550f54a9b90a64650716bebadc514d06e3384380c9003b5290e42989d4f28ccb4bd733a0e5dd88e08edaf1aabb02aa09ad3ac24835b8043bc7c580bb9d061971e4fb334402ac542b17b54a641c47696988f3ebcf9c33980bb39c73d005b3b78619454ff35e1ca763443958441a1153820de26035086c0c012e787e797f40351738703850a22f1cc198a37ecb3a07e2b310ae52b57db03ee03fb27602d6530dcc1955b696b2ff0c27646af7e21fdcc14589c2f3caadfc98b273c9574b1931991549cff4dc5b657a51419e2b19135b07caba82a574c092313188015574fe605c3e05a275603f3ad7a0d50955e3e29df7ad8d91a28372297fa11a1f45249fd7e173d467f7fed9aa699621c1509875e2096c5ba815507c32d33a99ed598f5a14830fb4d4ab9fc1392cf97043e6ee82adc5a8eaa91af973b7dd19381a2fa81957b4f921144d9ddd6bd61cc4f18f432e932fff65d4088f85827cb1c5e2d34ba0267f52593aba9deb026229772971d928ca843aa407f027137dff4b13eba0ebedd147e23ba8a02c03eb65a3dada1bb84205450a7a9aa25d92ef5854d54e783fc2fc445dbd04c1fcb8bc73c85579c12bb271be3861fe027c39fe5b1cf080a4d79cc7c5eac2e896aca823fd8eb09e6b0704c1a086b54d9f463540e0191e000b74442d695a1799a4e3b955ebabca6b3034eca7b6c2b3bec138022a5b25c8011c6f5646c81c3fa844b9891597dc6539987ee13ba184238f0f2857348218eb79455fbdd88e500d5ddd53a9ba62c881504abe4cae3303daf32d9113bdd196a02077b24c919c2015beaab82960ec3b37578aa4de809a62bf3e8f7c63ba898bf0c9f3cc26914bfa0479830785cd17578ddf70306b4422bdc151047fa90386e8089386adb24b6b8d15572c7fbf01026c5af2de7eb0c2b5adb36c25b9c74dcdbb061b131e38e0bef894f90c3a36634e02851212fbbbf8ec840a06893e00d5fecd986cb7f6fd24c5c473fe7f5f66fe582c52e2b7f23935f19bd81dd0961016839db5f21991a58d70f893517121404f8a5622d4c0a074c7bf6f150d3b373626a8c88f27d5308e6d48b7594147a1f16f88f3c8f173a5c8e4390190d6cbeadac1e8cacf980becd5e9ba39d3568cd8381cecfab96dfa91b10b7b58bc89da82659afc7990b1e3e97f1e035e92449c97c5f6e7acbde920b652f89f785207e0b56b92c8db69d80064817bf5ba94a32227b959755ed526e7e05e98581bf23b9e3a8368b03508ca034e49005c802cd4490aeffa99db98a33bb15fbe8f836ef5752b14eb7078a1c43225c3d2fff9882b6895c4a3bab235804e6cde26d6bc41bcc6bf0cd72d33aabf709645b3829428d9a39512b1e76570af0822f8d8f878203b8346174a61638943c54c4a2ead1b40e4320211ab1d5bf0a0868346d582dac8986ca3b9011243c7f85a851a72cefc6d019c5a82893f5b84222c512f5763c73473ff3577a9274791eb64f101b1953e0c22ee1e30b918d815690c3836e779d011d1bce269ba653c4985b5b3e2ef479d2418b081064647d19b84aae48b889e39c943316fbfcf47ae349b2417dcc27eefc2be900cdf9719d693be3695664a53771fef8654bdcea05cc41004c378fa12a2cddf50ac98f43265e3e29328cd8a2222dedd8cb2d0a96563ba029b66468c7b8a56c1f02579af90519a0d2ba936922a254094ca8edd7ca4a7ea9f0807a074ad8dd9aa03098d469fb5df074d6da54fb92968572c024cb923006c87a22fd81fa59c499a3d0e773b4e68bebcc2455756380fe895e78b014f0545d3af69778e40ae15ddd4532fd3460808262ac9d30ea1994c422785cca3e629e9867ecc1855d80d799056ec73326366399e8656c693811c8ce653f1c07756c8ef1097d3b9496f1196e0a00eb7924743b2b8152a4953d11e42ad9abfd39499f4be64a833dd70daddab9d88e0ea1d46c50d83da768497574e74563eca8bf5777be0ac635f584cd65b9e9cdd064778df888e18d70f92a1574a904ba7209a61ec8e9ea2db350b2b764bbb78114e8c4c40d7828399a097ceda9615b2177c239981cc333a1eb5e75b14eee591eb486f87e41559658fbc9f3a83840a72f79dc19c5f30544c80212c36cd7c633c6064d527e3144907f87083da6c76fef47c49d85837bf7872fb5a3fb68ffce50649e11ff3632f2130de24fcd2029101c40a517b8de2ad59ac9dc7b5e85b514e8b47b281ee310278a5a4085633c5b16905ddea50fdbc8009913260e8065655320de44257f6bb86c6424acbbac0fe8839895d4fafaac62234d33125b83082864c40749d805fe81e70d4e9f6b67c1574e82d0ae40a8a829adfbe0100cf122ab347a56239a0052a5d1c1734141c1667fe9ec8817d2f6d7c62db5e3b1726eb44de3624e9d25907093d8080c56b1dc81858b6a36d8fe49cbba0291713b03cf99ee70030e00f3468eb2a1231d51f18abea950ded7b3a59c28714476b38e7f8507281315cced5d2130ed5ce20b5bd82004b3836f98b6e3172d971ffc5ea0b3276e6ca3bf135c9dcf49da0fa7336b68ba399708c3bb89836b104fb69aab8a9e3c431f21453c013b37a21d25ed2f3aade04b71e1133b36184bafe1d4c119642d280d81677c5867ac4d418fe2358ba8296ee4a8f0f2cfc9e9226823c2a5b36b1558fa19723d5f3723dd3cb2a57ac4cf185e93a7d91bfc4d4833c9b0cef64222965c9577134b3f5399c2e4bf5a6d3b74143bb20a04530371fa8612cc16e2c26a279553eb061504b860e838d85807a4fe685fb0b8ebf112014bee891d7786eed29ae0d6ce127cda968be7faab74a86761d727fbbbfe483630b54e1771a59fcfe7076762c00f27475f9d9bccd1456e65f64c9f7952f3b8cf5a61025961ab9c2f28f9322e95daf5de131c27f026755bee9f136230595c0e9302a85d1115ac6a8e785b4db2cc06b5e55282b81c720ccd3a5c6a4aa2a7c3661de3428b94782e36cf5e2dfee4b58777f38496da8ffa05483c22cc126d4c74af752cb35d22981a3147767685e515267b275320877ab5d69c66c8240274233c04771b59d4a9faf952ffd8b48a894e59087d8609285a738dd3faa752c2ac53a195dccddc5e6d4c9089507e2a2bcce59310c2b7ca15aad2e35b2f302936a0207ccf1da58143e4705601ee50c6919e430d6b039d4d79805614ee1db5ac9f2e5e0efc603a3e9b17fe90b60f11a781038b1eba91949c04bd49e4ac62d60a8fd374d287c649abe7780482399d05147a29113b7fe23bd5d3f9723fcd6adf25d5771480b111e7cdc95e4f538d7d6b66a95fd994256aa795bdd59e7f3d59990a36392c19baa6b0f77280f13c45eb0d73dcf1da213e1883256b7c71a00e8480605c0241f5e8065ce15200203d55bd87f360ad5b640e84aa34120614a0a5e456fa942aadab667848337d5cc9a117f10897d8cff7164300889796776de4267586dc7f647be27308af6aaa74147b31955c9ed5d6d528db089511ee643ee422b8c68ba8daa06c463f175a3639439fea3f1eb5a9422d6da4430b93bc9ccfd78c1868216c745cc6a865a148185d86a13040cf8f4029e99fe8511e69d52d974ced5d8737f24b5410529c3de2ef4221231098eb8fd4f42dc92ab85b027a300803647f805bd1838a707658a76f8955a7300dcd55539970dac5466345b9b697397dfe73dd8a9c33b6ddc8d7c5a0e74c6964fd7bfca9ecf1da92efeae0fc0c31f741c1ee1771a9ffd78f0c66d009e1e0e7260029fad0b242cb3417ddb607f20b204b8b8396f25249e18c6839e4e9a76d36fecbc7f813ade0ac4b68074a615c0c2d47d98bf12b1824e6187dd0a27bef1b0ba43e257624aed64c7ebb66f6539246cde441526fbcea52afa6485049cdb7901db46aabb57e6862af9bc93c51a4e104384a9e8f41c5b49dd40afbdda07daf023f8fd1dcc294bbbb709b8a42dfb07e46a6696e7f6cfcbff343aa54e471e2ebbac8a2144f74fc6b1e3cc56a71f2f34c48193d6c9fdb8d6b1f21c27ca74adfb502a81c0916ac79861090cf068b4a77cc07dafd7b65d18973d3b414b658d644a66ceeb914b1502de40dbc9e8d4fec7f214c11a8bc0c940fb79fb2b3bec90e0bd644d0b0510df7e7c26e759e8e867d657bdddc42a5b3b713154b64b94e0037a05f18e46afc587760a6c5bdd26b50d74253749ca59afbaf37c90bb74a37bf2ea46b37a9b0418f7bdead514d04fef50eb1159b99dc9e881869e071350f4fc21ab915a0da775ae5f61d4c7e2c5b3bd94cd5d39678b511b3c546af275cbf2691c692c7d48fc04ea632842c16549e632d3f907199d84f9caaad40bca46b809f3482870ffbea262fdac543f19d0fb176238dd863018a7a4ba2bb6b025eda03a8561e7b3f54ef0520ab683e32ed13c4e085d00845bc5580747a032c29db7e65d5e65dee425cbfa1a170810b5d1b0bd6b21ec90bb9f60c6b2e1653dc07df8b1205419eb7d174dc09d8b0a76111db1d50996385d3605e85885d6464ce1a0136eab729452b5adebae5addf0e1aa1a8bd61388ebf57a69f3a1d59fd4a2305cef3d814beb1dfc705656c003a0fba8b0ccb9e49e5bc2ce10dd2c0289b9509bc925e431229628ac0f26e2a95f59aaa7e188a2ab64b8a4114a8212c8caa7032220c07f5ea13a4e4870f92f2f35083443bacc68ee1c94b5f9f0ce784b5b2b6673be54a913ef889be364e1e2313464cab6738f7a797714894c3f48a1374212714a6ba2cef2251871ddae1abc1196146178b10a6eeb7fcd5a0186cd9475960133ec2e0e235c485ccf1672f6b4d0c9869979a50bd02574c77e36da2eb2c85cce22f3a4cd6eb00d1191c93b3c1c0af0be5965158e153c873ff8086b5a05a2d06a3a59502a2b98b7f7f12025e72fe85cc511f0396fef2053f9875a4752e429c7a11097edb0c56e0b0cf8eeca88ca796bb13311fc6cdf3a198697e5aabe7192e1f666cc86c08cfdaec702a506d8c46e03ee9f3513c9a8002f3786cb5b4fa83234bf2671c32b65745007c99c02a8827da804deaabc33ff29c39a25aaf6e7ce24510e7c0522cf222ecf793d018e19b4aa639e930b9df07879c1b9891306daf1eacd6b0eb9164df44d6e2e033de5e70173815c310626679f2b20bba7f03d0f1830856d970acc922a04f4147456881ca0116703379429663df745db1ec3c4bf370390bb00a9d8df5317f1f0dbf458629d0ae7ebd3c1632f39fde2ac412eda147d296acbf6f681de73b78c47ccd037090f51511208eb6d026e499af8196f6467ab17c0ede3c3f50b8e01f1d626d49e354c5e4d8ff9ee839061b92079a77b9d6f0372be558dc652c0f9e6a788591f64aa93abb59cbc014459c423250e06835f90e624b73f5f5d1a1e84c37852edb3c26c1f4abd664981f2d5852c9e377039bf01de7918687c8d901eb3a1ce1984db8f968303ee066b6a912c51c046fa7832df2b660ab588bba9737dd33d6c15a9bb5da75246c86520a8a0490148e078ce25f25b674cc05fdd1514ba073408252f414eb4d3c791845aec4424770b5a4513cac9d416a67be8e075b3dc5925ce50d663e6108c4eba29144adc2b08eba0becd5a3d2f5e36683e7aac4f453150863669c5072472bd230db33d06bcbe0e6a8eb602257c4f10cfe03e3f712c238125c587ab72bcab0cba1f21a2ece27431d3eb12c760eeddca04f41bc741179c3f211619fccd0ae50379275b4cbfc4ba6570262bdd8ba3face03a6e65d2fc6bd9c1fc4856a279de8a0c5e2ea58304b7af1b95a5ba6c2098a0debe67315acf99def2bd16adf079cc9532fe3f50be04123445ad2cf72e855eb36ee3ee3af5c958ea6d4436f397bf106727bb8dacaa0a0a044cb31f456a3eaa7e2c96800a3c59b6c3255ee8326545d956c9119c2a0a697168e06acf621abc8f4b81b49d40f25a1df010bbb1c423a37e0425cad5b2012ca5d4e3a0a33809535c4a730221ab583f478be52edc5f740adda94558c02a05cb0b56c88690e729bc549b94abcd6c2c8625ad95f31bcb22ecbcc88543bec00ab75aa7dcb1060bbb0577317bd8cdfd0a967d2654543e5eaa9f33aae3e901b2e4f1a97f7d374918f86ed083bd42c5a4b996d2739b7b038bdb46bcacaaf79bec7578ab39d64ad25f73f65773e859e2256f225d23fd74ee4ba350e12455b79330890dca9b7f42e8415bcf418991255b2df27434257b5f79831b7f2c19d930405c1f06b5e75a503e0e68398b1b0023e21f1923b34a75a1abc787cab4f2f71cef7d63915d4f5603d508159e12379ec711291f68b16399504dd2b1467a97397febe16770c0f2be84e1070bd7fe1e8e1e5ed91ad8d01ff3d9fda9380a4549d1ff03ee8e7199adaa900de48ba7e9716dbc5037dfe3dfcdf8287e58d41ba0b0974b342d201c991870db7aa1025987f13d1684998c2349877e37d0870fe0afc7187a0cfd23a91313c4e80ca78d029708da936e8b639735f06ff3dfa3dd4c0b88b836295347dbfe18db44f88f48083edcb1bf8d9b6e631c8c024199ec4a742276a570b12d7f6d9817a641605708c51d556f44f2410425b799f17c9d0e5598c89c75c86465e1a7c2fa137e45e7eead8a23058afcbe34ae1faa1797524ea3d5ba8cff0d807ddd6186280c5d452a60ba9286dafaac4d5462a6ce0accb9c7f2d9a2dff6f8e655d24213e46593ae40cbf59ba930203b0494d8590ceb027611ec73cb4af2da26f59d3158d51b921011e0513376b951babe06657128980117b3236b8a07e1b6339518c4bcf802621f2935e2541ea1510604ed5568a79db93029ed547322b7f491d814cc97b3dd98e957eb595f210c2a6291a01afd7f7322ca4547445fdbf605986ebdf5c1737dac4e0a070ba15efcddb901c24df73ee1cabaec7fdabbea8dbb7a21962ba70d042eb7eaec4d3d6a4b2c4ee8f17a12b7ae6057ae1d8b7f5bc74eec9501c25193300e1281f59df0099b98d44595c0d89e54c7ca284127fd416fabe89988564e4b669135d83f9ffc5c8b83049e2f0e8bbc813640e5953558df94f3dc627e1da26a1230f6caa37681bf48c951748b61e686c7fe2737a89680c6356ea4ab9fe392bcebd71d6439764467b3cb33758bdb38ed7e5dcd9d5ec9826a81febe0f5160696ee01bd105f2d693e5b3607a3e60b370fc8213065ff51080ab42c1bde746d3be6cf51ff385a9cc6b6a5ebc25eb4f82265ad6debfa04af85cc0433e6bd81dc9b841c618844604312de794c2c35a40b00d44f295f7fefc42d6245c853c9700dfccabaa12921e732e3bdc3316ae6d2288b5229ab0994ae45208d8106fc81be67429b16e51b58c2526a201e87b0c3b592b449df3c577d71f8a04539b6db1bfadd365e8e1ea1f857b310a7269f8ffca013ce0040e0a94402d721d2dcb7144842230d8ae5ba6096a0ef0e385442a06cc3a4fc189cb29ba7c73f01bcbe4578f2220fa8e9d8eaf2b3b30e44bd345fec1f4465cd4174d36df8286691f7de610c68036a1da277f4f4b6f0dbb9af096e28143741fab33a66dd42986d0325d7db58f8f3f1aec37532b5fe98f4f6712b05132edbd7a267ed2b61497fc5326fba562c135c5e3a7cf6e69b5c79d986fdeb5b5cc4dbd2f2feb682ffaf7b3db72c769a910c38a84479df4e6f44c7d965313fdfec9d38de5ab865afc6bc2f54d61391c793408189158dfa73e1326506638c269a3432c8a6a5e419617a7703406c8ecfd28090fa106b6fcd4aa0b694e2fbd71660f3c158f1ed1a71dc6308ce5e20e554460b5b6aa556be16f32fe045605dad9f9284a2bed96c0b38a5d4e3dbfe65669253d5ab09fe7c3f11499a8fa8946ecd4a614920d903daf0240cc858b286627860772bca0fe65cebf983a77b7e30b505f9f441a956f3a6dcdcff57d8185c2a02b625ac154d9a0c6bff61ae24d1e922c4356b5ee11a261034ab120f1c0d57b0e0ff17637de91264cbe05dd5bfba08aa52305499d3d066024c992e30ead93c16a2518dfa4021fb0642e0840e5b278d7b83130246640a831e05218332971a8f3a54f534eb50d421b7d53b055a6bd8278b46dfe7e5815cdeb6246eed85dd5fd7f696b988dd8e956345175dd20d87a952fa740653813bf5d6b61fe75d4f22cf02fe8b6f174f40e3fc6f5bffb2c390a69b8d8d4c2b57eceb9ff0cd485ef96ba1806728d714ecfaea3ae799b20a9005f3097fc71a5a35e225b8ac8732a79ccf701472f36e3f4e4eea47516d88cc64c1392908e19247c44fb364024b8d423a8f38c2fe59c47dfa238253a0e6d79301d9d776320b921936acfba6ca962596d9dd78b0988a802556666f5433b3e2f6eac21af4ca7913798aca6e28fc208aa14527d3073a77d12624deb7b20815ab75e1813c3519c1c5c37520a20b6aca1725d068671cb5ad166759d49905853a92e6c2490f2178feb5e8e7a215abdd4c4bffc239804416b105be934b40e1541934858416ea4cdddfa126f4038608496fe05cc14880c9686f4cd2e5c720282f44893c8b1ce3d65a78dc57b40bdaaaa452873b9cfdf65f872bc031a2c0c70191469a362e29b856639a90f17f131393ffeaed7f7d5e60bc82c8f7b1aee3fb798511eabb05e370cace1a987e43fda50243f5f5e7af26244a28e05f3d530fe25feb942c82d3d846b453a21f02f8003ea688a09eb4cda85f5db4a84b92a203adb2b91fcc1e838beeda0a61bd634bb4d7f3e07d81e70ae5ec0bc7e19f46d14da93fc3aac6270c213b42de1dfcc2214826946a151df09f67e61465d77b315040663a254059eaa2b2f7442217898c44e916ff6930ae8632d576d72680681391edc4aa1070d28a6984ffdb07fae5fe720a84e655682fccca14b3b95fe45e3534d2dfc9a8c33af0f85e9efbeea970d7ea6cd67615b3cbd1ae6fa268b3e944a1b257542308ecb256693482c425a72058f851080a90de600d140c17db9529c5f1ebf25624bcc4fdf21632702463ad80557089eb12bb31f885231372294c6b89be502b9fb67970b0e1342eec0f2e79a978e3ece3da45e3710375d4574a7d37b7ea180012f55c028287d3f196292b8f15cc730148c01b945c159079253081b0469f3f173969d10373e8fa3e1551be375b6c7130257dd0f0c64454faae33038fc283da2728131ddf19092ed4cca205bf64b58efde89a5f2588c80813b57c5eb62935436ab33317d14326d6d1a8ea568fdc2b6ed08ac46ac01a7c6947b5bf12d025d82b0d09a51d51b3706b8029b2db32204e8c14c5419f7277aa22177da15b9548ef6a1a1021c546c0a7ac5a55624793ba578bd21dc25a704f71dfbb7e00f0bd3a4d66f2052810cc522e9e6e2fd1fba9b82b60aee239697fb49c3c5d70552d7f5d19bea8bfabc0fc2b0ddfc9af698f200d9eb9c36c24743ffa456f09b23e9e5ed53aad1d7fd0580d173ec03e6ffc819e846c1b48e11ab6f5affda2359252d08c1d6e85e3b1ad4d54b987a3972609006169090f3a2ecd76e36025e1a63e586f9e7e3ac7056dee318a69c14d43dc3e8d10b3979b0a1ea2d5d295e1158f018553ae51bfdc0dce2773b4b5b40dccc626ecfa4315d5ceff2a04fc9bc5cd17155260dbbfb6aa4d8464d7290cc72a8b6dcbedff33af42ced74343d79ea8b0ccceb708278046f390b0e4de0174542c8b348f49e974dcc7fd668a3b60584681de095c4bb2459a0b4137e64a3c14254d453902b02a538db34632bfe1b01ce2aa901669e666a5a729a3418750525523195c1a880397813006ad911821c16b8a8036775d650b3b4171eeab7e2bb31cab766b05f649c881a2ed1e7c75c14f31b14128c4612a88809e212672cebd6e33722be749fc0802a2327d7cc5754ca6c4ae7da09a891c1f0fe58f9b454bd4e0a51af5e2ecef75f772b497423c4c649c12251070f9ca16cfa0738aa38968f569de57e21d23ca41e19f2575b819719b570ba228fd43bdef991de75303c8ba5293209598e9ff6b0f526ef6fa43060f12746dc9a07c8b2a5280506d2d499218a00488ce0e065ad8863dfed034bec077bfafc3161ae11163f774c9ec139cb2d5645518a76590f1bce60bbdac2d1cc4fda17e58ddb79968b07490841ff343cedf3ac1a30457350b99fdbc68e947c76eb6f11f1a6e0a5b5ae848e1bffe2dd4089d334657116ca14ffe2eb60d124ad10c009a9bb07005b3bf789ff688a22326909cefa80538a43198460683a9e4471b1a53bbd8866e349b387703335543677d9a4a54b02491062ca06643ad4a2f7f24b43dff7cf10b6143ad67cb1b94057efa53d0176317d56de1f6e281d8f69de842ac03dabc5548a7c980f8c8a21f6b3078814fee14aefba6045c5be1901993d17487fc98217755fbb946c6c411329b65a23f1beaa30c486814c43435bb9c359369b3f51b18f1a93f5dda68888037f42830fad7cd6950c6a76caa3eb0aa1553ec0154a844d5500273429f0f5099cf157084e086401591b370bbb965798bbedcf959deb54a07e91ed9a85943f7859369dedd6d94f3d6544386eebbd81189de3d5d7e804ecb2743fc9e2b1f55d390ecbf8923ef407b8891b187cad86a4859bbad945a5bd79d6a2c3dc5ea744dfea26af63501160e57cdb707b757c2b6f7aacad8fa76e36279837d4b118865a31125bf98e4772b2bbb74a397d1cd0ff53edfd81498facdab178efc1abe1d446f8d81074f6ed8e0e4011c36de9f15ea607049fe08e1a3d5e9edc7474ad2407c1d239ff4b0bc3678446e9122070ce4adeac8f87b5959189db3099b04c8eae02728c87591119273e30f7a02d5666093552ff32f35887b66daf533ab82c2988d4c1c6a5beea8a25a66d08982e842b471793713aa60b48e69c0a2a0e8e3d844e287e928afad1e0182897b98cd191355c60525231682082aec10d9edbf30243d076d89a3a9b10b9852f603b6d3bf65bddfc6dc7a9759f11715cca376b9d9d5e6fcc3b32da6b6e8e06ee65eca0102b82ff39f059c6891caab193a428729acd9d4bfc23b7c215c7358ca0f72994cad2a3636f244bd555399c8fc738cd689127fc465ae80fb0c14a23cffe05e490e4b2a671236d64b7f8bff6dc556664665174097238b61ba621725dfd58a6fa1c5b75df1d7ae3a10174c558603399f5c3ae3c0f5bf15a307d5c0cda1fee964db22b8f89768254c97efc8142d324820a856b1a00ab6aa4cea9bf90a75c5fd2f4e896c198a65b3b502722d5f5f333c066deeaf55da4814831026585a4bc27922c3f4b2b3c3ebad9fee018d5c454a7f491b09093edc15283e58d3f1f864030d00374f745f38ddfe4c28dca9943c9f5b9c3c368f8cfefd411a186a12862e4cc28f79b594c44b30f5b3dc42bfda773ee74147176acf1a2b1589a03243cda07a9508a02c9fa7d1b3b9b79c96b5c9f2d3f5ca7f88896a7f1f652eb43708d58a65b5b9db09456f17639dda832f0996f46c15450a8c404dad293f53a5b51b5201d72fd818bb8cd000d4da8c724eedbe4c84fa14f136676a679252d945d2c7e3f3349f7a94f9beea5afb334d341079e415b691620f70f04e0dd33a974800f6a81b68fec66b060fb7b80f2a6be16d0c484d78c2c88a129cb2caa3d7324d82d42a70328bf8262d72271a79dbdca2c1827292b4bf4c19cac9dd90b150d1179c4299c13a65226bf94e2a25cb5e0a44f2739f49c932539721a425806efac7923b20e85931eb52ebe109ff57261b7e026e0ed79eb54fba47504a94089da386ee0975c206cd5335497ded0352ee7b17aabefd6c059277c127397212d0a2619d6a3ac15409bf529a1bd7708ac51218b7ba7a4385c83bef7c2e2c74c0d823e220c930507d243a2b9a5c2bb944ce17ea5a0bac841d96af901769472de5ceb2dcea0230b7cdb19d76207f06815eaf8a6e9cb98ff004b23bd05db6ccebb87bcce47052c18104589be143e2523e7c3231874ab8ebe862ef4aa39405b87d512e4a113588aa315e72a3ca3ff692951a38e7532738c4f7de9f903bcf4b49ec12115caa2bbc765a706b32d9c3a7fda899315ac053a1b9f0882519ae659be9325bcbdcb0a5e8f781b88442d7cf0515fd7350887d2243e28e51352adac496b8d3f80a11d5de5f029b6c847d5929c985d20ba849ec6b29ff50af08b7f8936806281d2142383a45c1193efeb21f025b3581d5c3e2c2a025cb32e862812469bbe785cc8f4c9f5f33e0bf19827380f193fa5b4149fd2cd0c3da989d7130f4090fdb16813f460681c915ddec339f0ba91b75e50556fec47a8f3ff98d20132a371b23747458c783c76b3cbb991d5b0d65124b21711f927a5ad4ec04092a709b53c5057dc5d2996b50ec75a134e08ea11372f79ff577607c973cc4f8ae6a659d700b81d517a2e4c8ae86ae58e270cfd8730007945a36e2a961a9ad62e910a2f49302f72e37b5da2b96509ed24e34108def9699c60d764a048f4b15bd8c6dc5929832bbc255281f0d1b565b3437d8d5f79c38daa64e202f1e2486b6a225d84b6fc8cd10c6a1cfda57615c56b56493c513528e1db1f698336880c37dc6f28f003734293ec044363692ae938aacb95063b6e836df1a150ae99e4b620500ffc6f02d3c2c5ab76eca4e4f5f49be4b62b9d8429f7b119dbbe8f5eaf32378c6c974d08dfc816a3adebea48ef26c87f6a5a31ef4baa4ad2c5711bd842fc4f35b5b6bef928bc034772e29d7d12fd3b3b61cc39288ce02fa2aac3f5384d7795d09e161981bdd706ac5975169f5564fcde8730c8e9e333aac2f6b34c82a265896aa1f0cffa2c3922661c287dc70d38775a6102b0362580cf630bead344a7151c89a78b38b32e14e69e7e9225651d4d5e029c1e3ca3dcf644d57ba33a68bfeaa1a0774624230c57310576a5763e20461afe83d0aa7633b40abe05035e1227f91f23e20b2d2fb35738ee96a9b25cc1f2b4d5fe9e0b721c03c2658362cced80d794c73fe4987831793fe54fd9e8ceec69cc7f42d1ce401f9ff3ffc46a6b4a3def35dde3169f11afe2f511bd129bd3b98cb50a044d759978ccf24a5b2c36735f4a8d1ee949d3a7d2b2e6fa6e04c773cd9a9095188c777ba2faf365212be1a127917c8890e51b967bffb5d795ea2530742b83ebcaef03f0583b5a4b25dacaa5b0ec576bc8c8d130cb625a00fbdb9280863228a6175d163175dc7bfdad5c8e0f0d3bbda73b69623b29362c63783b428f3d7922a5199dde691332522cc5aef067f2f08f5a50081e761c4f2913036f95efd24994adbcbcfcc7b67f2e954c773e824d3d95f8aca04cf5e52bc671387778f56ceb445f671dd88f421e7c434b4977ab40ee5f5608f9b7ea08b23f2d870dc6266c3605194e09a9d6b9501d488008e1395ded04f2c8d0d744e23bfa87052621fedd6fbaaaca5ae1b386dcd2261468c7f76b2c289ea6e72743e3fb43f8019dbdf0b4504878d7b69528f44718ae4cc75d8038a4c2327acdeb3c6a6c59245c59c82f2120b46149093f23a21709d6c33e8c9df6fd25c110e7e4e248f7e96b1f914a6a53c431b0aee78db8ee06874dd6367473594005f6165249a87e8d5393cc1e1ea229a97170b82a5690513ded5b63712d0ce5d205d0024198f17a228d8f22bcec62a047a6fcbda1963d5d21832ed074457f62a30bd8f21aa1aad4284515117ff9607538eb51d51cc531d0fcf88ca9900bd28a07ef11551a114a2e691d76822e62a6c6274c8a57a33513f4f08d815fa6d6c4e69cb8c0fab17a8f8ddfe12c19a92aa13223213437ab2c1a8d60f4445a4017f2939598dd3a66ec29b695e89d79a5856594134095eca45f19823022a4c667b5791d28359c317c523090091f4f931db7c78421370ea8a7db22efe62cf1d571faad191899379ede566f02ca7d0c66215a9af4e8d8eaee682e4107545233283eb5fce62ce0149a554abaaf182ce649a4127df77911efb59e15b5ae5a98f481bb0ef1c99a95952def60ed376b73996903c459aaccae7f91011a30d6f7012136000dab754e0069367732339d2facc095341c57c88862ad0834fe7ac044dadd64adac246ff99569a120d11ade2f87666c8f801278075d0789038a7f70966c77d3e2eca2ddfb93524ba5b7b7fcfe082e6f3d6886b3b42137225509d7239e61520e98c0231562b6c1a48bfaa595b97eae369d9fb867b9d2b518b5ad89d95010aa1603afb5131a64f2c77524ef052659fb2d28398a995494ced22d049a22e76ea742391895acdd52ad57392c0b5295ea0f3188329614e748262de426f468b782a8be5fb653de30e49a986083863d93a004bce83179b28551d2d637f9ac90c28eeb637138b582491f55ebae4caf8fb76fc4614bfa141f62bb84b3e2ebef9aa8cd8b3479500e4b8c90ee94c49e8568d48f70a97f810b3f92a302e475bd530d5287bed51551d208922e93ae7dff3826546786d01ed0187a239ec9b8ad563622c4d849a7e5f74b7e1f2d1d13eecc1f5eab5ec6931d34ae1cf0649b4cf3086e85c48e6af65d3a2c97ce4b90c21c1048dbd9f76d47f3d77303a7d4d4228ed7329207655185cbd133a401dd27d6dd8936dae049bc6fb4a43a661700d94cd8ff9fd2a72a5a1037d5388662543abbf6855c2bcce3ff48a120208fa26491d12899d0c65882f715ee6e93f28bed2679a5aa7559b3ff172a82a96b1282f642d40fe5ed612e91680c93aad62000baf15bde39069cea3150173aef8acdd9c97784c6adcf93b5dfff574021eea110f85e88b446ac170a5293dd211ab2b12ed2aaec3114f309c2b4c13e883d91327ff45e8421ea22af943ddb9c214dc95a94a7624dc40faa378d1ec3c755e25c30329de3398ebe0327ee892ceff16b5adee16afc3806f8b6bd25739f23907521c06e90320bee22d3f754551f31555653323a9220070efa5242b183fb3365a7d8fdfbf8a95a3138cc71673b2432bb2b54ff07c12656c1bc10066a6a0120fd437e91ded602f30e13510dc192d657d4e6244189caeba892edda033d490732f8fd562c1f85dbe9310cd7df7b12cec8c21cdd4c9995de536055cabe1913b4d1d991e154b977a19512cdd7427b68c9d53bb59be4fca63a8b4d4fe9ea2714ca07cebbe733bf3550b1a9f62105ed08e7e1b329fb0a9c4cb7ff26a9470a29cbfb283543aef326aae261555569bc0efac348f9677f67df59455ac5755720b9d611d1b571db4b1086d59b8ca443fd1eda4197616bc8657e180ac710c3d8f21ba56cbecdbdfc4336755fc215f552fd1693e37cced71a8f242d3328456d0ad64f2ced4c33dcb08fe1dfb71fac591a99c151c8fc9017b175a21a04aea05ce380dcf14aab1ecf42e4487a57329b73f176c9b87ec17108fecd0480ede99343ca5caad7826f11d8903cf63b10a1b5c2b917ce04001eee1a7fe8aea78e1c32d0cee443b16e6d785111ea8813960469687e1eddc4d8b979be86295df22e6918045cf3e533e9f93a100ac582e3bab559e8bec866405f9d55369ed8768448232a148336d1253f6d3debffa81da1069eb1de59a8d955f8e00ea8168ea8658c16fd9ed04892e7af2f523cefff25de209e56e9c3b4c448e7e32afb5718778d306a9bcf61e6677aadfd604f15d433a567671d63f43019e94204a550f4b356273e5133f0aa468623bd8944236aab1c5e19c449aeee9c620bd057218a9c6cab1e1bf80d0e8a6ebe48a0eb35204d2e951bb9c752c71acc3259d5567c8f82babdeff387965e59caf39c86c53ef1f400480067edb79b70f388417f09ee989d306f35f8297908629f13459d2094d4c45f252fe6eb9212cb8d77960191d71191c03fdd7aad5d5aa7afa696e89c99d120179732aa8a3e6ac933abfae0606fa0e078f761c1f09f6b0cc64f0f8e167545bb22232e773fd268bf9695837f8ff3fc90e0741b4fa71a50cff68d9e460e851089ca1cf66640038782fd8c80738ec6dc1855f74d0cccabb4d84e5f2af83a98a44f2075759b5c23745d0e33b730cebb73ab9ed8c8c6e0bdeb4996982b13694d66d6bd341074e5b7e56a1466e559807ba23c1a2e2373b6540f3facc01aba690805205a8f11bfc74d4b2701688baabf495d5b87938f0baa45fbac3736f024880386b85009edba18285e07a8b46d7355d246a1bf846934a464a611e3a230512fff853999127e3ef347f88b0c563627502fc3de9b11a4a80e01e14fa6f1673c824eefc0b7d3f1aa1a8549365a63a95ce8bf166b8c8ad282a1d9a5b06f0132059517569a5a8a2f7b534f822c7315536b0a88a930a3137892a6a06c86d2522e013a4eac9832fa08ac24e462417e40a005208eb86b691d58f4c8bc82b1b75fc6d07b48f56854db1fe061ea5617dc1cdf3417179056abf928eef12563a60360d563c9daf969cb9737a525cd4d974592f16594a4375480c2eb41978b41e03fb48a88c0f2daecb9376ef900d4d47a7dbf7d1e3f7e917f889a3b2b4710e595d386b1e1a0680de56021a9487e3f9c613075d839febf48c5c3744ec44fe6552b11a6fd6fc485290145bd831a9497570557013b7067486df6ac1862cdca39fef15d500b7e28c2595923735bcf4e3ab95509bd45e52a4bc3b9ffc38d25d3e27854a3ca8e4e26a4866eed9c6e3604fba0db65a6c91029707e0067163fb52f72abf86deda90c7de18237fd17660ba92fc427806ed598d4a3359729750abf70645f8d3cdc5634d60eb338bc2281bd3710fbccab1247cd3eb7963c9c46820da15cf66e1fd5cefd9ff7f562f4a72aaa15abda7be8dfda8435662052e36ce1d1f4fef6c1111650267f682402ef3f9b97737c5af86db8c0c246ac0fc99c43995eb8cb5b4353079463af2a90b865c2041a111916ce0830b195f0a00874912f3b771f39631836bd29057b5ad4dc71ab517c396cad82e9d31d86a691e82afabab71340dfdab0b2677ae437ce823723902a3015b00edd6639b1d36f669c67934554ed04e7beacdb6372fcccea641ca77779ebebdd308ef4c764ab61f71b27caf467fdcadb2d36bee49f103c4df75571b4257c2cea459c15a2bd52f2c2bd7df629ae20c9ab853f25890be9dae831087d2161291cccc48d6bfd4143d19bad2922a20ac0316f29b277dac55017002327b050ba836131257ab0db76aad90d2a17183901b325c6cab9861d6b593aa447dd3ca07ad9e4658af4c51d782308b1f83e892b04c749ecfae959d68f12150cf309f90e8247eaa7234b68025fa40728422af00ce7d27ae78c646ddb0083eff09f72450b0cafac998427f618cd1715ff8c18ce3205cc47652c2c993568a40345f5eb72779b0004fd86eb2c1764525afe3d44d1e5ef3add97eb3ace00e9347afae4cd5f9511c33f807362e6f855098d3aa38bd3d26128dbd8df44bd80fd95ebc643f328a62e1a2f2421642a0c95a7eac4e85ebc41002fa5cfd2f7b40de7aac2c749dc7b65400625748c463add68c337ea116fe396e13ee1ab6ca65108bc8427dc1ee73983942f75538651245b4b6ec931413bb4c13a6bd514943fd65dbd27171183f6aa2127c8449369d30b6f87c8142f7be3fbcf1ead38ddf0c6157b0df3f852c1172663c8de5823db9b351429a6b176687428203e0315bfde28dec6596b15ed03be9c8b32fa4616dc8424a332aa680f9b53a1502df5ddb13bee822c83ae4fcc2f332e1d3a57fd18a5892b6d5b8639bb5f1813560d19240c3b4810faa1d8bcabeae02916d83b83474920a261d51bb09a8b871359647d173a61cecc81336c525edb5dc713e8d37046da3dc58687a93811c13bc4c0ebaedc5868ae89f85b59da4b02d751af3e7986556a75119e41753d10ba14a1116a127c62bb841a668642b763eaf92c03386436e1be91e3d10e6f83106a446e96a338e2ea698738bf6b8b34349e56c0dbd9d4994f4633ba2962f0cafa978140ad480dd24c8a521004c373f3797faeb90e1e9ca4b3e4b5c470a39ceff250c59248714e0d0d6e8381986fce4c510c3008725e6c1e6b04fa9dfcf572e5f1a120721472dc512c74d2ae082a4942fcb8dca9d6b3ee1764a0f3c4c212a8e30260f3d098575b04fae84a67f97e5fe89ea784a0279fe26f784848a2e4212b492bd6e24bd4cd5c14d0cc2a3616202fadbc1622e94bdd5d7c85f9b88c9a0e8ad273f0591c67ed80e626bb1e9135e137a0a49ecf77ff6cd310973c6500ff9bad5b5787b15e49179987fa88f077fd12e32e9c04cc217b587fbec7ec833d02a131e705868ae52eaa64d83aab95dd497c681b0d2c7e502d371e776cb27038fa7b094286daafbadb96429dea74f4d8e8f8e3df30e72dc6120509f172383d77d235c20373565d566be43eb966f8121fe23cfa3824e1ced9bb1091631be68f79f7a0670446479b462817c02eb3c32172d057557dc3172b12dd868099eebafcea2e6e738d213108a4faa0a7c30d39f5e9552e42477fc0704b9314b14cfdfdbfd6854d00c2d4b08cf33c52caea7c264433c68c41c6e80c51911c70e93f9c6d338f50d19965bbbb6e6a37167c0d70f2669d5d2feb413f7245f90ab387378d8bee0dc4a55fcb5760f235d0687b51b9e2a66cdb9a2b76520dadc3b58b101da6092bdefd940ede63791edcdd93bc71e82ba79c579e631e0760cba65f32417d977cd339660b4ad3698cba07bf385b0235fe192739f05167bc26f23b33cb891db00ab20dda7aae49d8eafb080829705033f324db6eeed74e327b98c736e8d91ee9c98ada6c686ac3e406771b287264a3d0e53095b835c723a4dade439d8e51682676ff8860b94499e1407dc1d523e8640b7751b2a0fd376cc50730cdb65551bd5f64dec28f7179538337346787c68e0ee50a240d563758714b06ec483e2486885d943d65ed65292434b69ad965271caf1f97d62380345602075bc3a41419cfebd0d88a681c4342b9b7753a3634fc19d561ec449598621227c032591e40ca818edcc5dfb3aa86450c4416c3c92a20f48bfd1421a9b208f56ca8cf8e58278c6077a3f10ba37413eb63783aaa089b217e755692f1b0d1e6d7bc4455153b60d3e8bb420968181183967b4b942fe0db86b1de6a7c1ac63dc9c4227a5ce1001950b6bd1c65cc8ad1e67e3d664a06bdbcf820e3b473cb09567a9d6f93bd24be0880165ecfbe206fcc5fa3e2affa5b682337a7c16c377e9c65500a7119f621220c68d46d55e57975a25b5ed45184d7c6e1cd95432db77667cf31cc57f6f2600494c1fe51be80184222db904ed2861a59e36fb1ace1ae02c068250ef9f4e51c2c235fb2f94b3b00f7f1802f962578723dcede3ef035980bccb6d38133c7936b2e3eb49ce18c9f2c0965ea153260e59136ab627c154b84c9403d45cc58e882e1cddc92add480a26ce86b8372848c3ac8aaa2e1c320220b52a8eb08eae20ae68aac994e2a88824a5eb791afe1447d443447c15852a16dac0f102f6ec6cc8832924983f4070cd22551e7ebee86646707b7e3b1d7fb08b816a7f77a68d9e28ff43d3264efe0497057676198446de33f15baecce729ccf3cbc77415a5d64af8d3a0dfac6d0c042ec1c811d7ec4e973049dc6a0eb9e0b04c91ad08fc7e0f4c08df76e33986c8da0c7a97eabb891fd996ff3f02e104b5dbdf9b7831c0326b8c48956b200d8b95ea7fb30d3f345a0447c39a3a5db56a664420fbd6e4896346f4888653f7d72e813cf8174b385feb65679c62f0f95d0fd3f88c4601165cc71f47d43f3b801a4ead5876e08903b2f523ac97733e25e5f5e74b757406165397a0045ee3b3eccf8513ad704a08c732c921aa775c7e36c469d20e97947026abd1778493790367dae22b9d70e04cc6ccb76b5faca3e5d6844e0bc50b7c2c910c53cc872cc5c010874379875a83cf0abfc92e2c4a57b644fa0ab02ee43c7e8cc328c8ef1cdc04bba8a29c0ead76d08963d5d089c0dc31aa4968d634f6dc803b7f7ca42fe10656ecbfef6f7f98faf7024a6d5f5049d3d16a97e23f366243806c58119d2b20188d0a9a209512ecde8fb4d6a1cc3b3c12ea4980a19eb3ccb7b53ad52209856cb5ccf01b73e420ec7e213f24e47bd9198f4509f9548ed496250749f49dad347e4043f6dce0b9735826d4a554fc043be7dd4d722b61aa64c7ecb3fca0eb59da5ebec9664b5c3dc4755d2647e4d41aaa9aebe9753804e38b5028096f964fed9a5c86c68c642a3b04a10df45d79cdc1a4641170e84b07d7067b07efd713c11c3c08bf401cb185ac46b4d4f50d69c5426f180f5ea342d7cc38e214d112c21a5b2affcaab8970480308cd3289c468b83a4f5b8ed822e2c2bdadb072aa5e8e3c1f399ca90d1de3f4cd9a00d474ac42e05295c43e51cf4320db89f78a8a59caef7f688d9a20f42d20065d9a19d3db3c9555da4680ae3562a7e2b612b7d7df47d5e447ba40e166d365a9a4b61cadbf99f16c3b75793b43751df1fd5547fe2fe44ee3824e85a54f30d0fc0f0fbaabd656a87a197625e1724737c18017109960a68f042148cf554615361fb46909140dd9ba5e0165279211b909a8d0eae444f9a099751d0cd60cdfe4b604c26a05850d247587165af630eb2688a9bb0e86a23aa7370408c4fe739bae2f1a63ac329eb81d076a8ea3f57c124d6355b44f35a2875cf390cf4f62a8bd1ddc14b825e4bb18a4fa236a949cd991efbd6651885f9b0caccf96c8e504be8cd53c722ab890870816744bd8ba37b25b59503a92ea64c13b296168c9d25de7104601d726e6559e9eb466d1777e1f79945b63eabf6ea511694d22d915b4313b9fdcb34ad9c2d9d5d450fcccbf82d1431bd991f9dfc05cd95d4fd877473e8b5614859ece1d79584ee30ded1e5676c76802679b69fd6cf9c9413feeeec8fff29c5b3dda9972977bd7c096cfc83ea76475790f04a3de4657ae229b7c225facc13ff83a6599aac01e25a8697d487febba0df9bff4339440462996045cff0245aa5c7d69b0dd04af41d0e238f92be64bfb4624970907077d2029330b864ea99315d9811858d58ab58a404bbf5485161a1612bc47e6a0676e451fbd209cfd024146d456f39139b50cb5f7df726be05da8b039bb246307542316fdea80fd80ceea02d0657ce2028021626f9abe05757bc020cd009a7a764ae70e82a917ad45cd24a193cb048c8d14be9445b63b7dd5f2fc084f309d1bd9b19d093e7b09b5cc927894594ab1a683e0d2ff510e7c414e30036c95d308c5a04f88036606ade109e013e48019612b30097e425c282368b5db048b031f201798095b0193f00be24099410b7002fe0638206686e358edf16bec1afd8d4ee3b7f16df4373a8dddc66ee3dfd869f48dbe46af1adb0dfa763665178128689b75b0dde187220af485a14dd085f09939e78cc17a9c3c7e66ce21635a4e93d39722d798395b0f13e3e7c89d330d967372fa9cb863c6d1324f18be47ee9861b60c13e677e2983246cb71d2fc19b8c6ccd162e6e4287d2e73206724d8f2a17f52f89a38c6ccd1629e18fee1e0486e091b4853770f65f1f97be0ce8c9375aa895121d573ef05cd9e76df3799ba14a82dfc5053b97d38009eacdad67af1c8c53e1d92883a5ffad380b420cbf229f56115f427e85bc3d2c87f7d1f293b507bbccd5667587ee23b807c1d60933f0325954c18f2ef11e725acc54a522370da8206f7435630a88d778e2b60aab653263f866d7211eb82031e018b8231098cdc8962bf5d82a539c7ddaf7494f2972a24d1c04eb3012101a0f3043e2959d42764987ee2f5d1e11afa4ed1dece78d48118c83b8991810f822692371e713fe66b6b2912e3e04a4fadeb90544590d3e7d18581f773e1e56f61641acc0573de12d6af6a0d78a59f4037767d25e33606496cdad5a83d215993fc27270dd6d22ba7927499afe0f3aa77af935be6fa7680965757c7cbc57c42cee0ae6c7833cc01d6b72eaaae5180aa3e852b02c973a85c017094b6d0f05bea797a0247fefa4cc50a5dce370471b0d9f6d465b6d002af78089e6aaba1dd5c56279b79c0357350f8a2eee06d2084140c4b1d805eb670f6d3bc43f1e54d8b391898aae102146e2d10e5e1cea833c895fff409fab2b184d9802e5cee2483544d2aa06975b483180a0f4fcb2928ee5ab0c1c775182f8ba15a882bed8c18f54483fac31f9bfd65d67c7d3c56f49194a88e742f9a92bb641c829a6608558975106ddbb6dda87a00ac68a70d12e5e061d2ee0674031d836b29db95eaf35676312dae63eab2f5d48c9ab1840e06969e395d80ab766927e3475ee314249585a80749d980c51c2e0f1d74d63b9e6835859f47ede59bb2c80814173d427d94d84741945cbb4f7d1a2a3bfd01fa4b512d40e1e36dd8c0c05554378ca76f78865f39458902a6f3071bb5fdbe7cd5f86e2511f5b77b2efe85eff72f5f65ac020091516dd31002f052b539222fa2f84127c6ad675d7b505b1621173952d648942805f969d7e4a3f8bd9cdf338b5f42f5f39a80e50345f3dc5504707b78ba7328379374be3c4344101f7b24e78ca78f6002977546776606c0e46db31b84757f95e10959184830e28ce5ab43250401e3b022f7340dda7db83bbd4d4e81456f6f82d1240da1cb35d077cc185d141b1c7ceb204581407cc6a52c813b618a04c7f806caa8df3d09b04a1d1ba57154867fe95318446e12aafb1b0d77cdc0236af234f7724a65454ab80c144f3775c92cc15006975936124b1c7cd40cd6c4ee3013195452dfa1341cb50228cfae44bd87812e15a0e514f5ca09eaef397dbcb84a35220d686f59b634be8810cc306c03170223c95054b99b8f33c1b14b20233cb3df430a868cb390c1ec34d3644fbd6bdfaa555697d7cb5cb24726dee2f5ada3c4da530ee15a693ed31aea1bf3a5e937adcfb1d68ee301ef9e76773c0674bad3d45a00adafc360214523581fecdf8464a40481cb22d541abd2e40b8ef0c6b2c64f2bd60ac3953522a148321ec7ff786a5cbdda9b39a791e09dce1fdcefbc08bd787c5aaaa79ca9602cd6e0f78b959163bef69198ac99e8cd7f9a8db0ac46df5df67960829408e2fd72ab370d786214cd223374d57cddaa898df01f7dcab64767004b9bc907a697cf090f10447c46b41506910d18ba78b4920dc3924dceff96b412bf23688150348a0d51cffd1d413b1aca1f142394e3afcb46b02a8682fdc88b15ac753d5b6f2163a9e5b1c492861478723700fab7aef7f9c3c7859627491a888feef4943067df6c42040a9ba8c93acdbde4bd03921b6278d39fa69e13dad6ecba0b7464c6b70ec34b93451b05f4c4600098e8b600b4681109a2c7340ebed12e62b301a936349fb7dbf7b0e4e7cd1ba284a71797a4dec830c731b37cb65adfc25e8fe8b27221b9debbc7084b026a1d9f53b0cdfa5fbc066d5d2d97311b27c8ae715085a4cd42080cee17c742163be37a9c00495978f9124df0075a2d8d77b3b575c96568a64a454c20c733bdaa3108d771c809e8d4a577d566f6e071f2e68ce086978f01af5eb88f80c990bd1154f98c607d6944bf6ca5984a8f4298491569d55942a62734836f07b17303723ceb98880cd87f3e0a82094cab9e73f7973ce217cff33510839860ea318856af9d58f16ad101c2a22d722e456f77c5b5dd27c5e22230a384496832e095297502587130b11645065f821443d336a74f310b4add2b779c18afe515f513abb14f1aef373d4f20532f612bce97183c56da0fddb77ece6313092d8fa1fc45bed5fdef6f0dd3b68b82024aaf760625dcff38a9a49c8c74d0cf8e1e574ec4e614a12a01905863b599398275b16f12e3d997c5049550196a893bb4a2ec5f5b6eff2bdea83b986d1e7a94145cc256979b5f956bcd9767498fa281c368248c8ec512a762d76aaef4ce388c4527d7c7b7fef2f5ca4de219be1f66189ec17409a0818efec1e3b1dd77c15a05bc63d0031041439487a46bd48e9d05a352734f3ba923d262d0af8a69ba2b4c1cc58055932742516311aba55c8b8196837e8622e1fabcf89002496e234019ed7deb3440a910209f8917e2ed152aaa86ed67618de87795fbfa3cc32e15aef20c1c46cd599cff0b030dabd2daef1df37ad186a75e2ce2addce2ae876876c3c0b823b30515f434243ce88baa3b9b881a669ab4a75630a774227a03a866ecd04f178be1122d2e99b5e00d5e0d343620347ad070e341209ea49d52914ce47111c809b802aacb43b88bfbb9a568211df44629f642ab2401623ba348d3ca60ed8e619005e1b9290b08e04b2ac7213aa7440a30e903847d6eecd39f7580eda3ee9dce0ec220c05fb2c51dd34725acc8ce054226c6426e942f7f53f24efc8b1a4605fb4e6b86cb4d6adcaa8bf53a68512b1814773b77557d8f6f32caa4370198910a00f99e9382967342e214d981edbdd03d83b91513fdf576152a2e7e70500c63f1ee95962dfded85fa3047526fd03398c5abcf3a3e1f02dcee7dd60d1d2d0884dd6631de69037414374dc5667e1180790e46449e10b637610e78e4600cfcfd6c7e1207300b0e1dd01c7a1923269269c866f8b6f075f3d5ac56904516f9ee9c5937a2002e66a0ff7c9c4c18bc9950c9a489974581e781d2779628435592d6f4b6b998259e910b67df459514a19f9445601155232449e94d723a6f9b92ab9d6727430c99c6282bc79944a43f2aa476efabe79332e9baff489d26676a4ef05f07964008361b60cfb10114efd31a910886d93d72ec3605762f722b55f26eb894e91db206d060ee4ea8e9238b974c465cea1c14a6fb06543629ee1165a34ce39bb1ebb617e96d80219105c3d35e3ddd246e2357a1c893e4f660cea2398632d567c28883d2e87d99baba04af7bf04542c24b2058ce767cc05232b48ab9d61eece5868b05533401dc2ec8bd7682de66ab711e89848bad04f56732803b2c3fd76080f299e7224e05b0a9fad8fc250c608b29be452ed8d8b6306556acf9f389a1270836a04413609a1d1ab6e1dd5d9d235057235390fad6faf8e21fa6fa9fabaff3fe86ae8e16ede74fbbaa32f73ad664d9c495ac98eea0e279ae266b9be1be102fd47c6f91570a91938ee7d3b926e76de3f3d7ac2ace80d4cb2d12a735769c2113368f28b44ffce9649faedaa6b98b7469a2fd3b5664608d66d6154ce85e4330bbcf3abf9d513c40d3d9da9801482019abb7fcef4e749ca616b0978a982d3e9c866bc8dd77f93f510422bbf4a5d47502761436d0d6e951822e396c3ea2f8e02b8c2b14d00bbae9f9cfb97c9d4492166458be0e3ca2e31a663b048bafc4cab50015039f67ffc3ebab48fd1ad68fc7106b4f36dd6f69e6b75264fc11c5b0c791de3ce77f9ba2b7239feaf4a87f7afc0bcced94842ff29d82192cf7daf49dcb93c2028b56ca886177fd293591807e18384558ddb900316b04ec3d623754250071fb8b6af2a4d87d18fedfdffb807cd408cc80f380030762e57a05d375b465a73f5fc798c68f54b4e809a4cc519618f7af1a468e126ba73d1a07906addcbfac2296f0d03de079189249d13c7377b9968a38ee2e0a2f6413a0c8df57327b2ba920f7595cafdb8fa79b58ec9a0ab4f5604d95ad1f697ff02e33c5f76e615fe588546fe0645ba6d1af5b6ca803184912f778b0db503c8e9b44ab8f98089b7294ef0ac01a60a4f1187a34fe4262909f561fde3d3ed1e6a5283c15b9964b198444d106e50998092d80a547d492e12f41a0c023fdc80ee111c946dc629081a3cda10a3b1e85c424ad107a4f6dcb99cd9811e6b225721990f41add00ea8b35b85387e09132f7e05e3a0ac49c9362a2a56b63d10f7c511b4c20250e9cde6d876114608e5d05eef5fef43caa203487dc81f6b805f97c80a0569e57b159e399600614a1ecb84c013930e49a7f4a9294f5c0b51e682cdb43676ce46f299b344ff2a8ec0b8dea68df35a3b10d5a5498a561a9f73f8679c7ad9fbf213321144d11ce971fdb9001581a8274a7003bb88259a7079903779b33dfc77dd5e2c4e247448261498158c4a6e2cb858a29cffdf2449b5f66584447d9a0381680f490accacac57bcf6c0ea0815357ef47bf7427a59d0de069675a8c7772d4ca9b6f6489b1efec5849c33ab7fc125cb50656e7ebecf740ba9ce542581c42a27bb853517d283e0160e445096a44577905f4b75056f4f86ee24e71e7a26fcd67309053f7a27cfd25d5e25cb7e152b2593e52db08390b96e867ab683cb6bdd64de1fe0f3d226486d5260e21c98a3c1fdecc460aa4589c9c7e2fe5c773c307d0a126c0ac11a4eb59e36214f7d9cf588b5da94fb963e3ac5e1690bfb9e794fa207e2a37bb1c92da3e0c611e02c73c9800c8abb6e3e96e9914e449d4ef6a1f531cb9c04b8bb0254dc6b99b33726e85d243dbe6afe96bddbb7364db8f90ddc2c55c56b7a8787cec8f99c2fb3016637cce6cd02d4c8edc45dab90e92470757516ab626ba967a81fd753c543e85630b70d7af61653abef4c880877201e7686d60c24773bc15c9a8fd5b4a2539bf3570ed1794069d31a044eaf39408a99bed0502aa6de4356809527399936eb60edc68b1be4921f5bbbd45c5ed17540886310de573e9024bcd290724386bf95602391dbf5c2a3b3c232d9d38a67422c80dd2a46f04b2baacd4b89c52986a90a5bb3a5e41740331f57e28f4acf81b60769c1e4302319c427dd1856f10704a8457ea09a50d7dad41ea1b7cbfbd428f3c7c2baffe07804800bcf8aa33785bcc42c8c910bb16217f466c237eeb8c9650d606240dd641329c2c4a641807a5cf4af5c070b261e4e3b3db17af687e2f946db01cef665837df1cb5ae982815b0e072ccc5eb6ea4820a553460be7344177a439c41d4eff30d84d8c69b360cf1dcc3c571b4b37b685ad6af7e7f30a7cdd4bedbac884bbd3b2d6eb4dc4d68779abbd387eba064596bba0c1765171b336d0e6c69a4be90223d0bb6f287b517b76b7e1ab65690b6e9cb6c2a71722ee3be095a78b3d4187e205c97e13b2f9212549ad7ff06a72ff5d0c7755feefe737097489ec6006b38a3414c8a8993f9f10bf5a5a0e9904edfc24584a06992e6de2c6738370cce0ee463df57aa4b77a59aff5ac9ed54fe137e5c6bd996ee8e6546481d5baf16fce1b74e3bdb6f7ee656f0aba21de6b1c017d88b85367f14f656a5b0bffda996dd180b98fd1d16854290a47847a04939bc1f9f6081828da7351104faf120eb2338274de14f873d7a7a969857b31957f1e323c82184b50b6abafc0b446889e2712db8579f42dbc752eec51e7a1bec5de6a4b7a27f678dceb3128d7b4a897e49e75b84352cf4e3d9374aba3be893def7c9f20fddde25ec7a05edd92f6b385b3eee562c585c2df2cdf2f41fed0d2abe7c2e2fab2e297e15907cbc50aa3f4e3d27d89c21f593eae1bf0acf657c5ab7a710dc14a736771e5cdaf90c060a5ce37dc5c518c66dec5952957538a577ba599cfd2cc4bb43206654af16aacd442b19a798b2bb9a8a614efed95373ea3999768259a942dc5bbb1727b60cd0e45375756db89b7f24a54fcbff2e8e8028d9f433f905edb8b0363fd5fffb90dddfe47466656dcf036f171a95ec15a377913e0e325eb8a5517b4dfeb54330b3c9e039aa3aa855b3052aa9a74ea14ae59b46ba470d6de23dbb6e9045638eb56919daa4b16edde55cadd2669b5a2c429a0621a8c1a841fc2100400529608e28a8c5c5e1ea20781e27fafd7eef55e12c6bc4fe8b1caae37b3b6d612a2d636217b6fb977e508e4071a08d3d5c93063a6f70428304134ade323c3ab90c4aa726154ac90fd26ee31c4ca02051e68c07c56a746a353a650a1d2f05a5792fd581347f922b613726c3134193e653493647590c28e0c9f2309f6abadec35b9d845c49f179b712d57aab7c34b21c37755ccc8d0c464f8ed03351226c3a334f003a10e0a199e7b7951c67824938ee24ffc9155bef803039213421d9ce10f6764bf0afbf1726e86af49d8afaa60049d3c8112447342863745f9a123c3bbd6b0e172d4ab65b75981ab11486402f6e8d33bd12cdbddc0cad938f1c795e1332e70b82a71e28fd59125fe1870f3848f8ee943391ec77aa932f48c92c68606f6822a00e00a226fb644596baae596ba6a3031352bc4d4acd082878367430cac5981082d980183e3f52b9b34934930482f47469c0d334a22986143110e36dc60030c830866942237ff2cacd79ea4b36389b2560abb59aa5a666a1f1d4c6c73de4c2980f6726d75777777b79431a3b3bb71f79d73c6ee8ec51e514a306c7c3f4208514db8bdadd37bbc72856d430c55518ecfa109413e44987ca802473ccaf111c9a7a5813a56ee5e72779883dcbdeb7e1022773f80e1451047b0c1858f4f8cdb5879dba8e46dbb0d236f3ea4206f3e4d70d00417b090b5c336d0c828ed912965824fa64cb041a66d84e00a0ebc9a1fb478811442190abde4243cc9f2564a964b4862038d2ce5490a1940b104a38d04d5b26125890c18174c36583093e784a922724ae8000e9450865d2204f2473a401cea92e59328813ab03f293e225ff211952f0db046fe88c421007cc93560f2d540470d24a5b5ad96ecb70e2c0cac819747f2104e2c7b0b14d8efc740a687e99b4803b38940e4696d8f1e666eb6daa3ad4ce7040206eac82f85dcba17038170b1c7bc95f1e6c09a590f03759460a58c929bff4e799e7042ab65ed5773e2ec0e869929868135b1f4ec26f172b64d579c2c5cd77ca90d3b7f8900b9d820cc566126550b2c0e7d13719ba065467b1ece0c4f3ce95c99613fd86bb5b6d592b03c3529365894bf24b0cab550a7b521ad8a7603ad8a5625571f6d48aba255f1628311484720df92675865fed9dbfb0f8349b0b6d24674a8854025a5437428d3a436a24374482bc106fb86cea6d995f436195ac928d7734a49a59452ca7e46339a6536349b369d4d2af31349da858c8c12d205ec66dfd992ba9c4497a3f6baeb4f292b97cd3eec322a3749adc8ee6e39a7fcbc72ba9e645795bcd3f2ba1c7541a6eb8926a59cb3bd98156d104688124883d50f5e6dfd633769ab924e94ecd47c581626d8278cd504ec8985524694230e803a992764e8c29ae8cd63aa0242424242a236dc0013e20398902a3f5202a56f909090909022ac616541ac0c3c071c5720f08aa214a1a13caf0cb1c168a9a252b56aba9d97e23830fd8a515830b231a2318ab245571e08a2427a91e50d07884068769a8e3d22cd545f1401348f4304d234d3b2c841ab2a14d4920cbc52d5aa66da81fda051a402eacca01ca240b1816ed92dbb65b76c198dae1a72c2e0c0e0ab6f61366258179113904498bf885d432ebd7bde943833d6dd46466b7777d3ae5bb7d464679dcdaa6514d367df289d31993541d5ea49fcd25efc8095313c0476ce395bad5ae984dddcc056fbe6dd314653de24d7a5bc4d4e526ec21a9ae54de3b2ca69336f25aecabc99b8d2c699386eeb60e84f53a96ea7086be067d10661a6076b20aedac4301bcdfe699aa6497a654728259bb3c8890decc0892644232072e287a8893432972191134300b52a154750031809694058102106900d8608030968074700418a15238080046486116140e9170c324018525a5fb4b42108815404243c1a08f10321bc60d2da322412628bb90358cb22592003322db0801416f0895181dd32240a228d9717b6cb902888a3208c0e932bb89665190f8ca094d23190fa05c748aa3efd823ffd82325158ae06444dc0c0082b55e4cb5b020bae341124234413506cab89272e2840c05a9adc618433a0384a810704bbca70e88c19c83439a307af32ce08e3c76e190e9d518433d238425249ea17f4f9017232001b3ca12f1f9200449524606087164970923d20609905329a65b406233489126589295714418a200769e4e00a1499cd3ab0840c5c800552e4600b1870f144e3828b2856605944404de45386444045e4cf7e8600a21184d08f1292f82162090330f1092288688827800c1044510928853434308411c4f8c072190ea521254a1a53764c61b30c87d2c8c11a6914e5c0b41ae3bd980426d7971a6918e1215c202221a55f5068a84a919101c038a23979280d339a20c1450f9080381a52034a9e191201c144fe9e63d6c46b85268d6a9a0788d08630541a011199107caa0a8aa05682b42b8698568a321a1021855045be604dd2c18f5012183f0308824f52108418c0109eb4981cb1321c4ae3072220903a0ed4564ece6896d11d32b02c19121921450b234600c508275e51ac29432223909899c2d60c898c18228527b6942191113e438a48aa7d038980e0402bc321352c906986444c9068b083ec8b21542a08c328096900d10408d90b8260d37082524a29f5a10645449c0f42d0807641240411911055e45586444240c9c0061be76bd958637422e67863582d43a21f1f3c614b39466984221aa29f2d887e84b2cd90e82708099d26c020065460618231b6b002762101318020c6154a88220650aae813642ea0598d28d410418449b135c321353a60a5e529cc73d639e9a42d3bc3f0cacf4b7b764398bb7be291a452ca86f204310a6a6db568b4c9df12aa840f980fa9048da22961e3db4a53ebc369b610c6186b8510c28cd65a82b07ea74bf7eb2157ac69a734f6a8a7771a654a9f4d082b841aaeb7f488187494db89510c3a3a12231e5589c10e9c32d4ca964a14231e9dec91ccc596b5167f47e8e930ebd262eded6157e4845b495aadc3c0e9dd5bdd3bddfe74b256d5a24552743b9db6abeec7ad4edf4eaadbedf6ba52e75028570a0f3992ed37fc1dc991db56aa956d657bc2495a79d5b276b3276b397bb2f69e77ef301175d90531ad5e254e8bea36a53aac3dfad445fd645d4e27a96ab5be242d7b249fbe42a14ef4747a94cac4fae9aeceba4356ad2399a6ae32e1242d4a8f504a69abd59242c3913c7f4f4669e960043a20410ebe324116ade4562283f84bc2e5c943f6194f5e6997b48361e2af475e6d6e938bf2ae3e6f5744c5624d6b97b4ecbcfac7d2e4ed3798b5c7169c211e8b1aebdca7655d6a3bcad5e567afaaf638777ade263a224a86441e18ca5ff421f2405046d939ef1019a1c6d98b8a1135c48b11e3af872c53af29db4b7872fd112526539b5a08cc8d75e0c94b622e9d967209efe4d4c07ef028d325ddf23e7d9276a1e5f44794f0b0b9b0d8b05fcd137a347449cb9123fb459fe8d2e28297b476a54a9c167c8408fb4558e4431bdb108158ad830748d954aaa1d6fa392b2cbd5edbd0b0255c85fd603089e3daaee5489caae10a68e7b473d8b51d265fa68a2319b0c6749369337da7ab15ebe1b767fae5c242261cd668d8e9d4116ce9dbe72b761160bedeb56123f334ccbb4c97788a02c0189379790ee7c897e93058637a16b604435ac24876d305c2f4cd74984dcf3293e9f14a1384d2643242c34ddba0098394601f83ba54a511cf8305b5621739d6b05f0705b9b8b05f0cb22252d12e98b029c3a69214b2aec0d22089bf28f412d4d2500029cd32186986a70eb209547b2624871cd8c6a15a39270b8311c2881f10a3917cc9d2e3c53da55f42edc94fb902cba47fe44b66b87ddab30200f892bf425a6e47ca1848338c0e2437f87a81a82fc5580f733d84b53edeac529afd19968f6cd8cf47218993235ff252ca8db2b0618d5a48ee0c657767a8a83d9823cb1af68b463627e7cb89460be897bcec97b4f25808b6d7b0972b38326219adc07eb625676715daa8264cc861613f2e77e9f51c9c0a308a9beab36f56577d536587dc94df917d6b3abdda7b755557a6306c0f6733645f222f12ed11ef40b3499b37562b1c93a53a0baa57f2f3f26ed9252b6a3ad4bcc34b86404fd62e85c0ace1782a6fbab2934175d3a5c49bfdbc29752d85e54d513b2755df698be52546a2ba09236149bc446615ebf5dd11c95818ce4b16d4ea73b58a744523356997a7ab29f134e16fd219e349c618a3f693fcbc277b59f1d7d3b7f6a68baacd5a5d1586556ada94f3b0a39485e55feaf5a6b7ee57b36bda670c167f3d64fb1bf47e4a3255f19055acc3c669b1f0973afdeaadab24abf08704667b61961d0c31b3e01d2646328964d7aeb14e57afa7f67e3c6493bce9daab6eaa3e753b6b37c99bba260cafd99fbe25d2528c7a4569d9e3915ccfdd8f875c67d681874c7db2e9d9e9ac37ddd2dd81568c645ec21bbd7a02deb8c2de6897da391b141081c01ca3ea281c2916d1135572e9b36bd7ab7a765195e5d46ae138b1a42a4b6618c95ccd194a574d24594eaedf2267fa2667e3c0588643eb829c2e0fbb19703cdedc48bd85e5b55e53ffc63c0ed527fe9278a8cf241eea15856754c9d093f56c62d7eaf328d5b3ba425d7595eaa2eab74e6567611b346c868ba48ec2486ebc9378291c6f1029ddc02d38585866df49eb1b3559e6e3a90b72fa9ca8393f3360513f1d09d667f774d6955d90d33728a5f4b2d56a7559575d5befa26ae16869c1d1ab54b7565917ee7476d865a5a350cf6cc8cdc27acbadc471b93cb1d81676d9e17ea96f6f8c2423523a0a6543669d85bf245e5f55cd70afaeaa73bd5f0a73a89f90d4a3bad6faac7ad945827ae7c6daed4e579851f7f452a705d990b598a9e6d26737a41a3520a7744bae971cf339ee4b75f984e4093d221460828a10a0e4d96f400e8e6ef96122216501e3d1e4c05e2cab8f40caf4ce6d6b1df6e7e4e453a491461a69a43021763ba158261c2d2d2d1c374c2cac154a754ae570ddd01423aba5595690f59de6b8b3dc387545b673f61c8d34d24823a5f6f4598e7e3b6d1cc77d5ea69c7d8a3e753f2179bbbd9f907cb2dc763f7a14b641c352ccb19c4b92f3519c2427af72b8961ba7e75a386e64f6593764ce701a2267e04ef614638ca79310976ef58d863de72454876c9556d1234eddbea45870b4b80ea5cb057fb505e3b8815930cbf330ea2f3939a7d3e92424c7c60606a3a1f13c1898974c5329fcb5562f2f17b645a55eae4afd2585ba769b3a9d8464d555f84b92934f9fa7f77e51084d5d6b734e54488ea739a79c534ea6393d14010911410e0c511221c892e505dd7769d71064e8eeb6e96e9a39a5940d9bedcdf92d838778d2b477f9c93cbbbba59415ce43082184b0b1b0f15edf05c3cc9d638edddd52ca150864a073ce49e3e1acad496e42fc13b249a79c3383c910692279341952da40e69473ce09e19c734ee9cd39672c452a71b43e73ce6932ad86a8539bb46693d60c0b4d8cacd6ac0d3a6bd6c69c342b514a33adce4cab6d64339b5ae50e4b39d59229a514cf9969b50d9ad579941dd92c7bbc966571ceda4696c11e95873933b9711c47b336b6d66446a306bd5a67d775259ab55192198d1995117758946a7d9c8f5aa4996cc3e462b29964367358b0df4c6c3dda68464a6fd9c41efdee121482d58ce4c4d2cc5a3768d846ce49b312a534d36a96d1c09bec309842f7ade3b49c1cac65da2cc5eea62cd8a314a7a6695d106d562a618fd852cbb21964ce88c879ad0b5232c98de3b8ada78cad96a6698f734ec945d3a4997c74c969c51efdd3a90b92fd3425ee6460e3b78823ac291d962e21ac29cd5caa9ac45940df007bf47510d0b2a1c8b4997c5c6c6cd94b86bff0a646afff132b371b6736246a398116db0c7e3a289c0c32ece0940c5b9e6d6db0f863b9521619ce3bb3ec5ad66d935ca9656545412c323c4b140875e0155d90599a32058532540520c3774d562e4c6929b2df64e9b490210bbc01c4494b0cfb554d27c7aaed08800c9fc30cfbd5b354605d707060c07593e1cb0a554c3fc86153e558c0bef999f4c7cca6f853653287f98e0bbb212fece1af872cf3ff5b22730a7fcc85d94b017f3d5906c73ce6dec3dc2f49fe51b81f0ff99f401267c624fe4e6f18dfcb7b7859f2c539173604fb4da01374c49e03c809bdaec7ae4b629a14a357fad52f2161a7933b86cdfe4da01a76dec265574300ee32e2c2f333e6891828238a50cc85f7c476e4cb85b9850b63aedb933f28d4ba9f975db838e251cb40c3e2a359dd8fa5fab4271348ca513cfab8fcc5a389c50368fe68527f60ca03a894dffa7ad2480b6d5c402816007c172e8cb57061acb5b0df4b3e19a2d003d703a45f58e2b90f757ae4fcfd55582925cc95dc3832347d03899c90c102bcf92a111368e43e0b5e041998729321511257f2869af09008102726c199675c02c4c185fd9a8ceda6331d8d3c3d2aed75dffee263589c21911256e4cdf428c4952ec5e50a4bcf4deed18af6e6e9277b6dd0b0d9b9974ea58e08fd40760de17014eac9aaabcd60061f108aad2e8cd13c69c8d917d608eaf4c82854de2e045ff35ba954aa37a7944da1de57ec65b5e74a61183aa7acf4913f4aacf0a489932c63f3a97b031068dc1b7ca4dc1b7cac189aae2954faa6bbe89b2c0bdbbdd59948891f64d8d1c0e43a3460fbaa734176dee83edf4e248eca9a7092770343c982f1178b2e5ff47da13cb5d0679b08590e91e74e9f2ebbec27eec6a1f66646e9bbcacc0a2091124ff2cc3f4646454748502091123e793e5e699c199b2c325857a816167633c8dc425c5724fa904df1ca7663924b8cafbf7081f5fa2f096c521c457fd13730cf2efa665af1a34a1256a41c8a55da9b309b88058c859cf626dea13312fbeef371a86fece72395be41f53773f7ee1bdc9a333d93c25dee54a8bd69cf5dfb93b53826a16ebcd2836c833a3023319dfbec21cfdce1ac339dbe9ba1c3a58b8496cec52f2ea1444edd3324f45c9472b3675be4b8c89db647a510d93e01a3bc3d0bb97604b0c66422c385c0a13cdfa44a864419a0424786444d8272ed23230b54f90132f1404a224a1bcdd9423e453f5434d013560bf58d8b0623edcd33c9f3f0eb22f2bc227f2ff98b4938cc17cd204b398b57fac5a47f7a3b71d95dd4549a79bead50c9f33d7718f9f8f8f8008990b022e94a9e9ffcc9f3891a59e44f2a416f2a6531a96fbe5894e75d70664279beae31b90d76a54b848abec97a32ddaced8264575dd9d1771d91e893296e9f7825265161a54fdf449f3c332b4d46dfc8f94eea9b2c0679beaf40ae0b925123f8ea8250a23272868d1660cac1a8e298c48124141fa9c2da2e86ce3ac4256425b0665e754b5ff0f0d0de844935f47970a804c10ba1580f2a2d84388a4d91447b16a970451f1f2aa21441e30fa7ba5b1a484231d8d1eff4eac258a4a26f287629311b294f3aa393f6e6a3933cfb531e8711c2f089b5d1eb0914d4c9269d51e66b03e3d1c06ce8945067c6676159edc5576d6641676c1b9d53ceee081f4307598cd0dded75c37477b794db0fdb766d64f2e7f23e578d5b6c9b9492b694b3bb2184d0e64e09d31ebc9452ce9b29e74d7777378b1933c6d8ddd9c60454d671db35a5a8a9b4954cdbc695b614544d2ab1a026163060177a3108c8b22cbb1a8dc0d4b030418ffef480d2cd4c3b64f938e98dd09b593725b69c34d36ac984ca3a6ecbc1d40d39caa6a8a9b4954cdbc699784add90996ab5d412947153c1958eb449b5504a0f4367162fa66cd8700ef98a93cd9291c5524bd544e965e9565abb212518fb5431a52c5a76ba32bdeede012b7f704a6b0651b4a1812a489841132bb17eeddb1f7253a2223090e5a7a4d99338ad28899092270df294b80cfbb5e2df3712a7c2e25b0bdb928d484404144ae1b4b48b4c7f8fa8064e64d99c19064174812c88886022cf4b0ba02053fa6eb528a5525079decce844885ad9204c865289f8eef21b0a36b0dde3eb86c21636c3b300e35ea49cf234f9949d7e3a9d8ef4cd119a4ef58f85b3faaabbb0bdee50da0c7f47724881adffea4dff2250ed2b9111488c3f59266529e31131b4296fb0a87d72761c72b0a1872f720f4639fb7a28ca59b7d3ed451fedddd50195d39d7d3be12fbba6e57acd27d767328ad4eb5d40e3a4b425fd2acd9c83c499b1aaa484848d4eee18361e0730723f8d1f2107e1d45bd256354dabb5d67af85a6bad75d62adf46d0d7d80a81e54037c9357badf3d5f45a7acd6a56b39a55ad6ab5e5663a91984b47b0116eb29eca6aba34d55a6bbdec82546bbaa9be845bedd563262c4ccdc0d0b22c05b046fb27a59c00f6d00e1b6a34b0466b6523ed69dfe917d92f067511500766ed3d05d499d71ead0415c52fa25133c9da95aca19135326ab536d33ad6ab430c30974e24e61aa98841b046bbe90856e62f0611a9efbeae7e4fee21f55ddf93af4bf3b2a6bd6a1046c3483a886eb141984f46d3112c15ed49813a11eab4f27c4ba9711e28fe54b15f0775901179be89140ee4393b0479ce22604d0e1bd8f8af6310ac99e768e267add606750e1227fecc39e747137fe6eb5733fc593a23eea22b869d8f41918ac681735a213527991af66bb5e47560fb72f6105bced6b8cb69ca696fe65285cdcc46515f7e1ac9043ca49f33c2181ff19c734a29613361d30eafd596f059649b854b1a47083d7d292332893fdae96dc0a16ff2b6c939bb66edb65cd23b3932c86e4913994c91357cdf896ac27e1d254e429327a641c80de050922349bcf6280d0f4f90c6dfe4c951f69671a5ef745f54910cc7d64aaf9f37498b7cf759fa2cdadd8dbf1b3fd21569ccca6296eab3342e229f1599d73eb34f9361f5794de2c8c5db1fcf0ca8f75177a1f010ab952c863cb95fe5b56f99bc0adba061255ed2a60c23493deb159e384bb57cd62d9bd5b1e3e4ba23ab2fb9ee2c4efcc943daada4edbb7fa4af927e92f6e83b3db26b181b84b19fcc9f39334a5bbe4f46c8cb1b833cfdb431c3fe30899bd5b2b5c4818e4a803929aab0cedbb9ca93ce3927b637d0c70b0ff207f3923f1a14963841a66f7d8c4c899cb8608923583c60bf7a030d00654a89342094e9bb05b1408a52831f2b404145d91414f06056cbe5d01c019b1865589eb9c670c6457c10d30ea14f64d84e296c1499cfcf1623955fcd0da5644d395bc258fb2666b03dd93489ad5619bbb9da77e609a314aadf44ba34e37433510961686c5e28945f6cf4924a29a59cb2b1946163d19523c8404198ad8b36f1944a7db04e75ab92f1b46948d8fa79170cac2aa652e7696197a7700bf3a45218d2ae95da5c6e4fac757b622e41628ed7aab575f26a0efc822d8cb52e8c9d7e52dd0f16a9345cbd7e49a011068a98f205ebdc65bdf6cde95bbc2c1dfe7aa06caad4bd9cfa25cedf72ed0ac75d375c5c386e6017015a7f790d2ecf812bd07297bfe00a4c61a4e52eaf80ebade85dbe4e77b956be4e6fb956be526fddd6679363927853a943a39cc22cf5f67eb08b7c8268a4bed35beaaa3ebb20aa14fe6656d9afaeebf4d5a395eb3adde52f6f5dd7e9ade7b8ebba4e775dc75baeeb8461e051866df9098f462f9f393e1fab481c1d9fad4f97d5a7eab3e5fafc093a72bcdc54aae59eee725bf7e3c9dde95d27bb225a0ad7f6ba4fd995ce42dde20f1a65fb6d83028bf2e9f5d6bbbc27d6faea56e2b854980626aeb7bc75971b69a9cee3c2aeee3d312117ccd3825ddd7c4f2c5691afd9c29607106c8ebffc84f3205ff3e799c288cb5d7739cf144674bc75285f2d98670a232d3f01f3b4dc8579a630e27a0be639825d304f118f10f99ad7818f84fec12328a70bb6f2650f632d2acde578e9c9b09c041659d4d5eae95b6cb53cde8a0c893cf0247f3143220f00e59e58cbbcebb6bcf59e584fcce5f6c4b6e65cb727d6727b622e37c65a2db885e3c9f4ee96be23bfea1a1291588dc3044c78518426a18c13e209e50d7cae0fd7cac9adcbe43241800f251f175a38fc76f9eac462219cbc850b633344171197eb30bb9eb9767cde35249b3962279c4a29a594524a4c23941c3972e4b8cc711d394eaf0e281ddf6989c216f69b4a344eec32c05c73d8ed38cd611784c7bb570c8cca9c1e7917c9eb3043188cb91f4da6df646ee797fb45a21f2b59c7656afa08befab5f6511fa150a878b4d91c7641fe83dbd1d65a9965be513222520c71f3ae87e311ac690e8af63428da13584393b2271914f40ad499aad3a45bbb6dcf41c57eda2aa35264bd7bf8f3be09e4e32426665d0c32cb7ca35b0e1bd8d3e351f7f2ed41b01f4d0282afbecd5b2d2929a4d0accbdc06aa3505e1514ac66ea35c03f50d8c3906b79356a2bd0934a5f44d6645df08411deffd7905d489691c9f4050a7f51f38fe1d078e7f15c7eb61c4a142f2d527cfe5e3f92ddc85ebf2811f50c3ce33761969c1c7835c88cb078e22860be02f3cbf2796b1eb0139a74236f807feeab1900bc3bdab09c19a7eccd5ae006279e1c2f018ee0b177235ef76197605e8580c587bf2c2d5a0c070352903fbd52a4faa6d5581817d92dbc763de9f494420548dd803ea7c22017500a09d51b54308849e71d8d5b07397911682e0d9c27b62d06524ff85bf8079048067c62ecf054304c0231b30108a452658098af5c4a6d88f266dde8579fb71e58f1f3e6e43711bca1be7eb5a46284ffda4f336b95f16eee30279ccd5b97763a645126702c9d71911084da26a4420da13ed45b4e88cdcdf00c0c2ed27b0a60fe43614edf575f2044aea6b4f2290ee40ecd11a4d6a0ec09a06c085f9476fa081daeb9e026b1abbbce7bb30f3a556f40afd88a9942dec179de0098499b03782e0a4f0afa270085bf986cbbf13f28df714a813f3be975957e15f0bb30efb887cf559ef22fac6c7bdb7cdfb50e7c76ffed9dc7d857f35775d8f7f9d4f873918763504e02e1862f69ecfb34d198a65ecc289793e0f2c0c1c14c5f25d784fcce64ed81eec5bbe3cf18735520014eba19e119be2df7e7d7c8b3f5a602294f3a54b10e113f37161daa38771fda211c80d5e017f35f7c03117079c041c913834fda277a187c1f4f42908d15f8c8f54c05fabf56da3dc0b7f31bfa4e7c93cc7635744c765300c33af8bc47bcc3bb286e63cbcdb93775c9867ee47e3c275d510801674f02314e371e13db06640149b42e29c980b329f919efcc743963bbc1999dfefb4f43c997fe6c2bce3fe97b95f4fbebfdf2287a5bc0f791e314efa464a1e972a7c02f9b8845d7eda5ce2efc72790c4d389777aefe27bcf3b4cde07cde58ecbd3ae08bd5764e63318c21a19f997779a5f7632d47ce6927aafe3d79534a7f7bcd72576edb8bccce975edf8cce90e3c44e6339f91c1de77a4773f99a3e0d57ca729cdf13d8ca4e6331809ecde775ae62e91d9bbbc5f0d7ec1fe7747e4de7bec86c03054012f91d9c7e93dfcd97c3ff01299799cfae0c1437a7f7d7b5def255f3352e6de5f3ce469cca5f45e9799c11f95afe9c48977893f27f2530999c71c097aa737862686493e8f6fb18361067f3d7966077ee11d352c9acfdc14bcef348ffbd5fc7afdf5172eec155ecf778a9ac3ae480d7efd15f37b1849cc656264be2369ee4e7ba7a7f9eb13eaecf8ebf599d35cd8deccebf51e3dfef1f83ff31d77c29a99d7ef7d7b5d24b07b19995318fe7ac8b0abf099fbd93c739afb29c934df71d83731f7b1037f4bf28ebf5e7321ac79e18f27bf7ce04f490c0ff975d8373f6e73087562eec3067f3ce41daff98f0bdbabc19f92fcf2813f9ebc037f3c4ef39ac75c086b6af0b724bff0c7e333df9134f757a1e630c37ae01d62e621e36124f432f732f432ff8efbc55c739afbf19067bccf5c9ad7dc1d34f7df71fb93b907bb22afcfdcbb1f0fd9fbeb4258e37de6e55d99ef38cc01a13ba8846d979435eb149140000000000315000020100a064462a140249808bbae7614000e7a9c466a509c0bc44112c328ca20640c308410020800c6001821a21901b02068fa44c7b603af965f4e0776b3e92439e6ee0df3f9043d0ba2916888a315e6157695466e2f4a16c7177c6a1cc7c508d3950d20e02481be892c2660f0d4ef419fdcc044cbfececdc2511a38f95a2efe2825a2e2ed5e42266650b9a123184c883a5985dc84eabce2305f065d9498a27bbbecd7322f50b349a2c92f2e591998dae67ebbb9fb5a98498346369818f17daa06af4ea3b9e2e3ee3d1b2f9240adec3664146f8bd6d5695ba63485d14ca530f552cb4c6fd15505d5be01c3bc03cfcb9d94e9788e729dc460dc8fa174560f28a237f4f44b96f716055e376589a1544f00059fc8abca1c349aba8ee5ec02610197c6f217aa1735a350b00aaec080e719a366ddabdaa7ae547d03a1419b5dbbe2c0c01dbe09070e1549ecf5f3cb5f819cfee76ae6d199e2757e38286671c5e0f6022810b3f32a86e6c3fc6a4e144ec95162ec4bb6bf2f85ef10518b454c17488d565d4221ff5f65c52cacfc016970e7b3140ee3e25f4865c7613e5397cdeb5c4db0ada37f9bc309934cb518f52ed3ff0146e557ae35776bd7e3deb7018a63c34eb7b4c1ac7d263d249e85fd60c762948d33bbcb8ce8946607312a51685b29f83b344c73b300da18ef91478956b4fd6377205d8c0a72dfca52cdc7fe47a5e80415542a9c802c4f94d679a9716dc78ae3f102c1653b2adea0e183061f3752ed685a1d88e2bc25dcdd565f0f2a1b81964c5adaeb4266dbe492c2269614ed8c80b0fae7642654bd006fefae302f749c2c96a2d873b9cc1786fc2e482c6326fd0c2e36e18618dda6b4aca0597a408a3f78815d2e9a00590a481de70484cb6f8d6ba4d0023d6b0c22a14d5876ee92f73c7b703c28d6ba62c4a9beeee1ec4a3e06d529de1c31e19c342dd879ccda2ff965a389b5c5ca79d834cb5fff56df25967095f1ff54a70d53b8983f5dd55de7cadaa146e8916d286eb4833f1d1a123bf24e39e5db4936262573fc28fad5369a46bc26dec02a0621ee02b1e13f7fa5896ecf3609c4c8ca5fa93695324f7e2d138916cf3f7d57d070ff0a99d71ada9e80ac8b9cb366cc399dbf0b8ceab1f55c7befb385a686eec46363e992967f1ea8416963410bf4f09ca5601112f1a0fa208e779d3f869a4493b048be70835bd1f6d6480cdbf0f75a27dfd58dc3f32ada23a9bd1c5b1ef15fa442cf400bedf203dd468bcb41c911a33837d941f71a418a2a96d3daa7e235fd29e71ed169f77381e86b1b2a5cd05fdc0896966360e1c621ea85e291ed2810410e98916b25f054327e2d7a5ed50d5fe8f5f3b5a93e52d6a6ae004b79c272918756ecef181daeb60355d748f975a7bad227bcb252f700e5cfaf9ecdb6d44b17144c155e2ef25f6eefd6fa166ab5fbec9900b17b4a9ac4313f0ef70c1dbe26a7469db161806c76f41e4f67550a101151da22ceac33fdfacc80fa9db3da808ad6479a05af102851e1e1b840c572ebee8f06eae4a6fc0eb4ac65f7c93144d98fec669b216d1b140b1f974ab36eac82fd0e1247b03bf2492b7237e6b7412b64f566d79c3ea4ddcd0e41013ee3a51d10af92f9a2771245d1d39a4c4a1b8858dbd38402f77427f1fdaeec5575d6dd6c5fca8f9a092f760863dd6e2b4af47e5f8e6050396de8972cd01d24d2bf9aaf82f4b1f9b9df5f217a21f4a22244b7f4f0745a3d5250e36ba70c5a592832e39960c15793debebce9dbd577826d60755aad603239865e8e4cf58851cc09ba5c8e6a2581ae77dfc27739f94d0cefd8e5c96bc4e10c3b253c4881138c0f4baf9f441e5c16a46130a90125618ecbb923494a6c27ad25e0d7c683706c3b9fb186370fb4c813e580ab3da85243d3b67d57b34da8f1b473b214806209dd225ecb78840b1c15cc7bffc78cdaaa108af8ed90e48e9fd1c7e6184bd1d328bc8153ca03bdbf3c2de2c50b60b5d06ec36080295bdf9c1c01fba71ffc84c06f67102f109780e0aa37f88b7c4e105db150b328c4fb0daaad6fad8368cc0b38a96dec46ec059bafcf0c56faf5d683a41226754a5ea094c6e05dd889b59bbd0a39aa8f870af1af6c19575a83f98d013097e00f1dd8c1745a6588e4b5c8fe8cda2b5d1e092a7923c62308bb7b14e8c8de31908216d89caf7a5524a030100143944549212aef0b9a0dfbbd7503f78003bde88f82d2ab8b55c555989d7efbf6b3ffbeeb98e8cb23644c0b5b9bcd2baddf06f1798d07b484cc6a6ea7f38099f162393b6867c91dc844cf7ab68e200dda96fa22b835aee20a135a349fc91522e4939b0a2248fff9b982f7720d0c4a1ba5a004eafa3c4f48776cb6027ad64715d879259c02660d2cc768cc4157d78e33a7c460b270cc8e5f372459b094dd6f9e36dd99101d1a3ad0370806813ea517d860dbd89995a2ef3aabafcaebcb0b56e293ced9d4aeddeec9d84856c52779acc58e39ee0d223debf7141e440ea059713e20bccb8e292bc0f0b51252ec61ca8663581f1280f81c245ec1cc7977418b4216fc57e90596fd21f8ea9c76d72921bf110a570a7d5aeeca8678de1425c72a6fefc3c7d526a557aec32801de6effab5d326c49950e16a56915182b2bb77ae37a2995ba27d7a5e7dc4cebf07804ca590e512e6f0497cbc8bddf992f8351c5013befde93cf3ce1ec75530b77946a453ba6882e26f13fd280c6d2c2a9b941e8ece17a4150f12d0a7861796e4461283eb77ceccfff0c3e4119dedc9785e19bb5b232c3f750967d825c20509f3090bd2b8d5592d298de1bcaba35a86998f366f484e85c9f790aa64e1161b65e314c551255ee1cc975f91cce014173bf4f6393e67b681cfee8571c3c82fbe7287665489203ede7879c856ac0ff738e169c68c47ae597811fc1a97fdc31c8ecbe470604d8c944003875809ad1f07001d117d4bf41857a92e9257b229b5f0e3318f1ec45b2ac903f3a4572d2136d37d2e0ba93bed7949f719b95d5fc55e45163092bd1fb729e78a9157299862fccb460d12b24c9edd78512a725aaafaeac63dec0cb282bd0e7fac500d7a9d9357058482800c6f5de20d0034bcb860b7e178165ecd39d90e1765bb39e3f82694a29ea30589469ee19a8a3af6d06a1edcdb574b8238b7d1b60a1ee3b0b02a37db4bd5f068d36cb2bc070cba265c377010af6c8669addee3d1d16a836e666a2a1e439307d036be7ed505033db9dbb60918a99360b296529bf9fff472ff87dd20283145cb3452e47d540833cb244291429029c3e71855ee3cbce409e16553e1baed26a875957b7270910a90ec1e021015e6b8836af1e01bcf6db986d23800d2c0b5edd2f9467b2e5fee8fbfc4393f15b763e8d2d9b0d1c5d9d9223cbdf06ca6dfaba08dfae1b9bda5d35eef451dd920383a6190570c17d03d64a1362ade067b8e975165d3825983264cca8a17eaba7dddc8a3b0b63bf1cd0cfd4e28f2b275a0ca6650d1f999e4bd822715c4148cbec4da862377a0679ecc77bb38b5ca3082c4446d5af019ad54a74526c3de0b0d1d637f3ea900bd0f1e776e66b57f97a80a3eff39d57c45393bd6a2ced250544e518cf086299b6b5f296d4130c9d9e80b9ec55a27cbc8680cf4eb0b55512489908ad637368b41bcf4ca700ee22c84696a5b73ab419913e9b3b64d6ec5603fa793f7fb4d24cbc5ada6f707b029f8e87f8f574cd1590b0880296d27446cbd45a38f532e4abb51621b40aefe864adc4ca0be1ec84581f2b0aaaa036e86ca1876bba5a30556e50981cc483375721f0149ca674291607e98ef668094e200ac068fa3bad6e6d046d1ea357ff37f743fbd16408c3e68d238162ad19d0b4273742a2c842520815cc3954bb9b2e0eee5ec8ff8a2eb86463dff758d25182754e764cbac00124b950667135b4e9a5f1b66eccf693dc8e3cc8ba6709a8ff8c5668af6bd15ad455dfb8b1cdcd295c4ca9937db9b1215cc07faf0660a4d0e9ab56fc481a6482e17492508167ca0041b812cd62b4eb015061120f2f775a1fca969af74d37da81a5bae7b8a318a6260329924495bc617cab2942392823ef4bfecb5de493a6162049225133400d3c10c815a0824a8b937de7fe7e836878d9a13bc5cce7a15dd2228e04148aae1b38382cfd7fe21a922fbf24e79515d01fedc6d677c81caf82c76976e8a55cdc40054dec6a240019cb5180393b207e357076f05cde25057d3fffb7181a273dc280cbc4260b7cc1356b76e249b759197ef93f1e3638f7488af8b46adf11bcc19887e8fddf322673de1c7bec2eedf12f31b9ad0a3003836452c572b3202562c8e82cde5071ce422396c10ebb4a6168742ac850a430f8affab0182c4130484d95e7d199a629c8c8cd73062c2661c4d143bd218c32e2cfe99f07923d6adced813c0afb41d836ef6d6e6acc09d375a63a1607f0340feaee97def24ed69acacfecde6dedae6eaed93b15677143ae87da89da0792582d9437a7c23be25ffdcaab5985ae65e26fdc262683c3c0fb892519ac27549a32d5271919e4676313224ac2755b6132e280956ccf6c6d6a6ebf2072cde802a3b5b0008372c4b6edd349408e74106f5b222d6136f5d6add4cab9350698107c1e026f8ffba6c4e75f070fa84cdc2ddb1bfcefe156932bddcda48abb79370f3b92079d35c9c4fa50d24a344705b227ff5a50d2e83c26088526e5f36745e30d1200a407a0147b889d7f05cf5825fcfb53c4c60e3388327a471fe42f91dc4441349dbe205e76e9c993feffc43864c591212b019558eac49692fb46949e6242d63bc8cd57b5e14d0170c27c569abd1cf025bf65a62b86f5a6340b4f03e265fe14ea937e51715f4314ab830239220c504c15615a643da015781eee3cea4a943c7f57b05a409245b93a1985ba856c0b50ec4779fb3e55425d4fa3defd0c5bddac73977a50543c5913b672ee039fc54e3dea3524caf3b7373dd9a4aabb657a603293b800b6e87d7b38dfabb00eeff5f6b28af5907621c89e5d82b6401403aef75a7de21a7538cd39dbb3c69b81cd452e8bb1e510025d04e7358296edad4e258137a2146c36a58731a9a9bfffaa915b4cf3cd06329012428317524330812978d47acfdc3359eb2400a5898668ce2807413fc3ee2570359c260d4c487debdd38bf9f07174f7206f49e23dbf6fd5a8490f8bb90ebb97af23ba7373ca85e8fe1ef1c3ea5b5df1b44952b850b67d9f68b83fbfc760b40357cd3eec28f634b25ab5c358176170cbb1c00bf74d4bd3f7f3e1ddee2417c77bcb0267d55db500e398c3cf6a05407ef0d03c9b04243cae235c4d319a76cb26e007aa40bf2122abe7e70c1a1d3c308a2c523a7fb4d1a351664e9106cc2674255f7df56e8256ce2dfb03e8079dd127b5c9c36502a9562f163dc76840d594785978433128b9b3eb835c2992650d156763670139fcc2768e9c2cc7056c7cdc09d8928f7b26ce7904024cd3a34c558df59368c563f7c917235182931c448d523f278dda44f0a3712a8a5ddb87809d2d72478bf5d1b466ae39f83c648959e191173d04e6f42705d6a281a6bfaba156598f58034359d6746261e1439e3865130eb07452cf218b39cc5334857ceb5bbd33c2e35e7090149de3bfa9c5340b9b646e817ed0c5bcd0f36a3cf1650b2490ffe9ac2ff05cacd54208e80ad08e9dec17358093dc7eed1192173e62b593286525b865a646b09266633336c06bcc7ca48504094d935cbb6290e1436ef075fa26db2902265f3d46b92937d2c7e1474cc8a4b5771279cc7a595153baf967ea9a8b50c84b2eb41e30e1dc1d4d99da4a5a8ad4c1caaebcd144ccb6b3846a04079b56e73e02e8b15d7d334d8f40a4aceade42e3333d32de4a2310b14f1a3b91cc6007b42f46b25a11f4839db46db88075cf5c01dae1af370580c601577ba06bdca77e43b2059a15269ab2c48f70d7d137361cdfaa5fa53c8e6292006722b63822fa0ca3b6be7dadf4b1d3600c70a77fbd24867202940b735c04813dbee21bb1b94d696eff9d7b9bf0168938580eabd7b2ad8911de8949511bd92071db1d0dd4c80f95b09e78f637f06d112a11ec9fae82a42150d667b55a4c03d8d61cca31ec1899993ae5a66bde19ed685e210fbdadfabde97ff5e01991a81661a227ba94641bf9da21095867aa1579a468f3fc187dc2c7023a7074548ac090f68cd1926d27aaad2ba709d2ecaf13d015787526ca3e474016e9aae3c5950c2d581805a1202cf5ff54e16a601d422065c4baba8ded193d825f3e9ed84230d7643e5b94505a732336ca7756ad47549f7cd06185dbf17312c0a285cc36d290c8e545dd9f63d9a7ee87b7f3d761b781cdb58abae61ddc99c5202d804353ba22c53cfb79066e2b4efa603fa5edce43bb5f4beafd3f0469f996ba161a78c893b2c28f5da804f84c0fd615941028714f3133c6692220fef1436ce0fce5d98a6aa38c16cc18a850f2680326aee68ba3f8d9d53b6cbd69167b98b7bd35e992d88e27813980f32218a3a66a0ecbcbc959db9661bb004c98596bfb91a61358d87a099b3aed0c4551a5cb42041149f101aa2e471818f793feef44f1174d7555b69179b7571af375ab28ac8f8f1a33e31c41335a8a18d8cfee8373c7845d5cdd3975e7350c6224ccebd87c3e70a81ddcc184c8ad6114159be29ce518285bdeb5808fde507751b31807335727d4fe4cd95cdfc384f6a4d002ec60278dd209405be92d4259a7e38229f3d50980c40e8e9141d44e2592c092d11d63f0360fc83232b6c074b7fafa265ea498d337ae6ff9349807e688668b5399d690292931c86fa51e33ca3202a9a77d68a9cb326945c967376a97c3d3938ddeb0952a492eb3b16652d2d473af01a4b0027858bcf837a424f989081634bbf7a11b2aac11f86eb03d31526ffac2b82dce9352e8b5ba1f26aa6d477b641a59ae62d7af011feb251191a627382a4c3e2ae2d8c05c1279439ec1793e4081db183ff46569500a9157f3970f642a37dc79bef4ad142533d43a0a24d75ebf7fca1d8ea65b1438dac006a74e6d7a8b238c64e000e846e63f7bd08eeb1f1d3e4ed812c710205c106fbf6a7e2d29d03ee7cbfbbbf11415fa5430fcddc99a95bb947153dfb373db0c0723c299d7cb3673fc7db6e4ce7a3d0076f7044b6c39667bf435dc5cc346d94bcbb05960045cb5ecd32d12a29dc4f984ab8c199ab97e51f77203b22bec7795071792321d3603af129574be29994e96186e7df2f5c57c9cf9d4cafa34e47054261e2bcc6069f094e5c1e1b1e2cde17e780ba90d113b305d8d9bb5913f7f3e489c0ba1d9868f3b816b835bf242ae9be4818099c2c8e6dc6b17e31235949ca12b9242b1a05323da125b2816710255280f0cfa6b768463bc4017b82305bb6815095324ccf5750c2d829552d0c38527c00874bbb6d6a6a4ddc8a65392d3bfb1eb6418be3c7ef0b464bb3d42bd982cefba7cbef003e4cb4c0e1ee01cdda7a0298854012a826b4c9b61cd1d4d0351a0415a8d810ac275c3e365563d8004ee64455b13e044e1c8951f8b3d8f37b0fd057da759ecfbac0a724e6abe63d01c662df91d42b8d1ca2bebc0b9b601fbc0194f49a7ad2fe5fe1a9136b0abc104bf2a2056b15ac89bfa2ad91e0ea53b116dadc4b4467d3b6bfbeca01e9172618f2654c615f4c61523f7e0a7ef9ac5b0430cec126d63c7af3ef0e0173d0c333437380175f68f096ede2f42b81672d3fe4156ee26a6c8dd4f241eae5b2f2a2f7cd8e544cde8813cf45fb96031970ce7632bd0820f2bc77deb00477a490eb47e2701d83456b2a08c4cf3158c455c8e217301ad11101fca9c80a1ae0da80ce68928b70d70a6caf878bdad5736d36a9793d0c8f605101aa4818caeb61806dc6ccae7bfef71affe90c02583e6873a1e1ea05319952a6561f8631750ba03c55f83698dce94ae5a8c7cadc299fac95d54e738e22b0e88ce40a330619c44a720c1ba2a844a07d6655dbe62e5f40fac450ec50d9f22404c3b624275373288065db41ee54921d27e4be57e1c0d8bb01471d7149a452a8d0d5a731ad11eaa1c37652203c88d2bb84f393f3f59898e48518f0ef98d9d6e260fcc29323efacd3cd5c17d137939c20b20040f1328dd98e33579f201d04339ff4017c95d243df3aef5e566f25224311cc6cb073d92f584e4ed94d0265b53a1e0208a6a4c996a3b8fea42134c42a6bbe74b4545c31142fc9ff13a08b0c0535486eb5928b4681b92482015c14ee9ff7d10791b55cb8725a024ae8c38d0f449205d38a198195d0fe63bd72f6f408b626126c93dc5353752e1a3c5c591e9efcd33efc937b4f128f48f9228457932695c3cd7f27004fab7508d0b2d3d539684e268f4f5906eb96337fb7aed9a89eb2c1537f7471053b9bc4a0bfcbe488d6f75fea79d30dd1793b00480d8e8c3da3e32acb1269a1c0f7930e89043dff22ce348f45a3d783b239c8e22967a544f998fc0b06275fdd92a0ea1a06e1f21bcdb68dd1f2f782f8db3df79af89214f2aaf20ac040f0f7cd3389357ed953df2767015479284956ff6900536feb77524a738ff418aea4c04cab2bd0d7ee14d356ebbc9ec07f2a7dd0a7e1ab7a65bc64d1bb2b0863835b08420669ea0407c0e3d37ce99d460e0030475e0ba4a470a07903d82981787ed991c565652e4c4ed6c156852f305a749e46c241e848fcde89608f68b823d08013dfb81342acf7ddba874dcc144c10d8257d24a3bcc8d2a936d7978bf89f6efc4db972e6321b9060a8a59c671348d3d9a2fb6b1a6a7da31c5a58bf38e01245e297108d0db23e060e19538eaeafdaaf8cee62f1acde682e041baad57d55b2f4f10773af3bc098c0772e45f4e15f8f4aa3a7714cc5c6b3ab22fb5bbaa1b16c11549421803cbd486009e5bc2734f3059c39479b6e7a9cee54d42fa56b67af70513dc55fd823c2b29111ac96247c4b531478a82d7102555114ad92185ac0b1cced4b084bcb20c5cb8f27bf85738d7d99ec2da8f5596cb5f5c5d250148adbc8634a17e68fef426b25f9797a85c2c6d9e175e96258af56656bff9e820110769060218a383f7f02a9cb56a5200b0b11893dac17a4d84dd2de42143ed0807201cc328d97c1645d4a0c6ea9ab7b8f7e9c09d3f13b00ea9c812a860af5fa1641b171e032aff3c8755b956a38a8f074a79a3d3571a1dcf10450f997c4c3a46c4485dfc52ab59b1216b5428e5322824b24012066cf2c857f044744156537ee79ed3fc9a2f94a8b18b1e160bab61e43df306368d30594f25867e4b220ea4640e11a64fad3fc3f94b537fd1dfc1ce268d1ebc5be5762a03de9a10058c137f913ace1121e1d11de1c1139693aff947e1fe9d734244b0459dfa2efc19e1fdae752d6c53c2fbb945cee06b882210c5db15c132d2f1f619bab4a734b56319aaaa1ff10bc0821d6634c969b0b29fa06432f2422d1dfd82edb74e482df624d0721a88b846d28d9296402eab19d0ad6f0a9ddedba7221dad00c84c33f1d16715f9842b007c8dd7231cf75310cabd2ce5d3dfda69daa7ffa79ca9f23c1a64eb1b66c070b6f1c7b9a909d4cb830c9582c6169dc100b188f1fbae69450c80a319533d4f2efa2e1803250bfbc6ffa61216af8dda157763ea727ef9b7a5173e8a884f39ed250ffb64153316130021725882b3752a37169df64b16c88ffa1b9cf1b784302eff90f006f02beb80e0bbd760fca8a2cd6a235d85d9dae54e384484f1220ae12851b8514e69d9e6bf1a475a8adca139b8fd7443aeec2dfab20ff7c1aee889e572817ded39fdc9229e66cddd4fa8cff1c7bdc5e58c43f37f9b6b7ccc79831451c53b3e7a17a810a42d1b8322da0bd8df1aa3e12ef8f305b1dbf8fc5a4c54c7b360b3de04785e97f1e9202b361949b3c8aefb3f1da0ed894a45e254bf20d0fe716ad321ef782fd02bdbebf3f25b27b1a66fcaae62683f6c8cb951222c03274dfa0736ed5a7e117b321e3fe16a034c40517aa4386d4e1898d8b725f298a99cc4eac79074bc99c1ec31a638c52eeb8ac6f48ccfb245831c612e81b733585c6c0cca2be6507d9f05e52e7087c64e004a065c845a7d724e786a5f7528abd56808b91b04f644d54aa09e1e5970ccbc44a36616af429b902b2c0e3e461a6cd492c69f0367446c9da40c3b09370b00f9c72048bae54d2b5300c5753c87389a2b65f81d636b5f9836751b7e34be400bc29df146bf35e8f78be6ef5219699d05886a315acd0c828cc63de998ca30c6fcdb285d05322c03e7d7f7a2a33ac2d7f53d2f063ab9a360511e721d47aec7e6ecd3ca3ffe28ed76bc7ddb89e0a64d3be9073b22654564acd9f9505e75c2728533a9671a127113cc9256b1557a26b7173d614d5144f6f66157bc2ec287672b10b1d3122f1f028a79bf6d4e74c7d426d2dca4e086de05401b0a9c8ad7f15202132b1761682ec6ab4a0264ad49e5c74146d40d4b92880173d9576243f578c641d63289eeeb22eb9e12eea32552d3ec026e0e7d89035155b8d5e47d7b607eb2fb2bfca7fea2c81ec52f1182cb1421d057a9079a420ea274a93c7b6fafdd1d7dcc886e4c8c3479515faf485a07082d5780992f524a3a674ff01523ac6010c453978eeb06417df9c72f486cfe88e826ff24ba2738c39d218b395ee884b772e14aef9bd615bee5cc1e9e8cfa7b4b627367733e755c44ed69e2321107fb97c68b39bbd40e54bced37b16655271c9a6d575e433156ab80f9e2be2d1d1f55ad959d87dd4bbb1c33140088f927382363dfa681710517d850a1171589da3b5df4b691404f733070420c7e9bce730cedbec68f032d352d868a815ed7ac43ee5b26e0189f62aa67989423d8c0bb8240398fef46469b9d016e3642f6a976ebacc63a2b6de541638d8ee5ee16777f78a7b62ceee5e71277752faf85a78c82f9b38a0274678964e9f056eb843e9bd5b06c50c8236c71f19ee0dccb07b2cd311affb8b83b2e4bd9a8ca6385a1494726986aaa51c2959e791f1720263a2e3e1fa9b26be238ebcd524fef1443a5874a3bf36369b7dca90f3969d70b869a6bd68374844880e5fb849704073d16c0877b35539f94e6cde74b351828d87eb4ad8dcda85cb84bdf3d8623fc6f8ec2a623c2df5025c666ef5c637f70b8ea471bad36d909d54676cfe99e56e500bd3c984463082ce1c58dc80aa7e577ec9ca3f9c73a0582f13ab5a86cef34fadd02fe0935667515a2a17bd6146f0fe878faf163c0172ff17f46eecc87192d23e961fb4b711da473a2166ffb21d49cb647a42bcb09d98cad42f628f4bddd12d09fc8731322b9f45c7700ca2897761e81f98d03823e4344bae104143574ca3ecef34f0d048f48ccf5889e0898df0527f9032b0a4d62c094f23fe38c78c9d0b9ff6a31c6a73cbb06f09e16d81b28e34703d869cd234532a4f90a91968560edf633de1ef7edfdc02157632debf0c71108713b30e143dac30d5ba72a55027b9721b7b04818d46dbacd8c6c9b2c53e3ab73f1fb2232118f5e5e35353bcf59647de5e41347aa9da68356a6df0ea20a50f236bbafc3eeb0421e2832e6de699a4287569517aab185b5dfa1502a5ddf6324e3428c90e97cc5c69f8ada10252c49671b277a116af7666f6c649570e6933f58211d8d7f50b91fbcdc36b0b84c89f42a7e47d68d612920c454053d4b48bea5f94481fe43476c61add25f8ea9ac634726360dba16e49f002fa64b70db2232dff0e020c940c441e659b9eaca5478fbe3345b2afc961b1418f01f84c61c24fff478abc556c8cf9182e7421ed7d5ba3e353770f60fe5d15c4839f671ed2c816194d64a588a24871a59e6a168a314b9d714db9310d6f99573a27c7a17be654ac317ecc9fd6ef11cf828a71c7b4e7f62d3398a759ea00f9dd82d94928da72cb5fab68da11c2d21e64d23f96d1834e0d0ce8f97aea3f24cd735903bbeae0cbc36d66e3ea96e7f028d066361c306aa914119015c316ab6617c0f932d881a518cc22c708cdb77157b96dea6e8935dd7243c271045fbee044ff88336d1059092067ea51342678ba9ad0c285de1165632fda90d50e1b7209c69d3c275404d3ba2177f6eb63e3fd5daa5fee0a7bc8ee9a1a54ba4d7eb2358a2048f8019c16bcd7578bbfa48488b59b8b87a188a0ced4358bcec8d86599d973899c02c7e16357dcee2d54f7aeeedef295685a3d2aa2bdd8dbb4eb3293755c3f02b6f76166dfa9d94157874a8c48243785c590303d3334a8841abb29f287841b2fa7cbc9ba280de3883246a91ddedd47c67b9d013424a055c0c24f28cfe6cdcaeccd56c5c744b812cc1ca50a6c27086ca610830def270da47468cbfb4c4d1f4260f208d36324787cdbf27a666009f760f4d36de1d9466dfec6052f80508e3fd17981a349b40e06ef05400250df235f6a9134a257937875bc11bd55f52dea1ccd927c51489ed4e8948ca5b86a8315e913a95b2492e725a029d69a13cb646b044006c93233672c9a586a803109b6d0d208cbf8def49d2897b03ed20428c4e2b803fa0cfa37d435d31aecb57737e76e4bb76679e4c7435d2302f77eac817650f735f069730d6cf2cc64c052ece401cd22eb4e24f2f122a6f0c16f56b95802d516a68a017cfe9d50d00fe396885d529006ccb32864d1a8b3041c149d3514a1800609f430caa7e3775e45f629b8a8b882a0a627b9a791ddd3376b499bc5edd20c2f73b3963876665434767afa375e1535699096bd9b63b31e43a846fccc5a99446c9259ae26cd2ab6d046f1b40f136ad4c9085c20323959c775c575ff12a1a7f3ea338f991e65822458955ac38f9bba68e34235e02db4c68dc73a70ccf4cff33636b02f34ac21025084bf42048332e1641a4c6b4b11caf4187808d510174c997261fdd06a5a8a0443051edcfa0428f94c32db3e7f89fb140aaaec66e2d2af45d4f7ef224d488cc488d17a16615d986c76682840f6b08bc44e66c60e8b0c75e4710ac75354783c554631a8a3e0607e090157e3a476069d920ce3440351e175b960f5d542362d11cc6f10be071d1b4ea4de1bc554402dd9c9af70e7265479ddee7aa94c8b97b7c424e666ce5235b519c478fb450819519c2f1f1054351eaf23edb19092dc5b96b674fe8ea22622481730f3d955183b5b66d663753c19d3182b72cb8344e63ddf0d4da719d3cbe513d6f13ca766982648ac48b669072157753abd61ff11ccaca0922c0fb28790db39378143cde2983d44479fb9c2fe50e87e83f11078ddd623f0694bd13708784bbfbfc46b46c17e0b5fe87c210bf3034dc3d1a9439ce9004019c138be7d6f8ec5bc010b4aa7cf01ecadcde2477964810d87c52c609a25bcc1001c0af10525f9654138dbdaf25b24a58a22219bcb2640f26cc4a507aa8024ab04de3a07f34032307e1b8d11224080724be7bf4cf855ae2a95e5a6701ad534149e920bff64a31043b415cbfa7470091a8105091065a4af8c7dcbe489b2de24858aaa9245d8b287309ef17f86b1e570cdcfb01c7ec82a2f61767d1c94e47c5f0bb2c7510063b63298a1e8457e5ac23ba3726d85015ae83ecbd60b1415e9c94dc46db990325e022cca6de063ea9215a053389d7b49b35276a49325269d0f94e96862821ca4c655977251fcd7fdf6ba895842c782c2c89addc2f89a5026bdbb9f48e23d942273621210fc2d5046a07c156120e6b04926d83b593e53469c61cb736e6259f95fc6576fc78722a05a1382c7be2df1fbc8bbf7f7237d0c775af540046d53ec7247c0de200c70150f7e7fd5127390442da085bf0367af3ee2f89142a561ea05d4775c4463d821d92ddcf3bdbe60cc776d97339e25dee77bbed116f4da22da6a26e30bdfaf36318b528263d71a2d07f1db31e18421c5d6a1f02c6ae4c4ee723489cee042e21ceae0c3b1da6acf9b2d3298ddb0335d2f75bd9593006f22131c6fcd30f57411c00f4fb95a6665edf27979c6e911387be4322767c88844b431f1120b5fcc595fcff81e5c92a3d1f8a1a64df3e82722c2530a8f817d481d231590ffacd4420607df6ca37fad9196846ea5b51d62f8a9637c40ad852b5eabc1aaabe12a23988161c0ff30bb25851089d8641f11efd7e8e7146030800a750badb7e4ae18910b9c796c1039d656c806fb38b0a3477da4940519c800c473b1dabf5a9957cf236f97eee1fdc002700779783379cb577c7cb3b030cb9e56dc00fb7e7c265317d7e96533ba78c01b4544c1e7171b9758e2a465950559ac6e902574571b553e8c0033933e7750bab6e785ad66290e0699cecd744f86cc1e3a28a2a3f6e6abb557cec7613cc49c33ff13a82b127e2865005563a44dce0a2a114d523f24f03f4b372ca2c40f5203f5858a9d3bc6e97b31600ec94898bd3bbc39d76dc8ee513aa7588ee3b152790822963a341285685c9c8cd095020c74ff3d1378ff9ec6e52d80947f723b438012d01590c2d897dfd5cb778ee793dc5ca7d7e6e44da4c0d9eb2f5a5d6f376081cff9ba45175677dab13255a6c0498c3320b53353ad4c9a4e18972ab2831e9ad7b4608657b1add140a8aafa3d4c923c04558e1e30e362219faa771a5592e01c68b0aa4149cd86873a8805a38f69008f1747e6ca3b6589d5ca524ae4ab40971520ad293c96e01ef592188f2ab977f674cd1917d30e911411ef7aeddd3732fe53c5bc49b72d90cbc09f3fd116e11438e061500c0abf121c5926f3b7f209d86f4dea6553c5692fa0b903074b9829b1ba0d1261ce7a7bd9fbcfe69521f1208d3d313130b21bb95cd5965dbbf549d39079973a237cc393218359cac66a14eb71c414d47587a4d8ee6930bdad911a3a2e72beba01293ae3781f4fa442d440edaf36a75999aa8373e565d65488e42e2f536031caf9e90a05aff63a621e4ae4e7c41aa42561db13cc50b353bc08d8b1625d33e46e6325b3da2058a3b98eee5e0566518a793842f2665cac31f32e523728a45c62cd19012c11b8877d84a0a6cdb0e74dafb450ae330a2317d174bfccfeae03fd09aa4e264f31df64d28dda21578ca331bf8738682faea06b7d45fc36bad03f6d4f0dbfd44d0697933e5577d1ba6e4537a7e6b7a89979c155b01bd8dce5867e287c4c988ea7d748e11df60c9c40e5bca48794f546268fa8ed0b068b197badd674c75db7d335694736869e557a6591859039cf1bcdb35a2f3239c29c3818457d8308a623260e56370861c5d1f91b7cffb07648ed4b3b5d51c3115e4771725986c33512f16735a5d6da85a040cacb2e84147c8af0e3c800fc722864991ad49259432d75e0d729a9587a681ef98d242b4db20bd95df1f3c079d4c98c05958f809cb3fe9d9e487c68edece4c489b6f2aa1887976fade781c44236e1d71de5cf59a64bc8f483384264f9ca1697dc5454c027811d29ae28618af915b62b2ac280d8ae5705d879103afbf99e0a86bf24f0ce7798d61d7efe5ec7a26cc95d0264e6f24300c491c89cbb52b144d873072fa34270c1aa25d3d7b4e2c4520587d9ed06126198fbe09fc6d32ff3f786ad29a4094c015270f10fb78cd4c4646cf851f903162e8608b69dad535a02040e933b1676c64d675c64f21e70428cbe12c06be37838bca025caf060850b217c46efc0f86c9a8478af759f71cd7ec9d5b51060d755c63e8e1761fc1feeff4ef3c93108d3666900ca89856d0c8170045850dda988b1122a50e50e5dd94cf39a98d28dc133457a230414d86c91824a4423498512fa1e9dbfb1dd1467eafcee2078e5189c4c2c131d3c7046eb1777f574ca7660a84a895625edcd856bf7adc61de7ee5806d28a399d54310f33a8acfd6ffcaa719fe13949f2b2af71f2df36043920a3abe327558193ae4022f196e26dd89ba84ca78d7b29e2487bed123cf01f32e9b392731cd0af8bc7735e481f670ee52c263362a687f60ec00dcf561020bc3fccb073de519e08e5c297242d6bf7f9ee3fa434ed0269f7ebab64e0a69c72e693e48204ff1f3e55ad59cacaa88f7cbd226fe11796217ea9eeceb06205cce67c2d12bb05ca7c6eb97d46c743d4a6bb44e4b891c5c32144610dff67612ead7ba95aa5e9f25d5b32b3c55197abb85bae026bf2a252ac28b79eecfe6096b08acf263def8fae9e39902b6412e5bcb3d5026d78d6c28a9823fea6f3b4c30f3b862c6c9ff50686b06c23d6f72f17a17d3735a0a602976856b086f53ca34353ae9e4bb0cc4e4a153e318ff55ca27c7219f7ac44db6f2906d88f3fdc382f4711c6f40dcf25a4e4dac4d6f6b1c809546c234352bd805bd5cb222e031824e07913aadccfb5737630d89c8c78d0ff87abdca010fa12ef0bfb431563d8f582bcb6c3a809af0d6f8a492e84056787288dc7f5f1c3e50b0ed1847cb7ae2ac003027e04d88047720076c46f32e11259137838f68bfc98c55637617a6f6b4aa570c0f182852f01be59242f525a1eb54f922e38695a99fef75a40c7f1140b6eb9665d30c3dff2a948882ac700c83669d8c2cfea21fda7fc2582829ebe923a810a8627b7cc7962eacb7048f2051344e2e656e357d4992d095e60ea0a52e7b4039e535bece44a7b42830b45468752db46c0ffc7250a71b8c26581bc3c89f9f4ede2b8843847a8e64e504352f117f026a5561340765a7901a71221d02bb3fd4c64a65c6200c408ae71e751b924bb92d5482375264e8ef531ae536cffc09c4d2fb3bdd1a2d727e8aa5c428ff47f69eeeb22b65820d1980ea8acf7b479e20747cb207c2a3b64ee49982346200461b8280b5bd1b0f9510214348582ad93b821f6dd69451c41356c323e9b75ea2b891071e1a858beb05e6e7658101b743013801297888ff787758c0adf91a5c3b4767ae292d5673c75a418c07ad272457b4f4740e103e9778bd15bd741511526fe62e28f6e0965b50a829bda3d69a6d4bdf318987a8fa66c76cd228d2e540dd5c4741a095d5b02a417fb22d7ca4292f544243fea08c42567772c28485b514a16c2077a4b9c81df7a8c57497e01cc5291fe29e05f2a640a03262920b5f2870432dde660c4d99f10a2f94b640c972c8c5cf27d4053062884958f496638de088513398203964fe12b51a08ead32600d9e02e4f50b177073b25152e53c876add683812f6ba6cc44c87807939f048baa221d200735c02acdd02c58579a01e5e3bef97306c4b79bc58eab3cb12b513791822ef92b84211318c1566f553f41fd7108b495d87a9eac0817ab9a34f3fd7f1bf94f14ee0a834d0c01340af85086367af9b47d4f0b0c8a784edf9dd8de2e697a7363d4d925402b43ad4ab08a619f6317e666d504fe06a2622f3fe449cb6cff387e49f59c641d7aae56cd5025246d896958951e4ff1ac80e83f35623d37ed0c517612be47d96f1005b21e4b680a6d6dd17492587add0c634f0e67488e10c5edb30abf2eb7015daa3fbccb94d8d81973174ff86760dd78b47bfc3a957977c3a7b39ce558e2353183a50bf9eb72802f3de1c06a7504608a4cdc8f8bedc3291ec6152cbb71603d0155684e29a02d218925086c97ea100e5c280458e9193dd949230f5d4d081fac95e9d0d62a7916e8e271dca11e41485e6aead38da1b65766d3d312fdceeb28c6fcda1142a1f7025697f436fe71919bc47b9e6e91723b6096b4833510431143cabbbc579500ac15ac664ab0d6cf3b43ca2a8d4c086d50b9ac8ba65b5caa0300aa23851c8498471038afa8eed0f78acc1dcae0ff851e530ae22cd32580d46c1d26a3e09deb6ec72aef402e643f63fe0e6054d0c8f413c9ba1a209ce7cac41a742244785f58f30f40edc3180cd98d8136d3e816aa4209d2d46d9edaf44736f1bee359fc8ad2f47913fa4c76dbe689f5d301211b528c0e8b364a790039c228828df3eb460a982b5093f1f47e5f4090a0dd1150440a2f26b63562c8da0f0dfaf186c8b7f7868189726584eb56f0171e1d4556d209b84360e97fd7240caf04885e2746215c7a09b157a0cc8cee307bd4db08ddb649dc1e8f27e3b81bf5d42865ff07fe31f1f4230c11f4d1bd065f1f9f2de940594cebae04412500906897d400312b36f1145f372e23175646693bfede6891f9816c10c2cc122219fa35877b28bf9b92db5f4d77f1639cd12fe69dab78f3b7c52b83b6131daddbf204978b220001590833aa0744408b3b1092c12aec207b35fb49b5dd9eab616093d709a4280510b3bd5b3eb02d8b5d5d75507a33d981cee236ded3df00a67e0bb2b56512b5e0e8b848a81e34547675a0bc88bde54bacd38062981339358f5d3b43e02a5e6bc004813b2c5baedf4449b2f7ccaa56c40889d1656f214b62d79feb3638b936315857dc31aeb6f794a93765dbe470179e4f1e17f0f452540dff1538b0409bdfa08197c51a92418c91c86c097f50b3b4981e293893b82a8fcd4c1e3a693c057f480f40ebbb8e0e5bbe72e07cf8d70d1966672560c79b9362312ac623b859f7c7e6fc3c680808d344c82946e1390c3f94d234451a936954db84095cad206d7a781a89c8fd6b49dba45fc32e09d5705717a8d1970f22cf3b772f4e87bcf483af6f25200a0b6e6bb250ccff9fc4c03aba70351910fd5b42dd293e999c0d3d514a698b27fbd689a62411a68d10255931f0bf940fbb62746ff45ee238a300d02fb11e2b85266122237119c0c4b37f18021f015eb2d6fbd6e58cd3de1ad5d8156c4692fd01132389a3df001b6200eee64b43dadb077fc8b0f8ce3d714cc26a764f8b7a3d8c30ea04bef18ab8f6189687f6e589ecab22b9f0349a61ff695555571a8de1eb2cc07ee3729e393f83c4171030d7321b65e80ea3cf71a1c7740b20400ba51c1ad3b3f122a4f83cbb1021d1846ca5c059477d89feb81d55dc74d389b4b7abd76d56cae32a9506c1198a8fe2a168ce27069d16a0105b19b226404ca39010dfa5da48e4b2ee27545c288d96ed643118a6b0916e459889d14a2105bd6ee75c65dc7c80b072f989e913ee159625eb230b2b30bdcf153092654f06693475ff7bb51d5906b3f9ecb74cc67f46047f98baa8b44a7542ddf776988762a6d66abfbc8dbeff6c3314eaeb9ac9d12971d92a689d58e8fdb0f93b395008433ffc876fa4ab638b874e8d404867da41955b068b59f990f265bfce2466899584d147bb16b96aa568e3ff2efec3ca73a8d27db14142d66577e4a0cf79dd634281bdbc9199e303b374ec928beae8d162c9c81ee96ddd9e5ac6ab35f88c46fb0753e7658b17253ad62039c6f843b0dad17eccc6b7bdbe6e7b8e5ed9a717b9c8c673e9370d21903f4921b85e982afe49396e5bc35686eb499d99cedb4becbfab9442baa14d200f6e8e7977a6db458b86eb143a55772a9a13856371a73b8fc846622ddd61d4cb4fb18d4546665692c91697b9719f22e9ee7d08119f76dd3267b8b9337e836e31894a07bc41ff9a84739a00f8cf91b51504ce4d6e538a214566a8ff2dfd355433fb7f1732c358a4409d01441dca68362e22a257ba5d723f7cd04669c09b47633c8b25ba49d3bb5f0b8f233ebb875999759aa1908368a33d068415394fa2e3481119c00506dd26e8b4e1c6d3177e4c3e75e14193bf933887d798a02219676bd71429bc1d10e176765f71cadd8e0fa549daab12193139a263aba7f24beb642d7b1002c13a759c30840978c4ba097c62331d61b281db82f2eeb29b92c0992cab28bfa9be1d52ce87ec06ef848f6943b6d2b7f872d19753adfbbbe95037d2d4d057872be6993884a612d24eac7fd854ea7e87548e05b595085942e0281cde4194c39d88d7f681dab247acf6d412885346f4707ccf2c5dcdf7bedf5dd002c03001d3bb957538db3ea7fbc793ba2cd37482bbbc3466aaf1344226fa3799515777dc0225e1fbb54e22d921ec33654102ac1e8a2db9b7bb4f745c317ea8bb296889d75e19692f86b76ee90bb266d89a2c09fb02520a1c581378226878b1474f50660f78c3eeaf3c260a4b3f30c2c23f38a1ae8a8ac7ca9222b417a796a021582890c817ef97ab63fbe5466255a0c3f1b684f8fccc7e6eb3c47a435fb8d1cc817421557bd5331a2862134aa7dabdf5e72c4f595aaba36d8447ba617722ec28f9484738ce3f3ff8c3543c9df0120c37f9d303181550530b6d36cd7ba0b719dc0eb47ad0c4c9f2cbfc1aef7b96e80d215f4426b34f6b2ffe33bc2a1ec878d5cedbca4060e06db58c129fb3798ca7ee07dee4914667374f93813afff3f772b6249bd649b781d2941f049832e3771fda8c5fe1d0c891c991778f63a7747cb88b3e05b8b884828885440f2cb7ab021d330bc4d4d0d96ba6e470bf7318e6e6157e1b3addeb2eab7e6b2299743fad1cfcf03fdf026213ce48fac7f91693d229e64f103702f3b1b62a033a274b08c765d8c3791dd4db8f04022f28f57ef4cd9bc71616db93d1d07d7207d3cfa3c83a873491095ba3acbe5da1a05d3cc29f254508ceb6476a4a1bdceeff6ae9f9f31450afa97f31667e1004329f9a9666ede9e806caa4ca1efb2351f7a1af6b378ba37507111c56c2e4d76b5aedcf944b9b6bb6fb793aeb088513836d475a20fc2ef361a1280e1079d5747027a5efc5d623941d4fd8d3579cb17dc42f13e538f5645608cf85ee5895c195325e247e5824e31ec5f83fd6d5b868036bcbf4f2c24be4100cde8178eaa66a034a357c9a1920bc1d70b238ed62e33cf0e37f42a49b8b2f9752433711d94a3e6ecf9dcea9b7c8d4ddb7a7c0058ae04bc61bfbf7a05bc7acfc1823d559be4dd06428cae8cd392eacb5896f2a6a4100b3012a8ae9f4f400cd1c88a02c68eb916021891d882afe5e623dc27fe1abd55d1c30f901e40019d65bc9325c10ada4dfddf4b59dc19731f2a0aae1385ac302c8f5f65aa73bf7a4582881b3d27b467d367ca2530b40864f471b57d13809a7dbeee4461033bc99819a926a084b735c70369f9e61dd347c97a931fa5167c3d51700d7bd79ad25681cf152ab9668dc982309076927a1f85e08e6f551a3f89c10add3602bb2f5e19a12321a0f204f7bb8d8ac5a3be4eabaf01cee1f687a29b7fe33e84436926005e4dc311ab8a44e581cef9c88638d5fb11274d8b1ef1e6e9064f409ac0b992d31e99d324031e09a1cd0f82855921d2d2881f0f5a4207748d10a403a04d465c008856251c91b0f3af112b6afba0332f621c6d25934a273234057948019dad33947af6d962e226857644bba37e0c03151cd3aa38293aa3ace9e2a9bdd16469e5634a4fd987f30612ae45d6a067edf7e51e56f10016a4752208096722e8fe6dcac78f34a975250869359c87ca8eca7d230b01e659769afe11d4fcbe4c964269734cd7da3598e3fa6f42303b19c450f983d1b973ecf774aff043864df2a2d2b31d160b93824443e4907bd8ca5c3f07c7912101510bf13b734a58d86887ea95b5b9baa27c38c487151b07d3381db4c56fe65b49abc9cc76db119fca588c8ece62a6aa28b00273867918bffb8796bcf3f0884287d8d7830e4c3662ac45c1823f34c4e5bfe6a98184e378fa9153b1665be9e9f0fbc610e34754aaf5c86a9f03798e2487d0c61a53413e0dc849a0080dce9ec665a04b63152adf34737d9d3e6d4c582d593dfe9c2d3ace9634555f339d3ee17ae4e295224c6f1643bd99d04eb33ac7416d041e9a496491c241ea668a806534e925a33f199a5cbf83392f4deffa37239ca8e07c7f171358415640ea4683fb80cfde05c6f30f3e8f9d2f15b8c3a0e0ca3b73f95bda3705858bcb4947686fc51c2fae87bee5726642c8b1a8154990961250b6cee87f9eb1a32b3657cb5ceff8048d9bfdd0a502ca5271c0c56e3b237a782496ba620809eb15efa34e0afe524a1a0b56db7c58d066974df6c1dd24a23b4a0a48bff6a10592abc339644316129611fe9c49c0f751c44f88195fff0353d69731c0a444e4aa0f7ee350e1fa18f12e575978e25beee04c2b042a0ea206ef51fbf0cc707e40a904a83e2a19c8904d3d8bea774934f138f720bd88871ced7fe8a1811a68d8df742a608849aab7fd90b95eb8a6970698430a3c720e05a2fd3b174cb6345ccbff282d7da3356d364ea437f859c959b9d10639221c6133c94dd357d45080d9645b1e804ac5fc24e751e624dd233eafd9a4bd46e45faac314e108ec77b940bca00be9caad4671107c91883dc79078ed9d242214bd5696484d8786fa7bb4b303263b8953a8686a2c1f07f9e306d844a4afe6c245fac954f8d667a09b1026cd8d67af985fbc8f969efa19f0b666fca97553a335a06e12df3b583f21b8583a9a79e8372e1a40c805c70fa402573beb132faae42ca492d80fc1257952c24ff524025716a9dd5461c89af73c9bbcec7ecdb7b990a85489c9d9960c7bccd8e745b880cc709e7687027cbf02702dab7260000df6df86ca3314693964a7147de667560bb3db2656ccb2cd1b7493220a9488305617330ccad252db6eb094b581c2a69c06cd773c3c611a33ce5bfbda061378a657d1f6c31a2bd1f6092ee403156ffbb3334cd49a67e47a5ac9b8b751f2592ebe512c3da705d99840d246dae7bce7d8154c2f35f9483d0e0939d1b4ced99b6a47afbd99a0d3c4a6036e7af1de05b4f4d2d1b32cb7c45d198aad21d3fc484e2c0307b89b8628dee366ba2e96b0c2a1f580a38255c17192792aba80badf7e978e30e42b346e8cc93474df35e2d0319199c0200e2f0c168ad9803aa83408ffd9c458802d226956c41913c17490fc5003e00e67925de798b0cda8d5eed7431f7535d0f1d0f052c5841f13396998c49a48a37dd00a4dd03a4d29561f9b311f436fa1e5740ec6ba106f1631d0e652278cac462e6afe78082a2d18d0109b926633c410153e54eb9beb1a1ad23cf6f1e577ac8f1b3546bea0c6d0d01cdb98cde4e356ca1cc2d0d0f897978c6d1a91fd319ed55d914169d32c343406945bf228ba2ffe2c0c7ca5f888b19f9742435946d01731c45944dc6d2c665625f0d6587ad2de39716aa244fccb73369412bcad4ccec3237ad9d0aa63009eee0c9b5a0f334eb31d73e415be6455d70917d28b6dd632fa973a1a9c63df2d45ecf2c4a921ffbd9b4235e441a568c8abe79927889bcf7e7ee417b2f23d3c98eaf770ea9cae755ab006cfada004af056026f401e18ab5b5937f0034641c92b417f59c627ccf2539f2bf5c4064767ab39ed6c01dcdf8594085856064481b430f8b2cc1c3d167ec61f22cbba8890d5f17baf63bcbdc505776797c635953dfb126624cc7a9ddffd784963de0f4f3b300ae5728085a8e8f2c2ef9618598eeb1b8daf5b31e6eb67eaa46f1521e250257b1e3188758b78a21f7f1144943b7e4aee978bfb1dc28a95ea03b6321a0a0e6b5f53cd1d10527d4176cf93525085e97427478cb5ee6abb5d6238462df3a2041b9aec01d99f20219a8f00b011409ca22a7909f239b1da1580b6edf8571af32ef8874c0a3780302b83c41f7ed57e30c5828bcf4bd04b8d9b293a31cbdf05516803134eecbc0df37d697382baaf8c66f9cb06621c23da3ccd14edd41428af45302d0a0ca353fd16f30f8f318c0fce43a6bdfee2c330f8ff0a80b91f3273e37b6484baf67a1a3945a394b944f3d165931ea5a4858c560800af2eedd82e9c3812977377c6c6339bad9351e3877cb5864743b44befb2a1a98708016247f8174b1413d82efe76f887630f660ac3039f4101458cd25b5c6a05e7bf31024c78d36f3ca53b5c91f672a964956960c775e80228c81e73171e5ca34c2cfb15d98f78b270a93b82191a0ac5482d882884c4eab21ee7f5a3f3158fcf6df191a93ce10bab165b5cd33eef7b3675ff2b2262ac5f2fc9dfaa9f5846e0a1c8eb23ffda8e459860426f1ee2c49a5f4d14b62d80d48d6400e6a42fef3c77862148924258fdb7fa4b0be1dffcf25cc8ba475c7d3a78f051c20350c4be802608ca1d4fe8afa390bf45e03bb94846786e42e03ccc7b7f842cca21545df417eba1e7469273ae38c4e2117f81f772d1acb24c4acf0c707a600654b21d96f9ee1cd82577d650c46b2fc243fc8a92e9c4377f25c8256b981afc17281a2d977578fd6e7e5ad2d5567b2464996508dea7544578541efe250c95fddbf958a373b80c57eb1d04fcb13b14cfb3b5b8bdf76e83713d1a4c320406af41fff1ee23e906deaa6c0041f1d9adf5fb1bb56237f607866b7345c6557bc119774f7dddd676aa0b1efa4a5ba8f10d7b6741c67311991d239e2318db93651af27e587d3f5604f5a0675d24b71e931b531c7245fe27d248d5d5ae8a9a64d62b341b039f90ad395a99f7f014a3daf22f6b0d24c1a38686a055dcf94b2d8572cf6d382444cd31ea8af32c1bb011a98b8b69df8cd243511310e481bfdffdf59dc9b4013ce32b72a29497f527c0be9b2393813e7b55cc0f5a1d3f9037950fbc53af8b0c185c411f5f880e0deaa51287a719fa18b82bfec6be3c1af461ac16f981cfe7ca86c60f8110e3ea286fc0da5dcf294058f73e5e3620cf552eb074a766e2b054493d12341c299afac1e4037213af44a8eb785b2b4bcf3e081c49b9685d3e7e0c0b7d9f4bb50861e4e97a83216cd1ead54f12f894667136d05abcaf09dbab318ff0904bc0368e3805ac82d7df57b57a76ad048f8a5c11c11c23048cb810c0515f63d8e3b960a64fc4d09f8a491301f0eab4cfcc505a86df794c892c095af8148d84b48d4635bf7405dd5ae9f35fba1de27fc18209f8b924e0d9be53e172ae94aca3bb26dc07f650c12614c479b1163dda2d08625c1881272be2a36c6e63b6412a67d85d9452e5a126c1f46d699b1b1753ea684cdc30a4777a4e520ff0610cfc484ff24f4d8c29219f33f660737182befa6e623963890ff53ca1e3689e9a0509311ac7ad383a17f95a6343234573f5c62970c9c3fb948fea27f995fffc4e162b55896ed0526e9fe392c077da2082c68fef00e433168ae3ae8fb6109908f0ac9b6fcd409d415e45375f0e6213e16533d626911fb7b5360c10e328c04423a2ab6c106f94ae59e35bea3903e235a29f4f10cd642ba33b09bc7a88e4903ab6157cac5b255e161c43f252ea6d26453128a18f4cfdf3b68e8e7cd61abba085f7c9d99c20f4b9116992f3eb36331f599f6845c4a8825e3d8002c33164799075bc1f9f3373ff49771179ab89fc7aa56cc60af0d474390761114c38d71870fed3fb9462bd63c736966c78b45263d7d26e17981df2e1baaba9dda2b24d2bd5591fcf2892f9984d128084fca1475467d2d1da996848948c52f8e7de949b9e03cc46612ed03d1dae1c704d17998771ce7af4ef8fd1188882497323dae7136732c6332217bea2bf2481ea3b4e8a7cc0596333c5a5a274e3f9e29096d3ab72b3fa231d8415b2d65cf362ea20bf43e3141e62f7c3312b119d79d365a93cde83494ef914afe180b7178864592e3fb6dfbeec3fac5b8bf680ab6edf411335a55e939597d0692c0bee89402384896516361ed06470a3e325e9d9a4279a1cfaf0652067a2d413f0471c4220c5cf8e8291fe8369f6ec633c9f7d9477b07aa6a7a10bd1da89565df506f25718716080a2a123678bd00e1557fce34d72793f8d1885c05f00ced1cea309cc8f0c69e8003675230a0ff32a552f8270cf5dc81afbb35ecb5274655790030677e8dc96474a0ba38e6661b573a873f4b0d7d4f75b9cca52cf29005222cc8211e194710899691afbc33017e9d5948c1cad5592f100ef1c603d7aab6c80e486f191795922c5cd3ba61dfe254adb74e76fff95c03f6d2e423a9c62298941ba8c1a2a42a1411005f574783983b6fb1ec8758318f35588fa83e8dcb112ad1558838ee0693c06bcbb1a7ee5461f882c292bc6f415ef068a64a889c1802f195fb3db1526494436142a5d3d894e606c4c33917721928001a5ec743fd55714cd366a71774b0df8211fde296c86eb9d174b1347cd51ed7fa73309e72edd4a93e3502cf31b1bd00a71e6a0af65dc8e4030e7d4a6ee469a0c67a9bcb2e24873e972ddbea021d65ee11a67cd296a4e365a0fd13e1ded90f219181ad20ea7d3ee2b6e773fee709439beefd52efff063776bf24e57f1cb9e92edb54a8bf904b46f70efacf844c4415ecc7413bf5711a41c1e12f9044b6a882f900facd184f0abae86dfd84de4169f4c508162492d16d858ffee2df7f7c4bc6ab0b45874c700c4636e706a283db902128d610e55e1dd3c41c694bdc988cdbb592f6445495c64cff52e3e37235c3de85204f6775c82a0810f39b0968993e4b7b70480754cccf08ce92770d60062cd4fd68c0dda4472beaf89fb2cbae928892146d4643b8fa367f503ccbc0301a83c190aca98f5c878fac41bd25c9149e0cb4c2b6247dcf99f859d307ed7bd2af9f3988b1079b751967558c42ef424eeeee43e5439a00d16aa98f9a727c699f033a7cb2618be02738c043186eea545597050865c060f5b0a154e494974daaaf0daa8ac4c2cfe1dda1074565c41412902e8546d9e752d4c352a0b4b5d1f4ac3292b687153ad9fcc93b2c3035532c26a96b2a7e69c5f982dbfdc2655cb3cd1f889a2a08bbf883980fc41b454b33572fdbd6d9ef24cb080a03f8bb92a150f9a53c12f4b393051e0516cf233cb9583abe0d26385acbd5bc1e9c67755112a739b9534f4c00c9a55c6aef0621824767528b11e3c066c9adb185efe676fb347c2a860ebe8e58af54d300a9b304d17b38fa486ba36bb7b61cdd655091451bf55d2143e1ec897cb62a5d4a3456361824e428517f5af97a77f69e580dfe6b82842fb89d0c49bd3c5f36922ea7c890c55de4faf2e7c02282c159d5734bdc4c45a84f292c721f2f36461b9d5ef6c107663cf30426d4d1f3160d3f86e101dc7f1642b7b092e4c2ad91c7b8408935282197bf974c975d201e95b79634e5c33864eb958273f781cbc5c6a11fccbd9a306e048c6d425338965f9728ef992a969720747325af16b48884f3a87aaf8d438ac9bd906e97b82954ace7e2ba378db35179f949bb09854e7705fa1fda630ce122e880b457c68670172050ea4ca02f2be8743135c77d9e740bd26194ea78885476dd6ee42125b30eea87102aeb10c3841073436a7d1402c69206f4f0802f9baa8bd67f39d0c27de9db3a5089818a9d2fddb2c348a2232dbf7daab43bf5cfe4d2f73f32a2ed5c5fd29e4a36a25c8eb6d07421c6de2a7f485b98f9baa861050cd7ccbb541368f73668e93208312138a528bb2336d5671a429747517c712e452e0cac4c18d9503b5718c8d97d6eb3a5b8e5923ba106039bf4dde3ebf2a01879ee3b668d9e11ca3851ae06bbcc74802c2393c0c7996907342b4ef20606cb52a39cf591b3fe72d67a34fb3f681de528ed701f8a40e0359db72be58f12c337f8bc7135ad3f3f15385a6148d2255d7a1e5af3c5387d494c6b97772bc9a7b974c46576719db46d5e8ba12e6c15fb3e4294428170d9e4866331eca07f2872ae69ecccc33456ac6855200a7173fb0aa4fa828066652ac1827ea4810e6d5daa94aa30e134cb9c841e6d85d961c2d86c38019dac91487936f77e5658c3234b54addab6502b8870e91df3b38a00b3e41417f55f49b9a956ebc265b5722dd684eeadb3ba279d0222b78964d9e685846ea4a80ef7d10f575c8ce21b51a29e81aed1a935341ad84cf4ea799281e035fb2f6e81ea599950509d224b8ff372d1997cef86eae563bb0a3d887a737045930675553f9200e48c9dac89d21c1a154bbbf54cb9703f29899032d6516e9dcaaca79c75d2dc761816fbb6875ab6c22fb79b9917451dd6bf9ebf0ca8ed822489056a3befa110cd8f5c95225395cbf0157bead8683939f48131e5e76ee3528a20d8323ae619288b8d77dea8b9964b1c1b92d2c49c459614ad50190616339b8281391a5b6bee82f2f8d098c9bbbcd9947caf711366e345c126f6a2ed9852eea45175ce0c0c07d089e70b7d8ccd3fba09f3363311ed55904aeeddef3f00dc3352bd18e24c433ec658dbd02f3a013893885adf7b96b4e1d899bcc91e0806790dd4978d8c59db5d075f40e5744f576255bd7816d8217cfb58c35e4a16f4b1018d528fb3468b5f4163969c78f45a0d2d5a32a281fe9c492870eea5cbc3d9d5f151380e46a383add0c5cf1ea679fd026ee68f42dbbc53d973cdcd530cb381aaa910daee7822e423564a6a757f0160745033b92854ed4133523be8d1683d369b6d980fc590606b3ec3a3b201e6fa64b4b1e02d3d2097291e0f54b4a059519a7b1a4347a3cb4e288f8125d357dae726638b95d4136e5222ed1cfd3fd5c62329f48a0fea5a1553a2b14cba21f55dc969ad86f1d28246d2945c719acf5c590412df332ee4e4fa1dbf6075c46b058f1cd5d873c67333c12c91662062fe879d6aa0e4fe330ef1ed06c92e7a9ed6f74d8d704b79fd37f9343e76962267ce9b014d51604557c3cf429fb43690f4891801fbb738a2901c86a64fbbb08825c7986052786786dd5d414c32861f2ae4ec2e0d0438c6a146764632a3dd25e4e3791f9b3d1e35ae7f4ad6cd17f0e004475781549a9c971954390f3ef0b7d310ded069e6896381ee5dd8183f9d69adce7f222380d918fd70435e5088df77150ac73d820c7d353b6a9f9a8d2a53793063242643a17385555e0eb9163b147fc9be7e208fbed7406b155c4336afc7dcc7a671a89b7daebb6a8574a95e3af8d73b7ddd688df10d0c284a0afe6a64299ffca6beda704ccbd12b24b6176eb888cecf2219c7b0c04b495b334cbb7fb2d82648229856f71419bc62f494d51b6dc9937caa690394fe598f3182d11fbb527e12b1a8017aa004f64ad4dd2f9721d88a2493abf35f94b65d025107d0d1c7a466a08e90ceac13eabee18337a8869507fd1146680ae5c66162aaf64134957c7592c485eec09a15fb31a56a6ce48c54daaf542b2878383b7687b2e63a7f411eb805d1700bfc56d072c9a651448073615590c70eaacdfe158067512cc79e6e3261eb13d55663b82b65b18282c8508dadc1b36776b196890e47cda38bd521ef61fbcaf201f432218df35bbdca3be2280dca04fbc575b5fec3bfb4e45505e6f4e03dc5a2295f2cfdd929619c3a80a92d3ca9e799cdcc6ddc2bba412c43c568aefe0909857824b6176f8728627b898e99b1683b124c65c82bed4cf8e5f177a87ad263861a0ace1efa56d171ec5c5bab5a7d70b4b1e609e70733ff1a72eaf1516202ebbb2517c1ca2a5c44f9f7e72282127882a977cf64faec89a235592698c73bf6dd6afda36cc6f5a9f31220518e956b3d3ced3f35157854c4925006d22bb394e17ac656609acf1f69b0e8c6ca12d63b580cf1336f733e34ce992a713ecfa3184e506c80be221e500c383112008750e509a883d70e0875aefb2029db9689a9048ab9bd9a1cf5955682669f85e164ece85ea1b3296e62edd4f22654fb26bb4758339b66f242faf49621dbb644b5a6399ca344099d9fa48e17aa0bbabc6f9f2c6dd1147dbfc777f53df961eed49ddf126583585efe9023743f14232b1347a183dd76f3a45c5b0afd89896bb12d5b702ebd31061572c10f894cde200b654171027b610c193ebd95e82565b5538bc00f69769f409b40709f969a6530d984e5ea22e0f689912b09cd6f796954e15e4e91fe09a862fb2d35fc38a34dbaa978511e74d0a26011b121c07ff2dc1172a23ddbb6303233f2ab7cd7749f54ff2723673d173225d4daaa092af0b4158830397061b2b27565e1ea7fe2dd456994f32f5690afee26390ebdf8dd67776d0b53b4634bf81e2fe734580efaf1b8e96d5dfcfc51862e12273270167a2c6a8ddf808f4f8459eeb8c0dabf70f39e347a38a943e073375aa435a8b6e69bfbee85320a82952e32d3839f56d31d647bde2c52422dbbcafd0d416c3a80956b1a946dee704085f4266b0a1ba43d043a1a1f9a5a305c15351d9981a263f758ddd46dd4f5f7dad545b35ba6d52a844a7eed36a439734fa8ac5fbd02c2071e99211c00030b60ca0084d1bdee9c330ad85c00f4001e6d6bf884261f632ffa08f661bf35db96969a889051237bcbbd777309f508dd0914239d623b3a15fde41487a563d4bbbd77637b775defcdfd60c3180cb3d664fa51287aaf77d7bb1b932fd5c5342a7805bf53d74d267b8750f94ad783e8dbe5556f5dfeba3eba4eba56377bbb6b92e3e17615c3ac35997e14eabadbbbed9aecee8750c117c7a651c138f6e7d5d464abf295df2136bc826d64fb9b7cddb03b664366d1adf683e817ee21fa867b8c7ebdbb3e62f2752ac2a347f96b4423a625d2e57997bd2eefca7e5d5e67afeb9862ec2a81bdbe1bdac8df65c3dc1f3f44895e5f595c9c9edecaf920f27e9663ef2af8266fdfede554e075f1a86511fd3159723ea8264ab668717c1aec42e52d4ec3f2abb46039b6d1295a5bb438cdca555ae0d8a92156b00a8ee91475717f69bcbe2a6f7ae9d977baa961ee53b2c6a12a571c62217682435b42e17bbded1db9fbb7b82c5fb9f82ad7050f5c02eacaf18750b98b3777cab1727c1a95bb68f17b9a16bf2dee7f4fc3f216bf1f82a5850b7c67963ec48902c22e30feca695c541bfe62081bf00b5c6f8bdfe4ebe2aa1cdf95c764adebb29925d75d931c0faa99f9af7c0896ab5c72a71cffca698e574ec382af72161617c72c180fe102636c3b557f9323e7ddff342dce72f1f1102d70c4bfc9337366aea8dcc7c8d0468ec72ca52da9948f3e28d38629603fd891ac795506512a67c823396229bbf664a98a41a9a44962cbc21695d4c28ecc17f9932f4b620a841ab139383ed6c7e463c24c39312726873227f644ccc7fed4253144512cc2d86861478290c8217246fd15521b65117246edc6b2882145f4758f75aa5fdc920c6c1879ec44a15e8c883076a6e2651147f0a1961563376e1cfab8f9aa17e56a5cafdd5267bef4adeb32311cf3b22ade262a66091c8da1666248bba34b84b13145fb6e188e9457cf7c8142cea0aff5f2c93ee8a504ebbf7e4171f9cc970b27d72b93e0e8d4fcb513031b4a9e70e664b90509836921ca5c4a228c4a7ece0de4190653155450c8533221cfcf9c1a8f1d003aa6145812c8224c66b060c8510b39c6cb0d48184c6a000231d65c3df345093e3972b561244f8ee461e1c99949266a5e20b64c0c3420a6e6656a0a24301561e506e6bc0c19b173c74513368463a7a475b16da2304f368526b5276b9e4dbdac96b4256d29a93c6273e1505a7d35166874935c6f0f36543860202f8bc82132070859e3001ea6c6b14d727cfc04b2a625130bb547faf03411190a9b086661d60ed9b2e843968caa92216710892a18a88e3d2e35b0d1464b53c5228df1891d72bc8b0c1adabc0559289040598749fbd8a64c122986c35196d8ba381eb24ce32dae064663a634d6fbe2a519d85035b34d548c14065bc91c311f1a04ad5eabd7b5ee9e3cf29440a84b72d7b48f8d3eefe8e3a3ca99528b3e78a6009d74e80f5d45ff4c54979460c3b884dcc74153f3c3290575c91591bb7265b3edb4ab8b4c14bdbcb1f796bb2524d8f0cafdf71160e3b2648dfa0dcf5ba169337368d533a07553c919d9eb89e4199fc81816976057765d5886165556bd33131533511876591aa31ad81e206a7834cc77611bf3a5071c9fbdb65374eef0d2325ce24be8b3ba648c7b56ef4dce64c81821cdbae488645765a2228d5844cd5bf333da8ae1409bf8bbd7b78b75915c37ec93899cf1913e1fd2d19641d2bff9e678c832094f2cd4209d7b64428166e9922e0ac644d1e310e110f349c0cca523368c3d618c0d1c34350ee01181eddee9b7399a30a589053943e659ba71a6b267573681bc11e95abce88626f0d68899043e720ba8f524d2eb4938147dfb453a463a7612965c0244b71fdd2f8b6eb2f5b66fdc76356d6241a289f96419a536537b943342df5ef1862d19437be56af4e9294743db4626194389ac6a9a96b56b59bd1addb6ed35e837bc4d99b357ad46f6d0bdc9968c512d2a35496f1039ecd3c890af44af9b8659c7cea4f36e44f6b0d570e863b368bc62be2293f9729d0b5dd355f944d5dce1512ed1c0d277cc0e13e67b3daa5e1527cc37fb56c819327296e87eefac6bd866e4bbe876e02b26422126f23562f6f0c9be07760f63b7b7b8b83d729c8f2d6f585a2163843eaff8a25e28f039e964cddb7028a42787dbad87b887369abb6fe7ba6fddf6cad5e87e3d05e85444ce88b9eacc977a9f1cba538a89a2efeef49928ea44a697c3514262c319c47ddbeef6942be6cbfcf614a0f912f3f6530ae40c99b7538ec66671b766d775af31dfede4add3e6c6d5d8cedd9bec63eee40c9f522063580fdd30e58a6c552443e89e2c57b161ac6a6dde2bc6236f8fa21bfec81b0ebd87a4b6d96fdebf7ffde1d0a6e6efdeb4d8fbbc57aec6e71d7be7c8193163de2947c3ebc9db75514dd4f579fbd25b6fec920dacf53a7f5d47c06579de64e27d2ef1de24f0bc7b21fbd0354dfbbeef35e6bfdf84cf1ef7cea6660e6fa11a44262e59f34298bc4acea00fa9f6cc85661a96216364af9d3bcb4e391a19932caf092b31f19c3d7b942167c89cb964a793f220afbd86ccb2d06916bac936335f58868c41e911edba76dd38b1df647ac3f3c8b15e77562c63a63b4ed98436fb864172bce24d8eb74920634c26b9df37979439a031a714b0886152461f9b2c420e298249e3585da04523489c884a62f013951811224f6c3373054cd9c694b3bc917202bf028d2db438b5162da24c8b162c2c518685656525caacaca8a844191595d329ca9c4e2929512625050525caa0a0984c51c6643a3989322727a55294299548a42843228d4623108c32202812451991e8fb3e6ba38cb59e17653cafeba24cd7715c94e1b8d8b26db165ca685a9645992c0b85a24c28145b30ecbaa2cc75595694b1ac5aa34cad94764799ee39a3cc9cb145467e5ede486909c104e8a70a287c789e48b2736489247472884821b26c92c515414b9848f12325ce1563d12bd30341a91461bce28a44b04215991e063d55ec9e2aadd78609c33353071a4329c5e982166d3062c3f853310cc37eb09f8e91e590962c8324905c226be426251356e630027b2387c8481f1313132fb52ceb9a40b5704c471965bcf53246a5ca22d7371614f3d496b1ce6add1c88b0b5946d620e43582343b055080707e41a56a1cf4c17d4bf19b1218dd9b558b3cc14c7999219111acd71c26c745249295529820d9f290a063da57f31bd2813e7fc8dd6f5a17b93fbdad48c61193246fdbc8dcd46422ddaaa181e949cd5a2966559169667a439d0faf29a942b298388af51e6ccce224f6b725f0338808359b81034777a16d19158c43c859846e44924cf2b7c4cd49c3c988e649922c655cb25661892a3e4287e01e3666640cec9040b47004a120435ec4451038b0832d87440822c18c18a220b491822054cb010638cb2bb37dad87c99b13b7637a594524a29a5ddb17b069452ee1b1762f63eceabf21095efbd76ef09bd61e9cd25a0fb09c342144d0e4bf701239f4e3b03418ce9ef3d39dd996949890a9ec8f5f4c5fbe4f4c43be9520b96d2574a573979a9f4caed78711baed9303f14141b504c9fe92b5d7b71633295dc0fd4647aca0d6f72cab5de5ea484348b5cc0604fdebdc9f4dad47cc232640cd3eb0d5db2e9338d82e3448d6ebadf090e61e412e9c689d2bed1b649ee878d3b0fa1dc9de3be594b0303857af1827a1e087ae1e87b41a905bd172f6e9c29cf761a0d8c9b0dd7d36b2fb237fcf0c5855c476fefa581a59f3032f7d0bd4b031bfa80912b07235399b78d865b16343747727cc46e2002d01460dc4004202964f93b6d3802f7892c49c581a3ac54ca48230b8a3e8d52cef328776f87ca4f1aa651c19ee6554e3bc7d1c44e698f9daa9a17d6ea795ead9ee9a4e4795da779ef3c4eeb3c1c59f4d1e93c3a55ff712a1de90453b92715fbbaee386155a72a27e26ab5a2d368441ad9fa5315590e3c5591e5b8d355c141a3824fafb59eab1d47e34315444db3548612c98aa84a011d38ca4e4e4a99157db5d6d1886447b556558761d69a4c3f0ab579efea3f6b2b0571d0cdf36a15e1eddfbfaf6a15f7b0b5b31da992ba3a1a8d46231044a12794aabe4ff483eaeea24f54323d364c4a8aa9d4892cc5a12a77e0c88a4ea3118ac99e946c6647d64a1c22eb6d9b8a7b941908c2714284aef34cf5dc8e2ef33653ceb28c4412812392c7ed40b9a97298060577f7c07b60e76d998769baaeeb700ff01e281a91482452d7913acfb3342654d7ed309d8b3b4c184b795e77af13bd870dcda277dde8ddb59ee79148249287c3eff624d2f72e7e64459fe9a298ce75dbbbebdd5e2e9ecb36d3fd4183824d9c0da5d96a48f32b83165f70a2ce3ee3911b87d9bdec72cf3e10147d190e4dd144b1567bab918636b0db87ad4ba4087e66a6b86061b32cfb68a3413c5b20e5a1e5ded9ad9769efd7d6b2eeee5dcbb2cc46f4ce3dd3a8956937d432dc63c4d1daa36c949548e006825a9659dc0dea43c85d7b86411f344cbdacaf69a73c3051df1eb8043c466c27e7dd4d33ddb3a311062d16711d531c87439baa51aa510f8737dcbd1ab0dcdb02325b9f5a9665f778e4d6de97bbb5a91aa59a46b9db542eb4a959c371cba1909ce1117978641b7a808b92c06176a2b089e298f0de81af4cd64e940ef41e078410e00d6d3278d1fbd8d75bee3a3777cab2efa60b66a2ce70e33851deb6813eb64d2385b4921dcd9ef49d466f9525adb7eed4022d32053cef74020fe2eddfd6ed7d8fe0d69d4de313f813f8ed21963f7c02b18d981dcc0e3ec3e8f7ed8636fd6dddf67e05718fd2777d6cb97bdfd062afd495e8bb114923913a52e77d218fd49fd733f4ee3b9d2fa47fdf356aff99b87bdb4874b2ef814dddb9ab8936d2a5fdafffbdf187c1cdebbc5028cb361105414a3abde1e85696652f1da4b7fb4e0f7e7be9f6f840cabdbba29344371c611f5bceb230fbbe7df47636d93b78698f0ed35b1f9ba5f7e8bd631f9b8fad1d00848d9cc1143370828f2d5b40ce608a194c71b0061a8eac659444414ae9290ebb6f9556db3d76c39470889146f560b5f5ab187b2aaf44aa28f444d64fbb77dabee3f495ef60b94a47e9874f1b5ec1342cf766da0bb7c74feb28d52a3e59ed9eecbf6b9dc9844f169f729cbe729a130d8eace1d0541fee2152a9448f128552cff3a84769ad59adb56ef68638b2cd40100c5119040f5ef35431df907f1cf81e7d2822d2e9f41da313dea182a3cf36adb7793872d7fd2405e524238128267b52b299c892ac5da17693f1047e74d2cabfbb912ecde923f0e65059b122ec43a5cdaddb56aeca4f9765bb3954f0cabd4bc382e35576e52aa759c12a98e684bdae533e545127c7f88855953c8f69a284e0bb2190fc715c08a4e32cb5f6d64e6d3be9edb449a7a5531c8a9a36f77a8ee34eda28e57ef8fe59ee5e73f6961702c99cd55eefa9c3373e0d73d7b8ca719d394cb9136cd35c49b471b7d72c672d77bb71da6739daf566efbacce4c9effb6e770bea903d72a14dc665dfd7894c9dac36fc914db7131c3de68ee3344dcbce5dd2b37b83c4ddeb6e3df07e3814619bf6ba73b7bb373ecc7157f36c9ac33e7e009196069ab76d17512cdbcff4774578b30dfe7b87b189a216f7681bd2bc61edde64f086417253d228c3dad4186b1c961e47f4deadb2ebd63fd87ff8068edea3f5bebd36959c0fdb1b9fb4f749c3372cb5d76ebbcff2287e05efe11ee06ddf90721ee79d50d268e4f5d6dbd6ed7d1bf5d6bd511c1beb9e7dc320b96d08247b07ad873b7a20f9fb4c77789b280f7cf70f6313e5dd7bf779d47adf4bd2edfb3cba7db4f33c9499eebede28d5b6cdebee9dd2eddaefdeb0946a1dd5a6b6d13e958f2d1f6fc1d0df39cd0b92696d8be93bea7d97dcbd5b9a17cf8536f3f346948eecebedb52e37ca9d62fa10f743fda9fb0e949fbe43e529b7f76e48b377eaf0a7f23d05d7efd486dde3f70bb4debd4b6b101fbc537cea4ecfbdd60e9f72a0fc741a144ca375df431a3dd605c9b5b35564b75b7ac38aed462da5d45afa7978d451da7d1ea51ca59bd5a4773311a5d7d2c7a3ece88f6ed6729f9974fbe928df7142d9918225f8a8a23693bb386b6d7c7d77b96bf6ca0e7ff5745b7fba33fd598cbbaddeb54fedc41dfcc87e23909bd67ea6439b69efccc2bba1c69dba7fb728f7f41a84e68463a3fcbbddcd91627f7a6bd7c65d8dcbc20c64edf494d39c3c1a149c929e6ac319b50182591a24bf344fa8d0dcd5b0de369bfd13b5bd5eee9e69db7ec8b09da82d7236d43b098d28c64a1ae91193747bf60377efd96bd753876f70d8c391defb7743eef16ddf1eaeb7eaf71f3ed4537cb2a7f7bedd933df72db3f806777a79db876da8e5e11ea25bdca3f4ed3393bb9496b651a4a40a8259b6d18df39e7d0343d9768fc361dd6a287ba8de1f39d4358cc966fa0478b8628ab9eef7ae7613b5a139f49885425b88f36e2fc7891eefdd74b14e79f8a10dcd75bbc6dd1ee063ce32108719c8e049f76e77431eb9b321eea6100e79944e4a38fc91edbf874c3814dd8b273547ebb32c3bf8ed8a0ede1ba21fd9e3eed9789b6d34ecb7cbfdb8c16d59e761198e1385c30cd02c7328c3a1cf4c798ae3478e8f41fcb0a4f949f1d988c9d7f7e9d58ae5f0d73729babeebf2baaeeb3c7c5dc44d527a123d985db71dd2932befdde34a7ea661bccbdb6818ee339d92ef6e4da77870d8466c628a4159ca08b4e19015b6626ca27866628c266dd22ba48dd094c5b044aed6afc6a185758e8480340c860384a988cde9db68982cd8503ee1c08ee1207285fd66c523ca707986308f4ecd03e9d4bc69e60cb1c252175fb0615cd2d6112bc051a2042272e844d949a58ff53a64be504bbb8f9462be08315fb29989f326a6200b64976098b526535c92b5deb488fd0822c2480f84423f3a4513c0c5a01f5186cbf4fa44a04e94b1995e2a1165ae402f8970090399864c1fca3164fa5076215329852ca74842a65c963ac814479fa892a9f5b09deb126fd7bc3f36943a2af43b3586c416fafb043b846297f499424829268afee8a09a3cc844a9a53a211d580b63132575e4a9951b1882944c21043948304308da27b70fd044054d54c7051b4aa06eaeaf4929e62402a353856c222ccd3e5186abc2a2204e27664e9461c9fdd93d6530719d3913d55fc9819539081334dcb507d98e8368b72692b9c444293175e6763bae260f620b1aacfcb952d83851f28a896a30581994fb73620924a1882d7d202020a03ec6e44661dbe78a4109a494b25b36edee96e10e748724ace1880db15c84d6311912369446480dc417cb23c8f4024654c7c49656c2368e4e755eb9810d634c0d13638d189638606b20a22e53080af632c1cae7d0215afc0e98e4013be325cfcf2000888619000160dca0ba005a1ee3383cf5500eb18e43df7ed8f8882d3387088a066c3873a65c71df68e623ca649f8fc9e1f34088ac8cb758394bca63343d4610dfc5637f8fb1616450a772b827ed3a3cae64dc93864f3964dce53a72b80e39dc25870bb1e1321e5777795c692ff029870e77b90e1deee2722101b80e38764ac33a5caec375c0425077390521b3f65006659f993208c705c6f8c696f95f20266a3e07193776aa050e51319d9a2c388c91658c1cca1d142a0587bf099770682d883fdce1cc0396c31b96475c1061b55010190efb584e177ca8b0c72bb30a071c7050a954383cae04704f9dbaa7c6a71ca994000420800bb1e1a947ec12464b4b0b0cd461a00ee34202f096c7d50df7249fc3e32ac63d497cca11e332aee386e7808500e0375c0616c2040d007ec385d8f0183711a0c88c1d260a35800be3dae8d4fc0d57755b6e8c8bc3cd38dcb852ddb88a71e3ea861b572d37ae60dcb8ba6ecc03a83153b7621bc6c400f4c53200ae0dd7c5ec7a9eb042cf135638810f0c0cc330152a8cb1d1c34f8c12248001a358ec819d748a9e3040a6101335491e2801b69a31cadcc0fb94353632c6943f0d037e8a5aca020cf96a7b289bc8f31207d200db470fe5111964c5555d49ae069781c4969969c17af77bbd46c05bd7c8106c057ebb427440b5fa3ec4aec003a1848a9f6c3373c5272c6f39c87ca9a71cdbed4fa1930e4ff4791e0d730a611ddbedb7db875227f450e684b045c2d67b8b74f7547fb3e2eea9e209d07057756abec34150d8e3805cc38a09eb71b53d443d5b6df71e9aac0d441932e4c981f842447cc9260b268e9c6123a708220c378fc8b3a661e28a1447b2c995a27bade65db9d3a91bc496f9eb4a2457267165114964a2e63c42c3b29133e4084a105b72e8ca5c8361d95a12a2c2528cc3c6445d2558f9309b1a0c8a80c4bbf6ffdc4a7c7a1082247890a80286945236866158cba6dd2d553ba07aa28ffccf44c5989999989923e4e4d0443125f24420030e7549b17a58d775cd5f3c72c7eb560f375986cfd20b368c3e34354772297354a2842a72d330810d314a4513ba20a2020c83a80917801f20428250980daaa0e9a805d49452411c74c5c1c1c189a886b1d13895cab91a89c086126789d9b4e79c162a478e20c6e80b4b1c598279491c216cf8bf982d67cf77b71665a62414d8a9c4199ee059459410d8b9842a46a0b3924300421474565189108ec0596975a3777ae0034728b2babaf0269f6e5839cc40c630264a7e07497a70566d25923f471ffa5b3fc1d573f55cb5d65a6bad97cfce5cd26dcd25b5a7f6486176b7a58a735e2eb55eb556975a65b89c40c7d2c9eda3840db71c755a4746ebb44ef5913b5902c9242a974deec82676e4ce04ca5d8449c4c2049a4016106850ae3ad3886d64a241330434880659249e39ada83ca8a1cd933ba886a7791ed03ccd9306ec48c67ea8851db17ab6755380381ab3bbad6e7aeb5eef3bdb6a2b05a84522c8735a279d2b03d9c2575b97b6e0a433516dd5478aa38c98b47a685c92650e8fb8e4013c2a0f2be877af29b6cc6b328a177cc0a7872592f8acb4ee001337f05969549ec00849f05969b5e7a70547569ae5042c663064a55d5bcf112772561ab60149c1b3d2b0adde134d0d129940e4089e55454d948cd911988004243b44622044d5348a61d8abc51e0a65c65e6f45f7fb41cb189edb7ca83ececc8a6d990b43f7369bd1639ac5a4155c8dcad19076648a17ddce46928c5d912790b442c6980f61184603bbee0eac0402ca92abd41a4111180cc13ccc091802e8ad6953656b94a996dd588f6d974e1486d5502814ba7693af4b2e4e1476b2b01d65d942a0efeef3406fccd7e79b9614b8ac895dd69453f6b02c7cbaf1d97362cbb22c8c7aa19a2fd3b22ccb0aaf8ba27f81cd2dcfda018cca4a9bd2534a69a5b23695b9be6fadb5ce1ad82badb4d25a2b0cb6a2d9a78956276dd95d67d3ee39273eddb8c2be2c8cfe4d9b36a530daaa1f9bca6682165f8419a5216d02413fe9a7920833833a456fd109058c760eae4b42b752a0dc9f411d6d6d7a051b5e38b92b9389eaba24b6583d9dea3fd7ead3934319106c587d8e4c597daa143f5950cb8699ab89335f84c064bd62a6ba02a9c0ca26dd6448ee954ca9414d727f0275aa59a2b0a16c02947b7234b02c9bcc972c70e68bfcb5a467db208727d8303619126402992ff4dd6f988a598093a392152891c3d81395a8e08a091357f3a1b5d1b9c646c300d17303096c189bf467a6cc01d23038ab6e15489181f851d3ef1f551d42a8923a3c35353535353535353a74e8d0515353535353a343870e1d3a746056a5d9d1a91361d69af00e150cc334b15335db6aad12c73376fb7d1688873db1a7fba14caa4e81a3930eb2a87ce5a39b43e58461262ae4a715dee4ec212b6485db0f1277214da35aadb5fe264b59bf6119f2862ed946c3ad6eb20cccd95e10dd90268baec53967ca34cddb9626839fe91f7b4513a5eb94bf5e1a581819c60c7dc0f0ace63616154a555cb0bc45fccc2469dcb56ea41c05ff403ab7e3bb218cfc7ddff77ddc51fe5d7b7139eb9e4e180573971edf1e46b724fadda2f42b2cd7acedbb28f8a44bce87ef28f8548f727bd23dd57b2761584a4a4acaa9621a1bfe22936e714893519e82439a9c72edc5a9742747a3f4931394d3afd3c9c9bbaee34eb89393128e13757f724d076fc82393aec9cd0613a613755fdcb89ae97b4f9484618c4e718fd12deeb18272c38a7dc0f856e88a0b96d7d3b75061c1a1cc2a57b1d7ac8ab5a00f16ab60dc8df644a09d74eb0257fd66e572c36962fc86cbdf80c3553808ea383c48001ee3372b19f7242fe337ab1c6e8e1bae3acd0d575d5ea5ba0c1c0400573d880dbfe13738a860c4b8a1c58a2013c523771afb25777cb082dc30bfcafb53672a315fb61e1934711ae6f44e61d189325c6619819d79f495539ffebd61b0563daade84ca4c54131657465ae982275dd19da09b8e8e4e5ca5c4d549461d2327df3c651fddf4947bcab08ed14d271d2b4ff9ca532eef094bee58197c7a2ec98e53ee12ecfc699fb9a46156c209c40903e28a470d5e7918814ee4e36af430fed8187de561ecc98d250f228c5e4294913bb12507730711e6e43d97903170d00521c288c087272b57f248cec8c953ae3c5d693252fae476581c9c21fd8e28ebbf574c599749b4f46918d23b943ff2d3a7979033641216b7b85e4c5967b12e77a24c8bd8622d911b473c7ba6ce9da73b713ad54fb9132574d3a87f72a576a513dd133b05fad0c82e87b49cd149e4feaa74e38a74e36a74e30abc713564a56558eec49639244485c5b0c4012277e2e6dd78d343a2c51761c5e9e6e9d69945bc775c983af3257a98c5a2a9105b0a39c345c628437c41437ce9236223117b07b17d109f105f342a2219e48cedfd1e4394e1de2d3a0bd974c58b1332cb3b0849e40c1a19830511a6a65344fa501841471379896cc402b1a53fd7106180c89cb9634413816147c4549f67e6d24397f4eb82b7aee8d45ef2ecec441996fce5fe7c22a50925a223cad01314f9294293d026624b9feea89cbb31b69cde82e5ca23220c7d424c519f4e5135649ac54579764dff9158fcfc5891a3613ab9d65ba649148c541a5deb596777727a826e42a49b944824d98488dc44a0373a6dc22b92441699454091682659913c512672356acfd979a73b0d439774aa0a3983662163ace02574872ed9c9134f21f7357be71222aaffdd898488f291319634cc8c22a65a362137cd625291db9b40b1a53f9f0841c1ca1e9eef4b1a66a3594cda853c0aa91ae47da88fcc51395d79651228a66b335140666a1ee7e7e4fa285d1ca4db0316728604628cd9303444be9025164d64938804223662cb3c7861c8ef456c51cdd4fc8c99491a8bd8d2e76e8f21b6f4b7db6490319e1051fdec3615b1a910532e72464bf1e284dcef20c4543f0ba62b4c468022a67710db07b19188a8be75fb8888eaeb6822d3c8199d8405d146ce6822b7409f202ff1851cce1d34c4598688eab7b8f3e72ec9730d3175c41772bf81763ad56f2372bf89c8fd9d9db91345c5595983145738d13384123d4284a1064e044d9c23e008e28c789f997693b10a9ad812438018bb042b63da0c7194fbbac194410b42ad71b307c7449d64287778b7a7e9fe5dfef3b0e9662531695d550c8aa14924cf1c2c3bc2863268c917ecc4456822163f57f830a9a24891bec4b00c8aab89716856c8162a08c68499420499a4496654c3c8d5270e0cbc3083b4287a767777239948827690c8254c41f2874221c2483004fd04fdfc502c836415f45dfb72466155da54042919423e620a9f385f26206f83e509d8d82c7d105bfa920731c684913b9bcd0f28acf56a447bc368414131413287f1c746187d76421782854e8f248b7c801c95d820284b4e869e5d8876ec4242cf4e7368c7ae23f4ec9403bb761dd835eda1ec210d9be46dc8277cc427b2fc4c1256eed022cb5f3631280605d13c658eb56a1017689d97143a4a7c8ee8d66182c57efdc77cc18c881a4684636a9a0830b84294a1292da74882fc81d40167a36144efc7663981053fba10d149affff9a9a9694084912d883f3a37b042981042bae8f5a7799ad1c1d3902e7ae44e13e718611007618206bce83a48d8060e0262535c31a1cdcc17d90f0390e56d8833a40a628c3e6647a4cbbf8832f4f2a4fec804da9823d8981d6acc6067cd34733eb4c8da8fb78ef4a4adb15d5e9e6e5ce217b1e593f235629638a4f9b352fbd4dedd6f363314ea50167746091595914e8e99b19161a973d56031ac424dd4d502454d1d6f762fc8c15982176ee0830023da6d4c549f6a60c3c86363c665c26857990608f20307a771e41ef33e54f9d2b02f582c54354723b47d08eeb153563643eb4395352c6b59166ca4481c59e28989ead9d34f744ff360938707db393251488e44196ed607f1aa77337faad66bc534df411ce47b909b5588cad67462727227b6f4e3ec79e4889c3187e496526b91687411e95e0983251c4464799774633ad5a3eb7913d38c46d6da8b623ef06a7434127d640a32ba27bf5b9ff634e02df80f07b1074728d14d28d3e88ab0195baae4ce13d4fd79c57ce18932a1dcda73b6b68d63aa8285827c820aa89f9bb9e9c3710f4598c9d3a966429e3b1665b673aaccf950a5f05899b54f80f61139033322cf6f9916ac7422893cbf5db3b6d8309e05bfef8237766f573e617907af63748b858c22f81dbc8eef2016c2040de9e085c44e7d3ac07fffb01012963dab4943ca9f3cb17c42c3fa137d9f083cf7378c45babd90d2c15ba4dbd3906e71e920b60d630fdea29cc803ef8db090efa35b17f54b570eb10dde3b0de85d748969be8fb010fb9177d169bc8b70ec14f8efa11c228b8097c6298777f034de413c318dfd085f744ff3403ad51360405c7d380810f866f5b8b28f40a25be3ddcad8192957ce876885acb5d44f9e7a2ae4fe09a70f0ff3dcb7abcd356459a3bb7c8dce872a6fdbb34b6dd2d0b013b7cb5f903b3b4d828096fcf8c81596f1e4892d54d81096488e4c54b73c92fb579ddb4ce9014e963b71623151f44d66d0bc42c76727f3c1ce8e8f4f94b93f55e820622193cc9c91800a9c30740089945276cba6ddddd285ba58392af14952440335c6ce52e4f0b109108ddd48f639e79c73ce89a53c92e7e90d6dae4c7ffdba82f0ba2f735e71e412ec2573a80ce51902a28930f597e71ab2c441c835e41313d8d02501d488f6992977e6cbd430dd7246392a392f68864fb28fd91158ab4a9e248c01a358b52eed9a9846bb8957d42bb6a0601cb1e13699ac0d7d62d544c584aaec32511d635355a1abc246faaa186ddb3ab3b6b7296742b034dfd0acd6758955666aab1709565e073c2d833e7264a29640f54347e20c2901349b0911466aa18166048a2d68b0611fc97d7aa49768249d2476f7916f8fab1cddb7ebe8be5d88761d9c90ed9926638c38fc68ecec9eb28796cb30131ba23cd61128a04082fe749289a2bd83a491ec20a1d612b1859a7e620b7d88084b7f58320dbd4c2189f4ed2c456ca1a71963622d1c62cd23c40ca080a20a3aa0a2044248a1831c244890f8f4b060618dd0914148924c89058e13990faccca14492f9c0ce1c4a2448649034431259be590645999bfb210f6820f73128260a934155d8cbe201425225164d8276ac8462a2ba39196465108c9e257a7a7a7a321250613be7f280165f348f5944eacc588247a684953953c26648d896417c8999a8b89a1151fdda84c7090b6c189b0c6131c1ac41c87a6b96481aeb03e9543f74b1192d83886aab890ad5c407045fa062b0d288c61796437070e490a6e8a8740d97b4450b158d0000000083140000180c0a874342b170409ce7e2201f14000d8ba64a7e4c18c9a32087510819440c21860000000019001860b48d00628607bdae1afb40ffb997e327352707c5c08a31b0c6489061496de9c085263ce1ee420974353c2c846dddaa2970138d3b18366d0fbc58a44e6f2755c2a99ec67717316a3968f5c2933e9d4a07a9509afff7b4d8c0072a1b06556ee68ca99b16357f77514715f4bfcec3981594865e7c74ef684e03124c027f2215aada7ad1c71c1d48158e77b03cd4fc2c5f47fca94f3a3b503a6f068dc3a14d770f61384a75e19e8350a5c96202734b3c1e23cd2563cc4858b4615c368ac40e99074db6073aff19db91e526668a4a7072c223bd6307be64872dc40879a83d1ef899ce4bfb6b618c07a9be7434516b19268ca6a26f768c7541a9fac9b1395b16992975874c8297f399d93b2bc8546075c5dbb88b3094877b48c6ce5ebea85d175ac86f32b60086657cb4dcd95ba2c0381608813623b9345baa6bd30eb626d18c83cbb42ec939fb0fc90d535306a2a58205c5c511a1a5091ca9ab75e852704458f038fae6d9bf35f610fee3ae4ea846008f79f9c09d96d92200c10fc09486385854f58036b3fb8e650b2e1ed5cad70fd2614b82dbc82c225195a5c037425d75298ec532e34b3dac180fb07c17d37d9907ced04282a239fb43ab9e2727a1fd3026154f51b048ac9ef95c035f76782d4c597402d3a9d849ae540749b0f83a42a8d6d90c57e6d61464600f2a1f95574651e19dcef569e2995595c1efee95463d23adb4e1dc57d1a2de6e3917041a4677841e0d66b28ba49bc5175b51c8543c4b9f85fe166ed878dea8ab12655e8557bcb1afac9ea702558d69e64262721add1b9c861405efbf33ff7f7cd13d4cfeb2c795ac646914ad58a8ccfb8d15c581c7a7d40f073cfec2318cba85327ba13f730c068da5b202a9aaedfd3268591242487227c417d30b10c7591f0ac52c27b329bf3c217d124c148ae91b67433e3bc51c1ed8744d2289ab68817f53249a9e0e9ac40a71fd9266f51df2eee8a13697d6c8a226179a17e5640b9319b345329d930945838988a6d2b1c567ec232cec72f6ac0c7b9341589517e5ae624c635207b53de01dee30683cff6824f9fd8cb44cc91d345bed990a0c8dc0dd2545aece2162ac058f5990a5906c64d92d397ac8303eb0409117a34282b1c09f12035caa5027c31cfcc875681038f0e955ea9954fa93db312722d52529df8292fbf3ffac215e7c5615f635306ccabe11c97c65045162946e63c9057a3898b4e1b6782efa7c93a4ec95be695438ab73b740398d623f8d223a59820819336974526c021c28877a57bd7797f39353d1fff4350d8e1cae2164946f6acf09304d23b83869f0d0486503b7be90e812aecb82af5ca668363cd0a569900275cfce2ab009d49336b63c6a101e24932b70b82e239e16bfb2795cf7261e54dc7b53e49cced8d0e171a252640fcef3768e04be57755ddf9627e0143dcf5d536e1254271380209a0b18a602e8cf3f9c4d1efe12272acd42c954ed60825dca15a191e53d4a3a7385ba96ff26766a173d0794cc660df0fe26156fc029fab5ccb1262bedae6bde670e071cc74896c7f38fac9d5682547ace9172d764fed0504e834d9629ca27d0201d5c920e42c882c6485eba7d9224c6940e44220959dabb4a007a6eafea42f4b7b83cbf27bfc8705919388248198133531ffadcdcbcd56f9abc1822647196122029a7be49bc44c70f27d12df41546118e97af53939bf11e1cebe8340b1782d65e3b5875633abf152357bd10787073e6740d6ec3974ff5b4e4dc5fbab4a08ad7b963ee6b44f8f9e2fab6e2a120ac1ee66b8079d50d8bbeecea329d7f8f4cb4abcc816d77d9a3a407f0c786f636303394212d41cb90aab2a242cbebac4f6fb8b68f862c64f8995485fb4d4c566c09c6c928cbf6f4f4f775522e4f0671122003f418dfadeb5849d0ce0d775d16b852b500ceea511325f59d408aac02c3eaa75fdcd034ee001821539ffc9d73c79918ed6f981176616bd19494521bdc5834977e4466b16a8493e390eb3dfc251ff5ba78d2f1fed70467b114f50dfeb7969c8561238d03f4af733406d58a33b385cdba9062060e76790c65d4e4a652e92536c0249c3bbdf6cf9bcfbeb8a2317257cf0f0bf72bf651548189d29d586198623adbdb8329db4b4102da44fc92c9101fa8d40734f338817a3873031052d18e4e31cc6ed614822e4ee1fd7330e85259e1f6bbc3a49d534e4ab8b95882641385884e24e4cbf0f57a1551935c6c313910e4d24d957e54cd17522035c54238519f022732cf265b1fdbba166561e8f75912cc26d2389e762fc476b5eba6aaefb2cc6d87fb7742ab16807a8024882462a0b81f5f003e853a8af3760165530ca366954ac30601bb138321b6091c7ab1771b82736818a41113157ac72e446cdc877a21884d9e049dcf0a1c8ec227d75518a4baf78bfe9c60c8a51da32312b2bd00247b101c3a0c83b8677373f1b5cb4c527dce9df333139d690971ba4d263c570d21d7e14e22f70ad95a12d3710215d0c4ee0bcd3f475c2aa0550934d93feafd5052ab294d3885f34e0a33c1aff9f8442fcde8b8288da1144f2dc59cbd148f0ec47199e49684cc99fbf0f9d34cb47e86d0b42124188a2a9608fc1412ea4dfdcaad5a170a62428036542499f762a7368ea7b4ef9929636000e27ab13cde83b4c686b35f60d700d9b92b8c81684a068730b64dc00007587f7651d9316341b6707be0d78ca87c031bc46f60ff0a6a3feb8fb1b15d0a8b3024a0c20cfa818ffd458f5f386dd84e71df6b8561f86bfa9162384394a0fa5191dad7fa402ceb7eaee813d7e708a166012100651a13c761f4fa95355414d5b00e99116730ed2ec688d398b195c8691212905c3604e4b3fc171aa9098f86281bb1f2fcc8b79bbeb33a876a292bd1615ffe92f6875e6bd4e92e30350bdb859a6a6c30ed5ba14932b74540e29e3393735ca1805634b22e24f749396948c614e402f7221eb8341db72904d642b8e90f9d81b4c19e7dc8c6b9044f37755eaf65836cb796b7d5b2f7a57e79e95bbf458694503e43010ecb7e15194e2e5e8abc69d410e9bdeb47da050e3cfebed7bb28d6a0eda9dab852211e3f1c8aa61d4a6b87ecc2c0cc3d96200e086cb06a057355030262593a3aa3aaf06dcc05c091e4115da068d87211e456e48e8670f61537cffda54792aaa078bdb8a58f804948875811c155f0c5c6bf021162b14b78f017ac709d942374e81675084201990bc04711fa700eae9bb4139622beb02511e0368077aeed34718113eebaf255c0cd9e84897a0216f8f0d80439c42ce08b641eb4609550c1a8dc839c583a72b46c259c364135cbf95519ba2e6c07bb1126cdebd02c29e35c413bc09f3d2ab7192bd49dd8322d8c41e4246273c63db828c8c4a871590115d0c47233742654423fc0565dd3f745f59ad2800020eba82f74049a69fbd2ae31f4719b8744594a360c7053e621819262fee971119ecaee6417c5a2e40c984f8ba0511ce55283841a48ffdadde6034f69a0481da5356d607128adf1239e86c8060ae2ff24a230a3cf6869e0c7a1dacf47a5d7b523542d17349c9f5512b4ed8d9273d23684e6c953651c35a4aec9ad9d232fb29ec33f26213e242950131f49b21870cf5d13e943f498db245856e3e20631ef9e438027d07f35b8f216871591b2498410ff4cc5c2a49fd6cf44928ecd20a970d1716e4e060066dbe4465c5475512cac110eaa9ccaee36bcce727f7571b18b702ffcbf630e2d839008d363dbbb8c04113077513cfb21d616b9e8a57975615e217b90194a05f6e20959125dcc43562ac889ef8f15d87f61d35acbce8484a008f1b3addbec08bafcf7c5252c390ea0102a4d8ad9286f1c33b23890c77628a1f1ede59e88e41df705e8c7800435e2a33026f0c481eb8fe318112365407d4361c47c2fbc6afc9ff2a0eab5312528a3a090eb894341c9e9a56c496620f617703bd1dcd92d05b2eafc65caabc42ce362a121fd3c8fbe59c3263abf8dd979c0a25ae772e1bfb55a153566093f4dede7ecceeb347b07d90ffa5c990a1226ef0398666faea318efe3bed2be149e85db476684276718c4247e04f7970af61adecefbf5d971c639af891e5e079eb98e8e3eb27a8d1f540e37fb815a27205661236db8425d2c749e1a7fb1edad2cd9a9ce07ef05633e0be4c8018196f2f738e1f1db99e8a3f21655858e9309016c50007f9f38593bb32b81286d4ee4771a64bbc1b59e259b467a845383acd5d51a285d68e63e56ad0c58c1819459aca1af798c53cf05bf7bbea429fb01759ecf44521394c9f69b1c1edab754c8f752eb80942976c1f2ab15ba9f956d347d036d9fe6b1674dadfdf48c127bb2807d227612c209d8c370eb07c5297abfb93d671c7969004f132f4c01a378483b45fbe74bc9dede740bbc3a04be43c19e2f6baa03f662661e28360803f26592a234abb6c8852f19dbb10db288c75f89ced5494a66da9eb9e44d4bfde85244d64702f1182a9b867d22b0cd8690b37ecc1ea0532650ffcc3a03556e49cb0ebd03d5851b10fa199244231dff624088c4a9f6d8f622b14646a672e4c6fba9ea28bb27e6734922b49398f15d37ec9b6ef7f94916abb4df63e5c802d0ef67289a5d157aab46832c5f281bebba26b1228b94baf9bf8f8240c3088c86c6f089da104c7106f78a3d13aa6918e5811cc74508c82ee0b472d6e55421d64ac22350535c4465ee37cb3d1d062ad3bd5e960ccfd89905b62684f9f063c239536c8f370964f866e7715ad2dde7f03729dfb203a4849d3320dfe406bada3363f2fe0e9de3d9a1928d06932471682d598db0abd0e28bff96ffa41b966c8b162e335c1ffafe241bedbdcaceee3f695c9bfe9381f35cac15b0c38a578a0da2760fbf93f4eabd2d1ed969caa04709f57dc274de1b35120e30cc68825660a31e2a5134f749b2c25fa4273d21a51c7a8aec8987a9780c1a4c69440a37745bf360220bd646fd06a710c3f9be977b793ed9a10a89372cfcbca3175001505b4f2dfdb4dd6c00fdfc623299202b95e1492935e4eef5241c75919cbe1d62ef7d49dcd22beda0da22c26db1a98c3250207254f40e2ca469788abe28e60541c6219357e7cb6058521b352756f68d28c883f70310fd94e01178ca2925848cc952f7b55fe3f5b2e00592b4a3e900032d46d1bbd40262c05a78ba3619e26aed24bc4b681ff97e0515e909e26fbb8270a4c9d97829df39d22f1036834d31a7b06a9a398f6018cca5a04df981a30ebc332ee016527a101bf9f537ac8ef50cb5bdf83d409fb1a96d72608da24b9993687560c33879029266a53b397bd6621856e510280ae19da8dffb1da775a51857979aee9c1b2812f7287873e8fe6b8fb3f95dbb15d233a1f3ed0a818ff671c3a395ffd9f954674800c3af45f2d94b8443dcc1f3104766083b71082aeef22f40e8abab5208af1f79ebcc19fb841b0f3c0ebe5ca636a3edf111e79404ff15cdb62c9cfc5a3d39d76d5f8297a13fc11d8da2e40ddacdb6f4ec0b52bcf098d0b544405b1b243131c23c0d48e9fc7e7c5febaa2a2875a8e5c421045d6dadf5b904944468f5a0fa93a71b5c41dad3fe19fda92723bb4dbd86e4f7f3429e66fb326470daa4d047c3eb23c1abdeb3bf91a16b67e0fe3ec023f76fc99c329c2ba05d75812b313250beb0c05a049c04dd74f9222db0bc29c14a2fddf76a60012114f913498285289cf7f96149c1e66bf8ec66201a80ba7b479d478ac6fe18aeaeb96a692bf36547c78ba32e97f59eb52fdcdc7f1cbfcd9677cb896fac05b718cdd3492336468dac9da2705b63d08ebb5cc8a9e52983358621c5c3ab6701d80e7480fb10966b6a3624d1a1bf94f7862477df7abf62af5965f984398216d8f4930e3c6160c0b9c9bbb771c5306be56a5a10b07973207dbb42afdca03c045241077a37151104085067f980f54cc70544b16187d45231a718018fc8131182f3c429908e426e7b8ead2439d1d4761b4d516dc89a71b6cee25ca89b3083944b19268bb4b51a2c3d49c020d9b1d1592297553a38cca8644e2231983fc526bd7f9a15cb36888f5f08a6a8e21df4c959b1f6b6784d8dbf7479821c47c27917dd804b79cfc96986ec1df61b8bd04d7637ab6b39469a773acce47ac4d34f072c54466c978692a1e3c85a7975f90fbba33cc38b257638f1069cca3b47ca15406fba6af7e8ed127de7f3074331f232fdb18f12a86e29e7f86ed692c710c6ff74e07ee390268572264eb0df42e504801827821136c2c20b0cd670e9e92b5fb730ecf6c6abe183253916875f1c37c40f035befa1050afda8adb3fc28458aa113810f2922229e229f08c299d4a8a8ce0ce1866d1a0dcfe7bb27f21acf24c43033d295ea8fa097b94c83f3816eb19045e6b1b39ea906d251993d53d55480db661563b1ec6b0cb36d03e55a016cdb89292da34e08e179e2eb28818d95ef5b881cd035c3dc559934b9d8bafe4bde69b865cbd9be2ad4ca7fb234274d95a2b7325618d106e54d8b4ee736132f0e0854727c15be2794b37c0414a54d89e60cda5c7189f213a28025646c555f2640759771a6526538db1c2a41c3f95e6e231ec59e6e0c64a3f9d590bd703af29e7546f8c8a4d680e8bc4a756e0557bd719aa0a885a4583d9b1653f6e01fc9e65b225c12333a3709809e431f8327cca9efb862253c64ca2683773eb78eaacfc1c07bbf07cb0b987f0bd82f2ae3ed7ccd3493b4151d7748fb640b8998478008edc8ea9c746ffa54703f235349a9f040e6866b1989113d064d0bafea15a88625ea7d09c923a8940b74944c73bbbdc9ca18a3ce4ca53102436e24a8bdbcbf7e2b9ebe3a9db201bacfee7a7fc532af8e73a1d7f5917f63b52bf9c36cb5da2c5f4c8d90218b390ad98579f7f661c2afc818015eefd707c9386629e8f4147ceef50e4a4a9de77a574980947bd891146c0f88fb6636c1025c3491ad48395fc1104b4f819124354c26347a5b03230cf0c0eaf54269bfbfbc7cf3ec23480a6a9909c1727db3c0392993c332c9efef8565624840dfcdfef80681c5d803009cd2abc50e0d33a5c120ef1b93ae73756945ecc203967f25598a90c845c2049f5fb8da3bb952187946c820faaa7bdd84f24bcf7d6b3cb630d2302aaf334651025875b762363f3f0d896c0fa43bca885c1725411b32619c47927d7cbecd6e16e4bc216bb0c6e8fd85fcbd9786abfa4fad9f0803ec11dada9a971a319c17e5f4266ac96251ee9a30beb28ce8d6cdfbf7c25596a48ed12112499580306b3425989eb52ac3439b96916467af337b6d6831de854af6e81884c80e187b9975ae6ad9cb9779fb310ffe17c9b2efdca7a835cc3532883e36c444b9909910108a65e033abbb7b050431f5ce27049108b97f4a3d8b5efe5e2fdaaf340429b8e4c548468f1bb312b12832c1b8dae39db1a438b51bb4ef492594664aee7e694c278b8a7b77de51fabd9fd938265dd22289ad49c5d340939f9e950a7e0d14473c7448eb6bc8ad2131223cdce37689592259ed4e6dd0e32e8a6344f88928ff66a1f5136c9c4b980693be0de0af7fa5cb0919523d55078a17eb8efe8f6b5b1965d35f5ef237eb8e46e2336cd437c57c892393259f241705b7bee0dca7b7a80cd073ae8aaea5d8a6dc89dde5109be94a2ed11b35b4d8cda6f8d61c055492e92517aaaec534ed527c0fefb482c8a2288de0330d6ea5094ad2838aede9c554859fa0c22623d18fd120166e49ecf970e63a1357ef70a7c830a8ca64cba4009d94639b29e6c95e3925f522bcac6b91b4d5ca75ed383f4dfb5063cb3bef9ca551454c044d29f618100a7ec1a78ab22582ee6472829230fbfde79cd54fbe284d893b5e5e2742b60f43b91528f91a1da137d64a4a9585be30b30e8f77eb1be799d8097a8990c6ec04c9d6f7f1f99e8678cc4f21a26f519aa3c3a16091fb9470f882a95957ef828c85c6415925cdb80b4b08b706e25dfacac1748d2cf3d77f96db445be5aacfd4ca5e755606e2601f042c45787794452f91d7a91bb4f4c5e42861d542b75ec6a10017436a247dcaaffaf751cc0389bbe19a40991c3c33ae166f0617d0ee1a013c49d5c4159f1f068f1456acbf894413f8330b027bc5845d93c84a5666e2943cd39b0f4dca5e87e38cbc53aa5e904d6ef70bb21f0b6b2b3244161b990d808a66826d04eb60f50da8d071d7f47e4a5c3fd2d9f7b4e1e0d8c0bf8a96dd7bf4b0d6c10ec1fd0aa18f524d97939b2749e4be3edd4a603afbd8be4312467102d1c5ef0f2107d514bfe681f5d5b64702bf0d4aa00643d1e13b30512ab8e61400949639ce12063e6dd402666e3ec437a24aae3e864835d4fc6fb38542625f60d5ab26bda6d1806e4b70f85b872819906c3a43f5a23c084962958adfdc1eb9a84ac70d4ad690306ac0c33ca56a76d1bad5bae88feec72256ce13578550e1a6b895ad964073ea0c2d571aced296e48c92e1fb358af3f810656a0da77c47a681eabe1c36069b4c7b0299b0faef33ff828eb1761f7f75e2a9cfe921d882ee73d262050d383335e924e0588ec6265f1b380b7b3957ef0c9ba0af2a51729a0d16484c9263d7c2a8e960707416ace6a1f31450c8263c3531beb2ceade9f05737180cc2723685368ae1302cd054111ad9e82bfb0ac0054c1946e81c720c8f83345d749d29562debdc520d595dff094aecaa3b71307017e3665c50f5044747a4116feeb6af777ac391d60735d04739d6b6ffe42fe970f01aedf0d308d965cd4bcae1083d6b1fe7428e8fe6e6003702571e299da69044f30f6547cc7565be556c517e9680ad87fbd339b48bdd93afa66873ca2f0e3355801d64d7659a050b7704fe37acbb43a237bafa8f573facad95cd93aeb74e850ef383f866db75ea8672014e23d2a1f217e984d2e8c1ccf85dfcd9837ad3143d3931867fd56d91f118d5888c04267a79cc408890d8739bafd490bc3021d5738497ebb59952fb28d18627692b51125a36858dd7f121ba7b8e2533fc70e9959c0188057b24c853c5597cbc484fcc741d16d30d35a69abf55471ec44cef6a7c28e04134105488fa66a503d20253310828d89aedd00137d1c24a6b311603104696dfce1ab0d69729d676acc8613b8c2e718e601debcec66092ee4727784e94355b6701d42de6e0605d90a983c6635a74dd9027bc5f57b969863efc1f6328fbbb1c8f2f1237382bdf13c01b1a68729fddb928b73a23e579b9d60d9f6f6a13348218f7bd20819e9a9293ff7c2489e4a69f98f77b12eea61bb57df466960334794f41589fc96261f28e7cb3c807808f18adf09c4fd3e6dde7500590712400b0d3cdf905a24b1c5bcf9a63134aeb1827d71debd7cc6dab84187c7e3c379847de41a796567144b00893d94b39df68efb733746b0f8028fa83640f158ed8b40bda609cb7f53436cd55f9499a6daf7662cdcad645e721b0813e3f4bdcb57fbeee427ae10ad2e9ac1e852588cc261603e8b984f5e4e94f3e97f9d8b4f6bb44d2b0ff7e9f93468a8612a24f0da13b94874f1a0096eb23763961a99116ce80cd3cd1ffcefc8ca8b33100cdca2f4b0091a4c05800a4a60914debae28d5707451154a325447d4cdfd6eebb9488225ec69cdc962f93b7e1f952ef5c6ae8f8326df8e362d6c1ea2efffd7579681734c23b637824883943b588a757eef5de1181081c07495c7c3ac01092c0baf4d648a54adf7ae347ff9c6359c213b49a81a795f64a14400b5a8fbdca5333b80122195d96709f8c9e3042c2063c8587e3ce9fcd1c3ad0527144b2d99c95cb1966b8c2648c9c11c1a30ad7c5bc46dd168aca9d4527466b2862991613f10fc96b8b225b8bf5a0c941e0a7a47ac3b19fa0f02b888ae93c6afb0729b52c1a3bb038a8daa25b73695d8ca4a960331a65267c0c13ccfa0f234134ae8696bc7846e37c744859718411a7658fecebf6c371d1ba114dc6d7e19e92e2bc772bbcf076fefa78ed5bf916c7431490e0c49801d2eeacf3d8e9f6bbbd2673c7836c4db574eaa25fa0626b82085ebeb8721e98dada40716b49bc47c77ceb46a40c6ed8e575e75c3f96c3fc186e328050d687e57fea9586bdefb1f29b32fba775fd5f7b2bff3f5f109d243b5f45516ea67de051c45719003d14b7940c4f5ae016faeaa00e7d9cf900309370f48f76973a6d41817cee190c4377c18e37639673936e865ca289b498a64f1457e8ad064151e87ddbea0268786fb15624ff1b5027fcedb9a1c7919856653a2fcd76f5286965c6b5c284691e94edbc98fd19cb81d92b1f11f9202464084cd3a645814e0d0568396e02ca72e64709c0fe891678b2a280d78ec435b5e65cf80faf27b67b31ac960098bc9f1539f5ddd76829adc8c84f529230d97a601ff8831dadf23a927826b169b4fa450678ae6876a81b05168381c21ead4a65a8475b657e12fed4eb040ce8e4358621e61cbfd79762478f948c4c98d90867d244b10dd3dd917c844f111f683a39922a2ed0c5665b1f40c17c21c4f5e34d4ec1202892fd68cdc390336fc7635f067121eeff5894d696bb22615b9ba04b08aaae208e70056b5cfb3c7f037176951f0b42719a1871ae12635ec3faa2740cdf1022a6c7597d0d09f243a19b2320c4e4d94d6e1bde4af0e310ec4796b178af415b80ba4780f045db65a3b8484f65556d88be28624466a675d6ed55844afc335a9b6a35c14d5218257b5bdb2bb28f481e5055307a4792da78ebfa5e5bb1b8aa77c55aeb27c515ce57739dd690ed3b77348150467808aeb64dc2252d8a8cd83222b599a173363ae9f40c1a48ec84e93c0009f2acad20fbf9295d03cc597e28ccb27022f861b8e0d895b28d597ead89ad120a269cfcf71567f984c1e29071bd6e4a46f00bf793a16770f899f2c5080287495df92945972b825897c96237b2d42beffe64621e9068ea3d83d6b75930d57d40ea0fc88dd22e5d0571ad8c181ebc05d0ffd8c7aaea29eadd323c57b405ca2c5045ff53aa3b12870b42ad5af5c69376ed4c128213ae66dbd9ec3d4444fad85a7075513bd444e0f8a7250dc0a3741f2b4af687ca3251e9acea3a5687e4748f00a06e4e490e088e40ce20b732a3e7ebeafec9a6711db1fcec9333660171aab313aabd25696dbdc6443e820a5f1ae43a3a9f411b321e0ce47ed072aed688ba36cc6052fc231427974a93e83d91d25ae8d9322abfbec95a875df97fcf56bc679ac7f062584d3f244d6e28bfaaecdd78c8674e88b55c2c97730906c3ed27d1cb97a1566b9f0f00ebdaec9019df914719de311e1a98010012b04db445539417487928dda744d5a72daa1986921f625d0282e94fb8e671d8bcb76380a8d475df23d3d60e7bc83e2361d860be83427592e15864c454eafee81397d5a601ec49a5fb5e79ba18722bdfd556f7978c0afc7bd15d0b01b2c0bea72af2383dafdb9f2729b2d976b71f28e8ba86b8c6e0bac7febf26b6e2550aa2810f2efb39781981ce990dcae355caef5f80f62824cf324f901c8011b404c708997a1ae19864e3f11e050959ec6b15aca471b17fd62a46ea7625f4e1b01cbe02b3a511a185960a715306f3b6658e3fb2fec74a1da54ae2a8132977bcf40759617e3094457ad059f36a2446e1f4745ca015cec3ad6f1af4c7c899d258af398b07c484b9040e249e0ec07d0b7cc1a4a405824a1e440cec3814da7af763aa0c42854161e3979b5d74de5a4449886ff8c36e22859072351901993ca87422fe7ffe4193164b185346dd82b89a2f7ccf60237c3bc52241cf475fa2c073fd639869f07a89406f75bf97f1d6a2726f1f6e065e382f700c4b13977b02e26542bf9608668310175ed9f69ef3004ab41a3994319a9fceb397b3eded87c3942df923a102e913e92d9ade92d6e39c7a95b3de92eb6192d5d3e2a9e81df30a9b807a51a4ee4e89afef2438bd146341b5b0c21b863b821b0a6ef4c36d104ea08f9d294e93059400bc9e9fa5387ec31f9f1b81a21e331fa0b96054e9c554f867dc40223af5cba662018930334467196fa3ff599e0dab1522c9beacf43387b0cc8b34f033627e81439d3f8fa5ee3729c3f57ff6e937d0952150ef75c728b3e6d67bd9843482cf48f9aa1d004c24977b56aa024f7518ec7d283e83ba8f7e511f9c0f50baf1368d791f805f52e6b8822f98d9bf91bee8832ec104233cc007700b6cd8d8852a9efcc9ff5bc8fef8d4041207a7795af176472fb0da6aa9a85f8502b70b41c2fb70ea286a1424e1c84537da386753c3c2a96666c521defd8be2082768e90f1109e677c4c836819cc88d2ae85cf5431a0872ecba05c421d0c3ab473c0e2c0f7c63a9fc55839a70b0b93e8e51c25432ef1032f8eac30e43e3aeb7df872e8a87feb378637f7104746ba3b77e1a6b3304ce5aa88ba42d8532bf064a911589b8eb8436b988a4bcf4174a2066e714d105a6cc8ece993df3a6d91551d858603c853e88fe1ad59cb64540e0ff0cf53da6258c87e5862b8d8edd13fc1e4713fd16f2aba3bbd356af62574d3c0139fc55559a6066d908551a34bbc576e5cb688cbff6acd00cf49cbe1adb5c4397335d40b0071e247c903eacc0e4a68ca3afb62e4c4a650d775fd5e4a4dab5ee27883ddaab2a1c0b5dd7d1998270e37ffed63f220eb1c6d9915a78c5cd2b3954c3bf0bfae030874e8fa17b5bb59c141046e4c724c2e20daef4c82e1306484923990bf7185a4bb8c2ec5595accc2e99605b865ba8dd15e0891b57da8264a71ab3fc267e1a32d2054d1817993d6b596656c65eb635a8cd0c2bd255b381dd6ca1d73753f20b59178cd1bda4d6a612fa15fa6f62419f5ca5947a5f666d32d790e8d60cd064373654ad147a47f401cc37d800311dbbd2ae7dcfddd1f2f8d95de384d6efdefc55e3dfff87a111195be01a1b59166d8016ac40695e8b50e30e34f1ab02338901ae5f101f011bea97f90386dffbef87a6de33b64b6a6db42cdb134d1505cea1aba117bff7927b6d32c4d5720c8182f699c1d3cd073a68e6ceeac122c96d1eff180ece1303b0ed8c254501da78a1c2f1abebf8e9124fe81bfb08c84469c200b715eef0d7ffc7871ef4ead83eaeb961d9602769c104f6d37284f4a7a5a9cf13596bb34fde37151e8df0f21970a3be913f52fcf9fffed0943e4fc59275d2291a87cbf3e71738b359d5b7230754b68a4ce85709ebcdc98f4e6f52e66a02f01daaf1d512a416a375982d138696206c89f31568c8fad663078d804bd53f657de6c7287effff7de84aa063fbb8e646cb808da45d12384e6b84d0adef87febcf70c9891195d08d4e6bf58d1a21ae983a01ff8a0c4b1a59970aa892722f4976139dd51304fd15b291e05178d4a63ac3655845f23bcce9e1ed9eec8026ec74da1fc3cc12fdf59df342160e1829fe90286226758fdf284e4d6e286b1201e2161c1a7534bfee7885062c1960d390de077d0ac3d5895d8b43af33c4287b34e5fa7841ba73722972d566db4d9fd28276ae28c3bef3236fcb4c6c7466057fcbb73ab9b2ff250df546101e259bef6b91b9bf6fe354b45660c5b05c36bf9e78c7a9c7269cd1ffc7648b285d729240b1c183bfe36c9dd348def7b44bceaff1f1b43ba1e94878722fd1744e24097a2bd67a760ecb8f31b1d506085028f63562b716581ccce300ed36f9b87d78498bce9cc63ad17c58cc6da32bda4a63132ea3cec0de5534c770c4facdccfb73f97245a2700929129aa3e774656b18ce47f44f5ab4a89ca086265493e1f45abdce1cb727cbdfe9dcf90da5484788a7b330c3d97d01c7bb8d4e051b6a87159079599b4f04b15e7f08ca4bc7cb2128d018c2ef5ddf4517ae5f35f05bfee049eff138a9bb8a717997c62ceb77d91cb9b1f517c0a5aee084af9f7e22b586e210597d33a5d392217f97e95cbf30337dba09ff5555631668a87ece94740ed49f475b82647209a400fba51baa9e12efa475ad5a456359be11f2f9e7180a61dc9943d5ca236b5f9b4cbf7fae8be445cb1d189c310ae8275e6b24696d1ba5a9b2c12ea22bb10d80438bc79af361c4066492b3d4ee12ae3b1d03dad55f78803a395e30573ccaa678c54cc6aa92cceb5d90d5a90d4063f7794f3d111c8a413178bbe4bdec5937e668268f2b3dc8fd5065313795b6a25167e3aec9de7c9bd1d1b10b817bc2c34c375fc71f479d3051bf6b562e013f9b0f99c2f67a2278e4cb75a7d2be0c2075fc8b5f33857a54ff423922f4acd89f8ce79e3ba4fdceed7719ab91fc361b647b79dd671175dac08b2e50f0968619bac5659c591e35c8f6d3e9a935b8ab8d5d82e0f57bdf9fef91b9fd6c0ed88a20571c619c9e5eaeb955db03e94eb2b1a435876d38f8f767c4d0366ffe8963e8154ab042fc94f4001a5397150b12e169d154930ca42980c41945b31025369fc40b839c275517cf4268e84104cbb83a5e85478d22181537209e56d0d37fe4af4d162ae371aaf6f63991f8233d77dd2a9d2d66c9391cf7f2af6f972b82237780303ff0acd31be0bd5eb1f37711868d430f000d4a338330d917ddbc8f059abdb0581005e13fb645f1af29088ddb3457d2259898c1fc54c83b94bf2faeec7ccb34f40df3511c88549703c0b361a7b0c3261dadd19f960e093f01abc235983152a9e14ca00e06d9cd19220170d90b8fad7e87b68a6b64611dce2ecf05d54ad871dd4684cb57330e5c0a8b0350e1610fbc593125a338fe97949ab427fb3ae97f33d08e12e4d3b74302481691c06fdd3ef1ea631b3cd7ad9ea1dc2689081e4f609959359d9c5c4ce268ec23f967cb2ef53af6f9d04029f05f7fda2d4a22646cc26cdafe5c0892012fa0a6753407fd671c1a59d73890f6481300c04121e5ab354448bb34d9870c1dc3d69c75abe124010becf4b456b444d0921ce64463ab12e933c971a930ccfa1678c86b9f7ed2eec857214f463b43989008a5c165b0c24cdfeb7e1860990b7135fb8319a7ccfd9a1104ff3ad6b5d1aa3ad51a734781d1e33dfa5cdd0bdbee9b26999a3f776fac94cd39ae19914e0aaa0744eb422e9b98d84ad5fc7b47da148c88926c916331a1d51c616352cd4111ba3f3d77949347b844432210af0d53efeb1ffa78d30b3820a08ec9913c171207c56a86a4e3ff364a4e8774fcb1c30c85fcab65f4ee540469e1e85b4f03a167940cb6099cbcd566e6197dd6f7110bf087ba164479c5fc203fd6c4661836b4dec8808a7597d8ccd43dc086d3c0fc87885546b21b1c58df4b1409267f82b4c196d93924b4bc5885fcd17d97b2b751791fe64fa21c1044c9b905737769917efcaf510a2b257a2ed5d197f536a009bebfd82c5a3c754fa5f365daf2a075875d92c9df63dad3f2c8dd977803a46c7363eb45dffeeedd59bd2fab8dea7a949aec59f3fcdb58610d1ea35957e249b3ffcf39491e4cf50b9fcb75815cfa3a6d7b6eebfc61e8b226b054f1347bff07d3c681fda6709ebb1f54b5a2eaa087c7a48ae7bacdd8a6012b8e866ad8e25b5d271d11a369462582a686938b79162ca33b8581242c40f8e623d274b6b030808daddf556787808c28148457a3f4195d5bdd4c60d8ab9d2add4fb49c1d5ee8630813ddd8d4ccac2bcb5d479db2f8d0756acac1e4b84db705d3fc54d0f78c16d343ea84d8e4f6dbfe64bd6ef6982dc2814e19dea1e8b442784e86bccf37118343ae302e95a26005381d43b4846e5873e1c815c18f456270f1c5ea4fb9601d01bffe4718745561397f3e132f14df213d42bae99ed8b13c4bea4fdbe026ab82c345d19429aaae2c00f78dafa8a9799486c4faf4f487c91b1f4f0de324b9f580989d0631f4f74cf6d63f9dd727a94a94db30029a06770f11be353ff5e71d3635d47592fdfb1bed6d1cb940eebca73c01c0ae17d7b9b803c7f9ed8b1e333aa6deb53a72b804843a31d10fb74375d378843ede6186da44ee83be5e8de78db31e58a1180f0f7957d4e21e9c7e5ef046f6e2bb291205ca674ea22915aa36ce9faf73a856b63e010be1d8e9f5924f6668d2f144c3f3f35614dc808101c0793877b3f53a7f8b96c29d2c7281f45450c27a3b6f62ff30bc51d3667f1aaccd18179dabeff0746117c47247a905a4b564c4bd5d1e5cfd2b9667fe657498026ac77dfc22df02398351d82f4982f7741a6fc9fc790bd3116b210c020d7941f7404a4733e950453dd04a8e9bbe48f3c2eb3ed7def4be2f172fc69eb551431ab0e9b530c4eefa7b5d5e42f7f26c7973f3eee7dd5c65becfe7387d9b2ee3f6fa907d19c2df3ec01401debfecb1d266eec8283ab30401d17d462a667df3e920ffe9af30e2259ed8967a65921988c96850e4786da3f083449086d50c02b9593441bb72ba7c1b66455dfeecaa1c79f021e3ec84c1b580d928a2e3901fcc05c2f43eca28fbc12c3c8a33a0ab94ea3e53f52e270c024494b9010c2fc945c5e11b3641c7112420be99459fdc2cc9fdb7336bd5e13882d6c2954819a04a05861710c4eef55992a978acbef0e040a0408c836dbdbe4061a366c36fc6068e8bfd9c7ec889af00e72a81109b0482ccf257b660ee7b5d20f85fa8abc3333ad29e1a70f8d2221f6989fed0a4951e3d663374e388ce0dff6fe171507e53cf8a8fd3c85db761ed251fb599d7d7515a657a0e2400f20497fe7f51fbd8ff9b1b8c67c145738ca58c57b2cf38506a243370a3c71fa9d5637a337855a8b78cf610613c94c419447526ffe84d9226c102d2f29daba57a780f1831d10264baac003bf984b3f47f18e37c37cd455781d7b3a6de687af9dcf18ba8086f1228f9c49dd73818793ff97715ba3dc67693fde399a8c6f0ba498ba420cf2420135214cfdd89011f710fdd80af0f288f4061b62dd24175967fc9045203f01e7ef298092ee97d64b8db07fbc335acde38ea0e201f83185a3efa0d87aece6dc907365070ee5dc5e0e7c5034e9fea5a804ef7eece687d6f088e7adee2275b2b888bcf0d510b3ca98d550fcfb7811306d8c9bacdbe6109c66355965bcf819a678996009a8e8a10b41176a1e368bbef54ff0a128b2e8c2d4a52b31243704101d71beac519ac3f10aead0e9ec6a5e4550fad8c54151f35e5e4b5bba53fcb20abc57c413a2607db266d6b5be8511dd1ee54bf7c83313acfa2c606305f0c701fafd50b58188ffbe11c71f50875c5e9201f520aa7f0b6a9dada402fbf6dc3548b15317fed91cbea69c8fa3a1f426e206c5bbb3f7506002824bbc2b834f28bbbc6689fedb14d6e14ecafa532d10b8f328a1cecb28402affe93b5664f5302a76bd085d124d656b6ccab8b85d1ba21c287d68a1284777de096be4ca8f89e98e4ed65404f5293f966d179300708f97c7c2ab961d5e1202afb389445add2318ae66b554b8d91332a782e4e49e6a473fb79e8466b34e16d8f3e4864a31e53b98840a65cb50c01c8030c7952966618d8a530cfbeb29764056f2c168efc00774512338169e582347617ba91bebdf60ac71b8371066e1b3f5573d3c5df5641f25b57fcd4b572f8a5a9c9b5fcc57ba58778653173d5fdf71aa87e8a8e7e8a4baa0002246c176efbc41d425ba12db094b8e64c706865931268c3330fad6effc93ae9e662d2b99ce5b51c1fa265005f25f15346528ba902fd30a0e3066b24608d677a850a14373b904e1c38412ac7fe332266b245e166301a26db75baff4a675aacfea15b9c64cb9add13b2ceb5b588cb3e7732a8546d53321002a384013319da24ae808d02b761033a6ee1fc57d430c54b4a0f8689c344e9ee9e5a383a832c54ae5e65dab3a16fc56f200970aef5687587c055e35a41d4af49dca9425950c7b03fa04eb8a665c34c1802e257ef3df5b002ee01a709584ce6e7c230ddade4691b50622da9d0f65cb4bcaba29b902a2da60eae7a467a9e71ce51632795416d279872e267b26bbe98396b9ea6760459fe23eb1af443f024f4b47fa5a0b4ec2766ba9e5ac8701ab033b536335917aa21024947d324c04057eb812753f8b77fcb0b0d490cbdd5c487ff0c1b916fb1620e4d6353873bc7775b755626f827352dd3dfcca7057d05e3db356f8519708239e63ed51035742498451878dc444eccd3ab5775b6450cda8742de5f380548f5e769fdab294ab3b4356fd91fbf0a8bc10c2e7ad3e2e55c92156d046af0571d9da3b28d460074e35478c0df8d7902e12012f8c61454b8399232538206deefedf20b0ef564a4eb504bc379ff4fbf80e38d5fd03f37a8cfc3b853f7546c879aaa6d21c99c9962555dddca49cb2715cc86f28fc868ee023dfc724bc89f075d7af1d81ee88bf4fada419e2e6c016fcc5073369c186f4427417e150e2879c86112c13f61bcb9a97368832af2a9e06f182ad3556bd93b434f03e8bc507dd231d7d56de8a0de4474318f9b7a613ddb4357ee4b37be0333d0f2c8d747ec185dfe7f69b9084d5b6b53910b01a74caedcddf79bffd3f035cb1d900a38739d7f45b29e9f7418fa07353f7a3ff07025d3fbdba2621b6c7fa0569e0a74292fa6d792af2b25f804176651387b73b181ef324aae94168a8f4f81d318f06f9ffd617de22821bd28d1b9017c6e7bdf0ececfbf18fdfd7bcffaf2bbadb522714c3d67e7aa12bd20299c20507abc7e2867ca6b39d0c253ab94ad8ccc02688fc7a887f802fe5f5a8c284a3be640362fa8af2c55a9ab38e923c4246603c39521a751d04930b043ad947b5022617dd64ffd8982c3dc153abce2937d5c544994cd69a2d54302e69e97a99e9f42ee2003dee33c67545c6b4e57b6284fff0344c7dea9bad1f014b335d7d39e2a240b6183e17dc9a0db7c17ba5cdfce15eb1f0733d059251e390353211db06071e909907af6ffec9e52e02eba2def6e40e340cdd5cf4dbdb1aa9c3c257cb22930936376b16ef0507963a54570e8f02c7e0525e83430889f33ecfb024ac44cca9937a9bb746a8ccf279e5d7f748d8558a774aa7e3f2803f86b0715d49345fb36724d06de20f8b0606a18451c662bd35601dce441b86ef1907022ae11a7c87341f62d063e5dfd6be5dd1225101f5412d340003248945c9fb9df7fa265e84679e600a1abb15c0dfbf45c050331f43675d8ba296ff00530754fe991a46137c216298df5706588bfa80fb779ba20a3f1f94c14f5832e6ee1afe190a1708c773796878f9cfd4b5a6f6ef531a31ce784f5d531c9ae1cee7c6b611d17f328e69953199c48cf902f5a206865b5ebff55090c66710084e8f0372f80c51419a77d12b388c00cc840428809a6b83050a2a660e3e5367badc8572db4ee333beeedb0139d72e103c9d9a03ed7bbdca9d9a06f9a17d4eb714cee7ed62df778772e8a81896454a1cc8f58c34e3fb74a60b19691bbfa238691c624279862ce434b8ac9e786734e2fcdd8acfc085860cf992eef37b5def6085accdf6f284a824984cfa1600ea710114949735afee0951e9fde04f09761c5a8a6e9064ab8f69bbeebecc5ceb070af858e9837c0747e4511542e21cdfc99556051ac94fa44ec01cb2e14b5641e5e33a229dc858246dce5339f74ec46d44a5e1dc8b45cb5b51e075c6b40d395126c5883bb87a4c62cb13c2b7d9a25a47afd0de4122c6d15cb4578539fa51a43f1cf6721c770f8fa4e41db134df9fd1af59a4b74cadc5c3420aadf2e02024780ddd1e320a1af632b002aac3635e75000feaf41b5758cf62eba81783bb7a3dae55af842964ef46d1747390d3f1c80e774e42f53fc760e7da7f097fc231942e06a91deaa8b8d18316e7d610a1aa27a73a58cd04381e30222c033c3205769b4be4de8545371a9fc4b2626b4b165fa08a7d0262ef024a1724e06077f1b54c6c4ee5dd9dc6f3e6a22b17df717326f689cd014fbf1e14800f14e1d4b04225288265d303ad3a167951599b43ae2d0b0a111cbebefd654926a4774eec0d3b97288167f6bb27e0b96a0f82faa8b2e13d0d4b8301b16aada9478cb8539d3da5797ecd60206f6295dac05a809b266ee39965116d346fc8aa4e2ea0964d18336412a14d1fefc763590d972acfd70e39900602942324b7ffb2142a1abc461dd50717125d06b953373f88bb64c6f72c4d5ba14a740d68733a11c9a22f386979ceeed2653c5d5b13b61b8fb3c67560a0a187b443e4fe5aadf7dd9dac47390c4437569894e779df2b5a7f57d6a4ba449ffd8ac9438e11fd3c88bd327378b4dc410fd3c2358455f1a9bec3241dae84b3ad5b6e847c370b47fe2d82feb88387887595ed99bbc1ce21dea7b189e98eee403315b48fdc1dbbeae94f72c9cce4d8a6bcf420c57bcc13265fdc9ab4954f52aedab343de063298f3ae6d2cdfb5ddba3ea6e27dfbc07cd807f2f2b817e6cea1f9be30dc53ed779730973c903925c69df96ee75ad45c9e01bebddd27b72995eaf50f70be3c6eaa85dfaa490ec1aed418531475c64231c2eb5fbb9b070da55a27191703502c632eff3fb6e8fdea0fbd0866568105b609b9806587af021b0e1f2f827354369587d78b5e7a7db95e2146887f4d4ce1022c670f269a2abead3b17199fbaf0ceed703e06d6e56891d6ab642782e7708917fc252d0aa663a8c3e63d8b5deb91f1b548e56c0aa7192e82826544aba0bc698e10cb4e9d1c0eaf9bbdea40ceef248bf896f25b40c7fa8b762d7572d197f4ee686e018bbd064f29f2dbffa32ed90726d183e05cfd02a84767336a1fd7c23663815093f2ead7620cb68f0760f0c2d736c9fa79322dd66f5068770fd7eaff4da3a96c887ab32f2d00454efd616e464a4f3518491fc0c24d2dbe0acca9aed5fdec2becd6910dde915dd61956685374929a46573a2dc0beeb81cf90f799a1adcd1324f547d5650978a904bc17270f8dfe584f6d3e8c67a959eb2ab7b952bab14511e45922a05547d128d1ec686145c53c3ddeea28a36bf219e18a95800f6231f833e113c0c832e21bd480e6a906eae3cfa060f8831e3856be25caacc7f8e24f9d68a18cc001d85722fa9463ac83eb58945d778c474bc1713df6a0b7a00877ca660ce3842b31f2540288ecd9f263d6d4c5d7739a968edb4aa81f7b945079434c09050ccfb2052492239d0e509590c731dc1ed1596780c94cbce128954f3ed4b5f2f16a280d1c5a0ed3ef01dd9ca46cd0d9930076c4a36f95d15e4734f7473e9918935b63e9529d79562f4fd9876127d95bb6c3400fa97d2b756856e6ca37a645c25da54aa1d785afad3c78f5763e4af2e1b038c02e3e62dfa549d230206f6db499367703bd96645701b1b82f2dfbb5fedad839168767f3ccde49841f8d901be51c2369ffc9b86a7b57b58ea47aae7996c80ac16beb51ec75a8fbe390a5f1d566c03947540a3ab66f7c0578ab3c5cb3d1b599cdc572d6e425665c1122b3cb52f82073c679d6f8ba987c3c56c3aa3d9258e10eb988f595fadaf7a8fb714486cc032fb53824922ec3d72f13613dec139e19c86297f5b1b80066a3e7c6b3d875ce6bdd0a5e4f59512ae4fb5a830971e8ac84609f2ebb0a9ac0c1dfd7d7e475497d42c11a7040ecb5b9e4f9087bcdf643fadce4ed61e6c17aca59ea785bd7f4f6f9dc3eb675bda3269cbffc212d62b0920a4a947815ba31b22a7068032609208024fd1420b86fe928dcfc91cdd7b306f70a6c75397da793aa2eb240ac0499aaba8ed640acf6ca08ba70110fda1c68839b2796014ca340555d0ea0bff51a555d06b2453cecfd1c3fe040457be16ebc0d967fcd158437846a4e39a6d094294b018a2e3ac7d59e105ce86035f05c938f245ee1d730298cdbd17c55a4213fa39ea93efaaf8e0212750ebd525b97b5eb24dbaee862cc7de73d9020c85bc78cacd74e6aaf500ccbb657a9aeeee9819824ef74f6558fb850570dba49f38a33510ed58d40eb5ed181a347e8815abf4e7caf5707b9257554d49f0f950c76b047664e448687285da13d0eb8850b5fde767208872ab4b66770be68a5594929f497c64a65d2658f007b70c17ad8e28ca3949400a8b369b57b595bd78a5a56e12c58fe22ab7b8a15bfbc2cb33c4fc03beb67cf206bd86809d09b7d98e669c3eb26ba485f8d5f794d0046fc360aa4bfab02a50c129b7b390b56414eea376526659e01b9f1b923965c25b1404c98bec2b654576aa70b30214b4c6070acb71ff7e8dbf145b0a912a829940c6ce1cb2e8da003060d50118ba6de5946039661342c445000c91f4bdaa7a343befae0498e685e8c4be658eea3c939322d3177452eb0c1c2b39a13045d07f336216b33435c79aba815469009191840ffb406c6fb583434b3643d5d921c4fc5408de8e16430590a4e6681532cc1690ca642ddfc79104b053e859b4645a9f9a1fe4b27c7f07532d8588b512e0124326405eecd1c5377b22619c6e24f29877f6f36a0fcac1a7a3f8eca614067a55ef421497d32506443d5b1052f479eb164e908172796206286dd576049671a6ace25607663c262a0a2c06cc0246b4a1853f26e53f00c5ee4e3cbf2940560545c9ffdfb9c60f2daa22d0b2ac7977676e6ce159411b00147a51a42c7f7b4c9e12cb0109921cac212b9ccabb2e7b10e17f570e5f2b2d95a9e511a246461eea60dd847b70645d1eb8536d01bc0816dfbe3317f74785ced5409624fbd81c53c49520c4f0ac13555498f97c38c1eeea51ebd20fb2c126875911478662a22565d50071a8b85e560781b0b50a49783212c8c65af3650693129bafbab400a3790e71d65a16d62a00cb3e64cc0b36f22d665a666f73e28f8accc080cb490b026795f3ac4a49995f6f364a2c1c8e4d2ab97c1d2faf12f4ab92d8e6b6e35e2512f70c67ea236a05c55a67ce2e34ba708f324a21ef50609567d910cf2d0079b6418a758138f755a1e848d198b9c1a763f244436aa7305272fc5a708df17fd982ea707dc08045b9a2cbbf2a8a9f3adad3bae3046e42ac0654b468366e2e88943cf33a363f29e51c17639f5cfecd9916623415abfc58928428978cfcdffac16600477796885a2e3eb75ede17cbd1ee4d2923df5be3e94dea46e47261ff987827c74050368e6acd15aeaf17b8301f60905ecbf50274d27dcfd40cec0fd189d9ec6fbc1c61d593ca8701bc32de304eea365189ad683195fb42b7c62562c4391b33eb960df11aacd957d11955671a741af1fcd1834ba832506110a51453387c7d68f049905550120684db6093fc2e376ab92726e944272f92c54ed9f108de09ee8b8fc8762bb926c04e459b476d88fb4b9304216b5c2f738def7589368849250026f14ba30e2073c2b83d8f75f702318a42ee8aff18fc6306c6f73e39f63671bdba214045c8c508936db83d8ef349b7b12aff222a08295618f1222b40a2f30b3626fb3930ec2c8445f902d689761d7b71bf445006e3c723c1a40a505198e6285807898478a5d12763015ba061bea3341ab38faf0fabc9b8b1232c2ac4c5a37da047491907e910ff3d82c66e3320283ade7b87a0fe48aca078952b4af222fa48e8a59577c7a9d49e0a814ea0b82d1db792e2a218c9a7dd92f69d556eac2a221d430443c53dfb9e766a07821edd4bb66400be005511986dc2ded50aa9fa4f5a4c466ed5ab0ae34a944f40a6c06d4862b848df4b893deabd4920a8f9a0dd99345a665923bf33790b35ce07b64b2e8f3f6d2aba5f7e75345fec4630164643cdc5f41fdbfeef62384947023cc67375febb2cc75e5c83c21424a5fe24a15b511f98f2981306628ee197baf77d7830c298e7df2744913a80056b9d040aff9af96896fdaa437b7bb82f64e0ce61aa06c4b6634c180a6b0da2d68caf2c1e633ee9cf704655f48721eaf962494d7cae0e7fe1cb00eb4d298c873bc6d761568e661fee4fa06638fc86d5cf25a607ed1d53a40109cfad398325e70afdc48ce9bbb6f093890f3967cc2f6d9b111b4689651dc209879d00e4e68538da47a9f067d0a11f1a0ea353cb93a4d182e363d673b0d3a59ef13ec08ab6c4898f8496a8a09f49e9cb728284b1844df4629c47d09c90991d20069dedf75f889fba4704c0963e27c7f9a4f6a7d06162dc6ac10cbc07f4fd0e0200f34cf26bc24d782d0a026e0e30e8445e9390da239ff4f00486f162c45b86da762ba40fa36896bd03b190a50a06b04d0a05ecc379014f4937e6c98429938084b45ef3bd2efe03d19879148692429ee40dc9bb4e476511231e930bf054e5474032daef52d61adde609f9a84973890783277862188dbb69a8613969f551f6228e2714fdc8df000ec92a988d1882d865c8ccf1ec69b034d2cef704ec510890e845d099d999060f0e96b5a10e88d556060745c338533eb7d0b838116e555818cf009735b56cbaa26ee2cf2b4a897de93cbdb0d1453af75f5abd20e2619e15369dee064b555ea1113a1e28c6e516ef465823c781c9b073600539376c9db1a829b1b0e4db98fea241a1d81857194f9f0e6e52a1b9b09c49f46c8b08623865ac8042a58eb62ad996032f86fa38c95a5951aaa39674f83f82cdae16468429e2c3282c63ddac98c38666b076d704dcb4bb72bbdaed64356a8c56000b5453fc80ad84021dea26fbc9483b0bbb0f993408918de67402877a453224672508dd36972c00a78adb2004df56ecef3139d80ea9c2c4099563738dc152b7eede3413f938d53a7c22acd1db359070d4795498c8cb2330a5ae21e56ec9d6d66dc590ff49c0441879826cdf5d3f5d89c8e8710f178e135bc1ac8cb24de7fd23d350c2eda84b01a6214a746efcf0dae462686219c3805ff66494ec4d69cfeb7e608d87619f9f3d5ccbf019e5d228c6a1d9a15d902b60dd4a6d72ebd7a0c0eb6710e5ab013f0613faa090afa930898c25cc09528b44030d2af60de0c8834a51370f849cfded966dbaa61bf04d1b1d4fe485f1122b3e6252513bddbbc5a3b5264c759a49941f7d7e214586ca401cc9d4d4dd1bfaaf32350fc6486bb0d22d0e8851afa22dcd0dba8f3d0aa60190169b0980216f34252187a5bad08b7b19e2a2f8e59233f69cdbe925a2e8bbfa635071575e30fffd6d2ad0dfc864ee86d9bb14513cc8202aaad7f82120d525b00626a906b7d852c41a6df1c17bf1073e109232e30f3db957d7e7566b7de694d0c673754298e8765a9d97976f44cb0773e0566740a1e0489d405219a1ba19fc5cc9f8abef8b7423862d2779b8e9abb19b109dec7b5079a253609afbbaba7879a03da8612221f0c8a2c4c3d204cc7ab3a8f1965374388a9f7a94c1f2cf022b3fb5cff8c0a35d7ff0a51d2f902a295714621d819aa7fa60c1ed787dea6ccb3961541a26769039be9e9ee6b7c0f1469ed8030f9d64f63094c4ef0502b20f1fa9c513e65e66829b1e7936b18e9eb9f7c180c80b025c96d49c22feae016a16650a5b099f2c0ce4db4bf5b86c97467cd37345403060825cd333909978d93e57a066ad6899acd09c5c517565202812342cda186889f80391cd41a0799d657388749462d9ce25138161b505fee4938ba17770d04c3b689eecd51159dc0c02d0962a71e14e0426f04eef5ff5b22d4d1cb5546b1a58b26c0e2c2b5bfdbe0992c51cbd7767600fbad66e6c5dd182681fe9c613d68f31fc00bb00b39c681e411b2396d125b852c2e0adc29c04541354499feedd46329229b3ffd412ebc37646075fae66368eba2e237ba646c61e11b8e14e1fcf9abb1495bb614d1d45390ae4414ccc2c991e6b0a1c6fc20eb4e2c3dce8db1998ceeb607321183e4ddab3ec49bd2e94de4965f43aa0de3b6eb7632f8ec3d8ccfeaa33a502f7a015d0b79681ca9caaba5ad6d182d27101bf14b5b0786cca9a3ba025ef9e5ff4df7598d0462ed92cd46a0bab22793148cca27b85b1ccb470b05261f0c3a39c4406de6fef7f6386116ff5b0b5b56b752fc8ef18194ec4d799fcd0c0c6f61ba22ebb374a01783dee533df1095cb6e91d775bf1442c8ad7ac447768f39eafd2058ab8e91e2604e0bf82e4405b386e92bff7bbc11f1dcfeccb2ba3470ee5fb8b9ef2f550dec104b991c325075686ae4284589c69a07ff948f9d9e4ca544022a7f6279ccb2ed352ad4bae9e6e65e9bf34e6de1941209e93bd3507acfdab285d961d473b7b16d9d76fa0a5adb04373962582c83098551d512a6baddfd23ea4181e401647b799c38dbb9604072d617edb9601cd7bfb6dc912f96f8f19de6a01e4f61806d986737a7983be290e3b417a45dbabc81dd355f38084812ee0e2fb66159fa67aa63efbbf157044ea181062b9a85d29d26de6301ede0d1c096a9ac16141850e834bc6905c6ce17e12c0096a28514463fc576be070ac037da5e30454d24369620b5dbd3b4ed6829d3060bd02b5a9fbe10b18e49eb926d1f2f6cbc714cf49c2a0046c6da30713d62f44dd7d71e53bd98a6a86e1f446c325bcbe1290d598476aa1a3426b62a7e248559bfa573d03268f9d2a8fda8138efdf7b7276ec25a51ed1567265414af8631dca963e38631e93df9e6a5be373a672760ac6cc6d5f30662dd7de7dc7a75784fc02bb9fb157bc3b76d9027bdd5e195905163149d795bbe36538c00287f67406191a462c6db3b3d62740d181fb6a7697ddce33c50bbe574df0c624085791db6a8fc3a482f651fb613f562eae59d23f3dab0dd2cca8ceae4cdfead67e6bd9b05a2c82b832e1bfd75c1cad825e7d2550b274276740889b18e5819529a98047cf3e73cced1232151afda058c6cfe5bdbcd6a230c3ec0a63b332769a30124806b1ab73b7945091b308a3c8c7c452a6ee2526d2d1d043518282cd1dece5b1a778d978ae3a1eab2b96352fb124704ff7a6e20f3da9a1a6123f4e984d33e9517949eb17da46825c5a83e4215446dae239a015512a7851632a3a202f0ea8f8b186f4356c6bfbdd05a87e0e67e78f713334998990ddedf2000d18093088372202e96f22ce482ac68c11d239c5076952be9548dfe642824905b24ceb58d49995ae0214b655630b44473c0db8b6f4f15160d936cb6179b43ddf380acaef00875f0de106c747a3c88d419fba00135e3dd579a71346f4ce33cc2d7a9dc1f1c88d2d585d74a98a63945003fc6f9ff471db7ca582098327f2217c099843bf4ad2f82e12542fd4b2900289a3aadf1e81c745db4f50aea27a196057d68c0e7d450f4ff72a460a512ca60a06bfbb4ebfd1d844c5a9c1c5180177d86d40efd6ffdb47310cd96230babd22ba62e5fcb879e39193a1abfafbab906f6b0c14fe2ed9f2f15e76bd84d2e078da2b61cb13023e941725d6ca63446eb370ef99098d3ebc9c6b2e5605a3aa24434ba202be22bfc9c10ab01f110e0c7dec7ee0673a087703cda24e68b33df6592c62b3aa55013a49306423b269b8db14ea2ed051c839ac3c5b57bd01d717cc04aadee10dd53e30040728e723185b9bc9c9b813fca7b9d8f0e06b665d1add0390f369e31f0034e6b7f93746407abddd81e54e05c8167e6ee2c21a5751680d6d64ce0925647a50ae00c9434c80bc34224273bd40722b3680fe26ed73a990f20bdfadd20612340a0f8dec82262bdd910175c53c457f4534a14351a4134c213965c8c5fbe9e776fc1abb9c9072af9616d2e934928cc6b4d46ce86f6bfeeeaf059e3c6c67b726f3ec960a74dea364ef2e607e276bc10512b6add49da43e47de84f6a1686cae93442a740109a251e70463d79bff20f6a8eb31238b7ad0fcb0f425a1bcbe6a83e1afd11173e365ab8b9191c1b70eb3fb979fe8a4f0c959993c44e68789a89f52b2bd3cf4d5a19bf85575a9c97f5f66adcdd24416185ee936c340738f68e3b4cf3b06ffa69a74a4d62b966b18387311fb786d6361b53a2d4ecb7e759011afa0b6ee86103548f48017d5d3028df992e21bd264b033edbb0a1f2f70f6323eca4a85fc15e5fac1d3d047fc5e762490255ebd8c0683976a6f2e6451325d6e08bbb8340a3cb871b26693c6796e268d926e5d54fa1a8ed277989e1fd86b892e152a106b2d6417e4566540040ab1ed099818015dcee788d7ffa55610d63a31507e66397adddcc35d9f9a82ba9ba4b5b1e0731d63fb1126b521fa7d758c329226f029ef6df9ea5ef787b25ffd3843cebec7b7111714e06501f4740d5ca2e61a24ea7f7b42d9ea8b1b38a8e001d706326a6015e3dd6f77604c0a19c532661406c4905a5cd5d616c483c7d1363dcbc505463f97652e08ba17326a1e807e3055e7b2d1ec775d54f668799a9523cdf8d2590f9bfeea4de6c2cf8c3a17334da1891d57a9c42d8185e425343f41a0ba3b98ab9a4d003e5d0a4f0109c5f04c6dc48a289cd85078539ef429297428f5061c65325528335a7b20c867f0ff38dc455b583bf1f4d83815e2a96eb34c966af1677e36d9bd6dc5ffa9314df9a1c9e5963ff85f431f4734ce22d3a8be55e9ac810016573ac31dee82810be687d8c0d45d439ff32c7eee9c34a55f6f20d116701b08a3d48aa2b99feb410a90abcfb328ee655b6f6f2de5335e95069c10613c8bdd392d08e77f629cf6d068b19e4146e5d443b48153285d633f9d1f1484c2dc75e62c4923b74f392afe401f215d81d285dc9aa5b8bfb6f43eb2633613a4539e1e5dccab933bd410f11c14853c60d2f73537a9821d230d7ea0c52f75ec64ce11d51cd4f19a5509c462d18f153d5a56a8f9d867827d80eceb02e7d439177a5d3071049762228f7d6d6af60f1ed7b4829a2d053e1e683cb2ba12ab57227f894ead70204618ebe08d246c52d11cb70f15e698302ef7b87ce31c5aad0a9090aad6589160caaceb93e8299f27e5715459967f98e577a20052c2bc96f5e5a4df606b05dbe961d90a68a71a55c31d8903d489d7078f8a9c683ed70d4352430478fc095c57ef58fb322560a1501ad71b809046a763b8c70a43cb983ff2a7a1415c6b07e991cbbc1a7445e0798aa6859735428ef049363016f4d988105cd61e3c9908c4676badc587b6bd1a26105057fdbd1e699b9a9b1f2b12c526c2ee279ca7dacaa990049e7ad63f0c53a9914b010164382c2f9687fef7b7af337077beb9d640a1c9d9cade1a972b1ab129efcb9fc912a52078d03f98cf330c7afb64cadfab2b93477788b52f3184fdb6bb6f712f86982c7c1eb2803a4362f3cd218e2a7ee80c7633ddb64b087092351ae700b38b5465a057e2e8efdddfd47a2e367af903a313d9a8df4dfe203169e7c0df1f881c4ad6ffe63d2a1610ab2b147b47bb9af58d3deac4771d64a9eb6e89e0b2cf20e683958653a18e49dcd69c16b36cd39034700d28eae67b15f05356c3f3d701ba69e71bbd9ce66395f1915f92039c0135790dcd492719bde78cfaecde9c6a93803e4116c52c67bae250e605796c7b9cb141cdbb8c85aea434e8388f9a84357182de00297eb62e88917fe1b4b5bab71fd839918ef48d6022a5cecbb706b92b56e1c9fbe49b9405a88a2584340c594bdc492908ba41e7479c479df6d3f6b75ff88a4b86ea6976868d66378587596ce93faa25c66a837795c27511c4cb3f6fde6091614f4cd567bab274c4993448de70f80044f008c76ce7174d4f85625fb4dea6f5b45a28eee96122f2d3fcc4ab02663687036bc3d9608e9ccbdb0773dade8cf4e911884d9f1572870cb913caa319ab42c45804a468414f3d40113d1bb04950bbe93bf4ad809274c76ca502b3c0e5db2172b3e172e239b5d163c60f578dd934100b62fbf009bc3d78bb31b4673d7213a46da687271ecac2d04d388653094dad14c81c147f4a49ee060f38b5173b4128b3474eb3a81bfb1f5846c48801ac068f0e3225a38184c1cba33f91a0a09c61160974ed849622312589a5c7da85de78be4768e1363ed1fe5218a26ba0905f6dd7fc7b111f050e9408adf80bd00f8deafae2bac60f19ea04b664fdfd9a24b9ffeadb0ce78db4a043b8f05a8f178adb10ba2232e30bd5d7cd80999be2cdb89ae1b18450a5a40a13373c2f232e3774c9166dca8eeff625504c8d4d5458d2da0db043a5cb190829cf5d090b3a5262a181075bd071610f1a3febc70b38e6858714508a25c712b8ca430709e191e5605c79c82abe3f5dcd156b99dfd7c53c0da886e979373e141cf8977ef2fd280df6c977a83838d08558e78546c3d0a93c57338aeac138c06c26172fc4fe4b38497eec5f834caa65fe4d5145b3048fec4f812c0f66b37ea11d201dd0029783b900fb21c0327cdd166a1e1c307c84364d012ee39fe04059112dde1ddf05454a7871238e45e69645dd649bdc921f060a2de7b68a8d7015350333450a3e5cd57187e5228cc9b7de5ca5d428bd61c9fe64abcf1b43bc9e335a76f3885a0ad906476904b1dac781fa9db05a3d1391be2daaad449cabdc483a79350e909847990eca042c538c4c83b74ea1063a47112b6af25ecc3add7cc6dfa563adbbadfc63e619d9b063a8098a819cdc45194f70bfbdf7901696fc0daf5e407784c158506d370564b1a814a4a7182e2509ea139beac8e44beaccfbde739b6cb3ecc6019af8ac6d115095cb56f039bc2202868f644755abc9c21e60cf48b503e853d535f417c9171757d716ebfc30520cb4648437f9ca3963b04c7da77e5fbf4f7b63ebe14bd113afee73dad00557547482ba7a5e0c46efd5af86e44ddc5332bb1403684b537f1ad9d7da06d00ce803b1934548378d1d06cec777b0befe9257e14d327260ff3201fafdc7444f5b15c546e9d55363a75302ef6b184358c678c7f6afea592d017abff6a67ae1df8083e75667a423cf2033b83678f2051d98edd63f56d3c5953351871486e5bca5df552b7f888f53e2d087ef1457f5813ee8529386587a762adf2c802f0767cda402916ed9586f5f8bceee5f433adfe7ac1119f656aa0ccc4cb046a18348c61471f7986b2e84af3353c60215037d5b1386b6cd95c5ab37d215353bdd321fda85c6637626f86294838f21b3bcd6bab439a592784fb83247345c51894b8fc83f39b5db053107132ee40090799cdc41900f577d627062243fb0f6ac2fdd021a828b48402c46ecf090dd94978fe11bde4d40d0f00405340280df5cde43f1ed2a025657a744e71c22d25e04bc201ddd20beb8eaf32b30f690d6fa9a1a02cc626704e8b32138ddbda631c9e9620d70a3a3e83280dfb1487bcb16c3ce81a810e1611dbe675f6555d123ae87217a27095f18b47b8cb58dd44e551eb4b54623c4282a69b88d54fc6e04e0a13d51dc9461a938f931f7fde18031bef785aa16e3278e13cf3dcb712a447432570cd78707ac98661033c6fb22fd44e6adce7fe30a1e308020506c5c73d25e0fe75814dd831155cd7dde542a6a4f1287dbf9e1ea79ea3dea1e78fc8d4f20c79ba0747d187d5a132024535fe86334aa388f762eca0e4ce498d978576f0307419e74c45998ae3e73ddefb84de251dd37bcfebb579b95ce8664d7056260f5bca0e3768d01b607c11321a2cf2187c2ee4f318c7ccf4dcde900b3c659727747d2ec528f3c9c13e76208f4020778d0a3c0c51e16dbe653346ec71d8cee71630ed69b4097b9f78cbabd0bb05581b8956dc853cb9a3707edc49227656060c34fe3f60b994f6e487e1b1047f105487e8898b30f3ea1cc2bc8e78922f2ae82126b7d7af5596316c4180f1e0a1bedcda9be7e82dec06e8db6f148030b832239c6b5c629afa53225ef50b4f02cd8bc965270fc814b67f08b76dccac8d8f2650da286d6845e603a608a455455ce66a02919faa01612c9f7535ba7fef713309fad078f2e157740116d02f17f14be0f4677fc5931c5d9de49b251811fa03ddcc4c5165136e41bae56a1d3ffe83bfc800a263dc6082fe2ecfe15ef5353567a824dc3703a6afa96dd0fa395758603a064dcfc72d8c4149eded1841efe314a01e9914faec47084272cade2bcefb0ef7b4d30ea927be12b1551d2849b4664d955a381db40757e530d7b4a3f6f86d33073ba4333bdb907fa2a3a63568b217bcd641b8fe6ced657055b31fb9f037f706094d1df567f60f39850a936d0ca4c81aa4532e987c72d30f9e59ba57406f7dcd1b84017affe2f9af56ceeb8fd45a3d9bbc98545a65bbe48712be9fe960c63a25ab6a6999b54bbac29e8f1f6315dc302541a060a3715bb2f91a949cb510e48ea8bcf4cc1c21768bd3d2a12d40d01489c910140c869bb9897594601820ed3f3feccd57b7b3c503c0d930f1182b1b1a38aefe13ac206cf910035ee7e4df6755cd09454b5b642d3a21a09040d6a0c42cb6721cd91a8c894b148c232ac224a41f697940ecb4aa220f73b001efddeacbb642d4f56053e67b2e4c560e7377047809fb85ac622c794c212b85f435ad6c4884e132f22c474768b73fa5ca69d763c78ae74192ab88f7c8f67f8df8ae381ced2769d67e23f6ed00d86473c003609de6d07a3d884a32f9ccf8684652eabb3dc08e0b99f7e7f5b31b83d2aadc707f536eb9e97f682e46509262337d1e5e17c627f8ded0148b15a6764708b00601e4615de3027212e4321abfa73c622d85317c88e5d9584614f4cf23680892c1c506c11a0908e7e6b793ca49191ac00038a7775e6ef0cd5f3e3c7314ebb16ea6698dba5e947cc808b48b300d7462956f6a3aa1a49db126d2a612b3022592ca24638010569d2e69d43843621e8ab09f4cbbb66b8734fe2d8df62807f20e17b21502f9052a101e44c87e5263a40021faf533bfbe313974b7661dc2598055c55c0c83a660f9b1417aeafd372fed6a202344e15e4c8e87fb0cca3869165ef0f36ad7170f57c09cfe824b671be17166e360dd040317fa65b6bbba50083b3d782d8eb48692d646700f431793e29d38ae8df0238a4f5625113e79b98e672130b4b8a0196464627e1edfda6146ec714f906083b54bfdecccd65223ccbd07fd86870cb4656acb541007021a7bea23027c6be2f6e0d4f91d98766f042fef26998b50f52a2674205d22396a59eaf2b8c71649a8e056189c0deebcc5f405355333ae3419941c74c0561774b0b14007ba9079ff9f40c4df4d788a094bac77f5fadb24aab7074d76622e47f28370887aea4c6d1dc87d523b365252d9efb86fc16fa321900616046ad41515e26c6f49d3dd3f560ccf85aefc5c48a04320019bb596fb3c901c766bfc86757f4725176382b301b2d360d9d120c16180ec60b07436b371757f4e824d53132653417600e15775de6f1c8e6f36dd5f1e21f71d0f50dd116a059edb803cb789d03c7b431c0b003ee483bcaaeb971b072382b703b2d761d9d331c1d67a3577078b18789a7b940f17a643429b7192d3c649b89951286f1275db9475d55cd21a6796a68ecb0fb5776e0e1eaf9f2a76d808360ef6b3c51f76bf1d768c7e56df7d677018c76ff10aaec414d58fd882dda02c230ea249ae1e0d1b886cf1cd93d5318c4d7607b577164c8b95a819e4487e99da2763e6a6d55f4159f66f186092d80f93a8dd4e9d8022f075cc01b56d424518bf05fed964fd8423cae7dc25478968f5ae8448bf6134b1d89731a69c3ba730d505987a8fae1ee1759272f6c5fd112b33a4ebca4e122ff67475f659e562a66c84ef9a6de37436a4acd1aeb5725f8c6831416cabcf8c4ca3aaf2814fb5a7a10e6f94a77789d01d2b71fc9396b3b6d38003be261f7290be1ca2c1018f432c0951a0f1f2a9e1208558e4563227be3c332da12d26bdac7ce02edc29c34da7344600448b04c3ba059dddacf0be424eab0ef2cab9e4800eddd247571cb18ff6d07d1d4a2c01363d6a8d19872d748e29cf9f26b3800310769dda6ac68319c5263ab0711c9a0fbba4d271334baa9739e4ce4450e38b4517d572498585b54613a9b5ff94ddc35c1ce11d13f3eb60332962022318de3141c39d000b03d84039d9c00ba60b676d997d99223eaeb3fda3f2ff36f3e0df25c58e4976536b8b3771073a59215f4aba9c2e3e1713bf6a659c9a06ae6220794de25fde6555341e00aadbc806e442260faac721d9bacdd710e887402d99dde28284ac60dc583f0d36f1fb2aa43b8dba23581ddcda33e26dc5ae09ad356da98940681fe519bcec25aef6132e54087f437ab9d941695d9c92adf9d8d904e15246bf2d9cca2146a29b7738f71bbbaf0f9a44aa8564d0e717afefe421fae29a841dd9b1b7f6baf1c1e688d7ed23f47c0c8ce5189acdef3422ce7c58b01177494da04dd31ce7ad3ccb9f78ee1e125d2db4414f5d15c7f09ef036ef9f2e94a0f80c402f0264d44fd9226a56f229ca6c576e2ff71cb86441f1c689b06b8ceb0dd7caf85b82e3a9efbafacf34591dd73d689fec463a1f11f2a85349c7294bc99a6585c1032db728cc08691d99dc5888f081601243ef02484e938af4ed38bbd6bf0d349e425800a2e424b602088d67c8a9970b158dadbbd2779026fe1e877e036eeef79ed82f258ad63cfe5006174ec9da4fe9d22c0883be069b9d20eb127308d31a363b7844aa5c3c3e405bfa6ac2dc5ffa74be0c58ed62f1650208b0cde362cb9708578904628c80315bb0d521f79dcbafd177527cc690c702d9a16a193d0b655f8c3ab92bb22843650dc74167205404713f6ea64fa2d0b70e8a0098af23d6ede4dbe26fd38a02189654ed8c523fb3d12bcd3665345a071c6159594a84044726ca62012d9547fd9c5be115c85b1b8984ce0713415773c0a51b8e135aa4dfa95568959eddaa3266dacbd5fd106935e285a894a9d7d0a9e5ac28d63aa3a4ab7470b29b6e1cadb0a3111127f96ded162420edb795c13991dcbd3971ada597a0a4f0e895dfe518776e1e9b61939fddb0123ba5d75501ba1983b5711894ad4a4ed17edcf5fa9a442930c2630f23d80068ef6645b7724e9c3b0f1318a0ae8bc3a7c02672281edc9b16d630fd4b1d84de422a783b7ffcd758d0b04c684b4854a09b2f0a39470f2555c858ad088f6d57cd03a9e676896510c15cbb44f2aed2ac5138c3522b54cba4dd5a53bd5a02f34d40d61f254723b05557b0e291faae9aa6fc056b6473601f8a882a261567979df3b08421dffb10ca6296e84fea9f1c5bd3ec6d011bbb6f4924a73f39c3ad1cdb011847ae3e9b9842aa654f4edcdff8a63e17280597e2b2aa5ffc7a0d8ba7b45c68dff41ecc6fd99c4a75f56b616d9109c7b86978b11ac9d8c1a4e4ae280b9712024aabab8b22f5f56304ea6ab78e6a3b029d5c0a2e61234463fe89506b44a4af04fa3874c86f6ebd0ca96fabb25ad8fa0311906e00fdc65c0785d9e9a3bd053c9952e5a34ffcc5d298a35cec3cb9562123a026e8f1e4e0c3839cb042afd09d35ecfb25c61f332521359aa7f6c9240f9cf03e82d731f9f5f487c6352aac0314d3021adc555f5d30522a9b0ef1e28426524ca8ab663d160dba674e3ce9ae2b07ef3299aa251505685066cfd96aac86c2e88aebf742986220d1c260a817bd5a040af1300a49a1878ac3a36ee8c6b98ea1cfd6ec45363dc2c3881006697befbdf79629a514f205ec05b305bcc1aa6f321d9658d2df45ec244bd23699d3b4134d6c46b5ed88924d4868a64454341372f992679b6c33da8e6c421b0c969c6626d995bf139e127a45f589e44e9cc9694e482152cb9d546772a6ea3f7da6cc6936b02bff15c3f3292bb72935e47b20a7acfa2f973306ed4f6304a095181512d28c9413261f274c4cfe32605e5c5a586241b11382fc6867fca9e850f00d7f15d19fd819234b125144900aaa3f92ea7f1a8d6a5852044be2b02bff203e1dc6ae8010fa41142f96e462497f1fee7994a652ff2c9a5ab650a5a9e536096911c36215344755152ad86847a5763763a2ad7e63d0d94d6b9f20744b4edcbd930e688082c61c6b7287059e1d9511d485c9c3d3625848335f3e561f17cc916e8ed067128428c8d177baf9afb95bdce11102ed6f74706c1e4f3c7ade77ba59c0d25800696767fc4b7e17d072a1b8ff8e1dde9722eed0817a17e6a7fc875ae1e32aef8cb747d5c3538b5bed10d4b7f1503c3d288ebb33de7fe20b5c5346158d0951a211954f51e93fb15869a7917d5040594dfae7657ff81e3e2bfc920c59dd0a47f73c4a6b18ee0afcf04330f47ed206cbc349b2bc479c26fd6f9af45edaf116bad5dedddd9ca404a9cafcd73e511f9c73776fef2697d58ac024454535a31e5a8c2c94b6265ca9d53c218618328218b80bde82c80b3943414008f1d9284dd6090e2d6029bd89113257152aa8cf76581097299c4d66a276c9486c1f765de735c88a60f275953b13a9b00d3336628193daeda4b6f3a6b03577cae4c632c54293debec6a063f7919055a8a0fdfb251b19ed380dbf7896aacc7369ce1d168e2a3f4fb7542a03d134bc03ad856e6dd211231fbe823f5ad2911ad42621a63a7b5ddcca335fb10de81ed5ad658afff58d41474fa401dd2f3d5a0c18f5ccdcf4fd295dd0929b18caacc9229f752b759310e8696fbeff6e44108340552b8c0c60dfe6994273543e4785a69df1dfc6f042cef33cefdbd7f721d8034e15f0555a54a15248814853535393d89432a2787ae2a94997265331bf339f128e5fa94923b1318d59bd99a8de43781f48d44bda08e56d14f6964975eeb899c29c4fb8ac03dda4a127d5b9981e4dce9f58932a1650fe79b58ca4d2f78b240ecdefdffa8790eacfea968f9e4769ec4b560a9e47e79093ba4944c84ea89b34645439e6349e47692af54fd24a52c7d6f4cf3f000fa9738917069dd58529764cf56b117b8ee4bc8c2edfd3f2348a501dd7e1f710ec979f2c3465748a1a9f54c00a1d17957548175dbe4c81638f60046d7997f7173ba6ba89fdfcf21268f99751022ebfbeea98eacbdbb467e4e56d34cdcb4f16d2ab493a6be46efc948b4aa37e98e73c75ddc19973ce39e79c5d4bd7c9a5630a99f3873961384ea3e226a7a2f2a8ef49993eb5e5e72e94da32ba5646fe11631c63ffc6719a14d88523c37666e280f3e7fc9df96541b7295a4165a6d71c0d7474c7409949e8349acd8ececef8cb0826b0d32c74eb7bcfa18367a88e584644cd41d5391acdc66667fcc313db6c0daa04c28279192fe3431aa2ea05044710550fa8f1345ef532f2a8f134543cfe47f8df9d5919550b68797e7e525fe8ba81ae726056fe960752b728288bbd33a597e2400a1be0cad230ff29fa80c9e543661a7f5ffe357e5ffd32aa1ef05fe379fcd7a8f13d22fcd7f8ff1f7b667c8dadf1e2f3f81fc71ed5ff8b63cf911cd5ffd813f335c67df1c85166c840cb55a25e8aa6c02e28aa964b5bf958f8de4029632c4fd1b2352c5ff2104e78e28c0354dd405751dfa807bba0a7ca309f82498daa1552fff22aa4c66d52060ccc7bfff240eaf730e2e732f234b9b38526b75a001c3bd37f125d402cc460e0892181681d4ebaffd00474541ae2be4610f71523ee4bf579b5db978e163398a63fe1cab8b8870c031428309cbeffb3719ae7fe24daf8f046da7b11d2041b279430734300374455f8ac07f21280a80a6f7c8e00fec6877fe3c6b36ea0f040588fc2e7b01e85f1c71199ff71c2b364c61cd6a3f0288c3fc86701b9210001bc0922eb4f106f3c29a2f0366666666cbc09e30ff26dfc8f137ee68198f0404af89cd597f0fe2500e04d187f98f000f81f25fcea81bcc22e4124c09b200ee04f10713c2916e00b20aa38a44d0735e941429b6c9b55ffada85ba51fcdba45f4bd1f756bcaa6d256444b9739cc5d4602ba7de92815168a0afb44856dd2fb243b653dc6e6e019ec0d7f0fdfbfa3fcb04b363be34db6a66b939d014717ab9e8630b963d89533d9b08e01f1a7a880d5324505cc886398f7d33086a1612a1eafc2f97124e75ba972def3bcb1c7cb540dc0717df58d2e5f398f8b391e4beea1fa566bb8bdcb33a5eca9dcc8403be33f846e23ce78d3e4d65b1647449894c44ec0036cc9107c9061a526fe61d09295941aa502d7fd2a003c9abc05ac96bc05ac763e36af79881562613db4ccc51b610d1bb94993fecb83eacff15619a5c29c356c14f79d08564f754c0bb5ec09a526bd95fa951c1c42a78f4749c29559d7d1a36b7be609db85fe685ee84f6ca157fe9c98819df1f7440cec0d1783765defa679c2eff9fef43de02f9367e4ebf7449d1ebdf2f0f4dff784a7ff9e47f8fde9797c7f3a7d189efa1576037a5c5512b45c261e4ea77b50605b0758d27fd4b133fe9b8b72bd35e91f9c1c96252519aaaeba494900a102cb8e81205210c3d49834e121d03b2684fbc42488ea34c94c63e11a93a6a1cf29292a2a2caf32aafa577e5f3d2e9f32aabae579a47ccbcaab7c4fcba7742ce3ae8cb4855d46d95c79069ffdb949b7fc9be55bbee507cb188295f108f840640da482a3a3de1f48ff27d3e4c3ac44600625163908d5e2096419d3f1855ef17b3bbe62cec72783944f797e1ce620670939978ce4a4b20e8bc5c2c961d96091a806a3bc4e932c43b8bea27aa139ae7d7aa9c7acf2ebec740ba6f2c340e303d68c80a23207618450e5e7e1b6f6a1f2f3ac80b29af4b1355bb97befbd6edc1718841a89fdabfb7e1b4dd37127f6e895b7d3640fd761b58d6e7d651f652eb8011353122d60814cc935a51a55e2d7f62f5f4197669064ca508ffc31095ab23c2f06ceccecceeeeeccccececec3f4d9ef8c1120c8911c90d03bcd9d9bddb1b8b203e3423f18a27962cf919829f109205121a9ccc40d4440a2366109a2205480cad00c80a211560b14b8ea2348164288a228de44807283baa7852e4480bb890a17a90c44fcd071b92340144d4440e4ba408c202206a53d8c086fe6a30248aa1708a2234253f0005315bf2e467c8cf2c92439325419014418208dd6700ad0ea9adf833ede73add15ad6eec93d965504f3407da64044db5251d74380ac2c2892896242931028a2555bc000914330025d54cb08276e2892f7a7bb777bbb7677bf7328e6ea5e65094a41cfcd0474562dbdbbbbddbbdddddbbbbdbbb35318a6c3e0b850c507a77b4df6cbfa3cd26de6c37dddac6f279e38dc51bb6d971c3db363b37f34df3f6f6762729a9010fa1fc2bc3738c7065ca19e379a9909190883890ff78fe333dd2c53db72ef315f820f8a7ef091fd503fe49f56d2dda6256dd8af1c7798977b12b7f1e5487e24cdc97ce44c55951b36e0d6d6b386762695a881d52ab25252d13063000498ae28c192b2bfec38d2de33e3128d7909a74ee3bd4f729323561df09c249b757211cc1ef1b4bf0bdd1d5a40df8a50b649b19edefe19272cf7395bf837ade7bef5ef7fffda2bb54f7dee55a66eeef7b15b67e63f879cfdf89464eefd234a7e79ebacb5bd79237c30a8ca458b2e1081b64d58b8a8a8a3847c7b89bba514ea44612edbe8845ff6920224affd91a21b666bb695f9b118d8b84d0b29b98748be35105ee69547d5fe2201d107eb79db8686b9886add815c91dbb2b716d0c42b92fb9569980231379230d328fe6b1c744b4d3149a327e9616626139da1a2eb6e5c5b638116cebabc1c8b652dedf89587f626b54dedfa1e01cf4fdbd880dc261dd8773e4185547a2820a040f41aa7f87d151fa07953573691167fc27a69e13617e135f9e672d24d4b3ad49a9eecf21a12b5f6e34baf2e55caa48ec846f30f9ca7ffaf00d26a698524c89c608ad168b3565b2297317b896dbb4c9362396d191c21431e651a2ea19899db0f44c8414a1991251d14c28e64b9ecd52336488f0cec4d433522966f2e11bfe2c4c3ea713cf58624eb3c9b4d81c9b11cb268bc5e60c605e5e3ad641ddf4662e2d2cbf4b61be9a49aaff36c36e22d84d86b186a5d964be82718ecd886ff8a302375f10a8d731d833099529e6e8ecd6a07e64e2d6a854ffee616bb83fd6e4b83bd3429a746ff6a91ca549e6906833b1c307f2f2e7ffbe0fc71fdd7f3fc0f776290a98042cf749d79f9f213fddf7ec13efc1b11b7f80db0528dbe709a862f04fcf237cd4d8e385370c68d7bebc574ee7ec8c3f4369928beaf705dd4616e99afdd5e0ce35aeb112d7d6a64316d25ce3a52e7f546ee4d919ff79946a22a209e7740bf8fe5493ffdc6ca265ea26f5a38afbd2b5286e544057766d0d379ebe24ba8d3adea6d3f188a3d3e9743a4de634e93839364dbab7747af8f42ec8118de3d435a2a0dd77834968d7349bad112b5d9f5f14fc83782f7f478d3a76e6fb8276638a8e59501f62b6851cc303cafd4682ef32ddeacacd61836ff89f3e9c4d22c0726d8db73cd82e7c7d5d8532154b20d57b70c7b9abea6f8373acf646006ad7502453314dcae040b9ed8659d8db5077aed2d5bc28540a503420f1135b33674a093e12c40e218bdfebe8967faab7a6605229a430bbcbc36c9ee3f2309ff3f2a95f9787791e2e0f33aa1ae665bc7c6a54f5ef4b064c932c1fb67c384fa78d85d296959516951696efb422d2d08517683d3d7f8f2728941304ec8c3f8b07d81b2fe28dab6b580f964e53fc0441c50e3f4d3c7952cbf4459d7329e5f5c8d3e4e4f131392e683fe9abb94caf81e5d91a5ae7b7b035f3a7d75d6f403dbf471e2ca83fe934fb9aaedaa3aa7ef5b301e5c61f996c3184a157feac49a2a29e3957f967673ac811184d5b88f301d1b32e6a1943018202e463c856bed334bfb8891d08317bbd66b32e6a23f07fb6860b4f10083f3c8da2e72b70f4888a7ad69dc0cee76ce65238773559d433577fd9b36de49f9db9b1a9cd3c31361c20f8295ed0a288ac269bec221a6d224d21f307e6d3640a3893b6046d9395dea4f96a095a4ed884398d7bde37c392aa3fe581bc548d5219fba76ca31a34977a3661d2644d9cb48954fd5fcc1535bbe8710a9593c6cfbf094d19bf50b7d8f368bb029f859a94cda029e38d43a5c0db269b42e537e9a22f35e94bdd2a276c0a75ab74da1c72a739ad5b4bb37103fda269a949f70eb717e107da2f739a09e3718eb359396130d50ae1cf9f4d544e581b9934186dc2605ddd7fc07ca1c6d904035afa12adbad3684bb425da52f5e762b4745a1735e9bf421335d9335a1bd168b4179a2bb139788cbde17f7a7f37da1a5e62094eb21d25cace84e015d33b5117b05998000a190cdaed395bc35da38c9c9e51356c666666661e4b162ac1064110fcd0719af410f652056511fc0ec3adb9707442b25b5d99b9bb9b6178a1f3b156734078cae9953f07e27c238b6cb21b6348b7626b38eecb0e22e18ca3d3834611fabdf7fdd327f4f91ec461f38bc27c4f0ce2bdbc07c520decb27c8bf7c82d01b042867eafcdd0294ab98ebe58de2aee602c6948ff499d0d92dc5e41ca8baefeebe49cb5630123ca33d763847dbe0c2f6dd871cc74d1f581141dcdcedc4af9ab3dddd45207377779fcd9b37770af72278639962eab123b2d0a47f221d83065a0c4124690a307c50aa0205330cc1233aeb3e0f5fdd75980ff90816b63531b1ad99df0b09e14043aa7b91128e6d0dd7fd37c28c72dd976a1b550ff01e7c1ede83e1e63d0f301c55db6824e5f7857af067b5cd27e6b0263b51b571ddf6df7be1d8037aafdac69c6f546d1f760f82dda8da461cd49f3ce63029e8fcd261dd7ffd4593ee305fc5be316837313549035af64c24a2a5c3981cd6ad6d8288a0ea5dfd55a40d87910e837143ecca1f89fe61a2a9899734a9c32322adaf4e25082ac776c67f08e5d1c685e49afdc1a07bb7fbe60d04b76d034fa80de4f0b481fc1bd8830a4f1f828f7a1ee0a3c653f827149fb6499511dd9ef3d8fbf80b41af1b59a4cb0b9237293e1cdf0a9acc24740393d0fe44564fd9944da139d4db9753c6c44253c6513e1aa8ff2e69d26bfe7dcfa32379edfb5ab7befdbe5df9f721287aad919a5ce2481f7f1c92d71ca95bb56e95dbac2ee99697be4b886aea085aa66ec881fa6fdd7ea8032c00aaccac270d9b51032d1bd6b186354109429747fe1f304292c5308ed6675d9e688114a5f881996b35a41d983908a9492d46c3a0297ada2f29cecfdd5d13d0fd263a8201121c363b6e6ca80eb45c9a0dcb85c4ee76b1a9269ba363aac95e124ed0316c32202de1e8baaeeb7e7b9bf6bc6e75dec6799bd76ddce62c1da5efaf29d5f28774ccccbfd3e4bfd840cb55e57fb18167078784176c8d8778f8197a9be69a060e9479773218364d34238872111045b3a259110c4533a2d98ca8685624039a01cd8488848a8c8478a1fc30d18edab9e4ff264926654eef6f83ecd6d25411ba35cd02e367cf903fb5710cf364a2fbada496293545f4f62be02a2a189ffcd80e0aca485d32d2f6cc48ed791cdb5533d2116db78924c4b4a36ef5126666e6187b50d8083f0943b48f5033d0466ada8f44747fc4f184d6401032d2cea800a41af1cfd95e3efe08c1cbe5aba01951cf84d0b2675dd4b396b5d00d347eb2240041f702e1ab66f469aa49268e8a08b978d3e4eeee6e775721039224e1323bb7cf8deb3ce5846e935bf3e6ed9f8668ca555325fc50c20f5984ac9202ea620141f73dcf474f8905eaa72eebbd238e4e4d87524a757056cca9f5eb34b7e1e8f8ceea0f4b70bbbb2c8fba76b89c481d22bf9c39915f8baaf9208a154504a55c43d4c7225e607676677766676676afb150e369a106456c8a2390581f77a03d7a23120b78109e7f1e8f3c9367a7c9de259eff7f1e5fda6181679b3b7ec33bcdd3c26c6b1ff3a9d41441d496b400fa82871f9808a5bea281f2c840b3f907887fd88128a514e88789fc004d77ff719afe2939c63ecc3eb13f516f7e6143bb45a98bb5a36f5650a9dcbbf8a09df84d6e2e624c933d95e09fbf7371b04df7418fdece30b703fdcbf4b82bc474ebd4a44d7b0cc29529d746b7b6da58afd5bf2fd336627ab5a586a0278f8996d45bd24fb57ca1ceb9a47f7b759630d4fe90674bddfee6474656b7df19cd48a86eaebad18c92d4cd6773fb6fefd2adf9dbafcc06841b76288285926b89134860f1ecb25ed014093950fe0ee830ab22072fe020ddddcccdcceb62c540ad20e2c4122d6214394110314c6f6b31e0d06464440f458ab868a2429644142a9210516406126858f213daa0ee7b1c6b1e19c6318eb14f73cd6db1e6d9692a22abc74393a7d960fe556c51cb57826e1ee7799dd771dbb6b9bb4fe6cda793208313473b14b5348184431bbe4e23d34bee73415dac19ebc00721eeeeccce1b4f66f776253da91d014612417c3cd07dcf4ba57e64f0191181388c37784ec42b8a9408519188a00454f480c81731081a628914454a569820091a9e7c5794a0464407198d99d99d7dbabb7378c40a21b57caf1ee1dbf438cfebbc8edbb66d7edfdc260f68b948373898210521995008a1b933cfc9734e9f93594da8b8412d7fb21b75ed535a9951f76f7cd53a9aec6e1860e4d3344ecbe9d636418488466d76f473bd719a6daf69a71a8711ef594f4bc5a880f21351e0088d2882848e5e30272087a39a0d6666f73ccf73f6e9ee9b3405915aa650e09344b4248908a8d5b56c752d5b2dce61b444cc0aa35aa66642c4c2f6be267a33bdfbbbd605922ddc87de6cc9bd8f364d36c77936dd363b96f3fe9bee5f990e4258eb13ed0889d6b414122d6c9e328aa0f375b68679bb14de4057b98b421c3d7030b033fe2a4ae8f6b3396cf20183073eb035cba1c0564bcaf3a8bcd3dffa79bf4223639b9a7cc8f89e948c6779fe1e984f7d8f8c7f7916984f7d0ecc987a1e735ed8c5260533aef7b7fc0b035bd39e4baa49199514d1d5e46260c579a2a48d20d06d3261b174f7245c19274c4b5b33756bbde7b8aedbbca02ed70a3a4d2ccc1d1a45481a8dc6756291d3eccbc325a7f17cc543cb223cedd74bbdb494fa9c7bdd9d4be19e6b7323f3cfcec0bc1bb613ed0889464b21d174b6c6bddfddb1fb1c705c5fa57ce5cb5b4a1982da8ddce6b51729c272eef771db6cd431f55b3be543433ea55c4019b56c847bda743f25e81c3d1fb706749f3584ced5a4ab28a1deced20f1ffefe3b3c5b73aafe18d89aad66becbd4251c4d93d35b4d3f2a45a69629d269fee43852622d656a7f7f9abca8142e3745b9746846020000d3140000180c06058321915840281c2d7a7c14000d77984c724c98c9c37112e3484a29638821860000000000002a008188010df3fbc11416fc68e1939ba6d428385c039439c70c73e9987a22a438b8b27cbd3f0ec0bd2f1f22d8870c8ec036211322c125d046ffcdb2a9682ecfd7848c359941eb62eef31ea41a0e9b0f22ecc16e6be837be6b49fb4794791d7be959424e05a7ff7975cca5eda5be88d77d379a1029b74aa3b986c2e6ab6052ce5e6d389b260c3852d523674af5e37e735140f685ce331425c46ef50458db95b84efe0fd76345e183d6e036bb9725c2e9d85b0a114569fb336e135508a236340e3d4f9a84c68fbdd4043d39cba62339f6697f30b53d7440adc6af45b42a9ef1c7c420053c20e4b87035ae92f842a1980ca84a01b4146da423fa5704e52259f53c57affb6d941f5ca24f58482a051301dc8b753579501af73808271fa9c0c101de25bb1341a86e21ba0df56907a5f7602ee0ca6f61158c2e1171b889bbbf67d5decbf929e574fd363e6f34f6f033bbe9d0ce6ae59df543f4b5ad0f5cd38eacd8882e9400b8ca96134338d0060ed4598b28d11be4b90407759965c2031640a104adb086765c590900c830007eaff2eb4dc9aeebf581a4dffdbfdee4d2df10eac3c7f2dfd6ff44e8faa60729746b02a1964910da4b533abb72d90939ff7afb5f288cbc1bb2c8744846e69967ea7750d08d87093ea4586b234ac576fb484b9a2b67f5000dc66bcd8d11629fbb0868d2861964711ed1c7088495f57397de8d4a309629a1d60c37c9111f46aaa07df2f60a789a99d601eb52b2b87eaf928d464531cc7e616f056b63c4425681df837349e18ffe0665ae9fb253da2fd7f7ebedaa7053793adc4433f30d94d30558bcc756befd89445c16753f59b64fa10fed62d23c4cbcca728eb1fe505c37a1dc8238ce0b6547026e71310fe90e73d0c730e421eabe4ca58f290e85bfc7207c840ef50cf28491560f6d9d8b4b19799f6d4d1cbf5296b7227dec1b4af47bf42b52f3b52852821db91d977c65dbb011e82fbb65e80d1b6b753668d8185d961abc42463504faf64a8ccd40d40da7a643e0f953cf77738fae3b8450dc3f88744e504222eaa0c036d13661f55d427d44c2b46cbcc2279e9fe2d9e92494a6bbe651ccd1d5af1906c83f25a542fbbecf013867219334dfd11a35417be63b4c2bf85e68ec4c21eacfdefc7902456f12be23e185e177779c3f78d9a0803cfbd8dcace043d3c748d10c6cd1fd9f34b0f55003326deb343fe8dae37e632b0803713f5b0473dcfe09f2d421053924c2eb654f1ef0897079a1909bec637fee543fec201de7c8caa3ee2046af66d309015204a001c4ea0f83865337b95722c98f149e227d09154853184e3657e2f15662ebeb3477047081690e22c62b91c12207ffbd4acc6a948974686815598c5a787fb28dac43ee0d361bbb0d3e945ac7c283e46ad40634900dc1350ff6f9fac9c285b9595ad7f4c281831772578369256fdbae12c7c01f0a86554d72faead2dc2d584589a82fd6b3739ae140685a093398e39ab6da2d3aef3bdac73a219bce352b3ed8a71a64741e8d500f011dd18ea3b2a211737d0d6b9ceeec07f7f58ab2a8bfd17484cf7484d9f9edc399df64d1d8329e6ee816adedde4596aa0ed2ce129ee8ddda18fd757f64023078535d1c109bf4eace3aba2b11e824656104e1bbbc0ad1c7206f3255041dcac6317b8cb230a81ad5ad58c196afa96b160506dc04acec05d3350029c34483d9b8990c9d32aa309adc08f7b491adcf3e47a45ad764f6024d1ec84dc78dfcf17038cd800167c74bd155f586ee9a626db3ec90c8a89cc5695601388dc52059cdb936c6c1281642042e301eb352ddb5d6a7af88ca9bd6fe37567816108c9eab58f80525ddec093759e7e9ed9d5f14954a5b9ff4cb93ecea763485445cd3ea8ca0ae5853ba7ef3fef741627d1fde17d3ebdcbf5f48bf031a2b2f27bd360a4b536e4efe4ddae0ee84e3b314c11f98ae123c429195f4fdede52e9e6a7f7c0d867ae158eeda51c704bb242a5548f92743812c2124edcae58c59b47cb3a6eb33ae1aa8f309153c7bfdc3c07f5f9129f16ecca4e9616d0ae86298611aaf0939b63e3e16cc5811e971dd290654c5506ea473c3c5a1a21434f1e1dd52a42951a9923228637f30edce85fb6e42209967c60848484b5e8db2a00c53ab31863cece850edf8ca16109583c72b0dab416c0f9bda585e091ca3829c40c0b9432203828fc4b3c3db08ff4093bc61e8b02a22a75de6e7e51fe8a4ebc88b6217ca056c90ba61ac70fa9372bfad57a9b937d3aa3fd8088df73cc272f4bd6b96fc5aaa1dbe4a61720dc781225d90b673814afa0ce53140d51ef58cdf15e7533dac44f8d8388fa25a89b94f235a44935f5e75ab342908252fe5c5c0ce5700a8009cb8061de7a99d0d6a56df6dc767e725ef29978d1e62294b940a6fe088b1b9f29f3097127957996fdc654a89a6e4bfbcd04593f2ff565b9e794030d3d9d95ed54703e7a0e4762f4a05f10174f4889fe5126645622b1e311eeef268d62ed3555e1c38529359b29cde014619720c59c0d2e8f7a17493a140ac916335708ba24eaaddf103ad728f2e7a10b77b5c9a737b76026b17acfe11e6f6779852dbf661d56762db623cbf2b8050ffa974d53dad7aaf9566b919b7774c0efb0212a41dfa65e84c27bd2b5ca9ae31ad02a34c7a00db57b5b2d81f113350a10b5fbe747f8a51f74e02ab8b3a6238e47a8b877c9c63a66219e6b0e440e35ef9379467501042493ab185098993d052fb57bc502c01e9f2110c4618c72c4678947ffaf2022cc0d86cc64634f68bbefd1b0fe8a74ebadeb1013db6b19a730a24f0409a85c80e5759d0f8d225288e0430afb41c6f48c8d9bcc1d6acee1673388346128589a3eb01e6be135d13eb4c042ad5b1fee5fc7b8407ad6a8ac11778a785105e0da8ccf8b86155908791f15f11c93e4dfc2c9617e4a0b246a3cdaf7d7c8e31800d588090eaaabc54490705670e4f726ad1caacb41120cf8062cbf3e00433aa2855a9cd4c72aaaaf9d85c6f015d7001194346ce6a9411a74fc147068890c2715d6fcd06e5d36cdb157f6e206062edce0499fc2468dad53d73bce1016f06a5d534cb4b9b227ef86445db74de10271c7a88a014c344e6911240ee78b4c1621e1b1f7a87964034a6f5bb9a7b5e8d3a7931a1f53a748209e099b771964db42dfa653315290639f3503b06347def8d35d579c9e0a6fbb0978615f728d1eeae36e6da27f945cc05c8b29d2b0752b68c04880232cef1f964e66818f6d1a2a044030d23928af467772af42de8023e2f503a37bc2972e2ebbe8cbabb05f130a2f4db22b6f4452ba201ea3c16d9737a898de8413056b292d1e93e18b393624f1b6af4982db65dc955ed442d92fb2d4c44295480a50f4a6763c960f656ab6d82737c5b4764317905846e2967fd1fa144fe84e828ef904740b2d3509a15568fce0733469a0af51749040467c9d8abdf3ec86678da489a86c51ccdf20d9bba9c9a10a21493acefb89f4124a01ae9b5c36f2a21321b192b18c7910a134bbcb8a326d3a6ce1a65a4385aaf1b0b5c1a8130b925c92fdf42c98eacce4d8930dfd0459bac17a15c2275d1f0e214bb9edd2b674fb403bc41598286f41ec868fb086ebf84341617d1c820cd5301caa7eaa34906278e4d4f4011aff783948690480b6e30b670a58955b24a82f8e8a940a2cb59dd548066e16a04ba71cb36f78394bfcd1453f49415dc2855ecfdb4f42d28849ed44c9f51e23a53a25ee003a6d933cd0781ef91d7573db0f63ccf87cf0ca26be8495eb1e06d104eea5a87fab87cd974c1e626a745ef4685de7261b4496e5a49462d3b67568ca821d8f2f65c9577f6fd594ef10a414bcfaab2d96d556a4f8fffd00cf0b3fe662de8b9d178203e0c6d861d95f4c70e8913a75b38f60ab00e3abb0aaf7b5de3629a8d448297982a21e20a019fac1db5e10a33ba3c00cc301a40af6cc506deaccb982b981ffb64f97a769b91b4de11acc8ffd8694e505899da04900f87f65d37986004c1222a81ef06be634aef422a700bf783c7b3500b2398d3ebd6024a70cd67e3ed21f1c2c27668981d293d9b0ad3c9b59fa1a2b38b32c5e18ce0e5b9ea9491807c5caf9b7405f5e6a292ab53334be0f03f8d750f7f82848de80519b34628e62fbdd9585c772fc459802705df60fe0831dc399ac70db73c6a3e542e320c9fcbb80dc4a3e29a010ba6b5bf5f4cebccc06ac54f1c33407e39a45507e2cfb3bd5dc2e451e36d7e1b5950cc3640840e89bc45c0174c59fe31a22486cf2b62dec2fe1a9ac65ee04c82ae9c3946d6ddac1a8053ec01a19a5cfab8afca6f3e0a7f9bfdc03c568a5f12d166e503659993e619d3b145f11c7254b8497961848cc62c47714d43cf7f7b84e643dd1c2c363b73383393b10324bad722fc410b60c6d38b44f009ac425500636acf2d180b34acb4c72b3b0a31f2be2eada2c785284742f21b81938ca1a52f7e3dd27412434d316947f9c2457ba69b9c5ac75cbb81ced1dbd0fa701a19c76d3942fa5e381f2c0c4f274cec4458c4987c3d8903458e47314903657956fa29af88a22cc49cc08ac33a9678c5f318d4074689d12b56c90daa79cb65e40025946822e6103e18c8e9b8910e7a59fd788c31fc30d51a9734eab0f25e9090e6a26eecbe8da317d7d8a8b99a60e2dbf698ef4bb406d391976a69f7478027dd3d5db0ca93dfcddee1b8892a4ec8eb8ec66fcfee2593efa570ee13fa7d26b7d7b6145899bb7c0ee822460ab2aeda0b219cbe3d678f6e3de043e3d76d1fd1d233497dabeaf31cc47d48591677c3033e7b0fde8047fcfd1d12415b3dee8d6f06199ec55c04904dee665234f66f59ebf7ba840220fe5cbab3637f4ffa9e8a9c59da675b25001136619807f7a5acd1319ff2ce814bcbb01c3928bbc425c40caa166eafb78278b116879c98756018b8bc8a94922f4a8eeddcc4c5c9c3de741deb995dcd50da11232bf78e242916cba97ea1fcb7ef479d55209e6c48913ad83c8f110446f7a1166c44bf180f1bbaaa104158ebbad2ea2a4236f44e658f9821884ff8f90cfee783d4a161c9a0d12386d3e27fd98a27448748ec9612ba99a66258300558863ae49d1ee342b9be15b06d4a4de1246d8adebb57ab312861647ed5ba9034fd49c4971346cddb1b47501dca38fd799ac04c0cf62a765cf339815d62915197eba18fc5959dba18e731419e55eb081613cb29db10f4769ed8c796a4fa5a2f6df7708aa7ad6b831efadc91b9013ad3c4c976ac2c3894096a117aba338b63d1c61ab4e049606b054989c751eb89c4bbff00a5aff4339b6c29e421e330014253f3358bc34926ada2b54bd483e12a9800e9297e91bff59abe75cf90fa2c53779cb012fd061b25e5ce26370ea529c42a5ee0bb88961a61f4d3dfeefee4c03480c6870bcc18e42830b5bf7028eaca7d9f6c3d50378d47c0f7b354ddd9a260e9ea118e411b644082e9a92994e94e665bca2d0670db440672c70b0027a8ac42ac6437f400cd99dccb6c74089f382f314dc486a2f5e3af36915360642600f87070e3bba4c06c6840b2179d1a1003ee9842fecaabe753511835db8df110998418100b370cb6921e37949d9771b3111c2dc74a86fe276d68619a752f332201ecbf315138f377eef90c7761d78c6e268ac2b2b9a9299ca316065f6a1b75defab79ff4aaa3d1722a406b0fae6686b6ba8ec1a621f58644ac702182bf5d9b274b863c83c7c608df188a437eb6823b2049135837851a590c1c728e5b745788eb0f4b5808975b31701f4941b40ec6e8133b80ee02a69e14e7bfc1149fdfd10a62a70fed82cce1e707e32ac6a11f59ba9ded4e25796024b1c729b0e9eb1a9e5f28f7e29ea2a7f2c37474df742470a44ddf75e180414b8a10290cfde0d97237c67395e942d233be269c6410554e2dcc5ab313af66b608c2aeb861556b383ae4629ce308778595f389b974b24f5dfb9cb8ac9019650f80ed15f94c67dd048a858f6ebbfd8551b393bbfd275495eaf6c4229bdc9bc885bad1a13312a8c6e73cfb003b18d924c02af490f4e6c86d3b8cc83b8f493a2f89a22380a5d9dea0e47a68d950b697ea7bb8f1a69878b6d870f14b99c446cc67265c2c78152dd674e4ad9ecb4c76b62b77c0d0f58655eb1b1a0144150a7926bad4a49c39b6ef8af0218c05affa96b0e31ef895f9ea0a684c072c18cdb7c3a6d833230eb9a91712fee75167242de473aa6e5b35e6782982ab068a5984aa944c7e9c85bb1ed8cbb05e5d140e0736fe3b4b774fb72428d714a7dadcc9b5779e9b7ac2c22d0e35471af7d2a3ec91bc49b5b58ed3c85e415407df96cf8fb67634b8b35901f299d421f8d7da1041f09580b2babb23fb6e8c93b16c1ce6eae2c60720211843db7a5f857c7b1ac0a4a00515a138934bbf88774314ade4b2beb1ed3f14dbbe19792a026dadd2c3451881689e228cd57800950e378647520f372b9d0b866d953312e1b8e9248cc12951b4cfe3e32940a3911efe1773dc191f220a154fef99848803cb8784cb8d8c882c8bc7c15244251553d13167e196446b554053df7da43d06042e5ccb7a30b12a6e9eaa6a988c2f6748dadea5c6ee8313657fe6ec84d5567cce4b24ccd760fe080f4f465aad8147d8fe1a317803c6d2aaa841f9e8e85983d9dd9ef13ea14b104248559a6d4a790ec178f57fbbeb14601983daeaef49c06b9658d34599b596b548d7d161482392eec030769aea81d1efc9b2a77fe0ce54394775e9b9fd4aafbd44db4cc79e8787c862e9e8b3c794e9200521abc41c4952d3ce651949685340ffa65219bfdb6d9801c26f58c9eb53353e5d4d085720e2d445b17d90551572648b6941544171101571e88216b74b077384a465568801c5e6ff331bdb352f2e97a7539c446949560dd1516b092caa3603a946218a40b8d9fe36957196de6869564abf3f47aa19e833ef78e90ab46e1590a113ee67c415576651f3e91e76481e1919c35b12d2a56755b8e5a44127162cc2abddb09e09052529cc6cdeb3e58cc03cd0e14fab33d0afa33a93e170c6a0466fa51114a1f5f3768c1f23e4815cec692d8662a0bcf136a623067a60f1c9093d6ef8cec51d4b3f65217b9a9b6f8a5ae6598004943279803464c95fcad3a3a000fc1f9fbf002f3d26873b4406d6224c261085d7fc4c1ba9088031e5b0e5f1f483d7a1a4ea80da9083a8d0bd1e397ac591886f855b2de7c375f0703b5398e0b0a3440b19f3cbfae6e09f492f4328067205035224b6a4fbc44233d78f18cb43a13970949f5834c4f40b1495b8e548efa78bfaf29955bdf5746e76aca1ac9f4b15e4184ecff0a35fede4c1649c6993bb28136b88929f0dc17260419ded7ec09d8d1e19b4037e48f57d2de6ca730eef147de7ee11336b3594356a2ab2657f2b1e720e69663d3fe36882c5ce9daa39a988880b0d1ad0fb73f660e15f6ee0e6cabbf6624669964f2d1207bbca2bedc8997697472588cfb9bf14f993adec19cd118909c29f31416200e89b843e74d79d0df5c610ac3c5f6ee48889c7e1fb01490cb1a8256ff117f837ed28fc761e81049eea7c45f6d130b29c52a065645100c4b000bd424249490a1d37b6443dd88601288d22f39b894501d4c1c8b64495c2df3d39d7efb21540f646cd9be060fc7c389e023752955d64bf53cf5da8b7fdf5552194bde3925aec7d23d1b46b814bd0ab50e82da3e777a1d6b278b71ae994a0ab2e140e14b0659979a56c2d45149dc04231f9b1dfd1942fc36a95864d4fd4da580e187c3b36e400fa8a70f193fdf727ffad0d95248bc1e2ce03ec11d5d8d3e1e32f331c047cbcf737da00bc35460330e473ef7166fbfc75dbcf387be533b00e9615d86a430fba7663af55690463d2f715a8715333b1ef6aa2e0170c4d2e3e912df3221e861128efbc10eea160079bd593ec5bf4af22eb648c8b45bee1b2da6796d50512177a8bf1025f1a4da4a144a5d8341bc40d4d8837327f36fed53eb1eb9e690396539f699cf9ac483cecec692175d619371b974e3ae5de58e2dc082219fcf1f033664986f15bfdf4941b48686c549143012e42f6123f4d8be8c19c510f3b506a1b68c1eb5437dba20277c11d94bd94a671b2f0f071c5b43c3c1dc2005007022e4c1b5c45564005f5bbf67b8a09eb06ef25b36b38945730f1d903fa8445295c71b9a706212cd02cc04c9a7ba73025dfcab49faa1776fd64cc2419501071d20d37c7b001b5b0a4b21955be26d966eb4a731ad37a0b9c8cddc0cc27def34a06a00cc8edc2d982c4eed931cc894cbb3f4cff3933b7151b71dd7f132a195ab05f9e420f3f6ee5a6bd2a189231887b9bd3b46f3a7b8864df31537c50ca7500f13692fd427ddb1453dbf872dc35c36a7f88bb69668d8672b9b48c3bef9e99e9a5e1e4d2101ae32bd079e144039325070725f0609aef3c552d03e5fa17041e3825db33711064a59eb06c2f9b7e36834bc715a4dde151641832f11efee1219db1419c1000fd6f221b4ba9f31cbad579905c6ea7dbf142c9da8950065a69819f99dc4862146bd2ce2d48c2a83d5ce0b1ba64d7baac7fc7a87254fd1ee0f762e0aa608338393eec2424f6fc169a6a6969a6a682f07a08006a03c797c0ec2392813cf6e75fda81bf6c3c93e844b61747a1c5d72b3bcc9982ef8af861e5299fca2ad589ec227ca8b1030b63a8e83e4bfdecc7abcd1864be4b3b45267731dcbe4e8d9752413689f2e1c50f009f438fb9ceb0e39296c816c124d44caa09bd2161db0f8911a094c1b487471575d552dbe1272e996a0660d2daa49fb03dff9b1a1252ecaa70971217b4533a0e7e59d8f07871c69f8ffc3399cd04af434440a8c30ffade43a287eb3117bc9116626f4c1005652d28f5468ffbf4f4a3f17290e27b06c1a0f4a37760122d4c283be28dcb9febd4b063c1a5343f134177e152e99bfda3fa150d25c15b6a18c69b8a46c953026731f4721853fceac236415266e898b105db0a3121b4d682d90f44770eee7901096b539863155e3e28b8fe235e5d474b4b0529f0611f060379a3e4f37d8e0eb397c932a01ed678d9018242fe6eb4b5d36be482b957c0d11e7ac0cd842261417289430d12c5d081227d207facc291cf02d22a75087ec0d9cbce192600c32fc62c5ed331a8fc5e463dba0d4a0711a1545b4a50acb31a43f95991d9b746d162c099f7fd8f1221224a8c416fb84a589056def4002369655ae7708909b982b4e7487b6d6089d9d0a9f7bf113468af5364ff749831a6c407cf782f2f99927150d93ca91b8587ef3223f2693fcba2074ac93fcdcadb7e89ac564c4d16a83975526e813790aef8851ac4b6adff1e1c41f84ba7bdfafa026da3e291802810610d78c34329825710724dd9a5367515d5813ed59b5c8959392ed4c462537da136ee3695e2495a737e5ebb0508d569ef820045d693719f549346aa410975f73a1b2f062bdb21da90df7b420a55a0b5948b63133ca6bc586d5c4117d8f1877f3f13832b0ed040127ab0873a05d20fc06e857fd5d43400a8e6623e5253e0a8ef4d85087bde4fd177122448e6abcf6803832ea731aa1c95fdc574f60328eee7304754f625f27681a37264897c84df8cdcdff3eccaeb95051646187ef0b716d64b5043189928a3710ff9e2763b91841ce42b10489414382b319a65ceb0a66571213ca6d2f546df4fe7d8668188389035e5b435d5d719a83d8c44079dfabd196f61fc60c26fddf345c7214cb6cdc80050a7a1212e9cf3032be97285421af45af006c4a28bf674e487d13653d141eaec1b729fc085faf2d6c6f3dd8769078bbe3aa86fb8b4258f6a344a2cec17070dfc698413a5db57ab9ed6ae9850e6b8e460b6d6c08853d287950af928d296fe4bdc7ef793f811a326f74a23eee7166fdb83971e174f140f2d243c9f98a33cb8edbc18faf98fe2637196898be92a3c94aed3de3b5a30d233ff5b4a22600a5b91174c649e5e956cf66becf2f9b741132ee7f8391ca5ef43d3f45f96f64a00b50b9e087589e5c15c2f42d57e0a95e1ccfb449a032b6f75a196323eaddcc37f24cb75a1060dffbec358a00b0fcecea97d04d09a50a578f14c92c71ae60ea816c4b8daa0e43a61c9ecb19c2b557b2637445416e91b1ac0d7c6bedf4cf8298e1d57dbbeb25a25e3321c2af5803a4d1a14e3921e4dd681ce948d9ac27b82a43f6f23119b0de4ba31108cf02ec93c53038a7163f2539588f0262eaee92e66ed030cf299fa03dd74a3f0571c66b5e76f95ba204de9d3b955354f53bc7509dad7f8e7169de94c534c0cbc3bca43e853482439b71d5c67a01bd0d036d65d76e5fe1854d0e923985cc151abed7e4ae614cb4f20aa988924f0b0bd7ec597629640001583632bd388419db9e27947ba6e93427fc3082de2dd24a2546cf3b7024f9eed1b42df7a56b588c888e88816a1dcd43df2b0793856693bcf97abd227ed703653ece68694f8d2c07495ec490c722d02331b960d59c9c0fc7ae2b55c4da7ed976918924dbef442795c2cc922f031c995bdc79173a291c0e13663f964288efb98d839289a615f8ca700101a22c7ba52934bec4f54fac46b11f955469bc14bed644e40184d12a353945c624a4c04f2f8ec36ba18b1024f4b01698c1e5f644983216ce2e3af9a85e2206a5c683c013c1e9c06ef95eff5ae4180e3c9c923eaeb40e7b57180702cefb6d368de9b81f2f6cea761bc7a4f55fa60eacb19229e5026e68e0892a7dff4183db1ba46ce0bcc19e0fb994accd11e87915984c1a508e2c3487fcf240e721b0cf35b1439a51757118b1c1481c0014e1c9d78d7303623d9ee86291ff9589d9a82c7e2d28b14955a7e526b096a8b96df6bd71e7dfdbe6a0968bd9d283bc7b347c39854eff2d18407e77e6d4365230ae1fe5e3e35ed5f49ee1e0b8465004dc2d0a3a559615b511ecb0c0d88487908ad1b29f9969cbe2935093c030dfebaca2454ee37322224fde714d23e4262e08f87f0e063a0bc9fb98fc9e2aae10812e2202da38311851983a9d9c901408159b6ddd435b87e4d29698496557762d9e16bef2b71260ea3d507a08db74187c1e8270dcda32bd8a543a47b1705df594dc0d1cff4e0e3146a0fc3b0778d912e4c8e34b56ce63e880558e2fe6263a4f5a80e963e5b0ddabd64c5bb73a6cdfac1bc260447d243aeada03a64d8dd5f094a2636bf62421f8c25d20b4b26d913508c9591420143211f55929e5b539882047029dd47d7fecd7809fc434b1c3fb8a4a940e9115d2081023f38bc76faf1dcfa48b46b1e20e6915c18ebf4328ca5f54858fc2f4816df280569903e81e6f68db31796c12d24223487aa2a178297a14e4ea390fe483bb8660f24e962dcf05687402390042d30a2c35818fa9de9316b3c79627400688daccb7d23d96e6fff42d7f63dc560675861cc35f5260f6236c922dad351e69ccc0756f8ad67b6e323a13faed0408200a0fbc0dc3780b22e7677cd3b232237aac64f862f33f64084ae221348546aa298c789a0d17c1caf968d3a262a08ee9bdd7653747dcdedddc3dfbd79be29c625f919f786811dd8ea280a060b0631b69941e50b0085f1dedd87ba954b097f5cf7304b2cf63160351c78fa4ad3396b4e58de021136df7c042e51440bf92556677cd630be115e14070e272fa0ae6a6f9fec24bca37cda4a40ddc37c9c204abfa6af193787b6718eaff64c0ebe362171dceaaddc6cdb3b88d92613cecb7c832ab7ce503aee2a0a64ded8c213b91ef35556b84f542792f9b5321002197392f180ee9a78cd21cd98333c305eb1f53372745721af6deb742a1aa622e7dc1964e377dbb9d319ce33e94d11a5d211964bfccaa969003e1d533537ea2e52d46f98bf8917f57247a4fea49c91f6711361f04bcbb320dcc97ccc81c84eccf956111de122cf40fed5e855841a1b4546af842fcf0a4d1645c3fb9505e887ccaa61389e4825dbb23ba77335633c895b41279e46279e02b18e96f566753825a7fe98c2c62fb9527edde51e43ae85263a1ba44096ee8135f2030c7efaf90720e8a7cf44a58953d3d6903d7390dd5dabd20ee242877b680de21fb206a75bf68834759d4a87d31dc2719b513ef806da4519b6271411bfe9afc5b8f7b1b6a0cb0ff02895ef908066ef2609a9730be939a87ce91cbb273e7c442f724593e24b06a9ed26d1ab4a009e67cb218ca5798f35c26ca5b71a2b065c7f52070d5f5f6ce7389f3654b49e888bc081df70334bf20e2c7b2276c07dfc2dd78e0fe6f260f2205c60f882f14180ff33400f5b9badac39d18f13ae12cb7654028ba0d88209d560d02dece6ca161f31249b7282b149b84d973de88b45044d236c23861fd6bc3884989f8a667dd7226a65b7419a1be57b9f50d40c73006ea32745945deaa4a7cd2cbbcddfe101ca46c72129a295db4c91f21895fd63dd4bb9cdaabecd500475633b252144fc64b93ccd6815bbfa47e73833511ac69fd38559a9a08fe6bf67e6197f2fffe35d51b0dca43059e8e54d206b7ff5fed050e71462428f233764f9cd883067ff7477984140b0acdcabc8ed97b6c001a6bb506571f5989e750145cd7182f8217b1e786469987e171b8dfb6007d39873a0c2088b278a1957d20d06073cf723b09c01008c52f118d5886749f0625dd612e5681f8ffe209a0124e6d0df07a9aca39029feb3de5840e3e61754e319a7628628ecc7312b19ab64cba12aa665c25348be574315432a751907eee057ccb1655b97718795713db1a76ae05bbeccb77395c9318e8fb539bc6790098a7190775ebfeec78ad4fca06954db4a18275fda419023f85bfd0a2fa705d335333ade35d661170fc0898a8f0759d3b2bd097a2080119db3d99493a626e3d0c5e23e797225824442b5935c28e2223020639764006eef088eb8f57a9d129859025f2e1d5cf16ecb8a6bc6141866192d813765ec3c215f2cad6c0de3cf190d94da301551af1847db2e0796aad32049ac830c25a10fe15fabe4fc74af39f9285fc19d6be8a76e55b190609581c8d070d0f708b8230bd269ea6c1f2d7e9681009511c0c9d4394a90b6548963b35399633bbb9b054a6b36c44f28cbb1860b1fcf65998b991c981b528b27147e7000001ef66a784360cf301a1bea9274bd7ca70baa9ecd245bf84fc825de6e579c629aee8f7ec0e867b30f742cdc2e45a9edc8c890b5075a8bb4e7c2df6c7e4a4159c989d941d59451bb67695a00dc60474134656936efaeaff697739af4a57f9272a9a6aeb2507b4b536be4643ec2189681bde910fe99dca4479c6681937a77e4f1432261a064efdde8819d950b5b00a4bcb1a4eb5b1a696ea43aca006ae917eb0ff5922430eb1f772f6b97ca30a578b4ec97585ba188025fa12ada180e563e3387a2e2d875e6db425555f31b79dccb1475a24fed96d52316aaf6a5cd932f85cb107c7f9824835859b396ce9302b104951c9c736befb0704ce8f5947894f4bd204065d4faa47f212dedf21149a602d261954d7cc52a896e81d7b578b381f9c0bd7694ccc7bbe1806537cd04354c002680976308d4f637af2d4d988beee962611a905c8319e3d360445f3741e7977eda96c0678465a7e08f367be9924f75bfbdafcf078b89221532a0bf145e6b5b9454498d4b35c2f993a346b39bfe603c57969a17083a49d724b09b6e2b92adf050c6e40aa8f90d38b5bfe7a3385764c05096cec3adb24e6e29fed5199b2966911bb70dfd6801ba3288ad9728f00ebc8aa431ee24fcde9f0457780e88a57a7872734befd65c282c5385c13cf1b7e20dea9f92ea843e015f2553c27a8697e0de286f011d7de2e5f185bba452553ee65bf820a04309bd75dcfd79eae5366060f09a9836a0723c4e9beaafd9f59ef4c2adcafaf8d0446cb7f84699a7efff1d32411d807e771e2e36b7156613f36246b20cc94ab67013d613b6b38cc7799445b435f1f8c0ad3e8d2a051575de7f44e10f00f320e664aeff8d9fc5f4ef2eac3b64a6db536b18d5e135d0dcd30746fd57fbbfd3c5c26fd1062e76e9594cb2dcc424cc8145025b79eff727ddfc86b24e3bce3161eb5a3471a1a60a12333c7c49496ea072ebee09659f78f7ad3dcc29bb7dabd0b1a4233f623519f32b6604c9feb7391181b2f6d304024dab983b833dccafd795e83f1817a87d00618d12008456ff77f440214ba82fc88143e5ff403c07a82a3fb39ddc2544d7d45ed2ffc267034b3a60291be53480a266152a8a30c16789d4f90217a19f7e5b4fcbbed992bc6b064bf494888e01b2b91d04d362478b4f6a653f4b7bba916655cbbebc8bac47745b9a16e34e4c709b8b05ecc39eadb585c847a02bcc593d964e63c423e414ead3113cada04b6fd6db9fec86f951f3a7b51193ffd63c8f81dc27106535c05c26c0efa52303114f43ac43a16f757a8a9e67f4d85f1f5cddbc190dc4f58f6f6333e0fc847ae275addf512e75c638f9bbef4461a0be4eece9566d315e9aca6265c8ae288fdfcf474c3e4fe3023cf1d4fb04e1222aabb1920cc118b7f489da1a1994649d644482b00764d04064ba7d23f054957b666d8012eae22e413485bc2a8d3ee75fe0a8bb80582f50ddd3f5d6751d44c63f76344f6d220aa133df4de18d86b5df2fe00c6a2fdd0233b1849b5a644dd3fd3b3d137f1a4597402c6faa9fac072a9f9c55106cfa8f4a36d3be334585445be77da1e8c072b20d8da1d464ec5053cd0b562f183cc8d8f1a1363aac40acef5dac70985f2842507870bf9dda91dced1a5c2c9a74eb0be67ae46e4ef49945208933da97b2a177ba5525859524f4c394295cf25fde937e1b4c02441d128a4345dfc4eab765004af4a97e88343dfd3e93521815972bacb25ca5b7226feaa98986cb2a1d4fa6634f04232adfc92cebb9f66b7bd5373c4a37c323f3301fd88708c52a01201ea30e6194ff2109128b318dae1af48bf420f4afa8e5b18236acd8717fbb265a7a72bfe1c631906c4b794e707b5ba2f94d243709a599da6e28724bf5a080b193315f9bb3f268f3f0a2980aadeceb715d091d83b5f048556b8c65adebea09ce19c6a526c00f44f18cd92a7ddb288bde3c681a513c099031b9af5da8f94388e36d31097f1dd3c128b3a88d941841c5afeb9cd295752766d3e85353ca02e3fada6f6c785d63f715bbc51cf480b4861a3feb90280b8b1276aab953290e8c30654f6fd3d11a6dcd1891dc51e6a51e5859a49c7e1fff56aa0fe80d9eb4d97cfca6490ba583f388702a3a41b23787d1b4d6aa81546e6eff9cb013a3fcbcb115e3fe7e068d8b21c420dd364283e4f1a36bc68e9139476546dfda5982cf3aa138181aaeb04f7d2b2c49276e90ed4d438214bd8d471df05f61c706ceae72ce548023919a98f7fa3169d0d467f982154a0f714c1f7eb547a58e98b57f43c4c5f33f6ca381024499232988d1b58b565ab2435e6cb9c35f25b1209669968f2f587d4a73aad5eb9144dcce87c224f6e78cc906b9525cb50775048c6a36263452d3af46260b8f72a15036431a1e6830fec2f8393935609f21f119798baa429fc4a201fd789dbc597fdc1691e8114849d2451cf3772b9178fe2712b59c9745ea4b8cb103924d3b7db400e3d9ca1bfb912daa478008c21c2345ef9aface9f66000388bfadf97ac2bdd523d7da6f48f88e828bd30a5bf9e5751a096e5de821a194d30a48c430c34da648eaa5bcd34ea197d40854be0e927de87d5e3a93a8519f5b56e7a978271d207eca08f90e0197bff84adb1db5c48c1c899540f2066795462ab2d86a01870eebea1f67cf280b03ba5ba14b0ef7cc42ee7083aa1f373386abc9160c50262d10f93556daca5a180bd1213564d6bac95973d0c9519c0d8559b38a2b334645a554f2d563069e7b72e3d3bb89a89ddda15b31c30c07c373e39b5e674863b72ee4e1fdb42265febc6c16a9df5f2f8819fbf7666f967007effc78408f46fe50f619187349984d3f59c7ed3b46f27bce1d9711ce83e43a6f657d05a47f3f5f4ca39da20a6a947ebeae524270ef6fa44fa952dce8f527eb6bc8818319e73b05a063723d673108e2820b30285d8f83eb689e2eccf6ebcf2ddd63e313bcfb3e9497f814993a251420e79efef3004cd65f44cd16ee16c4777dd44abd9eb8301342b3d53ff223973471d2ec5d2720810eb5e1da51ff9d583e1deac8d97aa84da47e315f31942480d10b8e3f7aecbd0b160a11084df7b12259a0d99ddb6e606f1439551202b33c5bec2016815ed70de6b3009901855c7a3e90c515a8662bdf4827a25ee665ca178c151ab0ef90f464e42b9cfc00fe404e2629087cdb0b57210d77238fbba36f7d53bf5d302580c34805f77a32ffaaa06df248fcd36edf08567e5c99286b0ada10138bb8db9ec1e7917e5303eac1d3b7165aac99d97095718f43d1e1e21f961c8604360639657878cb8d2a988cc6fe14b4838471e93d9b5f4000d6bd16096b047656f0a3c4a6fca24b4ecfc37a1e300fa3b13b437eb3db4112ae99913a82eb91ef2dcebc719aaefc0e0301a1d18c76b31ac25d20d8837c504df01834f6a7a03d248e4bef6deb4e409a82fdbf2cc780073f4c748503d9a881cec4005eac9951d46c53b792d3c6c732168496899510370765aaa9ab611210a35bd7bfe0938d4a685ed62e1ac8a09346973a91a5a8c939eb7243388edb0db00673a351b1733beaef82776b4b567812666fe0114ee0617e2340b0d4f0d4358080c7a09a961f25cfdcc539ef61621ad90036cc9e15c009aeb6b252452f3e5d3f5d3094ef5c23e793a22fe130e3898f7632c0278ad9cf005ac99510a95d48a03afdc33be9ff03cc32c40af01b2832ea1f51d181c14b186f5d155afefd093e31f94be008be92bc005260751aa4d83009b20357749053ec65d5aaf98f595c1f185807ea0ef0ae10572e683284cf7f4dbd88b4def0249eb4f946a578153bd4866ff0ffd085d7d6768c37c65732b9355e67356034f17a9079541f243c462b031951cae285944be967cb6148781a62b03fa70a7d4dbc7a1a755c2b4d1e90b0d47d97c14c3272af5edfa3a6fa87f3c0a80879883c2a4cdc96c406b3e461c7281a6c26a1ea040428e70d2e74caa11eebbac4639f8decc0edcb82ce7b68c590a9b4dda57744bde3d7cdc9e3c274ec5b73db9642d10e3f47cccb4614017154daaa39991191e2f27352180cb10faf3cceab4dcfb10e5234038d9f61c4e06106ec690af1cec6921170f0c623e4e04337a7be0ef7a0ee1230da2734211c5da6a171237cee84686c0ce14fc6553531cacd0e40bf13ab6a12841f6833d39481fde961702522b84588f44be52b874fe68e5fbb461d716d7a9b49a923f02208ab0f55920afcede5c53f3ee39e083c9e01b997d9a6e0f04021e8678dc8f6a3648f8783e7734c6cdc9b8d7dd10a4e79435b3fe402733bdbd137db27667a7266538da8747be52d6c9909721f9b9a13bbeae1c7b014423a14f18913403b0b1bbd2ec02f1485641a9286ebafd37018471432318e0871b29d78683042d07c6bac0a083c06014dee087606b92c2e9219759d7bf85673c7a398e72682dcf681f29cb410db429081be670285e91eb4ac28c70d5a9504eb9a502f6382b9f6f77c250808b97d1f5071b1015d55002acf2f9f3a09681050c58f9b70deefb7addfc0d6b02057f60426e21f6236cedf3d9f54eba4a743f5e29d1139ea8ab6681c02bc728848c03e290bbca49dd959b82ad7defebb7c9d789696dc215215cd533684f108c8d24b2016cf0f39e0636645258475393cad28f784371b83c5c3f7772a4a9f7dab0d241694cafde3a086230b668b27391220a6af86bd93c4264d9a1b676c320626ab14187f51caf9a374a9961ad5064400834365d01f6f43405128d12353200506d6d3c3bcbc184c7defe8346c67f45a8fb83b1b679cec59a613f762c992583b577be8082fca01b3b230b4546301e02e3b389326ca9eac9abcec968451540b68171a2ec2b6751f8218e2063df99b88ef45c76447dc1c6a6bdc886ee0fb7322578d10acb78ed89f2a9a06890b80ad54e0b48a6dc8ac41c04c35019f06a57bda24a7866e461ccf5055832ca556880bd87a418065eabbd111f1f6006dce20aa55efcda4387ba4ab633d31679eed833ea693f82c6c6fb7a9b826fea1b3d3d1655b442cc4fc08eed39567688d5a10178a766d115b21a1f2e59dd32e0f7c6a0d7c4f06de1659aa683b09962e31a863dc582b2946a1de2fbf5c5aa4d01df379e4a7b78ada380cdd52ada2fd737e043546c941d1407e156953a217cf7f7711aee380bf4a8c6bdd176efec9010a614074131beec2b52718696f623212a6c01d18ed0e23c6b8bbd3fd63e531999baf804e971e1de1e4dbb439446c59f9da1b097a2a95ee30bd0b2c7bde9a7d03c46d8e6ab01d7a59e5267704fe8d4668b5c82fac7192d91a60d547b123dc8a8255ab43672412f15b0f1db95f0479fc2297017f4336fcbd9faf54bc5495a22de5b0d359c6c33e872ce0fac226d0a1fd042d0c6448282955b92cbefe9844d7cca12b4a19167461109b20552387d25d3a7ade56ebb79441d50497b923651be1551c5eac20100e88492a54bbcf211861b0e41204c5336671b5123d2317e45489b45abc79fe55e0b5b9972d21f0199994d979e8111caf33ae07d13a15853f88aee26a0460522a3f9615954e35aa7a3d9fd064951b960a9a15914e2965286d56639043f5d4dd29a364dd73414d0643a9550dbdc940e351ede8c5bb304db3158c28dec2445cb016896227985d7588fb225afad8707771f0d78967d2eb09f6efe21c8c648cc8867914ce68e5590d396c74bfdfd20fe8aa53cffa8b14b3091d466139a97c82b6caefc0ff078818a18f8ec208c20c84fb214b2fae3a80d2ca189b181d6f2a9850b5d79920069086930a0298df90a71ab052d52fb2507164f3c1b7518c71fbaf4ed5e12d4dfdbee5b0199f4b7d0cf1e237352891439c9aab3852ddd268f52ce00464f03b6443aa40e4fef5838f2f5e90f3dc05d52d8a9e2d7d279615e5eb321260fd19105023763005ca06728d2bbb5620abdd05a5cf0cc8c0c24d8ff86d5ad0223b3b243bb50ca3fab25bcdc39760c04d8f2a0bf814f19c68119b5c8f0fdaa0bf7aa75274cfc30b1672c1810a7c42a0f9ecb7c8db58786ee6a34b91f792ae2f7a19085890aa653f182c844819aeb445b387341bfeb21129731307fafed2e5177b3664611e9b805e242207a2090498f8d3b41f7b4f03083e21dcb35713fc0759fa9ff6f67319c2f6377a7a95895e2fa699737cc390bf02d15be435e2ef4eb4678aec76e69264b880090eb241c04488c94ee9beaaa43e3f6c40d6a8d637a048477170818d598394c88540d07c155a3b1baed30fd878edf0828862e5dfc9147aebb178beaedc8e3919e2b33e5493cfb79f8c39267c8a8ed757a6bbdf93bd0d2ba6e643d076fe1fbb5746bdbe755cb2a665cdfad65fc150f7058cb38e7190095fa859f9d51abddc858cd2c86330b585d53f80d717af937454e00f735ecadfc0ceff89c2a0d9e196ce794d25e8d72a55f18bdde7fc8ce1943b08721bf63789929805d48a3ab52276c320006f6ac1e0db86b8703ba61986ee8950a2ed1be38a44defb2db55393de7aa1486a1e02cfacec5d1465a63007fffc92c5af01fab2c09451eee72fdea689806842475a2a518a982df3aa8fe691cab893add33330d7e8a17e1f3223a4b0c414303b7415d90fcad51693565eb4b3fe26812a25befeef5f5b73fbdfe182f12bd73c77061949c82517d30cc1e22a49dc6198c4211af38838ea5b622e5515568b297eb1eeb2639e09582b1a3704b2d636335d85e916e08a53b1f8f175155a098cc6330e6307c7466f8e790833be31ad2bda23d935b7fa5f68d74062a8f1a18c9fb7b0f80392ad1f78cb5b40f61a7220fb79caf4b80eee42b0f6c17df375e0b4fc9a7100af0c9c976cd33a37189c9ae39d853e4e9603537be8c69a6eeff1522f4238208c0beb20ff3d474a47196b0433ff0bbe60ec47e1157bda2b753de0dd8e0784761f146a80949dd0bbf526b87879ad9320635b3bd9df280018b3dc1c65f7c848dde78b2eb20e7981a9ae7ff7a91f2a8f5ba347680408afa6b659749bb26c9c48f72665d097445d2e7e4dff201c0ff010f48fdc207837713eea25af7f158f73eb610e53462024c2c9fbe75ccea90d13bc7f1e448c4b36cfdea86c11641f7e1922d00f2f9e1d4864c91d40607c97ac8e2d8031ebc7f5ba288f5e1337dfcfeaf0d3d5a12019c0fe4c76371811ca54069801aba595d32d7538ae73707f3612655ff26c88d4cb4d8831d5afc0833563d4ac2039d2ea97363434801717a8769013c11ac0ed6ae54b034d4eb059503c60106a7e21fc60c36b599fc90ef24603d84dafd0c4176e38c3ce392f7fc0060960cda47953c2990e57ae671ee8378368294ee13b3d59439897668c106385d9decae81d59278fad64d85ca62f08228ceecdb38bdb70f3f1197f809e9a9a30a6234447e6526f07bb7d535471cc348e7012623b308df1ac4816a19b529cc81bbba2c438872b0da2afca4d3496a304c56ad403a6070ffbad1d02dca4c3b05d90f54f1d82f20c09a1e05c7bb094bbe282e06ff6fb91bd5801229eaba3c1d71676d787b026f485785e9c17993d7ee3a27d53736e86c814a754cd144ffe2283c1359bdbb09422b45c6d29f592cb46da5285927d6546b4554da13cc65ee1fa22740161bdfedea00c7466b2643f0c3005eaa968856b49ad193fdd639bfa7d1e3674c91c92b9aac2bb01bb26b0a6225e6da9ee4f4ba6dd4d75d593ce761abf0998d6bd6cd2d9d54eaced69b7423d572862cb204970875912faaf2350f53e0a0d36e159c77a6f91603a4902b2519dd0d93b259571604dbf3d55765335dc752fc94b23599b08b050c2ae0b5448f0f97dba61425e89fe7d4f692c3e57d0838fca8777e9b514e9af1ea5b4e804608a616539000d469c7d6b33ae34f0f729c360f204f912944639c82ad312c4f31eedbfee0e00b7834297eed56e23388fe37b328d8ae927b4a8efe216611d8d97d8b835619f899c9581c2e427ec6915b2d2a4ebcfeeb0bfe9f9801f4c0275d10144548d8bd19c13e712d93349e3141c881a715d76259441ce4a2195ef70f99707f86991fe35b0a36561d9b11ca3fc497f68c613581ac83b8cdcc3dd9d1bc6eeffe13dddf0f6b95c135c8fbae10b46ba6fd8b91b427cbf79dce92b382b5142dc4469d28a65ec97e83913bea81ead74abf065ad254eb6d3bebeef60a89f5a25cf5d00bbd09ad583fdd17e7a6f186f8179df9d91919469b5c82e9045c8dcc59aca244253ace5f99963aa04ec1b91588041aa28566a89a220a3da54eeaeb5f889d87c1d75796bd00f5437b901c80da7cceefa3254802e8e2d62050984cf78fad896c8178049d84be4b4e1d6a1960795bec6df3e9be32b2c7ae6d80041627b398600d9eb6b7d3a25f718d40b5f18f5384d27782b4de90bc418b5c232b1614e4520b9fa6b47f5700806530b95a170921e1a53c4fdcba30f9aad59ac8b28710208d5860a529e1a1ae1208dac52ccc0dacdba22230d71b63b32c857fb1ebfe939d5fad572ae0828836a8d9fbfee7b880cd3a4278e5a3463b394849e731c6ddc8387ab43e443f6beac6971730dc49f700632d089cdc7e00deb0593caf0ec76d05df7b192f73f23184d42b4f7ecff851adc419d75c077eeac863014de8a58f9fb325ca546421fbad6c62aeebb8959f51e7a1ac9d29b9910ada085c85d5f1837e1b57ae24b4b93f5df9997cc73e04aa36d6a07e0d550ae270cdb3be37da01e4af63c3931343b912cb7debbabadb7b522d5541c335ef0a5d0c712d1e5d38de53f40df5585322467df7845f810c68c76dda9da9fb5a6b7d92fd5b00e9019dd88f5579c552ff95aa491df6dd2fc09cad9ee085f17ec652a4a385ee91732c0557620bde0b8b1a71d7e6103a8e90330d7f82aa9872d9202a630e76ae9c1737d1b658b124a124bb19e866afe9ccc25ee99b0e00a8214fc37ddd4600b27354bb58b332c9d6d78e690052955f4abb8cd7492714c59e1f1a848db44fc601834a812196c18189c36c1fa60299c464e1b246264be61436c4dbe08be8f782c1d1383b32b84e6fdcb922d348c2f6273c4c8ee0524989d1646388fabdbdc56fda910ed365a5cc8694d6bb314956ae4b39dbf999381cd7623de15b9becbede367f99c1db1d373769d30db38909b162e4339603e18e8fbb08d1b416463454ba419d9045a350749ce859abc5cfa94364b688a6939f88b325fb96301319cdaf405354b9cd62f8dd2c2fb9b90189a60bf9ab11f3108cdb9bf0fb5d7537f67aeead166028662ca07aac57fb86704e0d83217d64bd7fad12acf24dfecd197e25ac6bf9cbf0cc65c9bf148ec0cf7fbfd71dd6ab89358a7510a4f014879568fbc7aa6c6391c19f026879578348a59c7d35786fccac19f0bcc5a7d790080459e0b63d50606a551bce726cbcae2c01949aa3d35d47a5aa461d5301fd0b5069c03439c8304e5e76258283ffaa5b01f32505105f740db868ff561dbb0a45b2e2b72362250481d05083b10862da728fb926bb0b8ddc35de8cf87d4169562febd1a524e9b533ad75b41e59e1c8a7c1cba1c9fdcd16eada51cd5d86769b9073a9c442e7acff625a7dd10ade5c05fd17f1079c86334459d97ef4ba29007a5b4a64d804ce84b38bdcd8e02c61d1f793d50c60c7578cdcef582ae4dcca74625ab6eba6a2828937c2f9e6ea9a56ada503ae4d900b0d4ff84f7b01c51972891285f8676c659227ae3861b0efac63ca07668524c3264ca2d3ae502a0a342f60856bdc840d4a0d81f4018378b36c0cf7805f6b52405f25a29b75417622f25eff1ea2337f286acbae0bfe368d0c4580b941eb97328ad4623d743de60613f6cc48f248905479e7467abc63b1d0d232c3cbdd3cc0b05a7d93b49cb47e1bc20b05e4be2c807df9dcc11ce60e8f85c6d8b2cad1e9fd424b5e65078c778c41de4972bb1136dd8acd57fcc7f5f62d57585eb39d43d807705ddf6e0b25bbbf0ad35e417c713aaa90d1e75a3e9e411b583137f019b9453bf00d948ec7e9b88bf6e193e0dd3f8286bec8f32ef8ac2c8588858bab1c984295b7a44a0f43be0a3bdd44cca25f2c6e344304047e94ca94bef7d4567f71e1cf7660284223276374c01ac173b543217e095f77ed05cacff9b88752b540c300f6ab1612f8c8009e470f8e2b7f3853244fe9342640b5705949f58279d57d5a41e2b73ed2942981cbd13e6aaedcb0cb56dfdac427700bfabe36698f757af72b8ad9ba5a25eed143c1bfddaf0f0a1ad613d3857212e6a152a0bd13934a7f9fd567a5230516468f2deb78fac82a90909ce81073f2ec935291c3f09b3279609283f490fc758819e09d92de5f3ebce545a35d564e576f67f45158da1d4646f8701bca2063db25cb02a087817da464a2a48102e3877e02aeea535fd4da1651a1dd565618dbda9c8de4c4fd77c41818a9050688b2578ccfd1b4e23c17de61ac40ed2d61167afcbae548a807fb67c010eb26b9fbb4ce3791f228e70e06b48e8bb44086dc5f4072352bdcd1456582f206bb3c078d09787ab047ef547eb688f07e30c7dc30870156bbe07a2cb6f716758274632606f0089d4ecf3d73658cb392a0528a80f19779e538d600323ecab97a0e33e9a25d1817902306e131759c315b6857453287c0c46930b4e80f0a7fe4ee082359b21a222a1bc1d4566762e9dec8f5dae24eec8ce4ae9b51d492a20e4f923aeec1b74660d4b5afa370eeadd288e63943c6c25806f60a77137546ffba9091b0ccaf2093b6ea353abbb5cd03c56ca06b08a4743232cebe01817d8f75aba377972b0c7386814b35c940b5b1243e75dbe67d8f434ec204223092f4630adc1ebce4f276c2604c0b9785106138d63df3fe69681d790d1a054d1c18d4ecc2583cf7fcb087d5311404e5a8a51613f932174ff93e1bce5580b80abc9a025281091d46340a90c63a0ae926344f531487a6dcd78a6df4dd7fb8b419a6c9d2ca3031aa8c28b5d30ccba9a0ec5922617dcc1ebb501475a21a6bc70491b98de1303dedf668a235f05dc25451a34654439ef38be72d3c3d1e6cea9a2f3a7aa3040a48afb45665745254483e64dd3bf0f21a021ef8af767beef6fd2dade7b6fb9b794324919670867084508159ce241391f8a15cce82573d99e4721215b0e69f32116492f872d7b82b1e7130e23ec1d8a6c5761cfa71cc4d0c721fdf555917376396f38e47c05a91b08220404bba6b0ebcd3b8edbf88d0d39a72b67a753994e4d3913131393eaad16325f2b538dd5e9baf47495313131c9549ccd76739bdf820809827f88030df156557df97d9021df97f3f6e921425c3349fa97ced70f5299aa4e22e71f80ccd720383f3ecda87134b665dbbfdb6c1accd7f82017e69036af43dedc5811799099f7b75a8c968b5aa53a3950c5189732ba36d197ffcca9413a362f833ada88be74727490ea2269527551261a5d8e32bac4b54a91677454ce5fd6589783973dc3ecf944a508ce133991d3245a8489ced3292f60a638207b782bcc2115e044b3ba847e7e5aadff71ccb97e2ee70d290781726f8abb6a1512fa11125acaf4b9a319cf01e3e75a2232d7653ae21163cc43a61b7f0d32027feaefbcc7737e9f66e1b0ff8d7bead2e777d8b1483211bfc683c4af9f45b2c75d78f44e63f14642f0e78526267470ba56561a0511b267ed7ee62df339dd6920d3b6e72cf2965bcd04ffc45fe441d8b1952157ad80f1ea3ce6398e35dc98c70935720005f53eb814972e5251f2a42f4f33c98125792b47d3189d51fd64cf9f547bcbee40d2d122cd9921cb38214f5697e9aa22fe39ef794e0aeef0f01dcd2ae2f99de7016f686e6e6e685c2c0ae1b5f3532ce2f91d6dc4d22c9adf791e2328453ccf7a239da74639cfa3270d196408c9d344a6bb88e76368232834cf13e38f5e34ba88f53bbfa3ffe8455af03b3f027ded3c110bf45797b3a6ce4011eb6368a30de60b5797ebdc6258f05acfea22f5cfbfe6d14444a014fdf3b09ec86b9e8fa17d54d78e26b2f33c12d8d19345b2a69e3734f3e666ded0cc247b3e157948223188fc5bc062c578124a510ccda32d2089b0b4479f8764d17c8c9faf1d9245a3591aec3ceb79f07c0ccdd2af2745f67cfa379466521a1a4a3b3a6f665ee88d66d2190d79c81d92488c67e9fcce4b20e7799ec863901a5717cdb348223abfa369e88d2e799e88cec77831e7795e7422a6b5e753932e36bdd5a0185fb67462b030d68941d2a51f37dd79fa19c88393a44b3436714c2239cffa1d92f544747ee7a994148ed40d95da8941ea3c0f99f3536469a0f33bcf43e777348f9c67fda4d134a665c89c8369ce5325a7b11c927271973f5905cce2d0db796fb5bc2585e3230da494c65458d08fd14af3294c1b3594066b01be699ea563ee3987687e262fd190b5c8f4a15f436dd414260dfa744697350434d3a7359cc77ffab331975c75d618095c69a8a555afb5d65a6bad4cc250e7ea129fc5037cf1411edf8f19c4efb94ed0ddedc67a02196f501a9235dc55b1cba02293d60aa91a214170e74136220265e6dd841944542f3083beba6acab905de4e65a02eac204e2f5e359ab56386e6c397919973cefcf44b9a65e67c99cf794e994c43ffc7ff0f22c4c8c78cd498a5b254760e09aa770c31761e78cf3d11d5ab5eb45b728e4934876ab63f03e690b7c73999f0a5cd73439e7bbe549ab5237c9f2a529470c4fc11e44021777b4fc1a713739cc8fd383a753a6e6f15f520f248407ec99c95166a6cde62e17f244b0310d4b3ba42fdbdf72c1edf7bffe9210ca82ec7f97ecfe25ef5472f4fb32e0f56f7e37bd5177dafc29d2e12fffb4f1b893a08a82f77b923a77dc31d44e5ac95dcd37d1fecc8f287d57d24abd33c42cdc21f3eab7bf18f5e1e8b8766618d9fd5e9a2f0456d04a57ba30ed447af8f7ef0b3af2e77f4e81de09da20ed6dcf66a16fd72d6e8d482fecfa169abd17e708cb9734f054a5d8f53e720f27c23a55b7da54c1f865cce5a8f1e07b0f9b2c70e4bb78dced555675ceefb9cc39f9a6c2197bf417f7fa10c727e19199907694d4d4d4dcd1fed1a7286863cdaf4ee194d004a23d778aba7ccf8f14f31973d1867f0bd05eaef896a06d2336d8fbbfcfd26230579bed338e7654ed81caa2d6ff5a81c9e400298600c24597cd5ff84b5a6227790020075eba706f9f8eabcfdfcfcd4e3fc580522c6d179297871dc79a794cd49085b4af0e18a06e8a65fedfb73d0cd2f69be317dfc9843d5fd68535dd20c4c1acd8714752a66d55bf365a871a0f11de87bef6b70e06e0f480edc8d835f4562ee31891f879c791539f3204896cf7a500764913a640e39a74b66becdd3901eccb77970d2d8d0d0d0dc7d43f33536244d8d98f34a8be2ab4abcc3f99ffe911cc99ed68c1a42b46cab8c29b91cc739c739de382e019c3f7b621ea3b573a649ce9ded4fbb4c707eaffb3c4f5928b6dd976ad26cc81cf222ef3feff1732c0dc0ff5e02a09680a785b86f85dce99f96c692e7972d1c5626cb0586a6249854d75a16955cfefc30115fd4f993a2b69ad0aa7c9a55b40d0ed9355c7df82a92a5c1eac3950e593c444a021977ad1f8c1bd4e58e0d64dc5de7e9ec2debdb3748c351fcbeea12c8b824cf5de6397acbdb75531aa30375da03efe9db0781fcd8ded34969800f9640c6fd7df71fdda08f93d2a8ef7df7def4967f4c545fe341aaef6ab8cbe61a845ff6c7834106152239bd14402e5d6c5ba06a332c539c644f9230c59468d294182f37292e5136da6ccf396d92284e2f66d25470d3443c6923793e408a0c14e61bc9656b0aa1e5219583214186ccdb10212121a1214184dc820819626b90213ecac965bb73e9600a5187c900ce9eee098b11d410eae004f9870f4c6f4a18e7dc026343cd98261f64c4f063e70e358c7bbed39eb40c310224ea8fa48f15b9be9d35deca795e1921c8f4e73741f10ceafe665fe5eaa2df91a3eb31d32c3cc9db3be77c3badb5f6ae6ec77d3727be42ba7d6fc79153dc2f338932c05bb5ce5ae7acf34e3be79c13055b6bcdd95b638db7eaf8f3738d07e15725725d9b75d639c588961662a081d6724d87f1e8f3bb3927ee640c81a95bd1b39221b79eb4e2c7176cc894882cc3c8c4fe018de145a64bb116688c2679e09772d9836589d326632b4c1a2cd86c537559186c069b59d8ccc26616366b22d3a99bb764907053ed404377bd12b99cb01ed56dfb615b9876a5f6a9f745f7056e194d7269c19e2419e791abd5a095e6a654535c4d9ce26ae11457fba6b8da9411d989da2e51b9be6572a0259b85b1a5551f32dd5cbc55bb484d9fca79315dd56a6e0a964beb54dbdcd4ae5c6d3b3950978a85ab511bb689d2a8514c1b1d501af5837e385057a5e610f7775251a718b7e428efc2843113a5b3aa5845f705fedc4cc06d39240e26666afad8bc55bf036ae37a3169d4af5bdc755740c5ae17cce865d7e7b22b4db25daaab3ebdddacd3cd3addacd3cd3add5ce64cd68969774c9cf8476fad2e52a95e0cc53ff2de282c0a8f9ea8c2c96892cb7a67170bd4add52099b7b65ddf3a554bab41aa9fd9da76a95b0dfabeb6987e698ce524298c731ec77f0ec95bb5e438981279979cffd1e66033651e3919b5b134673427cb7a756ddcb57132991179cee6d75dd65b9d554e565df53137e33e094e46c1e92d3b9bb1e8ab9e65ad18beea45ad7a2222c521cbfb30b47615eaf9b29a45f59c3ef44b9f85abaff9c51aab41add68f63ceadd6ff38ca623333dae803afef3fbae5ce2e964b232f945f6bcba49591d689b4b4a66d6dbd55579daa4cada58f8d49bb3e0ce39cc7f16b9d7948642a65f3960c156c5cadf39514f58605164eb3d96c369b952e639a61ecb719bdcde8cd6b22d74a4295aa37f0e7f392e99e52f5bb881b8cb01a3366cc181ef665b5d1075eb53e91db29de72a95d7acd69d5e518e73c8e4bbee42191ad936df296bf0c156c939d551a387b26b561bf14aa957e65a25f659b665b6bd3a60f54ad2e511a54575d0a3975ea692157cfa272d6d65a39ae723adb8c33ce18678ec6808ceb679cbdf5b80616ca00d32c0d6f418328eae1ada794524a73b05f605c6606549c39cb71d776ff5383ba9f5151c85f98c277b2eb2182a00782df78f366f8148681873bfa8f45b6df3d7d20de83e39ceb541ffe286c485211a50e7e08f93c1c9233dcf579b88b6cfd430e7c70e01fd698607ad8aebbbb5f5b0f9bb5e500ce1e5b6f489caf64f717e4549d4af5e148a9be8eeb4221e28d215ef27df1c3f70fe242fcc12eab9e95f3ea698c6e0cb627f1f126c47f08927f5ea998e03ce87ddd6c776fb5c01dfcea5578ab5ea5cb957ebd77e28dcee66b74996b3eab9e136fa85636dd156f745e87f3385ff3e5940b7a5651ceeb60b104ccec959375f6911cc9eff90d828f7d71c89f19def2bf194c1f7f2297e55b3b20eef27a83780b4847d627aa371e791dd7dae5c774f95b5bf30c360fda58a82120f84bb9fb297aff1b74a321b025d9932948494aa0f55fcadc7758b2f7436ebee36ede717e14ed88f3f6e7fc71fb569348320e79430e7120f0e983460e3491863890ccd33772a0fcf4296cf5d4ea00bfe671805fa3774de99f679ee68866c65b2b585efd78ab355d572586de83159c02998c86d050197d42a750249a4495c0ea59b0bdcf14e73c8e38eff842c89e0d91db904614667443da10551b4cf66c8e9c1a3284088aef518daf9cc0492bb520644a0477bb373724ce0be15485c1781b57c8be3343e2663dd7e49d913aab1625a34dac69a9537aef0d0a83cb65655df5bfbdb07bcaaa7f854f5f2b4bf3d5caeb05b32390ea8bfeca3aad766ddf03b5c5813820de739ef5dccee37c0f9d1f9fc82f4b039ccf791ee3b334119d1f3f47138152a4f3a376223b8fa367d5e58ebd7a276985cda119384f593c5fe218ef896f693528869ea00ec9b279d61fbd7648968d6669b0f3395fb4f3396f63a38b749ea58d705ee78dc6dff9a3978d4c4d6263e3394c55a66b4f4a28bd0c8dcd8ddd25072a2f9102aed24d427fd6d97c5961369a01f4d26a9095c9b0954d1ffa443e3ece4f7107d2f8384f6b8ca624b0d826270762fdd4a035879f0c84223082e390d7e62f17072aadcc81eca67fa11c683e1d9164d3bf3507cae9726f5436fd3b75c5781012387b3ac2894d9f43f2200b6637ca81e653d4d4a67f6d35887bd1a7b1b19c549303952ec6c9814abb64e640e58dd1689c9403b176143d7120aeb669cd61534e0b962807e28ad8f471988ca903eaf6bf4b3568d4383fab0b07e779d02f548680e16818cee3e80fb09e48901fb50f0dc57ece8f5f4e2928464a694eb39ae7894c7ffc9c37c27919231aab2efaf9f3e3905df2ac2e1afd95ca1c9279fa76c981689efe172b6f2c77311b2edea22fc5066fac065d8cc7f1ffc67c767963565f30a60fd78593f2168da64f38313b2b12c86772c76d2ca7ef359ce791f3e3f3c0799636ba800c45886961d444a014e16896e76896eb1d398ff3b24f72c891ac49d5451f87f4c0c8b6c9ca2a8c3e7943f6a79756832617382449a18b2d7ca952993e14ca8322df9a7e21e72faf8d78634647fed5e3c8bffaa36d146e9ab7214bba6d7e454e9fa2dbd4c8bc2569c8b267cb4cd7add9ba4f9f1317d0da20186dd583dd8ab55a55195285d5a4ca549ba6d42ab5484db2b2dea9de82adda5161d5b54556ddfc7d7b4356a4ebb72679ab223d0e79539d36fd90bc01c91268979706e4d1c7974683705d6115e6f75eec15565dd32515c67947362e002720931927f1cf880f6230dea6cc271bc46cfbf84bbaa452894856eae9862a9b520c367dea54836848e0cd061836c5917a9a12934da1af12718c2fd972308740a03bffe775b853ad38ac9736fe6d2d7e2297799aa791c9e48aecd103f45820f87d0ffa3528cda6bfc34707f48178902a03fa440da00f7e5ec5f11b8421db3e91ab2cd8f38efe434bb69a49d2bf66de897c86ac71ab500d2b43de0c6a58fde32d2a02ede77afda9d57fc22f403c5b38f006c1687b886badb5d65a23300218a72a3e052cc24c728f1d38640ce8272b70a9a21e357aece851eb9173ce3d7614d57614f5b876470f216fd11d0ed48383a639a3d4a9d42541869111b8121a62d304304c774ab3830e5ee825a4160cc8c129a54e67b0369b0d2ac80bb974a6ed4c381f26a4b0810a909cb0c194131352174b4b92a49e9cb04198272b389cb04117b35188bbe7930964bbcc2f83821fff01402dcfecf954430e4872def3a9862700ec90ed9e4f3530d16252c30930b89271f67caa610a0cb8c8377b3ed5f005064932b7e753939809983e882376d9da4118f1f3d4e4896df77c0a22cb2effc72211c2fe8d6e416f357e684de6533f326a432693c96432994c269b426cfb348a6ec102de2aeab8dc17725c48e61a2e871c076ad61742d1eebd31bac51cba975cedcff3eea55b68b227f7094b8a9555d9cf8fec3ec634aaa3356a1b41d4dfafb26bf316a63525b9fcd9f6e7caa2b2f7fe6595714f3c8846d9264ed31ad6b449466bd40ba5516b5fac179096a907f8c1efc41cf8efddab2c32f7fed88696cb9ffd8910d0811ffc4f977401f8131d007e1962f2febda4ebf89e7b1c9fd6e57d0ed08d3f141fff10f086d8032f28d6f01f070aeb1377d9fd5383ec4bacb24f844c65a0906f8da38597546dcfd21a9555998ccb94d04daac3b4265bf27e6e8cb36c2cab7d8c7f7e644b41e66d6aea66696dde682d28e7f5bd87dfc3182fc59498c61d5a1bdf5bf6b7a53177d55fc232d90f77d5bfe1f903ab3f4e5e1559a65ee89615d4cae742d16899e63d7eec698ae57b0c7edf7bdf73ef69968efd7d9a45e49f8f92ec3dfe3cefaff7f83d7ddfbb98c5d95a17f97e496b58bc55033ffa6546a35e6acd05067df095a0bbdc4235c87b8abb77a1246a63ca31c65dcedc385eed49d3870a2565406dcca7424d55897d1afad21cca40d51cc7dd0feac05df62decde3fda97e671a636a6aeb6193d6748dc7f1f57bf7a5939be520412fc05e5fe07f7ce9996dabea44f92cc913c4f644ee7b195efbd77ca5bd6ae7e7a6f6b49dd9747ae7e5457fd624e306bbdc7d883e9a0df7d9df2d6b4b86e6135551bfeb2da7c78ec8703d9c718df171f3f381590c59fa28efb62102bd47c78cb86399bb6e9be242bc7fdeeaf76240c2359de6dbc7b64c873a685a9fe7b5f6dd8615ee891d5b38fbfa55c4e29cf61db83d5a095d6512191579fdfbfac369b03ddb78e64ab5230aca914b539aca43fdcc81ca298f3481c7acb72da6132b070da5e68f394a907f7bfefc41cf7f183dfd1ac412b30c4a2f844fe7da28ec9e56e100c6d9e72a981a3fdbd0881fbf8a9a8e33ed6e2026e0e6ecbbca72120fef7a07843f51e693748c57b498f043db2a45946836086035446fc4b8a1f79ffc7c6ef91e5b83d2aaab4e7912009641499e417c9efc03359b7b8cb3ede3e6a107e85d5e642f6245d4596b97924b8b915595f1ea9da5ecbc95671b54d1ffb9f44c67fdf6e9b44436de88420b5648bcd1f35e847a806e159fe1ab3dfb5843cfbfd2f91ab6ddb47807d960eeef1c330fe74b5551bd374d9c738e7d1be85652772e96050cecb3e2659dfe3cb912cee3bf10698b3bdc061298b124eb25011c5cbcea72a4fb6ad71e40f3773577d1aae65dbe8f597dc559f09cb72d3e8447ad9f57b68c81ea334be8d1d40c5ecea91de86c1e61006495ec4811c68d33fda251583e9944c88c3ede202e866e5c07fff6a0a06a4625e3dd645f9655e461be53f7aad7e54d74c4bd39ee20d16d644afd5e7bf1ac80cf3020a609b95c36efc7663cdd241a996dabe02fb2939932b301e2b8703e87b9a95833bdaa0b8017dfdd08dddc8f4194012d89edebada6394467d0b033d00571ce6489e54679ee4451ce6b0fb778b035d2ae5405cbcd6a25d5a752ad77f3a356d2dda6ad1a9d61cb25ce89649a5f6dc6293e026f0290d0acbcd974c706ebeefb857fdd7618efbef31c73ddd9c7deebf27a737ad0762fc16bff79da8c33ed656b3ec7bbf7aef891c7b7777f0c3813e6ff5f777d4a05c5d36e5b4edfb7bda68511a57e61be040f9ef9713887dff670ef9ecfb42431910cda1f0a7456dcca8d1337cd34c99e11bffcccbc8e819a51fa6192b34cd4aa1cdc29739343708923e4a72f8f8c507c3f0c110fc50b374ec30fc4007a859e0afb417053e7e9508be07fe07824f37087a9e66853f37793189c36421fde3adabbbc8dc7f24ad79ab7b16dd5ed73dc61df54269d06f927151497775e8fa56037463fae007a0d8179f7b20d6f34992bd0faa4f926c27deb9e3c8f34466d9b7f9a317cedb7c11cedb682328f68d6c6eb4d1cd8fac1cb583a37df7557220d5dff1ef5359cddf2f2993cd25aa2e990761dae821c481c4eeef1b7d39b9d818ecfb3fe690b53113640e797f7f86f418a5e1e37ed09c5b5cc5c53dd277d7819c7843fc2b36e0886e8fa43ba434fa3e6a1c94f4874d5f6a5fbf403cb12f9d4d1fea4630893fe78f5e2c5c84f339361ae7b12e1a6ff4f8472f1cfd63247fd8903e4a32cf13f95fadeabadd83fa4afefbde5ba129b92626eae0bec43177900f381fdeb24fa5541e328194feec1e634d044a91c703130175d6ef1d49ceeab2d406f63ecffbc00982a27e0f5561a87aac33febe7b173ff171f7de73f841a29f5951f75e0b3f58ce19f73d66e9a2efb1ce2208740c8bfbf74816a73350e4fde769efe70b7b9df6e93345d007d0079606dd7bcff2c7f8e3f19e03651d8a1070cdcae16fdf6e513adaac1d451cf4b01ab841a78f156ff8f4b15fc5397dacb621fd942c9313cda93ab5e122581da6ab5aa71ea65c72b53974041735c5944b677272a6ebe4f489495cc935683aae39bcb7de516881426f81d3a77ed9ed59bfbc78bbb73440434f674b52f04aee3607e806627549b707f641a863c64c4ac3f334b65ef747bbd3737bdace21ea85d38ec95032379dd2b0356ec67536fc219716466f30ff0a951f67cd894bf7062edddb126c093687a610484df5a32a54857258ccdf22b92d525d04a424d8f4a9e1835f86f75683426d911cd4585bd86d5b98087259a796a6e6d017d446ddc22d714b4a21207912ae23389dd93974c31e11e492dee88dda006361b0593dfc341310cc5d7299bc75932c29792b367d02fe2ab29b2ddffe73d2f007a56a10385dfe17cc8266fbe3f8dd89053092bbf7bfa26692f42f03fcfd02a0904b260678efbda73a039656010510720e4a5597006c752a80ab0a352b87dde1db1dea92c89ef2d6fd21d2c5b8ebfee7449dbc75df0024ade2aefbb8bbe0ad063d2875bf8252fbeeaa2b92b7eed7f70fc37a54c369094b407f70b5e78d1a5bcc9e9e16a5e0ea468d2db2f59608d62099caa4b6cba0613a08e298f3fd0390867bcea8135c89392ed8ab8a63ce39e7cc60cf05503de7a474ce39ab1017b470410b75bb2e8d4400674f896f168c07e125eef2b762ac1824a4224536bd4a5769d3db2eeb33b164cf2b7b3ed9a43678410bb509492e31d4f6c7500e549b1c33d5251f6e870ab515e32d5f926d937bc923b67a1e554a4df8be7e5fbfafa0934cbf76b5ab5df7714fff821af205dfbbaeebba0f042b58a93bfeeebdeefbeec16f00bb7bf0ebc00ffcbefff48c06e85abf0026f4743c20d9b2c17724501b7c47e26583efcf3de824d39f18e76c671096c9d9b30024e4fb17d490edf7e0b76a1087f3c61bfc48b2bcdec23fb14c8fb7bee79eee398740a0377fd101fee0ddf4afce7101cde10f3e48410a6a0ed00dea06685b0013aaeee1ee131c0c91448230823852041058182145f45b1533850eb31d5e68f2058a2ec9085937ed0e6f29ed700205296870d2430f598410a22b2106ef488f274640a2c5114d767842c2b64f63bac0965841142f4628cd89050c2b4788e08624685c3a101851041fb1e285d203591246872637dca0e48a0ea966457ea56cff61c8fe5697970b058630010f5d58a01dd1a41ba126e5dd0bc05050124b0285a8147422d46c58cbec06a428aec49a88a10ba196053c62830d2928a288305844d0a183d576a0a1476a0f15a69890984a32c6c68da94de198e4a815ac0842052b5d48490aa31685b3b956259a8070fbe2851794623a7814b518564990a00b1451b4c46ee0c0f0e891da0bb7c9044249102980e1e94b1556c4d490d85a922a27a93e4e8044851121d030821b56501be132a9553bc50aaec4906a21c4f0851630d65afb46f28eb1ba8ce45aa9d0ca83521213145844b0a289175abe702e7b1b8a5411831329a418a946b4508195b4446103432e47ccc930048e0d159640291155c44a186e4acbecbb4942a0a549971f6eb089208c161cbe7b24532d33d0e4249e444b0ba1529416116c8ae06265e566518497ceb3057ab86551c11547c2e8418a1364594060c20aa618020bd74511537858977e6b0a9e84cd051b0951bc38c20ba78222b6747ea4c90b51b531379cb083162338304520e1719574627b62427241072e382e94ace005081f76f832858a6fe96e96215172052d0460a2102326054530e52bf32506206e43d41053b205e74b59e856d17a1223c4174d285061845b1125a86ef55202272a2588218c089c1663b02b390b3cf4c0c50909214449ca62e64802bef80085890995159c705fa400b9903282929426b65ca14316599210a960295964b952801e9414e1f4830a9880e15464a1225ac942823c44eb10173c48d990c20f27c8e2258b920c9309446df1c4c6b43404150e6a0c9e4018148288d5848872e3ae80020372d34a1434ae64950081ec3df244834c0aa72b2a4ca9370c111ea1800e26e410a50a132454b1026aca4a890424a258c941845b9410705da0986aee110a85e403b27e641a51220cd314314f5e64a9022ac67147fede7baf9523271494e0e10729da123a380aa0eebd7763d764cdf4f1b7b73c87ae8bf92c3f846003942861603b7054101106c621635cab942423584871e10a264b8480438117d78fd401200166892c4e8cc0040e5c88d862ccd3c7c6841a5875a18b2b218922060726aaf8a2045db829a0f08e14e96207134819610a14332998e8c00c332c524ed084165d4411757d3a81029715580821ea85239c1411b4d06a21e20a771d8718931980b08202a530382888a8c2390d242c9488e24452d416dc13449400ac4cb630c97828e10712e08965b6f260d10483ad8a2a49ccc09d40866dddda975f212289c68a063688a0092648c022b6026e0b11307ca9fb8c0bf188148d5b6a654631d101d97be413620431b0c005942c60a858640939772b26e894acb5d6e71347381183a2e9052a489c280178a402575491c2298523312e4d2c11876841c458487001092e464040e3981822d6b9d2bdf37ebe3fdeff5b45b02f09dfbdf7823268c073885e24c0ccb09e5c266b7d66a1457d7757cade840acfe7e47499afb676de5c75b02a7cc102172938114105596c4fe0287984791de167937a6489ceda24e53273bf4302b660e2842b5d84601ab344ad6b22e2e7a8290c122eac70c10a09342580105725c83e5f2da8d5cc5aa11a84adb5b26de8c49624820f25f060c5cb72496cfbd4c3b65fc381eeb6ba0432330bb9c4e37629e0cc1864e1436b86002c11802f0090426b842727d70bd99312002926188491710c00a892710b4764156a7882b9a2a4208a36050094c8aa3d9f68494cd17ab8e04bb6d9f38956c3055eb2b7e7138d89b62484ecaab0f29f3dd9b7df696387d2a0efd5bebfbfcf397d7be9e307d874cef93bd4460694067d9ae9fc0ce8d379c3891c4fdd8e727eabb575fe4cbc13a3d571f7bd960e28dee8eecd1cd97117efe018db6d0f95ecf70904573dac7a08c1cfc3d539bfe00ff7feb8add53bceddbde3bcd65abdd66a2bb5ee6ea79d6228aa9c521a1c9deeee95d269c15a6badd6ef9c40f54bbc7184506a963cdddd67786b3e56d27577da8eebf0c59ef5be0a86d37edf773f50bc4f2c8ded8fc505d8aa050c7113f3fbf9de5d3a5b5dc6a1f50f745eede52e77b9ce76b8ce983fc36e5a71f8f8b10801bb3d2ff41e04c3190ee4aeba4419e41aa48090da30402b4a559d70b57abb7aa562ae4106982e7f4fa62b69585d6b9986331ca8c39d9d1ef550208e16dfde67163fa420b3fdc71f9670cec7ec72e5cb9e60268df98c12c1fd73eab4e79411255d3869f20313a957d41662d3a84d8308c116df3a90d0161f146f882a104cdae18be2e73065faa26a47fc4fbcb152893a0376871ac89cdaa01e91f87e9635fbbb32667fdf13104aec0f0b94fd3d166f7ce3f6be136f7806c0f80a958da5367e4ebc8127d00092c00cf083086a96d96c5b5cf36dafa8b7432b0a12c25851e40606edd2d0848f4000c60e2e3c4d75310b02091fb6503a6bcab3f594a504b1fbc41bdd986d1f639ff2230863892b6ea0e104501c314495afc3d16996945cfeb7ae053b3ac4b6fd6c9f7c90b22dce6073dce7a80d04051412dca0786284941767c4e6fec781b8afc1bd02e6e68e0ccddd75dba78722db5a2156d7ca0c3be9e8e704166d267c2a98855114f1ba9802b338018371fac29410ab12c6065cdc02a103a8942df6553d0b11d90800000023150000201008054462a160349846caae7614000c7f9e487c54968ac324c861148490310410420801000000034668688a036d8f710c200871e6e8a3c2fd99197feb07902cce524cafefd92f2272bcd65401e29fd44a851cbb690287d35105a34ced90c269de87d3e0be909d91bb5fa76f679e3117fbba73089e70420eb9c6e748c104ff861eb8ca2b4f51f3746c329ef600837f76a8b554fa6763110b7f4471eb75b4a8843c73b2372d6fdd865942867896a2d7345abc89abcce20d52a29b6128bcd81db8c86442338ec234cbcb83b6365398fedf854fbe56c2e36f1919cbee65ed49651a84b2d2021950d80245c84933fb059f494e33b854c2b17733857119ffdc11fbc0a82805171c16ef79e67f9e508bcab21dab9bd39fdd34e89117ceffcda36a55d60a51af47ead2570ff91d6970548457d049f538d49fe984f55a7448217630bbba23ee863dafa605f916929aa8c9556831477fd2d67fdf87a78a645e24d71f83fecb1ac1c4fba479b8a441b2ac63d25686ba86877030a0368225201cc865e5de05230a1a5068326f85ae1db86da83a1b84dd5ac66478224ce7c46b31354232fc493dd4a20283fbc2611d6b827ceac5992aaec1f014a6f3c4fac1a279c44aa76864b811808fe53ba7669060cb5a591fdaa5d1593f25a4afaa50e077acc90d1386c54e71e81d4c865a2e3e2766196e6a177c2e6d2aaf5f70402d7349561510db41f2151cf10e2a10b00f33e25c212184dd76d121bf4b8884e12590f16df0aefe8a6304f006d9693b9f6a73f5e80ae38a013717e144eefb77d3705dc29d984dc27a6cec0ee48796056bfceaedec36043ca5fb045d17d472061b98c4feb21138944c98a089c2cbababfa11c4827a16999e6a3b28b1b1342e0f87680392c503abef768825b451e818765757622b81ab5e217f49e4da8db92182f14121fd66bca5ac8726f3ef89b8b271097bb930e24d9eef729921c195378342661f36e92d849eecd130e7c7bc327653beaeae6f6845db752730f65a72a78fc49768ce0112f6225a100578128f489e79d1885b9f7bb00c1b0dd565cc633936e97440197f426c243ffb76c482e9826d733c0e3646f34fc6c67222cd93bfca368aac23def846c5728ec3b129774bec3e2e4d0b649c49368dda9644eb1b9b48bab135c1de477c18ee7221620335ab8a53350ca6867735764d98a5c238056f043fb133de1817074fa4834fa1297161212ad602aa122bd622b67531cce05f4db869de9249d103eff160a216b67be296351a73c72c2a1db5fd7291fd89b5a4b3db780b28a22dd354c8dd48c5db792cd964413b400e6d521bedb08fa8aaa006b988ed2eeb0ce3d13c48f88270cdf57794d4a4a5b0d35288332432c4e6de9dd37e9323f52637ea01d43b4b4eb213b224211288d915d5192c82341d15eb23c94591665a3b6007d41f901e3af918fd4be2e548e833b5c30ab5d456b8b90d58a1b11e01e170cbf870024d1e774975734acc1670c4f50496653161b7488c5c799244005f420b4e0585e7466a0e51aa980f59e468eb3f42eb36006b50aa93726482f5569b95312cd5489087d340e3b00d339839f8f88fdad053e7ea13d8c9031b388dc5256f34874541a37b04672d2944331b4eb190e0f665ffe24aba897b774ab1170dd37eb49c12b83ebee61887d905bca2fee42a7b5dd869d9725bc5f8cb96d3af5258c4e73e9759806f80bfeb254a4e61cac8551876fba88335ab9cac5c2ff0bf3d0910cb132763f313fe432b2d06c76b958cc9398e9a4694c911cb32c6168567e2874c415115d534da8e3d836bf9cfd92f7521fbe09297a3dea4465612352a76cbabc51d4e78925eda8a98a86291416b539ad401209a7864744a5a3013003c65e2ff738b6528d7b8c404ff0637ab0bb45f3b8a9e4cc9f0593981256370007c416025a8ca3f3a329754d015ea4ddc946922488b0ad28efa94a8f453a8f6657241b86640952350a862d412d01c35369833306f0889643f44a46a468659483a5d84ef7f2aa297eb72a8aa46c5f7b63215e395ed2948e3d05cd5e3f2a5ef6bc28208d2186b29fe9fd4dc77494f40476dfa33314401025e02da5111fe31baf7155acc08219680ddb1a54409f1b2510cc6ee9d202e3322841bf706824106fd43652eb3dcb47c204d3ec8b905d7b1b2f2031b13439eb7f887acf40d21a70fb149fa59c93cdf295a897643186806b32be00f7cf31ce2bc369c9d4f35356bb187f51c289e4b6474b5e2062c6114f8d95e69919c89b352f09514d282bc828404c2ca8e753cc7b705dc7b2471c693b62e9420a6b981402c1aba62eaf8b115d1f53d468bfafb6d517d1d9d6a2501e571d509a3eff881e11de5b7463775a0002d7093b55b24ca71acdc3be4b8f474a6fa64bd24b98a5aaf99027aec3e0ef6c4b7833c81ab30b7edea79ecc071c2ea37038870a228971b9311b9ea2ee7910f26f5bcf29655e4b85e89031b5f81381fb279dcaf80242a0bee71db5506125d5285062f242725ef1e2c30c78abdd8e7ee8b2d374a91e0243d59c7acd54f46d2be44c0b7b27cdf1ac684158ab40c58e6df6a2a3ead8469e3163be4178ecfbb53a6a43b7a4c895246d9109f0e0e58dbb7e596217ab5804742e86c292728964f7de04dacde186db7f09d2522751f7e021596fb0d4f8da8e5df75449f170178618fb34b60a24c43586d6217183e275d9325f509aa248e94c9cdfd6c1c2bd2cd3ed692ff0da7f923146fdb2b39ed7a835aa9d174a672adec0d8936cd7222935e0cf19db4465f0c99efc30418fe5b15f8eaee5f9021154af5e8e330ee0c2983fb1c0d811c7a2bf6f744ba0d54b9108a13630dac02bab1af867a87a5d53402da985c12b9dc7c25b8eb96024a864b3988bd2f87b917d19737f8f2a2ef98a492d7085fcb34a90e9e09657061d901522b85e4c391ed0c8f083c7774841bf96ef6b9fbd4aa248e42c3b4cc206d5d55040124989029dfa364c8432141044e462f9f74329e06031bc263f792c4d88203bb0841ddbe2c6815fe09d01c2ddc6de133771c4a536bb97abc1efe29c7b970dbeb30e7e6d6a9d2a88b8419afa7c9d33be8092e32fbe897cb11a2308360a465724d24b35cb3e7cc167193fd0383b9144214092eb5964e8ab220d5c4385d6a15620befc840c3f1b6035aba29d32d11e33a14a7dff61bb931429c3931f29a994b444999a4e25a0114ae58f5e406f947acf1e494796846c1c816dda64d6729874de08856120e43a2db6120af832ead6bc970323e358fa5358b5cd50e8f194f9e7af149fa7be620ed466a74607c66621292f0adfe88aa038abad0086538bc9038ac66c41fd1fa31be8901988b219a5bbe18615a36fd6940097221161d7a7175c87f99b85e48026aa096c8454404da8972d8bacc0ca388c5d64e5ebfc9d16b448c68be0e1d2ba9ba2e3080a23f41623edfe3af551659af70b9524380efdce306a16f46e3c409195f195e23ed018fada946f30f6e871461c0b732d16f356898f072022ead6b864ec4b721944d809c6ad3728e469e0f375b4ebe0d1ac2e1f8b82f02de4f023fb37ca2466ebb0a0c5c7da2603bb893e566dfd6dfdc1d79dc6d2f4e07e197703dc5501e476e68ce57bf2a323faf2f08a6f86933d638ade6fd8b8b7e6e78b81f78169ed0ce8a3578a6676e518006674c169481a8b30be9c541519ae1972961e2bd8b729810dcafab18b580cccde253f6bf5386fc86c44e95071b47f632f19fda19f2e805858b8973bb426539dabc414195394169b693549c9c2074ac903f0245e0da0ccbe553bf07821208bd1f823940bb08e175d42a68ab2e4faa8588291332694c31cd210ee6e3a96ddd2c4d5ab7384009e6ad5a6c93ac577593037626fa88e7cf92672d52d324c5d6c96088c37a6f5fb5bf00b9543cbcced90e4f84904da2de0d46f60ed4415fcd54b8fdcd0435a8d16d5d4010808fafaf9cccbb657014bdfd651e5adc7c5cd75d514c1ef36529c6844ae9b31ccf9a7c4b6efb3670e11f2b53307f0923963de024dd8fb8180deead008069c27b10d299a84d9ed1be6d545e560681688a0bfa8a030a1995f838ad76724519024bb1c4a5f9af344f037cc6f48859c9c2f44d5925e65f0d48aadede6812dd5f3b77e6f90d6a38c5fd0cf474da55c7b768a6b0673454eb32731f17f4f8140ca2846a640c6d2855876d4fcf9b4df27197b167e251837b7e175aeb37d945980bcbcfbb3ab07b350ee533fa234d0c4bb22ba140fd9458b1fb63029c5ea1f7a37416250e553d6b7eac8f53d4c79154134c91088c5c19ea7276df25e519326bcbcff9c10e93a2a7ae5d11b3d7a2bfed0ab03cbd130a10391f87b1e8562519ef4f5a46be5e5f2ec1af93ecd8ec786104230bf4db371927ab3b0323c7cc853adbdd7bdc025f08887230ad5144f855e8afa121cf0a919b6ed61a1ca4d66ee6ad03a9374ae0b46a0d7932b700191a815394efcebea5e5ab7b783845ed7a72fd88c9d95d35b9fecb0080d36a5f92c8102a6182c0380aa1d61f3d956818174caf9f764c8d22b1e5039bf27844b9d4ae4d8a808e08a984a384dd3c99ab9a21b4bb9ca29dbd2fb3aefaf0ed3e4117221cb8d9a42c72da1640e6a6667c0195d92b54d14b802c961e4f31e7b3138245b85f9872d36624cc29f254d617616ffb5dc348850184f162305622343c7b1061ca69db8baaba312471aa376df5d78327dc650704987df93577c0fe832e32dd2a6cd5b60208433173a7d41bfc98947a727c0c726d2d70278b9899ca3be51271d4a9eea132d2b7be91693bf8d17347ca2a34ce481958b9c06589e7329b3192160634a420ae91a14d62b01c01622994da47ac829b4159762d5a45c456d9f5984fe08070a2136323d20831b3f76c492176975158148989f1064b25d2e6c660a7f8d6fc1c8d2cf8986483a2963671cf3b628752e265acbd42669eecbb6217dc5acc344920aeae4ebcaf63252835efca55c6054d883699aab499087d42c14f46c96f0f94825e0519e47aa38d7816db244346fd4cff28e9ddf5fcdd81cb525e7457deb5f4de4af15bf410d113111ac6b87b493ea43948c2422d4417143680a5f56073cec1b177d12aef3381a84079785f35c8b02da768ef2673233a18baf719a00f6b6049e897a377604f01b510a3d668df8e7ee6952464396a451223ca0429eaf7efa3d25e267ca04e83dcf350563cac94046e59a8e3679fab9f16b02813ae0d4e98d5f65743389611621f6051afdec4f697f6fae3367c7fdce313fa19100c6d596c33937d06ad2a875b2a60d3bcc90433ee06e44162ac5ac41c02f6f96e2ca1e12958893104793125b10c0d891577ff0c18c6c4a21573dc159cb788c67d8862f73604cb5d50e75421a14a4147d62f6e2f0f92bf7051aa4721a58e208e31c49037979472d1557514b7483534a21550498bcc95d88d3f813d5127d82d786263e77939904e550d76f72e8b74aab3607942d9eed3e7317b1e98e5ab5cc178e1f1d01757f49bc3c20cb1ebc546d66004f303a981333ab505889b7320a029cc63fc3a337750a876c65bac26e82976778493a943deec4bc73c1ebec2c284e57a6ac0cddfa6c3c5ea55ac1c7dfcce9b7a5780e535be8331b323dd7286d8ee090ba4c6c9cde7d2996a38a0698cfe9813f3cab6610d4f974c964e41d85233a6c8b8c05b673fe67eb04fc31f19fb36f503d51f3679933c137fa0d0290590c18b32620155b1023c388808b9de48df91d1dd87780b6c70426da691d7834550a2016158e3256354f58a082b0afa78671a0423808744059d32990555fa91b6b6cb80be5d9a7b599c764450ac90a4608ab145f80308809adf3dbec8c2d6146286d9c7dcbaf1b07da8e11384f603eba4859326d156ff6ece92da8c6e406bd3456bfb315eeb386146708298679951248c8caa31dc0370559407c32e899673b52922a7dce31d52d7d35f0291768461d326722c9074c9b64618b4f26f1c57f17bc0a5607e0294850ce26dad78e7820a866f2973e5852f79bf3cc5d1449a39beba67408b5a42c50c91d054e8ac9df40b01e2d20e212a97d4259f0a88808cd0defa879ef11c3e4ec28e9acdf3667fdb86658a08cd11da9e81610f86ec02125e81c733a6b3491dbc0eae111b6050e594da4dc28310bc0875902ec4e1b692ad73e984bbe83a601910115b6cb3a4ac1dcd080d3951b941d55914e9293b33aa02730b2c232694da072e341105a699575cf523ab14044927cc8c714c0e0c26da6352e627c76670cb35b876cdb04c24ab7584d07cf6041830cd0578f380df3d9e0e0586d2fa8a1c029703d6429317e6cf0dd08ebba8cf243c9646fb3b6969c97a233816686b72088732ab2044209b1550a309dce7722289f3bf8aef8a248de09c7fbacc20e7fb775ec535b86bc58565426e5df5ecefd09d29564aa41a5f707f28be0d1531fa17c41e87dbe76a74b2ce9627eb190a5a8a62f6bc2c3aedb778378f18b5593477a45942b6da568eb2615c47f417cd0a954b3bcd884f680e96754dd20e0b0577dbdf039682875c0c6ebe801c5821a776197d26f9f5337b4a9c217155afe6708ac80d55f9c155915b68ab8730983025824f3d712fb8d787780d6b4f7219c21b77e2e5e32579e63026066d29e68d3baf8e919e54e1800edbcd8e104662f69437f217d768c58e7d09e35a09c23748251392646e1b56183cedf2b3a44ed73d74c160e6f473f2f002757b0d4a79527641cf89fac7b4202e9c9b4ef1f1e0cfc59b3ce9d87cb3fddb6c8be1ee52c5b2166d063fa64b8913abe39e4e27288cece83d183dd9fc8a1c6150dc0045f2eef76a206b60c154580e39f4c5ae5bed93c282a60795905a2aab3c9fad55e4960a30db96ab82d4d91c3f93a36168d8b5c703c2abfc37c69a455de463d2c31596cdbd768bab16d060810665c9df288da236319d763910b5cbc549f4a88e04ba39041d55be33d1490bec296e9bd74f3d27f90f8c8a7e595693367a8ab7cd19bafa03a9e0b1b1072df9014ff2d91874516160e8035439da747e4e3a1de180509e3660d077dba87dfc185a2a0d8d4b01d84ffc279123ee7fd619cd553baeb6348341f391c6431515367cbfba8a4f9abfe1d345a283880dd26cc29bfb4e76e2fa68c3eca4518f1d4594a1e6d2edd7c8245ca90735dfac80dbacf99f58d063a23f884913f47516a157106a122cc9de5d63460ecf0f6565380e9610da6117378590ac4ae726d276a69beaf2fde81b8b0c2326492769a6fa57af0a62ab5105bdbe3a765b8dfdde93f5f89c762a933463b412497cb0c4659c673050e12bbea44ddc5e50432b690c37801dba76c973f096484723726ea2c1d3e1730f2ce7a06631a4b0f7083a5261888fc691d3843f29f631781f6d4e475490e8111fa08a3c28963a52ced91484441c16a5a5c1e1abd0a7254b621fc7c7c847e975947de3318d52667add960d7c1a659e347eddcf6d36a3c27edc22a853d734311198959d471b20d6ac00148c48828c90ea6cd7142b83a520e522fb7f72c89722581cc1de63af0b058ff6dcb86ee09f0237fffd486cdbd1e4ee818f93c1f8c99b44632beb11f998a9d16e79930cba43d691729bee0379dbaa8058d4dbcf45eb7500e267dd3e88bc696bbfb1e2feb6c60a022a1d7826ed24ea77de0038578e82809238578e88b5a93720dca22235a9134919e0ea949a0f3db03283b7b0bd468f07b143f56f1e0073ea7fdba5d85088ba210db497ab80fd7c68dbe409c4c1853467d0acbb5cc21a442d49cfd634d1118a251884becd7c27f63b002b979b1939cd9ca0937248ca431020190e2fe7b3d10090ec1a88b3117c7a1b0ea28bb3a391fd641c980ca5d0fe5c4c90ad02a7915a9758d28d42538e3fccbe117adc740e3acad8caeba6a4a6d3414a39850c749b4d2703bc6b244b5d830c4ac647e4b6ad74d7a785acc28ae0d5d85e1e90d589e64755ba6b9c6ede53ab4a89b84b358cb82a794bc2f8faed871e0a7685e059ecf1618de400afa4526b43ed5de41d767c6b827cca2792d3112f5b5481cf86527919ff69b064935feede3e023882b2361c2548e8a9c66ba34e0975bbdf2a5285366762239fc725335b4cbc2412d1018e336c0a484834e7598d1eb147cd7c46257178b34012aa80f1a9c306c6d63d4ab613357f47090fd42844e596a824340b3680489457bf366085cae375733d30b61e4c64f4ec6d833d1050e120badd0259df9f1154de9865955b733738c8996aaa1cbe091966e68cbf13be8d9ca71ce17606a9738bffebd651c38ba6b23410425d1bb2b8eb91db957a96409c33729636345d9281c1b320651f8427e4a8a97282cdc9d57a2d5ad34e1e579622e9e48504aced3c1bb9c804e5d513cceebf69c67b6e6efb63c9ab0bde412b46a6e884a53f98cc2d817215302c7f5e804bbb9d7434f551f287c419635faec5497de2bb7fd319265ea31cf8bcac5f4b9fc52d5c4c60dbac2cc244045cad802b64d0310881ae4b811c7d70e70a167a31275e12185fb901f18dd254b79863a352ed85dc4a1dada9c7143b189710bf845c32f5685f872cc34921775abfb74f17a32f85fa7ad99a78e0d9f502dc5b479043d531d84060c454ee33697f3fb87785225091e6460b48997a89bde974e202e57caf7a6e4d84b555de206508655deaecb02956bc52a50b9dbd102b73a2818ce8d5b24fe28e77c10969539505d5c5a9571139882a2d2b9697496914335120c26fc917c3ccbdb1d4fa08a19f9755be20e44409fd7fa6a699198aae6314c2be68c8d17bfcd156043347c335804541a288e0ed573177f9fe336755b2394de9bd9a6046ad8fffc825de8b6507774001fa222dc46ca404ee0f9ee05b032bd900183d587158c4aa81c80c5be47099911e36dd6e1ff21e6cb36c258895fd616744426461f9c441e03bc8920a1df2034eff5de2a8f6b554c61a437f4cdb552a04d6f62b2672f23dfec82c71e57c5c2ac5d60812d353b92df92b41877bc289423a6485b3cfdad755210248ab7324ecffae6f0a30feb9b26d0a4f1749dbe10464de05c20fe8e58861585010b50c6ef69d84438265a80005aa960ea610d425c433f4fe86dafa281860e27b5921346eef35596eed0a3372324e98a3620e684096b49b32cfddddd03423b14ec50847c12480153070b4923fedebe2e9e74e29af29fab189b643ddd67b3af9915286c13e69bd5c8da852425db90ef02af3eff628d3513b8b90203de9c28b7e4a2f191b4bfafc65efa0db26fe78dc60ed5a95403f28f231fb05006b75ceac5f1cdde6d47c73058480bb8dd6ea5d0527854994cb35d99842b634249b01e9360aad971fb54123f267a2cb78c90bc9394f516543d0fe79d5bcb447dee6d13ceee72470dff088ec66e67981b40b50bd90f499452480d9be1220c53d80eae6d5b1c362f4bb8e3efcfecedb5fd621671e7c32dadc987abc3a51eeeff0b86c8097eedc06210d4177a959c6aabaf0ade1332c7abc33e267d1f9cdf0af150d87347b4804e15fee3bde26bb4760f58f5efd6733b47b7e21b781886eda52f2d20914c871bc9aed6842218a47ecaad0932cdc6f4a71a44166db01f873b5db97ec511448ae13d7a18bea9c14ba4a004400d9b9f4bcd59110ed1b8ce6c0ecd195dab4ec9fa98da84adc0c37dcd0ed9cf2f82625d90449a78d34d09bc7346f579e981ba7d21c29aa748f7815a5aa8c7ff6a16fd90ea4169237690982119dffdc63bc501fa4095650e8f6590e6c34fdded79eabf797beaa983ad183515369235cae4312ca01e3920db2a510c17c298fee689afb94f7f54fd79f05a8a46f8ff3364549f1a21af4224c9bad5faee050c4fbcccb16b4c832243f7a493e8a71c5c26e910bfe08dd322572a85ea26b5efa2d630fcabc8b54bbe817a72fc6981e0ba285f55524ba2113cb19a009b68905cfbcd6757fb75f569f14f297cb6ead4c7402c301262282a644081a97b35e0305febc42eb70e132c9c7e2c6e9fe1db2865f796e8c4fab8062c16a3e8ac69a2749359f68c465a1a42dd3de2de2af20e473c0d85f6df729b69cda455f6784f4eeda12e1f8008b436405e5877b2d2b2cc2f71e1822049cd117660f86032783d892b040bc52111dee68b8fd05950adb710b8dacf4b0f3a732622a22d18eb841f0df15a70365c4242142dd0670cf257db043cd878cfb6898d3a49bc27fe859db052d47840f8e4f406bd7ecc599a93533928910dbcd45f871475cf839fc643b470fc8b1c89805e226b100ac2fa986710e1720266d7148d2a55bdc0e70f8a2f10c19320f05a9ece04d13631599749bd954b9813ab11960385a9e55e04090c57b4a4a0e4318aed26a947aeee94e360a72ed4eecee06e7b04b24722d5c4808e647bc6e8e1dbeb3d5c5b7a4d842c4b7c2e0ec7ebe99137fb0c238238fd34aee8b21c45a280055da9204c670a5df10901c8fb228b38ec71427dabc1e54c8b2601ca0d9e4fb6198b08385011c97e4f74d6f9811b17237cf0d2db135a188c99ad81736fbcfffccfefe6771f2a60e98ee426292b92543abb636b1a2a8244106f2ad7721fcd19ca66171c6ed70322bf162a705ce8eb7a95a2d0494881ce5907cc328df0a155cfc453c0057f405c10240ed393e898e8e507e2e443f663fb4508a9679e70984d6dc598c4f1d603b1e5e8605d70a05849059e6dd6caeedcd5dbb051008a81fac4513f74891331e5bb94150bd0dc55a87e6d5f33f7740e051604109564b8b49c4b9c518b726aa45f460c9aa33efc2335ac8573edeb3ddceab85a4a604684ea15248d4ac273359b83f45032c7ebfc7d0d568557d261ac2ff01a42f0f3467583837a6011a62634323b59b107957daf7f68fe3fe947fa2ce34e591dd3491890c1ee75aebb9ba3d90c1674814bf8a5af169e4bf4e00f1e732a78c9b0cbe05bd01b8a704d76777bee7c267219ecc7b5affe90b02bc7e09dac7f4d1975c9a8e7f6e66351112cd80bf16e44ba13011d2a0f5d011badb03789366574cc8f0e60d29ad93cc40f58360eaaf619adb6df42c4d4e0e6639b429647c9f0c4672a5ddcf7c7204dbb1b0d568e08ca0dee4b5acf3ad746a0a13f6f1600d64216c34e69a920616891de81e515d884b80f49f9f44aca97859e79b42d3535a1f2007e83cb70d247be91eb3aab89ca9b357b0ee84f90c9fe3717480185bfb7da6c27256c1fef6607b6346257df31eb705155d27bd341647421e26667dc98482ecaa88ec4c50c84451a0e4952df50c7c540a09bb490ed13b0fd6686bc1e92833c1806c67007f1fba87f10c49a20604eb996cf479a8543bb003111baf87b8c4316620f360d492dc26f11c4101cc274653dc155cb045f9f9bb90f17b9bdc54aa84ede78f9837e4816ba654c213634e5c55b8e5a0538baa4a51584f98b4b14186a70563ae4965a812a6d290fe2d6b02b933d012e6e06d53a259777a86a5c8d40c496c8b6851db7a7be7505d74193d4e86cfde7b5b4b147634eacbd96f0ecf8373e702119357b6dbe10e43a51887a52d75007cf1ac203c162f802f77465d6ff9257339231577b7e4388390b6a2bcd42aa2a7b27d1462df3dffd00f92cb372ee8592cde9fc4db6ab044308a4709dd38d85bf319b090fb53011ddea6dd4d703322f8014f7dfd7c9e42de6a98490dc36919be84b09625ff99593a81607a21ff99d80ac5de1086828d5e10a45d500f5de27d91c041e03dd348c7adeb04aa11efa968eebae1012c79cea5ea6de996c4f8b2038dd579f4afc71873c4fd9dc4302854a26e5557b5e2cbc0047f9249f269fdfae83388af2e5909f79b1b2df13b2f104f998009dd206140519f8267f284c80fb535634f289f81a81d49e7c2713afdbbc5ecee769c2e26919a5142cee8669879c467d6fee0089960d84af82489f0878742b61a9ecf9de3201609bf453f482a6171d10d7516f0e4c93aa75d629435b71a352f47e9f08e2d6c29838e9e23ffcae87cd3b70886581210558242de111134bb7584a2ab3f9d168f18af167059eb2a2af2a461f300c7518b33c56159d0d978a51a1a096389fda3df65dfaf59488c862d2be5762e1a34bbd652ef43a7355b50fa7e47f5b306c6c6816d98fe1b0f0c6f2e12f981850268419ed92ae5ef29308c7377ebb7d1c9ccc2cac28edd3cebed585c3fdffaad14f085b43e78ec5b1274e910dd72e11720a054e4c117d8c4954d120703aab5f42ec2c8fa5ae455e170f61d2e59e288d21721822ba9a6d0684f015e36a60cdf80ec50d28b6dde4d572dcc6263cdf015e40d50374952f8f8adcb41e8760b9de7213825123db2978483f9d22c1106894c9e850c4759a8dd4d592cf8e3d2b964b1ae1a2caf9571701cc2eac65e1130bea9d9dabec0c4498f4300c721491b3aaf95f3d54cd2a2342750c8beb5c35c289d96e61ecdf84d482237a4c233eb4c9bdc5005b92afd41fe9d2bbf6fafbf3b38cce7250f592fbc52c90adfedd5fc4a2ad5d734c3858a0a06d553a0de4f8586b8655321dd980652a9bfb8f1586fa61d3332d3948e1b5738ec7a2a73542aeb36efd8ce806025c2a29fc21db88138ab40109526f1514ff1ab0d35d9f323265f4bb09d59769acaa6288da21b3f188e0c62f4c7307ab466a6daa1518cf375a9b037de6743700142c23d9f1f11a01a78ed735104ce10a45f78ded08cee6d4e0cfe7d5d8af32e258b43c82de6ec22cb43aaf3ec33ba4b19752d4f8e5c04245623e415e17b55149b63f5db00889d8a451e158ba3e867290d6816f33ea5f10d54bbfdce0a6cef939db5c694646f85b0752aa6428a2d05d2ed8395e3d3d476692809a7b044f0d4fc417fa741c5f837c609934722c0bdf518599a7509ca75b4ab4539c6cafe484a9e9610bcaee2d9c17fb872aa0c23d26b1fce5c043a0d174a5c90c33e3e50fcbc34438f15fe7c3b95b637922d2cd4448680ba54a3407d3827765301f7a00b8b15280a3a4fe5327a6579c31b81c737b192c0d0eed19486bbdf532d4adc63d10e6e79683d98583213522c0c08c1528b88f51dc9529eaa5fc2e1959a28385a5d6a48d25e891d8b033f378695c0eb15523f02267cdd728b3fd08bb42fa3d8f07df9cb5aefceca2cdd1a106ec25185b75891e00761a78160dddbe03e88d7597fe0613a6e3486c341343f789d68f7f2e372aa7688e9bae12136183f6f7a92bdeb67e881bcc38582ae26fac4f8a9329fd751e93331d5fec6c7ac725098341daaf34b37480f8e9cec1f59c9095d9fd1354461578aaccf2843249973c4c9ddc9bafb33ce3e0c4d098bd079ea8861a7f15a019aebe7c62b0d4e242679592eafc949b84f04d7e16b89b3da40c83d607c218d2c374e74023a7f44bea6810e14126926710e5929b84f8cac7fd6b59fe2c936915d7f633cab9d400fe0cb3d2a1b57a5b34e332395453172e9e05708d5e5b5bcc39c1321df7f071236380fde011dd26270500aa519b2731c398a384696c81d06a3022a6293b5f7587d5ef3112ecd748bb950237f4937be3b3eb206aff085485e5dba38446925ef4ba835a196b0cc0126f1aabd4b4c07823a12ce845452d334582f6bfcb6a1ba5ba612b9175990eef9606d6f47ae4661e97868a89b4c845f1bf950d4f98fbb51b1ce3305a5f893dffb315c4cf95900a0c2567a19913708405c0042e8dff254dfa600fd76c4a242dbdfd08cc06eee27fc3a2f10048996755b18b67ff419c1fdc04abe3c3341c06922e83da7997058857f7e79cc50b16018f309056fb7118caf53fa5061bffd8bfb35e9dc2ee93e73f235c1ed48742350619c3e99fe321c4f96731288234f801a076e7ad65a7944519f70dd3d4314b5a5c41c1113889da04f23db4c87915e0124e066ff9ece35104f7217cd2ae131abd9d335ee583b9abd66b0b56719e0bfc242c98ce6cb8ec78bbea43f6a8667b20ec84037fc787dfadc9c0d986f8d9c4d4339d0ed4bf86fe7e543297569c08a71e928705bee168d2412a016dad6c2b5345a51df60f9288080b07b844ffd9cd41998a9c7c7ee05d5e44d0807880acab4b39cfaa44157fac4a0e6ed29938a01fc586b3cb3a32de53117aaf55b650441a8b4bc2eb179fcc87e1f50105c177cacd33b9751a96315a4869ea779257f4cc9a4865b53ae60249db0242ac9d83e408940675e22347357426d14a74b68d1a18c788e30f35c0706a322bda97f2069b571e51f28e3c3c59588d3dd24a8de4c11af3a939e9a0e60248eb9b323fd064f08e88cec5647d2b14dbf99473c914cf2952982388d60b3dec2124b78d2119210d326fae7e2e4988ee40ea7f138925e3e3ae884849fa290f00b62965ae31db3c2bf13d05daf792a7f51302da1e2719613bee873331678720367c6ef152b7b34243a406848074f16ec6a3eb40a7588a6c8e1f4de399b899eb807402fcda1fff38b1580eed0f5fc0b47da9d526e8a4031939f4801d10a3ae242a67e66c24116065849233e116d74c9d8033354908aae13a73cf137c6ec0b40fe464836345c17628dd7f586bdb45a4d6c2c86e67286234e548e46628595d2e87ea12ede97cf2c652e54d99da85b20c150309211c38717f31e3517a0ed4f294d9055bd3459e755e2bd1bbc1cc462fe1198a287a27165ba3c6c10a59050f91b319454f80e72f39cde84183979a6102333f9c403b698f9d3fc01c2449bc8a46d3a469210553b227898ae1036e5ce814b4cc73c8a0d6b412a79d769b31f7a1fc869570f75c4212e5dbb6be6b4dee99c5b4b656376f925d079fcb70605cb936096d0beb93639e115ee4810b271ca59cd55edd4737e761eaa7ed90a1118621542011abbd9e36ff4a9964eee76ce62b6b17f7b1dff4eec31a7bbcb40c497ec175a81dce25e7008932facbd713030ce1f75030ddf17db37a56af37c0ff28ca8c1503f526384ee4a2e04fb1081efa6fa34d1586de26c6d6f43a99b08e0737523f9d47a0bbe20070cb23cf04eff3294f8c88df955c18ea2cc91b263a9fc7026c568a586b4c1ae78e6482ee154d1d10c95170db63f19dae95cf08871d959dc56f2837cfa66a9c8b1d61f012480822d831165960c4ff9b9681c566472bc60f5fa520199de14d2bdedb624cca0a9c8d24ea21c26832b9353a48729affc205c5fe039b9b1ef52f305038bd97bd4b13cd7b3a247be40148927f1ed4c194408965c0927b7a2966d17a4a178459b16c01ebfc4fd4ae043cf7754bba6a131c080c9a2151799a1870da1b79da09a2583f4b3147255ee66c6e23ff9454b20ea6726b97f6614c139a376a49cb8fee32ed2441c0b4154da16ad885eeb4e2631b5da6c40d44ad475baeac7f2ace1e7901f2c465957f8fd929f2df134c6ec3b0b7e953e5d0ca45a1a2a43480033b8811c3bee2f2b29f62ba5f36f4c293299c9bedf24fde4b11b4ea9e83001976ddf4b760a07f4097bebe0a562f666018eecc31bf67823dce1c02d7ac28096283a0d6342a8f4a237a3e154bc52d98c381082ebe1fc17aa6169ebe3af05d0aa35b2cfb4efbc6a88c006471847dd72bc4c3057498b1c79f5da2db6386586ea2451bcd500983385aa7233440fb126f352068b06e1b50b477579e7e81743a56f2398a65494fbfe15409f4bdb829b69e255c26102675c3ddb8c3d1cf0805ce6f86251602252c366eb4d3397767b4a0a2279118b4a1e9b34336dd679666ae3ad9f98ce959b3ee4180904ee9a40c511b808025b18f71c55d43457b8340358e3e8aeaf2a28093633020d404780d89b28eb12bb999098cd4091f2cc2dca0bbc39e5bb07be190c7bb2889264dba7bd013601dca77ed76debcb6039a9be329c2257d709fa004095c8666ba67aafc0e63f8e0f0efea3718ce2a1c7255f20f8e4bbb7459f480e4b1c9e7e910f99625da242c64180a8a3c153519882b1b836ba19244e1170eb94c5b3145507bbef996412b10a206c648b716e1fe9c085d9f7e660f2e21b1d1fb58ecdf8721cc9bb318067f9468a91efaeed88462584c340ccfbd9608ff46fe2ce7f6b39030bb6ab2f1ed2dd3fb61794d48131a17e09b664753e3f60bfe441597d0125773e75ebcde6f526b98c0e0ec3d59a406d51da3d1222ddb3940f674c98bece6fc8d38531f23f876e598b65307b66eb3fc1d23f21eaf406b6467dedc07dfd10ff36d0849b4262dbb95a172c39251b2c00ba2d109e0e603eb0addaa1a21dcbec670d05917ebb2ac95195d35d0008fffbbc0047fdf8cec334e9df575d3ce6cda5ead503da56e489d90255e0e872d8b0db0635adec800fbcef58138c39c353144012007337fc63f38dfe4e82723ed0b09287ea2ba6bedbe05ddd4257429aa3304fd764c3b47282fe00bdcadf3f37b730193c343da06c80b9015536be084d3e341ce2b225e037c9a0bea2ddb76ef97d57cec9d7f2601802571fcf5b4143aa35183cab80c6b7ce62fa25c557e61009e72ce9fa5898c6578572fbbeb6591cdf5de9bbbd48ce89f1d0389d5ae22d465b6759d17258453577aeaafb9c6750bc155a29f144b6cb68d014c39ec1e4099d201726271e95ef2e811eb047bb971c1688fa4e8d6eae1350c50dd60c3e296c1a2287e4aa14e3de4c5de840284a9638806f3d0206871856b14f8bd48747bf1ebb7cd5283b09c94208b37a71c4cd38144230744ff9d15054e5484f795c05c7d25774bcc89fee821d095070db7592ddf18a84aded3b59169696daf60c997a2a8ad2847cf72bbdbd0232f73882f8ccabe44aa102d2fc61efda093fd21e7b57180796032d3c35e0d684fa1b1e0bf83fe84eab8d3f865ca56b54128c8328f77cc936bcb65cef8ae279fa62085d34877578e0c5837b374dd106fbfa35f955c40bb5a5f87ac93d3c1ede77b8ed85368e9c14ada827a898b2b900ccb1c7876884e3d3f5186c5f31ddb8d2e9393e277034bff8a0f91dccdb00047d80aa1feacccf1e1749e5940c5c99f85eb4ac1c0256abbfb4264d07a77bb1f7f3d7ec6a6883b937fdee1f027cbaeb4220d814e06b66373768c580f988796132ff93b7e9f2f318133d286e78773f058d0a8d5b46fab4ce94720adc581fde2927fe0ce443577c50a7aedf8fe75e21d98eeed93567833bf8c878b5b088131128a7456b593c21c7f0b8c80dd0012cd65dbbcf6d3f132e097081f1cb04c9385e1444cdd836e2f019702fc856a3ed1ad0188af8637f830320670f0b331342fead9091f1edc33c5128bd259d9a8ba89b95afd984e6411f7a2ff19440657edf17c8fd071a119901de9faccd44b07bc9197b5e8d72bf587a583cbcfc096054f0f78f0f43686ff9e7e95457fded4ae03359cf0db5a98844e7a5b563096f92f0c96c463209a51cde59c5ad4e3180628b7a2912e50362801433629dea3d3a9e41438301fecdb9150bed31b03751d5861f604f33697378c396867988f925953a2994b93cad955f42f8f07048a81f58d47f3ed931cdccd80ae8ad54d063ea0a0c5d3d19edfc72d777276cb652d90b307f2d1021edf9212542e54b1dd7c3d2efd90f81831d8801b68850b3471536298481492ed68d2940a140f8a2b4796cf91d151eef277e2eb0302c64212eba6fa5c0b38a1570f309a2f79e4372d09dff4ca193339f9686c635a7cac4929764d1fbd1a1d09b29416b72a7d3bd0e82567842730562cdc7e6ea759d79eed85b0ef9591761af20ca076700e276985e25fb12ec662f0db86c6e801504857202d6b50facb149a6f445449ce14f62480ae455eafac6bd218873b10daaf77fedf387a71751418bd7a9df80501829ebf41b4edbac835a7a602b1cb334798d0bc7f30eff00acd3502fbf153a91bd66e37a372ed3e58ba073d2a06dd1fb539cffa98ba07cfeee901e1ec28e121069922a5152793c5cf9fc180804a0e726a8a308167989cc9654b3805762c194a693acf66c3c0e34e7dbad11850f1bb6fe1d408364c4cb10f4f3760b686adabb460bf486844bd2e67eb69a10eeed5599f1bfe41e11a59110451a1e914d6dd4e0832ccada086a36bf620eecec50993f63be23304cf04a0cbe725a35afd39abc971d049b65e0c4fb18f285d382256260eccfe056b839b8fdd70b2237f7b060b32932aecfa2600452425b2009bcff687be3814dfee877b012bafcd10b851a9e44fc1fb135408e9b1f9f3a910bd0c1dd88c9496b45fad4dfdd97bc51e86265d863cbc328b8ed8c18e511264f894d4c668d178b5d86811e6bb726677296265f5e494fa9a15fe058628c06f87eaf3bf88e0c2036eac63e0c91ef34f50b0595bbf4072f7224f83664695867ae1bb678015bab4035e2e109b949ecc6d66a341839dc7a25d8fc84bd3c739b90a1be9e3ff519571d624952e46bafff3ef676debc25ecaf377055694cacbfe9acfa7703c946da7a0383ea2eea88cef330c855f7dd8a107b31e45619a4a9c5b15cdadfdd01a5891c289b70372b6837a44517e56066bfaa205928739260e66cd3fa8e6925b1b1fbce097d3e0aa33eab0b7c5ea4c07e422449081da0cf1771b30515bc63cdbe82635e3025713ffc0f56d6d1900748c3244887d7c68d9b94da5cea781599621673046ff43f8ecbb14e53ea300921b16e5b03deec222193f52b3b325dd9ad631c6e933cb147d83cbb355ced395242260f66e87059cb03568922dd0c4d4b20ddd90f3594e823af705800bc4b4ca4b46ed4544e9f3ec5dd01fcf74e28426042237b53c373fb7ab2ba08d7784b2ee8dd6548c3a4f1234ae6b8e3d630b52e61ab4f8b031c349f124caea74ad91ba16e1ce341bda6abde7b1ddddcbad2762666762cca329da72292876967ee170366736cda2a40e9e3c2b3cb523d29a7c4bdab1b8e130d5b493ec3d7dea6a920dbbeb0e51a5c55a6605b7125c8d0ce45db87ed89582803e338efff9c748e30ba310384fcfe1af1f7268c5fc5f7d3cc280d6c903cfeed9d66110a02af41a2661128815f02498b1d48b0d8a9eea79a54ddea152b552b4e566889b5003f6069e273e49ae9437ae5d59ec71ec0cb865a135693202bc8c646a88256a0cf712a8b1d3bd57345b148cd4e53171af122dcda5d0af1529621a0edb20baad2accbde7b45ed1f02433195802c0af09bd866e6fa8fdf9e2ad2690dac35c5dda13f24f37e2b91a3b890bb53814b40e32a963771978f5264235dc7f63fa82be5645ec084dfd211f57c5693ddd32ef309ce0526e2d226a56f498962c4b7bbc054e8170d8f8855dcff4ac4245d64811a2e580dd662575bb33a2d72973736a41bf55830ad71148335787eec433525e068093e8cfb2bafc5b9691c3ce2397dec4b1402682a8103b420be13475ad71ba9f8a9aed8d94f7f7f5061dcf987eb2d3c592253378fc14d0a3aa0d10ca2cbc59fc1345ede6b5e8b9aec74194974712a33a46c853cdb52d61c6343c784ac5e6f77be5263de5ee219e9bc670346edf8677756635c386ab22913393c9fc2ee7916b130897a55e2b9d322a1185212556c16b92e45ca748e0eb0b0422e27503822524a7c9d23db635a49ddca2b07864f2ab00c0cc0c97e212e2d35db65102fb97fbfbf135a6cd391078f7fb81c85cf2b7f7c9e36b0d39161ce7f4b5ec9a63392126d2f3b1085a10500c2cc860c8b24ab5c14b996467aa3c1a45cb8f48e3d9935ec51df6973fbbeae3ff0bd2bd1f2d42386d183c2ae4d966796a6773e4da07b73b34881c627ecac67a7bf3ed229ed927582558d1c75fbe91588ab00b2c160de0a456df45981f73dc98e95be68899d8d13c7acf6017f02802fcb034e168560d1404c5ff9eec40adcd935180b9c33ffce7eadbf6e571d741dfd3c0e92403a109ff6ec9b7f70535c6df2c1e60537fb3fafe6c99ca6c8b0acd6c2b6756b410630d3b060b3a58d0937a9b042b6daace8bc39a0f87d122c38209d96553ca6c745d74c6ea889b7dfc20508033b81c9aa083a0c93874038905878fbd84463427651a32acda86d16cd58f92e2bdd0d97a46866ebd28cf428accd2463049144cff229dbf3368a6867c3c2b90efd2dd5199fa10f547bbbe1e50e0951c1bfd086d5947976d04c2fe4977fe644319cfea2112622176327f4e129714d5dbe33cf8bcb95237a7e510e1668fb7f5c51023ae3db2b8ba49ddf9f0eca1efec402cda3c2d054fd682187693578281022468b3a6a604adfcb66b6a718344fd0d5a1ae03173449ab78a1cc829980e0f6dc5d91f8a07d1ea32c31b1ad785205e776638523f788c314347c11957bbbd80b374ef397e5d3a31a105daf920493b9bb3beb701ac738bf906b4fd03ccdbe4b09f923e5551f7241f43381cb5a27602af570624c14db3774dbf9747fd800249c192e67e9e63aac9cac9e48057fdf155a577fc35bf9134317099b64b85a8a5744ba9b440872ef8e6d7abf35acbd04861c2d59b53496632119b0007a89312cfb97cff6d4f57b2fe35793677261abe12b459edeb996f74c30650a0792119ef9700adf973c00b3537aaafe3c9b448707c100b30a24d3f012d414f36cc291b6af2ff94cdb0f76cb6ea6b129f466b702ccad0fd6d8b99835ed8e6cb3b481426c2bb11e57e117fe590091c4229924c34ef030c1144dacab32ef4fa807afda534eafe958be8892b6e843f0deeba39d2f711851a4aa693feea33fc437744459e105712a2160bc5eb054c35c35b424fe6c8760c4bc8f925ff452b7913b4e416749ee48386d36e3e6e9af29d9feafe3ccf9cf42194b2d24774e9da182a59e14268bc9eb81cc2dc0cfc232d5362afd0da1862d12b0264a62c0a9ccce61648967759e0359ca7f0ddc99e25682de13876c551f23cf4336f5d88716008f9526af3f14a99e1514872ca14569d290c9d9f36f655040820b1255fa0e97b3f80e17417959aac383f8ff197638a47624a058f704e1439c13fdab94e900836fa3911a13328a2347507b0cb4ff63c686a122e23b8ba0ee8cc21966aa22db1b92d58c913aa876ca8a9dd60853a4dcfb760ccc9e50e710b4339eb01bdd9344f2a30cefa95317b017965bf447100b703a07451085657c8df483679788121d675c2820ae1070248b46284036925edb7a06132ce2a943e111b585123be604d8ecc0b1777af93a540bc5f9e1e7cdc5799c65d7423fc518a862fdc698c5be32bac20fbb1d0f2dc41d011d034319fe02c2154e064cbd204fc0860cfed60aae55a8ba9230780f31f8a9631c9acf94ee20730242f7b695fca20b002086a4e04a300166947cb6d7812f754e5a83d5c76faff07a0072b7500c7b125e2e2ef15f6bf2498f4b7812d2e83149ea24a50185e09ffe5078f9da944a60a391365c0ffa96e6055388fdcda03eba851ac03e5d736651ade4aa54529d0612cbadc5c4351d28a5b7dcdd890d0dcaeb5ad2d8b048e1cc8a4466c149b577aaed0a357792bb1c26419fc750c681e2b5024b1392ddbbc84b1c501fa5c622ca68a4ecbd62740e5f96c65387846bee10b14382e177af167c12bbd6756a15e64826bcd3df531406022af9e179ac496343dbedd3cb7532cd23c6f3f429a5ed1d0400f42eafddcd34a6776372416486b5009cba5ff45a70c3104aa7eb6a54d66ac272bb83c786e4e3364e1e786f57083013d89721fa7e3a192e1b8633c40cdde621615498ff3c5f1793b704a27bc6f36580e6a2a5c1799ecf3b2fb1c6a516ac4a12c45c7ef87f19bfc750cfffea3b64c34e9d7fa75c8b7219b70d92c6cab6c13311850d18eefceae8df35d00642f3a7f79a8de06605c93fc23a9ac45a419e4308a89382675c9e79aa7833d9f52f895b26b0309938a7af24520e061c984d3276a136b5d094532d36d452537325942e7e7ff4c6ceaafa25ffebdcfe1b5b0cd7e1fa084ab3d18713cc8295edb83c64abe890725728f5459c01f9d46595013c41fa752db2c6aa6653bdcd0552384ccf118ab95d9d232c06df5ef249132161791026c1500853755faf0fa6caaf099d90526e7859863de08dcca5a5139b1ee568ae9d9e7f0093971d883122b9900b382b612094d05ab9746b332212b5aa40d02003f66694b1a77f52af97420a43f2df22bb29a4111a4912bac8fc8bbc4251de925f96561df87e38875e6b9fe69c1053c7c3019993f1ee0ee11504cc0f19810b0a43ab4fc2b76e71bd1fac498cec8a12509e856d6011e9881304cb23cbae4f84b5aab03de24141a7fea7da0389e61e37497a878addc42b5c8dfff061727bf557c580472b3351ace57e162b1d731f1125c5ffef82e93bf7bf6b7b4de5491f39cf1de74aae13d6e1f1fe30235a49080046aef913925db858efa3012f9244bac90106393713e26cb871b5f27171c1dea719074323faa6a5d900409e28500b0f057a3b576d045196118a634446f23802d29acb8dba93922543e846f905a3e5b1470d30c9d95b808c913a6ae0178d0cf0d8481e2cc478250ca4b44e333e3d6c0dfd7bb8cdbf49be5833198708041afa67e2bab4ab2779fe9c34c4b5f5f3be759a5eb9d9ba8d50671bb81231317858daf82ded9663245d1dfcb4164ce8d738f02ac146cc7d008c1fdb7f54c0f4803a0dbe5f8c3e605f2c547b89fe3339864ed1b2da0bd991ced317c40376c5f2a354e4627881315db417eb20a85c08d1bd5c4c6955273084307a62b9e42af0f55262e54c5981a6e110df34e8e1fa0b9904628adecf83e4fbb2866f491c85f8106aa1030aa96146296543ec21e02945003fba134e46f6e25f393dd0a6612aa102cc58965e9550e48c389c1c04a326470cebedb427e442f8b848df82a8c5d55eed0d1446c6e5d6e0ccdfa2d219019ad3d400fde350d690cf04d083844adefe5069c7066b0ea24854ef3f5853328d7bb2ea735b370eccec5f0f777a467c98f040c423fc4c3df237ee8b1e9cee6c859c860c0297667809ffc0b73af9815b01384834fcb56d3e782ce11ac9124345a3d34fc22ff7e0c3149d66f7bc8feb9743bb54405ea88902011cfd4ed07ee19c6b8e6de6dcb08e2e86427e5e675dff052652af14d7af936f032bbbe508c54fd4dfbd16921e7b1c1471e96f3a521b911e32a5888b5a4083be97a3ab843a1dacc19e60f161fd84c10fe94c9952829da6979a83ff25faea14df6f6d5c876f3103a8a93deb51a3834c9da185355a8b189585f7c71ca082295ba29e326e60b9d08ec5386deca533a03b903541380cdf616135e348c6b09a5cd4a074d7c2e3d794e44a0c4ab8537f3f53b12ad2c2a996c5ec0a1b6d9a25d920a782bbf2120262a71f816a66ffdfedf5e5dbb868d37517607e880d697720a0119077b19df4e9e8709a177bd25508b00c6c79d4b1c1ca7f607aa7b18e6f93e40c8ac9d8705bd9deec9626cd4aa06491416430f1e204177b62d1bd18e9fefa0da4d6e032f785851dcfdd3e2a2058b1811aaa976c30a067d9accbe4504abb7c2a39492889fe518dc529cfb481320dc05a785cdac3fd225936716b149620ce252d079b0f14a947bd7efa17868935b6483516f7653fe108e5c1da91db5a58a3aaf920ecfacdaec9cb997d80860e966fbc4c2cdc52f41fd3dcf8bfd70fc4a28f99128b1b5e0d412c91fd59a9deea1d9b5170e92e1ba12064847d72f9ed9a58874ce906252c6679851b735cdd5f3f17e35e0ec6117e83b9718e85bcfb608428bf03df02e2950a07e80844403f21b8b0e6ce349c5ca2c260aaa780949050d2d9fb90f890e0d33b4a8263b0f3ff72d06ad34072f3567c85a6e301d5b374d9a78a94e5a3523735e3f9c2db0734aa7c7c5787879687b485f1fce35966c742b896aee8f0361d98ff221b58f0746a0bb4ff1443b1f21eb26e1503187f52903a842a278d3cccffe3328876cada9acb5f109f1679265c39ff58a2f5f63162117f63556389d00b7ddb9cfe0346c62344ac0c9f130294d0178425920cf8c5623e09bd387d8f1d1bc4a7df18f8f38f20680ce2734505fccf2d7e13a0fa68ae5f7f949346bce6896c992cff605197740913534fc38ab50d679c92bdd04e6682ffb8581c3de476b13bed9e72cc7807e6ae6a0f0fca5cd9a03086c1a5971991a7e0a074c128727a4fb8e63240df8d4e87ba2077b50ca0c1de5ca6b4a09fd2d16022aad1342b0dfcdf706d13d433db3f592bb467f911fdd09969d1fcb4fbe4ac156a4227dfba8999642bbb7239cc6bc02d4e1cc5ffa47642171b9807bc2e62027befda4cf5d0b994e36b110580f40ae0b8505bac989738219bc3deefb4fb85037482687f8f196ceed78ef556907987d952b042016046c57e36e47e9d5e4c71d5fb7bd2327809e4663d75a402c9b6d89857d2d07375273e3142dbfbd1c4c658fc64e2436ad5d8a714d9a626b8c6a26c3ca5cd8b7bc75162e5ea63a1644e4365ed6b16107b8462e7186586b0b2c43f526f0656c544b70f5e59e809462b1e00853cae8bb57e39c55abe0e2435c53bf68c0dd39e3bcf6da3250f4ad1d47f1e9813b22b109c9f2ca6b06b362f8a0c11774cd90321561ea5633ae94828698a5b87a338a0a1ee58aa9e3759a68ebce835ab5bffab1cb564a704c220e8f451d02ca511d3a61071e59d7a638c6eba218fd186a9abe7144e21dc862d71cc14fbe19898710ec7321917cb26df10994c2d4980eae0c9c512cfaa4068fed52d0c57c9dc3fe75b51f333210839a37ff90e8508bbe215d7fd50cc4c90daa00e10194fb660fb38b9a85be80afd5232edba10ab7c98dd8e5ca4eaa6fb7828b529711682b92e5c980bc877a83b9d7f0b848181110e370bee7ad434463f90c954705032d56c0b86305b83f61642daec190941c8b687f46d70c0c0cb4d627c0af930844189d3df40c1ac2c5e0f19e598bea0d6405cbb41e35fd850f26bec7aae8873c883ba0fe69c268839681796ad11c090e891f6cedb944fb4118092d8b4397dce87fb4755b1ac74217d12597496c41370c4653df24c3df308c2c9605efcaa3e8fb55250eef97ba85bac046d6aa82d249fdd348bae7b1f6e658b8072a2aae3dc22920e419e656810cf8f80398bd4dbabc4df89b4b46328524ca43edc03677156dc05a215d2c504db8b28e876a1797ae0b57de85f634151a8bde6002948ae1f540fe4d5f53186fd979992d4bd95c2fffdaa0d02596e17b439ab392ae0f50e6bec0af1ca9668890801d5dd1c9fa69c06ef9fb04b9b0260e8279bf93f1fb0de27d60b0c0d07329f986aa78e6660a8d0785903e7bd71fa74fa1ad87a952a22ca0c215d035817ef038624b5445a326d84ca96a7486058add15801596906b1d78381b9092f504db0008628d32fbca6471359d445a248c0019ff3b8f6157b7b45c14c76ef673a927ac5e425c0a2cb71cabda5b2f323aa037543ffe1c84c1833ac61c016bbcda50b219bca8b2d210914e2e6aaf5483c943fd4e7c68228b26bf751cb466667a66529cc4c19812a8306a25776f5ceb24a601baca327cbaf470a61955474f3d9ae0aa8fedef83fd62d1d1e843a835c3b993a00f09faeadf015cb28d01b01fe5ca49864b667ba0cf0ceea8439d39cb2a9ce71f7b3a5a9b2774141bfd68baacae775d48ba40cfdeb1d1018f77ec090a44ca5f4672f8a1b6110421edbf5014dac6469e980d3e207f43e25353c1a7d4d010974e145a7cb8a5e05d7d79c7f4eeaacd410dab582adff9b9ef8ffadda3682651dbd8ee69d60464070908bec020c622cc00cee15aed981698ca7d413b83b78fc1a2e6c85bc11787374342b243093d26301476c94e671d7008c7f076dfc7d5ea5d765cc4e82b0fc85ffc8c6637909142f4f3fc943170002ee6e4ffb8a3807970aad74a2aa0faa7a3bdfe4c925757497aee9bd3c1ed174e3aa9ef62d6c8df963d0272545e3a19aca824d7ebb9e22d1818f67e98d5dc3e1b3c45fbbaf8a5e053db3fa77af51d975a22ab70321bfc40af2e3828542f4236adb4755552c9437ce84d39332588d33eae8fe5119eccca99d74962a3c9f09ee67eea15f24e58fdeb5536db348f335c67ddcf19dfe848cb1bf5f2be87f5641032816376f9dd3a983822463898a9ac4fa1c88294747715251561bed2933c08be3c006f05a7b3fce71c179e9fc1bb1de53392ba96d0080179c058c441b9e370714526fe5a01dd1ceb43fdbd75efa6324d799955a4cf9441f50c25e456c6436f95fb183851782552a4e3ba51d2597225b8f2b84dbd01ab4bebb18a49712ea84e86297c4be4e394a3d88646bc8da23d09373a2534fdec05540253bb9435ce0f9c1ee444de9557f880d3bc81ae16dee47085dec4022057b0d3cba15569f542939f58609cdacadf9d5055a9b123be4fdde7985a8a4eee0d37032769527587fa1e09cc7122c8323d1c7ca23a98dd63e970031d8545288d6224c9cc034e60731a9212a89fc6704e67f4ff3638366168abe7c5300fa1dc6b95f56e079cc9b97595750031d8d346244a5619031e924c5eaa8ab82200032737621f4e5684fa19c40de1ac18cc6d416ca42dc59e52b07e1a69f3dca3ce63ba55a4c1a3cc761148f83808c62b840b9252ec3fbb2060b9e7c75f6d94891722056d24771bdc8806d3c5124153b2e07ccbb16b02987c0e0ad70a29b48ddba6a3201ed15f4c055ce2b9e3feb5a7ff082215587c9d891c9c02c2eae1c5efd8625c6535ec57be13465f5d4b5acff1945f0a7a11b0e41546b1e8f3a3ae85fff4ec1c89c2dd44398d04e7689464ce2c631929b4652c9e7645684a0857d6c2cbbe2363496c88a5d05050fc76d488ba7f953959cccb5ce8e062c203df001a542cd5e3ec4fe97eb15619a9149022c9ad71b9de36a3a5d257a0d3ccca684666a703ded560df69a53ba546dbade63093831c306d04bf0ab91ef00f96e22edc0e9745ee374693ea38e7c9a66f766d395be678036af3520d65e817f05c7a9e4b5ee15decfdd2c40e6a75789c112a2cd02da7e7aaa27b57b50626a8f989630b47550d39efbdf17d08da5e066039e2be80c51a0221853d0d840fea4e5d0a818de468b455d1a99b98b69ba89c118ff0bdcb250fbfaef760e3eb3098b92512e0cbf2cd32052281727ce17a1d55824fe953029190dab087eb179c5981c1e785a2400bcacfb5fd63025983d3841b2123e21b529bcae8f9162068db42e1280053dae8de583b3b713f2f78738df67188b7a01f40f0918fa1e51ddf85757ce251e903bb996d87ae4c845a420e6333e0a3c292521190d1318d4840446166d1241f4a837b47179ea554e144991857037225cbcb44e782a9c08d58f166da0e7dddd3b05ff2f7cb5ca53e445a3e6c06a1e3e03c7912619a8418606b4b5a6d8df274ce2aad313d5ac4305024352e9219046a0ec8ff68f6d7ce075490b9bd41fc9940f8a73fadf6577a4742f08d0524f72500d944e950ec1a7b45d2bdd46df4bedae251dbfd53c97a4d3c746de80214c46675522ca77fcbb13500597ad629d5625f9c2ea4c38d1334225b00ec20b9127dc4a15d65f129f4f95668768b51b02341df16c8430c163878ea13ed24b683b993732cc35b987c78e2bd25f1fc6ee4e152bd489baebe997a247076d380c092acecb79c91213a29ab370776cd2c3b3871204cd156e2ce3ff091fd31b65cfb824f14f400f6d9bea7894b8e2744e60b68cc0c05084c024e1cf64867a132f7429c9411fd0c9e225f4828bd9f523bdf3f5a8c21b09dbb8f4770bde3a488928ebdf0ff3d2c300bf6f4eccc94c5b1e4551ef25d144cdf78fbab3fbf37f01160ecaf91b153e6a9322575c082c4eefcfcb41f606eacbc87e9ad7022a0916ca782ba8a2bf4f408d135a1605c85ba1e211a78747ffbd1ba7c36589bb7cd801166efcf55a8e2e9e24bc6cbcfbf1ede191a777fb4a6a67a39bb92498b8d06b287882c859d668a7757716572334aa19f4624fbd7dfb3cd42a34f6f12da60c850a21a2e9998bf4256b961200e57621dff71ff89860ab8f1388b889e6836fd22f120d9f42e08f4999454c0523038e056404d989a3dbee88cb59a57efd7c5bd3827bc85a0bc8c02953ed0d7dcaa108c115a48ebe00c19ac6a0b7daac1b1468d6d8252e93f061bc381523fbea0f248d4de1945c704fd01510d54aceebfe791ff1a44852613dbeddecf2e7fdff1e981cfad3c615f65ec413546c787497f2dadcb2022a6a2aa463bdef07ce1f303b06c8413357261f8d662a71d344737504ba69b8be1ea8252e6838ca8936dcd7041dfd6a646d1cc0b12436f3c89a7feaf8f57fcd34f860017798b1cfb22395d2f2836dd02988ac37178f82e5c543a98b8982e59a0729168c866526ce53173da0b9300e6c17ce85a74032f052a82142b290911a0edb93bade04baa6e061678107143823fbfdbf3c06c0dd0de671ab6567cc3ee3a657e7ecf6d10acfe4ecb89f9698307623643468e0ecb86d9d9b18db9ebd3272d674b43d42784367f2454b3759ca6bfdb30a9001d8b0ec48ed1378d40a33524c3a81de9f331833c8a3e9cee970f651dbfce88b26ed8f6a4240fa2907529e11a44d4b35483310d25e3c006f2d7d51432acc216d43a4de48db275262f925ac1669a7e6e9317fac48f5a8c60620767062e78bc4d93c3fbd5dd47a332512d2fda7447f76a7badc77f862f290ee4b89804280f591a6badc290d3e128c46b681e69d71c3ed2a3b6cc4c530e0479309d6287b692e08529de31d830126afb4a4652a8aeb3303d3547d00b93b661edc1b226d6d79a7761fd1e43264a090a0d8e9cca3454ff74b8257714e299ed3c6f5b6648b9c26bc78ff09121b9a485282e4c5c2b240129ac0819c8532b0b9cca4472c180836f7285d09386581375779cb546d4c8ac6043b3255ab1035c1d6e07647f453130bbe2aaf04e5b7bf49d0cd6474c25a75c67901a23870ae77c32e61af3ffea207f8749100649c3cc054c36691d4127bfc7ad18e1f018ed75f0609001dd83e41d6e2ac66964c248eccb2802c378c9252146c8674ac23b81fc13b0089add0152a1141a6472370df94ae57e54ab1c3a26c55f24a1dd0ccd360e794f0a63987a08d50ffb84a76d3689ac1211ea5d8917dedd6abf81476dd0ac1c7017aee6abc9ee1a41e6f5edd0b89b9d99983857dc32914abc1e20c29c2c2882c21923c947c980e43393500716ba97ec56ce33fd4d01240988636baaf5aca649f87fe606606fecb888b6a7c62dc99688774d648982ce1cbaaa3282467050963b31d95c29f55d427d4166b142f767a695ee5be116bfc9455f405c12a1c3e7dbb14564f94c73dc0a7af0aba2b89c172b2fe5f9c059c0a51fa792481ba1fb6f97faed51fa1e1857eafa818290875f34d876e2a17e5adcc7bebc7b01b941b9934c2e4942e9064d836f976444ebff2dd6c52f44fd9132f4a4a0ab659f052f5b80e6696d217568dea33b7a90302d011384961f7809b0238ed7e26a28492676e4368ee5548a3f5be6b7c3f4342ade03db48e82bb247dc95b52638fc38802881d0c2691f2801d0be3a66700ca06e22070d3987ff4d44db8f952832f0a74e43f77b7874bb68fa67435fffb3a5d98b034ae7766999e8c9563366a70d86110deff01dc7cfae0391be8ebad2fe2d816a06daa82ad16587e43c91dcad74b672541420d12c50f82f737de005ad7235436f404e87ae5b3ac732a92a34bfa3c20f267fd4c7ab07947232044ed716b52a0eee6bbd8388d7e4d15c904a4ac17bc8a7c57032a9e8e8a5c30ceba4a078e335cf0db5a938e3233d9e16a503d741755da53cb2b848c86c25199e1245000f93043ed0b8ca80200a4d1cd64118869789d5e90f2b393c38faf9492f91051c29563e737b8d7c5c704dd199696ecbdf796524a299394015b080f081b083dddfbbb0e92e54cac171d082fc24500c43d3192e533573280171d8acfb0e33fc3cc2ba48f17e79700bc38bdd079718ab17b78f8e26c5abd38b570f0e2740200fef30889f313080d369831481685caef5a062fcea0a9e47b518a91645422baa2362f4ad98e17e5171e456408b2077fcd8b52a900dbc50e7436cd8b9f6366bba86dff1cc9aa4168760028e60680a22e0104c59bad251901c5cc0050d40b004700c51b8d480450cc0a00455dc4a5116dfa2180e2cdce50208062fe0014b50730a84d3f01a078b3f3880050cc4440516f10146f68363a0028660380a22e402b1b6dfa4240f106cf7c4031f780a226c06bb6e9077961261628661e50d41d30316dfa406694857f80621e00286a01f88cfe8ecf70cb0728e60080a2d631c2ad4dbf87d175ba30baa20f0050cc1c80a25e81a2ca69d30fc7bc012866ad0128ea1c5054c1367d1c50bcb92ebaa26f3300c51b15088a2afd81a2a6ff44f90614b3e6018a7a0798b46d40f16657998cb26c0d28dea87480a24ae70045adaa59e300e9cd162d28de6c51dbe055ba1615499d99a40227a67136b3e19bd31a5112b09766d4d42475269426457369ad198cc2742a8f5dc49c9868100d923ad9680924d86ba63edf5c37cb3564a1ecca2d2525a9c314862ce6e4464652c777b7e1177e022761240c0b1386c32d5c05136121ec72fa7c6f4e6540c96257264c8a3109690ae5ebba3f5ca32b044605a9336363665032398620757cdfcf375791ac601a93449c2d933a54db5c446c2fbccc10238707fbf9ae9f6f4d4657f445215949636df91d8d4d8fa40eb67c20b2c73f67b1ba7b752315f46e7bce0eda5fb6a9267b2d49007b326dad71b66912d4a63ff67c516362e747142005d8b283098a40f6fc1f76fec654c294404ca94bd3d6db9f7ef69c2a5a7a983d147776a84a2582ccf6f7917252ea5cabe420875486568656665b9e200fb1b0b267ce13a6e7d3393fb9bb3d6d0e91aef9405461026da74892ec49bd6c31f7b03939e80955ab5beab9d30f7e53eae1299cc4113ba16def4bf92738cb47bf50d65aa5fa705321fa725aedc559dbb8ce4bc9e899ba6bd2e66cd0dcc081812a878e1a9b1d3c6efec3ff9bcba0cbc02b92b327160f2d0e4e475f3467ad73cc3407e3ae38a741e7f3714c74e57405e6a17d24586ca645cec5b99c27c8593e6f61b287522ec63ddd7baf067f2f0777f5f7de7bc3bf37c8fdbff7de7bf3f7b22ef8f7de7bbfbf97e7dafcbdf7de9abfb783cbe3efbdf7eef87b815cd5df7befc5e0effd7175fcbdf7de1c7fef002ecddf7befb5f1f70ae0e2f87befbd37fede9d5be3efbdf76e7faf8ffbf3cabd7706e0de7befd5b95368e6affe7befbd3deebdf75eeeeffd1000f7de8b736feaefbdf73e8de7c1cd206783202c9e0e80fc188000767c0440a7070034c0d10087831b2b1dc20d34c8c1c9c0c6df70cfdd703540efdbb8edb79b8d9bb1e96ab66db3d96ab64df3e0766cdb66b3d56c9b8c6ac360db3ea523874c25c340e6d1641be3138d8d0e870e372e8d8d17a7c376d452b9361db455c964797399ccc7a7f18e3e6ee08ca6b5f6cbe59a1dd49f6f0ee394cdeedd14f7b6e7eddf896526f9d898eca1201c398bd26a85d62b58b687f3de572ad2a702ad5eac55b6f7b548f684ef7d6d39ab6697e76d6086f4a9473c2f5234b677f35e7dbd9e5e605e514ecec3a30ae953953a78915ad1247b3e2467d5714c1ac791e98af37c11d2a73e0179911a01933d35ef3d1d725695c968d191f754c87974bcf7148af4b1403f5ea452b6b703ca59d6356b79ef609cc7e6bd772fa48f3d7ad1c1d81e064bceb2af57d3cbcbeb69741e1c4e481fab248017fd0925d993e3e52c3b8eb011c953bdf7ae83f4b14f3b2f3a0fdbb3f19e9579900f156d2fbff7538cf4b940d3879d63c2c89e1b31675d974be68272cdb6477385f4b94701f0debbafd7547a2dbd9ab647dffb7984f4b94ad387d5797126b1bd1aeffd2472d61dc76934bebc991e2fce29dbbb4fb23000785186b1bdcf80f4c140f3bddf80f36cefbd0ab207bb64ae984882b3b0cbfb22d2071f4d1ff6a5922702d9de6b04e7d1defb10644f7def43e749bd062f6eb0bd773c7ae08b747b9f237db0128eec71161ee9cabee7fd943ef869fab0ef79af81e78197b4ab8bb2b8eaca711d4d1fce0acf1165715cd5ae97d2f4e15e1d286dbbc964a36c94d5a7e9c30101a94fdb7e9551d6368e553656d9586540d3677bfa01e4a2acedf572bd5c2fd7d1f4d9940670f4a2accdf57abd94a6cf762400a56d35996c948db2a7e9b301edd8a76ddfca284b1b472b1bad6cb432a0e9a33df900725196f67ab95eae97eb68fa684a01387a5196e67abd5e4ad3473bd2511a292bcb64a36c948ddbfe7d9a3e1a508ffbb4ed5f1965e571bcb2f1cac62b039a3ef90900402ecacaaf97ebe57ab98ea64f56e2e0e84559d9e57abdb2ebb5ed63a5e9938fa6064a948573d65886c76d1fe7e39e9ea64f069a3e641508c8b7889ff653d9531995ad40fb3494d98083715634c801713200410ec659d9f6736e523c38ee75f3dc6bdbc7f9bc97714037321e321907b4ed6750d3e9189f6a461de3f8b4ed833b389beda554b3bd746c2fa56dffc360c3b1b9b6230c36178ecde56daeed68dbff1c9a4ab6016120c321f3641bd0b67f6323e7f1c9c6e88d797cdaf679dcc034da4ba96ba954faa5b4edefb8976aae23cda5b934d7d1b66f53c3ce649906d4b564b22cd380b6fd1a239dc7a73c8ee3530d9ed68b872fa5acc197d2b6cf693a2a9d4cbeb3ebc8b52d78b4eda738a11c3e95641968fa6c2328aa545a63d9ce40db2ae98f7b926de3f4e1be481ff66d4bc5c5b4903a761bb77d95f7816f1930a78aae9b6cbb93f3881369c79c47ecfcf3ef590478fb53698e7329ecaa8baeecbb5c1f3817fbb8274ee6a22c0e8cafec87d996737151dbbab898d4b13ba78a1637d9b6bfc99c07cc06c5c168ce5a4b0ec90d50b995742e666dd6aae7643e2ac3e2dc323f559a0c78019c1054903deeeeeebec3f3c0531f0a1e62145cd91c6c990505aecde974f373ceba757fcf885377b73216dbf5f3a79d6ea79b1e50fb1944e1c62b32c116e6b6e005a48cba165fb88eb65402e8aa3fc5acd0b10279c57568e31ab4f15331d2474a1fdbe3a761644ff7acc9346663c7130a8cea098bb429f519ff6cd2a09851e0e1d2fbe0c88f15ea7419e5219612fcd1cd8fee681af7daa6713bb6d7669ba79b1fcd5aab7e0cf7dfdc600acae7e119a9ef3b3c112a131393b7349d4c934903b9209d134dcc06e3c73b4c745535d734cdb70f3450771ee6fc6108c4cbc60fa429c706cd01e92efe1adffcaa5d8105bd2b2075f05340fac05545592af80abf93e8318fd9e0094e210cbc22149c4473c83d83f7834c1fb9e3e1bcdbf4a9efb258de728c93e1f08a5cb07a78856cb3baedfa2777cd5da76d910034dcf950f594898eae83c81eb977360d695efc1b3830f091db878ebec2ab1b9a46e5ab95d672784634217494d19ebe9812e9c8b4f1774f71784552600b73cb8062eabbd75ee519d1de035b981ef3108b1e63fad1ea400decb4ea7d0b75e74722b3f3dfe764073e7d8e7e3a609a82f0f45e58464270b8f3a1ca5b9476ef19a1413ce4412226991327041584ae38cd30464a2e0b8235d1592daae17970f347e319c9e01c0245ff4964348de88bf7690b48c82b72535c900e7ff8af52693de60ca1ef4feeb989bf7b7102b16b7846f2fbcf18394f77f49af144c8294febf09b4393a8a3e338e6ef5e14f2c022fe4da26f0ee550c18f82d4c1eeae4e88b350a02bac04b524674d58ca1fc0182d4e214b97a40e7efad151882f31fc9f23c7cc0c550167d7b450b73f1299edf9b387ce231ae1c04ef4d745b6f7231c38893238877c0a4d21222e7bceb30905ffa43c6a26a78c02a5ed15f1dec116e607f953ef9e7f4e7d2ccced81297846dd0764fbc40eb60bf18cbc4f9c401c89418c266c638c9fc39db79c07c85d1a7822e4f7bf224773d65a2ce4be14e877812d878aa64b7404e188068f70e0053cc4cfc2dc1b98c28f17f0a3e03cdb63cca47ddcf6e579c5c6184856d24baa9f4c94557de5df650d631ce2e778ecd3af7e8d4f888c64eefc3b94e54fbec20f66e3a8240fb33106daf88336fe1f4fb4e87f57a8fee4f8bd22146c61ee0c6ab0fb68cd15c6390b37701247ac42bbfefd797fbed350d09eef5836fd1c22cd45a9e09df781ddf9a5f529377c737cd022654d222faa26bd9484ecc9811e217dbce88acf5c70e127e8338a44dff31630ffda22f3535e91f917949bc23aafc8b49bca7bc11e1e52b08e30412959142657f591b4a45de98823750059813ee2ffbb3ae82fe93fc41dcc41e8191235729ea35a93f66d39cc75643ed2f695fbfaee84ece9be7e37df03c5e9b2bd790b98bf7945e63b9483f1996c86690c6afa741a8bf5a0459fedd8b3524f531f7df21585499a7a5f39d59aaae87f28f34b1ff19887f55998dbbd7828ba93c3809c4774a75dab4bf6c8fa4960c6a830bbfe0f18b4e84eeee43c1e73e9d291a8bb23d125aa44b160f19c4daf7858fdb9499f3cac150884e915c8c34a6352a7be10fa087d16a62355094495c89eedb9dfb6bf9fc881e274d9e6b0e8ef3207e361fd9c1e747d975587c252a9087675905e79ba3f63333663dda657a897c7b111298cc2fcc859da8d5ce25abffead4857a44ea558bc68fad023a48ffa957af926d592a64a49489dfaa2e7b0eb733f5c5aa43198f388f4efd72fb2c78f50ff90c8587fa7a1a23ddf7d3ea5e01cba9da8556eae503757bd4e596d8a7a719e1e3416be4ad39f4f9f648fa493562ed5f99e9e8775856a77f6d01271a0ed3b149c3c48ee0fd164687c04c9058f50f07ead427f86eab306ab874c3879438b9656cb3af3429cc77e0c170ce11b2104e7a9b1e007b66efb41926d2b0d61841c9f5c51f76ad4ae4f6badb2d65a6b7561df2c575cd9a21ce5df0fc179ee8fa0a239abaefc6ab0fe21839e2d0fa5644a638271cf33342e4a29e5e74c8f3e2769473f107ce30fa2895cad0e4a991227845215826c0d04d96208fb88fc4e0c617bd769da431b5fe34f903df9ad9e2f93f26eec39ffbe8d1a9fd42627f3a53e51a5f70557989f0a415436dc092dd0b267befdd944f638cf7dfb53c8ce2b2205c50f6c79c54e20e731c1431baa54f2e7bb6690ec9992fbb7765224e9c3bea863b59c55f3d321849e20d878686d9289f4f22d9d394fed0312a5f3d39f4f73e6e867bf15ac7d77495fcd7befbdf7de9ad4791ff8bef7de7b6ff579e9bda9fb61ce5a7b185e9bc569698ba1d427482badb4f2f39772b6b4f33895f1192dd55a6babadb5d61c1974adb5d65aadad44b22db35811dbf6a5d7df62be9fdcd509cb96599cae6cf7a6b8ed59634f1d53f64c4daeecdb6cc73c45db97dbbf8ad98413ee0ff10bde90e9743138b9e5146999c50a2b5b66b1e26873dea9bce8a6a524d1a7a391d9bc907c2041640f1a6364687fe44abcb1e9933da93742d40ccac3a54d43038af5b52bd0ae11db63173127262c5b6b50a4443bca630a4316734aa540d199f6eb8924249895dd75a0e8aeed5406942cf6b461528c4948dbc6e1d89819942c67242b98c6a42b9bb625dad93b5b4be36850dba62f725bbe4f9e3ee8ac51482964382b1572456b8c8c8a85d64d4f0149f6bd56a98e7bd22dbd9a4445b28238e0c5da43035eac4294f0623d8204aff1fed40cc942c1c88bf50506bc589b2ce0c51ac3086ea30ac99a2e115ea42950c08b748b222f521684e0ba08c99a30105ea422f8e0456a020f5ea45912f02285512892359d10f0229d81c88b94ca7e91f630c4535e4896b70ef0a28b618017dd8c02bc485f1022e38464f9cbe745d7d2f3a2574180173d05415ef42725193209c7599208be3a68d0f225b883657b9c33d775dc10cfa2de19c43f846e1e601ad504a7f6947e62b735aa499f6e339d765ad5d1e049aba6b1d91857156caad56854a1a6654da3d99a06567b6f9d9fa837e6b4ac69793e0627c6396bda764268a9bdd652fa73ce3927a53f2db5d75a6bafb5d7da69a7b5d65ad05f033bfb48926ed68871d2d68a55e8c40e23a163bad79b820e4ed061462bd44de3e10d2d7a6aa973bf86ff2a95be5ef40929489d8b8287f77384d0a2def763f7fe910d50d6101530207d24307ddcbf7f5fcf21b13dafd8f367128b9b4ff82183963d74df9f462fd0dfee6f1c0625dd91308d9fa6e0f7526b41fc9c6704532a653b9b42e5e94634d28b1bdb8dcfc627e5782754eeb49c2ad5dc4013385054a924b881db089a90672c386b8cb2868c4e20fd46e0e40f710a66106824db390f96c9f9358953923d9e13b22725f3a91428cefaf2b8fa929d95ef7747cd987bb31b38db665af5219ded189d51564542ca39d248cee37bfe48c791ce2315e6e1b45291ea8bce26fe5918a82807b3e7ff8041d797f36cf4079a1fe22c1af071e813f5329b3c9c4b37b468fa1cfd56a8938629da602df270cea4ce3c729f85b96945aa583aea3cb282a92c903d5eeabdedbd979f98023d707b2a6e4f917eeefb5cf7fdc61487c1b2ed3737fe56f02d526a86ec997b3e3d63b26a8caee6cb6098d5c99e55863d5bf8452057f34db0e767d9136a4f2e629dedf99c5791a4cefc9c299a22c5f6fcf91628569f8a64aeb9fde63c582e6da24e1e4eca654fba14c659f3e9cc79f03f8eca662676f3e992cfa7239d383634b469be3365cf033ac58abf89e59b41334916c6de2966e447328ac8cac67fb7ed3e12996d04ff055b98437cfb8ed0df6610c8c29c51b2a788b3985a8ae9a455fe10a775d3bf20dd860ce930eec08b69e8af02de2dd01e73ccaef4936e7ff6f0d07bbafdfc193567d307c8f4d1e56c247828e5061bbf9633b246eee654fd9c01458d6303c59f3efa3529545f8e5c6dafa596bd3d069f137b7b1c05d8dbdfe8607b379201c59c02b78de6dbecdbf87e6c3ccd097b1349d81b68e3e5a64988d8b06103c4913a1b48f372d31f9a772109119ab79686e6b7ef7aa0b5294d0992880d319c2158c1eefb8979cb9ff4ab88b3e55b210090b187b0fce9729e2b7fb65c30611b412a09d9b549ea95c8a46472a8294b6b194957a9d4cb4c29864bcf820553f5c019811f28394ad1b4ebcb3ae794b5f370669a5b3fda39f6c22581edfa72f7d8f291d81739b03d10891cb37fb0f162fdfca1f31cf13ef450a652a9542a053ee8396947322b985f4ad943aca037f39904afbef74368fdf29e013d1089f6168362b69e0732bf713232325aa60192c8e55effc63da765b4d6325afb8ef33eb1f380842593c88dbff1338f648ed95ab6f748e698a5ed3da62fa33f2d23da2da3c1f7d0f33c24f6c5d4b740f70f1dc0f172d71a8fe3fb99791792903003fe2469251909b9806078a145e4c7850f8c59420561948186983144665e49122233a0129abf910291d8f7b81f66de7e343640570e22336093999f81ed0cce7c3333df05e8ca312801a9e3afe399408b322684fd99b742ea27765063066ca222a264e66b804d32112599aee42b513203bab055d1e1081b6658220a23221f904e2f314a9a9eaa4843082292aee42ba9f14a667e03945503d47425df41155dc997719926ae8a2a3bdd25655df79a4384ae4fc2f431926082ac596bf7604a1d9f2931498d8273ed39b779f9dccd6bda8df69b767301674d28bac23f299eb3e6757c8e573d063c387edea0791b357ece6821431210c20824384f9680b3fee7a7e03c746aee19d1344d48065050380370c240700a7da0e6f1353cbea6be6351b4555b66c1026873b5dbf1dd3cfdb4ffeffbfafdf0f6ef979ffbee771ff7ded77dead3fa6bd4781a9ac7f1dd507d187c8e4ff53abe1cef9f8eaff9fc6d3eb96f7e884f580db6d9f1e1afde0778dffc64a2fff8e70ed0a6061473ed6abe9b9f3b87082d4ea809f5a263d9f8455762e3175d48083fb7e39b9b47cddb7cf58cf0f89aa73cbee639ef7688c0e36b7e88efa8f95a987bc7cfa8300e14f443fcc6774dfd0bc8953f940cc5d9b2e1f1edf8b2873b3ebb6d3e0fb4afc1afe39b613cc49fe3f39687f8559f0379881f83cf5d9f077988ffc6e79fdbf89cc88b3ed79fcb7cfec2ef7d0ef32b8e743fc792e44a1e8efff9927b53f61975721eed77fdaeee5aed27b5cd1e11623577affed683ec5aa767a4babbdb26772ffee4b328143c6d99a5cb95dde5685b6b6f68d1d803c1e737a3b0bfcf3c0cc63b34e7bc71536e5cbf9a9c358c71ee7276d0339d87136a688a21595a9ab131c638632fc8e59e833b7586fb09e53cf9797c267bccf846b0a0c433aa89640322d84c318de5f01d408420a58a27050900e931e29e160eb0852440f2e0dfbc6eeff0fb9187dc73dcbddeb4b7f7b6efb64f4db1b2e4bd8cf6a7d3fbd4adb53a6ded3ded53e3ce4fff62b132b494df6508b33508abe831d163a2c75a942ed5d73b897414a2ae1e3836236da21e1803c18c93e331ee7307e278453658d5caa55efcce73ca4459381ed3590d6d882a1ca03f81220d0a6f809906f428edab0ef41507e617f3c679de7beab74d6fdb96da5e664b6daa6ddb3ec8b66ddbb66ddb96e319d9ba6ddb64b6ef3aaf03c189e319d9c0143cc45ffe21ce851dfdb66f6e0dcc86e99c4c9db75c4dbed7a14a148baf9cbe90ee47a9481ff8c8431c16491dfcda4789fcc96555360633141415a39449f6d0ad514da323dd9af653fad071fac0afbd967a24323bf5be395013b2a54880edfd8e8cf7da93f1bcd17b21cec3bde7b3bd2780f43e88ece9def33ccf7bcff3641ef48c78329ee7698fcd3c967951bfcc8d5764060cad56eb094d4113aebce5091213bef6de7b2f045c509a26fbb95694ac3c011440464b3c26d65a6b9178f295041365021261804c482208ef689a1a3f4b96905ba8c0b0d87105587111490912ca4c7ea680090a98c8518a1f7cedbdf7de8b63df7befd52266b6efbd59b4e82c29a8416ef1824836114608cacb154ecc96f0e0f262975c5faacca62934686826f304370dc99e5cb9f7de19165f49a5a526ed01332f610d1dc0884571c117504d88dc1df67dfd65df961833049530c43873d7f5a2a8cac507345cd5015c185da24a45174552b80294305151f282285230a94c38456152b493f1c516638ccf10a22b99f3cf0d32dae414443023308808dab6018c600c125f9c44b0022074706d3238e9321be221bee00756748318980f485ca1a105dc1623c238d9a10902a8254304c929c080c26310e957d1146ce402261915d43003bef6de7baf962186f6bdf706cd24b0d8333a202bc5ce40492042a889097624c3740415453a218b714eb0b04e8860ad566b0a26da320a588bc849511dfaf071feef903bd18c4411bb4e6890c517638c3186a2096a639cc31014add2345adf5087cea801430e5880d1058a9f31c65011c20816dc07ec300baaa207275c15474f3efc4802821611484cbc10b1c5cf84c2618c1fba128ee0a18624ae1841144ae6932e320419618c711090af649091a021adea7e5250c49210c8926a80a24395a11b4034f9f071fea160cce42e859f31c61896e42b393239d905c062a110205c3c18e1258b2b58680f5d8af0ba94c0a5559a46eba5314362965c308c31660213b431eea1cb82461951b41f262e6ac03180382f78428b2d404c61e50b26921859c0822c323198145d2c90c08c20ca920ce30e328da32516482d58985c18635ce5c3c7f9b7415aba5cb10d6d0da0220811784d00e921a78d68cc9596eb0b580e38074b74022a122a87555e2b4843a9e80730309528f785289632e00a59138ca5d8bb64ee501c7c1e91341fe7c3c7f9af42450bed89b5d63e31f2957c6598465225fd183ee009d34e102fb90aa127202a85dd42d3b8269a2685144d4dbeb811205e55a986a46ee9873a8009d7bdf732f1d68e945454996d8cb355420b0d8c1d722ca850c2894b8a6108638c9d6839c00917187ff2f4848a1374618124c61833f9f0713ea84b0a0aa32219c688fa82a50c5c44654c92a6699af653e42b7964e50a16a5127e9670184230f1e104569c0cc1840c430a1745d65abb84155fc92b198b56522dbd91259ac29d2ad689fe9a74efbd5a64f0b2ef3dc2044a206901c58c9f228c83982c9a946c0892df5a6bc388f94acaa08a84316bb98a4009e12064c87eb44ad3688d6fd0966d015ddc34848238e8d1c245d40ba2a2c002862e36fc28a1699aa66521e42b4964f482212519c9624ceac249ebe2cb931fe20e5cc64c7162cc099e98627471e24a54f5408c28ad20664c5c60a96734a518d074efbdef23048d9a664ad012941d6ed0678c3176f94a0a11d9225b18fd1845c8b0a488a84b1337bc649210d22a4da3f505bf20995165a8082943d8862dc61a59b640810e5f77982e8b2f166ab55a31608c31aeb131c618bbb6dc2283d0c6d8b593c060a225017df83817e3ba25cb0d395ca37ad444ca3d62ce643269c8702706891d8b2fc618638c316e32c2cac6d80a4d0ba4705ca0c829622fbc6028081fc4bc8892b840c92243030e5aa4f03065054a44f105d7d02445152f0a0d5cb23c63a4186717bcc08698983fc6788c2669204dbc61fb846b63998d31187a581b20c62593f029484c1c146f26c5698b202fb27851a362c0d7de7bef55c0102d4dd3d2c3089a3811c2e8c7059806241f2a52102e269911cc7849914c235dc96cbb9c819dcc2636bc144394b404132a4ba4ac00cb11627cc1642597a525268c317e828bf6908d1248100514315aa0d81002a3218c5a60b92c1947284a10dba161009391d5b20570400206298a20818bc98aa19264c91353d3981fbe98155130b6502e387cf838ff4d59c420858849c489549c1882660b4b2d529c5ab70a30568c2a3b425b6a88a2822f4870c49319943c29f1a24a9221588b0a0e60ca408a61280bce0b4d0372460f49d0e0c44a165e10c153367e5db497e8a00417b2041e8ceebd178acc86dc321446b68778b652f45333a8035e18b5ec4d4fb49c8a1f2daec75c98e84ae6265aa569b4963286a6699a56e42b7964e50a16a5a5052cd19414dbf7feb8039a8062b2538788f94a0e21cb4340e921662a225a4f842bf42efb4ef184114245f98bcc842f505a0b0b70d0863e7c9c7f210fdf7bef76b7eb5234dd1054a150b282a12a6ff1c518638ca950a2b531c63570d1fd6895a6d11a1301652230553af8c08f1645428c71c4c513938c222b5eb884a317141c143f0049e1d2c36c630c055f7b535130ba925bbb4b2d8021480a1aed892c964cd932cbe2880722a2789112810a2334ae931ff287f7091a829aa2e81283c18910d5034baf2d34887105891db00c398c51daa116162be2e1c3c7f9bf181812a30727c0b852051522b7a6b12f0e44eec5185b0d4fa182102c696202511469e143154fb4cc10021aa6e4f0650fedc841e34806335e512ef8c20823820033b524b19480a21ec000d3da320b8356d1524516a9a0d1e28b855aad56138c31c63936c618e32d474c1be36cc7259a949d291cb64f348d8a29464f90f8ac598c313ec3e52b299489f403ce3052bd6be609eed2e2a29950303ea93c542e4442331d232ccab862044c284144858b0e2e4e8a1aa880e2046bb55a4fae6482b2e2c485c9101eea92a4699a06f395444a1a999c620f1843e62bb9c52049617297178a6820818c66354dd3b4fb9ab7354dd3b42366be922d971091d16d00122f8ac3fdd162888240f518638c957c2597624dbe9256ab2110415aa569b4c68106dc84039525fa6b0c3a1830851394fd7b31de72451a1bdf8dc128c4c4ec96518a14a37ad444886dcb134948f91d7c826af1c594a9d56a51c118638c319eb22d9bc288da188b2562d8b62d4cd0821ae38b26a204e1189ad208639bc64499819e41b110143f685439da4952e4840f3f28e170839527a6174c70d102a858abd58a6225153324410521021fa298587c31c61863df18638cb36c1ac18f56691a4d0375103e4041b1a40b204d9f08f239676cadcdf9adb5d6da28a85c42d4ace51222ca259061a47d9a267fce5a105cd62a1a560933d8943a672e64bc76cef96d9ca7dbf91f27eb336625a0d17a2c72b6399909d56ab5a4dc2bb534d1b4ef6c37b9768d2c6440fd7815a3195e4489bd60c51437c81dc408e386fb2f7b780e60c6921b6078418b491573563486071b3235b2e4a22b99996427185ba9513c11e48442b9f77eb7afdf7b6b2d4307269e68594195344a40e46229fb76a1b20315306b5dc539bfdbb8fe9481914559ed7b1cbff2c5108d0e3f186f50687124b3c86634bc38bf7d7db981bf2ffe8ca1f8e2858d9f461546c9c66fe3aca0c5a864cc184a7b9aa1a90100000000c3160000380c0a064442599ea6499cf5f614800e6596526c50369d86b224485214c510a3000000000000208800829073b4a3001789589a955a9b67b75d52f2f90cb9b4157bcdd2c16ce3e00a990a31292226c739bbbd9835c20adcc6afa81012339a8037382fdd96c7f08b065c974661e27fbe4b6b2600cee951ed6d1c1c96205aba8556c83643ff8511e10c604d7a368661f77c56de01b4617dc87331da1eb9a09829d58134a28a237f74e4ca32aae8855cf3505a7cdce072f6b98838a5d6a375f2bcd63d2c995f60756cdabdf3d30f62ae806482f2e47fd2d0745d633cb442a43114e0ec4bcc81e0860d4f474b978ff8c71f480d3364e88b3bd2e3bab5e32faf69da18b2cec5c44bcde9d49c5e95099b68a5a701e4e6baa1272bbd14fd1e7692dd4b3bc99b8fba5a9c3c8ef5ec7a01abc045be1013800ec5369816f763a72e882d5cfc32bef807ff526d70d39601b99b926465357582a9a4acccd80c1d7c0992d1a68bfeae692239d12300edae46f6ab7ca0bb8d1ee94cc9925dc131abc92cd9dec37e20944b2766e17a123882e69f99e7e34aec1c67275835854084012c2b051d2f3360ef867c98be957e8668364aea0a8a4054494ac64b56fa0f73e6a633ffda7d13a6344c142b028d257aab535d8cb8d4842da4457587a87159ef481344ed96963c519c67e13addafd15397b633a8a08a34b8f0ee2193806ba69aa9f7974550bfd34c867623b14251abaf7165845690ee8d4aafecc1a9c3d4cd2b5efef7c82a8e9adb37d6935613b7d97c1ec4c8d7f0a486aca200fb12e3a03bdf243d0cdf6b57e274dfbed096f8dcbef9150e58292f0d7dbc28fd5854239db3400c10d241b4e16e47881bf28b7815201a36e38c706f89ee09557e7df2edca497860a1da903fe008c478d59a620c59a6ba01f05cdb7e18c0433643bd3feed737260631c9e07d006b5bf63808adbc7b45b7ef99aa0e5083a693e85183120e430db11e3e40907ea4618c467097b8074336ebf41d69d1874b8927312a54df0b10ad126090f2f62175a70ecad31d6ac2c30535e3e16a8ddc8948978012eaa875417b41318f703dd749607b67573e026a9e06665b282c4fcf8e299fcdc5c45993af3108d9f7046db141719d84240fcfdd6f09f3a1a9efe7603bb0744354e9c20019d4e9f5e7bbd596b9fc885e9e5e36f9fc8a9ab77be6c6a20d05a692df0b1253154b6dd6d245696b2afc72accb89cbd07d6ff1f4797a35be9f69ef5debc61b848a4c37341738aa729631219d35f0b839edb3b0d2e07523ea99bcfbd208cffd0ea2b9bbc9596451aa0dfebd42dc888495b4cf1db9a9ad6a0e38963ac28bc74e88861bd68a7ff8fdefaa4de013b5af3d5f2a02ba7e43255b2022065fa05515dbfce106f54db4f28bb7552e39881a4cd65865cf8fb0c5a1b38338633248e159842ba32e12268214f8a898b87fe3865ee5ce60ae80503f560080caf3abfc94ba346cc56d73435b2a66abcc7a44d1a1cead361b9838097d26b0ae0d8c8740f7ee85655c34356d631faccad21663422484ed4a4f5fde07582a2273f61178228dcaaca68055963f8e756ca37296d4b64cd06f6f05d172a7ed8de872a3081d11de94aa91798c980f1db1d6cada53246d0cfb194a6f62a0241bc0ed903f193288c7ca587c734b5be99d57619649cbe0f4c3333ece7b34b58c9b4e27f03f5b974ab95e9c89218d10b1ec4939bd4884953396d851d368ddfb7f13f340bf227dfd1741bb8fe5af4f3ff34e5bc49884ab6eabc5254af84399184029c3e5f463426b2c7c695e5542f3876a1597d79e0ffdff15a0a423cfd07b6eaa2756eb4bff2e7696b6fcd01092b9f5ecfa9e43cfed87ea1b0cacaf0b63910f719b5b5424e60ef026281698ffadf90c95bd336d7509676ee0250887152e543fdf4d0ce9b2835c2d8cd087e5b6e6f6c4aec81e1da1d356a58ede46d3f906226a0e856db6ae22c73f686e3f4bef09d476619aa291edf112a6db9eee92f22dd96cbfd36e35c315f161f0b46d324b0f879bcf98adfd8eafdeedccf22c164249ad8ac917983e181f9f3915ee441bfb6ad680c9b45468589bec364fc56c0eba51a864602bb0cc7161784c4224ae9089b7914eac4ac63d78e58190f1d462b80638e0bda7f4b5021af785c2d7805526776e6f60c979ce3d52ab4432dd815e35e4a0c4f94a25c16741d6911b6896fa3168042e74e8193560fbee3803a5716ef90d40c94fa423e994f9704d4563e9c484af9c5410bd89c4d94e3b1fb3f4e0493d562533e9b8a33f7032a42b646c56afb7b85d5e98267d4435a07e53b1f84a77321bca95925b94017437c005b39a486aa3415ab3194553288c478e017678604c213751d05da19359bc4f7ace368bbda50b60392e5b505e63b35aa39bd0d7fb811f2ee6a0c232cabdff2f178969e5c7fb3bcc63d0a5266cc2af47b925b1fb997088f34e21bfdd7ad8fbe7c0d518a52d68a54158b8e57d8d853c9d7b41062a71f60a10af8796db6cf4f0a7be6c8f36d4c199cdead3c01f747b2f2b830947dd087a8940e63b820f4ce65f5fc8b2e2d0bee4bdf30ab550cee9425a937136018405d5ff245d7ad8c077f4018cddfffab03b49e78750b570a49f50eafc38f9a12d526bcc88b46059a4c234ad28fdc13291b2e6cc106f6b4f5aaab4a50201a6842fb2bb6ded57dd6ee06b5567a8dcec04917816a71c1be608e44bc836ebe3958ce016c3f9857777952765a778f18874585ff4b64e6cd430e319939db0887f30c11ff5528158b08e1792ea4cca196046a792fddd925973b6012e7c3441355007fdabc21890579fc706e18737901ff960b62c22d05b65ccaba1d3d969ead8a4af0cb4bf12d6815562c66d6f3e13f2c54fb4b6ed0eb74c3e5a099abf3f3833b79d176563c9530bb040f2cc04db2c351e6f998d0e69b4396c79639d498a5b2dd351e306d8dc62280b9547fb6615e3cd399f5be9f2f5b9f7dacaa9699a7068a0361a48466a4c4cb17476da2f5f47ea44a089e4849c94e3d0d3597d7afce5193179f4ebbbb45296fb3e1e059b747c7340866b8e310673e1d5fb4ba63b2911115dbfdc595574b2fe99dd5e26680acdceb8e799a95c836ab0d95ad4b2b908c03427d96985045c49a4cab93d0f609765b4210d4aac2933e935c8d6fdefbff8a8f753c03b457ea60173cf748bafc448e1b4ebc7fd64d839dd7e7d5143dd05c1b32ee9e004b6c6aa99d00dab977667a1bbfb1ea856257b7c49ebf00824f1aaaabdf5c31433ff30f503f9635521c3bb9f506bac5431512b0520d378e0aaf4d90fd8022f72d806b13fb283189e59191a3e6c091c75543cd6cc87466b42842c45c1c1e8d0e71e93536a3fdb4d1b8b998dba6df603ce243cf117eeb6817d587d830e262b6721cd541856c8a0347fd4edc940436c308b33634fb4e99ac15d2d5f6931fd3c1c26026f0389838850355c373e04e41188ef2f9f49cbb6951ebca41ed52f8cccb902750158c937424795facc0867281b3268eea29f3f3addad0ebbc8840512141a9eeead73452003ab6759f4d68225221076ec90ec1668c69650907f7fd8ee3af136ebf9e2b6681b7dc42191c1b33dc6e669eaff2a3ed0897a3808ae34541d35f771594ed5b8ee6fb618a21cd6c3ea4006d6783b9f9a58fe76802bc45df94f7f5b16767e193a3a9af43cd6f0968d75e8a554bc605cb00399ff6365158bfead1b489b280ebcd5ed20869684e1de3128354aea070461f9c7c2c9634ce70001e97bce1f355eaf9948ac0330701751cbe76efb18859098b1b79ed39d974a016a58dde34f0e0c39ddf12b8ecbf8d7b40d7697e1a5424b94d7f062f46e184889e7d4be6b0fcf064bceb37c45909a679d3826a0cb8936ac8b26a0e45bfb0a4d22689f3ba237fdc72e41bfa32b6346ee399f6e9bc1350b6a9ab827f80fd3cb6f2004a13f6e02d567ced5c13344306bbdc43686c24447436d390603ebdac40a0a44c8f375e1f30a5d02be287f97b02fddc2aaf64b879c398ed31eac6f900ba8ab2528816cc480fb4799602d3fdd8caad6684898ecd63211cbee13733b51ce5fa9c21b97bcbf4ab59b7497cc855e666d904deab169136a81c1b34a455218a8a74388eaa106eb05050d23cd599c0b1b91e4e39464527a120f8fe91a612f693794c90321131a6bd42e4f0c08955e7f4d9a2db9112b83faa8317cd39fac2cb94910b282f74cb23dce4143925841abad3ca5f1dfc098e2165c3b5abd46d07339e8b1737cd4f7ab84d3250058e04982db947a6231d2ba7698b342020aac7fae284ddbffd43d9e816a87a3de6dcd0aff6ce0db0546392b5fa8b0e9542b6e5fd3c944ef5eb794f6aaa5a3589b13d059de1d2c71b6ac6cf0c0c5d72449d795f81883f62dd32c97bd8ee704a7d4367a014b589bed95bab6110b91a8cf39d23635d994c7c8780437ffbd4de259f678358f3e0e2738adeb753e3396dafd4ae5a3b16f2f30894027dff081cbe640300bf93240ec925b0bf86a6efac326311762adb8167f316cc358293a39821bfe952bbd81b4b6c2dbb51619cd0d6106261c7323a41ca0dd5667c1d9c7f535caab2da27f06787de384f4ecfba96b6fdf15f4ab0e08eac7fa3f6cc713b323c7361bac2e96280bec301cea58518c880e4db1cb61e9ab1488abf04ca7c0920c8aa3bd7984ce1587e6660f2b0dfa42bb0c5fb48eb78037d22bf0aece3dbd3f15c8e073605e04e427f4de09dea961ea93b2a98966c2607436d0a554619bc09bd9de262669ced06e1a2b0cc061ee782dd2e0f851c74df6467246127020c73703a1ea980fef963cbb9048867fd464892f49cec9a1f5ff783117af870030c73c4c37f5d99daae862c3cfa322734c74e760c8ee0c65b4ea7fa5e74934166754317c63ea0746c6d434b585eebf15dc4697ed53f178dbdc65a672853f4a6ac12e7745b94e53429f3bfae444d989a450304f4a46ebc9c491fe7c812dd6c941342b3196dc54fdcc9385e2939da9fd842a90cdd0fbd9348eece2503f9592ab468a8f864f6d41ef735e0380ac00e84e11d9ffc7ccbb16b35a83b6cf030a042873c97dea4a22394a1ea657ccff9d20b2515e133c75741df6091256d2c206b8510ac14ede483a066513e26213128eb84d4a4ca4d9442134063bdfeb22e64e774ed8f77b9be1d70e9bf37454037eeb53ad91c6bc1df58342348ce0ded737d76df5a3976edf231b2df75e38dd26e397f2634bd9acec8cad7dfdb84fe4ba971430111f13619fcc9344e55fcd7619dcfb4b427a1da24e10cab86da1785560eb19d8fee95ea9766f3e71c0b87c4b20ecc1f8e572973cb560d5d3920b951670218f17694b82b30506aa73890abcc8b7300384bf6b258932d1f9205c1494e44d981b7922db02a1742397cd3d8f66379bca28a990a2f9e387fb25e16daebe96fed5a79e729851c9eec0ddb79f4b673b6a9f4ad2239a17ffb39897185b4c266d39778f7bfd4d4ca694d5c2c3b4fc2dad44465aea4297525e633446c6a57e5f781983ee1021776ae690926950c4d6095ab7620b78ebc894d4f8a1339f109a79956ea64caa05acfddfd52c5b2af1488ecdcb0b9982297219c97846e9c14f7c6b2d561059da05febffc313935ab09b6e3d3e6fa69bfd06cb36ca4add2df7e66cd8d5bcb0a6f1801d39b5eae9b1bb218558193518f763a38c698a702df231f8ead8bc9d6ac55cb495488b18386c6ae39b58433ab9114de31ff5d1f4b7a727aa9979e19c75de1cd68f2e01e3e4f3f3fcc104e5361fb654c0c2c0aae4c202f308f370da17e580750c075e1c2cbcaf86101a7eeb99380aca3a15c0465b9375b20b912bf46c4ce9dc691f34cb057d28f458be7b3b25956b8b0669d234d2497ed1122ec3cb84aca600a4111ddd0d13073647b901ffa5becb4a8e354435a900668c127176ca3d40301cfbef411a268e7218f7cf23e50af1e0e4e3a2b2c323d3a4312265cae7a124d426a09dfc6099eb50a25099703c7da69b261dea18cbb6455e2a6cb76d283ddaab5328afa244588b52ad98df7ae3200e1558b00b1cc95fe3fd5ea8b94ac8b1f98ddc17581c780394bd45db415e705914c22efecce660f107bdb2ddbe618168781ddd0142890026c3ce6f55278c0c5911d3e6f9a371e5332f3c5d8c707217a9529d742266b51f8cf069f3471b78feb66cd8cd56ad1e78a873e44ebacae533e0ebbdd7e118d785a67286133ca6023264953a19ad6a215bb961b7b1350058e43a35e6b22619893a16e45c8e0575ecc3cdc1aff031900e37c9b8fca8b1265fe15bddfdeaca365ace8cf540189f30de1ac62cf4688007a52a0ca3199eb76b6180ec718053d6cf00b116c20b9203de1ad8cec04d0b8dffd63b28aa32f945bf6703b0140272cf50939a40f93d342d2cf143c3c1d38d573d0599cf963fafafbe1672d7587ab6cd9b7ee641f038dd12cb9b93f94cf7541290d1ddb0258159ec7105697facfea65202212d3f4c6549731712c6186ddd32888562aae5a3aabc151fdb64ca73b84b9180b1a753e9da5ae2eec31adc1440d814ac76b75d0510f7251a633234d0801f812dfb00b73bf1608c6471e0a127edb5fd6cc403d0c30cccb13f2a7918a6428428e11f40c90b3bd7121d717a5032e5791c714fa966e258c99c8c529489ff5606aa729cbd7f865836680a8edcd0ff887766e65ebaf81f215009b466ca1295afd3626468d7bddff5af49e8840da54e7901691308b91e6619afbac60f621595c6c7ac3ec7ddb00343c1e4824653b9c6aab38b30c052236b0a39910517957c391bc801fbbd8505b9294fc0c9682c53537112b056537dbf7bf2e6d752d349a679f0a021585863183dc88d5f02faed84cfeeda1fcbbe2899e2cb38098b5d815d408bb03d3b8b5a6052841f0eac59b9914e783ad42ca8577caed35c3eb485be09b41c43d08fe53acc92b763a1fed1f8a453de2556860c3b521adf769252364068ed6b730789fa7a0a66fdc475a61e1a93c0b8046e4b6bd11aa54726faa50ad02f00936eea76063a81c2a22e2350dea6d6d595fa30916d7f1e80ab7fe82c21336cbc289e3c5e08a6c3f2e0d7426ab58e2b6b953040a60bd8376710b639f5408dcbcdd1ecdb3501a3564ff3beee3a5d7ba0a6758bca2dc81231e0233217306e6283071a93875903b49955ecead296480b233d18fd9e90dc1c06dc621c1e1e37c8f6d7bea717f5a48cebeec09a7cf974ad3eebdf259f9731ffcfc732855afe4217b9190d7473185ec11bcb2e56a42cf4d57932b7a5fbe4fe0900e4e1c0afd7ddd3886e4564d801d9bc023ac63181ee9f913a0c6e013c317474340e8fce98d6e10837a197e636f782fed760c1d21bcbc473f8a18e12243a15aa9bc69124080c97095f74544a121e55fcf38c4de25e97d7f9ac047f3927910d770dcf00d0df614e5d3aa0537dc4b695525805eb702025228c0cadd5f7bec55e0aa4b6b64d4ab14c6e41ed9771f07b6fa135764ab2585988bcee7ff00f6d4451ff7f4ea4578244f5a848d496cd4ee1dc9d14d4b6109be34551b7d781d92058c43b8f700ce53611df569fcc1f615479b6b31dac4a62899897dbd3d066095a32aefaea928bbd9ba5e367406dd8359b9ed0c1a8c15ace934c229b0e8f0281671b316184261549211124506650e851c03036b1ea8c858c428de10ae205115a1cee0a593d4e37ef14eef1582caf7a8a96c52291772ba28a51b5df6ed897527aa45ee514a6641fd3afeb29a390247d9ff9a3b79599ec9b38301b50ca505f8e5bc9db5a69397e36a663db6524bfe555e9ee726bffce6352a8877ee90a17ed44da4f4028892ddb5b9ac9073635a79c7a04a62ba8d3a196654e07ee37fecd2d94a4bcc10c81923b420d8bb30aa0cab28f384d2e4433214219d123c72474c1e8cf8203d9ae303a22a5d0c7c8a29cccd9e357e8711dce9d01628d59a5f1019a24c518c02865ae754996d5877c66ee5b9b8128314485706ce39a8a59f62198088ff674c9db5304feadefddc6a53ff5753855e1743124bae4215dc1df5786896e80330cd8205ba3921a1f7800b16049dd546ba937b7b1b06e0d92f4e496c2e5cfe16eb2de57032cdaa0c92840d04e98c5c58a05821d4d29b6370000bc76c7bc9a303d4aa336b6798b0dd11480d6916f8c164502c38a598bec0a4560a6b7ed4dd5fe37343951320dced3e2af9c44665ab358d9dd4466d9abc90e06b32119e3ef818a82536be931c2c1bee502b4ec8d0293c78c5e78707d370b2495f52c60149a5de65c05897e1db3aa52fae888a47bea337539644803957c5ecc7969473270fe7ca7022678b75e6447e7753b296166e8c0c0288aa5dcbae9718ef1003f5b850bc0e09af26ace3b98d6c94e5e6c3b31724f87091d8f9ff2d1892afdcf6f28f44b0a4fb1cb96e8f9671c840c20a00eabf249c1b2e4719f03d3ff9216616e617eee4eeea27dad466666b181fbd87a26a0b9acfb943b803a80ca30125506721cabfe288e8defa16688228a8e977273afbeb419d400ce206614c5052b257976b522809d31aad04771a44ef1235c3eb4022135ba7393fb944ee74f767f899b392605a93a7b2c1d6ed32ab85699afa22194e6c840b680acd09170a72357f7716cd9ea73dfe565a6146de9384834c24d60a176df7ff0e7b1fd2a2d7272a7bbb47936f108cf377e0c157eb8ae1a27278d613ae701dca901e1a567bbf152845744d0c5df4e370013e3dff3c91aa8d91088d25a3adff19d341e0423d33117bacd8ac2d3b7c5ec8576c8e6008227ca1bc9c645bfb190970d69b54b11993041dfefeaaba69ef6f988e71f4512502626b07201d364c84d56c90fc3f619a9b24b00f2d19eef175fb6c7c9db3703019e000c10c5ea0be93c3699fc5b94a283ba89e13f2b5a8afb2a3af5853d5070ffe843a1e9caabd8f2e3509da9316711ce0d398dcc19c3ad271ad5cc3475b5c11107f739ef604550391d957d662fe4a50e34726a73fbe47120a1e247b545e8def59e39976046985750e79abf9b4c5477cd508286e1011c309539850ba6c9520bc109b265b6302a0b170a0bc35f4f59ac0244cd82370abafd888d4ec14f56e502d53ef24c03f30793c010daaada864dc5aa204bd4a30b7b243ff1152533a945bc4836f89cf813e10fef31c07b5b7d0dacd43ccbea61880c8bc35c5f0b4f5e59630a8f1aeb822febd9320285119d72f15ffbaeb11a4324331e2ce0a9347af1e9101209f680e00fabaa0b4645a4e04ceab5e373e54acaff45d57717d929c69cc0c14fa55abbbe0312b563e829e464aaf3d6635ca1cd3b2d2de4a39e79efe3f55b847fd947c36a13447afd162213f1edc32e61caed08ba90a20b5e6da074b74a2621a0eb29e6541db16ab48534b772cad16738898bdfd3e842b890a6369c79b399f730a6507b4087d1c5eaffa76078fa59623b9aa813db321c290032deab6718561743f2eeb1853ed0315344a00ab4a423c8cecd8d146b1535ed25c0a9ac2d15cc31142e7ed4cb4151a6dee7a89c36cdc3dda901a771b5d23654f2fb767d2819d72d20c37795e493fbef4f9add9c6aaaf9103a6c179633a0c10d7b176b265ad039a53e0e4cc37057a2aec74f6061e2b3ec963f18aa9b0c74327c9c80a1afbf5cc133c2e890ade8ec574fe1001752d77b49e83ae1982e88f0516961b742e0d5044a4aa62db5eee43b47da06be9839efbb397d0bdd8a174e4ddd364e5a42d54b15dab354199c7058f06817686f30a907385fbb0ae55d72e44564ae994bf3d9782d1483d2180633772d4bc2d742488b5eaaa7fba0027c3460ad5b487be964988395d633cc33a7f3f94eeefb22db89413618f0502b875abe51a563b8950aefb3f57c4bd270ef316d762b7d7ab329e93a19a5dfd506ec63d200ae5a63d6e02d985611481c64ba2613238deddfe28f1eb0b2956934395b06c5a61c1bdfeb857736a3fefab01756d581a4cd30b136f763f6cdf2569456f4e04439f20fe90c1485031132a9552fcad82a3899445923a5edee0307867e96ec2e7541d3b5897b0de141b01703a312e0c2a543d9e50df7434fde4857117dd54a436845d57a7ead81f89e54ed113903277db3bdc2a233deda93ad255f118748b2ff4c9b8e190aeec224a0b0ec9c8e3aca01beca1f3435584fcb9dc421c808410f17601c3c4622cc40e481dcbedd3a18f464794159791bfd98168fcf5e7c9a2fe2a88cd955f853d0a690e4b53095c1b83fe1f72d918431b218f9f94f900bc624bf65f6e2a5180f95bc259802aabbf352c83a2a2cf586b3d4cd2a5282cbc2c96aa5f402954d7db7224c611a442429c39d33b0b6c4f57f077afb388b46d666d8a9290cddf9df890856b65c71ef967e03d316ba7301edeb98276a745b202e45e1eecc6ca12e68804626431a4de55328eae00dc317887a8d28a234beb74a7cd19650fd06da3ae4d006c0e850f09eec6d2d5549e46f0180a21e6ce0c8d997e327787723caf2e3e346b23a9d309f0f16e370ff47e43580429d0888ba247ea44a034f1a7a9588982170130c0c03edc250f928f669734fb7c1e90bebab943662ee26c2344357632e7aacd0cfa5d47c512edbdfb70cdd173aac1e70427c81f74970889e79110b91d80c3d6440f49bae8b98afa20a27ca8fefa46baa837ff42a94f952f15342798e8ff3f9fc773899dc29a00336144d71bf7854ba10142ee4060303af38e77a3e5d3b0832ee792228e2c707b03edb572382678c336979426834a5d1b55298f91296e9cdec3e6cefecd6c0f43d9308b54ab9c5719fc0d9062eca721844d4f6eb6fea7e959d720a06d48100c4620a3274615178b1b4a4d7b53d713f0139b51a02cb46c1488b587051cbcd70cad1674047382e9234828ff8b460d5e15e194498abe6e51143f6516eb11859e6c8e8e8dbee3c976ec28e408d81c9a5d390a75e5a5e2b06fbfee77d4d380ece5015ff9e0d4b9154d3160f627fe2bd847fd1298ec42eefb6d029e91cd7f2060f08270a56b6c04bcac807ea68e3537b2c4d0f1d4ae41d0370089554c840b586e59cadc9594dfe893119d0acaf1bfc1f65b83392ccdf8f1b847644c04b4d6ad7fd5d4ce17989dac28253b6e2f3232883f2d8ccc30581a68a5c21c1b10bb1c93a8936a55fbea790fb8aa7a1b8a5f31c1e39e040d2c5b457b67c3bf7e4870f4677f4a19c58fdd3a6c82da9929b4ad5484d34d8219db0c9a4cbf5a606232c40a137fb50e0c2c85488b1430c5b39f72674c12276e356a270cdfbec9fa705fa874653a611ed8e245c7ccf69f9d1454faa1c3dd1d6a94397de4c79d2f0902f3a5262a16274edffcc87b631b3948cd4cc2947542cae2104d86ac5f896115c782a00ee4a89177da304ea6ee50ba0aad21c80c5c34f787ca23e1003fba884d2625b3a4bee767a0f10b2747ca10dc81777d797312079718ec227476d6550449152b3daa840d252f6f5b10e82f25bc9c17ed61e1bce605eb46ce8758abe9f0dd3911b66b212f1390d0506d2c11857bab27525b5272a5a4b95a0357ae5f0d2c4a8350b2e8042d740519fbc09de395c010ee6b4e22755e694ad5df71b09b033b765aa3b22d527eecec48cfb47e39097770a09141e36e45d12736344e32ad630361134448d3b126e990337d485f2279f0fca461c0fad128f530fc15f8b923ce006407785fdfe2a06d5f4194e016d6f3c57cd5ac661ec10a9a6a8f7ed9f69272d136376b182fdf60abc24f497116f4d572a1b636f9eb7e026ef184484e35a07eab254173a83fbe147312c6106a646d9ece9d448e440045e1def6f90b74466721029b409240a849e98ae9646342e3ad809641e74ff6c92eab02023255043d1f0e0e67168acb766272c1506bc14e2164b4ca7101ca09d33b6c3361ce6c2592526bd50990ee93235853096f573040e6daa11fe543f02558e453fee17af8c669890778e7b2c887b3ca9af3f419347050bdd07088063703ba12c100239ed2e9d56c1ee7d00a832a0b5713e5c0f2f45fd227757aafdd15938fde3bc5b417880b7e49c89eeb35a46a68d94468f7249077eb55d1b9a39db78d86de774aed3e3e7b574e61363daffb5bbfcc7dbb1f049b3aa9e1949dd566bf9d31c9ebfecf8b1c2fd5a66253f62d554c874cddf04a4e879f30f0428b6b9990b36d68f1314dbf3d1070c1ea71745330e60fbeb19ce7f9583de72b7cbb76d79b00991072505162a4c4eb6d70444ca030b88416de653057f9f1c6e1df800e13ef3d456c6ce9eaf825faaad51d7031f2a06f51d23f475e89a6621ed9c2d6a9c38998ac10d1fe344e90a106e41843d1176883a62d35dd119102fc7428eb9baccfe4a786dc0364c5920501a97e176d7316969f876f6739c4cbfab26e1251cf021cb1dc48bfaebc7c2a1661e27221c05315d1f41220f8c2835dd4bcc4b78567bea10c8ba4090021bc650db5232ac295337e7d40d43896e5d733f5fd8d95117c707a8d5c662681f4f3bb9bfc143d06a2d248108fef9506536d4cd3980881ca12035c8604f2f32161027ce85c4f7507f0873e2e7ae53bef9467828504d4e3675b22909502c79bc87394d48cb7bb209d60dfa89234ebd8eb3772631f80f01c85790db234ef58808313f398ae307c6f045a3f1ebab74bc9d09339e7770b569e744937bc2763cc4d6636d3bc682573ef2b996d96fd699dc7cda1c7c6ee590c6366666636cca774c5156e6813420e5816906a7a41701fa0dca1419b42d543b2695d649ff95f67ba61807f370200635939b47dd3c23f4ce54b1e5c2e155e9496418e240f31c7b2462efbb7579e897432e4a1664e357374a246216ab4426f97827616306dff2c7153692b3a8f433e40dd47ac317836e98ab48158b0daf241273c5b1998eff9c2728ba6575a8de0d4e5506ed77fa35c0cb61e4f899c3888eb247b615ef28ad104820a97474f8d5b2c430c2560f68b9e27b8411fe0fb85496d3c5b508c7828d3c3a536412607372ee4ff630671c31047b250640e72ed825d012aacabc7950998774a54156f78dafe42781fb9a8ba666fb8c4ae8a4f6de0e43508321dbb0e0a243669528246c8cfe34f41c9e1f11295c46ed444e1825b8a076b0ef8376b6d321ae0bae95d83ee51f95336304fe008fd93f82b58b36cbd5f822dff0c78d63cf1eb2d40e9d4aa7f365aa688f6072bb28f2643fa25816012b3d328a1f24e638e4c016f43e31856744d88586d60ab2c6072f897fdc64f9f3bb1850bf400f4a378dc6bc46833dc661b1e586a0a869c826b54d5dfb1d7c2c9f2a21bdc44792ae0dbfd8ac8095f69b919d4f58060a2e88f988ab14871c0cd63f0efd42403d717cdac51f26443a69e1933386744ec296d79562831dddbcfa541b5726ce8eb1b64cf3ad9b35cce735e90ee36865fd75c97e4abe6aec350fd9e043835ab4b026857d392d8b09cb41d5a4cc9a35346f52abd1d989a9bb0d62829e6cc4ae43ad77095de51a73fdb46afae55203c86bc182cf1e1f22981e93851ee4e88e1b9a7ca2ca8d1faf6a9576bf6547b6e63d40f188b1c52c48ac51d7c215ba3fc77c5070790c41a409a2541979493c9320b851a44767df89a11912e661a85acc95f226283ae522b6ae63592fb868b6b74a622d80e80e96d4d9663eb78ed2a40b2d824a4acd9540db9a81c3831b0a06aaef679994d5f2e10c4d71264b7d41e3d9c67035789034f91c479d5410df09ac25deab0238987124a67770e26ce4a54e42352334fb10c9ccf3a7d0b00c34f3a2e75fcf1e00e52678fc3210c6228fb8e510a0318075a6c0082fa8b8ce72ed2a54fd8904734f199c77ec18d217a34d1a225c2e8c682a60599f0aa0af2649974306992da04e251559a4e7382f1fd3d8a4ac6370e46c20abaf6099d2d84016e9f2e7ad6b5cff332fbf7abeed81798cfefdf44f2cc700e6f62ccd5a2006922ff8461dd0d892630f3709aae03f3b36daa95e53b4b196ceb21ad33f81ef200c2e57b83664e44279bc9f4fdfeb5e6fc92f408298ebfc144551ef4304019ba7b583111f9c30ec66082de8bc081b90ac28d273857f285e61aa311ee13b676c4961a28d3f102b41be004d188ebbd6ff37feafa90bec272ceb2e1ec33a0099770c744416d9e5341c1ccfc1a588b6a8ed54fa65e78466ec6995df8db07a0d1e44a801adf1a85fb14b19d30befa0cd0bda14357ce81536c29445604f3775831e43504717de0ae52972832c8820d74611b1a2bf0ea6483dcad463f2d0adb56d6a45b6c4281e32c715e9709edbfa7089629dc2dcf70b4949632ef32dd77f1245a60c9c48b161fad4c855ede1c7ac0604324da651c362a776bde28fabcdbd30c5108855420f776d40b10c9d5dae25c1ba3889f8cdcd54dac626df8064bdf56e765519d82faf2bd9124f804a58b2fb0fec80bda3bc0ec798848dae2621a1339a1bc2802a628f5259cf00ac225f6ab086b6786278532562970502f380d49b3954da170581d96b3bd1f79d34afe3886eb49d9ff2287f1b5f94691089d46fa9587799e8718ee1789515ea918f42f8b11bbed6a10451ebda1d12be87cc40b3582971c9596df7827b1a32fd45f3d479967ddc34d3370f8e12b0c43f5e6d49a87b2596c20a6f47a4612bee31ac43da2f96ec073ac1da3bc56b731fd774d3f822008ed3969df670b48d7223108c3451d193ff1dfc9de0cb000642a8f188b5550118ea17c95b21f68507e705d6d125a078929fbccef65f63d16e3516e1cbc4e80aa894dd00c2afd388199ab9de1d1e7352d889c8b6d3a637453d0ed844ee3a01ba52fa16a147783217a2029cbe4680d14fa0b5ed0d8058073c18ae9851a603d9fd02b8912ccee92526e75f0c42d89309de25c7a3dcf6c3801de6550e908c58537a9e41864310661c61e94672375efcee09389dcdc5f66717c86c744ee8d50940f54c85f2fc9a8ee2824d22b362c507ca4e9b7dbaeb0c80864c58a0d3358310b769153eb580d47913127db0287d75c1e84dda7f1e8adb3c457e010231e23be631a2b806724ca20ed9581920fd857b32ecaeb0a1006774e1816b983bbb29cd0351c6d1373aa10e8923ae9fc94ed108a52fabc4b0c5f1544c59c2e0d4447e50e5db7d8d07024d204d597e0e7ff9a4fc7a9e5b64c171939c21005819fd344ed80529addbcd97083213b8783a0d35328a51954c085bd1fb7416e21e4ec2218a2423634bdcd969cf5a593beb36ce4cb7b0354c6c42d0e4a870332fb0a4b6da29f5fc4bb5ff2a6c355ba6825addfeb60bc0c287e97e6c951cb19be1371a419e5bac0b9bfc3f28690afc62aefd0e9f18b04dfad528291efee35fb8b4c66fb9bf954b9118f2ce1e1276385855ae0a2267e9f8e665b2c6cee23a5d7b4b9a7d82409c1b1cd2fb1af8f0ca03442e0761c8f4e3ea25e86a46174ee1ebbe84613534f50e4e1ad2b01645d9122082cf84d589198c717c228fff447c95be7b5e558a85e52a2e94c55eea20ead7fb64e565d32b50b7c110bf4b8505bc2f1e07d76b363472db05391359fb902cb97dc265c6cd52727704e6134297bcd346f58d4218dda55f9da213796d347867a569c5105a40ec6f7ccf046aee230a361082874e115264bc25a270cfde7a198052a3c24a6e8e4f40cdfa66e9cc0abc9410fe492b4cf60af66437d5897896d8e5592e86cfc651ba058f8c87d90d4cbf6270f1e46aeeb1c4a3e77847b169b27c339a87b91d66507156f3e87a6331ad5e4e22bc89e3b6416739dff4813680a8d0fd9bf9ff9b52191dd985754f271d4cd9f21becb512408d8531304d380c80da54ab02291326d27c6defc6aa9ed1cbdc51c05cce30238fe112e2f3cc326877d6aeb472b3bea01b9909edf4fc2c16a54d9bde746590652bc643185dc07f3f15347bb2acda8dfe1cae8b9c914d4a3c8e767840c7ffbf0381df9c55ede8ab9298aa18428d5feb46410f388b846ab63da3c946309471f5b0a982686a5988502d6e8f1dc74c08e7ff1bd320bb46e1780776109c2208eb1417dffb95a487597740497726180bbda7dbb831979ea8a51b44d70800022a8b4223fc942327e7c5754739388a7f76d4c397805dd72887c8738756ad7ee74939376a8d49d7cacf858bf0a08a5aa64ca0caf348114b309066c67a6868255465d31554efb2bc42748dd9e69484573a032d9a0c880a90e3e12ad2dcef8ad970d8b04e3eb8bd48e347a658a892c73dca1c9748488ad029173fb7d58b753ba7dc33005f4e8d3e5f3e9e442da81be733bebc9733b95d55ece240f71e891c79a64bb23a8647f0778a845858ff85ea61aa4a926cd5bbbb9d924e76017843c0e35186f0aeb8ae12098c4caf06b2a5331d83006b47d39f6bb84f5add049e7b46c744ead670ffd58a2ccf22a618671783c387bb810662f1957d4e079e2de68f1c421a6ee779019008a2e39814b144a6578d579e121819799201ae4679499b01213c686037a6c28c35d5a1b686f1a623c89191a19358ec71bef62f6a04c4fb52b9e1399aeca00d993a5677e3d460200cc2372ef0dfb911543e44165453f1f23f6b787dd77772c9734a80443aee6a300da711e271c9d70960df3ba5f5817321da3d434d6c1313365f709340946ebd6b458a061b6eb73eb91459b00ffdc300bcbca89a16068d22d3e910624543c395fa9b28416518f4acb060ecf8cda51427c5f15b8a63f1c0262cee1e8a7ed95d7264b5f43170ada9ef062fa6a3f3bbe428ac9b42bab5bb8c6fdff66310611422fb262f6c5e1c9bea9a2b3a45c6b1dd8ebaab36ccca1a99d75f82fe1d1bce7c1c283ccc3db84842ccaa34245d895d7f67fb379cfd6613226ee557a368cd37974f8eed68e8be29321ec737d5b524c1e73a25f64f1589679b2ad1318d6dd2d825d27e5abf1dfb89b8587bc288d5015315f5a54e28041e73749d522a92db017d75e1475979b91da947d821b21723f3db9295671fae647e0309611ebea3620bb31d7f0aebd4655df43ac39515daa521dc14f926a59a5177e338b6f2a38a4459581af11b73f58b9cb2e7b05797343ed813bab7d56b4576d41f3bde6e27f6b9ebcbad079f650b66dda0518f4b2c86db180d81e8b5eaac61da76509846def8b79d8b64b12a4e735c16af521cec3ad27b15cf44e82c46012ead88bf4485c5b60fa94110f14b54f12c21c3ad3e7114782a589e79025562d2ef3a5d33cdaf40ced8c094d900824663e18ae84263c07840536c220a17d5c43a6e85fd2cc1462bf1bc690f48c273bb01b4505a8da50464523e594c34734edd07e31e8f93163955c7ca056a1f223d174272c59725bf2a6ca33084abbb765355a19f9839c3d6d87f7ff2acc55a79552b52211d1acc5017429875516336307e3fa8fbbe2cee7a817f2c1aae411eeef09ac0a4ef27b4ffa4cf3a58ddf9037fe984af5d78ada657a779e3eb3ee20aa910e1ab38681b03e1ec76e7613af7ef9ba47350265158fb494687f2cc55bb6a2d67e0b53a334bf585243e13af5c2452c5e0d2eb6aea38cd91c4275ac567f8ac729d47c61335cdb6bbc4317e9679436e5bba709654e49d96763063ffc58282a4fe1883e4a7046913bd508344e2e4a2bf6f6b62f6ab44c099d8948988d3c214179389f9bf2d43a42c5ac20ebba0d56865a292f1ff3c93d178df2ad6b311021af1c2d871aeaf2ea40b1768c0ff3cab3da038b08a45e1019ff77699096470c044a48d727bcb7182193401574558fe58b61b56c7ba3585a3f4400c4720206ab3b0570e881637a0ecb05885458c5cfd7929e13ca5a14951b18982e92945e1579a6ae36884547c38e0ce353195ec03e374e07f65dbde85b2e8f174a0603a579e2132496c52466d1125b30e68afe2d96b546135cfedb8adf98e8e882f2d5245939ca446df3985394ef080dc324ab9eb061d30873734f572e922d3abb2faa370f4fa27d59b1362fe09a9494d1c1f015120e0219a610c8a582b160178c142eda3d49e2f3e44eda428ffdb546b7af3b13aae2e80822c3752c041a3932aa0bf2e94492939a6cf95961328a6450beae94aa1bf0b5effb754c98cc1cf492edca4859c5f4b26fea5cabc9692c8dab6308091691426542c378737db809e28da8cc34a699b75427a76e2f2e6493c1958a35fb75665b9efa57297b107468c987c12031b34fd6b47084b59f553e9833bd42c957faa1125fd1981e644fb2e738d30dad10c473949dcbfe1d3855c3f0a32a2268b990c7443ce0c3817ffe1d0df8c74428ef154f0f904af5799954bcf60d85137fccf076e31124610f80cfc1131cf42225945f469c878573509c0702920cbd0e1ee56ea69a7d490580e0aee677ef78eb05020282e97c7b2548856e825c67f0c35fb6185c23da3777acc48636401519f28c93c93b875e8c09ca28d092315189624c1deebe190746df0fe88082551b308c6303a3417ed682c9f1716b94adc72b80787f0697a4062e6f891b0701a7a1ea5e5bd66c9baeac354de33ac65e0d995e98d91fc98d79c350e29659dd93c7c1ffaa83c0dd5e70312da0137d228abc1f074fdc606caea84071a0d1cb0869d705137135746e68c05260471ee501b31bdb490e6cb7d6a170a87274cf30003d1262642c2372fde55b048486d2dcacaecf8cb280fcb530a3074bfba8b8e823ba41600428cf91605641879aa3d1622b5be95b98751166a27011f6debb0803dda2305dc5c3120986da95839ed014d2fbbbc92466991ab207ffea54f17231cb2fac40d9682c2e1df2f48417e11feb2679f20465b474202cba751a60afe8651da7f84d118e50b4d38f275aecdf2ef7fd89022e3968074dbd00d14e23ac3c533a82c1daeec2ad0a729c0ab65c5c0375c0234fa9d0cfe4b4a89c8e3e20cc522b18c73c56d62638b19cbc7f3a0b5d03b108fd1b912e0c5458b14f3cb6bc49cf6158a035a77cd38af323618f5c08311d77298ea4324f08266a6c561c0a0188136c4f47b16ea3603648432f45764eec0b090b0ba6fe03bdb057e1474e6cb369fa78a2ee484a13aa2bcb88452d7977b4fdca0d28829ed07f09d3177fba494240d11bf0433cbaded6ace02fd2d0e7303f0634a00ed92a6377e48b14a0483d560bb94ab55a4adaf5a34e2338483cb14440e9f8c9b172d52d59f31aee3f7b73f1f714048fe511e496c8550e06a700ade3b983a0b426d5cf4f6e29746893c8717946c88f5249005a271637ead003df1b2cd41f41b08dae54c00d2d4d8f42a3b100aed136485b260844d9927126265f7a1f05d301499df38d4a003d27a22e4ab0f382b559f66a87624ded8aa84e7e623730f26da39f20075f6231852f8f1004b1cc032395bfc362aa9c067209ff305ef02d2c6644579a9664781a903c2a45621d8ed70839ab84f3e9a952293fa5e82915dabf83f07de1ce2652e0b5506cfb20f1d0648ea33180f477c69e451a7b58856312409bea0f6e2274e65dae8420d8bdc060adceb745251edc9fa068ae23006276eb161b7098a5432fc9d81e81b0ca498b24820e5b53704462a4a655c0146540ca16009db6b9c5af9b52d23b05752d548596c60834878fe92e070fbb5daac7ada19a6564d4fcde160b013f20ab5786d29ccb5d05ec4d1a10368c2ca362b935d5b2aeb19ca10120f34403efbf78b6f425070ecfe16ddbb993dcb5519adc999da7da9a7e3dde8c2cb9dea2fe843377c538dac473e6c06f812ec5314376ce44fd69b872ce849ae27273c392a5965a03674da7580a229b73263d4b7f48bbff652cbb78b6e69cf9e0ccc71363af5a23a084c6effd28e59ed35dd98ecca00e2c3f866a7a692665d3f89899d95e8ec7657a7955f4498dc06e62a4687655cd99c119b468e804b0307ac311e44aa3b007e680098e1f1f24d4c8dd9fafb3676a45212d7986a5e2cd3310c09ec7daaccac3590c8a3677ce9c10e20801d47294dc0d74da03b6fc0bf6bc22e6cf576d1ce6d25c9cff9c2369dcbc99732c72dfbeb7fe361fbfe4b2c4a11f8fc51ff74f4e4cb51c428c0133020a005bc4e94807c27f921a0c8e82b8154fb2df29c56aa6ced8abfa6439b8d02d55d3fb5b4f381eba5f3f536573c7060849301f96ab71dd6cb24f59ee5103ecb2c426803595d3ede6979f9161a9d7c2aee61463857b7f79da891926eda4b73b87d794c9c6be9f449ff19141cf65b29d4e93b39c77cd4358dbc4cf6b4d517cc52b61382f91ab660195aa7a87a4a28926b3237ac437c7b9502c91e17bcde2a847e4d19b0a63f603da66dbd57ac7c11c74ae130db409f89e336cc4d86bfcb63bb3f062b255c54cd7d6356656dbb3c77997c32b36b126343eebcf0ebe2762ddcbfa9ed03166a55144d630970207dbacdc3d072172b9feac02046e8ee44ad24d3caccd7fe2491cf998c16061c78819e45952a040e1d1a17e56e8661cdd292a7b6ca1c27a475d1e0850512388f29063d46ac41f2347ad28ca1f4129104685b2703c07dbe18553a8225655971bf1fa2185b414fd12ab568ea40af9831639ac47532a31c62e9f2c967eeb4290c44de9ceb868fac83a15e4b8e5125163c4bcf6fb7ef57554d65dac7da1c15a03a4a981c476086c201919aadb3931f35609cafd067ca20fbf98879713a0c87fcb299d710060c274cd3da7f0a4f16611cfd6873c429e504474b25c65cee202c2779de260b7c42d202609125f4a9b6ffff35b97f2c3c21fc08ad1523dbdf64bbf65a937ae8729371c039b59a97c0ece0c438c2efecfcfaa941f96e0802a464ab1f4fa931d03ce0c07040e0c03f33da93132116f6ecc5dfb9fdf9a94272ce8802a9414324f47e6c14617fea724e5cad009c310fa03073f5e46c3d218a9c7cd5f5c94adf2d12f26023c70f70131e2c2be3e2a3b8f73a62123ff1f9871f9e7be742747d5f8825d5c29868c6a5745ba28a55d7aba30fe2caab02f4ab7638107e4cd629aca00a87abe0b5b81a86ee00b7f5d577a38b2581b1ef34ab46c47e3c62a4fef2cc02977549bb691b55619ea0d80c6ec46814a159aae992d400ad34ca3f5d719ce9b97f61a0186be9ac02700494bf4e0acb11ab2cb4d833b76060475e49600c3a30df85d221107f8c3ab0e485e02f41907a1be6cc627e90974060c54c7928dc3b5b07c040823864549c52d100acd95263c0f78405389802256be0f66d36d0a78a608d909b4236a5f59ff1382fbd2b7fd5c5bb6d1586dd9399ec46b415f6330b2bd9b83c2ee55dc045759223f394a1974dcca9bf72ce36ab44f7f0671f894c47b836c4819b6fc47916d9d20de9a727d8342682d80d22e2ec2ef56afef612ccd31eaff59cb6c451c5c2985c50e836b65d1e22e05513bcf83be4f9d9cb44fa54859d38007174068f305513f2e00d43cab5abfb108c16e39c41f6cd19d2ac91970867433db8e17be369ee72671a052ae556f8466aae55ae132d5b939b7c2848a6c884fc50cba5ff01a7e942355f825d8340bf000b202c2be17de0cf0bebac8b3f6dedd69c0640a7a8118a823210440739526302cb9c47706f50dce50934dcf4e5cff93c5e8b9043c6d9d9c18ee15e8431b4f12753f2557882fa29fb4e80d72d8db86877e4e17bb135f62fbb3fcf9539b0307ba32d1fdb14d561a464dde926e626261c9c63608303157b68143136213dbb413fb2d347e396f2f048c2be045b059c60dbb255171dce29e764bf8e961d12c4efd4d83365eeee69ca83635d7bdf185ca86af01fe972df4ee0ebf7fe2c84a98cd2d52994131ffbd34b39d8e5beb60312312743946e10dbfbd44c0aec1412fe265fd0a65743c52366c4989bdde2dba576777ce939bd132a6128cba211076a45c070b5f918942b7d5e0b9cfec2695eb6f8ef4cb36f98ffc4faef6a661a64787d9a9f2299d0f4003d375a29d527259a539292337adf7e389415ba9f73135e8416c589ce0477dd9bc5bfa92d3808fb5bcef1284ee3082c53efd4b3bfd2feeb99817637b8857f14a80dab7840ed30ab4584c087887806940d1e3d8e86201e9e97112cc9c7c110ccfda590bd825742733f437b9888f068a70fc23efbdb950930ab81f9992c341c47b73638827e0ee4cece8e3a1ad1a63414e0e854143102ea69dfd75ca1741496ef2bc9408541e1ad6f6e58b93217a3ca8541dbf4585e282b4b277e7154b9b805c43b33bee0468a650914f324feffca65063107d770b5f3e87fc931550b2918af665cc91d180b2f51910b969b2ac50562c16996c9fa0a160c22a19ad734759747afe6cce98b9ce9807b07601fd8fb05c51f4c55f46f2fc31bff122e712e51f4d5e25cf58b52d07e6a9e51cf1c1c28803d4a330bbc883a08d3840fbedbad55a1f0e4a89c25f42cdc8cf3a4a862eda5c0cdea2ba741e00c31f218c94231c64d475ff24038b85a09dbab0692342cb30685a5618a3eb5fa322e613b896aa3b90c9a57ac0ed2adcf4ac64a71132223222e5904a8cb391f1e1f297da3287cc276a054a4fa0cb2e24a1166f2be2ead1d03a55b84e49dd283c387f91c0528166dacc5231263173c48c6ddadc01f0e8aaa79dd00a2ac9c05ba432c2057ca72b9d51fda11038fbaf546d558ef17465bd32fa4dc1484e63006ae48b113c60c6a5f0a440af934349506a7b57e6c002493214713ef762b9b0c4dbe15f63115aaef3af893abb55591063996f41fce58167b74a303b0d5df7780d12d558ddd0ea7696b8bcb509b78e1bd8fa9302a5ba3943fa28d9ed46234e8c621174829e22c835e3fd34515e5e55d2b65a4075235543d58028c4c74a64f28491cb38ee45617fee85984c42776829f537c0be85b234974f6a3e8e7d613c0015b817eabf2ba2fdb93262bcd16f443c0ede0078550b362e849f31c4d7a3fe6420b6182177634c1dd57f3aa998a01651bfe33b0b81fc7a18c48515870c1a1ae88b4158b41b5244f89dd9b5159237a8123be905a36e8b40e5a1705ce35fb74df9f8919e1132b2fcef2458f05171082a0327818affad2fa20d0bce082b0305728a008c03a5990a1ba9b6bcea0792889c1d62c2890f4042330bc71a81c19be930b9faae1356287928f2351867c811ec40c82fcfe44ab8e5f94901edab3889cdeea6c9aeecfd5506af9e849d2f79cc765952fd6cbe79d9588ae3bb33b1ad6df653e0e9086d6a3dcb84fb6f2dc9a8637996684518f43e2672995b6424c0936a95b3e3dde2669353de7a2d140923018a452efc8e2f4c1fb4b6bfbea0150c701788c07600cc1442f73586dda14054a97bc2fa5b881d9014d8aa4971fa487da281e617020c0b68d4e2c48bb381965b63c5e0a695d5c58d0671cc496e430c787104e6fba2fe6f29d34f3b1fde236579f52ff44ce57f7149d8bdb7ed3f7870428ada5055c921dbb3b466adb907ec6a1b6a990aab0d7ac04e6e6ce6df49eabdaf7ca43b7b1913ca94e8dd1b9401205aa90c4b36822b391329320c9539478c1e071d77e8d011071c3becd8a10e1c39e668393038d7adef08be63c71d39748443c71d3be8808372e0d6a1fc3bccf22a388987f307072ede4a5f8e4df8e2cf5ee527baad4a855f9b59b98f7a82d4cf1756ae2955f16756096d056c75e1f012c1cb93e7d6e271044171d4e2a96d09c04545d23084a3152c267480532930ccaeac78766da09b0aa9159733ed4c092e7bc4e6c2054a1d9f2aca981eee39afa134596875af74774bb8d4ce720b594e5a73d6ac07eeca4a08adcd9a61915b49bb949519663cf0165b85b26ad61963ccd6d451d69d31cb8cbbb1325a2bb366bae456d22e656586190fbcc556a1ac9a75c618b33575947567cc32e36eac8cd6caac992eb95525a83c1a7048b33a1bcc50f975c53ea2710484f4fac26b5b69d5943d2a3f105ecc467fb9e913c3669e39254a7be09664257a951e954720ca3c00de277ae9b976411645e567df59ff5acd045eda356aacd8969bedb64b6c9a756dc5b4ee968bb56cd1ac552bd6fa719451d959f12542f0dae8d7a73b4ae59b87b22701e66a075f90242c2ec653ae791a9d224235fa72ca738273ee7261339b12c2afd6d63092bc9806aee44e00356d86b3772f6569b2e05219310f0872115a38928b10d4dae0e17ef053494dc4c5886c94901ec8213a50ba90a0b39afb1aca722edb1c801b9daa513fce47ab708f90c2aed1380f51b362a19c408cef9e9874b45376036ec1a2f11288c07ebde8948b57a4d2de8c5c019e18db290e1cd28e50123eb0fc2364b962b8745071da645ccc002b69a070c48e080d49601f1cefeff5fac933fcef1073cbb7b33b9a77ed0a83d1eaa8b6316622d4bde0cb1f03153adb0b1f4cf6cfc55d3e9c019112e55b38ac7e79ad21a95f930bcb651c0bbb24528c474aeb49eb00b2ec22fa981d676968a07b14c55cb54728d31e6d130142b838df9ffe0773354ea5816e40c96f5bddb6d0eacab6daa465ec3e911d378ef8d33a746ba2439badbf72dc0dfd020e980ef23496df2a0345b899921f73fe8cd7604be6751d89ece9a2f97bc57929c985633c7c3203f1b5fa81a056f8b952d0cfa80fb73c93a50e626335b61c70c40fc813ac5c1b1b8e68552e05e2e974b36c32add18447b2d2e177b1d14f05f3ed31042e8eb8dd186027b4ac3e2e4edf0d7bb2b2b84f45a5b51d9d1b0f0511bdecb33a88fca40ccf9bd601d2772319555e43858ae9794786a41c85f1ace26db60e4a73da45eca6d70ec50188855603818ee005100c3cef92308b8db5b70f11eb1e0f0ca0dc758992a783c8119affe8b87871040649c5902be2e2e4c860ff47b3f0e1e63bd38d9c40720139bcde1ded593f2bff48333edd8895abc437d7825be87b1976c0d15a813d4f22055293035db3e895038c1c31e143361404714eb59e27aa7813d3d2085f61e18c174c2260689f317ab3d912e5f0dda6e9bbd8985d3401a4059e4e5ba0f496c36008773ef3e4d3e2d298947882372b6b5cba7ee2a2709ee1b1a3672fb4a5a1bb124d4240fc64afbaaca3f0365acc1f8b03ffb723ddf4431bf3e7430b97092c2f7dc31876c101f114c3abc90f4d2f6252ce98149ecc5ed8acd140defa71dd636ca1455be13cb5e9cababc4def79878e231e33ff1d7fa33cdf18befab12f3191839a49dc568e4f3883ebf7b90d9b0b1d63a8b95c5b5b20afb6fa3b0cec9e160773cad9030412343bfb00b5ef33206a03e5d03b661e7ed88613431b2040eed0aefc126997e7336ee124fcdc86cd858e31d45caeadd590575bfd5d0b764fcb8839e56c0c02099a9dcdc16e83f6c40b35061946a6290149643b4578388b4ecfcba63701696bd947ea762ee6861bde1a765ad8f5272463887f756cb8b3e67dd97a5715df29a0e0c5d339a223f3d9d26af0b3b6bdfc5cf5be095eba5a84dd27928140ca026cfdb1065040138038fcc890b0cfb159dff8d76385cef2e880b7091d06da2810da3b9d68e30f4be18b8deb236b5d16918c458422d83c68bd878e9410a2fe3a4a76ca4709e7c62febdd8608748864c5e6a8c87f49e25193061881e7701527d99d4083633f1749b4a6d86baa84ce8bb79a48577b58b8f866b283c566230a3840b26a7150ace4181b8320e06c07bfc1b181cc8439aa33ab0cb9a890238d60a96bab9e0a1d157a152a09f17e4ea331a96ed1e031046450025809b16a26ec3c780a4eb9485aee49549e7a9d2bc505cd06de08b3dd0ee7d9a474bbead9791b69b68b64d0a23dde3b17d9a16bacf56ba96d857b561310c53ac43dae331958f2b2476ba0e0428ce6f3417c19b8258a512175b58fe9c75f79909bbd4ecbf0033479461f63dd19fe943dc395a0d6e5ca99c2b98f5ce3ce4f9f3a60da3ac0696bc84f04634c159501c5515f4eb2b7ec8b0e3091a739a0b8259673959de6cced29d11c561575e34588dba84c7a76665f90decdbc1f9d8d841705ff4a688e0d231ad4ecd6a1e8466744b1b4240a1c2f1fc4cb86e19a0ee2943d448a0a297ea7b27a9e96da16edb608246a4d5d3c1fc8a7a5349aa46163423eb0e15fa8554454d60846a3cf767a340fcd2f0b35b456c44282894150461685fea1cef0ea0f316fdadb6373b7e882e835b7025a236544b84e753d6a8c1abf1a60940dbf970be16f8cf7b5a67139d8adec08bf3dc1d4afdcce654b21a3ddc8fc4a9741cf9c29322507b176991554ef53a71ba7c6068f1acad74da87a6a47856174631ed7e5a20bb122055ba38eaf43fd69856bbe98d2fba8cb8cf45e649a08c3f767101cc014c46aceb07561c4c04a67a4017995aba4c3790325af2b52cddb4fd990aed8464981ccbe9c4483ffc9f121c579047ae82c9282ab67f83394ca0b2b013c5d010af4dc355770ce61750acc7240ae50e5c6eed0e64552951712ddec19e28f9907ee30612477136af386f61118d7dfb570de7c4c681f1a17cbcbe442e5640ae7627934cce10ecae6a60ee50db66f3adb8ae077af16e826774a726fff1d310353034603bbd91abffc38f9013491ad3faddd27ebb42e0a815ab25104e6c8c69b1841e3436badaf84d45b4a84c494f20daea8dca3b05a62389b43522d3ea7e8c72edbeceeee454bf596369d4f280845d13751c939e77c216eb5eb2cdcbae49c732e06d55b3a241a8fc8f7504cfeeb160114632a525e623a322358a5206f51e99218d7dbbfd8b1ddb54dab893df7fe5ddbb45a4b654282836539f671ec9680792cbb1a42d13ccf6ac2076868beef8e919022393c2e4f366b46f1e60d14026e7c26b97d77f7a757bda5c121d108e2e9f84074adebb60994b4ee09aead75dbaf623173aa4ac22c2feb68df5f4d0a526fa9101152fe208959b2b4c64d388e3c5e3d40215b8df242415b3846476f06a7d06050573713ac8cdad0ca2006f3ffbf4b918434501865e6bddb3e0b93c403bb9510dd21c32b47a7a9882964f4a5e4a4fa78d7366def7ffd9b2ddf61fcd4bebfceffff1fa0cd9cdb2521c396bd01f5c13313beaa6214a198f8ca5131817ccfeeee6e347c71eebcb603b1a123693161a59c734671aab7f409fd0f28d1dc1bfc0f29c3e4c7bc20cbf250396041e15ddbb4bd8bf07b767777a42c249c7b4fe7eebe33a1213a24472867e3c50b628e0b0a510dbe6727e672391c77777777f7202e45a2f1c4b907714076e7f9406f79efd96b3877770722aab7d4a839001db5f6271c9c3bace52ac6d58f7a25963688745f6cd88d301d399ad1e5ee633f50398ee4cca880445afa1191437d91c15ddbb4bd995eb6e9459a59bbd62517311185a42a2c9f2a1f8f9755c60739b2e433f4734b5c9beb2ae97a9a58aca666347d718506a31f2490e5a91f8447166fd9658097845112381fedd004dd4d582da26b83dd12d7e6ba0e5cd15a6b2049f5962a974de7f30408d42b70cf2f7067edc0427304deb54ddbfb573282918f530b41bc1c434b30a6b2dcfcfeffffcc1d1edc7fd631710861884168bdce05efdaa6edbdc232a237e3e07b76b5c5dddddddd3d884b959ac4e02f75bab2345592daad2e40b28ebc5ddbb49deeffbc5c89b95c4e664d9734904b66d48b5fce90231f4515d8e8124b2fbb62964400843a939503ab36b304cd8a22b49195861d58402c79027ee1220691e97892e1f3b58b8699392504b24d5b62ef481ccdf07bee6469ebdaa22accc2ad8fdd1a6e11b78c5bc72de456d24ddc8133eeee24c434ce4ee3c01a27d6383425232e30d6955b3ac4c6c7552583a9b5d33484daf9704319c673e7fdb825aecd759943ddcb4fadb5f6edd45b0ac734451e10bea09c73ce334cf5964a5165759dcc889dd0ec5ae5114f1844e697ecc17a1159660c3073d0d3c00a9c94183c3830ef4e366ab1b5c23291c31eafdfb3bbbb3ff5732765815dd05299624083d05411244c4d5490cce869986293dfffffff6f2cc5b890915b41d972c29b8246997fe55aa429168b3cc5a0a25091a88854646a560928eb55aa9135dbd1b0f1003272411f71244e73ec4312791ec1c4258466bc51878ff7eceeee4c285e9c7bf03dbbbbbbd18c13e7ce33519fef7a4bdb15b7c4b5b9ee9531c32c1c81abec98e80549015271e532637be0947307823005757757712084ef7d72090af311c3062a2689e297fdff3fb9ded20c9c7573ce43e0213bdffb493e7c21592e930b38764ca86b6b63e085e6d18326b2851d764b5c9beb12d9620f98bec1f596b659a78265310192e20aa9eae7ad1a3e65bc8899c96514a2ac788ebef0aa6924516e4c315b286dc9744f392ec664f411926819ffabbef5c4c9b80e69261ed30a0cd06ddf3f5725d55baa5c660e554dd7b9e00c9390fd40babbf3dc6cb5536f291ceb132b1aad81447cc35313fefff7b9998318d0dcfef37c81102dc6863a44468071d9d440aeb04a61ec040f32c566e728c4696cd8d5530ca291330386870ab6e20945e5e55f07f724393c1e8ec35ff332a5ab088bd34d915a109d56bf3361c38eac6b6bc788ebbfecdf2d716d25399bb43d77f7a41a1a9c3b2cab81758ddc32153b7ef944249d6822a1313d323306486fa6f4815f1519083da22dd39d791585553c4392a8de5263e6903cd60280ea322cd3911bea04800a0bf2518304588f1d3771115186020d4ec0e44a70d960690153ae40116080686c5ac134d9fc40be2c19309bd1110ad205f463e1844b8b1b0818f05a19723c423da990fd789249326aad354b53bda5ce27d4034b34e701f8aeed0666d2bdf5e9a6640a366af6fbffefad37c6a06cd8cd1ab6407ae4e55cf9cdb8d0ca11c118cd84181ab934c1a69c463f6c58cb2b513d97ee3f7548f4dd5f49885c386106754b9cedf45a1ea9c771a8b523f366b311ddc731eb04a3f9822175aa01cf02459c197ebb756678d7366d6fd7efd95b9d5b92ea2d55b61d5a96bb16c248840550aa0b860dd205fe2970fe42585860435081e637b10e25730f28c011fc6cd7a312523e7777f7b8c697c117ad91c5c1f596b62f3976b76b9bb61d8ed16affff4f51aab774d9747e90f2f4921b37d3188b6ac75a66c2ac20502df4cceccc3499c68c18728c6e896b735d1d31b3fcffbf2a12a45e51ad183b6ac4dc58a135c3bc514fa6920afc98a4bcb4442075df33d5e399a6ac66b4d65af7bcde13f49ea1f710bdc7e83d47ef417a4f32c984738f73ebeefe434abda554595d1d7e88ed3a54c15fc10a6cddf663fdcb56eb0bf4c05ddbb4bd31ac00b0ccf994002061c7468526a08f538c98b8c5c56d75feff0fa2516f699127731014e4debc0ea9e248584700f4065207e44a41e8e05ddbb4bde1155c7777e5abded2e0507fa0246a1d858348486444c72b16d1b2820aa8a753cc8a698d94a8962bbbb6697bc7231d4c0c77aa663a1440129dd24e13834ec693526f29555617089e18887717ab42761d08817db4708a95814b24b0495334e9da7819feb9b2a45c87a477fb1e4e617f20dd3cd98e3094268aac64ec70295dd860b1462c60a5bb3b8f8010402eaa601848979ca1e342e28576f4d3f56c555fee9085d46dfea924274bc6ffff1ed3d38e16ce3de21bbbdefbff07aecc2fa432e92b6c967da0146559d98b09d22763cf2f6719e6d3028d4c0c7725aa9bb66b9b5693edfd2b091203014530203a3e2b462ec213575f31173cd08d207aabb0686c5878666830069deeeeb323142b091316954284104aa68965450015cdf32034eeee4094989669c8e9be6bdf1febc50a6ea74eb3ac36dca25f1a172e71906a5e1362553f532b869e47096a6473584efcff2fd930e2fe338fe5a9649885d906a7d74bc3221ca524c1403183a973f4a29725fbffb96877241ca872ea0f1ff9c99261bd8481129f2d0f832e25a70b9c17e371a5015de6b480493acbd199cbe55ed4232e352221750c03ba25aecd75571ebc382ebe50311383d34c8704616c0af0a0e9d803babb2fed1071eebed4a38cdbf7d7f9ff7fb9ded256657bff40adb536a2516f6991274808841191bb3bef3dbbbb7b8ee77a4b5bf6cbab60a092fb3c1c5945474bae1b37b61e05ebdc1ded162c2a63d3966dd7a089caa45c6f01adbb7b902cf596767d0822b683e30c7e84e4eb5d5b12a77b5cdfa3891584172873f88efc766dd3f6ce3f150709a2a88c651485081d9c98c705ef02f72fc40264bde00b3c7ce01da11a3122d6236b66246548887ae755ed150b295750e7b925aecd7575bcff7f9f32c33107e36628e377241bf13462e7844c0ac2c5b8c2ed8861924437a42899c2ffff41f70e46a83becb2cb215459c3eeb8c6cc96505fce395329d55bba6c3a9fd00da8a239126954acec11ab2073a28627a6451126299e8f4ccc8acc8e64d218f62ca2a43453d48f180e50345b9ed25b2533072be5762e38c32939e79c6984d45b4a84c494a2cada40d38562b66b9bb6b78f75e69fce9945a2e085988ddde9ce5b89ab556ec5d7dbb54ddb7b85317410b582cc9325684515c49a31a26cd95d31c3da646d7a01bbb65d5e6979b1c97b4871746df091c8c26e896b737d85b58e987a4b77704c53f413238fbb5e79afd65ab740546fa9f1884c9eb4a074770756d07ece39db20a9b794294595d505828dd8575044f8ffaf9273ce5f39f596bec121d178f4f08564c36e9e96d4647c75199e81f550656042dac84e988830861eeba64caf67be20d9a35733f6a3455cabd81234bbb6697b8f2d6742d7bfde0ebe829bc54ce8738189e853b3719622f524d51d0a115b2a502d18797e85d602ebad0fa452503174f0f8111183215b412648cc1fb690d1e77f001b2e7d252f7cceaba98feeeebe96242aa6ded21ddc012cceee6e8d3578f0d414ba2619a880089d1c3b7ec2c246a4d0a79b212c95f79edddd1deb897307aaaf6b6389818ac26f23e9aea991109adc340d64793430674eef6c802e6bccffbf12972a091571ff4a9c056a9bb36fd7366d6f1a361e324052d21328b97b81ba7e5804edc080250cd103244aa80495a36a840a6631035385d09d64ace4ae6351d82e8851c70fa8488015b7c4b5b9ee53f0c52d716d6ed4f94e09470c39edfc8f58002f5b6eba444cc6bbb6697b67e07b76777727a025cebdc689c84aa70ed912aa3ac4a54ba1e7cf4ce5bde02a0b6c05a99899a13fc2c8604442a849e83e29022c5feecacbab97e37d6c6624651b881cddf3d1901808804a1e42b2b0cc6617785cbf614909b7bef7ecad9635b5866bd820789221634a450c142c1b303f6a52b462ffff19dec761e2d803b61d8ca2be357d6a0d7b5390f2a8342d9e46c5009d00331a041c41c3401cca12b9a6031480070d8890bc7888d02c1286c6c180480c0086024100180c0602018140202c8a817096ca62b805e646dbf3bd2e64f64fe4f004eb23ff0e699e3247d24b954b5088a40ecaa685d8258c2a3eb4ecfe165df5168aaf83b3641af522ff11f7da40c0a2cca02c46d2efcdcc6a1175a1daa9d70e2a750166303407f0b645b3fc83a82b1e4899184fb508d9357cefb368f6496662150a6f7e785018b411225e0e15f5890e2d8d344d1149d7f438da024305fadde9c440f6f31737f3568755bb2fe44eb98682b9ed86ba66f98ab4db8a862750ed262b4417862278d552ef5a1ee7874ac9ff53c3143d4951a8845e4981874b338fdb6644167f70da9070c9695afbc2886b3fb9fd8c545bb6e2da3c96ac914c53d3127a343d6fafc05c2dd69c4ce918a410957a367bd3c9241d7c1218cba1ccb4637b47115544336634360960ff0568b26be93a44a1a2d9f59b24d4d66695674084fdbd875a244b6e018a06c2095e2dd4ab8dc170c6fe37ba6bdbdbc1e8b152458909ab6d0d8a7ecbca1261aef5c8f04b1c3068a2b35b45a74db7b089e3dd78d2b48c6259262ccc5d1dad46da316394643eedd95a6410cd1e4fcd54fca74e1f6da5b8bb3250970d8d5cf4a4a195d4643556a2188b7bfd5559311193eef6871797c02bc2de8bc8834292b0731e924411129b9ca537a6ceae2e270dec2c56a22b96e463f562dd90156539a401cfda7ebb33e130c2b32ef4653f2413e0940f5b53b2c237b31677e5fd1a5ba367da9a8f52f3ba211cd0a86acd14aa4534574928ada454c5c91726a7a4cca5c9e68a194a25bdd2e1ff64f5ce58c21b4eaffe332f7dd0630929ead5abf96f88b98f4712bb4d25861070fa396f6c3f228aa769d1bef21e9e5f3ddee813d442ae61f6e8103da1331e1fa9e78d1736369bc1bb1251fb2176cbdf28b32e55ade7e471266c81a4dd06282c44d1aff58c1627dfb68302d90a51f44d311893b313a8307cd6fb1475788acc3457c7b4fd399f0880683d5c650b11cd9d249aff68132935cc4ed966f320996adad48ae4546194780947df9a4e180080b6741118468028346c2867ff5c8cb30a14e029c09e75d282ea573c268928293a1f81872649162790292cfc5167a458b89fc6f0fd6686b2a058e8390076939b60b212624324df61daca31cfee89ab31cb4570f2e01b2296c28f8a721385c653b853610477395dfa84c8bd4a47210579b988cb39a3a2c755a18cc1bd73f346275a053857a265e82b441021e8be4a6224d4d03359ca241f3d7c14887a47646ca2c411139ff02fd8d3318ad02125e321b6237dee5811d423c8a9a8510786e2f6c731a10a01da3d9a5796898a7ca49222908098242196da2adf276c85713d91c840af479be33dd44a5eb0e00c6ea0f66ace750ee1db88d1ed6f050d177a8d4d6c6088a44ce2b2a490949a94b4b2a527de01514f2f3a3301a46b8f2609de770961ca6bc649fd8aa25cdac6883f60893904bfeddb91fe918bdfef655f3c8e517069c457ef5e66a650f89c7757ddf4bd252eb8bdd1c0613bdade3b21caf239f0030c93d7aa99a04305b4a17589908f2b2fac28b7981b3ae9e762258d3f881e1aec599b58884cf5cc63bdb190a6736247b98d5cb9fcd2610614e180a3298dde507fbebd8deb27dc403ec12cd41cf3fd70695f9192f71dfbf654235c6e90353490315fd6c832bde9802d0853ba985356e9db8af0566a27858c95138b2db3035d7c7a3a75dba3b647d4c995ce53d2c5b4d91dc8ada94cac2660ae2d76e22ba90317ed99b94c64fd6e32287beb800bfca12f1d2a2218ad7d1db5ca5d3b15a28269a39f7cad6b19a4b66f7e239af7f89607a8a796e0afd971daf7767d82898e207218b3bc828092d7b227d770933e0ce683636435391513bab68dad1389cd709cfd3c40e78d02454f3c51050c97d335cb8a9278364abbbcaba81e076ad8261218b934eccc8c43dff240fab4aabaa8d5c6efdba7c461f7be65bb39cdccadab8549a5b52571d0cb81fd05257f52bc54670ca95a3f890155712e5ed711a3a30a6ddb7ff721dc7d60c33917ccde9916c22a0b4b04d0af2cf4d33c4e6e10b9e3dce44cc35e553ab56df458700e79afb0220d95858da2acac25d9fec14943b4a5a1fff0bfb26a4dd03fd1d0b18b36fe593955a56e6c0b044fb29e7ae1b62415fe0edb3738f03ddadf16285f195300a2f2ee76deb793c71105df2539ba939f5340d31ede2c94a4d025862142884f01f8b598b61adddd5408ab29283531086f384921340b5dd1eb61b37b68394340723ded2dae6516f96f14f7a417ab1907ef8f3d8d002807e59119c18490349de164614cf2a4d018c482df8a54fadff007d1eb27bd59e53fb0f0af55089995afded158869ab33264e0d9ec7bf7a2abfb5b2d0686615895ea0b03f339048da25177b97eb715f87d1b76e542097067eb609b821735cd01863e577b6046e6f6ab46eeb5e81bd192f6d9586dbe510289c53f0e68887b0360b42f22b057fc969c9d4526900a413ef5d1d03f08bfb46d36078b080c9db4de95ca92f557d53820708327b4cadc8b2892f67e0abd2f158e8c14f38877a6bc6b9c4f3276af85a56859bc1a55a67fd2bec14f8b438d341d5082006a530ba240c0df3f9c5422ddddca3933d61c22138dde9184ceb36a9cbf6fb9b5fef5e3ad28aea8c716ac000560b91113db931cc3057892624067762b3a6a9b0941a5a9c4c7ef8dc2f56ac24df912b4161ccd44b5ed0e896a88f89219c826ce8f87bde318ea29d098ca286bce340d0a3ea2b03a4dc786543814d0d7e91bbd61729a3651746d1fa26c0c61c4f34605d342b5c3ac9f66ac3c78bad3d11a88b7ddb2cd19c17b8dce701d600e729e31978ed283e17bd13975471a3cc41380f61b06e6398572c969e0a97c8f0e0e1ffc1e75de3e8ad321dc86edf9aa0ae222941c5ad99472e7b497fce170223b142e1d9d710f84883ec8f038cf50403ac8bfa5be213df902c8c309038fd2b8b108378a2f35bfbd9111e646f33569271d6b03fb15ccd2e646434a6bd7d12267fb3cff5d27d2e98334a71d985bcbabddebd1423a96cd10e7887b26343f81de4cabc0c072afa62e8c7cb05c6aeed6bd1769bf89179a93f1a6cc33976e5b49095f86a79dc57f7cd358d583071d03968b122cb8d09a05fc47adec2af778011f7c542ec21fc2ac4299c316b9b112b064e15331e6cac305901a167eefdd1defcaf6816b685ba73f8b3cbecee1f82bdd2b89654323073de92f0279361ddc40b851462f2bef68bf7c6c788e0b05565c089e13c681f12679ab7ef9ba9ff896d673ef47529ac26095eec0fe4123407ab6faa661484aebaa842b7201eb6dfb528c06d760a637fbae62f1d3bc14d90c54b814df5806b6921652d24ab8e6143eada637b0cd72fa85511925f5d25b5019bc983960a35c72aef5d1235a162eea205db3afa039ad0d2e604bf6e5f0aa5b5d8b602d54635bd41d408a472636a64855314e8282d9b4bbf573350423b6d42693728633f2df5fdea4fc962764d5f80192fe48a89bfd148273cb239add4821e82f9643abd542ec7632e379c8cea7bebd05e38fb9e072b7da4c52bf436734d31fba1ec750e77db919b597124bdfa5214b2c8ecdf37ee72fc8b79da76f87306ec947a989a549238b1e6af86b9577b39aa4d7f14d3acf0d74b922082611b0eba19c2f31d3580b2505c2c15283ef06b9132264df40f716c2c541561bac5bf141f24d724920545cceede2df3f42b39d5aa37946d4b5ed98f05e1fb0e30bee5a504e93f4498f0e111d342b496ad2f4e1f4ff849c982949182dbad8a5fa4c9801369e0f9fc5680685ac05030ad4c4241faf9eb0a0e17b0b25f92004ccf3f3f5ac6bc5d61ab9e36c80a5b3397a18d538253934ee2c38bc13353534c1106bfafb9dd6d6cb96c74f050444df427c6c0ae8fd0b2952172f5f981c4cd6d23cbd4789c704e0d6f09bc12e080065c7516174ac9d4a35b5035767f959234c16fa345d631204cd16fc04323b557bc95c8ccc5953e3abab2b23c449f7f2660da91a9d322a49ce4fb65751590f6623eef1398865d64680c9156e7910a949b7f4048c7010441292de7129ce5036b030541da3d701c3fce1c711ef2e44b0e8046bca4094dea630ed38264db66adaafdbbaa81a47cc44616ce544e1419ac089e82c1be936dd76769eece46f515713c1f02649320b810a4062230def881095b3f4937fe3b0e8c6dbe3ca7818253db8ed10c6d2eedaeeb1e1ab8f3be1ff8f77209a1a6e5c21860d572e8f0300173fc6c2a2347e456ba045efc66b1abf12b2d76760aba0ec363e19c1a04e44733a3ea7bc8ad605d8e9725d0d49fc0c25d494e0344c50b616f398b342edb8a1894229c75ecd67eb20a2d7687cae4c330d13435ee91c21c74aa81054cfd8170ac3306e3fb097327717c6a0cbdc5de3d921fd7ef7a56c2e839d01a4b883fe558446cf79df45115ae624eb16c1960813dd326f3cbf8594aa969d497a53f2030e1964eae09908068ffb047e7ffa76a9d4564c424e87b4d7e412f2dad17699342f204022683428ec9295c3ca27e0ddb0c2f6e11701e9cd20969fd2f6c41a10902704f0ac8769bd1a50d1832c23d4aa888bf3c5d6bcda86761f92709fcb0c60c26f20e1ab9a0955c7a446dc5a28e9e1e371b81126b7c9588b349d0642927c3c733ce462d4b41fa86eddb5f655e5ae883ce7799b4156466c33b15f4e0f4cdd142eae599fabe121b1cd09c5fbf759726074cb015a22dc7dd33f7e1dd1ee44b9b9999cf6b7afe8b95e1331cbc12822a14a6b76411b37f65ffbff06f45ad9a19d4f7b3dc8c2ab945aed68f4d6a80bdddb98040e8fabf9ea2e33939ed1f67f68f7711d1ba6b4090d8e1ad131584f88aa1b87b0d1794ae4e9eeffae272335d065c65885e150a41a0983350131a91f648dd8ed9750e39c62b72e532def6ea17e0bffd88aaa70451cede72200c471d3a5002c5e0f6fa24180d1471824492dbdde688bfa41fb4029e138da56dfeb755a3dc949d459cbbc5a31c0aaebfade577cb36c49acf1af887f6679dee08e0e36bfa2d64938bd418cebe8c1067b011ea30f7dbd88d884d65e340493895c03c835bc0ba48e1c402839721a7e20a414691b12ef4ca1ba04fa31f71ea6a357967a29919bc7e7fe5edb61ba0eb613ccec12d7779d4cf61a6380d3dec324e84529b72deeb6bf2ae9b31323744956ab86e8d7bc0a41493c245c0ca0ad88cfea98bf6a97582645ff908de9a238ceb9c1415b68eadeb2f8ab10624f72c6559612ba7fc1323dfeb15bea4a857a0f56ec1353ced24deb1172eefe871e56575a0bc12c3d50b90bca175f125b9a3da0bf442cd46a68e06a86a547ffe5c17cfa11b977d968e4a02fbd41d07ff9e4d54d866965af5736f497b826c02f6d9d3e1e9cbd13fab1286ad5c4965502d99cf32622d97a200ac8f446cd0a477a938c0c13c9703a4dd57eecbcbc742fa1944608a93744ce7051996ee05a4239cfda769080fa6744df4ff541f1d08e784b07876778caeb692bb9e788673d9b17bf49fc5dac918d70377f1c6c587b8ba233cc12320a303b55b31d2b34a115e96a5eafc5da000152a60c3294c4817b3077df6cf4f3b60db39983e05f13e7c66e084105d287db7ba5328224be105f81cf482361c6d34632366b58484e8d25ec257da93ff08871708b6dd9ea121730964a1b8ca3e88d1063efb192853e840942130a0669a653da7d2df75522648aa574bd3a29257594a111c53b7421b60b088916063356ccf5d84fd6432eebe295c3552803800da55593b9f378fc8ffe76e2371bd072d3193c91b24adf9ec7b751630309762c578eec13a747e353128e64daa4a423933ef1e90a451973330c923cdfee5d7712d0ac9426173e69ed3e21c4b2cbc82d5fda3292b2d9a5799380030ab75cc9f823bcabdfd466e66a4f8ef611c1c7583d93470ed5c1d64217769222423cf278a34bffe5e1c513f0fcd84146f85880f415c520cab65967690184994116c5216fc10793b5c5c415896083570e317e7d7ddeb3719e333f36ca82805985976c9e4b50f355f9f194840ac79abd232f7d7d32f9a6efbedbc36b661bb782ecabbfd9ce877b745b38e77ce3e681d601763490bacf0a99d8c177aee097ed7eec233aea98077c7ac2b979e385d474805fab5aa88bcd8a964ecd4957841b07361c04408e03909bdb6cf698070725ba56209131b194cb54404694c68f6c36a071ecc559dc54e5b87c7c05264aa022c965107a3ae4cb2f0c72bd56eb113534f7590d4dcfb928264ebfa28f548becd638e42fdd4cd72429abde6d3bf20c95220a5c6a481da5ea48ed54da96b1e8577c332d98bc5a6b6d73267ca80bce40cd021d4447ae98300aa46b93c2d31a2d2246d8c45411993f7cf9215b7624817d8779a3c2db8328d10db2204bbc787500fdbc5ebc9bcd71e28038a7da15324eedd453fb6a54e921c908e3694405019695788ecca1f3b46b20bf381b859732ddd868edd1a7237ade96a05a5cf12e093438f1ef7f3b33b5cc7436835a6db765cbb14643b682b0ff9dcd4e4ef8dbf1af4097881b93880b6456dad962ab116944774b732b3f1c45a0520ab8514a0e5700cf36510c31584b061f1836b699c521ffdce1e99c0e89b5dcfea76c532f34f0f92d811440128a9bdfe03e2f4d24683d7fe8f183977ea8f2d944bf544358e0918041d55223dc07b43685381e118c6a3ba1a4fa713d1c2a4a3a97839c994f20c3013ebdddb59b65bb0011f6051f586c24eaf547bac67197cbfb47e939d5e9b7dcde3c5d4ea20b1f7c060741c76b732e4bda7ff60d96ed4821e2b971b86bba909b6939b71f8608eb662e62914c0d4b538c147a086beee2d3dafe979001300568cf821027573870356c330c6e036badeb53df3d357b97f1c7f9d7d370119e77913b12bcd79dd62c0785216292327e3a6880af7b6d7807929cd3342eba9283427020456e980d1d643f21d73522a0cb0ad4482931688409d813baeeb6ee34f654c2cf8cbe81eab8dfbae134ec60deb1b3ead5e5eecfc8cf8c804c49a988d5bffa77054d3b6fbe58bd941948de3e200e1fc316a70edbb85925d469c5e2897465d7c3e5c010646c36e09f1593e3f51735adc6bee6f9fa557169481a8695dbadd30c6f2dee99e6513c54376ede961d573c1f3cb16db9bb8185c3b908e91bb54be0225aa6d51efe24857de5891a7709bc9eee680c756461af9753bbfb475daf342e6ebcc6fb748536c58fd5b7dae3d2d8abdae2af7f8a116bdad5cb00d4a8215a349bd917ff1d450cae6d3b975f40630270d820fb983a30d602c66dad4f0c1cad0ec3e58a3315bfe59bba5018e46b956bbba8c77281aa25360bc431f499e549e8f928ba59527ac5cdcf39c07d3042e86e39d6a6ae8ef0f70a08d1fde3f7ec1bf6f66b9dad0cf8ae90428b8444168a90c0580156220318e5bf2e8b5e70e2bc6ece9cb04fa0bbfae26f607b804aa5736168509658c609c86a0cce70ae5daa08b14feab81e48c9bb3c59c1b0f62ddf690d91db364ea3286cf219e29f0f005579897d1bd9a8a1c97a41b800825c44be6c736fe3bd7047d0a2bf3124d921b7254bfb9d5993a440c0954d4c6ec53301b96e1735344e1c425a189ef90c7293dca0312772189f5c57ca07e950dd954a766d5779a191922e19966174075facb6308c5726174b9510075770f7915f3b06f658ce2a5c7b60d86ebd88574fc55cc1c02b4ac3b4320748eeea6132dca3dc6f5ac179fe71d6bec523be767cd55a9f31f6289966041c98313d957871d67fd236e24cb549ee258f7fe3171f1b09ce4b8f4d696fea15fcd087c75248bc919c76e974643797a90056c9b6398a340bacebab26a790fac76ceec942170938318fd9cc51d85817315db8580f5e34d1a00c1af6d05a5cf14e76f8e84477425ec910a3dac1e6c098c1f147c127e95b3f4bad2e01dec8c35931dce2c7bee53598b6bde7973071a0a6955d9cb9972566a80cec26f0e8ed81e7951a4efbb575ad6610a864e58422045f40d1f252564ec548b5546695e831bfa1fde8b6c5f919eeb6c8ac9e52753d0a6c678609984a35f7b19cad9d965d343a2b71f34dc84040834cc3d4e2fcce21788c995a6662684ed2a4be0e04f9acac09651a0754727b7dbd694d2a53536b13af58c89bc8f73397012d16edaf370a3bdcf4a5328e898a1626611c9f10af07b53c1deda04033e1b81101daadf8ff6df92ee534e5c8c3bbaa20b44253fabea55b28d22d2391346911816d0d8cc248a2140fbfcd8f2d0fe273d0e462acfff6244408d2c5b54e63afe35b07bf388cf2d7daa7fdc18785549b578d659486d0fb1a18d1809526af40485a1a19d2139346285d54edaf80860de4d4ccaf17529a48a69c4e4122edc0994dcfb1a90fc88ee511332d5337d4508933a67a95d0dd4a23250ba18c0c1235783de853b9011daf25daaafb7eb80b34e1f5a26cc4d3512b453fa11825a5bebc5a20d0910fe038ad381bcfdd14243566610fb1b5a3352cfa6b5be72c1704e45c9c956ee598be33456ae20458f125abc95b84b80240f89842a3138b5ccc9822bf3179799844872390fb597743cd1aa9d2b44d4759fd48e2d162ed7fb41c0bc62122f892364cd6b72927fc6061d4cf26958e1ce168a1919b6d894173dc8d85412268b0d379eba4528ce085e30955786f12d57962fe451c2d523d51cb9baca878322bb7e297b4bd87e43b7f6801cc12245a7bb8d7cfd9ac8ccc44795035eed38ad0ed080afb4c3c5485a52ee4f5e620ba11240addda1868e97c3bdcd8a83282330f2597b97030d425ff6092396afb002591b82fd07892c47c68bf3f62e4081783b7a4d67486693a735b34f2eee3b1746714ed4125aceacc82d19c9b343f54535b23619509c2cd0420c1cb025183376bac82afa30eea09ab0c9ed51a644b4ae5e5a863a63f7654adb046b295240b0e88616e53cf40aa602bb033d908a5444e5a8b78adc19930591c01946db369fc3f6850c874119197ed6d45624fb2a56890498be906d1ce405ab12ff4ac82ce1e23002c7a366128a2f758c59bb3d6bd306e7f4e8efbbd3d244b555c0a5192564390d69a9711fafb7b379ee72889f1f74cf58806bdfde655bc93d6b07831bf75cb4c71033198f2e8058f188a297a486a32835104968cf14f3d755d9bdecec0f94035c8679618a245f22d81a0461ad0e024054dbe3016f1fcf2a879ac891fd5106608ffbdcf3d11f45ccee091dda2cb13f2aa44f20f1ab83914b7b0aa2ba97b88d928a4b8cc0290f25af77e643422aae968415e4bce91a313487745c8d3c0505cef3be10c1f036a336c9eecdb3d4a6cc3d0e51cc8d0cd274f0e7242fe5f3139b383ff3f9515d9c97c000128f1522679070c725ee9fc00736a3c7441915bbe9c3c6ea7bc38e42b1d2d6f17717519c1b81961ffe69773e700549e494b90ff1e010340a9d813f2eb6df8887acda12fb90000e0e505db744670a3208f4c0e7e920f7ce35834657f4ab8c0184cab5bf9f0232a88ac0051e5798174988806eeea46858b5ed68cae539ea970199c98b3207671c4c270452761cfdc2c7119139629d19215776be4531642195195a6abfe50b50abe36bd26ac4e9b72536890598f4ccea5b5bb026c21ba3eef3f14875f10b37d783f7120bfae425dce01f2323a04515a6c9ee957f91dea4811d11b7f188ff5a272d89bc507bc2eee213e03917cd2c8c8fc27cf6ac5fc6c89dd8b629ece1f6a754feb15d595bf71bd6efc3947040427ae02fdada3d6579900ad5d668c16804ea9309ec56a54bd70502cd7a8a787833923604a3565d7b5fe2ff4e17e53cae04ec8f7a783b4fff7744826a42f38d87fc31c88239e828ea71d96cec115c0fd62a611daf325c5b310ee38c6d306358bfd6c1c6943b37aa6463083c843acf5a56c29e8ad8a2b92972d33d2ad507858986e49f9d4b578d2d02bfd5bdf320bb32fca19adcfdf3a1ceb49f3b1066b5af0165e8d449fe64a3e206270fbea7495ee99a4abc3eed2d591e4067a1a5a47655eadf13388bdf7305abf9385585130b1136a61b853b6e18118905c18629862ef04ce1f9139aeec31c2b763aacea6371067789a8f58c305fef29f4f6f8cdc789e5d7755120169e858363d8ea82ed17da2381a03ba9a4d94a8ec3bad452f278bfffb40a2879205aef144deaaebe4bcf2ca13d15911cc682f0a0cbe43c023f742dad5ea478ab83d98aeb6ec983b57c0011f22db7a8cb99f2782e7d055ec5ee8288f62d240627c95521277bfe3d2e121ceb296baed42a2fbc8819e343492a39730a110977e7d1d8054f6fad40ed6dac8c31eddda1205b087243335b323b969591237811282f7e83e6639672f9ad9fc553c37d93d946de542851b058b3020664141533e76557d75ca8fc5b0ef810ec596079e81d1460f3091bbda7a7c1efa3aed6003185ca144617f87e3a7ede9f743e6e977786f6fe53b4fea9a3619261d783f946a13c3fb9114856aa06f18b8c3be73ed7e2866370ab06f7979a58c16f7af20ed76d2d6f91b0af738cc5264d6f2c6a786aafc1c01d598bb8fa84f6a7e454d0348b8bcd41f3ae1f7b028337faf664209272fc7ec0611a8172dcc269acd9f607e26acfa24ff16d8e772dfda4283a63a73a372ef55226ad58d19144a526ecbc8419646485e042106076b482d87c9648963d2368ade1b834caa97552b417043cb900136df1c242e3f7eb56d79ebaf56bdeb6e235090f971905b7f2e182548929e025697e2edb295f3bc57b63a16f9efa707c277dccbe9ba94087596c46f533dbbd05ce3a6d605f37f1d3358776ffa50d7228d4a5237d2ed02d77a834fcfa7fc44c1102aad4e244a6cf26724aeb15dda5981cf8177623f249b8b763ba4edad621d119c4b017ada842f58708e25a503f6a3b75858eda21f429f7aa0d80f9444ec0bf5ab0cac03d0c682caa3257748598db2963fb767ee8393d7c5f631bbd4b858b236b4ce4599c576b9d8801cc9dff2af3ee5d67be05e4a5137c263b04d64f6278490f802c202f70230305157835e2d6174702ea87ecc213b267e7070ca091e77e6726d5951d6937555e6f6ff3bb8c59c9f73ce990712a59949a5071e4b9b07a42e674ae95cb0544f6722bdc59c1e8e471d4986c04cced04526e48825672ccb4430a78fffff4d2ebe953f97b541d6065a98a7aaaaaaaaed8ffaaaae1106d0e6eb05aa295befc88f891210a6745865536c5cac54244229692549abb2964c2477944a301972b960cdb411c2216c8a563645f5c99868533389a46555d814b635eae3ff3f972e83e0eaad3f9a6be0040fae9a96872786232aa3a3af1ac618bfb428cd7c717a8084bad60e4d702bca7ab2ae555b582d0d8d20451897f91ad2605eee91f9d329019cf1d8da8367b0bbbb3dd1bec88a61e179c191fa6c01ecf88211c312e300ab0cc74156db5b51d693751dfbd1fdf021d5c2939c2140d61b74621372a222a25122e1c2ebe3c6a6a6f6bbbbbb3ba010629cfea829943526f115326b5dd30865d5b2a2ac27ebfab076002a143a70eb122f898b369b6dc5995c318b5043c9169d5bd489546fac0a9bc2b2972003ab13461ea28c32ae13c8140d63580153dddd352bca7ab2aeed29b5b505c4465c3f13292ea09e8fe3865b7777d48aa42e2b63570dae271ab8da08a9c4058e4b77b7cb6229182d3696dc9278dceec32e5ab66c5ddbffbb2af84ed830fd9284c0d29a5e52841d2fe5ad47940f175dd45392f4c678ac7954ddeef64c8110774337f6ffb18e28cd44263d60298b584b9c333fb843b537e79d4bce39b6d6387d0504f3ce30012e3039f4aa0ce994450301de8ab29eac2b0f2de608012128bbaaa1a94645950c4de4d443b49112e0032a9d4bb76b8ac061bb9935c3929715653d5957fc12d6c7ffff9e77e57fa744f0007d88ab592308930ddf970d163234323864f87f14c39c7d5a5c162e276fd99aa41bcc38b6c0a2cd669b494da72041e0180faebbdb4ddf0c052676400b30785cb07462109da8ce14c12dafeeee0f3d1dc61b5a93b6ff33862494663e19ad368861eb1f72b1f2e7bc588c802220b960ed9011290a6a1933b8009821769ab090d8de7f96ccb53bac509ab9d5e56187d82c67360d4d76dddd6d18aa4c69a6fabf10d562fbff4c4294660e3308a662ce6fc953b2f7df4ca621b6a47a6b8d6cb8f16289dd97354f0e8ec063d0e45804b3a2ac27ebca7b9b73ce594594661e91496587aca5ad8396eea58fffff3860702bbf0e1b2d8b8cd40c59fa4b793897125d379272c920998f1b215217f15694f5645d77217cb6a6b6e4a5bbbbc99029cd549f32fd60e175bfacf8733ea91411e3f0ff8150261ac4feff0dc00986d588592e07dd750c09142f70090d21239a351853b06c77f7900fa5993f6088a160568f6aae45edbe6aa4436966cff7fb6004bc0ac618639c5bbabbd95a46a1f63be3ee6e2b94666ee10e325d286ce35815368565310c4ecdb1350cb41a5be3bbb229dffd4a5ab56555d81496c5ed77523d9f6d5d328258b2c26b7ef9ffe70280e9f6ec51eeb8c4136d58fdffcbb91473d3b7b19aff1fcb86d24c5d4f032cdf8f76769e865c923bd2610fb2fb6079fbf8ff0f2534b4f2b7b8d6ddad944c69a69abee4803cb37703c09b84ec171b390caf16c22ba89996a797946606cf30ee8131c637cabe59368ead71748dd36b1c5fe3fc1a0728ecf7ee09fc727158f7d41dc7b52a97aed89d7b61cf20dd7ad2cbba36f310b6a42d8ec8e7f032e7bf55f3ff2be9509ad9f3fd3c5002fe3f6f841a5b6245239c747c332335f5cffdff3d6c28cdd4f57cbf103d805db37a8b31c61877dbf296addd880132ba8e242ec618d718a234b378442695226a2c557c9382e2c17b51067892b62bca7ab2aebc921b43160c94d01325b613b2947104caf7bb3141614609163bac5e7e3ace760ad8fbea1b3b4598c7aab0292c5b03005e77378ec40b39dc4995245b7c97430a9cab1897c5fdeeee6ea916dd4a37c61d6731631fbe106be707a29e9a33bb73e6f1ed8992b1f4f1ff9f461289e4089b9e8eb6d7474b12a511bba99c730e16519a794426952182cb1aa7deeec52da705590b8104494f46ab7bf06efd3d4f91c32c901b784f8b218e9a8c68a05c0cd13a8f3b7eb83a154c62329634f4248f43ad087684305ce398f0f471146488cd022bc8f811923b2292c3e3a846850efbff6f3184d24ce1f0438be2ff322b9ab44769113c76e1fcffc16a5cdc92654a33d5d95bb25c4f36e89eeabc227470569fab730855da6cb69a99596c912872dd66deff77e8c043aaeb5f1e6753532f898a2bc8c66206f9b1e85249810848d68b209e196228fa884ecc540437f0d2d5e9e888712372bb3efeff5f97338e8cad29221a6c36ba860d163d97a5006ecc38ba526c94f08731cf3d41f410b6d8a4694811efc61e308dfc5a862472445a384ee494d0f755c345219b825d5cd50659dc11917839a71042af8e28cd4482b84afe38ae942b3760e5baf45dced852300f0613cd6870bb181508993671ec97ec7e79250930206408955717d111e70c4eb930f2faffaa224a338f1eaa9039dc345ce668055f1ebeb12a6c0acb0a91f4b4d7dad16f7a8325af407a5891347700cdaaa189d1825a6e93156528cdc4b52f4e4f0816406d8117071a8c31c638aa09001a7528aba5458f251c48bd4444aa5154342d3ad478a4fd7f18d44be97be9eec6f86d0b3f2576ba96c8ae545c8105d970c09df8cd7a5cb09afbff9584509a5994f464eca0648577aaaaaaaa8a337bdf5a95daff1f86446966324cb9862d51c46cac984e45ac57b3402522449e488f9501820792373d2db723c1125a472d406ee648ea5815368565956adb4ef0eed4d0b8015b21c319b60454b26abfbbbb7b92509af96414d16385c2b6dd3e3b2990a2ce8ab29eac6b1490b21cd54562e66c81a7d301f3824caba8c914d21b3143c0d6d4955113d412a8e753c4cb811398b2b46010f278f05801416163fe08fe3fcf645194c143bedc691221bbe15b427edd30b6d2afd8c7477d39c77004df6e47621cb16952ca51559574238a2498d24cf56f4dafa4f9012a996b915d581536856571cbaab029ecdb4c5d6663d1c7ffff1cb61c22872e6723e4520c1d442f1518bc69acdc7835e49d36ec9e2716d030a88fffff01ef8fcecc06d23d4368853c3e4470158cbb0257e002a6c88b5b8fedc724e34176cd7a39d7c7ffffa82572e5ffdbffff3bf5509ae9fb013308a7a0caff12e0e4c379f14ceec9399b90455ecbaab029acfae69cb38f10a59943b5b81e51902c083e49959a8bae2ca6254b6e68d316f5e043974b956a50ef58153685655b75c74077edbcf08562c44c52d4901f27b058b28bc2b12a6c0acbe217dd993986b86555d814961debd66505807a594f742bca7ab29a6bdb8404d4ddad43820bb025b29b2043390da51563657adf740b57c3d5fad121ca41e4f6c562654a33d5decdff5fc48d62ce3fbefdbf4c12a5994a0f64966f115343195239bcd8755dbabbdd54f9b7c45bb0375bc21155228eea85ffffff15302436214a3387c52332790f6cca9fe4869f7ae07e1cd0bcf58aff8c4ca78fffffc5b0b295ffdd4035030fd44c6f85f24d81f5758e7530b662d724fd09d6201fe3a2635a35e6fc254df00e05feae0257316163238af8308a0103ceab86fa529aa97eb744db59dbc7ff7f61cea80957336e37398ad408eaa7e12e9733054d3e2762f070a167c609d70425263c7182f39bf5b8bcb5d4f8432cd5b5efb74f27889baf1baf28ebc9bace786b075e60415c42e38e19352a88cbb3bb73a8cb8b0b7c90a674c4909161a49b8080b93881bf9b09d9ef37c36abf33eeeefe4262115fc995ac8acc07b79393d14cfd55d653dec0dadb060e54b5fdff1fb72c0df81e9596924f3227118d12274a4b48079f8527231f1c28ca48109934f460d0c1579ffbff91921ae9a9918c8d64d5485b8dd4d548628d54968998ec54555555754bdda563099408c20092540449662868464cc786d5ddff3f0a08a5994220a28a5694af92165e50f514b946388854a72ea6c80cfa04b595ba6ef75b53fb9d7177b71630a599eadbeeee96b7cfee99931cc298d8e8b101b4aa6deb1a65a4ca5bce2df43ae59c734e12a599ca10394b9baee7fbff16530a08885b1c331bb639e79c5f21946616059020a8946ddee6c40079131a68040d0371248cd31863001400070c6a94b4787808311687c5c1a0480c0484026130180c040040a140182c0a82d068aeaad17800a9362d9b755087dcf8d252e1468564365bc5a23186ea284aae53a43daa469d13895e5178dc5b2b30f6db8c5b8ee015ec4a8f461befdb5090b46549080152a5f5c6c1719314fdc262e01b2f7cb846499bac1e5a5c8b5520cd8497002e3e611b62c8d6356057eee490bdaea48a573a2d93e28830caef4f278d631235b561e51fce0ed32674e53f5140a552cb7c0d081c6388e0e53a5effafcadb1e9bb7b2e6da0865e6d8d3fa48fdd8523785e51f71ea1a27128cee1c5d7b313a04ba1da2b55f9be41aedc5ca165f290751cf0590cd0d4fda27e79761ae35b87b26687011cf31c28ad9d213cb6344da3e3f74b2b3537c3c235bd069f7683ed20d21d4b088ed678cb5fa2a8b5490f1b7bf61818323e03ed030f223019ad1775bb4d2eb1c7b0de8168040063925f1d163b92332dc60a11e8fa243ab93c61fa4c3ddd46864a2d2308e8fc72fba05207a5cff261d3ce57fd7f153a8add024690786bf3c69bd9e960280f148bfdd347b2c4e8917a4c69317fbdbe47b65fe1897ca90aaf0343c7295c6066ae25db25a775eb1efc34f746a1c8bcbd876ca1a2cda61554b1aa040360a9311f60f9eb859eb6db7cc115a2a1a8a6941cbebcaf6f168f51d1ad31f04589a44a940b87979d0107e60ec29246a8ccc420d8fcb4e3ea5c9ad027bd880c3948e6b7159b4a8a37f4cd55e4911a95427b5d31f4d2e256e27bced380dd8d246cc8829b585e8f429a07f12dd81c90e4aef2cc53877cd856996c1478ea4231f03dbb9cde6d8de7297ec59b50b45e032049ebd53d24e41279dbfbc974d67b9938564044581bd86a8ef7d6cdc08a29870510420372809173a9fafcedcec4568d34eab4f895b5024e18176a716b453bba8d17c4aa98641e0ade052f9272f7b879a028af2df79c3a056ba73b0dcc0ab1dbfbb3ecf2a45e574a9214208b332241ef642a9b3a700c8f059c4882a07b51c53c0c4c948530ddf491f68f199e39d885c90506d27e3097393314eaf300cd5dc2bdd5d2f3e57a48999a80f83cbf9c362786b75587cc2c110b6df4957d4e698db840b57f3990871489c67d1bb9709db69cb1d53706044b9c4beb1b1adb6a704c44f90b01ba8672f30afb5ce0a199edd47bcb8e111e93344e453b4d2244f262a824b6193cf528a7639103ac8b8f2a9bf48c2343523120f50b2cd953e8adc09dcbbc14975bd51999a584ada2ca8feb61edad30207495f2dceb01a900e462e6039dc910c907c662c6d15f58cd2e8df6e596b650c42f533dd0d81d1c710855ed25db4128e89420b34545bcd5aa3e8d1c07cf9370547599d25c9fc3535068d70d49bf3178596ab1957e640d3c1c9ec30ab08cf814acf3bb05ca64ff945703c097977487372036f25de21d993cf004ee36e30c75fe07c8e237a9a01dbb35788df885ddc18fa956f48bfe02d9850367135038276ad7ac5cfdd5aeaa97facc69751b681d13c3fcf0f0452f8018b861cc8eb837bdb995d8d04681627cc7ad38588b3024e671d9ad1941ac2b52ff4a94ff9ec9119c77e26f4f16b2da84947bae7c04807e6d7896a4eb000169b8967d635e87bccbb421fdf28d726084833494e7f3dd1f6aa5243fb3ac63ed839d08a4bcdc162b3595e9d5ebf4459d602799f8bfbd5586690b47ed354eab043c80b429f3b4ca1c0e42edb66179c066fac66bde8536217815d0c77fee1a07f688191d7f3b54a0584cb0b21db23e415fe7ad635084a5e405de7c966d880a6746f69be38a57145cbc3f38f131b5b21e5bdf0498a9e2bb907d5aa73183bbd5e33089b4311958b675a65fc285b577710993c27a5231644a4e96ae90a487ee4b5f25548fd8b6d66f546e5ebf31b5b22d90630e8e9505e9479a09a0b589aeb9267946fb14fe12e4b5bf927e8662b339ca794bcebe7924a432b6b215078fc811dd261095648c9f765445061abd52791a68bd1ae7496b0272b2185476bc1989a6dba82cd1e6595ba632c3c2539e2cc4490fddc1983701b68a2790f7ff8a7b6f3504497669a34a6b5df26ac7103b1be2d273be43d06cfe8503da2511ac3b9093abf2e5a23c034eb8629158e83605bf704cea275cba29d285a1868e5d6848115adca25851956892766d702a8f8df05f238a80c2a402072804596d9116cd5da5f2fe3f834001ac6bb2609735f829a9729c75aace0d836b8065dbf5c8df4e036076bdbc0eec5596eb839aa9f283ab945c837d4e89aec18423f40e74d8cf50c31ff70db8457a528c77a3e540551713c65e776e3272224a12720a9b9e434ddd663f81bf7a6ee289d5fc5c00297698a96aba2e380fea4cb7fcbfc438db3b037c9112df4f081eff2c5bf86c1c8ca5e279d4970ce10ac5b53260a9c2fefd849d37021d84db480268086fe18e2e5b029c72a3852341224da2635397b4231b8fbd5efb58bf18e9920bae9463a4980e06a20cf274249c6005bbcfe7ce8dd18c24732cdb4ac1bf83d11b657c55bf551d7b85cc81352d7a7c53e89bb842e069f8b2c124a6af601f9b69bcef5e6740a0ce981c6c7714b376ac1ed6ca411b2609a0d4efddf50162a9a18c4213803c0ed22e26c04dce7b9032a2229a2c083b04df26a7f4c657e471792b80fb7e08dcdd5298ea718e1805344b41e197f95e6edea21c20a06b24c5f28922d3d784a3db550c565354db5df30631d0935751bcf76da87e0e9a011f121f4da51412404c9d533ea8f1b47c2a766ba97b8751079d3abc46d9e1afb396038767c27d5737cd76cfd19027c2517ace2a75d1f2d7da2387c4a568d9c5904311c9fef21808ab53b5830728ccff0c1a15829d66e8dc39a93d514b76b89e25e9d555cb043cb9d3a0e67a77c8ef637ae9e59c1be788c702cc609d62950454a5d9c578b09ceda25e6bf646822edea84c8e59816c400a69df0178f8bda50a373bfb87b442a100764cf27fa86762ddb6f1c5aa225543fb481039a155063cef8bb3ff2e592becc16f12b8f93d10ca7bd85a644beaf8ce26b7b20a3ca57a6780188e2f69f9d7eab8449aa11e447e81c35a2a2c7771cdb03c1d4f793c81db60088efa7525a972757d5d2eb1776feba731f8830be46903f5f1411464cfc60e10d96c79d2503bd6befe426afa55f50d91c298ecb41f2cc0f06dd7fb4a868b05a8a9ef77b074e8fba84274b104ce035f24e70b17b13a5dddc4b3ae4afb24ae106e84657b1ff6c49fa936154f1752f996f6ea52be089cb271a71c6f783c5997e91a0552fac57f96413a7df75af1f4f8a66abe2e4135ccbc3d8d8d6fca963f9f533e50307e01cc56d5bdf4feacc7a481144a30d3c7c55c6ab2a5c9ac38d13b0cb04ecc556f816d52db834b498546e7824764d532fc61e67023e4d178d2707147b0092326e06550ccb01985b2a541513973be2ddf77c2c98099f8990cf5b973b5286c9544566989fb38bc5247f2d5a1236d6beb0cb0f494e67b19d4dac0e73700df4781b55eaba76e47bf70066d446295b25417f50d0814e4b3530c7024fddb0944d737fced13c0d7468f2a1cae92ba079f3a212f1c0875d95c9f86399f8bc530c36c8a3da191668369fd1b569cb210f73b09820cf233bbe24d37d370caad446009b2e668b8ebf147e6fee3cac7025d0a2ead12c4e9e4d048658313f79ad5a630264c542096ffffcafc920a94d3ded10ef4a19a576f6e80444ec71712de5894aaf9d467b4a712365766e2347beb00c4b98017a2fd4955a3ab7285639dd392b77110c9c1c1b2d12b20a6db882c00473798ffe2d7b3ab67acdf9581ac9484a5000817f0b57a48b9ece8473f75d62ed4be91653391c90280e4be7b125992693464a0a244222174232851f3db684db69d822b1b8c75f36af99cd6babc646e9796a2a2e1551f9862c5df57abbcc8b6873a900b24ce7abea3c9078941bc4807f073d624a9514c8b2146fb8ed92221633ea1b5444cd927035792ae8e2c22a206cac941f7da4616aa0e32cb533bcfc704b421a5a676a2e93b0505c95b550d4c544e00267dbb6f72c08576e2350e1f469f3e98483749111862081de8772d4b56e7a49e96a234abe8eeea3e116b5c24746bdaef6b7d3493275a86ddaff53e254618fb4a19fb07b06e30ae735850e508df75c9f19de0ef29821309f3215956c124129bf0e4098b807229ce8086074b272accd3281655a57a501733a1da0399981da2ec635447792f9e548724a19f09c3fdbb2b78e747385b38d78a995c203d80254b4994420fdd4e01a32d812921ecc8b9c7d183ddad7d23d1f49477977a961f6ed52ee658fc49db2bead3b00d4445d8cb97b6af667936aac950647d04f7dec399d5bb2afa1d386b5c29a41583892242fac073acee0c7740470225faea9d56a87c6215b60c2c7ca9734afe6bf501ef2b16a25b8c64276475862d9a795aa5e5e334442f9a253d1aa5a930b88a83970585559b7ee75ca8a2fe119d85d7b8249c9ca40a079b1b873568e1b5551ca752b629be3e06ff7eb0faa4b490e11f169d15fc97c4dfa13f10fa031ae24e8080137e5b3690ed6ab8a7ecf87c7577fc2d8ad055c8214e103cc8920ec43fadb5e332b4e05261adaa0a2413b37b1ff24c2b81ef63b9bdad754cd1737d28904bb5670779d624f5bc1068cd0801e6001b8f0306d27a5957f15f60d1f3b443ec1b2d9802710178009518133e01a4fa23257e2302ff1ddff9750f6fc024a837c6af43b46a17aecf7bcc0a5d980bf7e91c36b631cab928e7652a3e5395289179e82399b02c978398c4e4796c2efa5a6cf4fd704ede56334970e33d447d6146bdbcdfeef36d319c0d40d3cadfe267a4bc688acbc30c9fc52689aaf22b132504da6328d3d259c692c8614c6f114c2136c651b2849ed57681cbce5d7ec868975d13f3b22e2bcb18c369a150158f43c588f848518a1c3503f525491444e881a8696f16b519af71ccdd9780c76e973e452fa293057a143030a0b4e6bd895823cf05a6dc82cb945740fa2e2e85dd61fcea1f0fa746c15674086a9839964f7382b9a0ffe83fa03816808fe035a21d6a7ca3675d7b1bb7c16eadd24b159d2d42ac8cfdc552389d1980bfda9ecf64ea08426a51851b7d24f05511f813af01fb2d15ec806d423e72ed88249a22b5c3428e0e51b9d1d6b3b180430513c2b90157403a533edc59300a243854217afd93138c291a9d91c861a66597f1c0a75708ce831bfe516e8a23f9eead92a0086d518d7da90b5e096652efffb956a443fa022a07dfd5aca652255b4bbfa4d6e7bb38778a0ecf9441718c9fd6e352c12b38112bb9575bfd5bafbfcc2bee0b8d36cbdaf4d8a279fb2f2c4688d7900602553a9d939afb7bded4c3c37a5d2fb7bd02ea964bd9a69a5c263234759936adf5f51f07cd2cdaeb75dd5ff76b4902adfc34dfa2ba87d1eb5bf0bb7d3aa9ea8308dc0156b5ee68c85c67cb39352aa13391e126a2308ca6a4d75e5110b0e9ae17cb0c50de55f41dc61f0cd5ca6e79715841d7bc10692cd9881858bc51c94e9229bf1992361e2e2bf02015ba3a9b1cb2f04f2ec93a44956c4af931d28f597f0c6ca20278ea4d5888d53971a147aea2cfa9aba84a0e82fed663cc3f65ee777e3e125eddc757ae872a6b7dc2560d07201882badc1fd2affe4b7775ef80d45ff2feb7cb12a9121ddd54807c1587bea92c8ec3bb40d154828071b6e417fbd5511957577aacf68db03863d4776bac2cafffeb84b7b9182131aabddc536355432bcf0f696a9aa126619489e1c2f837e56090a568740e2d036e1ad58ddc669f3eaeb25d781b0ca4a2309e5ffb034be64faccff6797b8a4fc845ba4f183033f1ab983220670ffc2231999ae83ffadb834c048dbb4d7fa5ccc3bd941084927cabb0c7ae5e10f7fdcd441232723b42a53c91c716cfd60bd1371d858f58b717353649911cd818916db570b2c071169ebb7653c9c590d438783eaab595ea2702f0ce7b618fb342fd2e44324ab75a30a4e6d5d41dd8d926328e96f99d871e4a368dbdd0b747bb8532b48be47f39a153c69b843532a8e93d7af5d643312d8e5b5a0c4a25f42219acb58f2a70185476e6e08384c5c6f49b77819579789c11ad80cc72568b6c9e9ca2dce874dabd6bba4de1b94886a49fec7cc5493534bee914bb968949d4e9831174104147cb09ecb22bc02aa12f5f38d0b5f26f0053046901c43f244c279943ba89b11e1f1c79b935d56d06f6bef29bb9aceaebb31884f5044cf3a013a91e663af156b02a26eb6f83c8cfabb0ba9646523a610d6fc98009973a394195fe11da460de87f866fe5573402d801d4f263ff9965a7280e28bbac1fb3a91532b116666f1014580a361bcd554368c3ca907f8255487d14c049ee329f36d1c965f0fb871c65390ce44bb2842a5678922df7aadef656b933226b0e5f49f1a87934e662483344f5b90192273be59a78d50521104002e0bb4d70665d566e48f7448cc15fea67bc89905dd64d560c9edc3772d7fd0fe79f04e1e16e153b4c198788d4958a48c5bf6907e77553abddebc4d936111042d296b4a8cc2094514829e441437056cd6627d8e24d3402595c750e0d6bdaccf61142b9eb40d792fc1263427c75c4804ff7a085386eb563553020d5a2d91f2b938c451d0e1e46a5e477f6fdf6230c4eafbddeb1e129692601de45a6c59a2902133e5f6a230745579e1ee33275b7949954beb877d6213b8f9e936bb6029697b62096eca3b90903a67767f7171721fad61db7398a629fc90e9e847224950390feb0d01424edb4a4e35b638e838aa595cb525d25225e5b021ec60f55019655ea536c72983be55d13bd4e96587c7ed40f3b4f505f0768d27f3233e3826a779b6da96ea128905ab30ae2dd9a9274f0d2b6d99e1d25bba5b08dce4414451e17b3fa4c6859ba8c4afc647dbedc69b6fc8b76d45f0224eba4fd5c40dba0b542bf719df330757f504b21a1aad7f1d401e2a5f83d2c6d2def10155855e5956be4409a483234b7c34f32756ffc36f090c50992fe760a384fe8b8b759e16c0e65b8ce2469aad7cc75aa3126b186eb7be7f494dac0d612e66872435f5ae50ab6971f21e6e8e22a4029135a61b7984c9f5f7c4e18a0410c3badf31e95fac7cfa3d154312c3a4682813c11462febbe9dab5402b434a4f05e49f052ff85a0d4f45bb7192106f45466f5b20c3c70847587331ac70b6d0f4bb56230fa6e66c50038cfb4624fac635162f6fb87cc1cec3726c00722b670a1efea68938df478446285c70505ffceb73aa508ecd914b96c4918556a269d38f495a8251b2b0be5adb91bfec46f849b060967363139e2d45f127b22b0f8439cea55ae51766bb3b92cfca3f4180bc8a1ef7f548bd86c4cc348b8544cb3db6ee5cffaf1929356d9288cb80407e847082711ea1d233184ae64a78a6a46ae59a4510ca760ab19d7d4e0ca3059ec005c0e864e5589b654150b015d2d314b519eafcc0b80c768df4e54836a95ea3ba878b035b0a05ac8826c5306e3659bd8126f5a261dae5b3851472b3cb8e118dab83603a556232584de2ab5d5cfac6d967120e469e0a249c2a84e7c2aa22391db067a3a275dc2a092b81d30f0fdcbc34faaf08bda5adca3346ec6454a80d366e899c2f7cadb8f4033ef6801f4ce7505fb9f5e9297932d4b73ff34b69074d344473edea2d1c4925f5c2b53c8436839466313c99b431c4a191275219dcca464842fd4a8352f4bc28ca6816e5564428a218d1396eb5f4fb77fd978bb38af36fb6e95a205e34cd60cfdc31d8d3139427e3707d7c278e09dc9b8a779e465269b1a008776d0858f259f86cb188578370b958c4bb8b76a169daeffc32533e6dd1c2de30d379e0bfb66a448b0f3060859cf1e061ee028af35bf19db4166d67846564c600ee1b32ed8ce2f2804d5646aad46f6f8777af521c4dd6a0bb77abcdb88c74ed3e9aa987517d220108b1a922ebe9bdd0598967c5d22003d678dfb92172a40a02c641e9346e75e2312cd96e7ac7691062d74ed5b2011007f14b9d6efae4e95b3ac039efac873f07191ce29a3a77aac664ae8246c66268b102f78c60330563d7a56c026214a8af6f2dab6207bcfa4f7edba740107d51fc2eb52afa264c2ddfe1827d1a1d7748cb4445b2f45b4bf953239bf7e60864399f74f5d24c1561c1176b8b564bfe086fe44fe158da0ec3a66ebcda0345bf73b5dc84a4698bc1c3b5bf5181f49cf3c818972d1d5b58802a4b7314b83be9d99ec89bb4015c386d96e87e99b3ced7eee25b9a7a22986e0843d77aef9caca912b5d0c88f55061380ea1529cdf84a222eff2e5864596c8cd9124646384f0cff8ce257aa2eee2dea3c3845c968e7b1c794963ab7e0908141a0d262886a99bb5fb9cc2ecd67c5574ffb4e7cef3e257c8aacc503f343ca9f02380b6e2726d982d1591818417dfa0ecca45fde0ed31a94b84e858415e52e22f5b9766a6daa8ec656da88c25c5c79700a6b9a107647fbfc45bd95232a2bdf60db1fb6bdf309bdb0ccccad1f11efb5dc1711d7cc3edc0caf0ca1909cc8283cf2a0042aec2a386cf7460c95d20944ab34213b702d777f31df7d266c3b12e63de1627802154a13eca949db37729d8d5ea9638b3e2706bcfdc5f20f2f822940ccc657083040464a9f93aba30924406880913b6f50cc7c28ca331d8d1d02f0e1def8a485517c0419fc11c39e23914fad2699e57923472d7145fd0f06afc16b379d8bb8a69cd0273254d470cf70030ce3fa02f1baa7213f2c34953515ac2e0ec98348f36fca03d91e8697cfd74614cfccd74c7e4798cb3d28bfc961136e2ac71ea2aebacb81bbb0829a0c07f672be14b67c03d8d9f31f0e2f079bc71de86b8b96a0d04e057fda6d35a7bdb49db820fd89817a7a9a28a5521eaa2d59858b96cb65a380b589ad04836f715661cd76a7eb5fa6c9f6d0a2d59634a960fc11538d24812af0cd6afc0d4b093f1801b6d001c24ce443e21590c524d303000805b45bdbb0e03447da1360ac21ad213a6027bc06c3e10587b4e26967b33564b96b530a58e6a5a8051ca179fbb5547e2e57a0d43287cd018565b6b75ee05ae07b8016948de2c4ca919a920991e025149b093a8c2fdc9ff78c7cc9406c25da988d7193b2bbe45cd379fed3a6ca7c322b05e4bbe017f1f2fdd3b2dff9d641e99a3d566d6134550196467e4326a20f8d1b090b8009c3633d80e776b2179d52854d461e1a8929d1e5096dc8743cdd1cfe7ca31ae14ad046ca127e58a3097e81c1dc164bf8e17d4829cd8de665a551a0b1de6c4d5e094ea71ee568863068de987841de2327e4b70d7b87d846fc1e05d1939bca72c93146344659f71332aa2166e1ff3417077cb28429b10d1c22da1a2ffe7c673ce6ce8fa1e66f22b1e28fbe63a915a507b095a8aab9bd16244cd369d39451dbb5db4145a583f48c2d2a7365424c4e77ad4999163905a029b4ae74f9a8720f310a7d4012a44aec6af7f92c71f53f5e9a9d92bb8329157068338468af607b2e87c5e3a455459dc62ecd7a00fc3a18cfc04b5e46c352bd863720b26a9369ca080274574e2766041555b8cc21515907e700c78a89e0f619a757e999b034773ba04951e3f7b70ddb668d91c8c0698815e16abb03d03ca9813aabf069f6ea2a1583eb4ef5288d43257e704ae86f80404db7822f605e894c5997382d6adf78ad1d86fec29589e76e887142ca7caf53df09fd1390b84365b0ba89bebaa1aca0fc5510538954961b137296bcfa152db73535223a74a4ebbf9d25eb30c726e450229ec2bf5a7d06026856c6281c0a62e534a486ea8f80fd2ccf5356a8e50f54fd2677db304d5220fbfcd29548d41b91a9971e84594ee5bef6577bec7127915eb0d24ac0c82b0d207e565d5b130d4bf2f1b50230b381934c79ef5cea9f8ec62c7344b17c91b3fbbf0f85753f70ebdd44d7629623304549ba870cb87bac1b9343d114855e38c4d9ada3d145541990aeb857eab77e2f70e400ff1a8bb12a429b3647749339e20a044d26bd685974d10be43e38266dc96e9c543670040e9443eda9b180d4a8a7d95f7d05ead92a4ac3d23723de7658e57e3d5693a945dac2d5962852c65fe9eb597ea9b06b3395f5d1132bc756c03a1b5da16d1e87705053bd9ce85a9d73412ee9aab365298c5cc7b0dc456282424934ed9746e084c088c62ce9467c5b0b06338b6dd9215b763372b481fafbabba955234d57fb24d9e93bd9e06afca8855a6b057f998b3e58f3d5902dd95cca4b81591a7bd3c8ce56b2915d27dcd51679cf75d43925efb841a040bf357f0d0cc86abcafe2456937483f7d5b58ddd0dac538fa3fe2342ae65669f760a288f9f523bbfd1e24dbc76b196aa82164a287a63a2970b881454a346aaa0dec14f963cc01bf266e801828d44584e73d3b94320fcaa79691c947539aca8a24bf6d9c9c693876e5f9cf7cc807051bb4d4c21b53bd5da722bf7f58a36b964d03786e4f165e04faf96b68a74e0f09a628f297cd9c348275c32d1dfe014aedbd76714faef4d670835d13d71bc14a868a5249d5c94c8f9a698afdca73b3a0b8808c4ed165f9a5552a9774963112457136bc5be16f2c70510dacccbe8a8dae3b917518f6af4a772d573e18b947a8af60a601823eb85ca0d83d429d7ef7cd186b6f555203a9729c067e422178ac293c12158962c6c03842f021ac12960562090d1930e50943f6b202ede345de177ed4ed3b36f485cc931ca5eb7bb0efe1be5bfa857a41381c089f7c31ab085328e55aaff6455cd76bdf74cec0ad2a706c47222ff2aaa7de08e034f88fdb6c9265b4a29a59452060f064f05e8056fe9929f4ff23cb951a8526506cdb77ea32d50a8e2350d30dfd61da62ebc4e6eb2a432f6d8715dc1376b49e5eba796c7d66bcfadaed88e55d08d22ac42d349a0c736271980c342bbea6cad5334dfac3fef37eb37fdeb60db21ea53fca9befb767e4c807dfa1011ccc62113037aede8fa0875db05e091c8f49f3a6680e9c8fa10127c18e2c310128c48301f307d82286c1c8eeed8b4b3761405c2911d7958a2322a53b59487274e4960c4a06f735d8ad7a4d2086b243e3b0ce86bff94e8db98f08baaa331e85f578200d37ba309f37712d276eb49d5f1f873c276eb2e36055d40a03abee3766fbe0c76bbddf62bc6a027d0d7fee3f9f5f99e6f9f3fde68c2f6ebb5ea288c18291ee79194c18cbf30e6d66ef7495777dd5eaf1e23e98e3f4a736b0783578192086b6b558cf17d0d773a9fafa3752c924915063d12104186e973249201a62318d62740ffdb19323d86b5adb5adddee2b1d79b0c9a0153fcd39270913ac3e4511767392f0ed38518281528abf5efdc784f9b583bd734ad5ed18e38e247debdfce6ece4942f50e6e9500d46f55013c6ab09b9384e954ed2f65e1ef18c3c4d7516b521b7c2c4c9f63d57427a4d2d26eb77bd2a25d8f419fc4fea3a4a9ff589faf02d3579faf441f8fa41d2140abbd38574defaedbbba1edbb51706e551d7a77ea2675e10d476ce709c9b945e2276047d269cfdcc20ea356af7834c13a75183a9f9f801d61d09fec30ee6e97f4934713b05787f143fdb6dbed7e97f809e01146dded766fc71feab8dd6ef7e16eb7fb3a9223d8ed76afa34ffed011c9d365afab4f479d12fd3b924c33fec77a0c134f3da9baf730ee6eb75b7bdd8f1d91ee6e8e2aa04e794cb79a3e7d822e9474da6eb77b3a6a65ea79a14a7053880b7bc2f0525532bc33be513c69664aa03ca7f9d9c0565a6b6dc38dbe23377e7829a648c1a1e6879427301a3b8ae8e0338315192bb12d3562778ee7799e1785a40a4d1db4a2744520ea4bc8961874e4889385871a58a21c3132b342738351394dd92f1b5821f6c2b2318354a13903121c7632c49adb9c1ee004e3032d8d0b61d6c4a0e5c0522bd346cc6c5d34ba72be72846c25192a6709c207f69ef09495f6d219e79c73c6b203c9e76c85735416d09c78a05f727659da861cb6160491fa9a210c980db46e121b1ee440eed54a6b1cd3ca87d5102bac2dc46bba57ceb060396d3569af1abb26298a95f31496016d870796730e2ffbb53515f62009db4c59e51ea2c072ce5987a5b185c5c985fc14e2c29e708642e4d01e58df7703dfd05c36ec9448648e95225934cc98c1e2a4c79490106c8cf1d8820108a719dcbc91e2c3cd072288e3a508950f1fd28cdca0851b865c2a62419a3cc881dca93cc881bccae9172cb2b04748539c1c3abc6e13d61286858700a283e389d5cd063872ce19890ecfcb996a4e7d4d894d8d4d954d9d4da5e170ca0ab03a6269c8c25b6bad776455a1b9c585e5c50eb01c84f7de7bef1d984ae692cab173e50712ec39799003b9df9cd6749855c656396e6c9db5d65aeb2f3055689299e10c7ca179d72d87f94e799ee7a1d1aa42334ad797d818129ab29c6f50ac0de5fb0a2cecd13da692d868c0f2c2ce9a929c73b6a17b6435e51db308980f6b15e2c29e30d4509e3170669f51f3a4425cd813864fb58a191f39e79c438be79c330e1daad0d48ae2bdc0d1e520543583f2eaaaf185c52c3b612a32990a4f210085302d64de10b1b1a6254d142968b059ce535e235a588295c52a889919179048299b52662fa4d0bc6c28d953daa3d20526c4ca856bb5ad9688588098f51045446dcdeac992303435ed2b2aecb1c156b69293af88e11757c6c23651c07ec0e94aeb41447b29e1f182f274b5d65aefc0aad004436676811d5a0ec218638cb1a8d010c5a13144091324374c512f4faed8b8e0799e6725aa0a4d1d3e866029c9ca19b0b2b51583b132ca0e0d8d0894a3acaf7cc656b87a54b3a979430e0f7220c7e15b67adb5d63e7455a1f9855ff820860b71614f185a5d1e39e73cc40acd9d33946faf365f61cf5c43c32c076160a69cb9b141a8a992b3508554716ab86b1ae7d9b5eb8aead2f1f365ea862e555e806839d124376429bb6d74641bb6daf03ccff3920ca942134b49d6169705926091e2f9408b0c8e140e4da684d1209f3d0ce41d75f5ef08f28497f0eff2b8bf8bb3847ff4e6e23cba5f0924eb493dbe9d82483dbe470bbe5081e98d88befd13fd8ea4ad033109e7b89194f1382fe15714a3fc48bc263cca8f4437218e4b23584186fbac215c17dde623d82b562775fc7a093149c9fe2091257cc745d1667331898fff884a1d8ce4895157bfc7af9b709b97487ee867a965f7624fdfc7c7250fdcdd472499dec74571e90317dd821d9081c293f8b8530a177d042b8a4f7ee45e41f71e9164fa1eff7c4423a2bbb8820c0f5c1c771eb8e8e2e89404b799dec5a524db93e0760a1fbf8e134d8ce4f5126f93d1236adf587cf2230fc62437e711939464bc55a2494936075d474c02475dd55510c484369f7f9de66c5e08a4aede4692c9366d22dee24cb28ee9cbc84cb8aad3e6b3bba7064d1ffa219c4fde7e7efed56fd4d5cf9f4f1029467decd99132fe3ce72ae2cff3a8ab3bfee9781ec9251df1c98f76becc32bd9e5d0b923ff1b3fcd2f2dffc6c37193b62f6273f5a41860374c61de83ba3d3ac21507ff2a31d9f479febaaf6efbbe988f3288b641593643d2e25a5907dfb1e5d909d8e3094ac2efca414b26bd7a30bb2ef11468cf0a92e7c3a82a54bffadade9eacf1a34f7d6596bd1a2e0a6aec1f036ab8a15412b705668c807ea4655450e22fa6e5a67d7d1c93fd6ab1d9a12e2b04854837c070d4109f5a894e2b5ef3197dd8eb80d4d09adcd37ef8f7e2ac8a3cd75f5ef6d86b7f91fcfed5655648cb348a9125dfbc93e9f29b7b5bbd8a31e993e8be4d267d72299fdf6e59b8bebd86d48b7ce4db36f6dc2fc3cdee8cf1984abcfb086b5d65a6b69a5420ce5a0eabed65df53beae8db71522f59ca1e849f6596acf7448d5d5749ecd5f55d7a6facd61393741583fe2dd9e6f644281156f0a044b0ad306b089e7b62f5db6f9deff883649dcaa84cbbd6b16fd4d5ab49fa4e3576edd397e8d2d31208aea75e7dfbd6d37c5bd259cf2fd0e9acdb3c46f53ccae0698ceab6915cca696f73bc455db535ebea6b9cd3da27282655a78e41a48ce9d674c4baaac017d709d66cc23c81aedda7a01e93aae7d18278d7a01f3cce5183f926d0f4eb79c612b02f62d4c7a3b52246ca4bd8b5679164fa3c7f89f41c8fba6ab361512242e8ddee0f21f6bb2f98cb9ec5189e5f18d975da2df644a5f95a544af176d4d5af172ca12255c7e30573d9b32761d7d5cfe3985d571ffb1ec9ea7be9eb579c382425ac0ebea83d385bebf883547dfcc17e6fd97707209ce59b45cf27b8f1c6f72798a95ff10af200f8595ea97aadb7f92dbd90bcf5ebdfce0ffd10da2add493afbd775f5c9eda0ef7182255ca4ebb691b479f5adab6ff36f484a9874c78b048a4950d8595b5b5bc339bb4dc4f7e623ec55242fb9f4365b92aeeaea83180841ffaa67723bde23597fbb57a3fc0824ef7f1ee547d701e0b8346b08365f9a3584ea37af3e82ad22769b98dd139564bc9d3f95c4a9777cd30066429bd5818b5e502e2d67470146418e0b6fd43eb2d38d58bfe3dd7fefbd3731d6628c2fa698e28bf1a423a65c18638c31c634bfd9b1d65a6b6dc5855f3dc6feea2754b7e35093903ecf4f33326d8ad0a8b1153c61da396647d2da91720de120fc7e9657a27e3a53a5b71cbe09e4d488d953aeb20a98a75c4fd1beb4a2e4c96dc5aca472f6d689e8eeff05e5fae9942ad13525a0a74e9d9e5ed15987b2c2595ad1a4a6547ebaf59b751c96b78ec2ab8a59768179eb406fb73c9559a4442d28012ae5afd73bd67a6bf5112a9d32595c914e9945601d95f653b7930575ccc23781b8688b4a43389d1a992c54a04e4f8dcca2e92c54a05366d1747a5576ed9e725d3de59a2c5498b3684af93b2a5d9172cda239d4249ccfc34565b2983f47af348474050a55b24c21aa02852a5c66d07cb2443f4b32459e0ca7e0286e4d787f966488fc0b7808cdfd2213e47159645ad0f2659f40b4524b2bad14db4aebc5bbde6a2badd4d24a2bfd284833ddd4a3975e5ae9a596567aa9a5955a5a69a5d8565a2fdef5aee096c5695500e66c0ac0dc5cc19caf259c787bcbb9bd53800230972911270073db04604e8f71a19edb24a802fded2498b3360198f35a94a08e718d4004604ed7b1253c2200735e084ebc70020463521f70d3e38131617a3ae0c5c381314e466c80c4cf72cc11129ea701132898f8598eb132918131347830b07381bdd366a72ab7b0402de1a54405c05c1633a5870245ec1056e5cd981a54da1ccd29b4ab5e7c5759c504ae18279f04c05cae62b04a44404c90db096272801e04c09cb63c7378906e6b6e1e3601cc691f311f17602ed7305478aa4ac881396db5741e400298cb340c92f0280c91efc3f4b079de083a57228491d2a9724003be2b068039af0518c209ab588039afc502324f199e2a05b0b02bb6f7c5236d2ac09ca76d08604e7bde170f84045c30595870002330a72f98ad112c30402d401198cb3e477c88866e7845000d064810cf95e709008c140fa90390d33784131e01c008e6740827bc126d0827243da7c9f4785ed61f8039ed0198cb148c44f103e6b415a2cab6b967f34e8039af0af1e46301e8f916e96b1325c09cae42e0f020096a16b807869e9e1c2484d8f1d8353b79841d03c32f60b71d9d9fe5172a3a399d45ca93c57305f2fc2cbf64f1dc2e7804ac9a5305b6d9b6ed67f98588cdfba263c4fe3263a785ae56cd56a9c837a3e52b15f866b47c47941961c190a0ae6739fc4892033956a094399a5368d35ba5955a5a6da5957e14a4996eead14b2fadf4524b2bbdb4e8467b80436442ec57af303ac28e9b2cf06ba2a220be7a5151d50f2222fdb3f4d2f57a0f81391d6a855a679de990171bb6b641606e5b2f572ee8bda900c0dca640800ca06616f20bdad620fbd9f8de3aad12a2ebd7165e6ab8c06f9de2aa21604ed32d04e6b4cf522c2dbff53a03055dd41643d54bf3be14b616f583efbbddbecf368bf22cca0e7af0ad9847da5180df9a2fff5c151de4b107abe63ec102b7efd58fbdb5ceb3487b9e45791669c7b328cfa2ec9e0952bf76b7534509d0451e730394313f67d0fde1d303e6b405261b1cb85c59d2a6c6d8d9d19c42db00604eff6774805b058180b9ed731655d0057d17d6d6eab9972fb5950073b69c776f60b75b09f8a9d7122d7c6ac8155ef580b94d02cce9902be41971afc29cb61f0f5fd501c19caea012f08ade28a8048401cccda2ecf7bb4734053df75e679db5cf599447bbb5beaab3287b9d457a1665a7b328fb9c457ab4d9abb61f90efa330dcc03c6e16dd3cfecf52ccc757c73f4bb12a1a0474819f7a16e70b600e8435e1ed672906f533df9eba531f6bbe17d605eda12092756c2faeb859441d378ba6634ad388cd781223e769ff2cc3da8461664d62c29cc48499691213d6252c882c61c2a4a6be84b130f5258c888d2f6147a4bedc301e5e07a91b0bb69f651891c79561476a7801ea92c5a52bec8bd70416c407e6a50bd7ee82a509acece2e6757e965dca3ce946f889cb962d624358b0167bbc25eda98aa5a23df8597249c24506babf608489ddb834e9b02a3a5c747e965c5830c285446e7b5c452969e28252a48b930b45ea7a9003b933c1ee41976aa77af47ba9950b632ebca1f1264d7339a27180727707b23f4b2e3c9e74231c85b9e8a0c1858a06d63f4b2e505a3603669415fba8dc5256966a4358f0254be811e40895314ca444d16052394790148c703831c61ed2788c4bb0a7c74d47f88e55762ec1d6bcf7b30423b36686accc6d8354e0b2f9da7bed9b28cbc6a986383acb9e565c7aefed962599bf77a82b9ca11df3cecfb22cdff6b32cbb7096212a5f52004291b5660c884a0a463e346839928291d84d6bbf9e97b35d6d133ded83745d7b76d6a09bf6fc529c8fe3dcc7e636dbbdf4ba09b789267c489be3fc2ee96a7d1f138e73138e53e1a3a5ebe95f2d5baec7b7b82392f7775c4724975ec74391a42a489453412848823ce15e0e020c10609ff6db7bb494bdcd7b2ee882e9716e13afcf6bc9d0b7dbdc8adfc5b93b6e0cd96755412fd5d9b75e7dee7105ed19037a9c56c80a51eab75e2919ec2d3390d82d631f080a7806ec50ef06fa8e488fbe0956a082db77443aadd075901e55318b7e459be79b45ed37f18a3de3d4592c5b763e2470bf7c73c6b85c732ac0569a53bbd3715352ae613d39c5f0b55bc7dbbf4058fdbb4048ddfaa66ef6d55c830098945d19fb414a199b1c46787864d4a2e6b8409416465504d60f31305c3655a9e6e3ef258a23809fa5da1335264ee4c08173f3195182e7629c091d520f4ee40e7676b5811fa4f8f974ac93f0504c5b7b6830a76a3787aac6d69c1d95cba91116e1b4d3ce9aae579a9a1a358d0cca2b2dcceb428b2a02088e0808341f3888aecc1e7eccc131258e952c376c1c695d706940e0f8a8d1dc1ac104fc00132210e44035688407a469a5657d0e923c693425f4668892064395b42a1fa8421ca449ed90e694b5c624ce12375c663a1041c31054c2d42096c3161c80e8c1ca13ae1bf8a0c4890d9868e98065c30d34fc10e222368478089220497c34b1dad2c1c3d4912828729ca4c8d975a53d6944e4240edacf6aadb571cede5a3b9f9c5b2b6e1dbd1de28c11051152a15cf587195fa3ccf8ea1fbe5dbf275010c484d6ad6b3047b30e9b3c59648c9da4d41bafabc0f4d7317d4ae10c29e3383db9b3d0b6beda6aadcef4aa6489b2d8fc6d60cedb1ef69c8e6412f6ebf9d314c479dcb388fabcde2c7a018b152b40568cea0aac2535d850264c0d607b6ac43d6dbd600398dd0b6bcedce00104b5e3e7aa488d16d09c9419b3c58685001421bcdcdc20062e3548dd70e81143181c9c68b88216150000881b6ac7d5e981c90e438c64e192b59f7a44e07186eccc17133332ec1aba0612235fa4685185d94e24ce3491e18c5a94263a84d84d3ce8931f3eb2667003856ac8a6915d20a64717acb118ca7cd93376806ca95135264a8d72ef78ec10e04401b91a0325e4c99a138979099b51840d5f566a1e600992034c4790afb1393c3e48116707355b7478128395b41461b45a984104d85a0c7276a000f2c4860f1d48ba1479128707cfd65690355a3b9098ed008747070d086288ac356072680305ad040a2e4dc2d8a80147899c227cde802468c264cc4a0e6590b81971f6a19064ca922142e03809d2c684561a1498304b98e4c06687b766876d6681c34406316084f8f2658699587821082a24438ad090a5e644106b80f08222c30d333748c3234cd3c30b674d5ac072068d0e9117998eb4af1b67dea8e932a22b4195cbd75a271065b26d88737c31c645583dbe3d38b28a7111548f2fc618bbc9222a52c4eebe41b3d68d9bc73f4b37664f863e2767a218c29c513833f33c22ced0c0d24d0d1c4432e833448c8800c10043d566ce72d04424c428c4b90715a0c9bd2900110539d33ad3218a5e7b56c313d190eb40672c9cdd78d28d6adc13fe555ab3ac01332b239a5aa39bf4106bc8f29a34529b524aa9d2b54e73e02caa3e8baa4fb1cdee6910ce928dd9d7323f59cc59e267c926cbe38a588326c566061b29363b9ed8f48022b2c126091b18b2d7b7066dc6962ad9ecd03f4b363d2e9ee19b75d61e280be41ac1b71cbe9f659b27f98631c6e2cc5d2bb6016b436593f859b621a36de46c9d1fa4fdd6ebdb919cc1dfa68fa8ca66d1c5b4379e9f659a1920ee6799a64a47fc59a639b293a6eb71651a227c843f4b35509ee7d93e355b267e966ab04010d4d9517306809fa51a2c5fcf8d8afeb334737adc8d8a297e966633f4dc48902041a2c7a73413c34d16277e96663e3cce6ff3fbdebe1d4915be7a6abe59777e6e21b3f138141f02ba6e10451901114519a1f1a25243d3c44a0d8d112b35ff59a2817af0675916a70c08b5322a6a6550d4ca70a05686835a190d6a65576a287e966536ca769ad2595429d2dded763b8cabcd741a00dfb7f43f7cbb31ea67a46b2bb53adaf588ddd65a9f1819ed8c88661035116146040d22a7b5126564b839738ce600cda13866bec8ccc0b9beefbdf7cd073fcb3354fefe2ccffcc0e5242fc1676a88ba6ece5c455937e31918a2dc9c6921cacd191e4e6e7269268d99312b381c1aae88331ceeececccec86eb41650ee4d8882dc847960324ab01e776ef55632b23d3579aeebd60ca64b995f172a30c9781722b73a5ac24f3fafcb3249bf2a41b8140d839555427edb573ce592b8cfabafab5c5bdf5ad935557bf7eb55043656988208208a3216ae08166082238d00c71e3bf9fe5103b3321ca6895917143e68ccc1632568e944132c496d9c5f7de2f7fc9b4fede92cc87bf40d59691d1e17702949ba8c4eb9a2a6a1022c50810000000c3170000200c0608244194244110835cf8011400095a924e543228a10444a130140c8581280a621808821004621004a3108603599e4379006f08f625a52f948835d9a91cf4e87e805b4d6f4df601d52d4ec33bcf3d2d3cb2ef4fa9b91f13e453a7286cfd7e8fdac537da566343a7491b1cc373e4b609d88932d64caf92684f9c1034e1d0a249b244afb01cd0faaf5e890543d89466a24a107bf452882f2f7655c21e63184f5a1211b5b7854fde71975188f7dfe82e7626de93b73ab7205a3e88db850b9ed2700fe052e8227d43892d0f9e0a890df5a8292dfcdd9c319a368becad0b221fe3d33c420a543c767b20ddfe5c764e092255a42feed35312f4bf3144d892cb770951a74d32648585e0da50e8ab43760ca2e7ce409b4da4706f4b8b06731862c939f45b5a5aa71b3e8867c6f5ac641db00cd6ce4e1b222e12dfebf302f45ee3e2d9ac0cc6ad6d7d4ab823b4b06a5fdef9ad22c8adee66d72d36d8ad786c782661414b245898c148db51440d011450f010ee8398131faff942ec5208f7e5ef2d1ccc21a262a20cb37a500841a3031097ad9f31483a2882124a7d18101198160045036a53e505710e7a3168e594c5dd7f64d1398afcdb0fdc23f5ba786357702997c9c5ddcd6d5dcffdded81ddeca7bbaf9bbbd6df7bfdf1bbb83b7724f377f376eeb7ef77b63eef0566e626a3f0e16c337fbaf1ffa39f18cee60f6c5246bbd2b22f01f85bfecebee5079eafebd571fff9aad298321a14848d670ec65890022d8cf7d4f62be8c0b3618661945fbb2a405c71d2c938fe25d95ba286e9d4f2331374906c68f5be2daa789e8528d054e053941107c14d5c678e05c3ead5a18ec07b3ec040230766313faa8069b20c3fa51d5e9eb27931cdba7796860e2f58cbbe8ecf560470f7310e3cdeb508a3d87de9a87aeca0e102834ab3e11b9296e0a3a9b298010e095f65f4ba53c2c2cf8600b69d2458dc159b07a96aba364a935308e1d0268d2e6f9bf189eb43cb4d61926a1c0073c3823cb6b7d376abd199cb3532de9876240c4c7401d9f8193c9b93ddd927f3807637c0cb07eb8e2639ddfd99c5bd22df986b29663626a20a30ec9ce7826e278953010f7f2300f5556f619473fcf97638470f9d64532c1b2e142876991e660667e5105037cfacab5670b835c418e009bd1fea3aadbc60b0118995b0b90fcd06d804e1eaa2005c4baa5e0ec0a7ea697e289f063f44252ae61e92d5069a44c3f3c0f2c192f8fa8edb0c10b20be72a73b5ef8c765af2c9941fc0a8db7d2d321d8214fe2c548ba653ec3cf7c25dba3ce1155eb54748f790bec9e87d2d11f41e83414c1a35d1a17c99e47ac3bac0a1b3757a0258a3aad9b1f0551b16ddf8498b0eb2715daee53d45e4772fb3dd9fee4368de2ea00fd45bf955bd2b4dca24bbd71975b113df686187fad43635358bf0fe976cb36f3cf86a3de28fc03cc35f141da38c9e6450a1f4081578f3f04301d68b7d5a2800960bb15471668714417bdb2039d7b2d2789f11ef588a2381ec7f122d7eebb1c3157087cf1e884ef48f14b7a032a4e607fb04706330273e0dabb033e68fa6725ebf0962426a4d04df6f55eab0db04f1c406dfefe849aba0c61ea22ffb307c79d75940ece2f8cea004b9826b80a72533d1a795a3244c3fcfe326120233d650a87a3b4900d8ee18e7b59a8f7ad35e513acafcdf96cc1a5283cea440c46a326f85bc4d489b05d0de1db35b54b76e4b31bc76b159c3f7ff51e1660e9e326cfea4e88e5e376b2152df47b7386967e694e57f1775a680f416f8bb63f83ae6219b423a1f5739e91e0f645fb4b84a2fba1039459fea0e70a618044bd52f66be2a7c6b2e2f8b9adf6b2a3fe8a3f2f3a7ed93d59eb3fa9eb137a246f69f18415f05ff76397fecb955367a248a54519099e6bbd81a0b21fa5270b0b85ff6e7d993e6f75b7f67aabd8f9ed41485f237278ade530d445bff18c75b85ff7fc2f9de27be68f78e665fae71601be0dccdb7ee91117f18fc46ee142690239f18177ad42eb53847210a930bff3a0d56d72547cd9fc03a30dbfb49dabf82fc7bed842ea0f92e9d0f90d41b320c2e329d4690cf682e8ae47866d63c0c00bcb07237ebe1a216ed20cd2e9570d3efa86ff968e2f962fa923e2b15671157ab70c8fdbaf07bb229e33c2d132922d035f529dfe292dfb8956c26c632bbc66fcb21e435e7409d3d27da04ed2aa043ab300a267fedcf7a96ee874bdf6ab1125ef22a33c8a36cdfaee5bc0305c94604e52f2a4da592a6a59c1f4ca2c583f0c87459c07c344b4a48de1dcfd77d0f08c464f42ae4e62002637cf83a0eccf104d644ec545c9582047d19b027738d00f2ed5510b93c27be604146f3d95e207f8dca8eed6cee43786779a57831fd86de207a99c683182ee11bb6e1acff97e521abdf913497f063ca2e616dddb6a2bfac013dc1a982ee576bf4565af01f4cefe7412c19ad9dd912fe737913110040c4de2d85ab80db42ccab34779d582a09e8522a70104c8d88d12751c020224d481dc7651f0bec65e745638835c539c06a90165d6def00526e1a641277fe3d17aad37c3229f345c2deee09107fd449ff066d17201a95e194cfcbed970359ecbd949f8a39a83fcebfc10b75d046603bbf8b83f6dade6e002116f63d82f71f9c3784abc1c3f2a252faeff43631d7818bfc486e057c117587713b4b5b11c6989e10ae4f565520ef49d5811721a5e2dc8033c90f31299b78b83721a3a39dff6dd94a441374635b66b3ca41fa23fc105447415c0e561e45bc2755ea714402b7e2e1bb4dd55a6866781b6dcf21d705221bc764621fdc95844111d660191010ae80eb3170c3a47aefae9de817d421010892f9af0af0086fe30884891fce2f9f14f4f8cea0edc00bc96bf4a3e32704131b6aba03055e1135c25687890a19cca4b8b63f20e7842048daa4534863b80e3d39a73c03d12f49e8f265262a256f36651730e8c4872ff782a1200097d92203e24e1156ff35937922443dfac89d4b6ec80164959c4373830417aaf5db1ad5df4b2561695b89b5dae157857c1f0b69635107800919e869959546152853edc074579282773d03043e8f9ae5d1e5f4163700e5441bb6fe4c9de9a663dd70d03e7b0915db4a6fa5c582c71db6b0a0948c4ab3b4bdf01d249a5cb5b38dec3da6f88c9e2186f95c3ad0425ffece3e04e380fcc166865285e67d20f4c6e4c4addf7c05fc1061f13841a2982ea494a8354d27827790d5589d111516ae393bd6087192577c8d57dc9969cb9825debdfdb12fe70b5134ea01b85b7f14ac0bdacc11adfefd5baa661de989898fc06e6592c00e29700e0b32ed9bde950fc166dda832ad7ba4331239a281e25a61337fd5feb61900e036470920aca9e2d67c2aad8cf4727f4e613dd3f02c7f802c19318df719d543fe6d3517ac181a6a347b4ebb2f9507367149ff3895d96740aedadbdbd2f44066c564a8036f731457f571b4c0fc706b476c29b772ab2aba1e8f62f9d43a716fc497b602f17cb4708555721f80ca5a46a001f17dde0fdb01c7b771e1f02d3cb73b833f601f8d5d108c43fc9479017dcf36f242a7c4ff8639b13585c9cff49e3d24fab2e47253d7f39cc9a57ad2546d5c53fc6f9516a2b14f1c31f832dee4196f1edc98d6402c949e3a1e7cee8bde2880bce3a582f209ff609c7f1e285ba2c60c6886b82d9b8558c6120451aeb7bc254adc7afa8ea87ac48a52f2b949173130f1eb32d208a10671872194d3cfd8d79fec66015f1ac3f384426c6396eb9eef3144e459c539b68e9fe79d8f0295c504e78d58a5e0377d513000b0d28dc92b67c765a906168544ce77bcdc3b5ccf06758c27c1641103c5b86f0f5483424d44cf614894a71e02801eb2fa757bbd5c0a661d40047c8752ad9087c3aa2e42ea5990d41a63efe3f40e15f44b2258e3adca0c7beb74b35873a70395eebb7b138ceef7e8c02f9e26536f1c4ee7234bf51034ce338f65c04fdc1c57f08f0a72a884b225e09833430f5d2565566edbf0f28f8d7a16097e31711f9859e7c9815c63f17e6f5e4c8b3ba1ffa74e64f602ce27fd408967b8d11264c8b0f283fbcaf3a4751981e45d2ede514808974f1599df6516e71ad2c8a0ede32a0350d1abdd6d238d537594124f384f6dc7bad8172ea026a376b5d4ce47b144f6b861249c80af9949c9807850ac769d6c7d57bb00960185c899fb79acd3dcd896756d5842fbd2937c437d3413d517f06d79df13d9d0a82a310577f76115cb7ce8f183a0488c29acfbb2270c5dbb873d63e651fc08a0a2ea50b9300430fa204f4d527c93bc13a78f8b181254304aec9919c15a1b27cf16e80fb954c301857b01e7f438effe7bd7d9f1d9b77dc9d3fe6ff58269d59e5df21f3bcf57f2cdbc4cc43bba61feaec9b6e5e360ecd3baec94f689ca7ff694bae5b1042127e91897e3842438bfb517b16cb48c9194b7d7785cd6354bc336d879323a2355751bf1d66eff21d7393752a040f2103d5635c1a8ae0c790ac8de30878b64ef22a5bbdc5fe652e9668de6c7744b258d21e2e7e9bcd199effd4858c2956fb182175e66c579809dc0b0abeac765d1b637818b6f4f32510c672220788b7a25b4e2bdd2e430f69d893881d4ca51d274e36514a3550436939f2a9c0bd90ec3c53fc63664a4c3038714a5ef0667cbf5b318b15ca28eca3c3c5cf206da3273f211cbf2e9f8dbf5bfc651ef5021145594a72cda05965ba0d5800692a8b220f104db55839af75c0ceaf16f4d499025f029762a8276271c3413a21ced80f784aed850c286ed166c69744c0bec04b0088202dd3f3cbe6a7ddab11bd9e4919eb08510d48d32638a3c97d01eccf7aff0d902d7935616a35392fd9ff1ff6c3d0b05bc362c63d466f0f087015abe01b838d07bb5ccda1a6c653a6d3d912098b8414c63a9bc263260a889259724a895f4623008181bee3bb060e97fb40e9a1c45f92876d28281e855352949800f2f41410638338aed668bff597401d05a4b7132d88b4494adea600b8e511fa8103bc62143fffc39ba50b69a32f1811fd4f05c3fb3f45e312e8aa374494acbdd5ebed202d3e5ad841e35568e081091c1ecca26a4fe73c74c644301117a3faf662b3dd8dd49055d05b1691958eef1a7cb3e95455d8712745743d274070b67654a0f242ba1e3c5b7d94333aeb1886256290e8b0383326fac8cf4d4f6855316046979c9c705c4f6d8f85dcae983952f8a5a37466220985a8db6c9c79c81308fb1603f8afa5f631d01bb6b1ed0910498fe369f44cfad48fa7338dbecfff4aacf5b4af05bb3ab2bd779c46c139175c39b00d03844580f37bbd0c3974170a42ff3a77781003b754ffcd2c21e82f7461f0522396e0d3592ed83ab75a7ddabd88ed66f08e5b48c6124963897a829c8792771e13b8ae9eb53f7d2d3b05188705678a5f4a432cb55a7674f5cf23d34994b377aea71cb9c5b570cdb2c60fd22103c79908875410eda70aa732f0b9f997feb18761c70c9362f0d8ba5294b580f534d3925f35b0e7d3d9a1250d7f05830894ef2e45d06db64355b1fe17b0a388429ad61e864c93b72a1ee94bb5c48c02941a578ef17a81c0c965375f333dce1d7af1b58a1899265dfd8d866268ed80ddb500f320475d38a06459d4879be976e8fda7f657846a8e822293dedb4e89a93c7a965e48e21b58df6d4f019480120112204db01082eadea8d3587279845f21e9fca1ff6ce0425adda94afe106f2b3089ef95e088eff329b3d69b1e68ae5c78836af78e2af38f7bf3cebbffc7ec138bb4cba69fb5fc3354438a394d721b5b2bb9218d98409ca79adeb7a8ad271abdc95e036a34de06410d5187e480ef7719a17902fdcc970e27903b58d10171efdb31067d98f72605021ca9ba6fe8316d59588e410edc4e21a5fd58687113823b94f49b6f8e67d06391640dde45ece52890794aeef96927d9c6f826c584b880d8b25c0702b888558a8896dbad6135035c70c2826bdce0f8aaee34beba7df23a229628c9487c9b814fbb34384bc14e0f9d1a8b25519c376017ea0522201a9002ec2f0ba8b1d98903c7ce8387106e0485fc0bf9a25dc0c3028ee35e65d53e7a0348892393e599761292b3b4589d231998ce01d7d70813711dd4e8543bdd9c631aa84d51462dd4c85dece81a6baf4791c0f0c74fd7673827b6e8068f4313b96f4006b7061fbd7e7bba679b3c6cf1a3984d9d318b2faeadc4db92dbda621a0f8fec3b8887fb4332ab5dc64aac7d0e9521cd3a3370c6867b3cc5846a21a1649a76734af2ba22a2852ce15b0ab53b6022d3f6f5c2c5d52f6d20299302e737ab6aa4f788b785c19fdf6284ab9bdec3d55ca22c9039e83a809b2cae9ca098013dbf489fc3b7e8a3fd68ad92a2a47ba26e478d49f9c55c0427a9e2bc970e8c80465b30ae794be41c338289ab6102a4d108059e03500fc88a65b5307f4efff528134fc3bf3ed0b3f6dda14faa9a5218e194d6d2beeef229795c8534313b9be8a29e56f3d0363cef8814a31a13b873615f56cc4c4ca0cca8013ea56a1632e52de396aa9dab565b15538221aeabdd3094e22f9554fb6d84e3e2d39fbefd30adc835b8d0b9b7b96ac0ae194b4e2612a466f1078bcc59e50bf17330882382284735daf404e55a5e710cb2f92e8e21269efacbfc9171798ed1a39ad5bc23a59613ede5be283750d64b3eb62cc2b49e8201a62a2a3f255668d4f75585913144c832becdccf64a1dba7494d94716e6f695e73185f0529457e7c967493cfa6fb365c50df115c33aef0eee52c07ffe50384a208a9662347cce083e28c48bd4ba36a9f185f4ef91a3bd85fbf97b6e1c96b10f9f59ce03468035899bd61f9753503a4d74105935ea8c2cbd206ddb97d5a1fc1f4d4558041f427a0c8219aef47a1e05109dceb79e3eccf4090d4520f599ed31346247c5aee8e09fcfdf8b4d84cbce98bf32def5a5c2c455c672ce30c1f31f2d3fdc21e5be3bd85555052d991f125e4509826ab9ff190ed51c7b959d636456f20111a569fa29075d44c58df552227a5bc2815abbca36f535b5ef380d14ac95826688542774e5b7bdd7f8675e6a0e4e9ba85368164a4f508023811c835df08c85fdf908c4f5f5c099ea6225c7a1380914a33c65c5287e392993afc3eab4a91baf82c4947ce631d09648998f534f2d2780d4f4e8b2464cdabdc7a5ad5995735a44b080ab6b82ddab0f4f36466665929a9ff34565ff06159becd0f696d7ac246c750e36d7a6f7854b9df58ca295dad97a2066af1ca4c706b341b8cd5d90c5c5dd3f85042707b66e47405c19ec9a37a7e9fce015207a761c932f11cc6aad441341f29dd6b6833d490d4c6437cd36947fabf40cf55ca5888498d774033adab5d92e766807896948cb1dd4029d3278370a24e8e28c8884117195254238d531f6ddf04d634f8239745707938c581eb95658582266563928be82482a1f22c86c4994a14c3c12a01d70d827703e090b0caabe13f34cee6e1bf6ce3747ed3835589e7b1a0e4d0207e3bf03baee1a4f88b1cd9b9f229ba0f47be70e85258a6759d36da31e27c1f017480295217f2a5f6af7b026246696c99a7f8bf49122ceaed3c258514ee00fba085f6a2bee10d69f2574c27be352b5389b1f7e1232c055d8fc5ecba40e36785dba784f59696f39dfaeb18fe6e738f2896b3849a3122cf36292fb9ffa108ee0a140b0a5ed177c06939de4ef1db3b6708707d16b094ef7ec3059a454d8d443da37d1ab0fee2bf4c0cd2c31cc6a667840df26d690dc65c78e14d956605ba29beef803b87e2bd550e101b0706cacf77832b56674d5bc429d5b9fea85a919e12f74d64ab2851f54821b9f84789b187dafcd813a4d73f9bb7a869ae048d031eacb48bee13485b910d88268bf068d753ed9b9bf62d2d2bbe9229186e82b3a24a3aa10cdf4045484ba4fd0b0c96119535c5835fbe17d821fa3e76e016d5a67755f9cf9d98010b50c95ebda505f2754c3ee2d7b38bc398dfa81e9d4b0dbb3a38c0cf2659db4ac786a216fabb604639ca7833e7d39011c742a12c7dee8cc14a3faf8b43d2a29a69c6bc3e1eeba9ac3469e4dc672b360dab155c84db6df08ef16b263f2ef264bc56092583431252e1bcd0c7e4b8b689535d6cfebadd0eb04998ab505d93e3c2713d4f74ba7fa12f429b674c3f3fd2b11369df79667840c09d0c53259edb1b9e99ed7c9da418b9ce3346b03b80d8d5ed68d36745c12339eb03bfa0f9f47581dcc67393091525429b08842a16d53c57211e606cfc9d2c64923932bb2560885f8ed8149b09ffc2837ac7f654ea49ea2a03fc457fa3c98e78cb4113763aa6cf85a6559f4842269fa96b19026a33e0a93f7fc4f42405eaae74f2f9b3de7582af42db3ad747c5ee6a72b28e36ad50bdb5ce98d2f9043b6e42de9954e15c8f4f1686ee2652273b9ec7527b800e4b2279dc242d4c653b7b8934d6bad3cb56fedfe5ff560f2328160d4ea4f30704026f3974125939392d595cc7624476ae9f66ee1997cf0d3e395e67d5baa43af7dbf596bb3380341314081a3d82a0726006ccc1f4c5ab85f70585d7eb402f2a13535776807fde8cec59141882cb347633c198cd31e6a60664c4e749f18443fcfcdbedd0e52c4d1276440c47d8ab9dfba245bce3681f83f552db95d4e9e95883d007032b2638ac1cc972e6a796f4852c5f3b69b7eaa6ebb26ca62b54bb181533fd6ee5ad331cdb711227589ad63b43254284f79fe478acdef813cd26b0f86b3b093325f2412e1bf68589c401080d833fb97113e741d0e427dda0af9721742e2ee89badd512d9effdb9cf8bbccc1db77103974f07ac4512ff07e39473de93d8593be55cdf78c88fc91575aca8e387dd5c46f152ea3fd14ca040e65e1d7e48e74716ec3a4df6ea7604b5dba0eabf15017cf221eb846a7e9545953906e3d868f366017f08f76d53dbdc550363c6ac6f57e19cb34d19acaa6e8adf510967a1ecfcfc00bfa2667a78103d3be1fccef7d0a61a66ebe4294c7563633f99eb50c38c4dee1f1fb9558e67478e072efbc257a454a716cb1fb7bb1b9905c2c55a9ce7e0f0110fd853793a1c8082feed4e65462e3b9e49eec511e0da82171b0baec5ec8073600c5abec0d3e1d482a71f73c42cfafa8f1dd0d63fa3ed483dc1b8a87698eee95e6b9323dbdb1f2bbde05f48d0d9c7e4e596e30da07422f458b3cdb8827b41d9c9d3089bd1cf1e9dcfa429c6dd5c2f8fe5f20d151761a73267e07ae3df15aa6ec951b0f65d69db94139fb9c76ac6ca00db0893dc880b8fe81e8e1f900787f90596aa97dd72acdcb00ef67438046e38096398c53349fb95a6919efac8d03d14c41b183205d5568cf86f7e36ad2ae17ebdd3abe6ddb00ab775047d3cc8807c21cff016025ff9f145ea3b2352e975cddd8bae00f08a0c123ec9cf2f31ac75f45d30ffcf9b5d65d7e7e338ff5f400e80b71ba45eda3f60be08d71c484c8268b26ebeef92ffb3baeccbbdbe0355bd5fab1220f29c08eaa40c96a8aeb53fcdf4ac84585dc372e95eb1e6060ab8c6730b4ec04d05690966679167e79862304a8fb717f4d05432a9ab6082f7d35e8646cdb3eb69a31bba7868870b7871ed44826d4fe400f1c5c43537f80173a49a1088f8abdbf171f16e41368d3dfbb8c7b1cce0a85232440e33dbae5f61fd7ddd17ebaafef2e5e06617880c0054e000b2030fbb5ba4a02073aca13eab276c1e874c376e2a2b76dc7d4923d6905b65245297165d41a55e95c30e623c0b2d8815b5c9986bcd801706f6b9261fa9b0d26c1ccdb8e283639600a9d283cc8e4bbc4b9564ef7df332bc4f9905797a26e05e5cd7af38fd655260c32ff83a26ef1746433d23dc61259ec835af1322767f06703f2e6548d858321e4ac4939f8c2c77bba97c91677b3607b231d0c774fcaeb9394dfdf96c9a899341421c90f4e14758c8a9a048d125aff20669fed7b0d474bd44caf74b52a336a671c493db06c7c92a8c1f379fbe2a8382bfac44d244fec0e84cc0d61a7c30798093b78c538158017f884aa3347092aa17ea04a0e3151fc40fa48543b01e00b85d8a71f6aefba7a299a49827fbe147f9ade3a0f052cec21a0b5de196599f4fda1f050e6633fbf267be3c63d997e12c00f9c84e322d22c6b94a226a31dff4823267a748cc81eb9ed25589685e524f629c5c69ea8a3fde99e60f68bc92739b5c6e76c6dc1adb3bbd7ecab4897e753241a8098f1c2b97dba8bdb0adca96f9ea5e608509c4d44dfcb60eb73f40254aa577c1bcec7eb270f9d07b4e5dde04b9cfb41df49120dac2efeddf431725c44bcabc887a98bc5d5bfb7e873e442bb1dee089a00b955970bcec608d2851637fa5d0c11c88b5727c11fd2becd04cbd493c666b77eef24949390b5df72ce0fadc30eccd3027f67a2363fecbd4285becabbba57688e361fc1822ab6d1046d67ec5c44ee785a1eeac309296fcfe39b33c275e7e502372bea67d7e4b8899a573198e0194ff835b19bdfc91ed42687dff87fcce6997338d735b1c019194e72f9efc8aa4a2141f45371790d2a6a4c9b5b9dfddd693f556cf28ebf758ea235268d712fd9a9f1ee0e7efbc51f7ea49e15ff3cc3a138c00ddd0559e6c9d93f649937516b4d630667a2fdc345c1bb1c1d218e5bb96f924271bfc2c359e6089f136c68330eec2d46c3afdea2ef18aaf9c35f5b12281635f88c8a6ba24b62dba60682027f826b3117365b3ac7b37da680a707a9f955f98fa417fdb1589e36a471c4b7c7f66f9cf73a619885618466a4db06782bacf095a564ee030959ea4a0c4e18f861e0a61c657c2afa8ee3a97b98e67bcca8bff16d76769cfddc16d37d163f74691e65e79b790ba4d3624cd1f3afa601b50cd72a52067077d31a24145638b9269ffae4ff9564d2388d3f63f917a95d3407850b3cc464737060a4bc6bd79937ce2498fc21a0f56963bcc95b34e578621b9246604a6eab683367bde78ecb4edb56aff1e3f11b71cc3d5bc75279babf9bf726c96ee469eb290793d075066e2142fe4d03c09f9e4da606077404f91b0382baebe1513a7c9e78fbdc9fe4d53bdd50ba2e3d8e2ad7039e61d23928247b29ed788c7fbbbf2748197c4dd173c5576c6443d630ba44f702429924ff2751666fe2d34e266eb3234fe3e9714c66aa2bfc16cd54bf02b827a6f16e41a67bb87c8a87ffe9233faaf8c79274eb1ba74fe85fbeb8c2619975e415e60df508bfe7e8c8f4cc26e34f0367aa1070505f414fd9897f828378f7993f380e0f7b2d672dd2fc5df4cf200607f63d5a4b0caf05a001a9932a53b00a8370802fa03830a95e4ce2578f304746103c5d9b605f0d6bc60793165b214dc65e507c31dee160af1f207ca541d98cc67776368152f50ca6fb84ab326283d00f220a93e7c931e615feff41bdc56f73a73be4ee12531407f25887fbe693b0a4b6aab279ee25858e139838c1ab0404cd9407597d45c833aefaa2771760946c16f1e3879a9b4f29dd8c95d8e713f11a3a49406bbd7d6d828e0bfc98bd62cf1d1bf8e36c4725fe19941dc9937e89e32f0cdba3b8946c7da54063260ed9df879c827c92a529796c412ab0c889999c50729d28f042e0112978e7811e2a5c42fffec9c68668d577d89fb78229fdd5d97238cb33259b33c700f8ca52a25209ec3e10c742618e219105eb9bb6735cf6f1fddbab1bfd0c8b05c6e9484ce5599a819779a6b81c386730a87e39ff12023df3427bb02522f80456bb8e2c7bb6d948b8810d8a80acba3a34ccfeff4e31bd1c2671b0e555abbe6b498deb0a0d7d07ea096fef8ce84e4986eb8b7004141642c85faae28193616c2fe7b76b9de48db86600b8981c5f6ef747df083382c2f47aef577cb4346547b7f43ac0f41a7372332f2f3234289daea64c95c769769895c7340c38596cc9f5e31bdb5526d9bb24917b5ce6d354c4186632a9d6299e0c8618b5a2fe36eec5e37f2801fd6d84e7ca462385a1ac58f486fc1c9d1832ebbbadc41a3fd0f82503eccf798388548c348210cf69f4dabcb6f3e70c332acdf7a5b54a022e98a81fadca535bc7440f4a6e2aa361d68e3cfc733f679cfb2120f07f63cdadf67f5bf748a71d19d664e555f76a3c7e4614c97555f232439625ab73c0d8feab4f534fa91354acc90dccfa886b3e71e3454a0ad183c0a486c219122dad0c7488d17c88f88f53d65ceab9834eaf8166a982539b69b8bb82bf0c3b30e9a2e61ca91202752277e207319a35691a4f347bf1d6fc56190a3e43cb59e447c41e2aaa2054d3fffcff323a42789e3f6218a4c4162bd1e574121b69294acb375c5da3b70b8b010bd8a629ae64054ed83d63802813bc8af9782146efca9565d7d32ef466cce972964afc1b6e1190708292ed48ba12085844f6b3a9d5a31b0b2641271378b5e5d216568db0e19c735932d15ddf7ec63e91a59de4e3797dcc628ddbbffa20af29563e64e78313e4f2656c6f57ac414730df464ff451e603e5bd2d2b8bc6ba62eb3b2e1e710f20faf2a0c232f4f4c2053dafdb14bd6e4fac6b212e81d23f510a4755e8dab7e8f543303a1494be1ed505b366f28eb0a93c476f02605e0ca082c928a91057112c11d02a602c7a37623cbd4d599da03434f1884fba8701b9323c642f0dcf983e39b5f682e014eca3ab06d1734aeb415a714dca15188dd88b473b5b06c985cc481f4e96ffc4d54f1774d75ffa9a7d58fa7bc53d5bc7fc4e465a9d8e105900ad074a46ad711521a3d4d5bf278acea9a887ef789d962b17d91dc082783d0314f67e4952dc5e0cd507af0d98cc92ad91f6e2d1b14b395c70f22494713ae3cb38ed9e6028e859bb379fddde512295d8220cd7d16968dc3d7db1e81f6575a7a63988839cca6ad3857f1198b2054c4a775f756d2c3fd60b922ab2b34fd3065cb487ca5b0f19a0ec198a45fb2b4b9efaf8dc82a260a741c148f22acb2aa7b6ef0909eed5f7292bdba20195ed81014e2582511b88c7c30b7a0334216555d712149fa73c7cf034a6363c3fe1e18687ccc22fa64160ea2165933a4a7a09033a2a4f71945ac18e70aa68be25493f37e19d9e2adbda64a9b3db6314c47461ef63c69d024d5d1c9bf5ed32a350ef6787b722bd483e131a1eee097f49508e45fc519b2c0780bc95f0caab77379de21097aeab9e2cfc5720f47fca642370e17de94b0830632b1ad3a28c98c76776a019d3708d0a6cf276777330385716cc014e04894fdf4c0b52cee7d30bda21a1dd22d4756847447b57975ad120205f30ccc1900a5a0b9b439e46428b168c2c05350ee1c4061e7c4de9284aaca9b0e2ad5f19fa3314c26ee93a79b560a91645ed7a6774a2af802865dcd6eb6ed4621f96ede841c6434803764c3bcd27cbf92ff7ea5b76d77a40246c274cd83f37d7f3ed70481a387e1fd6deb790004dea14dcb062f3f5975943ea801e12887f364a76a8a0ece689b2c1d589ad60fbf6b1e3105bcf3b111cbfa39f6b01af73e4cc51f2162631e79202452c805423938ff5c1685e26ba5e0882f6d9e1eff0c465a5f571f3a181a20dcaacb49078dfdb9bc9aaf63d25d46c9cc683c352e40d1e19a929bccdc4a6b5125d2a7929477d57756cbdc1e9a3e72655b5029aede61c395a29f566ee33bbe66b0217b04c2597e4cd0218b436abbbe6d024238d08e4cecda778d6e3e50a5754689d3bb4aa8668f1bfa0a101dcec9edbeb730c6dbc1e83264ab96b6ee75f7ca748c876f72e3bb75f053c879497bde09092dd653237a79515b4d856563482a13e969ccc9d37a8627d7227fa78a7f8a17939bf92574aede9e7658a80dae2439228f95b2e3e2d26e29583da6b80d3cdd3d23f025729a2322dcea557d531583ba9112dc0e061bda12a9dc07b420d6f7d9ed44e0f2eb8eef8fe34a96180e4ff7a3d51a365a2cdf8add5a7569f0e01a2398c1413d3bce43134b0a43db1dc09133ca6b7b4813b7bbe553aee80265f375a0e8b0219bc710255e9c52199b9de6547aa4da874aea2d5ed1552e3cd13102ec625861fd9c0d25c9b7908440c582613b4a061d8b19611b0b25eb7f1623a2584ee9f5a64fb9165047756f7edfdefb841b41844546f918e694e2c46b278f30801e370da56434239909ede42d6f5b61276759949c9f9ba347ac972de480de83d49d55b2c10749f41da528f1750508b263a956a5d3d3b35417881bd9044a97dbca8c956d3719e38f34a9767082a2eb3a299381ee90cb65485f76c76848bf3f47ba156fb9f9f2f0c869f163b54f84583fc84e74c1df5f0e5c708afaf324d24dc20ab3f8d0516f30df92f869c4a4100e318abba7540d5ca3bac2424cca56c053f0a5837810712f9462fa8ae765a5139f4005d0b460f318aabff72a651fd6b4700946804265eac12461b7703386c04fc3e4904925df87d8d7e35e9bebd192d8fc1c04854d81f9b94d4c3d2196e4498f85067fb1f97fe7030262991fbefd912909e6041cb7def18c7e8ae85aece40418ccee42cb1f1843327d60f1ff419316584a2a67771480e89334cbd1a6b33fafe419f10d55dc34c597003f2ba75043be6a725f83aed41ca91c8d709fe780145f4201a76a54bda0cbd22030350e89a3116da16a6a6ea7b763eee767206d057dd10216c39bc1a51dba4d007b23dbf2cd6325c8ad3aa82d6cae6a5287806447dc143273f500998b85400cc27b350c27c262e0e38f0a50033e5e4a9cc95587d6098fb1d2bcc104df3f1f5e8989c8dae1a48540532045e742987591e2bad48df05036d30f35ef3aee01c10d983dbeaa944ce5f3406a141ed59d5fcb1561397129c48323a965dcabd89127ee444b01ecc83aef5a2466dd2fdc4b59a5ca3abe50477f42bd881bb9abef562a7761f9eab54c04f6af33eeef4bf240d158b0488c4c19376ddad59c4c01e8d59bb022824539e7a00c67a8ebc97a3aa9f582c3ddfede0fb47db07f297a6340f0a8e9651bf416fd3ccabfc12f69ffa81ab0cb04817cb566ea7c6f2e98109d278cc6552492bbc8b6aa67b885e9f38694f970f7504cc35091f20a97ec324b8e912aa3af3d03701c0137826aac74c548dd55a05c75323a0f950efd6d29717d2ee7414725c5da0660791248fee222cd0b0c02dfb61597e0ee9e51b809586e01c1390c60564554f1b9d0f1ac86a74fa59834d0ccbf386a0cc6cf644fd97b4e795990e53a5cca75f7a86dff1ef33b8deb8be9290a7da49805921d3ad50db89d1a7b3ad1c7ae03e2ce9642f69993c144cd8e5965c9ada5c5360098dcb008d84bfb33e10002b73616e34cfc076ec826d8cc02879b42f6fc94e501723797dbb816627ad5c48f7e352794feda0cf77fc1c65e4dd6e4010efa061c09d56e7ed6e8e114676b50a14abbad14cf2a664127b9199d42c543e5ef4692b9ff5821da9f9b6891bc74880d07a0953d504ea3b5d27fcf6d062d1ecd58f495a60458a4061ee440dfb8713b34cb5065eb18392851c69c52420a5b0615ec80c0ad1e54b7297504273aae6a4c434b2b83ebca82f80870862959315e11292a5a8414c99d6b1bdbc7ae66ac0468a39ac90342357159307b1580b2883b6ff98b4e0caf6f8816f94e94a0390493629de113ee80cc56670866aba0ff70c43057b0921eaaa3a313cd090d8862b60ac688c16f2731051c1844eaa168335b6e884fbe093f1ddc0c707b714d750aa4909a91523a8cd9504a2e3a20c0bbffb753ba4212cbd3c3e0e0ebdb6a6be818a149e114c5fbaa9831ed830d80196a00c4be9ab8d4e2f3c75266288936cfd0b27fe1adfcf500f4d2cd736165e80b765598297a57f28ae75e54480c96d3b4a39206f9f900f993909c28697b553601a150d33fd921d36425483618e00d07b953ca7a5eec7b9e0cbf187b6be811a159d0d30bf94e3e03f209d3066dabc90a71c92ab37bf6c8b1726ebf32adaa58b8a9ec1ae9d3b0d5df3a504f2df55f72c2c4c7d24405b55f81430a339b74e40e4ba5adb599ac60b1934a785ddd2b7a8140896571ad23c2038464c8f087d4d3c81902a14b282695f3027ca72add8388c5d21b8257243d666eee44ddb1b36afb76c05361bbba9087ec58f3a9f5a993fe467d2b8fd674a66cf05264c09a52ed99342145c9615b3b6e2eb85a6e2ef96fbff37f0286fe2189325627e8d9338f4805d089edf52626d7be864964406fbd39ac2ba5e3125592a7a338d4a5808e9b3961de474701222ed554ad18bea31b4286b9d2f2f2a19e34f0f71ce6136cfa5849f05032660014660e830cd012dc1445ffe19ff4c158de1216496446ded1793c296578baebe6c4d2e07faf117cb0a52250589914f2da356311186b53bb6bc2a36f2e43cdbec8114489f9e1e128f0eaeed7b59a531fd45e91f423b7a26e3960ce64cdff14e48898f806366b1ffb6a00468e6095034103464d86b589ff2bca59d92467f245fdfc69b034df08ef5f56445abd9f50cbdc19e80f0e55c6dcfc6d19ac5bb598e5183ec69d7767f5666599eb295c1ac3bdf2e735d86f094955746592928aad4b2ef59cf815107248a3cb00d62141aaae8f9c3a84322e25f1802030ad5114f6b4c7f20ae4d70e1ca57b2725e2ea060e5a49fca82bad1a209e6508bbfe56607cd1ea35ceba58d02c45c036ab4ae8e5710400b398d01e149323e741ce94282775c1cbe664cd0cf78bc3d3e3038041707c220e96dc40f08e2196ce00e3a9c734bb965461c169cfc91795d7063c9ad56944c24484b530c129166c027bda4edbdf7de7b4b29a59401be0976083808ac9353cd9e9740ed11037f37087ed23e38a1f688859f341d4faa3d62e2ef1663e2276d76729eb1573e727cdea8f6b0f7d159a07a778e3c69201557b7b97570cbac0320ffb02ecb3f9975200bde9083fcf31d1dd6330e1c30182c76e3062c0683e1c071a29a33396e1c7ae7ac4c26dd9df31da24a61e10db7cf5d2e3dabcc759ac970bb7c5cb7d9e7f52a16b9ceba158b62076db893797ec5a29d83532c729da8c6ec9c7356ba52d9f890509c89e26c0bc5ccfa4c0c75e48c0306c3913fc5a29c43aa830bc3ccba8ed0d20c7e9073ce3860301c19fc3399e40330b3fe41fef9160efe65d669fe60b00ff67ddf7798e5e0cbac73907f7e7078457b9bb778f0d3e79c33ec200c070c8623e74c2679107a99750fb2d65cb805688bec9e58c4fd070cc5a22d003828166dffb1c71d2091615907aa1067d63bc0f63f99836fc9b1bacdb1caace7c85ae726a918ece02a1c6b0f1c27ed66c57cceb3a393734a4feb653528ca681f168002a954e32880cdadb3f278904e4ac18f7aad95ea8626996c1bda27cf99e78c757bbef29c2e9d3d653cf94746ea107aed613faeb85015ce5d69f5e34f0d975e6350e0ac330093a513b0b4e795bccc4f9a5417327016c648bffa9cb3e452250a971780a85d239aea140e58a26d57e2eec46927416c192597a62d658ce145470f863e51b1b0ac3d4b2c2eb858cec04e9b0cdc0e9d6a082f54e28b022c431688aa3d4b1cc850b5b09a325e61853eb488c81197d031b353f278d153e61003570e3678814eb0a8180d3e586cf070830b8e4d887360f297847e224727c50f7705076b3ad8ba0000f8464606101a1ee810e703271d51661b0c415c0d3bbe1c84b804a0fb22002ecc00c6f0d89e00c14c8f2a7c64f183734300170c6d6200c16414603bc30059d608c1298814224e866d032dcb01b61f10e079293242842d09214c8c5066081509e0b020c28502362f16805fc0806d8c226518d9c106475c20817bd28028231a1cb0617980c743095d906c4440e00813c444800909909980190aa0a9c01516d0e2026d30f045065ea0013cc606c8e0001a1de021ce094e1e8842c2705443920d870ff48002e60202292525588204d298149e4832130215a8508a60e362052f580869ee61043d8841821ed4e8818cae5645ca55f2a20564e28416319b3e5c4ac3e446dcb3d402a7052d3770e173d2c483a1e9b3c1c9d3f224e5c40828259e744f4091224a29650553c0a062c60b3eac11e504860f86186e90c1d352456a06303424514315ae89ce4a1457bc2a6cd8e2062fb0747b963f8081c30f64e4f0031a3a401967a9c5cd9ee50f37e8b2dc273b6c5378d8828593a187eb458b962a5f36a53ed8227eb8466ca94870b967800869a64fc6596e11b3c970d72d68367d1844b9e58a2e21cd5bd4d02e34080a04e542b7d01fa80f540bed81f24077a059a80e34078a03c5426fa036d02bd40aad81d24067a055a80c34060a038da22f502a740a9542a35028f40985a24ee81375a24db4097581b64099969828d5483002165610810a21484a0169891210a0f0812447a4074ee8000736a0810c60e00216a800052620810898000124253cc001460d20e18891220c588002882460c80842442842c001689b284808062800902102fcf0d103041e03104000847604cd747ce001908c7ae1e1059c75f686da4a3fc51e33d35d457a2a7a818728b168993f188b0ba071e88efa5b430a640b412eecb20a75c80debcc8bf16e3224aa219e43ddab8cdcce11118990606fc799c443ee3eda6e0f762156a1ee6e88bdd4aa30d64becee463bd61280d4623dafee7acc5927903a91e81eaaefe69c75d634f0efbd07f1ad9a5cdad75e9188266dfd148be6d0b5b4d2004e2a83fd5662a9b2377c70ac3db66bbf7dd364bd1e527f74496ae935d3e6c7062a6e6c90e2829b2b6b5ca141b7512d464bc616639581b5026705576220b45d79c10ee32fe3eaf1c5f89663b86779858b2cae5841668a1d5dae33ee4721acd4cba8ac041ab88ce5952c54289c999a2b51b212ae4071569e5869e25aa8426f996ddb368ee3b819a2700933581967e08267909a82c50c59f00c3e34c1a2099e5d51420d6f3a2c35c0296ba001951916698466694a19d29431b86056451a5f182a2aba1b64f8029732bc6046c5ce9e659518ca2a4d68b629a680e14dd40da228226c6226862e3786219a988941871007331886336270010733314811c312294881d099281bb80045d40c9e405146a9894243e687bc67f608c65946899965d4112fa0a1d2c5940b9a28f1829b5bbef0c54c89d59e651497282d4d4b44e5f0b4448e3dcb176cb0c73dcb17c0a863c488f91123342ba2a4c2460b9b4549c58a8dca1927a0d4134ebe259c7861ba22382fdb0f984a96a74bc586272a559ea85079a2d2c2940e0d318a31a24c61c113a9295c40494db1028a14b76739e54c9d22834ba3a9062e70cad2a40627dc7456baf8c270b20418181833c41a6160d0c50c42d4104319058fb809d700d54df9d24319a58c32ca9319988e8627700aa6e012b426103540e9f095529e3c990921658d2dee594ea161fb01bc1dbdb7c746ea78fa88fb778bf591fd7634bf4ca4de9b9b7ea9a3f489d9974a67523321b66a032f332f5e84bc04f113a5890da0b0186f9cbc3650d438e902a58aa72e50cc38e952425162df3d4b284868f9d12234fb014a540950ac64114b285252663ef4f024ea095409e504ca05a129a2c062b6eb02ca666bc289a7c56dc249699b70c255819b70e2546736a431830e0a0264bc18a18316694c896206275f6ca5131840cd6ed061fcf62c9d14b1c96719597b964e76d8e438db611cf72c9dc0d093c6c66620b15a40fdbcaec92c90fdb474d746210b641f8b19e14ec2a43560d2b8739ac802d96fd6dace02cd1891796d2fa5df178469f12908f668fede7655f6c5fd7473e380f6a5145cf66bcfd2ccd49c7a0b1313a918fd3c08f3415f8c731fdd237a3c841eeb9008fdd535b479fbbcdeb269265228cc4b5da1d8fcdcb121b7f4a1ae22117a1bda9c635b6246dbe79ea209760d18555aa06d5a69b440f3320b546f81de8c732bb92cd06c02aa8fc58c6c2761d236dd8049dbbee96981e683da4c9a1bdce68ad0951fa1d1f5cbf0b57736c30c66348c76cff22906fff9f62c9f58507d289fb4944e47ccaadcfb11cc0ed33483a17c9262877b964f4ccc62289fc438cd6098c9308bfa89aa325b69207419bef85219b93d4ba72f9bfc65253871f9716ab2f19e65d31a9bbcd0396e0665e4b8321568a229a149cd78abbce9a87432b8b965d3174db328a3d093262eb4000b16512e3811830b61a4b890830d38f62c5b88b3557b962dc8c00550dc38712a5b58d33473ead9b36ce189eded59b6204605b3a69f0a52f108f7edf786650b58c0883922617ba76bdd9ca25d1dadeb1ed3bad7365a775288a375277f4cd188a3795bb6dbbc853b087ebab644fbdee51aed4e8d7cba0947eb6a4e4b340e09f77a63f2aa71c47326db66d26efc4ae90de396b137af88771dee1b99549f54ad97c9ba79e0ec8d966ec62ca60a764ece324a28c7594c0fd2a9a9da23e73c94a3534aee47b0d336932fede9efdc3724d6078d7a3da6c91e982647f1ae1ead8647df05e05b4d0ec1c7b714883dd59eb524ec1bf73a1ed9f37c251609373ee5a2c6bf34d9b3d2a2ee71e9284bef82e915be4a14427fe469f122c523eef44bdbbb7af48ada8b21ceb2c1106fa1b5cff67787d9de7e3c6108c1675d27733959759ab7d71efb23aac7bb73a86bbd63fd870d7f58efc7eed9e48fa8f0944671e948a1ed79da1bb577f26e6f0c0a8414dab85672bae101c27888814f4feb022e3dbe97c9a4290b747b7ac6118ef72d098f1d9e4cc2638f5f9d14da5c7832a9b2b26bcee83b5148f8a35193747b07cf8945c21dbe262d816ebd3cb6f79516f51d8302093599c2d2f60e7e78db1d0b1b1f9cb4623c04dc9cbe63d01d9b5e72d17341c20b12511b5c81734271c76f1229081e6311c4182b31d97e1f625dfb8e7f2f88cfc4fbbde045503f714a4cbe5fcdf43d7ca89b9eb8dfc7eca1ba8334226bb311fc2f7bb9c9aac39f342e370942c3bf796efa096b16ee2d65f2d11a46100e73da16f62ca90ebfa1b3377416c71c42c281e3c61c42ba71c3c61c42b261c3670e21f9f8c4e610522c069b434830d86b0e21bd5eae39abef9943483d3d3c73088967ce26d2ce8ece1c42d299b339849493d39a4348ad166b0e21b190c639ab5fcd21a4d54a358790542a710e218962388790c2212410fce610d2f7797308c9f3ba39abdfcecd21248edbe610d2b6e1398484e76c22dd399b48d6d6398454e7acbeced91c42a273ceeab7d85b3d972c135a2f1990f6501adbb40c23a53c224b50963065182c5041584a1388d977cff2082c6ae89a103b6dca54bb9174ed490b9f73257c235a6dfb146b58d76ad3027137028fb7df1b8ddffe4d7d17f5953252bde36e24fe1ee37f376a1d1ffcb273f0463adf3e6a1ae57d3c4b8fbab675bac6b9f97eefc11b71bf1775cd5e4aa5299a9a94d178d55bbac6b4bdf51d4dbfe0e7e85a4bd754ba268affde690bd4a48cba83e7748dbbea2c4db35820fb51ff7ea56baa4fd76a9f91dab4405246dd435d0375add3a3127b0bc46481acb54016ce54944bc8cd97f11d1a8e66d9e074a3cb0da74734347c995a4bb4764d5c3f46a130b2ae9b6166ad79c86d7aa2d56eba6bb69bc8d24cf72b3709427b95814dc50e956fc38656a25fda75b777b0c87b2b9374b7be4cae003ba973925a391232b7f7bbc57b393956ef02e77e8864f4c76ea4e32f5d938addc675bc6643d79ad0383429fc987efdfb0d55dfe1519de7b121aa87573d3c19d3aebb32a94335c904c9e8f51b37fa5f52484636fefaf5d44a2f5d7bdd86a66a5e474b82ec7c09dd54c78680e739781eddfaf89dbc84b28680d7d1b24dc5a2159c39be79e99abd05b24216c88e87d16c82a6af9b60e33cda8c9306804f1accd69e4e833df65786caf1d89b6c5091083d08c66207ab88a4c9462cf69812130e0efb8d14f05eda266042f2d2359fbf0ecbb5251aec20169134bdcea483c360afacc4c4e730cde4f31bbfa19b9e72fcf598911c87fa39ec93168bdd464c3fe5386c72f0bbab26b98b26e8e72cd1a658046faa49d53d4d2f8e5a05bb49d93eaa43b62dfe836cc402d97b909158207ba06c010b642fcb2758207b00acb0c90e72931507fa29c7f57f72932034a51c9a05ad270dcc643da23ac96eee5527596ac58a15fa653fe5c75eb7b0db5328ebe3c6ed6fafcae48d5cbbf1a77c92aad9b406fb537eedf5a77c1baf0cf5bacf6119ea751bbf91a15e875dcc47cf50af4f0bf43c693e36b2a7c9240bd32a6c9bfe95a5ae5e1264537dd47d4e6daac6fa88b9b946d0a8b0ed7ab049109acf6d1c6bf0b0dff807e5a39570fce1b43e9e9a04a1e1b88f37e79c739ae08c09ae6cf086b73e9458a0d1fed833688791a46194582ce46e4f937649907dfd84e3361e821aa9eefa271c879df44e52355b3fe1f8eb9f263d9d6477d52a6cbbd35b7068166e5c93dde8d22a6ce44403b393898d87607da48fb6c07eb7bc8ef1abbe2ad3066f9e531b9659989985d7412a1a79e94e24616a4fb1c8c84b57a65d7596782a2c0a905ee602b3eb7dd4d8f53f3a8ed8f561ddc048bab6cb045e76bd8fcc882abb5ec79d4dc0596957e3f515895dbf6202d273f182a67a7963a5a0ae141a2c55c6568513d309f1c6abf20d21a682437c117e613262d75fcca22ab3fa0267acc222224ccb4d0e1167e858d929228c104f11687a8a28c38505cccb8912c08ad8f58f95e04c47c4aebf4f09c2a835f4e9421b3f6a0f72b442c75a6537ca6c602465bb04f366d7eba8a51729bbe6791e26b8d689ea14774e547b6253f43951c5b1e553b1e8a7a27ef7916635f9b32b185234c1449b4cfbf5a0a89174fdbcdee6f8eb53467afb23fbf0a7f6f8d1640e9d446853fd7ad594295f8b5a8d4d93aaacca6ed8c011e28073239f60238c18cce7f3d1590255460fcb6ebcfdfa2b1bf5f0b83a179a9e3c02cf199d9c1d6e47ebe49ccc1a5b5b4b68e4a1125778a563d31f51067ee10d35fd7c3aceb3455e38e293f786db8eb3cb41555e8461776b10942028425a64e0ac207532762fb08b19651735e6ec83336fc410420f82b5522b82dd5593b2a2dae96b6fc539a76d0a620d4ae9bd58b62ad65a8bf1ec02a68b178a8f29bddd5c24e8e244e57400679d73d24fd102f1660d1067765dc2da0fcc998228c38c2f3679d7ec3341a02008306a04a125082c4148d9358816bc33d000c128206e00820b07486a054a0a65a24db5246befbdae15949cc0711cd759dbdd7bb9241aa8b2eedafbce765df729165924dd08f75a6befbd5cc6c70aa8b28a801f171299cc474703fbfa24f1193b34ac74a3eb32a11f96c88e60c422b11a489254abb4494a558d098261245dbbf688e1df8df127ad8393e3f6bb37009cf4d931ee7773dc274d567bc4badfdd019de4b163deeff6bc4f9a071fa1f6887dbffbfb3e691c042a9fd5b1988f5c958d7cf46bf57195c4354e98365a693289cbba88f6bc975e3af2d49c12a441900983cc1764d69464d4b852261921c818b15d5546a689edaa32b1c85a7b2f1932656243ec26992ccdaff1043ece4beea990797baa91867c46363ed212da66e38b30db10d96a4fd28d978cd9b0bda4c3be22cc36334c2e3d41c6cbd681e76da6db6791abc95a4c84f94d54afb8648526175a60fad1bcd54b6a9bb47bda1e829c18a3904481c1519b4e61242b6c1c45a144d1a8bb46a98413a5182e3c6e18bbde314a3258ca27a22e1c7a4b409b435caaf0eb36a702d44981602057870c757e18bd93a110581098f1de2704a8d36a08f5aeeff8e0b66d0fa8174f42bda897b6973fef987ba7667b79d6183cc1b4b977f4c87e567104a23d750a74177520f8caf6ae6c9f936fde41b3b9c788dc73bf9af41e3b5704e3e9ba91bcf79c80b3b95b27dcecf017f69b7b6ef34a2ce285178570dfb99777deca4721197e7c4e0cf6b95d1deb5d8ec1fefa3c7830c788848f3d7c4cc7605a84a5adf31891d7592cfbed3ef73cefafac7357d6794f2655f7ce938f5a9fdc773419de7b8efef169e523eff9cb3fc2107bfa7b9753a0db1efcee0782ffe07c48df93b60dbaf9bc10b87692eb66701ba3c3fb262ea03b772b825095ddcfa9ee0a38c1dfed6e9be5acb55673f4cecae3c7be97ead4dfe625d4356773da30f15d749b4353cabbb5dafbf42ac5f976d786f0dde25ff6203123f93d29f528f5b890e46e3f02026a8feef63aacb5a30b88917e7a5d9559fb201fc6b9ef9c33341c131a384ab0eec2a9156a0f4ad636f7a9497b255c469a5465f5e1deb24e95e12b49f2a1da5565415323793457034ea2f10b51982f15e3cd23d41e56d7eef3642b1079199520203c3d39d11059fb638739d4138dcb5569d78dae5fb68530bef435c90f5a65ceea83881875aaac5231922ed797129cf48571962880736febb69dfb100410cd213a87e8a6dc11d7e31289454b689ceb537be053fced604eeede7ab77db1f16344ba6fd779eb5b26bb93db5597ecbed3d444191b0c37b1a8f52e8ff7597d3b9d7346ce59158b6c45c0779a5c0a7f445184a5cdba4fed71bbdf1355f137c75acff9d4f94efe740e5e47c75a8f11112fd2a0e60c00d9dd9e2727b3deca222c6dd6988f56faaa7c24ea4d35a867956ddbc34dc7865c2626bbbb734328d3eee8c6224c279898c02e2fa17190964c384c4c3ba9b5bfdba4707f4a7687b424c84edab687e43d692b6281dd1dc792209b8a0958d2c16972bbdd0e7ae208447b4e1de417525184edf76ee19cd15f1e73ce48d52e1a3267f44ce8924eed986e410f0c2d78ee734f8fbcf7265dfc39758380b43a317e155e0cedb55693dcf157994719e3eade571f3fc5d57857f7aeaeaa0b37f86111b8d77349b8d6a515b55c942870a3ea3ad5491d7cef05bf5d95799431aa1e923faaab4ab27baaf4e78c3eecf2b418d3d1b600ac32da755dd7755cd775f73ecf3b97bd9fcd6df73c6fdbb67b9bee964cf0cb28fce08cc24fd0970dcacf4f10f7feb85c5d5c231365302106136b9a687ae1327166cb6a8f0e0926c4f8a8341b54c558c97ba918708a609c251352f62ca7b26c01ec598e19635b4003dd2d0aaa629c5546492a72b4e6a02de3fded829a18f161468c6a8f4b373ebd32268c7d5f05382b7ee7bd83733749adb5f66a926a926e22916f7376ee3bc7b845b4cb0a1861eb98b36a64e69187919c6fc8a9a6ebc6f122b993562a543fef18368b9a33f6f72c91107382375b68cff20447ec316bf6077b964ba0d92092da63e277dd3924f748a2ba3c7af7529d1e46fa2934d74c09d41e36ebd833828cab7707df913e36fe23d341f270ed1177e1a827d7a9ee6a65ad95816098b9306fdbc3fbb56dde0eebc422efa0e775a0d779777361b661ae9b2a1106d9a031e3fdc765cfda5a67fd06054f2541bd7d77c73a3cdd778e431d34c448ca36d639c639cf39fe8f90475fb14887874727e11dde8a444810274e1c347bdcc93af9ee9cf0adf0ac1c7ecc2178fb2ed424f80d1cab6cb4e1528bda34c0ef344f3a3339dd6cfab3a9fdbe8368d22c99710eea2f1f71ef4e43f2d3a4d0feeaf45d587d8c3e58b2fa31cfbcaa3a1eea805f69f23b47bac60dea2fcf4d392d748500ed838c18c77b9ff60ad9b9432b1689a08eeb6777dbb98fe3b88dd3dd9220e3eebafcd34d9ab78d9261789ac9ef902e0972ebbd7462d5f689d0ed4de669f267671f8ed3a40edde8ddbe713f8fef62c662d1d6d11fe4ea0ebf1fd60785a25142412818e7c711bad1e5e2b237c5bbe81673901023495baff5ad30d41d9875aa6cc35e4ee2dadb7de80873733ae9120912274e9ced537bdc5d6b5556c7aeca683767219971aca2111c0d1262acf73e394c8ff8d1d212850c3f5c497eb424e19b8483b4586a6d9b5b83b4586a2bbddba7a236b80255d9674feba3155ec79539906afbed765b47df6da758f2d177269b66d2ee0da476bb1dbb23d0de6f47e03f91ac7583a326936ad781dbbdf86a122fd536ba6d14cb26f1bb7c64cf64df7c91d07724adb249bc7145c8baadcef656e049efdba6491a3541bd6992d3db266ef6dda59bce57c786d0df22f4f74828140572bf697b49d6712764fb15b7eeb2f4d86d611e3539d708e93eb1c803c13cf7ddc07cafddc11db72ee67095bd1359ba21494aaa6412ed3e7d74c9ac43a15020780d39d71cdd6fa7dfde515c414a528dbbe3cf7b2b4f0b744cce351b7f03bb7fc7502810aac9ef9eb7e1cd6df7debbb94d53287407be4ce892aa8e2640b3c34d4f8fb388b1b241d8b34cc24d125fb66ccf3209a89dc4d4067778fa338758dc75f66c82b067013a0ea60ff0cbf5f54862309705c1d0f33c2fcc47dfec3c2f0c5fa75f62f751f8af087da7c9babf8727aa96ce2cacb53f569aee8fb4567bfa2824bd935693176f4b35262b6cbc052c30e24c9318b65c41850aa2a8ae6c19d326aa0d531a4be8a06123031b19b059019b2deebd3b76c0dacc0e1a54e082cb1316fc0046091a3a2a90ec1a356bdc4cb1a9114cdc7174d924467aed9a3454054e382976c884146c9a9cac0973860b35d600b1c11cf6bc55765411da820b7c31be343466b0823245b0f1024a67868d102d3653556ce0650d2d35f0618935d6e852c0e6068e0d0f52ba30b808830ba61d3e44c1268d931d336cb1c69a1748b005182da0d831650b279aec88d2dab3dcc2cc1650ec90b203ca0e2818632c05638ca1f0ec596a41841634dc90051a38642186942cd66481e60325c66ffa510b5b4ab1e73f5862ac65137bfe0331e5127b7e0e81dc86b76ddbb66ddbb66d4bb3831b1a7cd12587285eca8889aa200c4e64740146142d5aa03853c375bcb8811733dabc81c1541764ca266219258506db0a153da8c1e50657b0a07b028b3328166840b14135c801c6932e6caca81203369d1358783143100d9ac61a039d2d60d8d1640cf72cd350b1c3290d133b9ed29c200d1120ad61677de0d1de90ce3bbbe9cd29ce1f0f4ca0fa273218b6d0aef5549f80c6483fc27d9d3540cc780e11374e614f7b2bb52a72697f01ebd7b4b36799064b53cf9e651a2718c400c60e4e51bcf083161460d1c5c3020cd4fd7f8ebb26ac7c3c3ce1eeb550601175b1a8d2744518ab2b6c3083156058b105973328b5e18a356964a046898506563c61c598d91560e4809ced79f2677b9003888ceea827b3d783f4117f58a07a12ef793a8118a13bea4998b38a4fde15c8790653b7da24655a9190c3364281502cdd28f3e06eb25b6d72b4fa225183825bbac5943161a140e814dd515fdba841ad78a18939ab27690ebb0a608d917e09bd00d61845a05e689b393465b95aeb69166b7b90581f9f40f6554d0f8ea9c3fee620538725daf653db32794d6698f16346694596b20a2382c8b0e2cb0b3228abb0a18a2a33801304a78a261ce26af18558bdb43359497a7f846f871c71f7fee17f07bf4ce3eced1fdd2038456b6b772ea9ba6ad326d58da92882e0e7ed4f18aa549fafaa9cc71849a14d647d048931f250a9c2f0f334488cff541f736aae3e7e3eac218c9ff3a178f04eaad811f1e08d446da4a2713658c322234da29126279553a8418dc6ab1d0cc02ff6f6895590666fc74b58c80d0fce1ac2ea24f74d2cb27d92f6e2b7af32d4694b35b216c24ad76aab33991584fa5941507de299c32e5bf1f75ec81d4caa966e5cee5ec5a2ee134f1d23b22f96520565ec1558ef9e3cca18aba55badf5081fbfca2024c4a6eb84c04e889b29352bbaea446e953b0be4ddb31b05d7de7412d70ec573a73a488b91fcc1719947191dd771b6be8a4482bcb12a166b6419b13ebe356aaa5b44c273b1d811d77b1ee3deddf66425969411eb3a571a5f93325a5d47d75a5fbd9673d57b726c7b4f6cd3b123e3ed1fc07a8f566a29f5dc7ed2d46cf19c5256ea79ec48cec79bd0eac95259c9953b0be4fde6696f6cb2a302873dbb14c447815ca8d2b5255a4d8aae9668abe9810ef62cd164d968ca6c3176c47ebc093d67bdb33e4656cc84d65937caf99863d43a4bd728ad4524dc44234daaaf94d493747347ec5937a1e7741cb7d7723e6a23d6afaeb5ced25624121ad9f9dc463cd7e9b119dfb36e643f3e288b31545a29f55ce52adaa4c0abb292ea3b42c4779abc9be3b873bb23855c737a625e42396f87ef9680d968751dae735920bc6d22110fae08e20a9ee7c87890314e1a8f96aadfd14a5246aaafae94f3d675b492eaab9332234d4e393ad6b5748c7352e9d8f758a78d8c629f3ec2fa65fdc61e30de7ebc655d95c757d15b8dbac611d9952ae9824b5e650d31c5384193b7674945952e69446931861a4c6a58d03101a6c9146fa6704393b2076b56aa9f349b4948816eac5370c0a435717772128456cf9da8727702c598fdf6bd1a71ffea3d13ea3d5da3b4dad43d8c1df13414a5c58e78ba064569debb1b7d6288892cd07d988dcc32e211a11ad4867b585f754d2a76a49ebb513da76b52f635ababeef4b440429b6bca75db0dd27a410c8149030f52bc55cede493b9513fd6afd1ac0e518be9320340feb23f49e56a29fb42526daa7d8804933526feb9644c5720488c65a29a6aa684489c93df7cdbaf0d138cd74bfbd8e31fa983edaa69598e0dfd18ed955335d3d2d90c5126693e3cd6ca9519aa7c4a4bbf70028136a5094d6bd09a57577a13b372f53a7a5ee39cd8426556b914d934b8e92e0578d023ebd127c7a229b56825f7f330a98da59af6a098d8302be1a85fa7a2676c9d6aa86d01938e0f77df105f7867b33a788b271e61451db942cdb942c47490cd034b5efed99333a4abe8d82019aa6b635f3e628c9ed42aa33b366571a7669146e17524c5b8893c14c83022e87a926db9ea51456ca28b49465c204855146b1840b416bca3371ce9c31069c3f9df5517f4fba40fa39c1b97acd3529a31004b591ea56d7a858f538f220e3fed34d55d7a48c787acef3daceeb29fdbd11fdbda7f7b491eb56d7ec5dda8805e24e754dc802713cba46a46bb740dc7534f855a86b52de6be1bf7bda687515f7d5e9a90e754d8905aaba46413d2d10ada7f5352923faaa6b4c54d72cf0da78d6d428c4faa44d375ceb9346395ab8cac9352923f02d5da334f0abb3b252f8ef635692320a35cd6295c2f368b3d29a7eb1403cc818b5524d8a8449c3df3e699f56c237d2544f53c0250553fb0fa0344c9b3fdc6ac9560e88a02dd86cb185150f82f7de7befbda0787aef1559e238de0b8aa2a8fa15c14f111441711445d678a20a8ee3388ee338b258add63bebe3f5d6c156265da35095b56eebf82f5d65e32852718e5a3cce2c16164f1031c6d8a51297c2f054960f2af1b67e652bacc9d541bccae291ead4b5319e26c05658a54ae2529565b8ecfb9db28c15d74ed8c3b3dae8576519a8bd7a584271c6bedf61a9be8a81ab71c43ac6d23150c7b491cf61f778cca408ae34fec6fa8db10eae56ac1b815ad4e409fb7e458ae756392a955e81fa28fcea44555cfd86e1d5a46ba7876747b55aa956ab202edebc11bf3a61b36e3beb03f6f1e098b15ea9ce3acea1e8da57f599335bfaa56577257189b48442cd06bf7a3de7ad1c4d8e2f55a71abf164bcce40946ad7c04a65de3a793b8c4d545708a4531f1ac079cf4a8542cb7953d4d32a56844000080048315002028140c88c40181482c4dc32c647e14800c8b904c6244154a22418ee32088814806a1184388318600630840082169ca46001418201158a2d577ecb846043cf14c7c438620918a066b84dc99b810adb5b4fd522608dab7562a9c3434482543684e2ae184f7720053e0fa25bbb099c630e86ea9e253d24e22cf2a5520fb06205a3b3072bea38bdff101383d761ce609962d3c15cb319fbe29163cfd34a72b841244ec1567e48281e58e78dedd6962e25c31f5d174f9984e107ef44b6d9597dfa579430bb81e119fd5e5ca611fb0048bdd20c01136cd6520cd1d5d2f7680965d00fc0dfb2ea7be4641e0fde1537a128c00e5a801cf37a823355a3366cfdfa1373c326ca6440f74b9ecdb7a7fa4cff0adf64c808f8d8e820dea28595f6a786a5019bdef8f85b0dd0b91d8ad6f2c15b117104d2e9800f1e4f54331d4ee7fb973f5dc16bb608988c721082dcb6c09157725a4d5ada288a1613cfad6864e2b49e5e6d61884974da39bb45d670f293140fde30e14e353c9c228dc34b30adb9e86469172b7108f28f01d92d6ad27832cf6c4bd1e7f026f380318cfa1f1ea6857111a25e84f96a5eb799640d1836f89bda1c3b99c4b4a188072fd52e4225eb2eebb54dba0bb4e5771a738717151e011e3e7822503212650da0f504e75b55b6d0672bf9b9e38684aa5d5780923fc4ffda11e942e141755b0d4c3db57c73aff42cbf65bddf4d9ea6226e14a93d5401862af123730a1d581ab5ab4540e6fa0a46e2ba4cf3e1ae5568aef82369a1505e04df7939b6f3a3a9ddcf5bba07370867aabb44bf228e799f37739456c8c5472c455441d03e73192f4679c9155d22dbad5bdc11690208bc3a31a55c2672d0bda8157957f8dbb8e178c961c6e803e4b1fa72f35a52243dfba9e6142873785c9643631bd9acd931d0598658d2f025e1ae4e36e6c6bf4afe3e3c0b4654e3361da24632834b6c04764095429bbad47d91ab1e92b163266d15577fb439948d46f0e6b55dce9a6c32dcde79c6521aed209409fe2bcdefc98b4cbdbc5b23c4cdadc6d1c3b8faa7ba3e387c6b1bf7f1193d675f9479417aca3d35c84145139815c5943950309f8b5767069cf5a6c828a2cfe29f5dec48261ed07d416bbb18931b4a70c3b98aa20924623461b92225dc71330e809ec9e420a6cc45abab3d36c9055b31d17ce2e59a1ec4f3fef3fdcddfbe8e784dc7a03318a3bc16f6965cebcabadc0f45734ba6c0a7828239a31d0d5e29b2c83ca48cbdf03a5c1c0dcc573cb47f073baec4a8579203466d78788264cdb333e353061ef40b0976bcf502fc2a95a18cd877b695c574000df50501315e9905e2a0e0d0eea9d868795231005a79b337d7e4c7bfc50de8315579b2ef2cf93c9feea000cf22632f0a3bdd2f7e7db25ac11a9f2b9520e7561eb6794217968bd43d754cae53266c9ba7a84cbe3e823e2d4ef9df0afa1d6b7fc9dc719a6933bc89a3ea6075dd69131473f3a15c54f8a59726ac4cd86ab6833b4c77f4a57ddd59ccacd97f8e81f52dae0a6feb685684bd4f798dd99eb0b492d364b22304da8f2fd6a6f342a3ee8cf2ce8959f6078cc9615537dae85888671579d11596cd552a32ee40982e86110cdbbd534333e5b4b0d4dd821d83fdb99434342e0d6a4220a5d991cd6ed3f1bd32355552cf0455d48161ec5eca4394e0d91c76b41c0f5e86cc5cc808471854f70a0e83bbc80176579824aaa1c05a121d56bbc80abfd80d42829f246270492f334d6590c0213fc29e1630bb7ae9d2169dca6a4e62624281c0c7cf88ab8d0c95b4e12ddcf76474c59a4cd54918fd262a38a67a4ff30278d39a4cc396b0773f2845a70d8dc884a7a4e94ab1f1699e044458b466b8f7926b6762e5cd44647bc7977d9600407386f4764cdd8d3fc5a6141246a6c1916c75713d117a8088d5c0911d5589e13a9f905d9691728878f3716430500992e97c55ecf3aaf48426eadf3816bd7015f66d469fc836fa6dbc8bc9763f9b34929ee790370359082575303bedae2ef7100ea15307c9124af2b487f4c36988dce6dc4c1110ff6cae36a44dfab582f39bae77ed0bffda5875a43d70b39df6e266c190b6664732cd8a59dd5cf99af8d22c50550ded7e01e467aec7174e46f0eb511706ec5bbc1dda545b963ae2be154d4867522a3ff6c4bde7ef707051ab4777b96d2e3796cc529c578f57007023492e112defce4105e17836e5dd9faf2392c9e152b364d24f29196bb3eadb04e26c8b120eb458e02d5570e745c8989a860cf2bae2d7c527fcd78fffa2a0ae4a0cbcc3e5dd689b4b15e4395bc90a990fe72c06c49d8d57a74a838c16f50a422c023201350c8e2eac4c65ee0b6ea579a611859be6da66b3a8ebe99366857c2cac83d6303b83a1beb989b578e79c505a0896315688a114a406c372fd4a36d40ad32c24d29f665b90db211a8b7311484049be543d6035c27f10754b1bfe2ef3dcc57923dab2819ce3c2de0002208ccce3a89c81f3ee8d88645bc2c3bfaefddb1c286269d6aaba2966de2f305a97b90c64bb67ac0e0933cc4cdd861d27f8c54df2e4d775d8d5ab7e48b825ac5aed491819c0a74168230109b7220a1ce231a59dc2c77149997806544932a2f59d4eeaeb15bbb1d4f064949f863e4ee640babec0d969ed3c9f51a1721015759d104e9b6f83bdd3f2f4eb557ccb8e9ed8afa8e211a48defde0c42e55bc44ad102611e5104370eca4a2b28bd1216debc0a0bb659fe6a8d4b40f2813c9aea27cf7c006386f049395bd50c77a0a83fcafbfc79600953a9626dd3ddf0e30ae36e7827ce8aa93aec9a146e04ce289177cb2acd8d38aa80d2e7da2f6e6bc688c46ff53b5494260bc9ac10ae3b9ad4bbdc0c2ee156e737dbead3861741e58f12b03402c1bda38ac4b9ae58b312b468cf7e03e7a3d0769724fd7a24c2b6c37026158e80ff0fcb65a31cb1031f471dce4f2719bd4cb56773b013326b18cc40e1b982a374ecf29673c75364da7445a747e6e2bdf2428c8e7137bc5ee923c60526eaac1d7d89e77c983a9469a43299272fa4662f8786957720f988e1e49071002f79c4536547ab25f50cd969ff643cb502b64b0a7e70668926d1b86d0193416498134febbeb97d571d89e79fcaf044908a129f82e9b0e8911ec39bb08061abe4e224f8487ff0394b22f2f46de9619bcb45042bdcd3396b0be0d337dfd1433444775e6c17be52892cd9d65428db381c5879bbcd9699cc0f16190c1ecdf217d3761136ca413878bb2a874f2c5a5ede8c4c1940aaa66946eac4789163c02dfa94e3245aad9b124897c8a9098e444e66e1aa93990f1437d62b2d95c62fbaca8615e7c0ee5ac76887f2eb9865b3dd9b837adf70c69bc516efa27db985e04bfc04944d5f1d81d6b270ad73cd80b2a72c4f96c243ad7c57014fced287e3055560118a88f8c4f07c266528686030f458f58dad06d2e6e5040b411213eaccd3a820bf7d82fb109b8af28b7b86361445f0488940b6f7ca48fb0fe93c716b6c3eb253888830e42681674514552f80242fb5d08dd0752374e9eae6ac68387d7296c62ad4340909c900f0c25bc84b6fa74884b502787256d75a6a74d780ea423fe456ae079f0da730ab43b0970d0e55514f9acadb6596ce46841effc8a0494421cd83d84da3d50952ddb4e9f3dd2cd4f7d13def6a6960b3948dfad36577137c589cf2240a95f60928676ac26b6f03c1a0771839728595c2bcd42e0c4b0d205f38d120e534a69e4dc4df04eab0a3160a6b81b598f620b8313bd4c660ca0480f4a5313b02c324a169b45a5bbf506fbd36b269bccfca182c9edd56139f1bb574c15569ffee7097b54de6cd526308db0392cdb9dcbe6586a013d4d54b0d800c1143cda812d4014206724322e23089c2cbb3dae362557b0d5fbe0db21a4b8cd069a6bb435313c514b6cf1bc984758b9054a5b741be6a67da1e3f13d3b2657633b216297ee20db866a7ca34ad6c9a011286b180d5e89d450854d21e6e26f06008ed6b486bd635e50f6a71a04467871806996965195405b05586ca45337cc5245210597b0770d6c9fb087f0841c896650604d4a5be54db55b682d726e0045e34888c6cb3f44ed21b78e5b7583dfabcf1ebc46138f6b59a30f816ce2ec9288f7d129591f422e1264018696828b2e5498d2738403ed8f280b071c74dafeb0ab27a5bc22b70c1214301cd0abe059225c572581aebe581c7b71ecf2cc7481f0acbdcd3e8ca0f02c829f2a9312c5bbf4573783cdee6df8aa512db5e418c11b9840b975859a9ac3bb962c74f92abac648af97556b435159b6f83a4cd98651fcf45633b749aaefe26ad15ba565d3b922c927538524f2e0686e6be7e3879f6a88436a4b798bdebaf461c335563335d7ec4d4ef810b524d4c883cddd8c1656fe480be8f63bb03569ddddfaf0c2880dc1d17530d9a832a5bef9ff729aa5cbe59c75372846a79ba3102d47cba719215c25652ee3ac264fc1b496ae668d85cb7083d4ddd77c79280f4f5cbc19c5c1dd39b1f92d3acb8d83d865927e873aea288941e04f51946ccce313c62eee9cfc26130ee18bfcc5a02f4ea19eb3c0f2f4f740366abb1fc199d0a603c90fef13c3b3ede3d4422b714dbc150ca278f50a2f7132e0b6c3d68dabcb3af5e2bd93ddd8c013ccb24e3e36cb483ab1f5fad65ae8a24a135aeb4516cc29349de02b2334fc42e92ad9e106674ce3d4d0287d80293b2897382b954f8f040e67279e4c1f116b6b5b777b51e2a0bc80438dd2406c2da00cb04dbaa2c3e9ea9cb7315022fe5bb03025fc85e4b853e5fd49f7ae0f83f83addf427630dafd03c5ab391734f255510e166c3adec2325989a51810d715e031979de12e289ec574c04536aa56cac1a1bbab7de94b918738cf103b427c5751e95da33b2a760d41148c411674cf6a0542954a44307ebd4bdc65a653af4f80165e60df92850dec7a84588959b929f99fc9432ea48df5a593fce625d3e2ef433243ece4230cc3d015d578b41f183e8d07f1e999aa09e8beaf6827df7cd003e430b11a8a89b2097b84662b77b1a24840537f1906c183a34f7e997689597db11e924fbe94c22e443fafcdf2050040fad4d9d8ff043643a136fadb908f8edede1bd265b7cfbd621eb9676d934917726cbad248a53a33caa5bcbede8cc2901136bcd575940e08b076b0ce328b516f9d496a21ec416c93249275e8db92d267eddf462c26b36caff620ec9ca86b48fdb624a6fa65c2b86b59345be98ef171ddc0927b6c6e009c42d0a32b06721b798e82b8ae916435f93fd95bcee28a8b64cbf8602d11f48bb5bf78f10e331701629ec2117bf278912667d8c628fbde47f0ded3068d6a5c4c575e7b581e920ab8341bd7c0d6c424059a490321185c70336757bdfe1df9c4ff85f4e2d9cc6dcd82c54375667da9f20bac3464386d65da88e5a709231f7115be4eec5059ae911adb158b5084448aa5fe3426e4a2e492af50ab050a146e7b89ded71c1d5938fe3fef170b84c08ddf2648905bd214df6dd94398980d2c6ef7ca0e8f35a3b4d2c78dd6b95fa31a7d9fabb35f215dfadcdef395905b1389aa782d163e49626fcbb8b8187ad981b3800d331f2a384198b98d4c6e095db5e76c31e0949667014c98efdc882d184551cc1af8f608d2c18aab8063491f0aa27206ddc796aa0891218c583e67588164e6ab23f3c18160c143ff67638d85bdfca873ea2b7a95262648cdb8a3b2cf64c0db22fb8c848d455c6f575d2e9af63795b44b10eb773e293757b14c4ed552517f9baacfe780932df19c18251201ef67d5a56666e4fe7e06c3d622fcf2c6331fbbe5790353ad622191c3145436e43e8b33b8c45c09db93f80b77b23ec31f25f6148c466f41e4d270b04d4c0beba7f6a7788f79c9e7f1b4ebcb06634dbdb20ca283ca8a8596b28053e56bdd4723943562e542c2c88d73689e50b096d78c74f04390e3eb10f54097a91ec29928a9207902b73b037408594195ae0b9658f3cac217002e5ab71e5b448a32175e4cd164e355f11ccd26441964dbc3db7eccfb27bbb65679789a10ec109edbc60a2e8793c842d7b0c0a2219ec550c8d9d04381a431a41e1d4e5c7f9f9f192be4b2f188a223bf0f9f109396eaf57f0c55a5e063b34114cc9df22d8ae2ddb49e0c360079a10bcbe59624c96bb6202a50bf6428c4363d516ca8b33e750614ca58e1bb72717060220fa2d2cf3a2d4e72de9173a9eab61f8f464a1e56edc1e1d0280a2f7594edd1abe3c6ee2c8ed41938d4feb4d50574d49b1168e2bff7d74d76e790016faba103c30900278d58d5ec2327bd980dca03c7211585f13d78f06df3a7f54beac95a8fed34d49db5a080ef5d4242236ef2208d0fa9e731b29c10e500972d776aa751db54ed50a79e232b253bc4652656eb74386e08c5931e016adbb3d46d735d1172eeec6ce23b15a4de1c318861f5621cefd514f798118b52766f7245f93bf269f7a6eb501837f38134c470a4fc591c54d0fcaad7984f767351bafcf222cc1916d1dec2bfa0f188b2e62586309ee71268d6e4a2c8307f44485419bd5e3e36a4737425d4ffa2078ec0366bc60bfa4e0d5179b05de949651af3ff17f593a07442d2f407353f0dea9154f620deb7927b5854cacb40566bf67c592d5896012f2b8395ee4cee2f65a723ed0c776a90c2c24ca457be678511ec67cd7a19410112c472848eac9d020e5bf6c0d88b2606806722dafcb323325703efb9b44ce6045132c3a5cf93b355d1d4762dd8cfb590df85e192b8d675d8e54b99446e915ab480321634036277003cbe49fe2606f5366955787a23ab085a59306b1f3d3aff61b5ea80a2095ba25936e8b6822253b0155ae797262ebbbf49253568035cf625925c07087d8494088f983d8a3b5ce31749d8e42ea0a01b4892c48a6224a17904ca21798c4ccb9f12b3cc39b9d8f756701c0d66aba6c60043a012b81f6bfe850ed993211a5df900b6a8f473d762b778680b7a049ff91ab97d520cf4924d5b01d6bb42c4466d4d2c63725f83ade648f3bea9aab74f96b58812ba31876f8d054f32d07ceb63c4127955087ed1b53b5f6b4d753d274d1790d3795b994a860002f97f369ce6042a8c434111caf0ec191b8b171e505abe32883419ca136ed99d77e3429cab897d5b2a46f77dddcf15e81e4c292fb8130e094a1b13158b4d315fac1cb56784f8bfa8cc4bac21793adfeaf43e3b72f3b458d8d81066fd9d636d490f4b733ef160cadc8d890592727e5fa6a393977b0c57696511bcb776ebd7f976f322db442186ce6fdded6097697a686535d09cd98c0f6a95f6885d761fc7f1f105d377f85584261f750591f1f94249139367836d1387dd25ec235c1d700ecfe4fadf44e911faf1df6bc5e12eae2961050a609f99fa4c3f66239bb980a2a492b5a64eae164324b5d7761b49aea69c8adb37d832e7032d0a041d402201e718e29fbfe3eb633185995e1e848da250b7686500c132733b40c41605077dece7c5fcebf10c1b83976f9dc912cba624a08b64cecf20df211a3490bed2f093edde5fc6d7e4957d9c74f09dc88626a6136bc3cc55f607909a1e9c5af38d4d9ef16ed6d92e2600ee55d130872db418fcbbe23a8d37bd4bac0e688f2d669858b4f5c258a5af97b26e5440b13f7ce517a9eedb4d6ad897403979644596a1d306f02a277e1c9c76d89e9353c1d7045ab90ab6197a0647a433a6c21218795b57bd0d2427962d057c1b013c2b03772ed100242fe40818444c4398293ffd87422fa2c9f83548d9169e9e7b578569a05c89b245e22f59f4c55758e942fee57c42db33d874993d496263fdd146162674343e5852e65136b23b98a46ab0c7a907e3c8d175e76fadf384bc60993bdd784fe919f585f6d105c39b299b80f39cf4a4b44decc421371c1db23d7cc5ba70043f73b4501a47e6e43e9cdd6853c182e69d57b657a370a80ce30ec4d81dba877bf005bade09d84f4e4ce24c3d47d180b4a1551b4eadab3ca8c0bcd111501c5ef9ffceb915adf368f364c0f4abc48e0e32325eda3e139b904a2b01293a88c8bf8da8b50b2bfe4da64433c66b50a7ba47d56882b81ebb733b8a4aa5d16ae8de623485ada78d5c87b2e001c783b8b84cb4b0c2b8964e985f04e0218c4e8da8fd5576ad25735fe70371e065101982f061e9197b75a5fb87bcf7871479166c1eb420f96985aec42e9f56c01acb10d7c091a188caaa7758449fef068c420ab9fa7299618899fc4d3a0dcd00b8d7537f400839e8e28408d55f9a99fe6d0050a5de6dcba337544c108e2ae71e883e3aee780b812f88edddf472073c377c5452969952a537e5de6372da1c2689c18fe038a472ab5e742ad61c7d4606b30d98d7ed0843d7ad4690f489d901fc333a893a3e8fa676dfb51494a94665d6e2f3f213b8c04f516704a216dd47c6cf2162085dacf9c06376fe60f9e688d35e4c97c8d20386185cc48f556f1cd395c7f3a492cc2e136959a4f05cb0be84708ac56e1aba75129f1456d874b74f1788db8c8d1da72183a5b24519d0264eb7a8ea863d96b2603c76e592c25a71cc0d22b7d0c142d047a867e43d4fe21b39216dd2a2d4ca4c8cba9f5a6bffb1799bcfbe3e6e5e41dab005d285e6a744d39e0425250b4528244b80eda2ddc4d2686dcdf4120d9027b14bb70ea2e03c32a68d71cb586feb421fb806ff089b90a9eb5a809b96ad9aa8ece3166c7f415c2dad714fa14c6f344155b567ebfeba835b82bbb06578a17959ec13189e44807354fd6eac811dc674ac748c28b3b6b20eec3a97d42d948f8456814512f440eb16344d79c5dacc02f9e5859afe90e051e8cfaca03218c6695845968181991f5869fd9d57fe7ecd53f5cb2c7141dd5ce9d9646954306d07efea29fce502535de8953d5ed681b1872241eb7589ca5c79f0d42ceb08794c56b7ddb869f75fcc8a28245ce3198d4875b4739b02ad28384c65b8c2740e43914d591d6eb064ce622f95feb007592082472c02a5b44ac2da580f5a4ca73485803f4e814ce40b65b71586231de1829694535980c02ba85566b9e54bfc34a81277f0bbef96e4df60546382f8a1dad379549136133da853646684e36bd9993cb6692668313700ebdedd683a65babf356e9436bcc67e513221dcafe34a092d113bb55223644eca266129da8990c3140bae2af9f6a452e28b04298d988139225646683112c64be939c2f7e5335eb328e265744103723aa3f30c0c4e1e1c929a6c96290177cd4e06d2b44a671c0d8b45357cd1134d57d81acd39bd0ebf62f3765038bf621448d10b0bdbd858b1073cab64a6c1bf7c39ed10ec1ad36c946bb09c71a66fae7a9fb5a013aa2f45e4c18d3a3e76d9ca9c381eac3325ec67f2eaadff6c01ce2fc0ee08744a34d447fd0a52467a8382d2ec745f8bc75cc57ccf53196000aa4f6da565cd8faa726d0f6dee6264bbe46400bdfd2f6ec23f1972ecdafe860d76f397bd135d3a74349767d5d7155dba54c409c15e6e3c71212225a1b0f2039acbaf8883a7d6bc0e09903a2b2bf518ea391e897e4426c454dc570946fa7440bb3ccb62e9225eed0ba25b35508f137627d46114fe3afb1091d85f7e5c1a2d50ea3d4993aeaeee101d7289e74b8b40716756b27ae4c9aa5e3519d059e303878a9cf7054222af30afdbe743ad99f53058152c24586b06133f836c6e16c0cadcb9ef41fe50ad84f7829f216e08a0aa25a3828c992d92da3c72a1eac348f9a136935711fc83913dbef91e4121f4037749868819045d4c97e9c8df1b348e8ad724dc0f160f7e9ac50b7546b45fe6d72cd91b88b1180b80e7e87b261c17cb4c3fd172dbf078f71cf1cd60114342dae02810f2c390f9cb920451438b811c93b68fb0d8dc2878833930d27172ab53ed1f6176e1bb20513727b5ae557f3adc57edf1b7800238aa988699de524570809d18397ba7ce4b30944a91490846d550d0ae8a762cdce2df65f7915191bd2f275245026867150a18a51223585513afafcf13481921fb3f61dddc35b4e5454d6d11762f2306e4c7e0838f05542207a1254bba376137115c24bb7a4399ee50e2f7dcc668fb888ae82f37e2d594ed567387abdaf284d7ae0e4e1c56fd75d8f5b24eadd3a94f107b02f02d37c226f679348cf3d3c0d7a42491d21d3f98171e75e0c208a47ab4b4a32050c3317757a4cca29fd0d8c7c672e94eb02c85696f8d985d79c8b21f624c31febab3153aabe275b1729248435e6438b9f7a8e2e424d5754d69b44fc73afa6b1d23a7f19617c1b5a7130a902a267740a5f7e5da973969ca48afa4516e34ce2e1ad9b4f52a6ef197030bf27663a34d673fa6ced37fc82a044af5f214149c2dcf99b10834866512b16db42ea2f93ed44376f88b0844583791ca04057545e66cbad13d2384b5ee738c47546b64c9c765ac6cbb626e52401da95f315a172a284a8e4eca5aaab700d9fbe90101144068ca09940837a1a5ab9f3f494801776bf5a9750230c98be899d98b6d8587f7e5e2915a161287170c9689b7da15d99a01a9a3eee6422deaefdc265cd65504531114fd30d800225efd3311195d0975b448cbf4928931ff70f1fc1fbfffb33f8937e2c85c0a241868329d434182959e123b420dc5004580d120fcd3c8eda29a1e98ce03e780881d06124c559fe0ba64058d82ebb831732dca707e54ee41bedf0d2f02823877d015d998818a3a63cc845f6afc6c82023ed95d8b90925c6f7a18edd906da0a5cab62c85ae9c04c209225948b3c4bcc5c53ebbd872fb3bf281e5b00721f9dd2344f0c0b5a344f0916d02e4ed0ce40a38e6af79f156083b35e1116223d5eadc98ccc9b3816b88257e6187a0372709f26f9f2256fe744b7a49885e41b434e0264b441aa1a98f0e4d915d89856d1af9179298a7654851da3b642d9cc4131c9a52efba9e1683e772a793e4d2edea95b20eeb2883d212fa42b4795be85e1580f71ee2eff8458e3ff2a1b49fcc1a0b4da76e04ea6ca9b90507104d5bcbcf62318b2877c62c24cc11eda5c1dc9c1179412a4054b4f2028193a67096d2095256e9589791fbe1edd128ecb805beb82a37639c6b0c243b351f93b0fb0a892061eeaace2fc0256c84ef0032e3a8951d27d1550897b07a21f5e2ed31e671fa198c76d5bf12ce1ff6f109c79cc48f8015af107f77f2b862a9a3a12e48e4a6b4bcb74968be534688958bf6cbf2f694aba2c0e50068d2dbc9978acaf21e33c2534a48ed044735b1a57fb75a2abc68a317f0beb8e799e3aecccc5198f7b39cfc1f330c66583ffa7c3f66122d40c11cdbe53818a97856daa051e6e2b03d221f9fec210822bc8705fe26e20c0433a41ea1082d8dcd79e0adb1b623c78257aeeb6c79abdfa428b436ed940c64f0e40ce61e20dcf14895f1c0cdd80a7ee22a09d3fa91316219b1ac3725c7559b18873be6c730d65e7e2ac1c2ea6c228920531adcd85e66daa411384fc76f780ed06837b331607c8d0d1e18d6363e6e5075eeba2212e985b6af168ecd439794c183d38cfa6b4142e04a877886f80a578642a1a55ee1573d5ffd61d682ab2cc57dbfeb234915dcd13bd5ed6a2d50a8022a2eabfb118135086a627c27016b1ca17475ec31fab5c73188e67bd9c1b540984bd4a70e46a62917a4500f0b5d058c995c74d5ff2676f897d837e0cfc11fa8a408061feac5a8724dce74cc41c91eab62a48e2be25195d66a7f841ab135a56a5bc9e64ce161c3c156cd9bf707055bde6cb889c28afab349a93832303c08b6f82b7a7572d80a0c0efb6edf9123a3c5a9513b53b13eebc8bfa5da07b2a5a72f7391849e472820bae446d0808d5bbe4613d4d8b1c98adc4e6206d90840ee460170a9cd28ebf41cc126b54227adda5dd26adba8fd874dde316852a6f27d7441a2a3221fe46ebd5f780f21d77abe0916a086e4968c964efa937f2f2486e014c3094dfaf186054babc7864af938623ed3c0be3602dd20e37b11bf5ec588c1e1e82707d9bdc922baab90d8ba78c96e0e45556a40425d639e3e4550ef3c0698e541046c47b85917b521ebc18c8398516c0da2ad35f0ffda6fb9abaf0b8b6f5ff2636e5b5086537a6f512a2137c5b209bb94f7b092102067e52426d02d87622c3a86d81fdcaa9b571fae25c5f50aa7f2111859b9bb6b200f838fe36486b935eaf20611f27381665a97afa43920ce3013b62f0160dbb52e64dc9bd31675567b4c8b97419fbcc1bbd80e342847de462911897583dc57898ddf4fe3f9d2fe6fb75187b1021ac35c2c10e4dcb3a5cc2b15911e1812b921d79b3e9d1f39d5833c9de929d0d06ac6c9b71bb0caea6ba90cec9f5b3977d83fcd0412289c800703161cf40b7c89740f7fe1f03e226d97a30474f851d402a88cbad3e16de4079f22e0baef28003cd11a8a82b8f489fd9df0c9c713fbc348202e48f3e24bfd4e8094701159a97f35d378c4f926f535a11c251c7372b0bc6b527fded5c4f3de11d6a120bc44f0525858d99ee036aa3062ef140ab6355000098c37c14e167713f485c28ed1be2e35df98fccc84805f15b74c262beb35c9c148656086a540e2b027f491b6e08ba409b070ca0386940c1cace44f2b4e4a81e4d38b404506adc094402b6beb329c24e578844e0b0913398bfa3741f64e371abf3bc30b737e6cdf1da88eae298f859b5d5937d55cd680bda55811f9f24731b4997168de5e896c1dc83032712044ce90294a54a5d703d40ba73523021214e29292fe4217c53ef313c286c0861ad0b424b76606c183cbfb0af1cfe945b0d117e58ad30bde837887171dd11801ec082574f8331e29ca3bd752582cf4e6559644af3808580304f0c2fd5343b69c9807705fc98db600d52c9cee43af5de176495ac7ee9abb7886f31a1ec5ee60321c4096bd7fa513d82d4de573ad6d91fb1dc8a57ca4d24509d20997618879a585f0a06ee639ca66ffa8a78e5fabed09cfc2042f5ab71e2960df79936d1346a4a1631ce44993bcc6fb53bd2efd5770853b081dfa6944879d822abc8984a6bf1d8b4b190663a001dfbb4dbd2379ec5c21a0b1309713c0a63ce8e0db91079c46525d0f27434e3be2dd52b9dbac8d9dbc5a0dd613d5a3fbf8bd279679fb019ba519fc7704766bc44d997eb98efce58bf02a2481eb83ddd23259b14a9a92bcfa0af23e4739264df77238e2c25a0a26c3fb564b2a6aafdbb514917559c7376c07c62c5aa3da013b9a001b7f19b0db3e6d8fc0727cfcd26be6929f721f204e40dbddfa2ffa45797b08f870f6a3f0ca2738507d98be8a80c2ffe0b8a07cfd80dabbb48b9b9ff9b7eb8ef113973307e8e2c5a50f261097d54c0e435f6418148c733281cc31bfc9ea53484ead400d4462927cc812c5bf2aa664bee315ee6b409900cc7e71baa640079768b7aeed6f41f22efed0792493dba054f7e43088ee9332c1887af7d4e2f5edb9511294f70c4a1f4ff82e3b5fdd0bb3666d92fb93b65590c7b068cfa378dfe5e653f8be329e0f86d60775bbe4fb665c6a20be4d0c995f0e1fa060d1328802fb0846baaaa88fc736bc6d73986c94f9234ec521ddf2fd9fc32aa851b38adcb575cca8b61bede1e81bc531ef2f194972bd712e8a3b807bda5175799b2f9693261316525a824f99b7b0bb95483213ad8c153ed0da04c353399c26f23cf90aaa2c0da4db07c1272c4db047acb8e39636a9dc0d61e5d2bb3b4ce8321146e823a6692c3609fc0b23ad060024c7c8301d99a05f7c418fd2202aeb1724ad969fa5ac66761b1a4e0dc0bdca8d5133f2a5275c4f6fa6362f428a18149a8e551fd49a90263665d82c88363c201e451f79906f5047ecdd1409f959c288f19a5170cd15a8265ae0e43a8c27f52811da31f5a4292ae01acb9e990e333976597e3324061ae3c258069a8bc1696b102014b27dc607222621c7c2c37d3a0185c555903dfcd69c12cf7768ca6643a59887417ff0cd9bf90fe8d3cdf14ae074e56cec4eabc51e723c51a35008124d0a4234a4240003701b3ea2d9639847f2b468a3324650f5a537071f70f05c3ade0bb071beb12c6cb7ca73b24bf4d5e6f4910a8225e62eafe155730cede643cd362247d42b5a200e23f7b6fe46492f911b01f910c05456c0b210c0e697e4a4ad432094599e1268a7088cc5ba9a6ef48b039e418ae512d86e96e86e7e50608d3bda774984e2faa22e2703fb2d1991c2a48c4da7b80dc28d722d83c3369a57ebd8a96c5d88deb65e40812872d43cdbc2707d0f70c67063f06c878010e11b7bb58f863cf545b81818dcb14d6171edb7e4a2f48ebc81d9d1113d95ac78be7aa964b53b0630e680307c42bc2c1fefe16b8e0e748685d9719368dd1b7ae53774e14bc11cb22e6152ceff88ec343e56462344d79785e60bad0a081ff058caac14814e8596b977c7f924260b502170e58d0a3c387b50861e4c615735f69da69301e48159cc60fb238ee2b378e51c2537781bc32d492a0f57aed8e12f296e20da5a001e9094b0e312dec8d0bcba0a60a0efe6044ad3a94553266559d86c1a3a8e4b451fc065aa24995ae40c5ee381ed82a580d983d865d83b9997bdd02c570f0a9879d1158ec059c0010569831e78eb3c482a77f2c1c8fcf227f0d7128a0ac460fa1c634e4e1d84cea337ae915392ca23d252d320cb611511d0c9febd06795c54e744f2c00227959a67029f340f8e8bc51837478f2c4aa5503a03774e03926419530cc3c372fb8249a7a2e5370b22cef2110712c2ba151dd6279a91a8386cbb435bd9392f22c508304b79fc0ee10b5497b4a28594bb697f01c4caedb62fc3f3be271b1c092dab0a4b8a3d9ef890bae7284a0a562b115c5ae8d86bb508878059ab170ac245ac291c1406a79c1edc7d233d20298fba9837868a42848540f6ebd8a06b04a1e91b07f4900a619d8012c4d1145edad88f1c07d49214dc5b75b516aff81fb237d2849890b5b3a711bc7c661e9635d44cb550ea8b883289ae737bcdc871e76dce438d2c415080d4eb8841c9ea3d72ba0d5a493f81574e256bffa7ae6848ac0e75aa0e0248ff4a28af4b49396290600a29a8dc88f4f47ebe2cfe06a2e2049a02f4e45dc4bb1801dc005347cb83ee469e32bd301f2144978a5a9018a0b6494e7cb6bc4918437cc82fdbde1139989cf7856ffa11e400901b009f45d150d020f5e74c15a2fd1a4f830e650225c31604b74afe17d662a568173590ba710edf45edd4769a5f42a709901a0d9132789c77a19069ae3f5e19aa37ab47cb5ee3a3047a6c36059a88e5012621737dd004639218ebb134c45bcf09da211e6dd592972bf3f44894be9ce4607014e4536dfb343898762436cc712947905cd7e8e725151bad273cc7e134bb53544855bd903e19b4e200cfbe4527594d5733937b02e171e311b2e2cdb5bc0989af370095f9cd03221dd0bbbc9933ac862f377dda023ebbe4d1e940875814da93074d84437018cad2abc1a484a26a187d5c126c2fdfe460b46c89976b02857e086aea2e61fba5eed0e4c4c0f1e843524aad3723f5d55180701bb94b87229e9753fee6d56b03e8966697e69b605b941574ecf0a733941d86861c16cf935a2a514f88ef40a870088789e811a43462a81e5c85208021086f6ca5fda9837051ecf0a337cb68ea3c136762b654279c8c54e590041f968e371b802038e36d8c5df3570141671b5cfeadbaffd7fb269b428db56d31174064b119508b971e7917ce6fb5497195a05ce5ac8975bdc266732b280638fe83968a65c7e88e62e97e0a9a17fa728824ca35084c55d88da80d7872caa71d091774707b2a8d3e93a2292db0151f5734402f0008db4a08b7c2d8be43859c006a62f0e7fb6537f6ac8fc43d09b88f122d7c5432d06460b30c2001e340fbb4a0f323c02b8b412a447181a116844e1e24e11887971cfba6638ed6d1bb13062e85f8623e6f6936415eea6fd264d9a8e3551712ef325d48560567a96c6ffe962d00f5a059d7bf33da7fe2279eeebde8433b00ad5f810d7fb625e6b8ff205ecc732434406f51a5b86a62a33b3c35a974df0d0e128f47e5d49c0d2b856718bc9a30451d148c5f51e77425fd6a1c37b0d797cd6cc029a8ddedc020518f7c2c102a34a8b416dd85aa7bfeeea400ec3eb47405c0d611547925780f6a67cf6ec26464b70d8927a20d1078cdc01909d1be1d52a77707355b61ade20b1177861d8ccd01c435d3910b85906c78e6879c02288d5921ee560d7906f18223ac797160110207082dbf5ba5faa0203332d3eac7534ef46162a25742a440b13a882838b091e2ef1eaa0ec1c6286eb15f18c57b346dcb058d93a085ca04d7b24460d148395efeb08993bb5a67c800752d4f2067a00798f67453a921e5672101ac6f0b3dcc15e6cbd97f3dbfefbe76e5e2768d2272ce7782ed67188bb5158f769771b323e4fe838e566a508e3f8bdc84d1475b449da6266a665c098e195ed38c7d54408c954e59b78ae1c0f2528d779380f6a502cac503f89c02f5d1f46a8c56867e5526b0e604dbc1a2195c980d61831ff9831b8647e20130c5f3c900259e15d1da5695fc89aa2a0b1f3302c3adb87f068f868465af72d2c264a28dea23538cbfdacae66038d525c9e6f5b4b1dcae87ae93635308431adb0f887b8494c650b89f5b33d25ce5f5604dcd87cfc4448ec077ce6a152d0c954489c8be4909575b0052fd516c5305cdd9e01d9294878a4e01ed1cc185dcd43f318c79cbce8d09f47e57e1da7fb1741035de0dd0e2f4fe8dcc184da5b4977dcb84f94bf6e3c5c1ad2850e413d075c1f59b376cb0bf9c3ba1aad0fb4870945c70a4f376f67e24b04a640d0c4504fe5f13a292a32e54df1e396046768c641766cd5bff9ba508fec52e54ae1b05a46090004a69995b01d1a754d3c2e4a5b686f413b5a92e2b62130f37f1dff12d29e9861366a1d379a08bcafe3b7665457c56508e642bce2c202256dcf200cd7488f3348fddfd88529b0ab754fa9dc12a5b545355fbfbc65dd206635f5f44009a1a6a4bfb99b0d5990f33dd67207c2f14b220012cebec72616ae02f60420ca6a6b861e9613d4413977f618590e4f5849a82a09657ecb51477076b1826a6861c21755b9e25111417c13aff1d109f3611102f514487b92700d19115c9d0a24593dea658c53a061cdebcc2f93445565d83a1eb2d8fb8e5a300a98af15b30618237416ea8f41902f6f9b496d31e8d6c24cd8f5e9e305e0141dba80c835802e8fd4a86ce0517b2b90ead1103ddf455a75e664aa3c13046eb53eae20d1f0ec2171e531245e571c25da123dc77e1b06063bc2d890c870fb227839556505a418331498820de7f84d0a7eadfc689f63095fcd0a9b96d80e20a96e04d79f109f59daa0dc831b4f31667a84feca68d2c9bcd0ec2df902f0232e05dfd8f80668f0d8863105171586d83e878d3ae9e87562e2e0be0df4a7540bb1f62d22b28975dcb143384c9ffb7051a7377fa848816a1a6165216f299ac9f0eb3c13379a9a65a37c29a08f7b5ca60e703388040ab2e0f9f9edecac72f4450fa95024c13532a6b7679e1444e62fff8a9f4382d198aa1c0e4e7d0913fd01dcaaeacec8411fe98e9772d2690ea211e8733b68e596295fe0bd30851e067f3d5df139d10e04f95182923dbc7a04d6c17de6a5f6c18103cb4ba19d0a5262f85faba3e9aca6cb5fd9b4d3ffb29ab2b45afad0a6d0124b57629d0c0b1e2b71e859fe745e9254dc898a282beed28c409adc68705812561e10ff93b62177f3a71620b2aebec38c32ef21cc0a76591b5d1a22674d002a87203ea46e237119404ed6ce8ae1b39b961bb33f6eea42d498ace063f1a1f2bc32a123d865c1ba36d0234e472b9a5f7bc7d37d40cd10b08742de7d0923ddad89ea3a503b838009acc9561881044293aae5cbbd4a19190f23c064e97eed720669da61f7c0752ac2a2cc7886e6ab17453299409a888059330ee742b6322734b89791f550bb40bb6cd9998ad51508bf2f47e660dc2b3f922d45e58b98c0528ede4515d416b711c7f4a12d30532aab07aed285075441aca0b029060f93b363fdd34e704ff392c5d88ebbdd9c93f34a06842252d65b63b1ec5ae10c3b6d76c35bd25596fe83515d2423c979cb01c199d2c657ffa60acd52b6dbbb5b5cd6a560155c79efee534d6b158c0a41811e292878cb2f01e257b200730a05c943054e2ae1726ab063ea6780f3838782f41d8cae302566cfe669a47ee88af71320e551e06332a6bd73038e08f24f55b0a01425b1a2ef917f3217846ef50b2dfc6ebfbd9f105c66568c5e5c7551da47065e5d659cd92415b739257a668c958701491ec3bb4266a9061dfbe8be448852f25d525debec0316cba2cb325d1fe09dadcbed33122953fed9b0fed4bf6319f810f45f6b1f58a5ab2dee38b6e777161af5b8ea60008d75154d7d7354ecbbb6b7d4de39877797def1affd8b7ed4d9f0fbf43e43c7bfb116b76ac33932fc27d4107fbc987e42fb8597d8c4bf8cb26af6418f2102cb245a9792760bbdecab359e43905034e646a4affada4f844e4536d3c411e9623ab415aee61f215922b004d98c18e71b74ec2cd92d58433287efbb9700ad523cf24aece6b30d8965c4aef6cc41972859d0be6473f5ed05f476295ee252b5ce5fceb6c932d3e2bf695ba16716b8507a87db37545359747b77426a969d7f10f2ca4d06fbc73bd752949be25b6a56f1829087827b2d715ae272b90d5bc20adf6475fe1aaec9cd74790df01e540f8fd3e93ed9441a09eaeb52c73dff7b71b3849b84ff893a0391c9f0085702b11f16aaba8ff0e3604fb50835b140af2a742b352179c89bfc0d4e013a98bc0d816f7d9c93f37b40b68d3b12835b3d1a759f50c8fece6258b5b11e2bf0bd0f9cf160c5d162b51755ee79d26218120c03909aee1633c3757a3eb91cb3a401a387ff8b24c18f6d31f6c92abff1b7969bbc4ad2d5b7d45c2cea1cfa1e622935c00539f6475204b12173b017887401e4be0ae67061c874d90b05495e68ea178adfcd1328ff7c08f01d61e4c389f1efbb1b462ff8e205bfe74f3c75362fdb5626e9f389455ae4caf39b00088fed13b1c3d43644eeb04f070d57e6b620a6c7cfebb6016c0897f4bd7568dc50cf105fbc9311391ed6bc3f9a93467a151c353986e030228e5d69068b9111dec876a91f4afc5c1c13ad3b90537f38bd235e915eb0b85168199f58b4c6474504bc562771f0833b41a632658b5bee28f371ca848f1636b73fd8dbe0fa1bcd66814b7843ed042d6743ebdd707dcdcd39bcb330774a616614f698a01f2617d236e2c8891d4860373f1917d3d4de40dbaa2076d69394033d8dfdb6caf37d1f53a030955cec1be6fc131cbe153cae0df6cc8646e5cf289d96c4e7f07bf035d10702e39742cb724f55299ab3509009c42934188929030682a837e984135e84bb8b08a481a948e63016477d93a377adc2a804cb3278fac40387bd6677205f9b3221b2ae8cbbd43ea3965b58c78be4f09c71ebaffc76e524aff609021074ef9b09a274b7ba62d601d05a50c31869fb23315d6c5d195aeee1171e15d20454eb4da390970222290fe81b0c9c795ab15a48da09ec80edf7067dcd1e488677b3546db280fac43a7d1cf00af8e7ba02538e711329d1623d925bff89b63829990a1e878c38994d687d46700e2417f18d087ea7ea9698c432e3101b16a0a4377a356ff10838e9c358a38ffae21e36865b199a215b9b2332bd7c920603e27367724c6c2df1292a9b13160282cfdda2518838b1098ab036b4f3b0b1a2fedb939fb6f3e4e5f01317da779f0e780538334fca2ea1bac5821f8385ec1f2ec32b19140c1c6b02860fdde082e5e446c2c08060bda3223c2e8b8d5d2648744dc435d2287c97e31d4ecc088fc2ce16bf4c05a8acd1d59071494793b80ee41d44432de6b8480b664df153a8020e6c140d182561b8dff0edae34acd750c18336bf9edbabff981c2ab4ef9b67d6e16ec5975b93575221e1a8054c811da5dda424dcb9a37c873d6d48c7b3c885fb395892390fb84e3a38fa142fe24f0ac0d434ab14c25072d2de9cad4943718fe1b228592bdd69964c789abf7390c69c1842ae08ec325b6422017d5d23bc69484b70815219f3077b966253dcf87cb17250b4ef00c621b25e1f5cd123c30f04a99516a831e3ade53cb3f224cefcb900d5207e26797d4a44844451c0d9b067485f68ac6fcc03db623dc99bd6395cde143f0d2a3744eeb1597435ff1ad5bd2b50e8a5ed591b170b35415414ce271129aabfe8fa9558876ee508f92e94eabcd2d755986d5793dbe36ef83b1117a7264cc4d741aa6135894c5f5cf751d4965fd55e3b2e17060d33f45a91583d9f08278e4adb71e0e26e864e4e079a064d3583d96871cb60053f611c0284eea9bbaae7202df7cb3bb10e8565aa827c05b307209c890f73dcb4ea156dce923d578fa16fd5db779597454264772701fe9e9f858758750102f7da75791a8a1294e206e0658efd6c26e8965652d4892b7514c70ac83f0b998d7024ea34d515ab93f85c82ee17e43d01f5b7c48cb13b687d1ca54b1640141200800a2f56911a8b8aa82853069ddaf66012cc3f6c1f8a340bff67ecccf45bfd6a9f031f6f37f87d6d963e7f2e93c557fd3f7b3b28f4a672c4ea00829584abfacc4efc1c1ff78574e5b02a8bf9ff229bf1e50bcdde0603e3cafdddb374f19a5dbf4cdc7ec994745951d71ef4f39946dedf04b483c4e8d9e39da357bfef40ee0914dafe5369b2ebef0cf18d4535cf0ee296ee3bfc6bbe9040f71cc361bb368b68e1f3b38a5d0f35b357d1f3e7a54792d9b1af5a84f94d3616e5b9c577d42072adf36e96d8ce1f56151f7845991e93cd2a5f023c42e254cad1be990a36396d0200a21116a7afbf10c308dcb202fe115a264f12084b31b32e1988e892556aa14b68c83d657cb9fd74d80fac6266c19b17aeccd579c48551421065f59f90b0eed6e51a8d381e3ebd2a5fac1e93edcc52ba959fe687dd0bf15a62d271691236bfa5614a2d4bd80a91900b91bce59b227ef8b8fbc2bb3c221b1c9d4afe1926d7fcb90a5193671d2c0bc7f0f9fade4a8c742d8e9a8252056f937a771d53cc75ae83824c404fdefb0dd6dd4ba93acb1c6fcce3b7e0770705d6cbf30c9cdd9a02a6158afa4d92043ac6a2b05dae97856fd0008e417d6345036829d8cd37be6322a20bca1cff5ae6fd08c525c8fda31a151fb1661080cdea5858a99f3a5e25552b194d025048441370bfd12bd187aea4f08d8d340512e61a7823723b48770c7fda02cd45101b1c276799180bc375a6910383f93b60e62789c3407e587322ff0258d8c25b5ca3800a59d5d61655c919dc3ec1d2cc3de6da90a483b23c0587dd8060f586c72d02525b67f6d3857eae8e80ff881660026376734314003ea52e3a3654ea6b3c8b0877266d6239421926fbd2d53dd87ab40df0c19e466c623534acfcdd593eb5f72af43b400712ab7799aadb6f6da7e95c97310aa43d9451097f077dd6cb601b6a4724410864696511d90ae181b71b23af5bb7a2d0129da6d43ab643a93748c6768b5f5e91128106a363e5e020342a7661e8455dacf528d5b876b59a711597cd26110a0175b8daecfc4c646b395342debe32b4028e4567b4d7533b4a5bd22a7eb48187b1257b53e49356e85058dbe0f02a1d4fd1508319bf588ad3e4bc77a04565a3e27353784a3469f0752ed1bc76b49bae667e9d6edc955c53ddca6e9bae21e56dd0f4128f38ce76875ebe3238044eb5668d0e0f730a5b9e1bc2ea5d57e226dba3dbbaedd866cb4ae57aec32633d5fd150cb1ddb84aa5b77d4a686f0a071abf1ea6b5b74e579274ed0fcf408150d359f55be735298df6b3274069c3edd1752e7d732bad8f8f808110b3d94fe1aae62789d6edc95522bdede3293020746ab6eef674ad7219d6738b9e31a7cfe01564f314eda535a6bd02c2d161d4a8d18df93344becfbec845f89fd4a3acb91f32e5f7ebdc5e2709f29f0cadf0bc7dfc0759f5707a7bf03fd90a0cd1dfdef8db5bcfff255bd090fee84dd827c9da6ed737f704e010c4e42ec41f7b77e82f320b1ea2d33fc92a70487f7ba7fff4be1f8028cdbe30b103431fbaa0ab7fbd88c6004fb9fd4af5e0c1be917cc85d73fc4163f909542ba244883507167566c96021699e0754e8d9fc0f5edac2c2d4046b4a192a2681e1430b18d11d2454965df7dbebe52e9c90fa5e0de7efa61f5576fc316a36a17166d84f674080348781eae1d840772156751734d28d90cfd8bdc2cc791f874dfd7afcb572f7bfa39b94fefa9aacbeb2e6d8b11fed667f56b07329463e306c5efff263ac4fe08deb47c963970032bb1160c1d422119b9ad2497ed8a37e040fb983ff5af2b1fb7f80bfabed6dc251e13015dd984f99769bbaed1ec83c941d7e880834c29eee585be12f8bab6978e8f5ee0ea216f27e9daff5079901da399756d327c192f6a48365d869880322e18c572370fe3a0f724bf22fb7abc157fefada15473cf55fca1653a56a0464150784821d6fba4b0b0cad77b38815423bde72eaa24b5eb38a67eb1549061309d134405e7b9fd1d8a27af697beebc6dd580c624a21d7acc42a5624a22ce972f16a4a7c9c401b73501a611e07f285f42747c157ae801b8a2de289924794f86779ce4725a62d91aead5948dccbdd752219682119b45bba2d0c6854b04919639c40074ab1907f894662c258b4c6268f2ce49acc42c9cd23a5c674591dcb8bf0476481d205f8dcc8e8d0f3c89f0c2b5fd1619be27f8accff0745c3f9525a6865b10885b359a4d42f1ad2ad821a1a64818cd3109047398d50be4a96e37b6f5e9262853ee67371d1f8fbfa4e4d0ea4c31cfe6bc85dee47fc7439fa2b78ce2803172d9c5b5962665b162b28422cb943cf1b5b9e78b5baef3a517a9a7dfb372b6a7b8685c5ef8fbfbb21e8000d7be14c738ccb66a631ef7584aaaf4860d0740bfe0fc21dde115d4374e8d5da5d58e96d319179a91168d93c79f1d621bae6d1b792bd9f90812053ca4ed4a90502cc2536cb80008f171fddf736df8a7a62986899c8fd7cad17028b0ba10d08dab614602261ae2b98f31d3f4c47d68de2abc3eb6a4656437eca6b205bea11d53e8142ae95be95b3da8cd18ef76ddec7cb851b7622d506a87aee324bf452277d932e4b7f795e41d434ef7058459d3d0eb7ec64ee8d4954ea0c4ac23a448a20d5794dc2c380d9bb3cd28561104a0bffa92c15436b516a7d459e265472d9540c1c849657fea4b25db5f08d377f8b0a1dda891a8f582d5a585bac33aef7026b17b73e43d8e1f540d990ebb9fdaa4399a9aa65fd4b406bef88025aa7a191dd2554c88ab39a93b696307d3656fa01474cdaa63fa00e04ef19ca1d428e60e2daaafec19b7b505fef43dc6731a6c9e3b8bde6953f40b7678c6970b825e62efd77987ac940706b21f5bc8b4e69a19b717a4bfc659f88d2579cf97781dbb9134bb82dd5ff48752606c331234c1a71bf3adb953b59c563fc752e0fb1e3982f83d01845e654fca040e700e69b193ec82fe3e38eb87f456078e88d5390f0b20cdee94b3ec55c343ff114e57efe2fc35ef73a7d62848ff7dd405f6c6b769349df166b12aea1338054674c358903cf8d8b48bf737394b7f744544d2feac34c37e72c9f5a59bc2d557d23962623bc62fd3efb72b6140e3abf81fb865ca7368ed6d46a7a85f224ee5e9f6884aa0d9164302b3f80510360bf169168ec1a5d8b38bcf19e32d84fcea3be09d1bfc7d701b3fb9ed1d0fd27d9286928501bfca543ef61ed20a1d4bb7631c925b9ac320c5830d2cd81df5f8e8e0bedbcee622a6ffd305a70de82a53b876859b3abebc5c901de69fea666d7e609a0891b7ff40f467483b87bd967f0c6cdab30795e5d715d559c9fb8fd95b789651ab5318a1b92cdd0db88b63736c3f12e009631799c125d08ea60d12ea03e806a438a3557d2fecc8dd28524555f863638676a8df56ec46e602e7122d517ab4cc876155f1fd07a70f50f71cf09a230f5c8864f3f8cd2325a822734953e5bd85aa3ed48d4772516d543e691a7c759ffbbc39d0375e4972e4a8173d7edb990a66e54fc1212888ba79c1581d7bd47e255084917dbf56f8d26058f929534ffb48ac8c558bfa9a8b9db2722c52df3085e1ac2a87660af55c4d5685d8c849c8634ea0cffda4d91ee1674e2018cab89a554a6f8a7d974ecb45ac30513631651af6660f9f45c7b1919bdcbe29fe62980ea659c28e58ef0f6440d2a59b764c30901cbf4afa9d72b9ed69d275310f02fecc99548619ba50de5d1252ceae54035be7437774dbeb7799901171b54ad0d6540098b1667e450fff2ab2e125998ee843e44e9a6ccbaf69aa42a7108ca0ad3db02fd1ce0ee376ed3d0300b7fcb421f11af3330032b4d9a7f22415f3cfc5761776e880566d7ebb32956d0d9a33534d1c3e326b9faa744da12c1a491fcaa50fb5271250a65ea70eb7a5d67523797dc0ac05b7593602a46634c29781b6325e36949c508e8af3e76d84f534a897ab4ec3c124abaa0e367cdf1d06b97780efac5528e477e88ba1b87ff8cc8901babf44e0b19d6f2f0e551212d12a7c995638ed8f86457e555586b1d89ded3506731853815a27fe195ab9dd25788de93121aeed1eb4bf8172ee91990d8b7c9b3dcc48e6ae6456b68386e3c91761d6bd0d43371515b4134bd8a59886633ffac12576c5f3a09d7424de3ffbbbf4793efc90446aaf3684ce09a50e73430aab60a5e6b588c99da6fbe825c7c9ff45449df600b20bf77ee9d039497d61e2d8ab6ed6ba045828635d4907ec5c7f580701c3049b80284817bcca4506eb12cc7b54e97e483c21ebacd67e2ca1b565e119d49c6e84813ffe9da21faddc3c4099a151cbc04f76f22472703e6ec03f218c3e5722c66d3e0988b2f49108a13f32f5445de7e68b10373eba433ddda86816c6bf2acdd40bc07d9cc31f4fb93e4f2c4c0c41d57daaa55b40b8eda685bfa3be43ef755d875e4150870f48268eeffa84e9f725ac38360fe6c036d711cd0d67cbf85522a2c3788fa84d802e485ecf7b241858c48097f055432227d19ad9b1d8a0046ff16f668c50a9961e4d69fe24f2eb0f0d8a215c99e104c785c4f26970e14a788c8ae84f3b3cf111f016610b17805e6bfc7509dbe5111a3f69f7281235e1e91ec8acd47d273ceb0b05740cde61c2c8120f0f110808bd24ac59d11dc3821aca83849cbf868118bf0d7d3f98633f4a407cc4ed7a43d3ead57bdaff4eb2e5bfeb932fba6af2f7bdd0bd4c60deb887a738fcc0dca647b4959d1ec76ed97f1e0b72edd5a63924e0119a1e0bda7706e1122d0d72c3b7e0802876cfca8714a0dd5fde04bdc655a8b8a984e063bb256ea33c6caf8017a2d3b6193a868f6494b506c944c51740c6e1fbc69be9c859daf3f16d2584e374f35fe6b8521828f6023f939c68842198291ec47d82d13c9355a837cb49d75475a6af0c81b1bdeb81c42ddd4b186ccea18a89ee699bf8c0bf77c626501dadd9e59c4ed5aa4d9b03b8e89121bc67dd8dd84ba0f3c18014b75d6a7d5f0eb0981546063bceb4fce36e1d4a869a4d35e10d9cf3b03a7ffce7e8dd94c45f83688e1fe1a4f8f0b3712865e2ebf17064cdc3dec5709d3a1a1b2b2426f4846fe4c19e7791b9b3116a2cdc3cd84cafc528a3bdd167a63b372f9721a46aef15663c7e7615d6023ec6f3a368c1c24661a128d0fb5f5ef0a34fb7a68339d0ad5a78bd3a1fcf0561291ada4fd1269709e02076abe9eb848fffce711b73ddd557f01aab18e773dd45825174035165107bdaba788abae04bd3b2bd56fe1b4107deff3e2e1eea6bfbbfac25e1a277c02d4eaa3f9788a02750468e6b09a77083a6a047e7945c50f979a82f21c49b4c537359682549599251ce618b011554411cfb1b244d6980da2a69a45ef3d1c0a708003e96e4de7e4796bc52ab1ea4a2104a50955d5cb989469db0f2d83230a7024c2be5108e72a04fdeefeddf883ebd6abbd52f757d6d7f25e51b7bf181ed348a838d86c4a54d208a2944326018cbb01fa3b254459404c31930e92f708f42ca7254892a7b48a3d650cda9c609b016bd8103ecf350f35795247f6966aa65ff1929d68ec07e65df15ee9a609331e46676e78508e2bf29a6564abb910a462d88c01fb43d329d436d964efbda59432c9e20370049f04f603021aca40426d32bede257ec8f48ab60a81aa4157d2ccdd0d10cb4ceff566b5e6b9905716cad83dc0e0d21ad9839858a2bf436640a637cf6a0d93600bf996dbeb02ac4405742d234975e5402412c50464504480cc5a6b2d50ad592f7119888307d50e90fb8e186480ec430681206dc82fa0404005f88024401e12db5af08548ab2e0d13bae60908b556fd839229dfe8960a7c7001103ac0554bca21df9c3dafcec16bb797dd25d28a446b06b6edec1b570f6cb01561db85a264ebe11066b6b62052a135b97a259feab4d43c2500fec1ed5e85cc64ece02c6b00aaf7822ea2d60a921c3292946f690ae2fa976908fae1af59e2065204c1592e69e41bf8a0830efa0c04414b53d15e10ec85e400f2e74852d8735fb200638c31c618037170dbf75a7b814a24510014cc8081174a5a59df2f94958562c837bfcdca0ae2b6c13718d61e5435e811fdb7a1efa5ea1544b523093b8ec837f06df50ac2da0dd5a94e6bf7f42fcdda06e1738c3fecded841f7f97dd8bf8afdd6b63e0bafab9318804c6f05d885dc640d35087f4023e4a96e8bea3178aadb92915dc8f549be799341ac576f1200f9067ead20da0b05d666da4276a8d6acd76acd0a69c9e550adb5bbde0cb97a45412cd19f33fdc412fd3fd95620529f8c2485c9183e02a86660cba50c456c2e324f7568bc7ee8a90e0dd5d3f184f74563165169108d8038d3695175104559a64e3d3104aad72b8ba73d05b22003183ef5b61d54b7ee8d2558fcf55bab5bc763071b209ff644d7eb4ff5c1edd55adbc482f6f5c8ca245f5a85aaf20c8492db6fa05fcf17935c2298e905bfc51f13f4afd3a00dd7e96f28d17fc90bbe4978d0cafdb70bbec1bf8086d000742f19e47311155ffb915f4692aa640c1f416b0637f0bb56514abdec81d8b5efaed59a50967cabd5a8b5f65e8bb3d50bf16624297b9d00e2aca621f25497060a83270106d5da750e252e3b10870aa2faad59c1187b4c53d1df1ec473ab1848ba3456df34aae3f27a3a5692b41949aa5a37d1dfe51ef770a5b49a91a4aec7f01152d891a4c09a81e79e974b39c8a3217646926a135da29f66fa095d095d39096146bfa4f0252a84995013dc2b70184c82de0a21279e8cb434835e07fefab58d2ff83cd208ff256f69895c8b73a5dd80fedeea1863bf98620dbe1066d62a5fb71a84e2e4b6d6f6f5be77f6445f3fb982f06eb55ab93a7decb5d6ea4454441a7433dc5a9b0999c94f240a29c9ed336befe7e16bab9610bf21e857129156db7d868b16d2ebce84fd6df6f5ccd6d75ab5745a37bc445aa5f840d2e5caaa2b57c78b935c560a4ecef4c89a5241770296d41a158ebc11d3e58c1126a9ac5c162e86317cabdd2ed06d696eadc5350bb0a4990293142142c40319c4636a6510ac54e30585cb26c34c149d39c296d284174a2d28a6aeea6f6e53a273e46f6559eae810512acb058cd1a93107690e4ec69c11c2847861533170d812dc0d9382b3615c706594b892c2bc4018be5e589a7d61cd10bfccc890993ab2b9954b1232ee4165c303abb284bd345d1b52eeac031b9351c360e0c8b026187276883aaef7f585f565c65a7bf157d8979a6fe6799ef7816036c090c2300c65229810b01e198c0aac0a98979f9f9f9f9cc1bcc0d0808dc1d0a04183860a464adb86e9086b12755f3f92bea270412878aafb52c266cd1bdb13dc8604dc068f0fdc26076e03f5c2060449f925c3502e99626392e304cb41222748154521ded49299941997b032615c1efeb4441b95af1f5f510f546b12f8baf1851bfb1b18f65427078d9c3272ba7cfbac922410c3e8cc419a830b81ad6a8a4d08d5234629cf17374ecc4c098788e001cdd5e71b449f398b111b3e18dd8782e0504dd0af510f35eaa1c120d9c7212888839d7d08b3f73c030db924914762cba5ab0a98fc817cab0501790909faf1d5f38826623fbdb42bab6bd745064806441b946c6b926f05b8304010e95714ba2e23d6b69567344457ab37f26c9c2ffade888271f6edb55b7c5104dec8fb69916948445addaf83f5da10883626b97d66abb61f41a5add65a35bcf55aed07272c6a42d040c66b3d14eb060b55cee6d14d74535a67ae0d9493ba99727572907b01038f4a8e474e890c3b27d8f9e5ac9c2470b016c02a1255c74a8f330e948f25a7184e30e70372755e9c7abaebca19811c97ad3337766e39c99cb95c97b37339b293f8e4a29c5f7250289e3a6387c971a08d0b4d6294f92145eca90d1593932cd725b40e387dd4545184349533399aa74d2873684ecac6022f098a4e812125e5f4713a97b301b90cb9761246bfb8fa044aa795a8988db38d20c06a03a60db927670b66fa85b3079133b73586926918b4502f37627a085503e6dc71622585c8751634e700725761de50aede42d784625f504c60319090ece82221e754e46c3a676ec9d94072d4ca996b1c58ce3267063406addd99cb79d13bfd8467860a678e3e69ab14ec60da0585734c379d39386067aeb3c6ce386797e91044a95c600024272dcc7d9d09c869c9d19d9cce33078637540addd3317427fdd24b754c8f8c335752acb042a72e1b3bba8a9e51e3e53c72fe6893739c233820c849bb7214a4f172774e30c7845ea15927f5b2878d99d30590294cce496cce49ab206970d234f77e3afe63932729cca163e3d52442209783a2d12ed66439c3a8983387651627eacc39e1ce38556270985c4c9933c2c3f19ee73645de9b31c2db21a7458d156c4128141b9053a4ae9433945983f554b726ca8f4adaaf9a286974e50d3b1998e2844c580dd6a8e60c53995b03e4c7a7ba354ed5b6ad55e55936954d69aadcbb01351580dd76c0c2097df29708e3ae569e7a74f0a07a49f5d587a27c78fe8d627d6dcf6f50ad559f40b68e1b9344d7f189ebd80357670f63a9b57660bdbaadb492ade5d75dbcddd7fb3a514731c1370dc4175f4cded103f286ffda296289fef60c3654d043105c41677fbdd28a0278b4c88ee8e08c5b688a02ee9ab01cd1d9d938c106d49c393638f8032fbabaec50129ba366840d97bcdd7a20fb74c794f5796a58d1c082038569c9c834e5851bcc0404000e2822c606cc489a1f7242aaa75b421213d3d10c0b4e06a61647f7de3434dc5c11588d45dd71fa121287ea6a6c097735c2d0b0b9710fda3181799fa73ba62a7ff35274022956a8844a035a98e244513903c74992375e5ebba5b1dd52938d4d8804a703e378e06c06e2ca1a720e8fe07078876b4ab271ed70562f80a73bdc1010bced374afd5a15d501df5ffdf12561bb9ba4fd2c6e5737698ea041a3956fb91a5df022b0d0a0dbc988b3938164cb1af2435503638cf1ed70ea94f1c0b5f8763392703632bb195ffee7e96ec694a4322cd8c6d8c6ec94bcc031afafcea12d50e37e754aeb0fdfda1fa291ad1be991d65a6dd0c7ba881abe25e9102e5bbb530af3e2d39d52162b0fa8c96262430718205be10a6d90feb651f29eee94aadceeeeeeeeeea63a6a8d3e25bbd80a6206d415a8167d6fbd679f375a073fafe14802246bed4d4713f5ad8bdfccd2ec539b01032e7a3cdd291d79513692189293c3d184b54e7a60146cf28215db7bdb12b5c57e6ba7b576f3a8607baddd293d398db0d65a6bed968c9fee9470947894722b28c9f091f7fcc5bb24342f7bba4b129382dbb2dc005b0ecaa15d1294b74f77494efe56ba90e46fa5ed04a932861f729ecca35c799524c3b694d4428d18d51b130757a9848c102fb61956d23ce14155fa50a19c03048a470a37cc183196cc3c315e64b1c3244e0a6aca2e664a295faeb0983daeecb460735ac1dc09f92ac17684a3ac8241030e0ca43978ca3a5f43d8985c593274e52e0b10296617931f53259714be4c305fd0b10389a9dc428a619a90472aac44a16b4c2b29cc912d245009d3c47603898985c8181fb67387c9e6a849c99152a9678f39858e0dca4d0b68ca27634aac2e495e306f58915162a351c47c8342c906aadc72be98612be1858b72471d17e0b84893a38c090396c9c295f20428659e25a610236282bc4082798e50da71812bb5b838ba72078a9c2f6bcca918cc2c30b61830d8784429a5aa4c24165018d2a3470c39b0d43099762d605c19b3f5009b6a23e74c29b6339b6831974c797281c891b284a812cc092f9c6cb83d364cab3d493ef4849993c5ab44b265961163eee11255a58472528a49a2470acc0fb127734dce84a1474639a6a785ad8598e9e4cd132e3364a47809b3624679526651c551c13403a424c3c336c20d332acf525925cf0b5b0e3b5de2984e6a82982959f8f294432c5e7278651aa932674c955ea24a273e969e909c34c696cc2263472ae0090266f2e82acf6cd99ec6bcf0a2bb6233e1aa6c5255de99ba1365d2f16112792a879c65f02cd5c073545ae1319bc06c4d5d65922daf315abc9463ae985e57260955361c53e59e28b3ca8789e7a954e1b4b9b8b364cbdd392aade03901ccacd2d5c25679648cb98217dc9512cd559baa335368a24c2c1f2d9e6c36ce387696ca37768ecc1c1e9b0e302a5d495b25d718938917b3ce95d2cd559b2a38536ba24c2b3ecaaea71a67589d251b8c3a475478cc12c04c2e5d2590ad126c8c69c38ba974a5c85549a76aca1495a812061f604fa69bd34c23b654fe103b3212ed9e5bdae798835aabe5bf0736509d12580d3a005aa7510fd5ab530fd53fccc822ec065f39f8aa0297b556bbc90e94be1d010d61bd7a394443d0c7b7471b4c82bcbae184e71774a72d4f773ad63c039eee743071acf7a7bb1d475ebc333be219fe661eed5e0ad0f71cf44b43d06f4f71ab8ba8200e7aef44fdea462060f1f445d4149e57cfbda741f6e2f0edba672f066b2de8b1cc2fedfb482b0b41ecd73dff2ee897e679d05f32c85aaa3abbbf5eabfcf5ddd31a11cf9a7641152b8a3eeb4645df5b3ca2c0d6abd75eff76704c7167753c71ab4822530fedf4b675f2b9f7f50718d93a55cad741bfde3444e8177c9987b4a29f6fda4d067afd79f8b6980e281db86ff7c6beddf37afa2c76fcf8023cdded40fa158d45b0bd968d24ba7be620f8854dde46bf79ee5303123978947a18fda3c93c9016f41f088e34702cd16e5d846d1c1a5ddc0dfa6d44e1b9d7f5236ab7fe91e2877b3cd1ee919e75fa91e8d0e4adc353a71f60e4a29f6feaa1dbc11c93173d0791a72f6281c23d7d1c632ff3d0739079d143bff9f82d8721a9211465b4998fa1e8216de6e38c06bdcc43da8d05d8e1cdfb79a3fb335f711f03cd380061fc83431f1f692038a290b948fed06e78863d4f45d1087e74a3eb9ee823f369998ba3e8230d4d84a4e83ea2288e331765b4cf7de8480b7a198dbacf38862842b0d6c21926c70e2e7bc1040791a7cfe28b3ace53fc2376a03416df23099ae74d54bdfd03699edfebb74a3159d46f9d08457bb56c70407d88a7bb11b27ee714e694f51d3aa8e8dbe79ed7cfbb794ec7122568d1a2858bf7c8d9075ceea7ee7d14d3a0ef403d50f77639afbf4ec35aefb8c147269bb94fb563079ecf3ae4b16d588915fd9f7f7091653ec296ff5c7683cc5ff1861ddc121b464a650ecedce83ea52e8e764421238dfa3b45ff8cbcb5254bb47b200d6560fd3c046927bc15fdb3b65cd7ed05afcfb07b8e6d4561dd5e9a11ee6641b463ef89e0f5d89b4c1396af1e3279bb3b897cf5d0c51fd0fb1b4d84dd32df50d44fdde87e7bd1cf37e9e2471a759346fd94bc591abd61577122bf08d543ff42af5eff0b7d457fe8962422896827784abb854e43af2e86e058f4f32250eae1665358f2d63145f5108734a3fefa3d82e486131f1149547d453f2eeabf41441a74fdc3f5757077425d10cc65592b55dcbf95290ef04d0e0de0c38d6cade592df62aa00a7d4baf776f616c4758f8487e997a2c5ea15df4a5e3a14f68d1eb0f803807e1d743c9e188bdaa81a358aea449804bd2c22ad208aaedf0bfe54500687d935cdb94d5d1f3edd35698979156a674349d3111b53029023a804cc983162e8146d800093160000180c08060422912446a250dba90f1480076ba22a604026a74a0261280a83180662188461000040300060100041188ac11854fc696ae111e10cd10d4adfbc73855d6c5157cacdaeffab8fd83d5225acdcdc68de077071832af5d2cdfabc90ca8e2f7f8c1279d154a6e33d9d7558cb590c132fc61a6a10901aa598ce0cc1f20c0ccb2640a9b6728ea42ba70bcbbf03fe651d285140cc105c091dacb07029941d5b2f1e33e25c8b669a1dfc76c6e6ac9840555cf00274f7292556e0b781ee185bca54e5e5559c2475ee2120161b6c648dd381deb760b75ee8d7b9eb6ad3e9a0d46f6f4b2f1cdee79429846ae5f5beaf9532df5bc74d7d3181fd89326ed2c0238d99ba2300cf3b1c8729d8563f61c925b8630918259a2b756a1069717ea2177a486599dad36d5b4743f8d45b02eccf814f269d4916990b8615c0150c1805a6a9cbe9367557b336dc615b3773ee07ec408d282987cdd98a249f89508a3bed15e2d030f1b7c65196289326d671f83f4c878ac08855dd3d15145450e449dcc2e46af93e83280e881ac1020d2cc3a45c96ef1714c1c9b4ce3e988238eb9141308294b8486afc40e39761217f810a6ce77e0eb6bd62d73bb0bb5e9de13343551cfaf0293203fc8ed9a3d3179c0fceae403a7d1ed6aba382889dd9c365da81c119667005bb2a6264678abb08f9c4f86c8377f47c0c10759f5adbf434f2d7a12ac4ba24b2c88528832468a6dd6d7603127a32bc643541a8053100f150193110540072110aa43d4d604e40eb86f280a677056eea8d98a8091b66bf68ac3d5bbfce958c2bc68ddf448399cb0e2736b27cbf2c090e7c3447f81609b8fa5236dd012b3b2e7092ec10984d1d100d3e4cbdfacc0a81f6b55b82fa622ad9fc8283971daacc47ac8a602cd2167804472ce35b200b78f860d56d07c8bf43cc5c36226dfe2e8603a3e007e1fd3ddf6f00b8a51595b6d6bcc8f6586c39f7f950014c6acf1d7ee5dfc5b21e7639f2faca85c490d85232a757185e1379443f4766b451b3978dcf28afe121259f5a428cfb9b8a05fd9dfa241b6f91df05bff4a848e24d692ed71484f1d701dbb6240c88174a71a434aad0b97aa508af2a69839902773466c15aa40a7e8ca38ec26fb4e9a88d3ef3543582d0950342440f1d07de537c36772a18692039a61e08724eca09b2e20a6bfe040f4c0cd8cc82f0e68cb5b8cb10f300857a0de483f3e358fafb7ca02f03c68657034be02aaf97dc25cbe11021142143908d6ff1517bbb0380726303058f17ea7a30baba110dafee98c9a78d0289b6ab0c425e5b131539ea8010898a0ccfda467de2acc708de1b9305bed8fee4859cc9b998872d7c3d40427dc96ccc69cd5f37ea15ab0c19ba41203c47439b7dca07b3e5849fd63221580870aa7f1b82d297dd3be103b97117fe3305da99aaa26500d855c84a1bd93cf9264cf7434dea47152942bc4684482632cc0d2a8da1d03804eb8638036b6985d590f6dc058877b64e9487a434499188dbbc8fcb1a8e226da167e70e075fbdf7c048916f998a9f18a96f32da0bf692e218396ae83dc27fa11a79077bb774e01c0526fdeb10ac2d3c9d01d230d23de224ab256f81d95cae2704eb805268241aef58b49834cfce937034b68750c284ef428d3655ebf60466ff44253edeae876ef04a7308533c65f3a2191604fe675815ef8ad254ae12403751020103a396798a16f409de6c177c113fb874b652893761f5aa8c06d4372431fc9c64b4394024a02263f7e842c4a28b36ec22ea43c877f561526c6d4223e0ccaaf6eab4b6d5a12e11af82674ebcdb3e5889b5ee4eb65b828ceb40c77c219825f6613dcff4ea2a0e0d16d1664db8be1aac149300db70f56bd3b7d8406a156b11107e30d626d10ad5fbb75bcca7bcd41f140fb3421d85e5103ea54945bd62d5749a7a8423c7360205160497a5d01cf00bbbb96cf0a03812aefa0931a6d6917321ea4591dd0b0511ecbff0c3d2f7b4c230ffb2ffff626608b94c6fa52a698bdb39feb55787a42fef2ae9f537adb7d501c308a5bdb9b989a9fe6b57f3b49ae23be0e1cf1cb01ddfce3921218a2dc91280dbd7b89357640a411a0b9f52a181364e7790298afa9213bde4ea2c53f71d4bec974d3e234dc72a791404ec71b1136e8c7613bdad0a6905f138d70b201d85633374c3f9731ef882e7a96f0939c5238d25c881cd8050ea74cf1a34c6df452c976e59fd4e40d99066c1ae90d6dd1352bc401759f0e42a8abf4adf5b6a0631ad27b030c6507a22a05c8971705026832d817aac4ea137775eebf5199d576358929b989bd6673f3287903d8509ff64299b3ef273ef634a9e55c20e7dc0bc7199779071513d208746ea4342d61b51297f052a79c589f141f6b48fb5885a62609d4de4aba808de60b2bea5497b2599df07bd949890766321d1fd058489cf8c6007b19694f57d25fcdb1a1742cb12875d87a381db63700d6f236b9dc7a2cee5c5e7f93ff13bb92852ce48c39ae53f3c1440ce67f81a7744082950d5d7e2a51a26fc946437189e0e40de978a6ab75ee422d02036efbb1b301b35b071867907547f39288f674ea7a324062ae8cd21a2f510d52a46d6ffa945b3991c109be2ec2d87bf52471c8f25b4c44964ac3864e66c12b6cc4b21198f8ddf31b33da55434e883a41ac67fa30a872049cd9cd3d8845b4de12aa4f25ff08c414f9e142d4bf7ccb32ec6b4830a392ee26f58b33cfd46426017271918797bed8d8f4bdd09569ce95cd8224c843345b67e4d1bc6643d39e1813bfeb1d249249c56e445d599ba67895a07c43fa7164723078dd1d15c1e9a6bdb2759666c06d0a48b9cd36ebcc7aaf844dbe2783cbcbaf3826f36b7bd63331829a9e5a261044adf67f1cc81a01331cc06ee82b106540203e234cc3e20d36c6c69d5ac98c930b87f633efcf8945b08d4a0c9a8a43c37d30eb00a4dbd7b61a925a5ac77bd835c4cd7e3ed79bf85bafc44ec9d609af201db1c6bccc60ce906ad489d9e9674a526cab6d1c23321b4c097054f62f65244e6c0c7b559d85a45a8030ea72505a4fdb5ad856177eaa9005d99a8b42a9885af4d202eb0bd029488a5019b0f31abac32b9376389d784f8d4375dec91a61cba3fda995a65e3d32adacb5ad33e1819f843c288e2cbc0dda463c69b0653cd530da60a1c59792b1ecf899bfa7a9d8fedbca79893e047a31e925855e70ef875be3d1eb4294eefcc6d1b38b092d854eb75036bc24bb5475a066ae517b0c869eb91caa8a24442f68899ac572c3a81c16eb4921d276afb4ce383f741f8086f2210e3f8797928702898b743a53c5b4d59d8141c420e221db033f651634ecf23cd438eddd1b3d9517f1ad3301b7d514b68d7876e6fb230092a1b5eab1f8c9dc942364eee66e1732b188514d13dfde661ae15713916075a818e245c694e46fbad8a73e80f074caee4ec23398a30f83b0e76731f2524a8e1893fbbb38cee05a0f159ff844817bb5e22801c6eb222bdd7b528476a6c1920008cdabace58c39f5035be4f5afcd1a294a801a6f74d42b20aea1440aad492cfe492a60c503f4eff54473b9a1bc1aededc5249cdc72c7d5bfa1440ab67ca8f334ea48c93f7616a9d4b9514357a2e55b32c4f63e698d22d754b10bfb81c97966f138222445ae6959923db6693ccd474dfa64767cdc863f20f2cd1682b1a82f03e652a9c5f080e660ff75e4549610143e1e35dfa75d5bdb1c1bd982873057734dbb6530894b52e6c9912c8b9d45182d89d03208974d0b8749a1099cd4a3c3317a5c8bed30eebaedb7dac9ef639f070642bd6013df7b5cd1e3b71685bb1297cc5853610912173779da50f3f5dc400d1b30f55208ef98e7e401fc5044421fefff76913d8779e11e93bfa38059a96cb755ef7878214bfa9d04526be9866ec0d1c136fdad2da0d20b60e4d5c5d73f35dd8684354afb2b4a44994f63846f3a0d3b5222caa64010772638ce33f271a7f03b649bd8ae339b5a2c18fa4f87398839c87ec7cb41dcda1fece1a80ef23c340593706486b07b1e18d80f827ceda83351477e8ed76d2375135df7626abda085130034f22880e054d6f84f18528d81c5adb32cd85275d11758c30ae049c7225ee1e0311d04db57dd4af396edd1bbd36be9ed649f12c82e1065393afe5f314c8db69cc2b4f8811ca4e63115843018ebdd64a6879c86fd4db45b5e452dd8c3b58490081cd7b08c9d02482c9c20c3d9bcdd15723bdef419732b5c417dbb7f9f63d471a287a80b8563e2c19692e596e5038abe631affae8b8866e02085530f33dc65ef6031ba69c318ee631cbc0515c723d82113835ef1d998c264b4909698caf265a53630897a2a495c5c716d5d8c542e4233ad5d542eb18eb04f0d00791e082b988b574bc3f128f9704ead41cd77a9426844103a98167d44d97ace544a52243e87c04e2d94d18cb89f0754e6b9a69b19b7c7bf4c58b3a37589bae0e8c8ac3e927a4b25190d030b0bed72c0ba734409e3b68097504ae3b1f8a48ae31e323b0fe4e2a4d7027d0ac60acda63a2df71d58b61c418ba08b0ae987e774c204c8ec405126d35e30b300a838fb01444b75f7fda0e2838a3c749c6b9c8cf04dd644420a278bd85a41d38b81376fd08300f263e09622509dde9e1d751835efe0d7d56559986d160d432021dd8df83f3e41e32a9058c54e60994f8cb24978289123316b9679a738c056994a4ca0b0aef3fac7013f7057a4bcedc14765d0ef7dc85013dec130b174e3c13e266cfe592d74496f1decd8598c8db4763abcf863c878c6cf804f4642f306171163a8ee22ac2d1ccbc1474467f20f508bdd7098f732c60c8f41b303fc6113cc74afdb0d8a8af79ca06ded9c1497f6b439bef2e08d0d36a6a03fc0888bd6574017c5c9bb845aa2723878d0857f7ad68bb4a9695c7ad8f94695d6d7e4a38e520ac37b7c7c1862419144a37df5c014bf1a6af88d38b2293b0b2792c52a82f7c90081a0dc47cb1760521b1e52259aa8452a46de052d92140987b1349d1c58dd979e0254d7f54ce0c772c3dc9f5364f3fdb2c89de47b390fe3c268438b06a516647b3aba8e9959401ed982bb16b901f496afa453e5d6b32bf9171c93f48cbb3a2acb7c85c2ff7989c71b011db0e9b6d61b62acf96b3f08619e84fb1a0a5cc13ce9aac1bbddef4f8ce4f285c1b975271cb9c422f93cd869019817d34a5eaa920faf2d4b24df84e4a0bfa8670911c6f3a2c42d6d46c198a021dbbde9d046e1451c5dd60c5b5bd27344f4a80799f54c7526c7f4b2746ce44bd45cbeeac757c26fb9660a3587353c0aad0633aee0e4964d08e25def8072bfe54b420b050d282a0c3aa32fb2b721e96d09bd1f9c48d66cf325de169cda579b32f3ce64c525c773d0f79d54b7e17e0b17eeb4ab505d72114f250d61c5645b96e7f612524deeeab54c6a0bd71bd661864fb08baa6e2a078df09d9c683f13249f2655f4d018a83bdf4562e57874e1e34323d47a49e67cfd546deda6ff61d1130b1da5694c65723f416081d3b6d89f2a7cfb4b0adcb46f6e4fff19877ae73cad32226ed0458479ac746d41a3c22b45f1dd622856eb457ea1e4a0d283276fa9d91131b4e4e02072a17cf9c2b6e5d4953de16e98e34052b61c3edc5efdd4a22989aef84692c3eec2171efac1a43d46c4804b506a4cc3f20efd2e1a8383cd313bdecb2840899b0c49d74f753eacd82fc3f1cb1ee71cc385a84837be940b9a5b16da816af903ffcece73d13f6e993c40d7e9b9190d917046adddec151a39315bc2f9550c75f0c34d67d42321da1b751fcdac4c4de99b907358a19584069f280ed63ae55f3dc88eb41bc1a75abc9490c5a8227dbd6a79ee2d481c4fdc1989d3c4f751a0ae5e14b9dbf4ed15de08ef136c73e517c36a28ff34f6b5e3a4c940e6f5c07990eaab578b23c8faf347eb4acc01177534c20596a8792fa5661ee71b96d0dc71419a2fa94b71f0641058859fc6273452ecaaae827f53be886484806f7d5764fe0c38ced1bec1763a8c6390b2563ab10bd05a245b66c9af386e6ca65d23ca45d8f8ace2fd12423e62e9152fee9079d7cae11ee0c46404c63f16ba36d3269c5da10f215e23e5cc3bb1bc26bedc6c97e77903caa6f4933c07629f00943aa6f11a79c443bef83a0bb521de7439a2e061cc09ba5d8739a15a5196e7d58c9a45f178c59dd9162efa08e0837dd0d3ffdb3f8dc0755158a8ebd9e7d482e1221b461966a1536855eb9333e1abc4a5ad08e5771fbab8389c10e5d13fe08f5502abc93735d579031df138cfd4655067855640cd072c7ac4b62673cfed9288feaa01a539c6e1c2d0783946eae08666b6efcf5ec235a83213855303ca62777f8a70dbd9588bd7b2c55f4702e113a1442f600ebf58410a41a685c4d8a99dcf81693aeb1d7fe9647aac1cc74fcd4f9d3a40047c4d99f562a720041283d6325943605fd7c171ddfa2bc8c82cb737beeb172c02b4ceb2bc618e63a86bd1c3d95cfc93ace95110ba10286d1830410fd85efc91f18a9cad9363b419e3a28f62fbedd09c648f5bd30095a7ac99ec7fe4e9ff83502a40a6581eedcf45a2d591bb0bfdba27f6b2f27486ce5a095102841bca9e1c09043f23b71d2aaec20dff05c63c222536acb6efa65c672062b23fb3f3f334108f6d36e2ec5fe0df601ebc87ec3a22a4944b6e0f34256bd081cb00673e0c6fbb9f47ef25965bb915629560cad0a42028f607b5905aff98c05a7e2ae8f57249a5040da96205f343ce035a89809c780b81e47aa17243d11083460aef2f99e399fca13aca616b53b2f895e37aa65efbf75a30277b8834fc5e873149fd7c666d2a6fa281806736265093ef5ee44c3ac18a198bd0e7b582d253388ec7a630ec1ee924b60721f0b48eec1851c2826900b68815eda050274b4f6dcc7c6dfd4f1e0cea735a12bddbab9a9b3d977907889782200a117f75b1b8778b816075e543571dbe5c96d11384bce3f971772bab81c616dbee9f29a78acfa3c5848b5837bad7bf192660a403dc88ac997c4e941c812dd4889402a349e02ee3f56ad1108e8c824c58f2b8ec01582ec8239e720f0ad16f10b5e14249364631e181e626ab5b04dfba6caec86a3f05f5027171c62acf2e1c2246e4487d39655f218faf1519a4b6aac9eeb5a2b1de13c2a8591c234a89fe2e04891ede138eb07296704451920370205497dab2db1055167210c6345eee3aef8045d2997921ef2a240d9fa8540b8c183c1ca36f44d5e8dee48a79a20a5a314609e767159808b47a4c2e2c0389ac983be28ada68a5be3db009d2e233058a0e9eb887203061f5b0b1a567ae91bebb30cd7a3dcc9b86782b838fc2509ad9ad6cec539bacb0970f19ee0d66d95ff0a3e7de0ac74037b8f9e73046e59f95bdb146499a176687bbc6f582dcfa49013d47a66a0a1a393c10cc2efe68854a1e66d3a25c4cf8854409dac16c0d82a44de006faf8133b9c1bf2ac77aacc6809707416b037a94ace98895a81e7f3b815a2d03bf08f6be3cb2a5799fe1d46fea7c61dbf9f74fd86549e24c686199fef9275c49ecae94726cc59238b2ada8beca99b882ed9c6b0b6ae9829ff8401b8d28edc4d3996bb17f209fb07f446e0821b00f1dd3b00c78c214c09d2aa3d769ee8539a846b1dce63d83e93ff6ef37b4e932b9fbdf5c5c807d02bb9f9e26ce61c44a3184f2754d61a5e791cf609813e68d498541ccac3e5a4fdc134e8fd5062fc075cecfead1b846be30f65e15a95619fdd85a11fff33eb5866150176209bb004aa0070190cb867409e6b01e969569b10aa9d9846f32209423de08368ec038fdd46d29217c11b408c08b003499c526c21eb776ae72370830e2c7ca05178141f819de12234413218bdf0fb2ede2e903ee0610af440143f41b30411f18e7609abf9dcdbec32dfaeed8cced654efe54bb2e828a2330709f3d3816703eab66bd880b27ac724df5108de342f9d13936c40a84de311a120854373bc648049c15579f3dbaf3e13ebcd62e703e6de78f8816e64f1a9712e2fd08ca699f1a6a17899f9a438b78ef5b01c1672e762ef253aa0e43f7332c07ade707610e2808fd8b66c29c115f81fa6a47418e0effaef627bf0e43fc33b5250594d6bb759fdde53eab960702882158e43f747456e07faedc73417faeedeadde2fcc039822196112f840888310501c1a7b97370d11fc372d02e458c7c2ca89bdd92d667b0bd25e8020d2ef19e789ff5ad0f4231021f041b44244255841e842322c5113f0df3cf9a2d0dcad266854014e4f35e0e63f629ddce20d4053a8ebb6875e9afbafbb2959bb2deb13039ee79d52811bb11e088c0207c76e05820f8d49aa54400fd7f5cdb2e9e3e076b2c849fa4db59c43bc4388269884d107f10e940203e0283fdd96c592128a09a7411a6407022d409c102e1134327b4f8437351237e1055bb5e7e6a49ebd6880b7d29979b870bdf02cbe7c78d22744238431c8b3081d08cf823d485c00f016c0486c8cf789d15621f89a344ac4e88a0cf4f7b4b4704a04f3e436f17a84f96ab4680475c422884f808210e61265c248dcfbf8b17741c250cc048264bd21dc6c2874edbf6574b3446a74892fd915f8731fa99de9e0262d16d5c68ffb88f352df89f8a70319da245d6c7f706a31118bacf263a16909f55e383501881f808b598fd17ec2673f70e2de957be63c06e932b281f8c5383c010e88a37ed6c8422e21191e35f3721fce4c2816a7fbeb462207420441719e14054a1502c84b70d32a2c5c347e39e42d4e9074dc999136707f904ba154658f14aff68b140cbb5ef2bfcd06e2915d1e2ff09b6abe2f919b7092116205e208e20cc204c1b01d4f7c96afb84a0c2f4415a3739ea936d6d1174b1baabd20bfbc7eb680c4a1f49bb5b5fe573afc98b70401446e023b622a0405040ec8350cf8816a34fcc5608840294d4900db0df68a55fbafc8c65cc02f5e43cc54d5b118213f14e0446d18706ce0a854fad43477c10aa362268fe73e0e221c48df093f823b841019416a6b9e1c4533e99561501200816970f953a6a9d3e15d92e923f2bd70da13804e0d715c924b9f94598223623a6b7be0844f976408d98a1dd828db8618c9cdfd743e1db1d73004ead119cc5fad16eac880ac4dd0807083128be19027a4b71258e1ff68759f0d66c994b3d18a323dcd18cd3fd406a67b04d11f9a6ba33f81fe1004676014597d65c88155df8aa582d9ccbe104f904bbc208e5106e448a44b460fad41c6a08f17db5444ceec813217e562e078218818f48d60f8a7db20ea351f273e5ba1157fc73551fef001839445f18f8bcd0e4a3d14d5431848ef013f80fd7883081ce1ad144b06810c67adc093bca4985829345bda9710f90e7d9e0b721cb61c9668aeb3366c6c2656071dfc0de396942d496aa8534524d91816a37785ea7b69e1632d42c2a8082f6c12f6023922e74ba70508c6c3d8dad4b0feabbd3855e720de3e7c033df67e219269e088187828f76292525dba9f2f02273c4fa3e72acaeb1f17d5d06ac51f942519e752883cc8c104a31ca6dbfb0d0ad27056cce2c74281dbabf04b164849eea78bb0d563a40e03af63efddf22790ec5ae19baeae5f51c9ac1cae8a9668937d4fa622593724a7cb20ba44965d7a2e73f0a75174dc61806c13c60756e02f90537bc085b4b5bc2f02329b9850037f6e4f22f9af210dde652fa91af1d5f35a972d2c2f29b3ff152ca6208cd670a662f7f9ce18c40877aab9f5a23f04a4b4e3013263b42d0be41490b6735c105bb62f59e221a3f7835e5a12cf6781d742643e8c94c4581d6ed1df6022d46aa8a570c44372f61d8473c759e464480cca582c34cab1a7341215aa45f418e9bd52486a3e347a6ead63944e7dac11010cc17783a52b2d843d0c56cc17d75cbf045e3773eb550c54a4d7b3ce7ca5f0c3b308e821abed32e312b104f1977cb2ae372e25ac8bb2bab65c1bfb128b14497321398e3de11c120044641a31b42fef43dff86cfe76fd9cebd7f39cb4d455496019d4b289f5da96e04d061397a11243a1cef3080f5e1a9c1e15da744691101c4795341f017cbe057837d3978085c2387a53d0c06528895e1e428fbea0f59518a7c0d26fb2674c41453445fe7e7beff06ea43a92e791cf61584c5adecaae6922ff22125a0eefe3fd6b102809473f59ee10b63178a7b177b358d86db064f65b867ca6e8bb5002a921841ead9742607aa4d03f7a8aa58b66c18066cb0e22572294f6ee02d45289e277b0ec81e36d3d36863c9566473e18689a202f15a576003f8ac00f240d1b0fdb6bcd0857a6e7b86eaa8d277b99e50c9399fd48a1cdaa10b63c57506a039eef84e4e922c267376159c66abaaffdbb186b6d4f691e88e01096f081af7add35a6ab5d5bd259549e9672d48f7b1c8be26f6de0cd6fd3eba1220dac1b416752d3a4b46ac7713fa55b3ee3f300c2df0f08eb5992ed682098e77128e86de69978032b80a0f26b909b04c3b546eef6e727836a12daea981ff85d0f6d3e385c7668be1d2e422a859ec0e921e7dd6bccf144ec4fa805fdd037905708732b7f5560810661d6678496bbdc706201ecd62c2b1a02302605dad60517727a20679aadc708d1023db52dac037851174654d5aee99c8c1c633e150c39a0cf06aa4dc5e2cb630f4d492616b288f3ba90cb38dbae6ccbdf9e038dee09d196ee63df81b309f644277cac67154cf54da4eb71fcd3c1f5789e73a8f9eb08d579e2b5bef842f0330ccaf29662ffa74787ce4fd8f1e29dc46133088960ec5cc62528f4dd0109f5720d5fb88d13a3415c1be9241085cc1bbb6c5e7e13cbcc1d5e340e91037b99f62001dc4288ca577e6586e5fc4861e3290fbc9baf51d78ef7f46720e16bd840c451e99ddcf172f27a7b35c6fef2e3c82535c9aa6f9d92384ecd130a49f6454c16060525529f68ee21c439ea9b1193f8a5cf7af1d0a09f4516a905b138283c05ec1c03aa4ed05d274155bbf5d5b7fad5b1ad1218788d6646543b7cf5d2b85cd366c53ce0e4fd7287047c7b2bf1d2a79e956f0af1d99ae73c92dbdee061f71e4f661646b1c8003c4cb356b5f25524ea1fe5ea07196b40c2221dd2abc4158059b9122be16b618c64d05a763106bc07882ddd768ce7bc0650ae0baacdb227a7514bffb8336b7783c048603927abc8201cc87214e24a7b0cffba678726b8d4dbbd590124811b1427c05fc205d37730a9914140ddf0cbf8573b05753da8b48dff27b6f7c7dd5679db2f1dddae9c7af267c218c62fb9a7729c846e2cf29a0f11140ecd658b5ac6e36b8ae8490ddb84a07214e22824bc1da031cb9fe4549ea348434b81c43c153a70ce1cbd08a8bab1c8cfe4b0c052d17ea21f209e1118525c287ae01a8dda41e658d16e8f4c015ba3fe94002a376311e901a18d15a199ab6e16c874089284775ce7594970a56836ca23ce68963c9fec58e96590a81b41b37f17486065e0e3f472549d33683d2ed59d6f968e08e311ed075b19590760400b20c23aca3d19ce1aeb9e844a68602b4515855e5f1b24ecb1c2d9aba5dae071fe50b4863e3e9723be9fcdcb60da082c68f3d7758b48333eff20fa89bc0aa46db3fe58df6a19f7d1d46ed781522c7cc0abfeed35736572dbb640fdb980e604b4ae5c6aedd02051adae4d77c9e3ee6985cd7542c50bed2aa89658e9f3522d7f47baa55e9f13ef23cd98b79447f27f1192ef038aa2bd2cbb70ff4c05e27dd8c645dea92bd070675cda7d7ad8bc0c2e3ce6193428815ebd698c6700de565f4ce8adb9812373582755924d124f5a8dcb9d81b2f44eb2acbb6d73c2b272bf310bb9e67acbdc84f8a6c260a85344ae80e3850c9a0cd28d2225f0bee6bb3023adf19fe1c224c16d3ad0c238b5472fe0cd50963c0140e592e9a25ae7489ac89c109492c885595f80b538264b2b04b96e74b57b9fade342c1c9b6b18c9149665edd7698c8e446493459060e618d80a9b8d6619a0b3c8b2d621f75c598de46004895960b53f09fa9d8cfc9d3d110f0deb8ec5521d51953ef6f5be2b1a2dcda9a5c4cd3452bf3f13190916eb4a5e1d104f1506bdc8e8cf770ae7a2bc2d6ac5b79eacbf4c6c05c45bae4f447058d4e1fc6e79769f0624ce03b8d267841ac2d2fa3bc7c7c62e1a634660ca15eacf306932c5462a6005e08aaacdb4fa24818b911629ce0294f3d5026c1f53a640ce2ecf177011235d0b119c3f957e07f1b41189a265c89d11c904ea69bf9c3ca62ad86a17d9ca1adac34d2da70756712e90dad7a4a66fd6bebd694088b53e77b6ac51a9396af2356a77c5e67a6c53abec0a6f8a0526332dd61942a4cad5bf7a9a8ab88aa5e5fdd9bec59f9a216d90162e1d64b5742ecff9ec71695a1ce4067978eecdfbe9e02a47137370bf21b931963b21002dcd74eb6d227419154b8ef69a70eaf013eeda25eae5e2137cb7e56337b32615bea5c5745d20da9013f264ee084af59f841b34cd65228fd7226611f2827be14ed1b0f5c84c56f3af448be1e6facb0a9dbfe1dd1c8160d56824dc12f5177861c7496cf06868e4106d7e14eedad125f216b6712818fe4ce48606fcb79d6f31a56606384d57d5429394dcde3da5c62259d35262b5c5e6f89c7694e03dff1adbd75687d829a46809679488135536786406d6dc269d4c0734f04f1478fbb1cf39facdc42bf87cb10450acd1c9fed1f5a10e56be65eeb4ccb19f4036de83023047dcc2de521a38ab690c6499c113c0751a8b09c73539c16030e9689d52bdbd0cd8c4e96cef17b8b7fbfed1f6bcbf61f9384aa415394febcc05b1522051710e4647fc027e8be625a2133c691e48a309e6f03fd81af657d13a0b243f5a37715b31d9af8d0e45d98122c0d1cc3f5371bcf25ab655a3ba0aafe770f0ea8d463f18220a522b0827f52685695ffb9d470335e896d8501313ed174b78bf98b3ca6bf0a0f1bd34a814c4153ecbc4db6f4835aa1302b2e41f15cf1d5604b730cc687f502f73dd15a84d490b298c8e9d77cf0d3e78199f63084760959e1ec7fe0158ce978e22e99714840f1f425ca2e3fadcd58b268eba294b89f5a6e799b71dc4a42b47376527678ec25558a98ce88fa1d0db7341bfcfcbb94e1c520e5c608e6417a4ac1dd03d6c6143097460de87e9c5a3d880b770a294140ccd9b4fd4ab73b3b4b31833a4350974b438378614420676ed08f09d0db01b8360f6d51a721ceb74d91bbadccd14c122612c3388942f7272515262179a08aa4d9ce0353c46ea7aa09c4609b7d4704106941a5687148bba795e1ffb2cf3e922ac1f3fcc448131cf3f9cb21b157d88fd6bdb633b8e216e4b3f3e6680d689f11124a8fdf78506f78682978c367354e234dc8dba6ac475e7ee6fb3c9878e62f9bf09bed28b7899423c99758fa49ef322b0851fbe24ebfd2314095c9ef755895b25439a5f7db90a99521d1e7947db30efe84c51b5983329e62f99dfac60327fea0790031d69e1f3fcbbd2b0598b9c6f2d318f26476e15627e26b0d6b27ad027a50a832a892f1abb7cf21d4bf9a6fcfa05a62fd4da6aad5ded61ddddf405196a55fc552a782d2ed840a065d0a925a1b626cc83f3d737e9d09f6af0f678609f301efd750755af7efdf383003f0d10df0322547aaa1cd2c0c19598951da7d35d1abeb3ad4b720f1dac250075b6fa5010573fb8efd79ce826e9c97cd73d78f088f4a13efca13751095d35d816235ac5ec0a324277032bc9034422e354cfb62c12218546c9896316b456f2473f0647aeb29aed0463389d1d98c92518d3081c49116005ed6d9011d45049c448fd8bcb6fdcdf94545df8683984a5c762654b391d629b68ab236b62d1c3a00e42eb673a2ffb48b0b36bba70dd2b1789431130b0bf3837e302d1dcc45c5bab0aac1204fa193ba45410380b4d3a958292b819245d14f8bac8c1917fc8f55059c341d6c367a511eee873a983b4bf2a3ed17923ad32d6439de64465637772e31287f89281863798f56d40659be0800f7dd3d79b10b9e5de726f296592329909670ac40937dba261d3d9a871dbdaf463fa7ed51fb5404a80e39c9a0a95e63eca79fa33cb856439389ee6e070a19c108643c7d400c7c775e792b0bf5973a1d152ed32d7856e48ef175e172d154887a8a492baaaeb615a6e1ced3beb51c93796f6c1b723576553ae0acd48f92dc65866abef77a16743997db9c71fe21be771f4bcd13734a8c72633aeca0bfff74fcb3876b1ab5cf66ef7d5a6a676f797d3b2ee3babf1d55ed85d3b2ae1b2749c032fb0fcec72bdcc433c39dd75e5b58069f4fa4e00ef9c57ca4efeb679dfcdb0e3244fd7fd94fa765dd8fda5434a1d97e4f518d49cbe2e7aafa79267be9cdfcddf5b379299aba352959f8bdecbdc79b7bbf7c7bb3fdfb230b54a2ec27200e923c92453503b842d6b59d66cec1fb54ab6b72114fb5b28af8b339425a994f2503a36896dfaf82505cc7621ce7b99658a6b997d0eb69549d9f7bd135bd68e58b3b3d0b6fc62cb9a14135b695f7de9f75866297dc251dc7648f7f73f5d44bfb189b8dddd9dda9724fa729571cfbd7beeee491c4f8b85116be6951bf1d453a5d0b55472e4fae34de2deea3185238a084ae26e085a0d8b02fd79eccfc3f982d18e76958523a02240ae6ee0b6dbc6e3658c87de997d12e028ad8aa82b1c7fb6cb8ff8228791fc9c1cdf2d6d8cf3321fc14ec04fea66521f078efbdee3f81aeaf891d2211e5248cf04627300ed6b0f6bdf56ac4039394d71266772da9a356ace9c3143864cd69831565bb64c59b102e5e43465324da64953523ac104139628519204638c43115ca445d2470810fc187e8b35d00fe06b6323aecd0487a00892c867cf2417381bdfb81096443e42a08b509268680ab94c336b9a914478d31d379bfe5ce342f8a9cfa6ef4c2e347bfa3e85be4349229c4d71a8f3de4b99d6f94ee31d7aa6af8b5d8ed7d1b2c739f04bb13e0f7d25cf9440ead7d7d1b3e994da5f28a54ff534ce773ae7b117a4c726df0f0e7e9806f1fbe3e8cf458cb5f7327bb77ab5de2d7313d7b6619023c0515a1d7121efe90dcceb91eb73817af4a94dffa547579edea6672e6e3a08c8082af777e40757bc53eb48c2c65ff147edc45f7bb0be17e4a005506e22225025bb3e09bb86586afcd3d34d7ef6fd9824badbb669201dc0ad7abf69ece9b1674cb26baddb05e201dc9eabffd551c9c63e5b126ddb5699cf86c02f7fc7f8af27a1c0b93d97f52bcd37942ee2f00ea15dbcafe0ed019c7b0e000fe0bb726f38d2edddafc3b3da1b5ec18fc318e5a143d208080bb83dbd1fb5c2dddb755dd775f75eaeebbadbddaeab33ee76dde5e4b66db7ebae54aab5c9acca29a59cdf757a84edae0b9dd039b76ddb6ed7dd6dab72e3b83be6a36e0451f472c582e50ea516fb47abb848ab1938366a6b9b61cbbbc3964584d9f793b90817e2efb3fab571feac9e3a838da9576aeb66b9ed726fa7784526c6e9c938c40fe7f85c39477ef0f1ec9bde6f3bdfd41fd6f9808800a5e8e6429ede42a4ba9dcc36738a0daff4f164432892f77263f19b1a96419e6fead92b16f3f14dbdf33c3f32d2f7d5c551a9aed9536fb0373dd6bdbd72527e309481cfc4beeb651bf77c5617b1a16ce328f97472d1a56fe4a31c7a6175d1f33104785dfcf14d1d04fbc13ff473f2910d83f4641b4e282b59407f11007065cb9c645db4e19c62c322cffbedc73905cc36248239f8a65602802418c8373509f9c88623d44c70912e01ba027a930c3030b5d28e137000bea9a1b8487f837c64c314c40db0074c216d0a7df4779aae80738f730aeef931978e8436f87c88b988ef219eef878f259f7ca423c88faf271ff5841ce8f80048073f40be00007d2b74908bf806e1e7f3f2277dbcc701f8bc2ccb49de6fa1ccfb9c2d807ce4e120200fbe39c585f2910df1feac96b522cad838d90bab6be3a16f4e11ca453ca33cc4f7379fe81be5232f9c4f2ed6a2a6eca9736c1ce483b2c7a27c64c3f9f461cff3ba7cefbd2e6eb337ea0d0e85e1f01c311d3ab2f9c18ed9ec0339a55c5b28a54f372a39256d3f43d9f6397be72967cb8ca4fbcfc52bb95bef379d7400e586dd0db65724833d36996d5b6d682483edf5f3e873ba0bc79fb16773494625db86b231ec579bc1a65af6499ffab216b5c496fbb5a996b5a82eb675b18636c97e8eac1151266733813fafdbbfbafe74004309e0323b8ab69d8816ec9752ddf5939c86b2ed9fb3e9d109e1092ed207c12631919ef084e65f7e1550ca70e3a8d4512ff67ece106807d0e5d2b15d86e30cd39ce459eff364cedbdfb8017e4cfadc07f5cbf5f77efaf3eccbbc05491f294baf69d57cdf7d1bbfc077bf7428632a298fddb8db79f8cbae1778e30607862327474c878e6cb6a3c7eb10003b1f978106df75218efbc73df9e8761f11b9bbf0473632dbb46953e41e4fdd7803f1e3348f8f1fdfa6710f8fb8810619ec0020d4dfd3c36d3db6d29e1e9fb2a7a703977db9c61e93a4fc15e692e75d15419c01222a0832fb67cb5a10426c22b0f68f2d6b4358ed4aa368a896a5965a6aa9a569aca5340dd572217a75110896ba58b508404080a3acb9bb13b9aa56abeac90ad3f660cb9a1058423c6d71cb5a106bb610521bef4f6be317de91b01b2fbfc78774bc0b71372fed4bbb727264fa755dd7510976ddf7e1f8d98121930fa46946f052ad3ddf8ef2ea7b6b434b6d38564aada59b7dca86e65f615b6ae948d5d8d1777e3a522dcefbd1c30f36a433431a8e124be7836c33e7b7e1927b414bd350ab471c618e5c84bea568a6106badd61e73502dfb99fee4be28165a5d2b1ca19ddde7e40871507a63c9a537cc09c710c78d90a6a94fd65a6badb539a4692690ede96fda7e1185e91b389a7bfbb582e04534cd8759a25813699a2c5114459aa6880e2da13cf563348d28ce5c9caf43bff32c916662a2199a86a6a15a340d0ed7314505c22116fb99b9bb3b9d651ce642561508876a95ea17f5b19444cf66cf9768b6dcb20675b5e57355f6ac01d1b4e78fdcf7c409b767524e09640338fedeef42236c4774a17dbf3a827ae4be43c23df732042592a64a6f3b241989fc2d23a13fff0b6b0d61341fc90956fbd3e624fa3017a23f5f3cef3bcffb8e8aca0a4b92d9f275bacb5926dd7b45bad0b3dd7f437c6fdbb99fb9c8e52e0ef7b9396673dbe6c2a03520f76e2b13ee6f112ebca18ccb479c6f6ba3ecdf2a5137d0e770b8dadcdf1a7dfb03d5b6bfe16cddd6c1703a9c9b0a62b910dd32270f99569b0bc7efd6fb7e7383c97dfb37b2915ce4de2bb77411fbfef417dbf901818892254d52609af214e5bb3ece61f317f5753776df9fc97dceb7ff26960bd56c64d67ea8dd5ce4825f918d7a9d925ad7b47a5599f2016acb5a0f637aa8b2ab13285c9b366dd66c7f2272bb162ceb102c1dcdf61f97760f4ddbff89cd7623f2e73be1b6943e54c909b7bbcd3db513c9f61e72db943f7fc2c07bb276ae7cabba03399807529a10a6cdc659898d4327af36fbca073dded21ff57b68199e5f0ffddfa34708f5df8308fde043207abc0cc77ad41e3ab1f04b1554b024152ba2bcb2658a45318abdcc0890463a51ae0bb1904a4ec223c849d8564145159f638d2063abf3bb15d8aff81c8b4cc2ec672f33093b7e470826ff949c646dc8491667e160430d5938d54083163748f7e123d320a50454a4f80c4ea6ea471650ba0ffbf6addea26ac76730339a9492006168b31f4880b39f99843034991f10022d480d58440218275537f0f8d9f3d060709e879ee160d72f6daecd0eed248dc81dbfe3f10e4d250c4d4a0250e1f13ba8ecf8998f2410e677682a3c2210829492003cc21b763c952aa49404a062444a49801da14ccaec67dfc6c63cf4ec77e85908267361b513fb1d58da198cfd6a3f87b222b2cd76fd93d1dbf997b62b63133871bd077a6cfb733a11c196d7f0678c35dda2d1b0c676db77854fb0abc853a0d16834fb3a44dc477d223323b28d8d671a8cb5386cec0a9fe4d8f604d73de882a33c3e831732cc204399186488e18ba6189ac080a10986305e80e105332ebce082184c2e30e5246cf318519cc9a3d88c8216d044692127657c06c7340bd3d34c775dd179aceb8e6969a413429946ce42b664d0622f298fb3e0b49c9471194bb1d7797b060296684b46b12f721dac6b2ccc4ecbd8a66085366da05161b6c1861950d8d84a23823342a005822d47432a85ad355c05521f58430d5f81d4133562e06aa43c100336ce022925363068810b3048c3054c69bc604d9417acb1f90527a8b1066317e4a46c711aaecf2ecd819b972e6e4d778d73dfe04c1758db829c84b385c1065c9fed962bbc925b7057c06ca43490df6e99915258dad98575dd32e7f08aa43c210b7252b63929631b831524e5c9af66056aa8b00215ac01c31bbdf04650548086ddca001b18f0362e205d44c3db54000d2d9a05fe7b54e0c31ea1a43fb472923d232759cc0205ce48d3820969cc8852821968982620e98ffc92fe40930217249082325ef8644b76fcec6558864309ec0867a1741f2e970be1327292252327590c0304c838d314813328888104148c21c303a4fbc82fddc71862cce00031cc48698099306818210c306a6000185fd8b0802fca4c5140192f46a0c48b2ea824a08b13dc808013d49c0e20290fe5a99900872492f2982087241370a18301b8d8a20a922dc890e0089912ec60a4045af050002db2782a9245560f44b2b0f001098b2b7e18225d0cafb0c24261399a5600414410578488b212d3d583ec635ac410f6b1cb155abdb4b17f33898b5c4ee262953792b8583f3f969e8b888bf57318640338b3c84778661181e509b847daa6cdb67f84cc45f0cc9a581c1185c511565c215584155255606131a20a24b0b2646b8ff86a7051918f6eb25c4d8184540d381f7531b3f1e3e029e4141629a6a28022892c502431e6092590b84f28e18418a025c0515e0de990479ccc540d60ddae2b464b2e62df158eaeb7a1554eca3fdfa56b189cbc25090fc2e423bb05cc124a80c94776892f390967259af8c2449516aaaa26bce4249ca996e8c00b972e3929632e4cfc74e9c2252765cc0490172ef9e8a60b5513df9d01a832c02472640ae926101da101260d47c9c5ae9ffde64e2154f9e8c64b133909e72a2e1d34f185899c9431123924529efa92a80322457d44fad4072285a414698844f2d42e57fecc3dde2b8998c049782e919332ee126609305bbc7860b5258c962aca23e6d69f5aac94f8e2123de8eb111b1209a201ac5b9c4a88c945ece770cc8f13ce2472120693434cc37c3a74acc8330637ae7141b3658dcbcf96352e519b880bd95dff48123a54d5d068b42b46b54af2c831170b242379e4bec20bc923f7d863d39f4ae423bac19619ebf81cfa4a8c0b9badc196352a339b4b95edd4a963225ba2327d22d3c79aa34bf9a955c66fb5efa598c662635aca5e17f3c774900f4486985319091124f21056b224b1fc9d452227d9c7e1a8c2c63fb34c2c5336768242f1053d05fd008ee2ae37378f6f6ebec8a16c8c839e408bb5d537373fd68d83c3e69ddf7600a9ee1c7ed207f64875db10c7fe9c19c97503f637b42ce7717cce37a166ef6c596b628a7d5d283fd012201d123de7cfd13623c1be1f2fd6370bcee320995bae8b593e9f8b29d68735ed410178e3c3cdd7b7f928e79b89251f49e558943dc2da27827c005f145db48fc371ee6f1e9191e0178dc8491c11172a221fe530c4633eabed7e72f71756176995d207052570d3ef2410fad3888cc4ed22425991fcf4732829a51308293eab7d06f181fa4aec5fb1cf3199b18ee9ab6ca372127e1b32718d4bdb85e9148fafe424fc396492dffe242227616bb3ae547c56fb967428f6ddc7f2db70d6a8a030cdfab0e6f6cc45ece330874cb6d436912d59bbfe91fa236c41b3b7ffbe9f511909fd2f1c71759ac5949b9d572af519e552aa45946a7b39bd7d150a5581ae50976075ae20065886a1dab2f6456b6f59fb5266d620a0a67489d6aacaec9b2d6b5557bb56455535a5a5caf6a3a5cd05cbd98ec82389ec960db1db7fe3426b61dbfbab412fa007023501facf5bb70fc93c8c19e1b7fafb5c8cb9582b8db65ddf4ecd8b43eefbdd9eb761e15e8a73595db57b8abda7f4066dfaa6b0c7ed95e81eb9f7be53a2bbf33617c445a0312067eff6e4f442ee6efaeb026a27773f99edab9da4b0bde7f413babdedfd7571ebac68b4cde94ba3ed4ddffd4387ae91bd4c8e65e3fe93443226f37dcf876d39cebacd761b4b1fefaaae9bfede76a48f2d22bb33dc913e1ee4a27fa7f78f2b70bcdd7cfaf631ddec36b739e76c739e73dd7bd76dd37a0145b5767d92e1d844d4a25a55d48698aa597112291a9aa6a6a5a996c416d5a25a5ab82ca022c0ed81b4803a404180e38c7dcee7bc0e3438723cfdb94676adec1817ea1e470eec6b3ef26ecd431cf6f6efbdcf7d3afefe876327e771fc38db76ecd9361cbde7381d1d9d7abf3ed2fd1a5a3dd6d9cb8c64ff5bda9e92ef998ea635176948cbb8e8e1c08123680d98f3f5bb2e27279cdbd373e7a836775716def8aa618fb7afd356eb6c998ddc8761acc70aa4051c41a70ac79f8df3e07f2106bf778c3d1fe5d70b17d1ac713cd6399f8b1f0dc7d9867d125a5354ec211374100c5f5f445f300b1ba2f3301b8ebfeda8e4f53daf8eb36d4358ce17b98e7d986b79ff3dd6361f61dc853223dc778fc47df74bfb8af9f3b7bfaf7adcc11ac77ff5084966d55c04847deda7759ac0b685ed1b8e4db6cd099bc070be623ba630e2502a0e6c00c759b3db47abaaa8a4a4a29e9eaa5029540a6ddada62a3a585a64c991a16d6555515959454d4d3531597e252bc696b8b8d96169a32d686b34c6de642937b202be0c855e1aa38f7e442e0d3e7a05ce8f5f4392b2e14c549f9d08ea7cf6d71aeca85acb8312e247bfadc1587e5423a4fffdeb7d6eb3acf52ab54b74c532c2b058d8bb4aaa96bea194f5b9a65b2525ca48fa3ed14eba46d156d9fb485d2d68ab65156ca4e511e5ab35476ca46d9a91b3b65a5ac0e930b8df639269db1fb6dcb458ec907d07ef73a9e4917724cb4b9e51c937352c66c66e88666d35243e96f6c249113a5592e44b35cb45496ca5a55b9500efbf629968b54774fb35cc866bde895d534cb458a44b13ab9eda5575859b6b3d862706ab9d06d92443852aaf8539453f995a37136be459ba8942af48946512a498463775c0ca0ff5d402d0c3261a009238c23aa6a5848dd1a16524e566a5838cd1c2cb91ba08b332b5844050d415f5a3b6b5830edda021a3a0c7c7a16f5c349a3dffd5e6e8519acac8dadadf2256baed7ebf57aab9dd03b34b73b739ab5da61ae2d9c5b538d9a352019e7a91fc6983d4edafdda69b1c3a8daf527533e723dc2c0dd645ba7ae5f7fb5cb0b5fa112359cf6a68a4fd87604f3169e3102c7993545d6aeb0b287a851a5bb3e91aa242ed465b1eb2b39c1ae3fcd58b9c806f4e6a472201880f42f1d22522489fcc798f8b3a590696602a93f694276f8ba726d210ec71f3353881751858cb1ebbbf628e953a79a1f57388aff0a69369c520ebd201a45996d8cb070415580726fdc1a1c7e21d32b1c63e36fe12882e1f853db4cb5a16a63d5e6ca6ad74b27d977e9fb8552896e1cd2bba51ac923ad607369924736491ee9ffb968b9ac8dc071aa715aad6d31edfa738d0fb9118dc9df3fc7d992726a8f5bd6b0ac6c9d1080b286e5b41d8b861a16d38e6d55b165cbda14517b0cb2eb1455f608ee3973af428bc27adc6546eccfa5cf6b54646d599b226b0aaa8d5fdf77b511388a9bca5a154f43d4883e08b4d61a7e77cbedefb63d2ce643f7b77fdfd95617b9589fe621419840b9e70701c185ec66b939e75f0bfe136c848282762af5f7e2a12cc0192c1d6389a574f7395f4ae91ece2c5f414fe058eb9cd405c614b425352cab3b9d56bb71f7ebe494f73a4d9973765d54b54229a59e67a9acb516e3acefe3388efbbe8d8bfc755dd7e56ccbb83e8c3176b9ae18af2fe79c5fafda9519afd70b046b5756b5ab346ee7f2170bae685bd6a4b032b4652d0aad8d5ff2af5700ac4541668f5f83c114ef9cd86e345f7e10bb85a00dafd89f3a0853d31bd970c779fc03c179fc7d0c326100e957bda38348008e4182b87b6d02120314e6fbc25550068dac7966bbd33403f40538cea63d2ad15d5fbefb9979668c33f30c972a141403388a206c2db0cf19fe7d5bb775e307acd8b4eb2cedbe7694d6bdc56d928edaabc5dabc7da79bdea8fb4d2fe0d5c58a28cd493a7bb56cfa553bf1dd81a57da5690046f73b2da3f7a3bcda307a611add67ba51b8f04b9ba512b828befaf241d8cc797e5c4cb3ebff0405b9d2f446f7cafd2d518c19b3062881ac00679248a604bc4f7feefbfe600dc7d8f62dc4740efdfc70027c62b713dac6cc9e396c10e6b37eb1988d7d92886e0973ebb9c8cc61d7a7da89ef273c7607969ee4d8f49d706ddab4b195e6a68f42a561559b072cbdcc24cc9fef5b14dc753da514767fdbb6bad5eae0cc1d080b1cc59f2049c4fdac2ff6d46a7d73f73bf7fcd86ff6b9cce51acaea2f6da4397fec21449440e68f3baa3f28615b76bfbe33f1eeca9a5f5e6e47fa6c9bbd57ec09b29bb538a410bfe36b6ff7b7c7d4e3aaacbb6a99fdedeb6452dff7116cd372dbdfd76df58ef4d9dccbbc50d43d4175c666dfcf07619228ffec7e736baf75ab5d2e5babb4dea7e7acc6661e7e0745d49834dbfb3eff700204bd2f3ba0d2c097f9e8fa3bf7d51f77daead82ff2a3ee6dd697bb1bf7f6feacefdfe9edafcecfe9fbdee7eebdef5d7bf937ddbdebfa786edbf6485ce8bf415142ba9ff3ff1803e6f979168b6dddcfffe12eb75dfc713636abb7ded8ec9f009df4d8575e7fa5c91abdbee7c7a9f72f50cb94bcfe755f19ca12a3d7cf4c41ad02f85830205dcc21942546e0cfec7f5d1a757afcc2d07bcf3dbf9e87c1ba3b5d77d01aef7bbff7f3ffb6cafdccfe557a2d0a2b1bdcb20605d5c6fe79cc4519c39b8fdf96b1ed5560100b402547ba1eba9c471658ee602969bd2ed211047dfb21e667d69d0f7fceeb2e0491793fb7bd3f2ee49bf7d4eb3acfebbcce9b92a86e32fc7333b1ef9f93b9f187d508feedc96b7f8939d7f7957c63efbbcf7fe622fe4b7b7ad37f8c01f1df4fd7a357de5e7e9288e32a955523db738fb47d0d47285fb5b908c65daef5b1add4529713baeb7363de4e5d36fca4cffc4ffa383ed38d9956b3d2799f60da9452f167df1e637b661b561ab0dca9d1f98db5c89da87242ca2e722149eba54377da5cebf4af19c9d29ebae62217fdc56c51316b36a55fca88d932375be9f41eae710321dd02cb2ac64a90f9b9e3ce29a55dd7751ded3a988b414f200a75dbcf5ad84be8c7c5faf25f624cccd39cbbfecc56ead405ba58ab0c226b566a86dca8fe111772d0b6b1c3ecf1ab5f3f4912dce5234abd7c4429ce479466bc6d5c7e24b97328733d931cc402902eed51696357e7d1a58d75936f7be11c832949fd0ffb7da2b6bdd6c07cb1a75fdac5bb74a1944efaa4c7eec0d2ddf32f165de817b098569a26e8f2055017d8bc617bfa056bd33bdee77ebc746f5eaf745456570da7d432f9db1f3901de97db6545c227a3127d3752e48677055fdadcc9c0f8e5e3a01ec0b1eeca84993d6bd8b2b685cdde9ed2bff47195d6c68fe3ea9edea7f46fa6d8d3f9257e9c70ecc01275590abed2ddafc79c4fc9ad808293d9667faf74f78dc7292022bd2ac80c084686f3f6f38391e184b58c04c80d7f0952011846963ff63a1f46e7eb62ffc2b139e76c6dce16678cb1cd365b6bede36cbd4c11e369cb9a99293355b6179a1a8b93858333b6cab8cbd4d660cb5a97a68d7f83dd54fb22b5ede32f4fdb7e96b2edbbd850d176065bd69ab0b263574e4ca8d9221a34b52f5b1bcb28282d5adbd2aaad9da9b2367efba5ca6ae3cf531bbfc501bb954f63fc1e95876cfb3106f595181157a28a30c28b741f477491e222290f16aa2c4d4817916042d21f534b24b145521e25b4685142bac8c316aa2a2e5c7838420a4b961f9000337dca9503ebd20587892e436cce63ea60e4a4d585f3765b2d613739385ebcb876c8593e243e15745e1a61397d4ae743269da598c69ec467b502965cef3e6424dff9259e5f7e25745c3ada6aba16595db0776b5d39195655758343e501972e60c48409e3250c145658803dac26d0e36f70aa5cd92a1f59d88d0d9580b09d93f3312ddd0358cc9f90d3ab0be775b434b25ac26e7270c488717db9e9dec5e423d71739d6d13257a8048412d32ac87eba4c014b46b247802c64922ded9856c092512cacee817d9a91dcf390b9f3c7ea7477e96c9996ee8195696984a54b31abac2ed74b23aced0e02421de18804172b4c1f49a295489ffa405bee85015d01ceb0deb6cc0f98dde0389820128459920990463a0a582241152e545c3eef522527611d7212b631adf3ee057bd5e75f72093dfeffc3303ab8870ee33ae4e0841039e0f0441038388d0142d21f3fb2d30d5040f90d4e258a0f7e4c8192fe90e2ff6325488944f645fe2309b0c7cf5c421835324f20043541628045b2843051be00a1b5d63d5ee6231d5991bf0e424a22f4837fa8306af43f54086a663eaa40987f0da5ff5f66134230a343203e94edd0e37b7c91b761a4a3230b7bfcebab305162efc29291ce13a11f40219d90244aec75429912ae8d91ec9ba064239d5f3292329d708afc61d404fe1e8e75bb7b38e7f436f0fc9f17631fbb41412e607777f79d9e0f8c92b57b827e071673ecee2478232c71f1e8494a915b6badb5d6a7136d322961c0c5265068d4cc336e45e4c80ee0085e38ceb6f759793e9a735a2f66f3519d765aaf42d1683423b46a5a66b4d00819414b8d04729565b1f5e1c393d9291380df0c7b5b5a4ea0d6534c8b494b4a9bfb094d2c52f4979edda6d068b422d068010d19d0a04234021a3b481fb94751ab055a6a481eb9399e3d33cff65c25a536d3b96d8865e7b6e196e88564382bcee30e05e55330169ee246bc9f4ebe23d2cf0a8fd70eb97ebc9e8fd9a69d52f9c80b4f00facc50f4846ba7f62b1fe3ad61e546bc87e2a27b3e6545a1fa6ccc7d77737ee5629a315b5e6d5933c3873464368f2d6b66d036fe3e97669a3fb5008d3389ed3fb148b712f233ac7462dc6fe68f227948e83fa19b89fd8c94df4ece1bbf90fb201d402617b0ff5bda198e998100fec044cff3685704841446d8534f2bd1ed791b80ed26b0ddbdd5f94bb3c6a6d9b26730d93362c860db4f73c6a6d1923cd2676357d46bcf252490d7e77aeb0a7d0ae509a758a1e16e755535c6cc1ebfed55533e45737733cdd2c52d33aa064a1771b76409a766842556b5ed242c51c3850e5506e8fdfc51a281ca5ce8563e46cb6ace3d4e2d2b9f34b5340a75e70f77958e4a3292b5f6b367ea0e2561560922c58726564f0f0dcdd0dc02bb7704c31915f533f18e985f31900235e5d12268115e28a5cfe7539268066d012671d15fa96a2ef4e69c33045d7a4a398ffb7bd46ef2cd4d697b3fe73767c6a15bb9e86fe4542e84b587dcc87dbb4f8d0ee5504d5c68eec7e1940b4df7749369bd30888b6eed2cd2b6ce992409c544927c01bd1fe5d591efb116c5fc9ee7fd8b5380dedf1dd145ff3078c10f9ccd84c6ebc16658a0f75d6ce66fa3820459017aef85452e42d9f6c799883dabe9ecf909fa71d1843ddf5a1be4a2f778fa0407aa021ce7cf2817921a251a6b59b027d6533799f9d4389c9eae7b84fdcf1e61890b49179241c41fe95947326c596accd4d04c810ad22148871a1a5a2d055653413904e11054058a4a5a535881f3524bc194329acaa8613f399dc97232d85032d46c4a51b0b5291952bbfed46345e228a594ba4615b6529d61a5563018e953ca29ba65ad0c2c9bb3e252ce329c8ef02d6b655839ad428e81e5639469e26063bad96f62ac2579e49a2bc616c814932279e4185434d8755a33341a2d8b917c9b8f26077bf470b4ef36dcdcbe2f06bccfc43ecd48740d9540a1fa371bf1df68341acde650060b4b8951b5b777a92d6b669a6c186cb4dca37fe6be0ea003c1796c102d37b2db7eec8d4017b228a8b2ed1ff1a16a44b7fd98b51f34b3f63d23b9e1f6b022efffb9906ff14f6efb757ff5eb827800ef57ed142b5502c980f49164fd5bad24b25512552a89a84b22aff5c8927c77a79bc317c6188763936f672028c0b933d52f7757f8737bf5c35f6c02f1ffbeef8b4d21b319def63b0912e19a91e8c63fbb8ee7f7481f7fa035c0ed69917b7abc3edb9fd317e62edfb66cdba6e461918f1ede6aa5a5096c4d1318f6a7be54270a58eecc2fe809b41873e198c2fe0b8e31eb2485cd8d2bece984dbdcb67163fd5a819e00e757f965297db52bce34f92e8dd66ddbb6edb73265f6b66dffdb6f1fb4d5f6c664fc1932b15f5fd8c9acf4058fbbde538ee3386e72f3bfc97d94523ddf6afad16f7ebb8eb0c96d8ee3b86ddb40342ca5da7e2b18d097f6f69b06b29a77537d7bc0f3fd3fd8fc7009969503e36ad39f4d341a7403385429f79de6ae7451d226b00f89d79cbeb052466b8b6d6cbfea4274e24e02d938fab8bae4b63d92dc8ad89f219322f3392ebc2bec51896e2e8d335bd6d638eded83b6aca531b5a50bc9dfe8f66c48916c3849369e646cab4d60347db1820d5650c8c64e2551ec2deea4101d1248fd106c01942e521ffa55fa50243982520a906e7a933e4b9b7e5b248fac926324962423cbc82fce482934c217670c7153341acd8b9114b265b6954a81c1a80206141851604c491ec964fecc74d2bf014ba01580b2e6c5d31ec52dfa9549840f41ad54216c101b101cd4fda1f3c1eb013f7d3ce41d5c2478550175b891c30d0e384eb01b7050c919418e29311b74d4a043834cca6c861d32f088a147d3c3a05f085d0000d34e940c5ad060b2b0c1a4896d78daf0c1c68fad9e357cd4e0200640d8fcc0a0833402f002a035412ef0a005422c18521364051fa880080d215a209c21803403302304344529d865189141803322a0e0688c2162209921124611300af085913247bc40d285014e90544b6282037081802d124046490914a0c502b26040d6085834e00a0758f1002c12aa80001511984202574ba49840142540418131263c5101272c802f6085811032308051004c4068224403441bf88003414e1872228482071d085202f240009e74f0819f14808080835d1f87c067d7c722e881f243051fbbfe0f5e816749dcb40d360b1ab49041941d2600b810bea061f8a61e31f09061c70c3329321a746ad061436c4a8e11e450c17103cc0907879b1c6ee800567991c06577701ebeb99f640fd9872f88cab77fc0501e107606918fb60f62012cb28209f105d1bedeb007a80d38deda15185bd6b8300387a53976fd118888ef471275df105d6c15c5ba58b3ebf704a92f42ad9dc069d75aaf50fbf381da502fa4587ae5036aa3678b3d3f364a04da026908c406387f02bd001cc51e203640a02d10280d50ea88202a178b67a9486c53d7fb6c46a2405be07c117ce886d579b65742895a6dcb176c59db62cdc69e91eaa661934fa422901ae0267d828c00fdcca7e59b5abebb059979b5658d8c947de9508fbbf291e8a2ddf26d81f948144ba02575d8b2a605995d14debac45767be5214a108680d90fb1da02cc051dc3bd37ead61b5e18e0b855a8cd9b2a6c5963db5d8b2966566772085dc90407a7e2491c51bef0f943e1f13f9888614480d900958133954312e3a5d66db66a9171e55559f8f6c28f6c400b4f38bf82504b353c36860258af1e144cf131c8cf9812200510449217415640a222a40a86200584556185d21021643b2886451002d8e94c00064926c8180cb8512132ca036c2091cd00509d38b08dc324bbe28010c13c2b080190c88318ed104051b387302192894a1948227685230230469a09cb182160d8d1654c0b48217d434b14086164871410d6ba6bc804a1a4e30c8814d9518eca0c6d31a3e6c41b111441b56661b2268512c18d18254942c4c532e28f1c216189868a28aa18b0c5533809162458313358cb1218a295723a0820ad60d573865e1a0450e6474e0a24a8d045dec508607309eccf430860f677e28030a0d106982d012420556d40cd10222d65c49238a4d116a18b175c46c438a86a5852c4c48bc30d594840c4a48d152c396294b5061c2a9891ca8aa70d9a1cb93171faaa0be0401c64a1822aca2c418e184d41359c64c41a144145ba460e28a6a8a2e5454550106cbca0a27ae1883451459575950a1055609ae2093b585165c90e9e1221fd1508c02e93bad76e36ee7e12fbb5ee08d1b1c188e9cd90e1e3d5e8700d8c940830d441e1f3f7a7c3800f2d3410080823c101a0af201911010043080108ab6110144381a8244a448018c1c416280a42407404002942860010c18a1010e78000910888004964ca0040a9850010b5c000319189934d1c00638708213143aa0e481271f480104211001141556589ab4c9c26c6146994cd385f9c2846136cd18a60c73862965d2306b9836cc29730493cabc613a4d1c660e538759659260ee3079984fb387e9c3fc61424d206610538869650e3189985766d42c621a21e71153cae5c40719fb0b03f2024a3046a9ad3e04969f9535b140b3e79c73ca39017caf5b05a4eca854ecd8e992562a9a110000001a5315000020140c088542a1509806622ee61e14800d86963e6644950844511aa3389261209a310600838c013023403052c200e2bc81b72899f9e7cd6fd94238a6202ef7316b79e56720c2df59f15d4193a3422d7f5c7fd6e72626066d5eae68c65d70846f165ad570db2644f984d2952ba1f4e0e586ca8c371cdb51c4f6f77d77b6616085a60f624cdc9e0dc6c06f929dc35e5b3213203a3748268a061ebb5924a00487d941f763060fc7d3ba613e0a555fbdfda1f2bd8633d15304cf0398122568d56bd3afce8293915f1dd891b9ec8eb639b13be4a9e945db66fa17a9b3bf4a9e6e04f2513a4d8b368716b6dc3fe600652f29387dbfab43bdd8c698d5a0cb29b303139af69d500d30ed69d5a0f3cc21a78306b43c8ee9eb42301d35d3f111b422b84340c03e52ce0800c03c5d1aadd1103ca840326ad89d14edae928aeddf06655fd8054580bf7fab038a354b85f296a9918481b5f28945d9dffa1c53f85bee953d5863a4601803ee49643661d45e041ff98531af7f5613b878f8d5d52e46a3e2865da20efa1102ca37fa4a4e02cec7d743f9475a8dcaf0e14e6fc3f4db09727d561b4b742c5a77bbacda1dc07866b5a3a32988de12b71c0b9b48dea7830b802e65f00dfc029ad1f82f5f7feda24371e01287d1db568c0255e39d00983baff1e7269ddd2618c5bb79c63a748679a82debd2c32c79b9ac3f2f755277b8fc2ef4a11311a5c8c0e2a0560a138217524d607c503ee1a4d9efd70ebc87c96655eb7040837e8ab04919e28a6c623b96b0b54313a53227d26357a8ceeaf5450a9699dc13a7d5bc691205c0716e5b6d42ba580d86ab6232670c5c8d1a06b48a0d346af5be24a4a5440b3e93278e1f1e152bdd4b198efd6f8cf45f53ad1aa69852b31c8d4881c38146c0b028b21603bf50a8f0cab564026c09f369ab3b91f7c962e517675f7c23f9f3c44b7c1574b2e6799a58d462330eab8c25dc34d6a9b2698575f9c379eccea95d9b3ee5c14cc0d3cdc078604a30c0aa075d0437c5c5d3c50504ef991ea190c5ee833435e538c176c606662cd193aacf527ee6cf4fe6a2852ff3d025d218a75801f149a5484cc8d703f25ccad9a19947fc4b56dc33cc3a60a2d88f42a0375cb49e7f4a18c5f6ddcde3c3b479fb9832bbde149554f6f0d7f1ebe1f3a32f3d722c90bfbfc9f25d95ee9fae61af2f68cf847731a078558e5f9858d84fc4628356dab389a8d38cb6d26c8fcf6311a43a8de227c46dba0d34b1daec67cb564618ecf124c93eff025b4955a2d3f738915c805796a65e3a6e0a7674ea6a89bcadd5a89b3be58fcd3d69de8f59370814fb7ea02cf7f1b00694e9b31fd00fa807b07d10fa4e9f0447df7e93033f2e0c3c36365993e1da49fba9173ab4eec947c54e6f0f2c184d855e9725ebf48be60afa19053011a43a0353fabd99c4b43676c5610a55ea1bfa35ba5325a750f315f2f13d06b113cc67a18a0d547a5e85b02a76b01b1da7c35eb0871af840d7b91f2c60b1ad80e5351a6a526f49115c3726f0ede861559fc900d65b5c675bd0e548220760d68d41e127c26a11e1aebe2c9628fb2735676dcd6e423d1303d46a2bcee761964ca712c3ef1b58f8b12b74fe0bbec74da5bfeef9416f90c34d028ddbef4c31a6a5a39e36fc311c9eebc68dc756398c1eac625070c1acfa8dfbd737ca4d600064b03ade029a51040f6d7efb1ad94241d662dca5776f80f9518c19b154c8b064b1b87fba8981f0bb3ba40fae8dd4c0c74c2c4c2baccd0d9c97446dc773b4d53cc9a60d72383a78acc78d4e598702bccc80dbe41e740e60ade3c209b57cfcee2a097d661a57f9788353118e2e6944f81a68a750c34635025b81047c3433d011b8ff9a7c262bc1f383761486ba3ad7cee4ffbac6ce04e405e454e9695045dcf28706c5fbfca4e6948cf41cb353b9d15b983130a1099f708e693f8e1add347df153023559984cee54ec537866262c8088731f39283370ef6c9cc245d2e4962e3f09646518c53bc3b61188f5c044e45e91260fcf9b7bcf6a06df0c35902601bbbcba0a0da24986afbd612de7f7720a8f1cd5145cc91358ddb0bef95df19f6dbeb02135836a1f405dcf40b85aa04e6ed39e907429dec5e1cdea3472e0b7ce4938b47012a8d0755857b8e80ad7632944e3f322ab2a7fb2d56160a9f3e1358f8584c399bdd8b103afc04ef958704f4eb73bc1ff53b89a2782dcf13efebe0ca17bf3957df2466148761880605472c82f24f3dc662575c9cd8dca3e65f68a713321d67177de77e55a965644712ee600d17a760e724c5da0da5d41a5a799449b9370c1281ff496e14dde1f5069be1799405be2554b72d4bbb836a9e5b06d31288b464ebc5a6c76569269ff06e52a409b6dca59a37fc7a102e8d9c359d447614a880409e8c5fbc5bc7c3c46536103e650ea0e0856a5aac4a5e1147c4fa46e9d7eec862e76ba00664cd79aa1944130162196065f914d019bcd1b55a00d5cc12efc0b9f1fbcbf48790ecd4c5ac4ebe89ad469c4bfa4479794b8114f1cd04db45ac61aece26bd43c51e1156b4c3c499faecec5349a97c9304edb9dfb44140dce5b07cbdfa8abe5fa7869ff93e38e09b24467a9970d43c77e62aa7d2020c6e613183d3ef7a630ef56446851267735e272af9fee95ec17c39b3e589b41ae20461847f342c8d22c4b3c28d6d1d723fc7d3c8d9ea04365266ca2f6dfaeeba9662e91c82d820bc157e222f70abe265dd3ffbb236990c8fe485e66860d81286a402ab310950e700f60b1f4ac262a068b292b3e3cd558bdf128a0643590b7aee983c538408edc7dccb1a8ca5791f9ba5212e63b6a3bf20e51151184497ada94a07f3c1c2b4d0aaa2422b4217cc9e1cf4eaa66a8bce14cd267bd6e60d13fc343f221852206855b9f71d7428936b37c0411d29eb12fe61da96cc93b5c4bfee701c875e6e79f4629f42ed817c38abfdeb87d6be6d82af3a342e3ed8b8b341776b23c5edc42fc1e9db4de12ce3d93c9159b9f6b6465e2de74a3d496662d0290d8d11efbc0a94a7da4b84d63838671389ae4aee356740782152d9565383ea3a22288649ef2b7045c9c5db760e1b05e9e112b6e4b50e0e9fdfa2cb9e79d7204a42778af7b0fbc061e16aa4ff9ec28dd39a248feb2d4dea6e84cb2b5c6d4ce19a453c1dc51d0f40c3065f7913f7323b0e79122243e8f6e481cc5058fbcaad7960870327cf722ff0947a260c8e5f71c7f72670f7883d64ec1a2fc45c88a2199f84e7c5f49be80d1c1392259b7580268f38fd5c41b06d0202454ba5e79d5f6a98bb412b6639ff57020da4b30d7a13a7b6d0d86685a55a629e10c940a8d2f58ba06403abafdb4e916c697dec40ad9b6c2c336a1676e88c0f00c65e445ba53614c3064658fe9c261781e36afccd7884808c97604bf4d485be8198d19e4ba3134092864bcaeb80f29cea2973c17e3d7873cdbfe54dd48ad7d311002e1c9365e0cd7bf4ed9d94620f6528c4c2cf3ffdc6dc82efc1a9a1cedcdfc230ca473f5fd7e83ff62834d36791115cba1e96a503af9d223eeca1c8ecc9f830348963098ab221fadf84c87c2a765a45570d4eb50d7f830a65ba0157e842a23a8f9abd0c9ff73348735d3ec253cac949a5eb40bb5ff23568191d0b6c02e2c8925eb976758de3e6fa4d73c2b409207bdcd096c1f76a6e06b30217eb22772b8ad39bcc819f97f91124240384ed84733141a4b54d5b96759bc4e90b3312c9b7771e8f535c8986d931a0a9b365b101920956af64c19f449ebcd678bcc7efc5c73a92197f5fa5629227d883c4dced4b2fbeb1ae49de71f6d6ff052c815d537f8b087b7c6b0cdd914b88cc104a10355ac69f383d3e3d0456c1cb22d3772a750b3c1626011eb3e36191baf8de2c459599cefecf5b8602917f15c8eb3596847b2f1f21b58b882995023a5e7cac2767dce1a8f9f16a5e8d95b8445b4e949ba84932f0b90cd15320fe3d237dfde415e6ab17d9f3a3afce9cca17b5ae3a2e706c0faecee3750f125cc104d5e95a16e6e481da1c7991cb9127801e43c3e0ee0280ed0e1f276d93da76783df2d360f54962d0ca421812e4269b2fe3b43f86debc6bbda8179326201011e62e6fc5fcf48ff268e83ec4cb3b1f44a6e0e4080bb7c3f1bea8720e7620f45f87cf35dd3e05e80f357f87f70db8b095b917b38df685a4bcf59a4686a3847828a571d019550116edcb2812f7e9a04f0d6267380542503804048a6ffd2b3b8f38c05db4ae9f462cc0da1a963ba847b0df8a0ec775f7817b3a7a24b86c7e57a0a4a10f19d9509415089557846623d1ddf938e1d72242743114aeb0e4c23afe0d7e56638747dea688a4431ae834284bda82f6cd161c620f6601ddff296d6c65c4a754e09f5ca02692d57705c944924b46aef351ee4bc2ca11bfae85e06c1dc4c59de534875e42a14c499a5a0a55f213adf2d4da62c1ecd55e0dafe9756133e86b3a038b7ecd0db0f32de7e3af94eeaa0d57653adf62e131b085c757585a7e0ba0be4a25a8a23df3de50d8dad786cef7142b2bdfb24d4b1fc205f8a450ed9e858d3f8240ae1972f3e906080f852a926bf25315031950d0f1f66720499f819ff31da02143f676a5318a04470038a9f3288bbadff3e00e028cbc9d1077b201bbe547be96ff810736205ce889171f2749ad182003d0ec8c8ee2a82b395a41b3e8c90b48a937605a0d90a3b8da2b575fc5496e2a24e04a4c2c8465d34c99ee11cd1c7d09b3b97dc65837547222c25eb5f8a9b87095da22870a2b1475c9306f7134a6a23e3893a42f69249cbdd3bbf97a0cd4bda7b64a8366971b6c215494d6fb841a4116a5b0f9c7c68cea28f1c973ff276566544fef557c389b13846d89d572094f8786e6d19858c4538697eed2dd0be18d2a14d581137ac04a21fef59a7697c6b37abdfd733a1c7f5c9aaa69d9e1e69b6b494ed95eb4f1a577683e94b9bb1631cb101284c2c4c9bc394824e1c3354a8215b9f512147e5bfdb71c723a7bdc60361c0aa41c676d00a2b7db66e93eaedc344839167dcbae76bb076d3d1beb383d8b5406233dcf0439af87f7e86bd73d42bd63bd86d47c2c4d8c7eb223f86c400b902ec0d55e3bb744eb2fbf8da782a49dced8137292f34623b034ed6c9da3588b1ec938cdd0b894407e1f811d5ad5d61b84ce5f9bdb8007cfb3520d857f9765f62d8ddda5f0973a9ac2cd0839190a434e5a62c7f67ac52560c47e2a2edbe56f6fa0660d8652691841966f69d88856ac048b93d1538e7ec7012eaf9d3af9cb716a1c13c9e20caf1c152ca5465a1be6df36b7b05d738c009f4dd60d720526d772375b058457cee3825413b021663aeb17fcc26b7759bd0f5e1f1022d7623bc47ef50c86ea07c5453cc44fb9452deb8c11ee8c535b52901513b031101c28efa7dd0fe62b3d53bb53f5a6a8a5ed680413c2e722bea9e935c6919fdbda6d5ed4bb8f9b891f8f045b60e77ad31dce4fd096011ec46a95f1ac1b7ffcf39184ddd4eb7bd82e49af45c054f568bc5f22626c512e397ddc93e023e7379d357ceb73a6447e5bea952f206553409643bc7bf357ff60860de76e6716306127e580df9e25ce2524115b0681bb4344bbeb76f49f7591f842b1ed698e26b480a78f6f0c5eee593ff2c5a8114018755abcfc5bbd4f11b3bbc5590882f8cb82c1e00f614db9e6b7773ffe1cb84d0141a6fcb85cd7101dddf7c211de38c05756ebd0b378884e28de1ee3101cf398010e5242a1f2d58fc81bdb937e76c28689e0c743ed976bf23de71ecdd6f501adf74ba9f0e4f00849b98371b3e330e49506bc13a37c5520a5bd2c3b78c3c9ff1ece4dbb7c87c99747627de3941dd0703a1c4c19c42503ecf6edabe189d8f2ba864fa9f052585fbd82fb9d863217421de006d2200b4377880232a199ba26e7c39627cb2fd2d1e16c81f96a5b044878a8319ec215b2182df011612e3bd1e3283997fd82725181bad0e48d7867d3ebf3312aaffc0fc9b8d48f61afbb590b197aec69df6c0968b90ccd11e45a560ec4aec522bd5440cd43d3542b37aac71db94355e8cea6583edeb465684b99178d30e2de6de19a45224584c49cd531e2d81d3bb95fa97569d2a56c39e38c9e89e46ce130e6de1867ffb2e5803f9e8805734a705a699666bcc0ade326b6236cbbb056289950494bdd34f0d2f6ec26f88ad67fcfe028954557a8a939c42484bfde452b644e63fd2dd404e6e172088810431525f89f7026e8f6bea92a27af34032955d78060083fd40e44bdd507c011c4e68eecf9a13ff40031b102d60a7abee8bba18a57076e3a3c9b070b03a6ca59684ef810ade6638bde1bb863bedc0a1da6a8278429f41e138a6766ae122f2ff014864d3d6ca0e17293d5baf400209a633227e9e824737576cace05089c6daba68a0600d23d4d72150bcedd66a158e2265a85445830b5532a67e9f000436edf6ec90a8d2a3554a1426c38ffc750418dcdcb5db40c1241bab2a1181c2b5cc243f4740e146aba55d3856f2a44a050928486596fa3f0a0ed9b058b242e1a54eab6b910102148cc87f67d0d0ed55ab050444d2bcae8e142a58c948f07100176f5bdb5a8522250e2aead060c1744689df83c0c0369b9d351c5ae2b0b212092344638cbedfe67840e0551151c5ee111b76faca9262327e697e398f59fe54fe1c3e196f39bf24df2cef2cff0c9f14bf9ca7bcf0c709e9d4a03d30f960487ac69389d2e20bb9cb21c9f288f2346fc273096c34f946438d861c0d3f183e26cc20b47168e390034347c38f84180a371c6e18e260f8d8f0815046a18d430d861c0d7f53031fbb670c01afa0760822d2aae28824b125904b23b04874faa18979ddf2aa5a31b7b392c7c9f1d406517e9ed0ca538ed43bd82b53a4e23010c6a8ed15ac7b5abdd874b0f7fa61cf9a1d267dc9025c633c5bc5855a93cb13c5bfbed84e9eb51c0ebb85fcb41ba980c40457629d764d09904b9800ddd7060ec6456c11384b3c06f4d4da0a22b942d551fabc8ebb6e6d0e7accd4168b6449114bfcab358eecdb0c0ab0a07af0e4c840a755152d8af1cc9fa9fafbe5040d191a1d04ee13664c132867aa8f079898dcfdc1db1e0f60d73aa542b75e860d72dc2df1020cd8652a6ec310cb6f143ee63b848df5ab40b254ffad4f3f2a33bfe7d88a3251630d93be51cb333c6ddf6edcc6a862c3e450da8ac972a306e23b74e8275117fad9d89b00420337c66ad01104f15bbb5824a4c4c608089730b4b0c9228075365139fcd0b9bedb38704fe1c7c28c4e80c7965d3751e6746038fc9409cbf32ee2f0d3fc911bc6da33ed04c7155800a800929c3763ea3f2deed7fc97fdd3f61686b14126a995f074949e622156fca4c88333ae414d1fc8e6b0ef9b1cafa978458d0174dadb79805743ad61ae0c068c5d1e7ed40f1767232ed7067f004ea14575b20ca0f04898a73b789e1301394b4b8b91989f38d36f6bf33725890bae0ee3994de5f87c60b6b68fc5df2999055468af7e00003c1a99682e2bf05b6ebb6815f67f27ad51976159dadeb8de690cf2635a9f8ea67d5ce848369a1941e00e2f815c8bc6199effc3c70b000b79917fb1a6107ee7e2489cf11d8446fa1ce223eb29698b484d47468e3f4a828ef49aa9277dbde84b6f0fe436f6d95bffaf40d592a58887b1c746a660df8a694d0ac050a9d01e1b38cc73e80b7ddf7aceac7c0139de9883ffbd9ed2ed57bcd4b91e6f24b0aaf15a6dc124b271a91371c862575ab3fe10f4b963211fbe877e7f6f09ee3d72fc000ee43370d138c5291ed3cbe4c73a4c3acb9c75145916cc3fa9ae775443c177584949c0c7940090525dda2343e937ddda34467563d703d0e30d8ee3a0b80e8af320380c80d360dc0dc671a0b8078a7b001c0cc06d70dc06e274509c07c539001c06e16e304e03e33a50dc03e2a807556c9ee940dead9fbb2b051206fc950220a85b2067c91b5b9d3e5fd100384de83a9aab7509608f2a5b54479e5d0aa28d47495720adc03482d00844151c5da0b402290aa6100c45306ac1a805a4149032055cf4c2921dab2f2af9fb202f833649ceaa9749ff7cc89351331927b5c5e4bf3df264a899d079cd12a97f8fb655fd42c9ff4ff9996849e4a8ba90f4e7b3fcac9a9338525924f9eb915f56ed449dab2c90fcf4c88b5d6b52c7b58b257e3ce0c5d42ed969d565a9bf0f7860684aeeacbea8ecef87bc0c342438575f26fdf7515e468d091cd42d93fff6cc2ba306d20a26e0a4b298fcb7679e0c35933aaf5920f5ef313fa686099deb2e4bfcf2c41b5b534287b5cb657f1fe1c3a42dd1a1ea52b9ff0f79b36892e4a87291fcdfa7bc4cb51371525948faef395f46cd491dd72c90f8f5cc1f5b63a2ce5597b5b487f05f3957a2e2bc3a19115fb4094434904e5513d1fcc07e9720f58bce8c56f56f9a127a3291a103a03fa19da6a481d1b667639227cb94274dcc3f9200ff85a5f316e5e2547fc8d7a281cadb2300401732b8b42a4f28d77d30731258b5b647e1639700835fd4683b59d99a50a28e5a8506e2133753073b8654689ed0dddd208f95f159f51caf8abda5cbad49180528bb7ff87bbe4df882a563ce084310b3b3e8b85d559ae817785064dfb9bee6ccc6f8b3965aac4f4fc5ffacc6b04bf82d5949d24ddc836a211d4a8bc5d6aefb50f9e3c058a910a429a5a5ebee014e60014fd62ca0e52b2657c051464d83270715eff06f62ed35be36f0d5b534eea3217368f4729b88ab505f70d821e26754b37f5e46241b30bceed67a401bacd48bbb2eab814d5601040e0123b3b23009e84f17a6931a59688cc6b20992d08210e88561646c34968f540c471894321444c60e53ff66f4caee184ed262684ae6ecb0bb45065b056c77c16eba925a13b11b582562521fbf94873d619eeab70531e24714acbe94356bb91915c0fac77c542a93b488724726e8d6fd9e1a49216a7631e54a89d4da83b200cc283bd4d5731180441606e3d7cc53ed285a6580eb22dc7c814bb801b0fd45c3a95f1d881aeb23c85cbb818bdc1267304b300fa1931a04c97265ced83100e11f14e58db4695f7c1c8f9154906181106d9835d501370c59e9948660ad847152050f176cf790761b770943a707586b658461134187b090bc5d141d0b490e01861f715c3c5723190abfe31685d8a2f60e098b82359419927ef08cfa9f1257c6a0c43e8a1e375b7a460c2084c5c8c69dc9315dc12a80a46e5eafe083b67a826015c0ad9d26a66fbdbc2035ae9cfb0c37387c1bf329c9827c7fe557029b6add91d76d70298e123ac84fcb22ea6ba6504c237bb872d875024238bcaffdc7da628e79d15d5b93620315093de0b9da45ab349c77125e0025d3e14192ffdddbd78c992ea16c449f177c70db36dc6ba90540e44ad7eaec08310b4eed30a3df91ac1d72625c5f80f1e1c68b700c63bc1472d7fac01e7b1e1e1d5cab15ea14c89bb6cd3f14a7e2074df41270dad480aecb3004e0041681fd598297d441085ee3104b634c22c2b123744afdcc0a9ad4e782f5e7edf042f138c6300dbcac674bcb1c1aea0445cdeaf7af129f5d9a11807c3999068be878336933957d889cd239c8a5d84869ac317676cb639f552d1ce6872a78a7c307e1282e4992e07d906961041e389fb11d767dc59235451f269a33781a175ca40d697112c4019f772636dbc157e396be82e5d36fd8ce34036fc3d0e34f1824c5206f6c0f7ad4623cff513d1ae8b997598678ee300ce48f30125de34e5ec7ffe71ad0a71b18c0b9a297fe0db5c3cd66c66b940823ca88e66194138a1c93d979f5c5497187c71a826d3789886a1e6723ba6f82a1c11cef5893ca3373868fe4625ca454c482d179f2d961b4724a0d372e84e8e47f24c2c08c32569dc1d69dc6e9f000d4939d93d25b814ed3b709af4f6e2132bbf7ee55157c27c9fe51f9bc8efea51a8bb93aec27ab903a9c277db20b8dde60a96d149e539fade01961f450c9521889251468649401d99e3538308dcd721a4092ff426d1dc4e52de7a05a902bb29c8e6cb4c6b400021a4be749c5117954894d4bc38d041c2e025eb89df769703c125d2fb449d3b9f825e20e0aa4cf309d77cbdef8d99d7d19fc8be3c44b942beab9e2dacc55fe5dcaa3cbe4402a6a29edeb54433a59c542172be559018c2b2cb972441f3078cf0f478c4b3ea98573cc54b9d39c7a69c7eb702ecd5ba52deaa59da39c4e6db914b92693c743facf9cab8481815aac13f7d9bd8cd655aaccf55654a1fa188a5167a1dd5c7da4214970dd09aeb67dbd7e6de7b9cc72a491c6d0e443f655c03fb3c9d27585162f326ca2bea75cba0676aaa1f7ec5661c3f898862cba4d0d6390992b98d475930ad4219128f516753fdec5e9f9a3261f88c3d61bf3ad11795fa5fd8d13bbed798f3ae5c2ab572aade07c861c7199fe5de3e246d6bcd7685c0c16cbee624bf8358efe934a0443d68be4c9be174e730b76c0261e92439a23dd8562f1e627c3a72067ace8fcc744fa54ffcf29a98ac765225a52e5c64f165bd3171e2256103ea07815e661cafc66621aafb037eb88be171d14db7b3fbf322f2ee8ccfc69e40cbb637fa6716fa5601922da82231017411fd955f291ff5b60f9e6aca056464fbc8ec546f582eca333af1de4f8be44004f5f49e37d8629824a304d5736822ee25d045c4a955c42cc154e267ae73830f73fb4400ca8662a96ccac90561a3651a6c19723c0b279ec9a6b10c600ffcf6f08802846d2358ec19624f369fe6c05a4f5c5fc70f1737911e444edfa7d1b5c79630f852ee8593381c1572ce2a01bfcca1b7d0d2760d39e25463f917a216217f95b7480f8c35d1ea78fcf2399c6e8cc74486bd29a4ec7aab838a5ddd2da8336f85dcdca54ef3e71ad737f8dfb02eb7cd92a0bd07e861bf766d8a1f898cfba50b5f9da3215224b78b2073b94e35e81557b6a420bec86ac754f18d25a279c6753a20e9cd8619cec2ed7c5e40f3fd0d91deb6d5b695c30839cef5afe5694ee893bd8c98ee16837e97ff8619d76678fae59038a343171f6e48881a14ded0777b8f31d467ddf88c7396e3c28b5a0c0b1130bdd357d4f57543cd76bce087367ddb90aac059bb9f7ad5b7b7864e05dcc0912e5e001fbb32cdae097e6f7dcb8f8ee8abc40d111d336e43d78600ceb1654c6ee7a2cf58a117111ee599ca301f8e51d390694c6852b1258436e3b04ae4dc61b19948414e7769919d74d76466c0c5f085272b4aa891b5615a2158fe04e2c0bcf9947c9d22d1e7c08a194ed6516285f5b22743f1b3219755b0ef53d2de56c62207b9865319c97aa71205eb1a538f1dcc31bf7d1acd549d4316199187d44f553d8f4ca4b7b37148ab57383e182e2fb1df4660fc5acf0218b81df436baee404be7a864e0456bec5e71e16de4a6da1e46a3db9692ae2cf9b3eef3be15c99c5d270273c2822b3fa32a769a55b1bcfe708df6e2bbf6b0da0711953c2b8f0c994d21a2b6a0141ecbdb3461eed3f8ec0d40fc4cd5f41728ed510676822062f80bd7b6b3303fc0b6abd4c540ebe99cbf3cae1c2406285d4d2d8e196fe5e32471e96ad92874b4617179ffb70175fc3faab89ba677b890e7b54eecd7b940de69829815257d0d18828705ecee8d1e1ada10e3c4ca96662d1987e879c69c5fa8ea3006da4a3acc9ad13b02cf6c9d79cb094ebcfd47826f4afcdbc443e6cba6e952acd52dcb85b5600fed6f06139ad32680b4552d272c4125d6f87e8d6a10aa2aeaaf64b83f21fbd4bf11b7135ae9e7160db71e7c092d171641ddd2210aeb44428167e1b1f474942481d646991391424788d606f8a12e0544bb11edf5e53413b556bda8acf173b43bf0a80d59a65678c5435a356746fe56990bdab94a57142e3d472623cfc1d71cbf21da931ac49dc89c53c667928155a945770d529bf1335a7869bc80588461ff00e7a4b7be008851b841f29c2050a0aed7bf5d9c8fafcc42baa72df290ad1460150a57c55699ea612977c7200c375657df1acdef3a79437858f541ac3d2dc8702ede5f19fab9913ab0983f77e0f4c5c0a7842fe316c6094f303cc84707f018ddb5fab080a201762fe80d824b7be5e314a8c0d071e20ea23f470162765fb29875637c24725d919eb3c9748025432e9d100f2ff89c44757c2511ec3fa40c4f2fd3f78bb6640696eddff515e7e79e9d2d2877505dc676e6d8f20375f7c126739b5d634082f53d68a29b1929f526bf701604dca66981f86a23af3e80e264f2750cb38731807453791178c54456fca431418662feddc54784cc29a18e5942485f609b4250c7a439553b91b4ac79397712b8c711e8dce38a6243495c741de7f4ee93b3022dd89e03c0ea25463854c1c6c11c6be59a07833e5b21d671838420c4a23d66dec9149024d0425e86718e431fa799261669ae56685a03c2b8c21199162637f1effbd81bc538df7cb13e204bd2470b042f2371b542231ff1db1437efbb1a7fa53dcb25a1213c96ce28db42568cca10984c75a1a8a2c27176572da05cf340fff011c729c307ce33bfdbde6d7b315393024c2282bc5ddfc46814a12d41760ec5f96df39c397d54adac182132eb666aa8766ae9f5f680128105f99410ec7f50859c95f61c5269a63cde2bdd02361958a9f1ba3bbdbe5197113579560252aff0a57119a638af448cdc2ea2fb021e9dd586c6b519b73d974044fc3d1619f30f3e28219e3efa78f54990ab470b95bb035c8e01a31421766157e5544f5c5d615890fa801adda41379d19b1c5a3cfcbe92128644abd19ba41779d93590e6492c8e906e7454805da0cd2679ebeaff1f941a278e3e34cd2cb76918dd238faf48b943d4eec898381394a41bc3328518124d8b3a954089d52c9088ae045065fd54058d9bb60516b11a76bc3c24389a0901f80747210278c631e381384edd8308b1c7f866b30852a49bcb74411cf8fb2c1901729206b55b077c3ea573a6ff8d26e6c808942ccb88c0222670bf09b3df601cd76e344a20d53feb9f016cd34d9394412dabbd1bd7fecc750dc6aef2a40230633318f76019b0e4c89b19a17d3c63b6c73b3ef5532f67942bb4a7f4a16c16a0d60c412b311501d0d51f3a5a0aa8d692539b87eceaded56ac132e2acf2d9738ec4944a45a44c40a1eabf38f1149be81277190664186be65eb12f61b027c3d3a0c842819830d212ab7635d58c976c14784e414aae792c9d283302a792809b2c02e5db13afd385c2e6625d9f79c07d58f6093bb4653d3d0c172a51c94a49c0756a9b7f8533499bbf3384a925b153d06169c6e02367f8d4ae0facbc659ffd89c3121efbee219445f61dd6eb9a057e199a973627288034013206dafa58b194eaf76090d801219e77cd01df6a1504d5f57ea58d351fdc20be8a9ad5ed4f2ec78320c47a754ee8b0ec4617a3935ac5139d188a575c3fc01f52334350e14d65891dfdd2615658da107bb3b5ef1bd177828c96c3d9df2cafd10487eca304507ef4ef022b157d4e2830eaab088f6e5151082f7f43b0dd0a983acd8d50862058db0c3fcc3fd5863bd0c7578144eb4e42431c2c4d6f435ce6f795adf34e919c367bae02b5241a32e451c8db62e7a1bf275780b85e7e0b8996a0127d920398a6ba60531d7ce9cab62e3d12c634f9cfb015cf027364827718d40891abec56e83e4ab53bcdd1da577583d8b2ead4380bba0ac41e8b817b310254a56957e14255320257693c97fa87266b1aaca70d561cb397bfda00dee61f0488c7e01295375aec4fa7483532aef4c6d1750f8b9e2f56d1816fb3aa83ec428485c02f2170249b52d0c24bd5854c8e3a46a92ba5410b709952e55c3673f58c7d166d5bc0bd547b7a17347ae9cae38e121b5c4f182a955e286be5ffec09f1032a063c7d48784518df844fbf46cda52aa51b1591514f4ebc0e1260219c372d5cde8c922ef99c5e5f5517c62d73d0591626287b34cd1ccaa6d35159c0ffa71b16e8eb13bc702168e33ba90185c946dde7a7a08c46f79d731d7a6a0ed0c1201094fba0f53faa6155be8053aa1b1a7dd85cf350332a20084b86c4482af597e17e3b3920fa2cce942185c9721636dec466f6f93660b7464c04f861d9aeea848617792ccaa7c5d520381bb1d360d38b2794b1ec9d9aaa44094ca90593e9ef3d7f15a0601477cb852a8294bcd4545727e98fa5d138ecf05f86c6f8723e886985c09fa9d6491975fa00577529f7b638f6d086ad051e3d952f215771cb4abab01758fa1711ee5313270165c8bcfdeb63c8317068d9268b0f7f6f22108a16bf38f02697b873309489b038c6c40280bd28bbaca0486a5adfc90dd0017ae736ab3bb0bdd306b8003c5d413fa2809168bc451b6b61c310af53f64b4d784bb43c532ee440d5409dd25c0303b4402ed7b67b94231a0a1d86afbf5398a48a2c024ce33238e93e27673f3526e87da6bcf9a16e991ad0d13c81611f4d5650d77366a17de0462de76d02b82db9357e23171c269c65dc92b215864b5b4e91ae6dc23e0bc3643a90103b06dc0c450083bf1767b2875fa0c7c3e264b91e3a5c0bd58727282493c2f87f4af126172e39992b8c331ed13e5048e8fb5eaa3efb74032fdd44e007273274d3662ab306bf8bebdff987815ab0156a6b8ffa0da1c7b79b86ab5192fd30972b1c2032eb2db38e0809c44c76538fe910e1cd1a4e785ac2f5150a0752d584b2bc658c8a01b524d339a4bb970bb5b6ed43e82e986a1fc048f9e2a491cd9bf2c78b08ef486aa02e3917217c8b671c7b45f8c8ba700299ed127c3d0fd3f12e8bffb7c03647876fa96552241c9eed3782df6315a43252a4508f12aa200cc080c7e3bb8ee8b8810b8def132f395fecee287250f6e7bef415ff10119afcbfa5ecd1be4add1ab234fa30b8fe5350c13d8c17908e6728bae1ba4fc1e1c072b8c94f42faf58efbbee59de4348271fac73e1dfe7d33ee46b7558f5c4d20bdfbcee3e82cb9f1618256d501a2e0e9a81b8d8f0d23c04dd23fb6990c884b9e026590f985b7b1d31d6cad366394c811a88fc52544147dafcf8a57bc068a26949fc0c26256b9fbc7c3f503fb9630a2dad9f2802495eb3b898cdc6aaf95c1c70bf3630edd9352a5faafdd8b59c78aa4de6a33af874ed960854ddad1495940564679810673f13cae7f9cf014664823e5d40ffd8fd6cf0f053f6d4fdb8a938c9b6784c72659ae660422036e822910a608c50087de16aa0243f3dc44511681a42bb82d9d608ec7b59f56da555af749f0f1a524d28e4610ecc15817b9f4da8370a5f98e3ae3911b0fb12bb3fb1ceb70323214537a74f0516aa8726468636e5302645dbec7b1fbec1f0006efc71af65f4c54d84bcf7b33f71ce6e233ee13c743f169279595000270ca94757f4f6a3f2f23f636b40bd317fecec2243fbad6db8ffb3dbf1a50eaba69872cfe5c2e5eeb6c29d2637faaf37aed8639fd19f8bdc51f9b1341b98ea34baeab3ea9689dbb02572dcfefea6efe9eba3ef0eee9d9c569472deca19cd46b1040753a4967e43b4e22243ace19a2c03e5d295f28df8435253d1f85abfd4dee5736a31ab10e155d39544596d82845fdf3aff5fd01e9a4ab2d3409ee730406192df3f389f53caed958614f1bc1e69361012689030b0ee149b52d508ffd7c21905d1a9050709f6efb9c53884318428c4858d1e84bfd64d10f1d6a601f320ef75dc27ef429e329a90884e58fba1f772fc0f5dd7c210f3bbdc3ef860851841fb59f2a26d249150129dffdcc5ba1fefeb7fa19dbc64b08bd8e4410b456ddc15b59862bfec4086dcafdf66f97f538585d9ade8b4735f8d084c9407525491555cc01a798c5cfd8d6915b653ba1421e095f9f36d36d9d54314e842dfd3e4845af1670a8b444320281f2100b47dcaa3cd6e88f414716585a23bcabe91afdc83aa9fbb4d781f7cf4a93931f424cf7ad41d3f7e9f84dc064ce7c3166bee18272e8d234196161b5f3700aa67c2cfc2473bf94adbbaca9a9a5e6d2768fa521fb6359db929bbe74eff0906b27e5654c0aa82ce381dfde34df49cb993be982fd633df33106db0ea409144e9d8af5099a657d6b3e7048111ef0a8856b7028255e375a118f55aa4c75ac2b912aa09fc79e8ffc4b9ec686b2b7c5022ba457fb392bab81e9ddf9cf7919a07dbda1f41c7ee671f4473deafdcfb01f24ec3d74992d38883508a31784d8eb87d995275d791bf8975626935584fb06f55c3fee2769115e970228f6b7f4d1c2c6428dcdee9083a82026346f49d6f21927895cdea9b7d85d8e79b9e044d38d72e1c925f6e1fbd26958299c4a6c2c84205cbd03a12a57526383052cc0c24c90f62dbacbcad10c44de686572c99702552f8b1b37a9bee72d7f2e7e20aecdd73046c95c818e66b66e6142a8523679822434c4221076fb208746202dc2f8abe131d096881c366668de033d150164ad7ba27ba1a3ae5eaaff0b994323413ab7198536fcbedf2ff627a7cac37bc1dbf624eae3f8b7e8a8805fd163144ff5b9356c87e379eec00102d365a4fe0fbfa650f0708340a39020d484505342dddb0f04658d637f106642c9b55fc6f26933468a51049b8ad3e59b05efe56c047357d553e9f428c4b44dac044f4485e5c85f42a394f96532c795071021e297290f51724e578b741882ca0d4832bbb48dc05ff6f542601d9414462538b97e3df592c901a4e754b7e4a4a76ffce0dfefcff0d54264302a83fff43d7ebade7d0930f5fb038945503c360d5bc206ff5ee18369bc7aed8c57d61fd12f04fe29c8a97c14bf6b434223105a7694cc71d795c315f3e37c936a20cfcc42600d05e0d0b936a2059ad9eab76815c6a938bba286fad6544e463b660065cf476d600ff0fe641cf3e1ecab36ab16e30892ee8ad8c18a724b8759a7b718ce9ae94728b2dbdad7198cc3ad36fbd4eeb8a1855e14d989888b6d65b34be223d6a152d3757723621886ba44e380f2ed0082086126310996e82ec742d3d52045bea78dbd8dc37c5303e583f18db09f8e075586237f08860662393fadc0fbc06afc49a57ad50a7fe9e7e9163566ff93850b988f75a1294bc1ac150be1f449883bc9590124193dd51410463aa68afe6c1b29f4509c660f86e75dffdaacb14f93188543f695f0ac49d11380853c84e2edf88bd83ed99dbf9012f91cc251a6856a240eabc5755dedbb71e30b8330b80b41a75f5c40bbf3929b7e99a8ed341cbe01bc1f824b8c0701e3c00795581cfc9bfd4b5f7de1ce533f9e60106009a1748f5e1798be5b3184dfd696dbd4249aa32e6846cb5817da8280d221e03288b93eadeafd768f4b0d2055ef2a32369e6f71eff7d8db951a88c2c2cec294842de1a62ac0a7c47ecc6f3c1f9eb1337935dab2fbb8e63637cfe067084ef586d862e2e9a48d1875f9b2df6bafa846d10810d6ed6422d8e3f3115f8168597f6f0d7d87bdbc378da3db6a32d2d162502a0f9eb7940ddcf532ab6182f57b7bb62317ec7cdb6ec463d6c802747482951a55bb7842247aa2e847af215ea81e93afb1095cd315b839e4073a97f717f9cf251374fc192ac190390c66451f1769f2b895a63fe6092ef8f8d9ec09a793857ac85684e0df92bfcb6a8fe11b69577b23b0addf2a6cb3806c7c643397de7228e16ff1b4fd8f3722d5450027b652fc7c48804c71ed239b773c48b72594db00576172d345b50f287abc1284ab52c28b818b1906429594439344c6a4e5585a229b40c51565751d52e659851fa1ab908d237e649808be24ea99b0405e8ababe1ca765307343531db4187335b096c689d668d582edac7047878825840ed90a00f8ef6681dd759f34462992265498c09e99f4f779829895e02041b45d43b525bcafaf8e600b33fa5b4f8012059284992b1bc86bbbea350597a498abdf3effd8d31ec6852fed4bde085665bef81ba98b8c9777450c473bc033efc8b7ce275868d1ab9517abbd105b9fc2504db4476eb44203a62aa451ff06f8c38697b7aebc416cf000bf6c17772f27122ab280afed2a2a81705c7fa12754d087973f7011c960e17fa3cf4df062a10882a923b035edc3893ba9d2a77126bf1ceab18b383c53e3f0f81b0caca25b0ec797312a8d57c1e02c096979b40d9e7e625a06c2b6884f990776698c2a391b8cdc50feb8773050fe4c8112f919d25b08647e972010000d2fe751f230b254e1c2e7162f552154ee96b0376523227ae1e8f3602405e3d0d0769043b35c76ec6ca95d9603ba2f8214038b98df3d3e65502f5f3105616d003b5d22e35a91563fd0b2630fbe67a14cc1c130240859c88d04e194c0db43c3cf40cb408ac76ee7e8a761c38c3e90272c712d1254a4f63d9dbb663c7e4b6b7c6ddcb6f9b45779f73fb6b748f1469e55c5970d2fe586e601b75ade37bebdae9fcde5cff7ed381d00f5c40d1876f5b79051c8c040aca7324ca7f83646348f48c1467e15e19cc18e4470b2d4c73acf9d72df9e3c8a329b951ae0da3d18da13229b40197f94e5aac6a58577c7be682b66955bec4f2a0c8aafa07ddf5efcdaec4f35b793f17395d791780630cbf6fc5fa5f1e5d15bc7baacac304111f49b7140c2733d98166e027945feae464ab690e085f60a729981d8975e68c2cdd5fec1ce27e964d8f7b5d8a0a9c23eea98c323b36287297a4bf55a67a6133a7ca4dfdb90d42c8b54f4a11f9e76e4ab3858a3828a9bddcdaa6552b7b64c2571f0464898c05b6f6a6b747b4b5d30c156e5abf1f956f6e26914dd8bb0f42cd8eee6e9c5f2529d9e66e27a61d76e4e0ed4a0196af29ba99ea099453bc7f5529d9676ed2d80f9f270feffd5505c99fbb99627f78de6cf8f75728257fd66d9ab2878c7c52597a1a983d52152027aabe702fde2ee6f65f1539d9e5b7a5da9dabb705fe7c1489e497bf9568f7bc9e1d78f3a12493bffc4652fb734d6fd84d8111480a44b634bf8ba2c214061c08c5c742db378beef95f8636321fff326c5b249c0efa1d682aff0dd9557bfac0739484e920afce4f1c01147ed3661e81413e6fc4c584a48ac7a54e9d3986cc87a81ae2edde442ea4632ce4d0a6929f5856a6af0f56eddbcd9ae7b8a668129fcd2d23a8c9bb8da47d4d16ae987099d1db582c719f7b87d02c20cce0ee19113ca715f61e08b0510174da6ff7d18f7956db67fb09d7987d98aad837189535117a8637c0f15d67009477cb2d6ae2c22e01c5d8d892456885dd30340a6e327eb032138d28c4fe50594e6c164419638a1d74c7afd4132b53b7772d8aeea61ce16159c97867544ace78921f2c1b4168eaf6506cd9514622a98eed4cd1ed3ad55269e2382448742a1c7689484db4c011bafad42dc73d5d64f93b03097a0f9ae5886745283616a789d2f1e5e0d88852acdfbb920581066f3d28336ba02dd0887a79f7ef3b2b055f9bada3d5541f4fc7cb708a3a03c112c19c3d868c525e4f9063946c7740195b7ca0a9e2ae213f4d720ea413b9a2e3abaae397ab33b06eaace27b8c3dadd840cbf5ba6d0dd6f19d8038ca627b818e3fb9d4951c7da80ed3bcfbc046910776a87389f367dfcf3b24c0c1312f8973796161ee298349cd2b5175cbab35f89372a179fcf2ddb5b798ec6f9f4f6f389c69bfe99a1394020321916afa277a4bbdf2486bd5f17d1babf8ebd59f4f2f8bb3bb7719ea973d7edf2d2871e6c796df39fa54cb17aa50e6a6872d4eb9eb8e8e8be4a6a24cb590ab1c5e517c7b18625a022387c174d22b6618d2f582ba289852224c9fc69cc3207ea084305cf5fc1301672e2a809331fa7ffefa04523a67b6fc43bd5b5319c4ec73d10cf44d74efacaf7939a755aa0a683a1aa5db6388174242194fb1bbc69602a23241a81cfe574e5245c556dbda03eac64ece5a5b19461225e3006aed1ea401a5137c07a50dde03604889e7880917cd7da84df57e9e70b41bb5e2007180489fa8bc11e4e8b5a407ed72ae35b15c0df7a0d3ae4a532504696e61ba6f9bda0166b2b8d7fa49ffdde1eb2414418e1df07ccba396dca45d2cbdcfd72ad66c54a74f9c87d2d8fa006e1ced177a8645d7b171f1ca95789a8413de4536d70eaa16e1c24cb1066947de1f78a3bc61c60d43a7a259c3fac2c88ab1acd05b89d7fca604c872103ecd34120ade48c48ec79b0f934c07e47b167163ea3fd0e07587cac6b01923b6ab899ad380eef4221f5b25d736e590f5e6754b8b526b3f3ef2b0f3607c3ff3e8ce465b32826e98132d27039d605b9dd9879f12692667763020c2f10976797c03cd1d17181f0966b1c28e3ecc02f21e5ed3c5e06af70544eb89ad87c4528a41142a17f64ebf53c37b596fe249c24c05a82089e627cfd0833878b6f0e42fa30154bad0e8e60d86d917d300a5813967eede2371672ac81cecfedb7fb665c905b4b82749747712bae563e28afd0464039f17d232d581f42e2d11d6fbb86ecd97e88c260eed466455c4509de45dff9b147036af7b13f2224bc02d95d399279ac5e4be329a76bbef6cacf5135988aad925d772a87af5a5edb0b4afd1250ad4a816ddfb33502d620cdf8af3cd12a99bac594440b70b449a5b9cc554a2d346a41f6a66573fddd506da59e98bcda9f3fdfa4f2e0f296c427f320db8f664ea695a14c39e5fc6ac557e06d6082f62a185e6b46028ca4cd3f5bfead0113ad9f5c5481ded52605c2253d698e7ea006260b63377ee59c46c62e8f88c628c09bbe306fe2d2976946b51eb0c6ff710e08db721f1385101ac163a52149970892eea45666c2b78e941ee62e6ec7d9a66828620fe053b607fb5bb1634a5f4a4890028f14b8b4a835ab988ccf2159ce73eea956810dcc221f6c88cc43f70a8724c6c748123940cc95ab39ad8d70e47aed04d303f3aa07d87e83528fa008618157ef03ed4dda648927cadc3ffcc0407f0b8d66c80a6b158115659586a5ecd8b68814d246a9fc4674578da4299a27eb1618fdb5bd4fa8d188b84116d4e4df7fad24b449f54d696d5344f4a3230b29e5db91af1d6f2c6249146b1f09444db30add7f8e88ff56644e834ca313fb7cb25e394af3bce32102f9b362d73aaa16b9cde0d4357fb3e5161b0b4ddb7c351321cd904626be85561da98739397c20339ec68289b5e58b83e0467a0ce8274e6832eb6e43650224bd2974c867ffcdad9440dca8c9fddbdc8b6fa701206ae776e55908691fd9a2842dd136482f139e1afbb4272de27e0b43df896a91e0fe088a4b2c2bf2d33f11c30c68a19e196cd3ca97331f742249d85e8a35c6fd685f28fa3e66afd842b8e28865035e071ccb12f62dc7100ef6952c032e1178f8db0958fd2b20fdce1b5ad8c04d65410646bef32a6b28709b5d8166e302a6e8e97742e158b353e26b0e9adbd6c951dcb4fb6155f24c93f5eccc17bf9f20b8751ada81f2a1d1d2489ecc3df5e8bb7c288fe91be75c22d6a4b3a33e58de84f6bc2b01a637ce05cab51484ed0a166bb966cc23ee56431200c1366c6f18f4fe64a382aeb357c7c9647c9340881f6ad95b6fb66b2d3e33e6aa1cb041ae66024b0e03ddb2fb17ee287be38977d06c267ec9025bdfcf9f347bb9f1490f2e492c788910cbf486a1b5e87588f4b4d6c31d4f140be49b8d0c689adb3adee6a82e89e9ef1b96b3f00a4b3a81853186140aa4cc599f61882a55ab5cb8a818acdb15a6e6037f85eb0e411a85ab26da0f994c16cacddbd562dd5d9c11b64e4d297bc70cf39c32c39e5b514f30442637573a2d5d5bc45367c74168e68a45e830e72b165b66288af8e19d8d2465a255c7d966a3ae693f92f20269ca9588f4ee5130c91f8cdb4820d022723fdf40b916b2d1c06cb2c9e5708e35c726644dfa2b90fe627befbb83b30a5a59cb119d0f6c663d7b69dd5b4bc410ce5050685387610f70f983f45e5d798b0bc6845c6998d7888412bf77967103b6e399099a41feb3166a887ae81b1b4e649341c9c9e51e6d6108f3bf4ad754598ff0821673ab777f87f861a09fa27096be0ee0b098377719fc331bce8bbfe02ce9e2b24f767a56402ab456f37e0eb21200575135286288306fc9c9dd4a10ef4572a7341cbeabf61d6d5ddaad70eaee1a23ddab6ef09b9c4d1d61807439282c37c798894d692030d16e8e98b9e18e1e7a28934b626b4d105e22494ce31642600badea37e18b34866762aba2c8f9dc4fe641e99c38045b426d6e40069e08378cdeb32d0abdaebe8851edf018661d4da4f875ba70ca42fe75b48cb0edb58e8952f04d181e5ab76413902e26fd1a0e93382b2e87da7a5aa1402b94ff02cc9d2a13d9b56b65feca5f5089f74598e42a5c91869c25c432482fa14f218b60dabb5f6bdfdec9cd2df10ad2d750570728fb69e244ef6734612630215ea29ae0c0c02fade927e1fd177ef2892b95fe9392f13550ffbe0900c9a6f7603e2cb13a77f70ff1fed2b4f4efc2396cd00dcc344809d8ac9f9b0517ef6b9839430a1ee307687a72e588fe8a926da4aff893636ca2f8ce337c7d8e30e7650868e440edf1e1f1a0efa9810626673372228e1350ab8179ea03b9dfa94e9995b3a3b6121418b48af77a4a2c087fdb014601fe736ae80cc26181978f466b221967a2ef7351d60c7878c2353c8141a334c6ce5521b8efc5a15a0b31e6152ebffb9dd5c3ccd35a5cc4d39979a1db6c296a171b5b4e62a97000ddc05b6f0a9e93590eaf8d09468900a2ba254a9dc21c8579009c427b485e96406b5ec6fe0d6ddfb165eb5714aa64036b1d99db667b8e66cc196c64827d6d967c693c8bbc9015506b93b05b317b76ec0eed64889fe618dff9aecbda004915fe88aaea7d9d06b4332245e660571f4fafe4da9e84d32261fb3216bf3c2355cfb18396844186eb8f55827beec0530812aed470a68ab3fb9442c9de9ef26b3a405bca2ad5e97e4c352b7c45cb32e890c498bf870804eb68013a0b3f8ee810182e4aad47d1d65213d3ce905b966726392ce69cf3955198bada521cdaebd77d538b3659c6ea428ad96d7918ee5b3d5d50289add16e33378f28653fda087a527ea08f33e8a1b7241a3e6770f32f0d9b78acab098f8eb1529553e88daa6d68619337a1651ec27659ee9683f36db19086fa73be70e04548dc10235a9a29c18c0fa48d6c04dc134dede492089619a1824587f1a9526397483f13dabae8f2b2172496b66b8c5d860d940216313742f3323a9e3c2b03e59dc7ececc679c49407b056d6c3806b9c8d0123913b6510314e2de36953533ae63e76a5d125dea4da9c5ea42e9f3a9e25599d958b216c2e035c13f04b2615aec119dcc812a6b17b1ee4a09340acb1846521fd056a366c9c7479b49c20731f886a56c21e0796d5ee19c0b6fa4547fd89d653c56e0cb5f3847c15b29eb1b9863982d94f20be6507823e57ac3e92ce3b10a5f7ec1398200a1fb9555ac267625f9868d5301a25969ae2a8d7bc0d19fe4fd8730eb7923e30c06e8fdacf345d2bfda7adfa2cc5652b8abd4ebef29f3190c3dc36f85248876fa2a3387d544ff00f64df30534458aa0381028baed1fda57a5b329ca2a628a30e834c6f7e09af9178fa2eaf2fc49348dceed4f9417717142be3019b94ea5a9feb4b2fd082e1b099ff8350ccbb653b8370b4f9ea4ee77cc01a4c9f4894453645944cd7019e4a09f647024e843e4a0a06f1b639470944c3e9b5f40e28d49858f86dbebc5db762bea9cddafd721e09b8762adbc1eb3f8c0a026636e1a943874b406804ade017f5fdf223a380321c5c98618afb3364209ac28d130f3471e4628473d2a3b23d6329bb11e2da10068d1d6b8374bb4f740919bd7f8cfd9a5de82236dd38332acc8e4440060f8be477c7209a745a4665b8bef4d7629a18813d7bd24ccbfdfed9021c0227a7fd1ac755f644eb46bc9d18c8ee4da56d2abe4595594fa68b54c09502fd59694cbae8dd33929c59fdf546020954bc63472b26f28b3e321f306c91256be8c02a7cdc99e6b43d350fba9d24df29fc8a93030f93776590a357e49ae0d03c40b5b300e499e90a2022cb8743b9e1af3834b633c8a8a6320f34c5d9ca0f298c6a17738ced89c2b4a1f826a3aaa76fa93303a979e3a04250ae7b211cebd2d262fb4533c9796e56d465e1a4f4db56c5027ecfec255186b21e31b5991f615164b2682d3706374d6caa680423159fea37b475770ec849f0534c9a516922989aeb2e61bd965ded4210a2b28e7fb21effe49ce119ac426e181f848bdf3fe6435bcdc9e114b5a6b0576adc87231ba064798107f311f292bde1e62a0f6f79c33bc7f61820d90d4ad01180a4a7096845f8ac9c2dd09c941843688cf3168216ea6e8bd5203853f6f6b5ab978950258a90b2bcb637ec844f9a96e8a0dc4ef08e4327e8e4a2ad493e1560b2da6645792b08d316c40bcc6c2f442727679eefd8fad4c19adb18e07f8bd74537f06eb9530330c0b992a91e0d95d6e62b3acb7be3bfa50ea9d83ad1d00a0b594a813ff505bd01f55381d386c0978bc589cb4028ecd7519d91144ca24b8b7e699c1975c5d5193d697d0240a382051943c2444c1a58f5867f364cb8927cb55b475c0b445f5a91821b175456bad3a4617d4e09ef4ef756179c06611eb4a49cdc0be82efeaaf5cdac56d3fa1507e285155d40212bb8af207b8bfde03c4956795f187a121cef8ae1750865d4e1914bca7710927441933b80c68ed16cd388ec6c8a132573a1e7abba1ad17e9f8e0b9a93c9d0db11e406c7983c42e394f500bf20bed9cfd781c9d57c2c3798e3b52722cbf8e603cd82c3b7692b38a02f42ad838a2ff4930ca63b8fe6cf37a56d9a48cf5878d23ef04178bab4855283bde449035a147bb4d108017f1aa777a72272aa7c389fc5a8db2170fd820991d8c5070834a17edf818828da398614b6aafc9629e4bab6c1547801a826dccdb79f78ccb38b8eec2872c5bc7d6a605831bb2c67a8c8052a65a19eb049a63606a0c1c2b67d82685dbfa6a9b3ab76dacf611c516c62fcfc8de84825393a6fb9d42ee38d3fc3c53da8557769b5a98c6528c5a50146c61927cea5089e9c4d596b529e74ddd3d9d13cee41bb744db68b753894486c6e897c2f5392e1baf303290dd72a530b8192e81c25c66cce8dbb0ba02196aa855bab08ab92774f8cd8262c3ef04f0658f30bf56142999d0f75997a18ace4045b5d1da4716ec64a3b3725e6f252df4134b2e405b41a06d669ed010ad204fb250861bddffe08f5486b81a01d95c5e5dbb01022cb05094c981e73a5c9cb5e9e5d03df8d04cb8129731b93d2f8f9916c2303010f75a17ba8f54f05a5508380159ff21084cda8d5a55e31ea9784b666c4306e82a2115d19f9ed4979b3774acdabfe5bfaff6874fb5c24e1053b42fd5892a9bffe38901c8ba110319cfeb39a03f769b67aca52710c7e88fd75738e1aaf37ed04b2b295326cadaaa6dd7346e530ef88484b1bfa769f50f672b3b82feb7d11e946dbe8c050cecb6ae9a441109ea7a17d7ca98694e4dd149d1e292123e28b31055f320b0f4dfee8d3e6e2520ff74245e1bfe344a8572c5b441172adc12a8f33b393bbeed8c0f7e2624b7bcb57b7da8a1f53fa5c8b599b7778bd85cb1e8b90cd987dea1170cc900c5e80db6450acc99fa4c8dabb159ceb1140a9d2aac75468400494e733781294e93b38273324cd3b9260fc7053b0ba0c69d78bcd230467a9a4ddc5d8c06fba4da7e2e4983aa63498dd69c04a5feae7a93f49c8d25bf83205a2cc0f6fd176d32656387f33c83ce4eb96275f532358b9e45bf6a2acfedf0147f17a310f8549884c3bfd80eb110ec8ce29cf41272220fe960ab2c59964e20a9d4d2186b84b560c40167412c0758c51503fd0ba4d3ba864308742c75b05dde782ef0432ac37deb30add13dac0e6021705649e82d2cb8e30b23143e04c643738ac69cdda9cd97f97cbfa1baae2b076fa78c3feccd32962bf2e9339893301b94f30fcb31e8564afb067320608bf2fc177624ceb4a1612a5ded969beec3a10e00b84db5e8180579da0abd40dcace57de8705d80ba468a365ccd0c8c29397fc10a58101a0b71c4524f8b8e3e85d6b720f08507542e71b29501daea58601a2f8c9ec9971cd07b38a18f3f2bc68ff3b6c7532d9de6c7f0a9a69637f427f2bc787eb8ff8e40bdcc9bf18ffad53c3610eba57a53abff388e44db0da5db1367e80b51fa709a30323c59ea2e0c4bb7c9b4ab335648d49149bd827e9d13fc7e7c977aa5d38dda79c7b9cb600b73a115989527e80f9759ce975fc544bf5e00454763bdbe29c47f2cc7d97aea1368fc44c3fc5d9115372a9e429188985911900c62fb737308e290cec5dd26a37a1d835414d620f17167cd15dfaf2c91c911c26ad604b79195bf96023745a32cff21320bd862cd4b8c382d92e8d1bd75e120e27905ee7e1748507fcd0fbd8cc395c46c641b4d881e1f4a7380a7abf9d64528ae1a0bd256c41baab928948172a5ea1c50862454782b70148575c771211488a495cc77e969c74b4fafe09711c6d7093ce993e1f190e4bc9645352354517c34bb8ca173ac6b7f6e91e56d16a6ed899020cdf524af9e93dd333b09b48764117ce6eceafa4a4a13702538e667f8b04e99ada0b28902161f7edd4f0aa5d13be4582d4553e409425091f71ea261820277a1f3b392e5e7bf6857c3aa5ea07f07211f31203572012654e67223fa74f351475492bfca950119b4a456c0d9a1938a56181b6ece68e5cb76509a6ff4ed0a797d5b55536190baab4be3855014104c153e9e00853e9596f5ba6e5fb6ba70a6c279c878609faa35744aeffcbcf3e230a0f945db6c042ca2fec4ba59e8eec387f50a2dbf815c10a229a659ec44b63b3f866979d17c97ab4d34f0e0819ae8f627bc5a03216b7c1738513aa8905c8763ea82fbf15d4a5b2e543a30a913880f81702e8531d9f9e3ba5a2aba1cc21a1c1201f9d6d3984d6be271c24567791a708e638ec311843eaa32c93b89971d0d0b948d414899597b7808d7360cd5cb10eac20be53dbfa3ccf25984240e3accb26ef25a55a3d4e40f3f83b978032d24a33a07c78b3973eb5ef8f7dd4c63c8c2fcad8cead4589323cdd8ad1a4ad21b9d087f2154d954c959ecf3377c2e3665e9adda15310fc12d38cc20ab519f7ae365737c9b6ff33512a8f82efc44c9b171fa73f85b7fb8241ad0a4d545ddd6c606cc18e70006e4e0d534815d66d7911161ba83add966935550e28e1d90841a64e494143b21c5ce32e664a6487df95e2aa585fc49fb8cbf32bc2be86957dab37855bd4c99c45799a1d39835a02f47f4bacb97aaaf3b6f19a82816636a2f80bea9e4942e18087dbe52b1ec92a8663ac03fecb21747e7b0d2da2cd2e9bb80cb5840c0c27960a577b4cda60e26dd948b112d9ae621d81e2a56f2ee0aae1a270146179a6593246baad2a5fe5e65d097c66e90eef4d04a0c2d4ca9f9863fcc54ac54d9065758b8609d958a5c65b7c49de60847170dce5e6edd6590943189853c27ea5d049dbb21ec523d46b5e0d14a8f528ad5c750ce35580c3a61b4a6e6ff68a6326b9677178fa67e84c60b15998e339492af73ad20005e7b0ae34c7e1146d7ecaf4874fa4ce97dc541b26aac593349b32020a8a6ba57cf188cf3607170fb101f86b4310221b0c20fa290d96e9c42111a286e64490c3dc45f2e8dbe1546fbede92aef6a51fa8480fa5b21785f59571792ad4ca1d7d6b3a5117b5d20c728eaf627ddaa20034a08e10b6538096375847427270c646ea1a797ba6880a0dd36e5d00490904022f9643f0b8fffb53ccc01546726428cfde3313d66d98468914c75230fbd51e112d38cc50a9c27e6da079282c651f9eacf760e907890bdd26f1838a9ff1915e20fd357008b0a166934f8bc4f83d8a71adb96ec23c6c25d95812a28d8924e8981bad767ae1e8e426b1af0dcb24f7306a4617d25598e3188525caef981c4dada077696cb01c2d3edaa301844ab3270a22addf3e2a8b59927e4faefe39db21ea7c8973ffe32ccfa1a245895c513d8f329c6a5d25126eb5e215f53554dfe601e5032be3b7725907d4c5b9c3f96995f5970c6a2e353535626c7e3d8a60b4f402efd6183a6cfee9139c5724dfe2bd5ea8e9c70c3f52b2e91a23bdea04675ff7651c06dcbc35215e797ab1df2a2ca2fd6f0c9aeed0700e01f5127578e807c3cc1d86d41f79f355b0124e0af71bef97f0b3bcbd17cb0078548b656eee0daa5cff37c2298827e7bd3b03050798b2517c069ca36c644bc8816d6c0875cab47d22fe0549d0bf73fbcfc44bd25020e502b0ddd32022b065a4b9f7daf4964d86fd3d8584856001e6c480cbb400da434149dc4b71f6b0d234497f840b36ff8c1da4638dad478c276773ce83ec65074ffc67ff750c22b449b4c40c040cb4966e9b7e56456f3cff643a34a5a135f976773473fdc3e159be69b6b87604e4b04385f9abe759f4b7a5859ce79531880c78018b2277113bec70c9fe48693c53402fd4c1f8cb8c4a371be0c46cb074312b2a2a1f601608bc56d09eb47c9010a0a0cb428865d521ff17b757f6c9cd25a9cfb45d0cd751bb4676869d73aced65105a1606d94c9a023731cf4c01e9dcfabb27a059678697ffd48937d81dad0bfa98590f977e248b39d17aa2d9fe2edabf51a2283f8583e17bdb0241992fd89a9007f0a923ba675320a45daf41b7f0b00490a107115362aa16f1570b9acefbf06645a41a3c868774c7249ae2b77375c94e93c4e9e2a29d8c43a60af2c1f9c3dfaa6a234810105db3795ede906dfc19b7e9eda26225e4eed4ce43e4a4e9d41a71556f507e78b55c018e6a6f6997e3051bda142d4b84c530181b93920c8e258c4991ecbcc8a06eea8408184b88def4fa906f59d2eb096532ea2ebac20f4cb79efae1814086516486f35de3459bdeeb8d9f12d5e95a08141638128c063bf2b52d044773427ef5dee7360270b4a507bc36d37c8881f5a86a99ef3abf51594c9bf079f391b59464c96fc2319644a5be30a1c009ef9c205b86ac903c21c11092daba944714d2d997e18b2e3502a7ab56661501c4c7cd210678f42b6ede3005168d7b55e3c5e70f8770243f9e1be5d489b090f30d9ec24a31eba25be0524fbe5b710306395bb930f7bd3ed4ffe0e2091be6495f4a40ed063bb2bd5ee21193836273069e9a82744d37c9d31b8d10d8b9eb4a87d6c8fa47b579519c85ff500f592df69984d18d40ecf2f3f593aae4493f84c69a6f3b6f09b4dd9bd81d3ea079234cf6ee08281cb5de7c6f1894c5d35757d598535a83aacf5352f447abbb3ed109c1d3601122ef7f6645bee1426f1039aebefb95737363a73999fde4655c093872dbfded9053c4752d364edc0a1be8668eb8278004bc7fada6e1f286512219da67f59ed3ee4562b4c3ebeed8c9522d32db4374feeb79dd4f9256ab82104d13267d0d5d05ad4de6c21ee569f310158116189063624cf8f687d8da80d2ecfcfb5390b568c8c941bd30b7d7d2ff1899dd792138601cda5046576ce52b82a112745d6948cde41b28493571dfa3b80b0ee792468bb009e9c9ea63c6397242dce0cc71666c25deb59e1b4f54ce16c610670b4343bdc5acf74c36b32b5a06f3e6cee020e8493935cadda985761b567cd3b394ea7a1a9c19cc3aa64fcf817e4a96a7e7fb7c81f6860d66213cf187bbc0dcacb429deaafdda5757a41d02918a122103cdaf0aa6558747fa4c32569e9ae490913828e10be0e6956c7e1317ce5e46823f9df6f275cd7eddde90f2ecc1ad638928e4f89d38beb5d7d661d44c3c099e22d5af1cacb7ceb3584d095a579600af5e8dcbe0aa166024377debdf49cc82cf03e2b786fea659f3e58c55917ee649b530caa93a3dc78abca9d0addce4d17bf5e74f34719e58249863107751ab8e964acc9749b568e811bc71c35d67faa6e904397d57165c09491ffb2bc69f7394c9923e83cd39337bdf7982a9ddb7adc1603da7e9b98b91a98d0f960386fe0dee54f01f3e8ff3af17690af8728ecbf7ad2baff247d9c9cedb8e07e04c7663cebdfe8f47d200e119d7a1b43d2e482ea8d9425f27fa32746a3a5cdf86fdb034d182e5ed8d6de535a8a16f27f361ffc852287fc1f8ea312737d35a0dac8ffbdd4b044ca5b84f3299c900854576290dbc266c830baa9966ae14b4f81260163ca8e9d0020bae9b32ee93aa93fbbb7a628e23782bc002c1846b87ccee32199a208c6bd33bc004c92a42a56640fafb6ddd2f34567dc5d606f117c5a6895aaa5708cc2909a761bf988c6e033be00accc897f2f0000c5a326d0771ec4008c649b4f25501305f7e2e6312c142ee4b47615bbd649479bc2be7d8572f091acf1358309109d61e47651738a4ff4d5d619cf0e6ae30989833ef2377ef907159caf89663eff8703c112fe00f10e93fcfe834addce21061a032aee988c762ecf6cc2a1014b9bdda2f7739425e0bc25a0818b2860b16ea7377aef064ad52e85f7ea3398ab808374d3e4efd9028028085ab7be4002b38a09266691bd868718505c36c11e13478ddc35c7b5691b09300c92ba25368fbe11030a2bb832adc916840d5069d92514c3cf50ca1b6083100c88e92b7cbf84051a7877033a754c4391a4d0316dc0f41308a79af4606db8bfa7aa8b00ee274b2b149cae04722bb7e0c701a088bff2d44399191901c69a394b2988f32b7e0e20fd1d0fa8c3772ea557c39c9e8fb553327e8bda430fa2aa3be8334156dbdddf01ad0ea21eb0b9a107e84070f1be01080394058be145811efe5a5d1502fd37c9082b369c40dccae3244282c08241d092872f204ae3aaf7b21fec48530f7b981f29875cf7d4a0ad91ed091ccd8266c4e45b93acfc37d34e81965d356ed50aa22bc7de55b26f49642248bdb744605deb38162d30fc2a0b24502df0c95d031fb3455f60964987088fc992d80bcc52f55c66421d3603ff9eb1555ee0e9ca4117ebff8adea3c2959c1ced5f0cb049613168fedb8aeead139b57284a1e5a62b56fa88ccdc54ca34fe7675d0bafb6831fac4f96869a717b688fe82f71f4117b846d1fd80eb692cff378cd599ddd4a3445fb898502a00abca687187461f2d34d8fc08ab5449785e395947e2ea5d6a817860afba6cfbf0723d100163adac3282cbbddde96d95cfeff4e85da9b4b6965bccdc1ce203c10f64a5fe6dc33a09439cbc5eafaf7d104c26d36912fbe55a7eb02aa0f260e9dd0f763d44753836033309bff29aab095be0fc70d944f494f170621abf4184f908d78fd945d44df28171fe43d50bbfc9cbfe7a7b45478e3d8c0712c9e6f8e509a5610cda471e6dbc1ebda75aa44ba0821262b16f9fe71f2a616940556b7d116ec7c0bbcb560be6c24add594b559094e0234e9302a6cfe8e38954615a3195ea7cdea8b36929506b793e50df0d3b98a89a6d823d0ea8123815e8f297250d33ac85ce0df2e55f01c338c547450abd85542382cec31a165241a29098ddae78129196488981e1b5e9995df3699f264e3470feab7656598ca52ae1ea71c6cd1c3f60491026bc4dc2bab9bd929fa5bfd00c1c893f6b7b4f04bcdcd808a619776144c89e308fd88681cfb309bd2dd7203da1ddeb6c08abdc051ad34e3adfc86539bee35d5a423ae301396736da34189d96982adbad2b031ece7ce3939af8dc4de689fd2aeb2e8a9ac6a2230034d6bacac920588eaad65a4ec5eb0e39891bec00a59b7cb957a0a49cfd0e2cb21fa43bf2ed23174592a41d33667ef5ddef028e86f899cbcd5b78873cfa6cf27c1a918cde36ff40d0ddf646c9f7d339e1a0ac0040ec096e5a64adad5b347fe40038bfcdf95d9a99d003dd8eb5185e0d9a7b31dd4decc53af4d2856649e62ea74e2f2ef373aee9d7cc15889473d724d809b2bbd45f7ae7a179ff682f29aafee556925d1c3080bb65f3db3388195d04958d674fef3b5528284231c95991735aa5d1b64f9300c8980c749d1cab0dee9f89b621299be67cc407da1e058d21e7df217ac41e2a559de995930b10342bcac95d02c7c2f981011cf505592aa2f9f4bcd1cb3028d1b2a82a79215daef167014ca99949638b3ca5903dc073ea50f486c695a35b43b98b6600a7cea88aa6ead88248013b7a48320c1bb1fbaadf40177dbfa50d2f32ac8f04bb85c2942e50f9a6a0e43d208616f32c26b98bed6a34ed8da24071facf8e199ca173602a6c233a40268bf9c9b4db9ac16d0580789f42cbd13331945f54bfb092aa2279e4f032e92cfabd04805a670ba89eb29349e015f36d34d3c9aff267f90f45db44489d070f39fc787de66dda5e6c32f371451af3c6a68417f6de7a4ff622ab3367dc64acf039266559628ea6a73935560b439d2750418678b18588647ec1c7a3cc4307c32f55ebf73480f7b5c43fb56d8c49871374d85a4f38c0fa2df8234b5056efb854814551030ed6a3a0c581bee38d40832fccd155c40f303b5048c0ca7d3e988fb81d36345d58afe29c46e29194d64174fa891b4bb45f88b4f249bce8df5e3f3b0b70b13abdff33aa42b5203fbcd31b4fd40c2ba096db2770a1216e814f9125e91b7f27c3c1b2fc7f3bc27de05dfcdcae5bd40eff33c98e7b52cf87a78e0e7b140cfc987e3adbe0bbcd5eafbc00ff49c7c9f17e4bbf16cbc17f8799e07f3887c37ae9507be40cff38aa8f703c90bca6a0396b7f26c3cf087676303c3f922e402af08e87d5feb73bd1756308463c5f33eeff33c2fc85d9ef30fc8786abca8e1d44b0f6778e185870c1174a96328b1458c303c8a47030ed5c3041eaa4ac09f7aa04287271e9c454b0f61d43855a229587ab8a00456ea9021273dfc107252751c5124868e91229e1baf89181e0a9accd6ecd084a6c0c7991c1704cd88f880c2100203d121241bf000b40606339a8301f5b1e33cf000e333560c4ec132bec17d5386b72c77ca8c6a7b8bbb23f98b9b1e87a1741eebaea5ac65e626832daa5c362e1b50c7ab89cb66452427ca84a69e974febe66504e6ecf0c09c960cd4ad74562298a6cc0d552d1d9fcb0604d22162a444d50d979c1204d261c2472484b505f45a50185015054b9258e6036f402c3f27a802c10948bc30c163c1440ca698f081415641398f470a6b832faa5a2d0c9ccc54b0e30885551513005005c4a64b4e8f9c1e2250a920f7d2376e5441d936556632842e9b950e4b0a54a105135ea08e16116fc71213ccf0c106c905ccbcde18808e0f873545743901affc17264ee0c1f2b9a172a3410b041089cbc90952f450e504235eb0201c32a3436259e18a2a9cb38643191d253b3c2e43564f5c409a9870e372d2ba79e958152d555911b9a1e246d58d6b4907ea65458812951b1b2b3950251429aa7c80404d6e5a301e3aad1b1ca59e1084a8ba7959b980c642156b15b2bc9696d60deb490b8994239cd3e6c8cb4aebc6054427c9c608d864e3a3c7100d825880e4b5c195270d4cb8798dae253a4b8a969c5c39a02b080bb66a6293031ea1535c36ae1b50887505349ae2b184ac7858160cd9bcc89513a5273f5e3716d8149980d48443f4f2018d80465a465421d10126b136605d6063041cb223c98ac80955a390b7aaad82c0156b849e10046005be4b28e1051a269630028815c89040a5a56a4fce4200e1030fb86c8172c2d2811598d1121f0d322842412c1cb0660d1198c181940c7a820071f110012e8610428b1207302215e4e0b4aea0220149103104103ff4c0830e5b7c8e088d38dc1005b5660e13d03e58430d05c8b028c30bb626e5c98d8b88186028b2c9409a2288803969e2a307cecdcb35440802a000e1cb2c4d1d2c5151da21002a0060270c26552ca1841164ba70296af2a30738012f4ac0454a942636ae56132924c0091f6a10000b231460871d5ec8d224e5c9911c4f8e551cdf1ba01aaac6770668c627062b0c4f031f06be0bd87c9163816f029f043e2abe26564b7c526018cf8887c18b086b880e90cfc75781d743e4c1daf9442f04757c38ac9bcfe6f5fa5c2ed6077a2bcf6bb9ce8e9590951555a00b5520961612eab9da302114a28ac5ca7169a00113c01e92803e728038a2b5421588c5c6829b213fca54d978013f585d80b35b392f2a2fa3d6cdeac98f1bd61655af28aa7a8060824dd14a073672c0a862ada962e504b94055ceeeb1a2cae6878ecff58425c49ae14c053840067002d842d50e9e1d3cab2051e8f5435c73026b8397910bca6ac70aca0b88aa155312a1d64d0b89aa154e2b0808849364e5813920d00d15d6062e2fad20ad9b56989c1e393d6c7ed858e0f2b242c109ad202b264e60655195411810476519100161393b5a22e8ad825648a0538e8fd6e7eae212e26a6255a0c36be1ac5860ce6ab5fa56aed56b657333045cb9b6b8767638dd88ae96cb5bb940225008f45e4056424cb0c1807544950e244c105ba09057d209201827f4c8a8af498119194833012f2090c569838a0106a5230ea214e1bb861a4247c21d2fd7970fb234ade9c20214c0e085211b0630e50630302081072800014b0c21346a50c3b95fd0b90d12a8e1c0171670c001c8941002081f7890a5a90a150c2ef061011553905942891248901119822f063ac0f080185cd59a149cc00d10bef8b040c4c003b0588002100045132f04400559085eba64695a9240175844a00a1bac6c4003127880031460851040b080421912a8c1c0170ab0820a27cc547db9a424eaa829830c3020e0802912f0441131b8f0a52c0410be7459527a02fb117524d0050474c02106335f86d0c11215a527301d3aa097418604bad0028b2912f0441345c8100033603a588af204564494e46747047d4d196478400b0860e1802912d0441132c4100017cc8c210303020742e31c7260b9b243470786c0d0020b1e7419f2c10516765449d8ac59a5e043014b8e2f8ed711a0115f11df109e922fc96ac81b7182561baca805187c433c21ae202b20de0fd08767810d8f9d9d5508eec8d111ea04c9018233e446b471bd5aaeafd562b1569e0771bf09739870779dc73769755f9b73425f62d2de9388bbe7ca31e32d0f73b884394d618ecf0ff5a1f44babafa5dad26de9935daeb9de485a2dc57103878d10870cf719b55dfb371dfda151a0509f2850e8b5b6a7e13c11e214e13ea3354cb5a57b4703338ee397252bad2043eeac273dac23ee8e818738465ce7f1e6dfbbe684f20641100457abefdbc0431b316cb8b876eb2f29ddf852fdb8a43f3e4fe613ebd046091b1bbcbcbf2bd5a1873654e5fdd0868a0d1135690d539a712dd7ade9db6ecd371b50e731df48dce3849eb4a6b639c1dd2f70b719e185e6b5596b7fe61f77f771f7235e194e92502934e79344e2c489919322274d9c307142e4648913254e92381972e2c4c8c8a8c8a88911132322a325464a8c92180d193929322a2a2a6a52c4a488a868499192a2244543454e9a1835296ad2a4099326444d963451d2244993a1264e9818312962d284091326444c963051c224099321264e888c888a889a10312122225a42a4842809d110919325464b8a963459c26409d192254b942c49b26468891325464a8a943451c244099192254a942849a2644889932446498a923449c2240951922549942449926428899321a3a1a2a126434c868886960c29194a323434441ba21a7a7717e2a10b059d47dc798536aef6199360dc5d03f0cb0d90cf8d0f0994043a02a54037403723504a2975f70cdcdd88bb17717790c7354461ae9c1908c29606b27077201eb69698cd28ec066836a3b05d7b93d29bd9cfbed96ca674d7def4995158f9a6ad355da34489c2a3ff46ee8aebe1095b3b2a0a74e408dd95962f45da5b732d2777582bc8dda9872d1da7daee9346a9b67fdbfb64d2e19cf151c83ac37d1cbb70779787ac07b072f01f0a050a4de243ff34eb149dfdc54d335ae9c574f6e393ab59774f18ec62bb735966b47cd3ac240cee3e86e01cac9355bafff8e4b735bbef974b9bfc30b42c0270f72e1e8273b8cd82977bf9b88c6b66c5d1a4a52c45ee2ee4ee50dcdd030fc1289d474de6ea641c0a2a2d0edafa62d2dd7b90b9923966b3471847913966a424c9eb7ae4ee3cdccd7a6eb296bfc9cf51296d6bb349b76dbb8f45933976b39df82a6ddcb47192ed5ad3d61cb5db399cd2cdb2317eda5f65dfdfdaea8bc9bbe4ee629871ce4a9ff1862a7fe7ce9c3192fea9fa584a6bd6a9bb9431951ac6d9e97e95ecee501eae80e83cd2acb933a65577dfe1ee3a3cc7dd71dcfdc693c1ddb778f899e177fb16e0ee14a6cb7b621aad61ba4f4cf196e93c0e8d49c62441faf78e26b4448911f94e68a513274a9a90e42709c26172a49db76d6dbbbcb5b57d16c2b65cc951a84a89a02d4ee871f82644a340a1fe7a1955e0e1477d5b33a7e96cd7d98e879e09dccd1a840f0fbd27dcbd87871e0f1e25bfbed6dd75dc5963bcadf358629c10f953a1e7c191bb5be0a107e4eef74d0a853e8d96e6896f9afe506d7d28d597d7d29fc2b8cfb25cd56977b771ff4a77ffe1a107ea3c6e5cade5ee9e679cf3d35b69f9379ffb33bdd8eebaefe3decce12cddb6e64d6dd7ee5d7119d768fd5aee3ec443a77277d1439fc1dd75d8ace59fc274b72d597395d479247f4aa6ab5988cc5a88c4a6598576b9669a35dd9db502ddfde3c0cc17073840151b1f6890f8410004c63875c08aa89a0488d0c831957c784060e4050fdd6b8cc3061134992a006163d4f35380245ef000b2c2b8cae924231c843983c20faf0a00a98a02c7d15455c57c05c48c0d38384467bcfc4b013473c6a3291085600d2b72de3e622c1ac63525c5a25e071e134355d5b3955a3d521db4284b0a4b6b4bb5a817fb5c3ddb8bd9ec10426506779c1d3478d4eb99c1a3cef3ad78563c5b8a45bd954dec6bf56c2f06ae89b1c01e2a7c7bb1af870a6f35021c213492080defcc19907ed4e3317306ec49420c07522b2ac6d174c159519c55cf151c5f8959992f332798e07df403bd038020084a7dd4061c33f55156cf5e51d62ae6d1970be49162f5881913b3e9916a51d69415eba32fa957cf981808ae562b70058220b85a510ff43ceaac2270dcf3bc559817b5b1a1ceca1ba4ada98fb6280be411f32326f5f578cce31163c58bbdc4ac3cda23664cccd52305d28fb65a3d62563129a71ea3ae7365471538424000234408e9f5c011a265b5e3078f7a3d3e780a30b3a26652203a62d13154ad332dea3a5bfc8845cb90a9aa02573cad9e174f1488b3c307779ccfd3f1c0a945573cad568be2b8ce961665f5ac7870583a4e2e123ebaead2a2df9702092dea1d7d2e176db97a52c869b5563c600a24b0a87704521c9c1565f18c2173c43a5af5a4907304d2291f4bc989453d58d1564f0aded18e35472cb0a70aef49c13bfa7aaaf04a687d200e38ab2216d107429042c1a1be2ad2e1f9705639383cded1475f3d551ff5e88bc78f7272a88deb06a4371ffdcc8034877a3c24d850efb3d1a17e94c343a6aaaa8a27050e8e56f486c7cc19ef684553e0e008a4ae23440b487568d4ea0887474787dee0d033ded10df5e88bc74c0ec5f17848b8a19e8d4749c0a15f4ec8a323848a8bb6a45c3d43ec3edaea1962b7a21cbccce0d033d48c0e4d811edd504fa7a76a453deaf59859d19b9e2a3329d0231dbad2f940878784125614a7a76a4575b4e844ade877c363c373c63bb2a1383c6652e0e00887e27c3c3a2b1d25215a40aa433f58d11cfae23163d385cc914e0f0994841b4a020eebc686278524c470106b51ef88888d508caf89b57aa486d87d74889d102d20fd68d48a7af4e5b4a274f5b5a4c0214a23fca84789ca086d70848000867aaf55d19831257c74454bf02848c72411e5a22d3ac6cc197a14654359b48417bda13e26057ac4ea1953428bdef49ca1472b6ad353c2ab87455d3d25b43ccaea29d3f26e06aa8858c3c31a1b5060460df0f33ed002de1c4f78ab700e1318f17e2001fe00636172629f8ff7792b2f470e325e46af550370d05cc941220e35719cc1c6cf8e385a8863863872880388389088e309c7f158211c73b88ee741ab1d8671a461f580ab1c560e1c5a787050c08343031e1c6378dee781ac9507470e7000010712703c010715703000fc5a28a0f15eafd70f9082a0d11b676220f8860dde8a7e3a5756d4a3204501e7fbc21ef405d27ae0f84794055116a0e7795e16edad413dd08d39dc308197e6042dc4da30b3a24f629ea6046956ab96c33cf6a238613eeaf2c0d8176bf57c445d84ad9694778515868c7f06d820063aac0d11ecf8d183c562adbc1ed08d1871d88f188b4533f0271e63d1230ef3d80cfee36de4f06a0388b00d247eb4f1046d638a232c26aaaaaa8e38720aea286d10c3017df5e908332616c67b85f9562c23282cb0e5020816b96898bd22dac24c122b6a26057ad4ea49e33908e6803daf2f8ad6ebd57ab53c25cf080a96b0c7aac56ab58c5a2ed7b76ad196165e822c3e1c56192008b672a07c3facb0022c232deab2f96c40b0f5d2c971b9e84b47e7f4540163383d2c0f02691530e6eab90163208e0dbde159c16c28ebd371b128c8638487d2d0c5fa7c86582c106c5110a42d90b66e22f0b2f93e9beffba80d0fd0f7b32a8aad98b810ebfbc0d647592da0cf670512f9a409d18055d8785ecf8fefb55af5f8b4284be7f54d59794e73404f07a43d3c90b27ae440f3796872c0f93e96fbc4b4cbf5ad58ac6fc5ea01f2201608aebef0c2c603592005592c10044116c862816a8a7c7c7c5e2478c3f5234c8f21b1154841230e6319b91c06d2211f209f17359263c40b968d0dcb8605d2cf676504054b0fb0e56ab568185713578b7a8cc562fde09902335005c3ca87e903af7c14a42c5087e5e2a161fcf359c57af8be8f8705027d31acbcd56a459b4cf97c5661c80611abd54b07908d19b13044b195b7c3615e0f140f5a515087c3be1ea75032107ab18fbedef83e8ac3e209c3a11378432c4a8d7846565febd3f97ac2901a617d4394d230fcc162b1a6f8a0184531f2fdb0582c168b0d13c4582c8ae5e5c35a7d3e465f8fd1e7c3a25f0fb562c4a250b084445f93152513db1ff5629f7b01c6c7a24ebf8f05ae562003beeff3becffb28a53f28b85a7dabefbb22c4f1becf410aead06a79200be4a2f5b558dff7b1be6fd5fabeaff57d9f1cacef6bb1582c568b25c4cae359ad56463cf40182e2b55a2d6aa4b55ab5807c82a8cfe7e3d35ac901ba3e10044110044190822008822005bfeffb7ad0ac84f0224aa91b3c0c7d4386b0f156dfe7f91a3455aed60742f1a0d52a8afb185d11be62af1e9607b97a7a8c7c3ee12aa4d4c8ea034d80b302f220162bc87d5854c7a31e0f088a7a7c3f2cca3a0ac3f8c45814e88302bebcd52a67f5d1950b9ee785a18a85d92b4fe7079c149c112312f33428382386f3e540f97e563a4a203562a4275c03430812690152d0e7c7eaeb6179d0e7638445874e80650804b1d547575f48519066e5a373586fe01039200c12c359812b6781617c629ed7628134e61ae2412bfa83e54d88b8cf7745e831d0d56a40f8d12ff662391112e147411e9607b128cecb49c00409a220011a17f298072420c27f5ce90b12033d88f5d9bc1810b25e37397ef4830436c45674489832622b8a839383b3001d1c34e1ab47478e8e520ece0e1d0a087758118a6115e18e4845c863678a1e3c1250410f292ca8208ad0870508087ff870203f5e41807c3150489055ec3544089121de054430b8c0631fc580877a3d3c457a3e234532300245e8c5400d32008f68f0f239f2c5c01f9f55ec85e4c7db0089c73ea00dbc20a06f0c3a40e8c5c098d0f8443824648030c9902b49e22d51f2112d71225c31216aa20993a22646454e8c604e9ec0a03c8902454a140ea4f8140e9a884d398a211d252129255151aa42c54a95252b1d2c79573ac07285094b13539626a72c9f16a7272d504f51505ba2b86c61225c75e1e241172f1e7ce0e5cb07207c01034208606421cc647546ab24ad24bfacbdad76b3b9bd85b152619630a570e6c5e19bc36272598cce5b9f7bead44d51e9765422ecbc114418330209634a20814c0955644ca83ac18432279829838299145000400a6700f0a9706605155858a105165c6821002ebc10802560782106186488410032cc20001a66a8810625c2d5006af06c1800016cf86e20000e37ac72c041871c7c071d0ab0c3c743017ae0c1871e7ef0c181f82108203c2182580d210411437811441851847784112b248ef0d89704125e6ca544125fcc975022897015f3623945433e6f091d252442228e083feac57c3e9f1f5f8f9107093124844e15108cb55820cb83be1e222342cf0b42e7eb212a22043d26dcf38cf08ef090f092f09af098f868921a38fe799fe7b570c6f3bcef5b83873572f82e80e31f0e0f26741a6081173b40b0461b62190b66f011f3346a8c50b3001e70887e48a3c61b3e9e6d0905c230ce819847b178e1feada07c3e1e1aa02120cf89ce84de43c641f0cbf11ea0c762008ee779dff7b97b40e0e0b80750f0267f8ad69f9bae5660b028baa3d1f2f77dfd39f7d98792368737adf4feebf6d3a8edda5cae32d1716b6bd299942828331cce358afbdba6da34edd6f42f7e1cd6f47525e9b574eb334c0d07d9e8c6970a1fd1719731ee7141d7d2b7e56a562ed8897e356bd7d2d75697ffb63149f712cdf46934ffd45ffa19d73eff6d97b7d27b526d29eeb78529456569a263f925d574a4589b95026dfd369b44c7dcecf7493ed5cfe88e46f743d1b17cad9fc439476dd73e8dfe6c4bf1d64774aee27c68ddb5a49a8efaf1a63a579980f2dff6dbf0a6fbcc99f547ffded1e8b52399716de3283a262de56a9ea4ddb9e668bd96ee7b6292acd434cf1afd5b147d5c6d223f07f5ba7e26ef128ed2e57f1395128e7a1c46dae6596b9f7471d3be2746aafd13ceffc1c54da6a5629a158965dd1d8b874dd870f7809cb9e97a7b7d52bc29fd1237a13005d3665398a64c99e264cad0142153804cf9e1394c7f32f0a1302a447f32a01428030acb610a9401f5a1140644c17cf912931c8c117290c58f4f95c1a8b62deac98baf0c06d3175b2dfb8c492d2b7feb33ef7bca7027ce5e8b334ffd7947deb7e5c7bd6cbfde16473bb5145969a6596b325b5050d0eb7acbc9ec364f9c4c26cb057d142ca25c41f1a67f96b4527a4f4a0d308002a8a1608a7c09a354b9e3bb7b29ba7b29d9a6fa1b46c1e2ee3f40bb97f259dd70d3d5ca0d06236dfefbb6fc657d2bdaeabf5f85fc1cd49317375d6c757e2e545152e4df90b4bb7ff1d06805771f8f9268ad96a5fc8cb73e738e7eaef4a7bcd527ffbd16484bd5cf694c5e4c526d6f9bfe131393fe9bae56e8f8e3f3b72d82d574dfcff944a2e3c6f8893e3131d1ad7f572cb4525d6f95eef24f298d74eba7d1bfedfcdaca68f9a59dbab6dec86be9d37de4e6bf45651c6eb4a3c6fdb61953fd53f5d26ba9ae375ac3389bb8aaadac56dfa465593fef5e4af6b65b6f24cee69c2cd737495892bbfff01006e44e6174574af1a6bb975aade1c4012ec3597cca82aeadfb9e1817f4a6547e5cc6e1642b94c18510c5c3221e34ce2f02b55dbb332fa6657d93e24df5b634baef5bd2d66aa7a6e5ef9abd69af8bbb971e36190a8baa0c2d094109bdf5b6a9d66fee6ea5fa71b9ea13dba9cf4ebb3e6de7fa379d475a509322dc71145f8a420e6348795cbd34474f283a8fe49231c9b82488fc1293413f65779db2f94b99aea65965baca6ee4ae34d99319d77010ce864cc6b853d89798a46fd2ea8dac9fef49cbcfbfef5398596ffe7dad69da9a7e5bcdc972f56fb2fd5a4b917f2365fbc45f6272bfa6b2b5cf3999695aa97d7fea65b76d7532a9ec01c0ddab42a14c0aee1e0965aaaacca040c68cbb37a68cbb7782bb47668c092578644a70f748f0c6d8b48a0cb183851934eeee1d8800cb147fd99866452a2de692df342bd2b5369bb3ecfb534a2f55b7be5bb6ae26762a5fead6276d62ec84c34d17db8d999ebcd59a666c6b6bfeb6b72eb66b37e662a72e4692ca15f74fb5bc9fbff64fa5696f4836cbf43647fdae1b6a6b8b25ffc6a7187bb76cd356d9e5df1b63c93826ad73364abfddf7c44736abdf5671f6b3537ef32cff4642d9b8fcd36ebf2531d46ee7ac49e5a74c8ca4f55f1c2e6f7d322b6933deda5ad14fdeafd5b2ecf24f6152dba4ddb67fad79b3e096707942e94dd6bc2f46dae553d7cf513557de9aab3c0df74cfa7f6f8bc361a41bf951a625ef89917664cd24c67d92dde6c94415b55febd8be8fdb55a728ad9fb4e55dcad99bcef8b6cf580e57b7a6d5a3ddb6a6dd5ba05aee1e0b74f756eedee7ee9ebbb7668e14b83b0ae658b366cd9a356bd6ac51e3811d41d07490f3317a18a2e6cb1743fec5131e8a5c98809167c1030748d8a20a0f9c8a2b2e807326e70c6f22072a649600c37d882132f8788115ba0e4faab0010d32ee0250535ac00010b0e22c2469428a32f3e0279c004a04d504463e8213aa31608059c0b59a2b72222079c271162c6dc1ba800baffd60860d178dfa4c86a812c86069c13fe04009ba0000f9866f21a10d1548f0a83be5a4e87abcb8c2af2ce14060e072e15598406181230120e1484e85788c0039073750a0044172e44f9caa8457c4c086177571802a3868428d139da06404272035e0496c38a091a901f27184391e20440a667c032a5e90522543e93f1a4041e29e1eae0102e698a3042430c28d5c1eab2f4178c07ba86c81238384294ea704e141024202fc020fc439c2a8220a1fd2c2075cfe00250fc281344e24b1011138102a1ec482a478c07d3c008c27b528a15b30fb3e1d01c2f00a74c24c816abe701e5d5c4089129070f8ce0f3e2230fb32858b2a8469962815e021990330a143180df88e146e74745140e83a70a0330001c305ae630510358cbcc0798e1a45e028536081aa1c243b6f728348154e0e618e38be38c36f1ca0c119392d3e54ddf400f5883aaa729b33150880074b44f8eb0d165a0841115ff82ba705193d607155b94810a2f000244bd0b8a83ee019c00808d0b85c5ef870ca41cd94960e26151376e4d0b49c3cd933709929ac0d14e172058f31ce3a018513725ab8d0b0848c24d1c1ce195560184dc810430b4f54813588a1c38e1826d0805a2e0f4e4a48e26068010d60304064ca8a0238802047192e61ac6c6040991fb43853b5a26db9917a5a4357421828e9e18703f44b811a2c3ab88d876f0237a87066800c0f9f106c48e0cc000c345f095ec430c48c32fcf3a2230401d040c1ec6392c20f09b373f6f5f8f184c36ecd14cf04407851c314aaf23090e3816c2799e24d41b48117b10d26bc1ebc0f5c5314a8f2ce7c30479014270c79627e7cd08d71a5cafbd265ca122c47428f8a042cf000a80683b784840278f174c6cce3712ae10c1a95175ed8442a031c68000f9e636004cc8d4995ab910304264dc8aa7c0c2a940082891639ff620b0c0930a2829167214512d59305d4a9d800174e78651879134d883184a7a5ca87180241075128e0e43a3479000910350b9fe1234de004151f67e18887324cdcc08497711d893064051e7c042b535ea43ce1e33a056084b89d271c572400739041430e5e63c283924106239fedc0438d15de08fd83333c2a2a6aa2f02d67beac41abf0c29d44708df0050b16f02b5130a9c1880788bc8a94595e8304261ce9450501243471c63910cdc05de08a0e7fd2a584264c200081177d41c68925e220c389ced470e4869b1c3c096e07270b8ce1858f2600f1443411c2375880181d3cd57082ff6c808010c4f18305ae01154e5c51039a13dc4836c1cc65c108efe9c00d0fa080c790d3a4d66e882e0bbfe00b03dc2813a5880f81010423527c013d481844620fe8d1c0812c4981104769ba0f2d1ae04511111db78096002f661b08f10a76c4c143842058701e13e08085a33796f84e1020284981dd719105256bbc1a21f0d0040490b141d7c37700600425eca0a1e33a7230233785180db88e021c608610379e3c670d288268c30584e76c500230644fe538ba015356d834f11b2ca6b2448004a5df14b111b1c11983dba83087084a700002fe82c30711e05c89c25f622680a68808d6b88b0d170604504214eeda6df1441ab013b8eb75844d50ad89b776805541f24410de82f58862c464c7591bc8c0136b5838c1596592fc80033480b384b0c0608ae6c241319676590c2b1c1c404e052b601839a8a50d204168910107431a84a4b921f45505766e5d2c5184af08a0040514e078e02b9a15b80700b1c65742277c51f2a307ffd630d930841948f8370136b42461060eff8650b23519f1c23f3217541942c60dfe79e172850f4e49f8c7448b36c41cac7f3dbce814c1f1807b2610634a097e28710f034a413ca00136f7a6085341014258e25e0f443d7007d8c1bd332b40a14651927b62b8ac50822139ee7d31c104213ca8dca3e20606d248d9b9b7e407d7132ad8dce3d14ca0f161cabdb08b0a42ca18c33d9782c50c104bdc49f00309317a38701f230d921305d871ff029b2957f0e29ec5049608634607ee54b08145911cda706fe20164f0e8c47d0825286e7c2b771d685ef8a2729fa1c72b19e1e1ce02540c60b2acdccbf85072021408e13e4297d3070514e1be73e4b0dd10c51d37f40517ae22dc6b54907152821ddc674558808b5c18f70f4a108ad0e4343194c6b9a66fe6ee4e1e1e4912439f949f4f9bad5d4c525ae94fd51bd594d58618fe73f34aa0e1f6e4062ddc2b4107a327554adcbc12361005ec880b508fcc4f1a2713f821c22303001b8ce038c0cd2333471646432870c6ab72c1a9484a4f149e09517e349931208d77828f344cc8a489c22bc34345c4030ad47866ac0d4bd07031f25090438d1230aca01e00cea049d6fcf0f25688319184522b9e0b47c218e240c9140f06285e42217441e3d1b0c3692c41270bef865a55900eb8303c1ed8b00244c5088c3c22e60863030ee8f0c273228d131239a060f0ac20c11b4882e03cf022a0059a1f27ba2a0f8c149ce1e3010f67786e6c21e288ce89303eb10a2b1ad470459a6fc9076832388006a2f840a02167c4026fdcbe17901051c4043aac7c1170322bc2834e5901f142448f122546ab290578c1a305ca6c8506541a9aa372063cc930c2e0052726583d51682e50e28334ac36c4f048a09aa2777fe562abf543d9f2ac17e3ecd32aedd66cb35d2b75f34fe12af96bfad61c7571d3eec6a4aed5f76fe4b6529fdb45eda87076fa421f23ee9ee3ee373e3d7c42164280c14f664139c2dd3d0f244001267e80f1450ceefe79c93d1df1458f352570f78f081f8a70e231451645eeee0dc101047a7a9082e2e6ee1e07327cc902830a3a50e1ee1e0356642ad0810544b8b9fb0780223d0af0810cb4d8c2dd3d032841731d2a84f0e1eeab3646108213483880002370f70f8b120765b070848513b8fbaa8217bc50630036504101776fdd40c31b9e05602998e2ee1e196dc430668e017ca0c8dd411c662e02dcb6080386fb97f8067d89afbbbb77640677271281f727376871b331ce3a8f34a12ff115ba96e6ee42a8c119552fafaaca84aa9b2af7aa2affaa7c55e52a54390b2cb8dfb88dbb9f715fa1ca6daaaa4ca8aa62a1cabdaa6a85aa133c03203cf0bcd048933baedda52f6bb6e5b66c5db1d86def9208d6acd75251da99b95c4cd79b53d4932ca71f57ef266b0e87916030f24b4c4e2959724a91e5cf51559c14894d5a36ebc65b4655715415475a7d6dde542f7b5d6952942c39a1e477a2aa387ca3703d61f23b69184c8ac4e69194f273399bb786e174d541c9e2218f936ff273c624ddf86a4a6b96526de9dfa2e893992c63d9acfc9bb6d7d668fed2b4e791cea3de16674ddc6fbd73f79e7c6bec846b776997f1aee5ad4c66cd9fcb62565cc64de5ae3ae9f7829b16e0ee57dc1decc06d75531b8662d179dcd7e2dc9271282809f9608ce813a3f38b14287a579dfe6b32bdab39adab89742377cd699ba47535c92efb66b3afabc2dd3ff010031a4c7bd2928c49462541b8766dd09b95d4d766b5beedeaa4f36856b382e0e105398ce37864bf09b4eb671bddb8898e9a9ab59a27695692deb6d551dbb5e53de9e74abf0bccfca9376555df9a374ce7f1a6ebeda6eb8dc81bee3be39ba618ef9bae370a7b9d7fea33752f7f2abffea7ff4c85d4160783e9fb5278c3605a9fbb6ea8276f35ebd1c556e7e702e5ee4b56623c5eb9b4df34ab130e43fdc54d51ee4ea99090d0ef9ab7a69d5a88bc3b1d94cb5517b4ffbea5e1b008414c868476dbe69a2bffa6ebed9e30d8ce38973bb5ec7135d7de5633de1a067b9cddf7cce1101e9cee4a7fd7ada9bb2be94479380448e7b1bcbfa9fe89bb87e0a1102afcb7fe2bcbbfad0c87734d467e4e0b913f25b4847c1a49d6222325342743b4a2225a7d2323263f4454abfdd00f91b526444e6aff4542e4d6671612c283bbc7dc5d5f6c37f9a692c6b926779fc241fed24ee1ec54774d72f72858e8f772f7206b0441836ac8c756a39a624a58b264397a52aaa214d5b4b47454a569e9290a8a4629452165c182654bd393d21295a5a826a5a4251a85a4d4c151162c31a5a724a5a8d89312d3962625232a515b94909a94b66851828a5282226d7edcd9e4c424b6f4381b8514b5e57136aa7c11ac59f3dfaff226cd6acbc5766dde504e4c47417ab8fb17da9465292a86b484c5c92906e40cf72f146989ca12929213545094074a514d59908044e08af23779e69fb2dbe26cce012102481021901cdcfdc78782f94295a2625c969e9c1e67a37ed7242af74dd97d53f6bfdfcce1307997cc4ada8b6f252f6e622a6d16772f2a2d2e6d16dbb5f9cb9c653f5496fc5359f297f59fbec44cb61af534dcd964ab51eedee4dd9dc9ef1dedeaf095ba5b6c35ca76edb515f7d9dd85ca2f31f965ddd5dd898c6a7b8bbb8fb6baff342b17775f72b1dd56e39cbb2b49e2eee5b5523f427717bab8c9ddc7a0c761285f4d0940081b511039c8c5b54309a50deec2401537ac983272af0cbe19c0121470971b3c2c8d300a59e36b44e0ac1a58285aa2c600f7a810d70ea008ff704f96e860e7e3ac1a560d58a20113677d6044d20898a8fb8f0b090b1b487197e2428b2d6a80e15e03c8c0e044cb8cbb801c81450f736471d0660133c81630e5ae14b280018438d6784b0b4e8d92c51a7757fd36151ef817022b63970a38714781115560a6a4c1a24dba18038a35ee728205c90394299c65860f330fcd8f7b44f83ca9e056e12b262d7878a1fac1bf29381d1c805be32dddc203a6ec74be9240129a55d593af9c9ca6907440046fe13083064e90c08b7f5128b102ce41c45d28d0204415961ddcb546083954913373ff2288395ef8a183b7841ce940070f4c38d0b559b3be0856ef13ef2a160ec29dedaea66ae62e9ccb4107a378eee2c12b59a2e7f21d1ecb024f87f7520216f96a06f002d7f77ab1c095e7b98f6ff4cf3d2d20079ebb9eb07c47cb73cff3582ca20fe773cff3569f15cf73799fe7ad5a227cdee7b93e0b9e3cafe5b9fcf374c6d5d76af9dc500ffc3e10060ff4bcefc66be3f36c9ce581dfea3d2fcce779df6bcb27e4f3be16f87921f83caff5795a3c1f9eb7fa5e0d60ad5c3a3c0b3caf82d7cad3c1fbbcd6e779df4ae67de08d0dd197e34979792c23ab289eebfbc024ab9beff37e7634f1460093e05059397d0efa178407ae7c40d6e7ad9c45833786e79f8761e89e7ba0f7796a78af0f89e77dacef5b79de124fc87b7939dff77d2d24efc8f7819f37b41a7180bc34f07476827c03f0581f100ff4569e7f4e1fbdf140ff6ccbe9cc7361e53c0df07c0a16dfca6b79a0cbd3f156367cdf0bb6f28c56def781f4cbb1f1f1589ee71a02936030e47d37add7e7f23c98f77d1b58b53c9b0f5c4d793ddfe979ab20cf06e7f35e2eef9bf25df05df01159f9cabd1d5eebfb3c1d305c811f90effbbc9607d63c1b231e0eebf3589e8e2221df920bc7e6f368af1cd7e769f15e9eb7fa3cd0b3e211f99c7c433e9ccff5b1bc95f7791fcee702bd156be50543421f3a3a3d425146868bb212b8285be3e26c878bb31f2ece885c9c39b9389bb938d3b9380b808bb302b83863c2c5191a1767127071c60117676a5c9ca5c0c56a818bb5888b55898b758a8bf58a8bb58b8b9574b16a176b0a2ed6195cac44b8589970b1a2213244489070274890ef0499e23b41b804a9b9fbcb46c78d8e17d0452f392e7af9e1a297222e7a0172d10bcc452f4a2e7a6972d18b072e7aa9b9e885ca452f29b8e8e50517bd10c0452f45b8e8050acfd90152e53b4074be03a405df013283ef00e9c1778024e1ee3a383b3b7038e3210e34788803101ee280000f7170000e5e7888431a0f7108818739b88739ec789843110f7320f230072477b7e0a54347c7cd6b473cc377c414f8ce8e10dfd929e23b3b3fbeb353e43b3b567c67678befecd07c67e7face4e09beb3d382efece0e03b3b41f8cece12beb32385efec2cc077762010eee84143141ed2e0000f69c88087348ce1210d6b7848031c1ed6c0f2b0061e1ed650c4c31a843cace1098e0d8f9d1c23dfc9a9e23b394fbe93637d2727fb4e8e19dfc91980efe420b1c3fdc78d0e1e54f4e10a226a4972510b948b5a4c17b59871518b005cd4a2838b5a9a70518b025cd4420117b594e1a296375c7ceae1e213918b4f4a2e3e7171f1e9baf884828b4f33b8f834848b4f50b8f874858b4f5db8f8f4858b4f1e70f129042e42b180fca88085325c64210e175bd871b105212eb630e4ee366278d3bac9e942878b5dfc70b18b222e7641e46217555ceca28b8b5d8071b10b9b8b5d8871b18b115cece28c8b5d04c0c52e04e062173ab8d8c5102e76f1848b5d38c0dd71c257053b3b3a628e4e8886150fd180f2100dd24334b68768a0e0211a347888c60e1ea2818487683ce1211a5a788806181ea231020fd158e3611a403c4c63030fd328f2308d240fd370f2308d99876988f1300d321ea6c1828769d8e0611a41789846123737e08e1cf10411b87842095c3c01052e9671b95886878b658ab85826c8c532442e963172b10c928b659e5c2c535d2c835d2ca373b14c1917cbb4e0621902b858c687968f17a8f0f0050778f8c2161ebe90c6c317d670771d3d6e76585e7c8765f31d96f61d16007c872583efb076f01d16112f177a78e802110f5df0f1d085200f5d60e212b7b8e2e216d5c52d762e6e11838b5bd8e0e21641b8b845133a3737ad1c7104352e8e00878b633e17c7ec70718c0f17c7f0b83806c8c5314d5c1c93e4e2982617c740b938c68b8b63662e8e315d1c73bae3d8bc76a060e03b5090f80e1426be0325e63b503af01d285b7c070a18df81f2ee9ec3c383c4a07818b3e261ac8b87b1301ec600e061ac079b1cb101402e3680898b0d90e26203aab8d8002d2e36008cbbbf5eee363a44295070510a175c9462002e4a510017a510c245299670518a2a5c94620b77efe13f2a403dac00918715e8c0c30ad05e2b8400063a81266a8490a8d2dc3059c08f9a963339f8f0717777cd016483bbfb8e009039e3ee3e840511d2ac59f317ffd7644a5e15544c910029bc6789300ad77934cd271100841705029e78e2092928b2701b28c078d0dbbc83b874a1a3edce92b22c3d2939c994aac49eb24441c9a2b06c8951514a82c1727a0645df56a5ee792369f974dcd12815a48d9bcad7fdfda7ba6bd2efba93cadfb8a98673c58271d4a6fa7d3152598f5c8c15039c711f67abf9960bd25e020c50827b5036cd7a84136fb048276208aa4eff547070e215549af646864d04e167d0d6b5f6fba4d9bc77accab479e8ee3226d494f8dec0040c486552f91b731162d49a56690d676ca7eaed69f4379eda3a67fe6dfcd9d17cca5c6b146baae968bb16db2ea6e34dd75b8c8eb7df980bc6517a772b1314b08912403ce58b60931841120a4802091f7f7ce88f0fd57d936f20092cf507210f930091c000121170172a4205891328d535a764c9d2eb5a0ddb5e23f1b27da6478441614fd69c3ec201eef4c17ca1528228982ff4733bbce9085ccd1141c4d027f74d992c4a141a8587de376532234e30a274f73781a8b6541bf1b9eb46fd9a6a7be2923eade633661bffbea675679a95ea53dfbfaff18da4f9c45ee8a8f5b96baeb95c4582a4eafe888e9a8a036d8b10f1fa4244263384156f8c33caef04429c719caeba7de456a41b1d6d9646f1aeb67c1cce527de44d5a8dca475493d8bc5f969696718d6a4bafc566ad7c3deafcfb7135d71cdddd8ad35b6c43667dadaf7d7dca64667d0dd31ac774e44869beadd1bf6d5cc6356d561a366db47c6d29dd991796c3597a71d67d6eb7ebc5b7f699ea2bdd6133f7a5ae369dc7cf35f7390783e9f0b087325f62a4f16963a67f23a9e829e69f70ed2ee174d5c1606f9e2566c2e9dac5490b1e8c0c50001378a9c10ca7ae9fb7b7410188dc33093b5c2067964a36ab754f5599469df1262b95aa6449794f9a3f77a464c9eea5663f3f148802f9f8d4f006944867d6b2acf4568ac9f29bb45963b849f754308eca5fe62cb52cfbcca2df66bceb52fef23e958d9b1e67a374180bc6510b88000e2f57c2d96a22c51e57c93f0af34fbba5376d0d0349e58abb91f78fe898df29a9ee9a34669cb33b7fee5a264a55cdaa318e665cabb4fc769c5d3ba3aa4c33fd3a6ce6cfe54e6a566cbf466d75df27f3e7e8ae4ed726e56878d35dae96b4b5bf4fc3367f8eeeaa3992d534fff6b7a829c6f2cbdfd18d6f4977369374fcb2e234dd35d1ec74972ed6f59fee16a47f7a2a4e4e9fcf5d719f9d76d54ae9706c9a066848a26599818c7a8ae12242d10c4453ae8305dcbd04016cffa41040e841210313ee3a94e18cbb8fe6b87ba90de8ac5cfa2b33fab7dd4b5d1dae7a26bb78762373fd6bf5a92dad371d2d77eea57e4aef5e4a1fd15d5e53dbadb79bdda4c5e18bc937f3974bbfab25b1d631dcb4fb2b7af75770d391f085ecb69b6f32a7e285efbe08f83315aa2fef520070eeeec2085c9861bc27ce9d52189bf46f51b18d9bf25f5d968d9b5e6fb94b1b37edfbb7e2f23bf5f0705a00c2dd6b184f27ca9701c0177a9f4f28d3ac485a7f2ecb98cbe1b3f37d2e2fa80a095fc28211cf8a15b058817a73783f4c48e107ef042e0498c0a4822f881745dc9dc943326276e009cfa8f11d15ceb91d6766d079a491e0eebbbf62cbb12b4e001881cee3eea58478f67ddcc50e819cced92d02a0072e4fed1545e1a6b5091d200510dc69d50be241e122582d82d594fe94dd41543b482827a3da55a6fbfb39056688c0e9aa23c778688647ccc657f7170663b2b979f2b4f1cdb3c43819add63ee746f2a97eeabe8c6a07bd2dd71b12aedd258d9d6edb76c93867a5ac24954529eae97fe3a9d2c432b36a5bca64af710ee7dc3d339695b9eaea2edb1cd5cb76b2d28410689bd54964b25c95e92a339fbc95a495517e69a768eefec3dde570f72cee1dc0ddbdf2e9cebc5d4ba38672756b9cddbb97ca670e67ab49fee75afeda8bf09f6bdae284486d7125249570848434db0b220c770aa871b754ee4454b80709c15d47c8033d9c2f4410a3830d170fc7784182061437e93c925fe26bf3f642afc7aec434fd4487cda751d9b57a6b6c45ffb53b87fb6b83e89fd7ee8b49fa64b773b820bacbfb3ecd616cd28bddc930c3dd87689428b403236cd179cc7fef99938c433058fefd391725ca58335fbb7b30fc7801f40260d2ea3e71ee9e0b345a4b0884225819afcd591d9a703747a5b82a76402cb14b9231fdfc53621c257d28addc36eb38e04e3ca4327f9f544b5419bc08f65a9c2e8cddb6a6d54de1ee473cd4cd4033aee9a0cf3937743aa478380543fe6b71e114964c472ae114f8fbcce130199e599c30c0f699dbe76ddbf0c41285d2b1fc6c4f3164a5b5b367cf41cb3769f53ea53b8c4dc5487321ec5b19f43947a376583fb35208676148c9994217a1612029108549a114262b479ba5e9253da4fc325752db9202510e804c327320cc298cb66b6fba9a9a0295f7c435aaf3088389a180bb0f7928c6875dcde69fa278973785c9caf277ae1bca842abf0c736ae8d717e7f4677a6d7e27aaca14f526ad72b15daaca346afa5275eff6b5414f5e0ca4f7f91b4f51dbddbba0fc4e6599abee88eaa7af51f356fb7c5e5b6d957ea678c360b32ff4fe943e546f186cf685d69668d55495a96add5fa02cbabf663dc53ccd585baaef53d9d6274e464fbc3be98977b4b644476db505ba4b74d457874baa3759f359526d6fa5c553555345e5bf96e969d44772b8dffb75ae9a4f3513ba757db33e0e88c99b395ca5aa7f8b2a4d7cd39f6f514723544ffebd9fd64375bdb6f6e514ce395c7d5d6f495f947136a3d54ed1fd1a898e6fd2acae5f2be9d36cddfbc891fc37f235dd996625ed2d57b30defdc2e93b4ee23f7c926bf56ab3463bac9cfe19b5f9f41789f95de5cc5ed5e0af61b5f4a85755174bcf8a4b66bdfb4371d9de959141df5dd42c75d29ce34cb5bc35f31b9ab398a3f1f79dd6f7b8ad15074ac7457d376ada6f5efec5a3a9b7d9efd0001f9cc806274fcfcb7b2fed677898e3a6c6a9bab4c3aff14a6dbeeaae9cecce1acde96026d5d73f4c97e4dcbaf541fd9f5b681663f3ef7cd71465f9ffb48173a0e01dd2c74dcf5c949abb634f7a4b65d8bc39b3e8de24df5e53df1d3ec0bd5968299cdbee89d79b3bc2dd75d7eb6fa934cb392b4bc95a4e3c5b9f3fc530c05ca1848cf28ae7ede3a77d25be9aeff64263f3f8eb45be36babb6a4362013e34d6b65d097e6597b7de65fa23b3357a52eb68bb354fd2e9f9d4cf3a4f2e79f5a9276d569e37b2b92d67fb7ed72234f268ca3a06a56b25d9cbb986645d2911bdf74ae5e28dbb55a5b268ba39de5ebb069c6366e32cd4a6586048ed27d9502c0c26cc954107931ed6ec9556762ec443ba1f643e5306eca65d16fe2271363a7fcf72e99183b955fe6aafba7ff4ca5bcf82848489b18e7645756c4dd7375e3ab7154b9a4dfc64c249d5faa6e285db352b984a3cad7b5cf4eba66a55a96adef92be18a966a5bb74d3d584cab896e5499b9d70bf9732a6a243305f5c6b96f54b0a547ea61bd3fc5219ebb014894da120770ff2108c096688decf61301f086c80c001770aabbb96753f8e0251204f05af052077cf04ad71eeca721aea6302fb86dcdd8b8720d8b8eb208d735748ef70b833a944dafd95fdfa9f742e577550faa9fcb2fc1975cd5220f2b9d0f281cafad98c6ddca4f56fcc0587f7ad3727d94fe92a1ba01ce10708f88008779fe2432fb696eed8aec4347acf1a0dbab8c9abc20f2c70f29e3526e3505090c5e9aafbf1d9ed1cee65da8e5264dde3a62af5e36a9396959fe57118aac4b88a69c6ca9fb24ffe53e885ee6836363637e5d32ff46f5136363637509cb68c5a9b39a08c6bdb6aac6b19db9c8b518fc9582bd578974b5c6652a432d6ed6f2abfbc273e82f2a7989acb923f97abb9cb8ce27ddb676c46cb5bebeb2850c654e858e9eb59fe5d8bcd3e9f24599fa65fe3dba6ba52a0197eda54df34fb5d7176eba75a7fb6e19cab74bf8de24d4d5b6bb4a4ed72d5fac98d2fcdbfab49ffb6819e3c4b9c73b6ea28ad5fdad22c4b8bdb9937db896ba7d5f47537b2daaebdd7da3e46475aded7146f2a85a93eb22dbd91f6a6a3f9776df757ac94bf2b69cddc9984e361d400a27451d89dc27c288551dbb54f140c857d110a1acb2fff2f7d1add55dcd66798b2d2181defeea5e8678cd3b66b6fae5dbec44d4bb67a0475a604a81c42dda066545bdd5f0a446d9546f1be96525a3e982f3487180aa0868271f75a70f758f054f0ce3ccde1ee545b2cda568fe8135ba595b6746f044f51dc7d2c5f0c1d6d954677556756baf589bb3f656d5b5baaff12d37490b6559ab6260cb6b53565da5669b2500b07dcb4b5b4f8a6a395da2e696dd8a4ee4ee5a11610f49f5adb1bf99beefababe99a34fee3fedf33965f5fdda97b4326182d5d21264ccb852a730ac1494131a77ff1a7d6262ca389c530e7ed3678e3e313189c185061e308ef93535eb7971497514e3306ed76ca92dd04c8ad109857c2e359cb1a69feb0e67b159ebef9a1f775e253a6e6c6afa38bb6979ad548c8e7a679cfbbc7b29aacb8cc9cf949626bee593ee72c5d99cb35d6bd66a1836654f310f7b5aa555006480bb931e4a796ac279c8e484dbd8d8dc8c363636373f423a484893b80cbab6e284b01d474de2524808a6737bdf7cc0e82f1487b73e916c6c6c6e64b251976f9a1569772b13c6519576ea5dce2c31ce44da412643132770318be2b5a23796c0506584a2260f9dc8b0883263de00983061019fa6ffbc946f9af68aee9bcadfb5cc6df9122369fd2479425dc9c07dec000519e7acb4e4610740b8fbb8a3d1a7d15b499a3f47f7ad36d729fa1723996645723262020535da4ab754c551bc69fe32637aed896995eaf095da965eaceb3fd191fc9cae26f925ddedfc399c73b467575cc6548c9868dd435633f7595b6ac4040a8a96bfebb559fde5a88fecaf426949e34be1a96be9ad24d575d712df74b5b275b6b5adbf34b10fcdfd4be128131d7127ce5255dca8b5ae340ad31ad02b7ca4175bfab7d9e7dbc6b5bb14dbf8d29aa53fa50f7d6d562a749c295972cea8b67436a3bbde36c59a96678ea263f9f773b97aa3e7d6b586e96c369b7dc61b8a8e5b63933e0d47f579db369bcd9b3e8d9aa6d5515bb31b5f8aa9feee8bc99abd7fdb74f6faa4afa3354b5f9f957c1a9dfd507d79978060b0289f19fddc6ed37adbfaf36f6bdec8fdb51ac6694b6dd74a9135efb77953fdb6fb536f5a930cda379b35a9d661d3ac54db5b141df17d129bf46d95b45bdb6b71f9f797a6190b578765b8fa5bb65fcb642fc3e5a9c5ec828725b744312110438628a614f3843940613f35a5bf335e1f10718ebb2ba1484a13e72a798af95246fe9490f65ef8bce4bc38e2fe49e02ab96f8b532a31d2d23be17e5b3a56303318ac66fe43d1516b4bf749a3d79eb65cb1e05dee44a8e498b3b129af54cd6fbb18cbb63636f8a62b6f2d1f77dad0efa269362a574ab1aeafabcddbb4b5963bf5b6f6da6de96c47abd1ad31d216b6eadd54fddb94121dcb92ea4a6b56a73f8916d1389395de1387c364d54d615aa51b5f3a96bb343fef4c2b423569f9fbcdba2b2d2fd171091de9a8848e48744c4261356c4d13933f4f5e7c295069653e375d6f96becd9b522448487bf1c617e39020a1556fa01d8dfe6d9bf55a9bb555b35631cd8a83aad5cea37de66e3bf749a65962a42f31924e2aa3f69bf6d625632afbccdd25a827d2e67094d2b5f586849b4cf3a4929431d25d2aa38ca84431d94eda27956fef273d796bdef7cf7aa352da1667b1ecfc53ff7497c6a0cf3224d76314121a4537a0982ee1ad31be91b4ac345a4fa09d69c713ef6805dae7111d3d8831c1604d929cf5f3b64d929c545b1caed666651d957c316eaba9d051ef74c6f93fd0549569e3a6376975e7f26fe4894dd36ea81d15cee52795d7e20e76a619d37d13aedc0ff5e6f9a4fba62e11e8c2c4c58d25d3ac485495699c5155a6d99b38e7cf69aaca649ab111cbd3165aa919baba6bb6d18bafc6fd97678ddedf516aa3f8a6fbdc6ee39deddff47d1cddddfae689f74f05fdc5d4acb67e50fedcb9f357f26be69969d2121285a2e2b4b4dfcbc64d3bd37cb25d5b2ad55d7395ab63e56f6db16cdca471bf97ccbcc5767f5b4dfb2ee58dedaa99d336a9c4b84a979bae56dea455bc9fcbc64db17a64bbf6fe94adb27153d393ce4f83884e1470c2c2c90ad1e9004e41884e40388d426140b4e695e194e41fbeaec99f326d4def6b6b5632eb59da29bba54cfa7878728011b35bf29e98fee2268c9f9c3c3756529e54053c046207aba52786942859a2ab4fe512d59757ebc72200bcdf5e5bde4ad3b67cf3dcf771b7caaec56176a65965bfebd49bffa4ddf8caf67d9ceca7acedb3dd52a4952acb2ad32736cfdc99497dcfb26233777e8949d3aca4aee5ef1b79e2299cade50b097959f0c690103b7811820086b20c93c8c8156478f192c6dd573a579c33775f5d40e5f396086854a102549859d02447142d2531a85a491c54dd88415e8460c498c921012bb45c85850e0cee5e1099fb1702980f0423d18a189d472128f2a764e5c3609f3129cb69a95c10ee06d5f29458850cf7efcb0b5e9a0fbc78f075f9b8b8fbce7a678854ae70779dc71c2e7285ea8b4d133b5524291263d9d6cc4195b996b76ea8df780a069371d992a4757512937444241744a4a224aaca34eeee9333faaff3efccfc5398de4a81b0366374acd5b08931d2182445d61cb575a5328e3b73df273fd37aa3d5762d05caddaac36128bc2feec4587e632ea369ebbe4f75b6625e5ff3ac690d54c33be3dbd36af8b18c6535ebcfaee17d1fb7c97a95c68a4dbcabfaa7ccd7b8bfed29bccdbdad492b7ddad20446f3d4a6b9edcfb63e7a8ae21cce4e4e331a4433a64f6633255dbb945b572c331a4461fb7158b775f97febb67fd3edcc52b7b3d570c6a4d604a81a233f7931b9b48fdc99121d69f7a4f71655ab54a540f43fd7be56f5eea528d0d7ea54141def163afef8e830962326282bb8a97cbc9f0beefc8b9b366ec24ddf943f97db62bb387779d24ea5d04b551a853d65797a52a2484b55b260d1e57d9ba938e1b5c0840c77a7957a669830e1f92032a1721f77884ca08ca5a5e9884473e0fcb9dc8fb8484445d21a221094b84844dd475bcee0640cdee2c5c50d606711387c8002359ec64712fb16a21206f82c6a466b67eee24e5d6ad3d6fd49356f2a25b39a6f052a3feed41723e576aff5934acf830ae28fa954774dc2b51b8583192949a2c3d72693ddf6cbf096555d92ffb6aaf5e364b693f6a59192244a5eaa9a34a94a922fd584891299927ad3b29baeb7276f95edd732bc652ffb8bb7a6e1f0d6e1f2b36cd7de94e96afbafc9f096ed9bcdbeae6299b632d9d5e19bae3799b6d9acaff799bbf677ddd56ed3ac3625667d9dc4acab28442053e7f149f9629ee81367b2e2e80cccd47dd99722b39b9cfe9d619f7341bb52b6db7667cc24139150e12fdb6d9b71988a93c9b6ae35b3de8cf74fe1cedfd5f6e697f7d45b5b538aac264de6791f8ffbb883e0e29137968c4a928c4341578733eda6ebcd027b3582149aaba44cf7d71b722f26664085bb5746ce4a337a8f84a399eb538ca340206ac085fb78b1d5a7495ebb6ded4247ba2bdd57d4c0080d78a074a741d5b559adc1cc5dd4c0c8dd450d76dc8dc62441e44f9966c569318359064614c8dd5b2e66e043cc20c7c81cee0e44a94a0ba39534e3702e178d84e114a6716f7ef95f9ad8caae8fcb8fcb1fa5fb262e195f9bee9e99aa32514d475cfea8f14b4caf05c2f94530cd9a7ba99adba273a76cc4fbc7a7bcf8c876edc65c46fd1b4f9176df6ab3365a77b4ba77ddd5f2e223a47147ea237f73a73e127480714675a54a546250b9f33449aaa740e5c5e443d1b1e24ead2bdd99db92f76f2533a6650efa916dfd3459c5b92d74fcd9da874add9a736790a634466177898ee5ffc65c70d3ee5626fd1b73d1612cd509476dc9475b9f4cb92d4f4875d7a4df98cbb54fe2902cc4214cb88f60be50994c1c8254fe99b740fd99a5b4d5a9441282852b45e97ded587e06434eee9b95866daef7494da1e89804a6ab619db18e3ead029d5a5bd95012ba35c6d1bd8792e0bda3d53069f7e36edbe659fe9959f5adb3fb393ca3b7da5d7310de417a2c2d3d62620c4ad9f2a76cfeb2b4d45a4b8fc44cb3e69f52a29d9afe8d02d5dc89b38ddec8aa44c7dc897395af3d0e9354dbfa64cdbafc396cea2fcf7b8e26c63d8fab3629dd436bf8cc659c4d8c73380cde26363176c251da76b1f9473bd33ca9ec6e0c1f4969fbb471d3c64db66b77386ffd49394bdeb8e97e2ecbc64d5a5355a6b7dd7a2bd24f552b1b37e9c7494a493b53eb37f33b55bb7375422abf2c632355652233fe146e5756243a8e74e3a6b734729ce9f09dcd92d00da89e0599f54baa93222b8db630eaba504d9d5edf6cd60ac651e597a749be49de2d4f92a73e731aa8fcfc3977d7f1c8f880eb74eeaee4ee11f1395ac3f4f5e7b28cd9ae2d317995caa5fcef4413738c728af889ffe29c9576db9af5d4a2c3587ed7a412e32a5787ef953769758b8e5155266d2bcf0ea82ad3c576d7a76d71164b69bea50283ed70ed2e5dc0bde8b1637f3ee428815781cf480aa23802538457c3cd8a1e2b7820031e2b42308128f643957fe57629bffc9c549a59ca2f6d75aa59497c69e1ee321955c56da1aa4cf9a576b72ad1f1a7ea0dcc175a67b66b67d91ac95ffba4fb53dbde13d37d62a09d59b37488e20db4a3d15d6ff868fc9a95f23be951876f9ace76eeccb42ab35d4b9ee5e719fd4a9f6649a819f95cca9ffd6c9631ae6adbadfdbe35d7d336bbeddc97b2202a85cec61f97aaf9e54ccbca1ff3dbcac70235a34033aa2fcdba7194ded7c2664034887e99594c858e3b7aed98f1e7988c3f3324549569e6437f664832a632f3a1fabeeeefd03834164d69bbefefcc7a2bb5edda7246e2fbd987eadca969add5309899ee2f0c96df566f6522ef4ed36b75a59d9896b6e66ee489d3959ae6ebea4d3fd151df9ad374ac396ffd379bbdd5fc407a06863e91d9ae7dd9971985697d32d18262f59e3f3e54e34e325bb2ccb8ee68b4ea6ebaceccf2d65b26f363a6a1a1da121d1f97bb7de4d6ac54f3436121fba3675787674d3e74d4d966697496df29e3a81998d9ec4b352dce6a3ae6af2d95745baa976efabef92595324f1ad561d3dcd12ad53847a544c719989a95806810ad59e9cb8ce6ff817dcd4f6756a29c9492645e9eb244412d59998d60661a067bb2d6f2ef2ff9e9583e59f3e7ba2d05ca785b0da5c337a09a9f966fd6fc3b303318cc342bd2eccbce2c7f77ebcedcba62a1637e5cc6545b186c46afc59f31131d731547b59dfdf88c3f3e7af75230d8cc9a399ceeb30dfa4c77401bdf5aed4b4ccb16e86737dbbd946c36f536efcfda666b3fd75c5a3033aafab76dd26ad0c537577137bd3381b22df76b8bc3e4c89cfb795abdb7ea7cae74a16399abed33c5b56b7be84ff9a70fbe14a8b65471b61587c3e4d32ea6e99e1e5abe6956bdff6efda4cd5b7fa6f9c4359d0f35eb8d3e79f3e77ea7ebd3cf47767fdb9a92cf65898e527467d22c494bbdbb313ae2ecc524d5fbd69c1d6fba6ea88d9b8eb2ad4e171fe9cf98093749ddf386244562f36ec14d547054cd4ab52ce58d416ddc74dbb94fdae95b7350b66b97be1e95f5c834ed0d496ba41da2c13ae38d6b29d57f061a7a4655d6569f0175869433a09861821faa2d50f93ef43735a30a3372a035bf19db8c29666c60061133723405d259e3dca5b4fc0d822940d082ebf0a59a4661b319bd5689cb1254931215a5a4b17e2e57631778088271acf9a16a7ed96b58195a5051861265cc8037fda1d7d26b6906e60f0fcb3075191c543347a3902187b364cc408600dc69143214400d05f3e5068b6fc903bed50616a020021068a08a3044f02b7a946002d896d21381980fc75c838b18b450830b2118101ae2bd207073772c4b3bb2da1e8bf6606e029dc7a0a08b4d0fc5f02f314e3ffed24e6d2757e3815b7805b4d8c0e3e113f0882ef00037c3cbc019303ace4330a090dd6ae66460fc70778f7a4b55996a98aa32d1c7d9a8f167b773b8af9bce481a4493cc7ce88c9cd16dbfd27476cae66d5a5aa5e1a7291dc58eea789652a4cd34ac292e631ae776e6d6d5047a9cdd77fc793d9b51bc4fbcf301ba38bf083b6d6f7f6aa1e3c5f4d637cb5b6bdef7b7a53a6c858eb9936e7c81a4347dda8d7cf25a283afee84cc378d3abebf8a3f5a1e553d91ad5554777399cf73531ee326b4a12d434a33b13a87c2dbb78f69a6afb43e5890bad741fb9b8f431cd1cdde55a5e4cbea65abf794ffaa479e6708e3e4ddbfb9f3f476b96ce1e87a16654ffec72cd3887fb283aeeaa14b6fdbeb6cea066411e64e91245c7d9d08ce27dab69d68bf5dfc8faf966a1e3cc4a549526252cb2a82625a62a4a50b2a62c4b51485b9ea2a09ab220c99e94929ab6e44e5ac3d706e52fefd9656b5babe1a02f6bdeb4d297aab88b2d6d22eb7daaad3573b94a475dbea9c3572ae85a4c06557a234f4df56b9ccb676e97abce3f55566d2b694dda99a3fa27e862fadabced97b2a60fd5f6b3aed476b1997fea75f84681b48949bc71ced16bbdc49870f937dd19dfb43eb125ad5971b492f74fb376aded5a9386ab369a712d2a4b138d82dac274920f458bd01109926cda7d4f3a54feced479d7abcbf2c5f01d61e9cc34add48c664c672f55f7ec46e29cd24780717fadea9bae99a4444d4e5ab3f4c95f4cd4e4bc9138f72ba92caa9800cf6c637363535fa5a7d5239d475aa5790165bc56e8d20c6e0850e1ae67d44966bb7647d6d296b792b227ef89efe729d997b25bdf24711859696526c639d9ef3771969517d3eac5b4f24f99aee6f2b6adeeff7f5773b2fd78bfb63299aef965f8ea376935ff35cfa96b65fbb5ace697e9fecacacff96b5fcaca8b699f6fba6e2dcbd5cf52a495d2329969da1b89c3a4ec5a9c6c67e672350246221024022fba7ba9f2a730131721e0228b17c1227151e5eeb66bafc597c2fe9e19c685c94554adbe49f16ee2a2888b0cdc9dee5e6a579aeb9b483a8f19870bb798dac26e11e5f4a6eb4db6efe79aa354e31c85edd7b98a93d9aebd67ce651caeac32fd26794fd97e9dc5e97ea58d33677c60c226c783591a16a248410a6e3e8454d0c3bae361a51ed6261e56260f2b081e56ec612de36125808795080feb023cac1c0022a651e4621a486980e0621a615c4c63bbfb4db86347f8c4ccc327c278f804091e3e0183874f14c0c3278cf0f0890478f8c21a1e3e8185874f7ce1e1136578f884093c3c802706c1710de1808b43d47071c81a1789ec7091088f8b4490b84824c945224f2e1209c14522d8452227b848c4051789e4e02211215c249200178960e1229108ecd0218e61c6c5316470718c1c5c1c430817d30071710c03b83846152e8e81c5cb25fee02efef072f1071e2efed0e3e20f485cfca1c8c51facb8f8c316177fa8fd70baf80309eeae73a4021dae1c20e2ceccc51d9c8b3b542eee14c0c51d225cdca9c2c51d0ab8b8f301177748e0e20e1c2ef270177954e0220f1e17798c2ef280b9c8e38a8b3c40709107ce451e23b8c86306177900e1220f285ce491051031bce262287331a472771d56887cc7ca91ef5861f21d2b5c7cc74ae93b56c4b8bb8ef08b91875fa878f8258bbbf31095d81e2a01000f95b8c143258cf05009273c54e20a0f959880874a6cc04325d2f0500939dcfd95138a3aca7051c7085cdce1b9b843c7c51d405cdca1818b3b9ab8bb053f441cb68b38ace0220e3bb88803142ee2e0007767f9d849dabe93b4f39d24169272707642f0c07742a8be1302ce7742d0f94e0825f84e082af84e0835f84e083cf84e0840f84e0848f84e0850dcd8bc5ca22836b9808b4dca70b189085c6c22878b45372e16edb85854c4c5a220178b602e165d71b10804178b6e2e16e55c2c1ac1c5a2165c2c22808b453fb858a4848b450a70b188022e167dc0c522365c34f27604a8c4a804f06da5999a744c213333321009000000931200304024180bc70312c16c9e86ab0314800372a67290521d8aa32849415219c20c0100000000000020981a02a5295a29c7c8d19282f33e01009b2b0265b1c6599152c7f19b177053c43555c0be14b1bf9c5fad8f1a6dea9b6020874aa802915f6811ad2cc131fe698eb2978a280d50a1ef92c4c70a46efe9525d1ac4e81034e235d5337003df3edf50b9ecdfb7fc52a17fd7f4086af45247e6af2f529cfe8e3149cd0db26bd903b6e488f4226b19e01250a3e9a2110a17132954b2729741f1642cb47251420a7add4522169a8e46a998080f4d3c70dbf53a0f36dd9ed1c8f0df14b9ebe397d2791b889d13924dd5ccfa3023e766edd6e5f676fa397900b4def30b070fc2477525eed178d3873c083c285cff25a8309d66ea8a70a0cda27360f908165e7a25aea728e39205ebc3182a9f66d740cdc774053a8fc169e04bd9730ba7ae045646414bb46fa4aab1c482844d99f544e36a4259f867339763fe05898bb9a20a5658329e25a8d15c75f9d01add8f8653879714a8aad6362c8b67c86edf38147d9b817573d83271a1192d830a836cba0e6fdba0230b64a236790111a66f81a5c10025160bf3b1c71baafe84642c27f24d5aa5b18e4a0d42349f848a78a8c5202d01ccbbc8193300d990b5dc5ef3a30dc7d2f8c94a8fb15806e435a5310fe768b2f226898bc334e1510b7a2d8832e01c3ad6a8577600183ffc3dc1aea0c6f80a6ea843ac6c2739af045a022eb631c4714cee200558fa4f71291f4ddc0f7c327e2b8899b4e1cdb3329b898df6cd3ab645eba7a92d95ec51ede37df57e99b801ed5c31978614f484bdf085cf2f9379c6af1120197c6cb5b66bd50fa4cc28e8bc48e48d19411a1545469298215a4902f6e8280b71a16f6ca743e3034371caff99d6ba142902ea7ea1086f52c049b0fc08ee310b25951c59f3b14f485ccc16fa5ff90fd808deeb3a1a08e59eac455db4ab051a1e400c887887a0bfb39a36a4e01da402a3c3a68f6a2efb681b4cecb44ec43a9ebffccc0595e83e47958328886dd0ee15aacd5d04cdba3deaab3a039a5dc52f0f519ce93515b2dc2513e911cc68f85a50fbc8fbb01215320a1f629f5a82146128b840010e72d8235bf803c66df8c2769a9b6728b807c3c830ccbce0954bc0b7a6c81a5479db1e6b6a1c1ab2054f8ddc9786ef7c608c6aac64feca2dc8299441eac3ec4006dee5b87a887605520c18f44abb9208536d5ff3a004775f8087e46a35398e9c9bd99239b8dd985493b305d7bc1dfc36a84680bb83712ea93ac6e77dc19b02843dc5658d2a48c3656cdee3e633a8652e69e51d218c104c913ba5229253f3b5f86fe348a62ac219d79ee8eb07221073e3671eb8a0e38b5b1be86b644f52ea8aede9db96da275c18687ce2625a8eaca81e37b2c1ea06d007a0eb32feb6e9ee7fa8ca87d8a12286ba76c42793dcb7842e72b0161d36b22fad1f79c893a5cfee9a3e5bdf39de13c96632e5a4dfa92d310fe400b42884e1bf8bc7d66877da31a00477e4e039009220603780f1dde98ee7f890819d0022e88a8ae4203cc01a21c1e19541288e058e01e7a016e78c7dd60600bce0e888096c3a436ff7ebe513b3f3a7cb90bd6b244751329e56b5e45f5930781b959a41239a2796118cb91708a260dafb8158d5bc416e3d8ad1a2e8eb9a4695851d5583db437d32eea0098e4efff1c8538d7b75b7b179ec8909901f5c4bd627463240404c14ac09ebb49dfa20f840094e90038886fcde1a7fcc3dc8ba721a727db6f1d62f32803a81673d13991381179cc736790a9155f57f35e2266486d9f7b1bca38de118a5c8eb9098f3c9de038e6af2da766307d48efae1b6e13f86d6ab2585d86dc3276cc781c28cf08a2ec7501620d10b0e2bd1bda1f7c9ec93531806e46d096a4521a3ef7fb00b084a6549150e5f2463db5db0e6da1a1c2d439b35f1c7b7062be2c5a844adeace61be787c05ebca890a291224cf00fbb807edcb6cb25f99ee38d82d40af621a3611f1685c7b2af18bb111591b4158ef6f0f334cb1e205439a8a88f00483b1421695df54f7fe4cb310dbc651faf56b3c6089113f87bc71e8011b0eb24cfeaa13e71c87319ac45469861e8ef0c66dd44eba1cdf368ef2a4a7fb53fee63260d5f839034786a9388b3d635b10174966d94df2d14686954b5e877c1bc53e2f96db90f8712979933cd632179ffc90b229d0bea9c5472297da974a00976f8de68b1da90c550fa7f98ae401e0ad26dfc0612ffaa83cd97de3432c98f8e8840dacedcd49d146b2de82944d8bb8ba11a581bc28573335f946148b0fd5aa7bb9162d183911dadefa20fdbb894b5578203e7524ca6d1869e0296931f0cce558f1431c54de2ce131f31149e4445db510120612a2a2ff227d3811547ca8f1aa21936d55b39b3ce73b42347802869f2287c36e9bd5e58c0b13e5473b2f08361a2e82357501170d42d7b3ad97a92ccde36342c70b4b442b8d351931650afa7340eb4088a34907604de457bc0bd4e77e585d152591cf9b366dc3dd46ba6b64777e2fdfc3e35e71e1b7cecf0730156c9c5c9ab70412c47c1be40be0da9e5dd53f4e357538d3d62570ada641da09a5f071f27c4aa9ee8f295d61b1aa2025a69a62468270ceb18b6def77924bdd6e97ee68d29cddea60a01cf9f529e77fa3498499ccc9dc7059a0bfa99d54fbb93fd9316266891df4f2429432624cb5530787f41792c433c1f2100fa8165b49d4ae8fe60fde9ed641140538a16078310cd2bfc96f5447273741d92b0eba157fccc94a8c22483acc34d222f5df2324e2dea7fb0367e354e8d76565969d2cb84dc91aee3a58d28ecdba617dddd6fe34f64deb4455192ec92b02fc8d757f90caed2538567840a37ab042fd0f91d221e0c22ee41025aa123f5a0a6703855edf03e4ec821f2a06ad04872b03046f0edc776d0035040448dcc7f29277cbb9ad186731b0ef6556859b9da7b5ae7651af672ec568a241597883a16f9a553d031a5426d87534edd71fc0d8c1de4be2c5630cb4781a1f40c770a345bc2e9d3cd23cc154028cf0f34efb0e84beebae9ff904d60ef88098bb49138caf8d30e15e2e8cbf035939a9ca1c1607bb793ad8efb3221f86c5a0e80f85e9ce1b5b36f69acfcb5890cee7b7043de4cd954f378b5c218a1a5cbd28585f7f7b527d303f92c92a5e2b7ca3496ff9cd2fa7fe91b3db65acdcc4df634e483fd35507818e0848bf0c69dde68d580e176f59aa23de20f1021f2eede580327e2963e955e0b57ee1b1bdf42cd74bba8149927cc17d43a69e12d0ae8ae63b550780397835ddaa317434d1eb604918909ad781231636d1140c1b3cfa67530fe761ef9b2a98ea0cde1c51f1769845824feccf0f9fd54f12871cfd878f0780fbf1d60f4ef606df7e0d4e16a36d6439c62a20a8a5cd7014a828e69af6a1800089a0671460b23438acd3796f99864be1b682eb30f5d50e90922a99a35049934a92238c8dce91cff51de3dfd934b783d8ccd7be13bd319551841047369a7d41671cedac2ff1c6da4b0f8922d4a394d442de5fa35c64cc6f8fcd322245a92cfe6a7b5b7fb10e81ef4e587994876fc98b3a2fb7c02863ec020dcd82ecde9650599cf0ff767e152b40d0b1cfd91b5007911aa8834467ab8048eae88771918c100245fb8d2c3a0e110169673a9b96c3fde2ffb930d2019db13224023e8a27f168e14d59166c8183710ead7229bc7a3e6b3f780111488e81897b0aae6c6116883ffc1192c84e6890a92148352b2c5d35466abe150e5bae7c34ccf91726ca13cb2e3f1e1c13af06f3aa7260736b08ef9fec83988549592c14a872ec98e69e05c11ef3e9564a6d90516ad899bd304b88d8b75a480ccd2e1c7a1cfaddc05a3f5ae3427d6079b01ffe349e67495e128abcf630ba66324532bacd578adb9cd99086dc15994f4d1f79a4bd85eb9359e6b0df118198f55d99d841e54f7fca270b7f48e927acf9a7daf36a5cac2ea0974045d4811f97e21a61c4d30177074876aad26316a95eaa476beaad9678bb64a326752da93c346c878c071edbd15f39636dc7da9621a8a6ca71973297c94afa2e5580bb27f5a3e5794f6be7c5dfa2a1f71477a0c5b8da0461d4183de6800c3724a4f023fbdfd1c70b9b7acee58d28cc2ec9c81996e13ad4e09c771ed94503fbe24b97f30215c7f1cc25315cc01b6d1e7bb2b28b8c6994561c4ba5fa605b35f358a0518b14159140c06934cfe69cbf0b86730630812fa559818e81608a55a02af318528ae8fe3d5c405708c9329bd78c36b00e3a2b45b31c4982513dedd629875a026822fc2e54697ac53b636ddb016519463e1924ba9640b02c1e42eace837c8b3892a7c0200d97d54de3ea9cf1c38d6e8b5695142abdafca574dce53086fdaaeb4bbf67690fe105df6319a1aa8d3acacf51c58a8b279faa6e7688613f822b87717733da453eb9ca6b52861d13699bc35d3a4de762515c1f0098218d6b8c82793f899343cde6823fa06f12a2bafe42185f8897a7b8e0f944cd0e510f3486504eb821cebbc22068640a3160d34c41f0976cbb3e710bf957012a322cf32e4e8ce574d1e4a2a404484d2f3ab355d144a703781422113c92238adfef0f48c356e9017455a77464921212c1cf496a528c66333d3c7804435da3d4b6fd8dc197fdb3b2f43908700d5f619ed33ae395a9925361f82a7df1bd1e8b154139c61cd3e8651d57b2cc48ea71a1411f29084c65ca89fff7661d0d2ad61a6de140e82448ca7997c066fec0eaa7b58387d7108a8c1bfafbf54e8bd1299e2608764891abf95f6eb6f7d5c31bff1a49c216b7ce8aa00773547cb592c57ed96ef8320ea846810ca8a5ef45e64ce028663af4fb2586f22cd354a86750cd9bee1b345ecb213e36ca183b3b538b928a54cddf5484742e46e920ccc42438580f71cb8cc9410e884c1e95191796b0ff55f07024941630d6ed2450a90c5cb8d3dbfc41b8e38126148635e3460901c2e9d2e3dcf22f3029c1b01a6fa8305524dbc8b9f52c3a87ceaeb3ed09a74221ecbd19fcc6e4c21d92855e936a902aa6a724cc817c08e5668ac0c7c10c2bc4278d8e57ab0c28094ba3ac20bb5692f4b7fb35c30aad25a952ebac0fd084cdc29a499d1b74dc2472f23089ea343465a12094566a9ae347fd5f38a78b31b62ad89ead323a884983d1bcd97328fc3d08081662bcbf0c021e7594624140058de19c854f9fc248aa069123e25218884fa4905dc1018f523c8e05c9ccc241f143a17b214d4a7099d7cd4b381c603b9cc94895d58970422c182626dc44197544388cd6b92a9b0612f683084a53b114c91094b2e5dedb992f2bf4de0005bf6deb0f0bdc143b9757f720778c692b474cb985d1022d2d5e7c9b639911bec47134fb36a3160449ff29823dec41d34c035476cc4406363d480de3e12220242b653421a15a5ed521f3d89e95da810e47e87a356a9c0837f19d72c092b2912b33a7bbfef0c6064828675706d9994ab3ae7dc235d9cafb852356300e951c8b662b1719382a78dd900856fb9d6b6a59557f38f7516ad15465e469e3ebb1707c97bfb060e48e71b39e2f5173985b27242d4c12a00d755a50f7336bd665bc125e8a756faede236086949c3d5ec911ea6a0b6dfd16e4fb2e4050686c38764a35275a0506f65a920254c173243675610d5b5e36404ec005e93e43a3482972e500fc9ffe124a0133690f08182e830069ca1310c66bf2dd407e8160034ec5098caab0c84a1297dd45d59d388093e9710b6fef01ec03556c745ebb103c72a805f058bad4de22772743a34a6d4c4d3b8a1116c7d4c06229ff4a5fb9d7e28dae5b4915c2268c0b9e8632a5bbe78913f569325ad9b329b6410a3cac52b8927548bdc64d00652796ede21570f3f2fc5c0cee8c0a2c2a3f90b051a11d7812b1d30b1bf8d544e381978b903f008cdbf3a0d13008bd1d80b0bc40cca00ee27d2fdf56a677b1bb9e757483e09a2df3de737c18286dd7813efba4d9525309b54d313ca9fc01779acf153291250225212a284eb69e5b521a11fa2ac38458338725ba40ba2dd9d7b7ecaf0efedeed42c03007458ca80f6185db70bf1d59b0faf906fd07a821fdddd4189a1cd360cf76fe4d8a46823d16726e2715b68c6c74f826cbec7b893f2ea5f48abf73109c2dd111cedb1816510885154d17d0935d62bde262898d45c5c1265a7bca14ac19d5d1a5ea251592828a246d17f9d90cb99ab4626480cc614a8812004f88ebe8849a830f6c29d754bfa2c732f14f3557f4060c2a4308b93536304d19626656af1622784cf0e78ee96bef16c465465c3d315e9055dfd49497c12f6228da394f56d95bc0f0644260eabdc14bf0a7ea4142b713113541880880405656484c378b00ea9bce23bfe17c4116ac23c0d8be3ae81138d83a533d45f311d0a10f8df7813dd3ddedae798d7243deb938ff57541e3625036effd654d855f8e459b31b83bcd6b08a6f3946899e113c9bd1f119318063906d6e2f0b3ae2bc9246e436073a20b3061e223cba9c51d8d7f7ef76e02e01e14faed7d77e2cc5652496d850520e96f86ccd3df7a03a946416e15e72de9fd83dc605516721de3767af2f9e518067105f4f87bb4572a7bf328fd390f1ed2b3ee51894deddc090188a1bcc2f363b2bb2f3d307254c0d8243056c789c395dcb1562c8b4addb53942970d4da5fae223045644c5fc6dd31697d648f048362059b5da8dfc6f652754ada18af960e30de9ca989bbd83c97d301256b37c92ee23bec863d9a7f9b07e28fc754c189a50bada349e56f95e326d75066dde76b855e16f792ed8c0178aa992cbbf7d0b14494d7e85ee60e9d05615d92c0e4b16f6ec8d9afdf610bf311c0a31f23db028c46296de47355db2653b57562f35a8e905c19c9e126a43a0e07adf810a5d66a12c9623ebec70a1d69a5ece3edab920b6360099a9df2f2088a1dbd2c8e0dcc9d0fd5e5949ad024d0d82c402a506ab8fc4d25290c5f20548818bf6683d12761d566adc07b26a6231f39ec1c5b1e26af7d594b17313101fa188fe718b009677dae0c8d53ae3e04142da5f6d3bd4076c54f3775c792d073cd84da7d7025903c5b908cac3456dc8c4a7c60a16af90159fd6ea02f0744b2bc702f1ea05646683a67648c7981da39354f4d258abc3aaee0abb458e39352c0e449282c57033d189305ab766fe04f79c35e02dba19dfb56fc3e6a75b3315c59430baa303324c0ccd8317482ca5842d23251bcbff6fe5b585b8762c5b906cbd60b16e0ca0ad2a837b7fdd86047b63e1b239fe2267bd4b61eddd6bd0da6d5c830a82d1035d1a30d9fe73cac1120e86f1fe8948764b9e73985de4b542c8ba75b98b275ca4878ca9025d8a3b40299849f41b7f01dd3f456c11fc919721b9c06c0dde4b6d9a22ad7abd2da1e99729bb383bef075e74ee91e41f76695a467f1596ca92900b6d3064edd5d3f33a7805935c02d98ea99efc922966d0d2d7bd6bf87fda18937316d5e5b7c73ef4978c34e4ad41b96fd4beba2507296ead3a778bfef7e1b4e1be6dc856dc3d9102304dfe139a2fa8680e5de887d3cac6418ab65f51f03b3e7c47f57320e3ea2a16d5b660270189cd275fea9d6386a7d5bf491c161bc28ec2f3663446fd0e4601e832257b976a08b631a4df8c9c4e6278c44038ab781e02ab1a43a91bfc2e6760464cea889a51f1970bbc07588a9908a7af5807963306ad4a57cbe6864d5d85e2598b9c8e5223e1ca1a1522f338edd78842ebc824699e8697f1f708252b028dfc4c7165e8a56b0f31da73ab71eb160569f8a5b71d8c101e79754d5d26230d80ab5d7a005ecdfdcca1c203b7f977dac8744901efc6069cb10674d2884baf10111157f76a10c3f623bff044a0d7f271b4d4e12c3590628806a43f3c2a88a87f84e9949369ccdbaf68a879740cf6f38e18b8e5247558f734a9ac52ae94d6fa159bcf5a3d193898c4349298146b8ade3cee785f7691027815cfa0690f7751e856517e1ad27ecb6fc44c4f48ccb1e6fe45b00b556f1f8170afddc15f31bca3cf78a23d8569ec74f8b2022f0700f7a62328a11945163121b79decdc5156ddb05e50e01c3aef189b61d1157386d4049098b5b94f04cb4046038145a6ac3fc1d144bf9877d9da82955461304ef72d9cc75ed5d81013f92461907c8979e11cb2f301250e0d1a05dbd317312b6711f344571ec461a3f7455af61413600390b751107c90c1f1affb88c68d3481071dec063cbca549223b6d34a1631fe1d2ee644cde5af17a06352bfe0fde4d7b2da0e3daf5ee2fa07f196c15974897bff81d61e435d489b27d2af986be6e297564e520c167617d323e94d1c6de1e859962a4b32074a63d8c8046fba8e3eccbde89c8c65a8fce93abf3db8bf2dd82a68f38744cd98c663a6c1e643b47b8ced4e8382f4f9232ef24801e6f0be047252cda4abb5d78bbba4145b2c6dd810fba1ebdd891ac38ecb7441ef62625bc5c86ef723965af821210f2ac7783e20ccff44fc0d60b258be4f3078499040b033918504eccfd647ca4f410a1c7eddc080e3421f4711933928d5af62f489dc09087393bdff1431ae54943b3d88a8882e9b1292ccbc506384b963915fc1610a1ecd41d36504b7cb8a14cd5ddbcedc487fc7600ee88249eea728ac6b8b78fe187aa05e8ed8216f9d107452a1cc0f05c0483074c5ff949301f9fe3c3942b6b80cd55ae5e1c7e18401809a5ce6b1ff47737540aadb96f106f7691e931709ebda44839852ef403659d1583f5390e5cf83280e0be6ee837d6b7566c919c95696ad77aeb91301587bab76b564f3088cea47caa69794445f5c2f74e4d1a034c3661a32ec8270d2d45b7d9bccc53204e57eb8874b932d0c4933535106e0e10593c95e13bbf42342209fe73ada9948470098aedf8ed810e607adfa7a7b37a25a17ec5dacf3fa4525d3f1a029b7ac08b64068cbcded6d7266ee2a550e8df04a1561e3ea2b1d9c0e7f1782f3838bb49441119ed74bfa7702e54a88354999ddd77af9325e073465fd0628a627dc0159a7391da9c322a6b15d802af107069d5ef217338767259adb17310c4cdaf9913d82d395dc7e827fcd0d27c493887247a9cc9a157c373f8ad9d76adfb503c03e44a795f499b823f7ff69b9c479c23a7f11f776436915cf735b96f07b2d63a52d7bc834f581db62c36a43346d7d4218025550935872da447019bc06232b34c1f710005988010cdec2eea65df145025e2c6b55c50b77a467b91901ad36056b203372be10fc682d64d7bc7fef30cfc0c0f9d20dbc14aef366739e3f9677c6e3c00c371de4e70dfff9369a230c530db6293c4fdfabd16895bfd64b44f703e9eefd42f8d3f165140316b529244787d0f11e321e4d840af54b0e2de66df5ed6eeff61b9825894d7dab9e9ef31b2a5b3afdbbd81e9ea7517154725317107ae2b530838061a34f0c546a1325cda3658f00fd7858e0c3a476796434d16f057d1188cc3dcc9b0341b253a19a2e8965e3f06f61ca9d3e8c9ee6c084ff5180536f3052eb38543076843e08fee0d3f62bb2d7bd3baeb27528bda8f39c927b2b2bfffcb9df83c96302ed5e82c4342e42cd9f734b8318aa69be2e9e201d47d78779840c179c8ec37a76d750e09dfe7093674021b4f55b708cff85a11388254d88830d4e6d1e8f63ac43b2610f483ad4bf95309d47e3583825bd10104a1ba3b5ce830e8c5808930ea274284c0941304ab6509c64e90f9aa8d8382d3c3bdc88ea2fd9486e38c93772665d353d2426e32e69778abbe50a342174d4b25ec58bfe550f375ca786e6c8f3d5661a1339ad41c227f8ca18f476bc457257d6ad6b6a9fcc564c6dd50aab81b2962ff5e3d034e1447059d00c4fa5d4936ca004aedec2fa03259317e05daa1e5d04b1f77063a4b314a937c0d04426cd991dc05f68dc37f99f151c6cd4ff7fab42ada690df2332724dc168ce9c5d33b6ef201b358b764b37c51f6ffaf884695caca94f556087d26e82fff1ba2434af893b9052aea6fd1d37f9cc490268535e6064f07b7682dc8f709b5bbab8867e940df8fadd940a8865de41ed839a63290e35dd577dc2a89ac58824e8cb9ce37a64243fe5447b68ec3a56a96202232e77196bee9f3a8db17f7b3854dcb871440b43855bce9d6e1fdc9485f59b2b33f8a8e1f8a8706aba83fc199317b8fbacb1f8b278c6a92558ab7b3c005a71c22fcdc766d71416c50e7e5905a02b4b90ed6350f22cd012007738df16b0d473c31b4676dc1f4a837d15993193be17cded6e673922f043bd2b455ce4807ee8e2ad64cbc45693182239433a83d44a8648120c66247c7a530c23b4c478600878b14c74686193f5382e921ed7c1d5b97d1bda96c1e2e852b5be654df1ff2a3ea0a84efee9c70b20909697a2e2a6ebb2200de5e82e8600b5b39261e9ac22b41706a02b0d371c344ed7e665bcf55dbfba1a78df805f1431059b499fcc9bbe0d835470bcc224458a89bdaefb2592531535f31b61ae10904f533be9890e0995d93590fbb4cf5dad08d3c16cf2a8c5b3eaf51b19e3b66331f55a2321e1158f985955991b794ce182d52a4fc450bf54ef735eb7c7d84f256979b6cb24b527877cc01ed758ae28850e3c514fbd9848f15135ae7feb59470336d008348c7b7f293611f6d3c439060297ae29016f12085057487388e8dc333b500d59a0cff3f2f3cf5ebcd07ab7414cabdf15406dd99e22c629cea505742622f121a21d679a40b559143edd23041c6866d33df1a1460b79853ee318666455ca8abe60c2a606b3a13f3be46d15d59ccbe4db48fbcd19ba517b3e9dec272fa6b200154117274da09a43d9ccc93730626ef9a6a40b89449660c03f8285bd03f16652884116e02b876ed62e183f5789a580763efd60fa8feb4aa6ada391b93c868089b4658c8a795fca8e4df7c20a5ec6bce3e63953df8a0a5b2593690d0f061fb9b0e8a13d4626ba5ce5f5a2a5a120c4e9f601762068c31295ac0c2206e6090f3758f163171fef3f1842c8e48ad21118ed83fca30a53388987fd77bfbf0461ca7be92fe4c77ddd27d3e381a87a8b3f93258be15e43481f43975df687af40b743d6c5e87766cec57a0d946a6f46fe9118f6cdeb68fd1650bc74948325fdb5de23901345b6d7958747c262e79249b88b3c423d5eabf767a3fcd6ed80060c1759707d24323f41fab51405edac29ed4ee103bf90c494c6ea381954a2f0c268f69aa394808472e0f5c64bccdecc7b87e8fc34a6cb81ea685140ace3d0eb36cb8f4692786da468aa84572b06655b1d5d488f4c35c958def19a31f7a3aea1fe5e3376f8238b79656b54a668808c7f043e42d4209f85a9d95029702e5ddc9f5ec38e4b09f8baa002809855e6c21c15883c8da6b95acbb99023ed0276975052022109844d216872252b3bf3a2dbac0b2d46deb08fad4fbde0d980c740147fa22b8dc88c8ac26ea98bae55e6215caad98a84646105982c15b879c83c5b6b6a8faaa4010f0e11244c8125e10aa0d6aa80d2b0c5bed83b554f813bf95ed2e54e6fd845ac6106b32c1921e06274280897936a262bf86f8634b023982f166a00a54fb4c3a23609327e2c02c00021fc19350fed96e111fa48912fb2a250b2f0f6c850fd698ef14c7ee9e764451e50ffdf2e17cf86cb1d4bfd56929639387e1459e3f3e4dcf0c2e0f7af233b0c90ce49f0a3d8b26b8ae43c4e2ddc4e59e3f9f27c759afc68c58956d3172c0967a3413a231430834807f85f734459b8552b33b45096803bcad2a1e04079fff6984667d0581ba0221032500c173da12f621606b564b00e5f5f57fa64cf0ed0c350b1357aacff45c35925f29476bb652b8fb606104c07245fa575a5fd00801a03361673721af9f2a0031cc4510a6488b910d985fe560062a65f8833277ecd0a9baeddec616cc5dd8334fd1778dcf4f9b66e71e69c96510f3cf0ee848dbd73d327092640187220e451df2da4c531a1022848296cedf69fcd50f6bab042bab36c68ddd0843d2612ee7c92fb517ac3b1ab37fb99c57d4bcc02d743d985380921cd4d8b0a78063026c720af59e05192c973d36463a5561db939423df2fc5999e89e863538df06fdd8a42fba346be291ba10b16b35a14ca539db0b4a0317a9527aafc05c147bf80f0f39387bd40b950c6b94e84216867f98e2c59e5b53e8c42141a0eb32016e1cb866a25451a0e33444f390a87babfbbc1784f54092a41685c0e78eb9d8413eacc21ffda28365bab4dc0023aa64e725a35d1698995aab2f268dc0f870650701717151bd860b4f962273e50936f6f2bc733a8091e4583d93da75b194b724b78146057eacac9c35f93df546c7e14bf3dde6464f85859d046faa98a437e8a136e10d0df5b95365939fe61038ca788038162c12053a60223e8cec033d986c11e02b17a56e5ed2d9cd7e6d0b45fefefe86a21af0a9f0e50e7afc8ecbafbf25c53f88264ceb4650790abc7b6a5f7cfdc8ed53643e16586f4125ca3cf7af1c88a9acc19e2951c78d72f2af47020c5b600cd8fac92f8a22cf9a270caadc88d1663a63e3289f304f3dffc26f1ee79af36ee4d6d5a5f05fe6d75ff65473d26386727715ca204da7eecc2453a25101afc33901258cea2a825223368c13db01a4135105899ee2a4a0c0b41b70cf2e4cc974c380401f49c30ea7b270aa0d922db7c72ef27ffd4c8bf2a10c9958422f7cbc2a7b94d0439d6a17157284c1e6725a338d38c1ffa4f6ceaba03c1774dbe7a3e0bc283c9b2e44c94ca1d16f1b7ed85cb1abb280b6bd82efb3020671b682e071e8cff8f38afdc68ed8470712fea2b908eab5433159f53314f28869f8751afb970bee9bb769db23d8f3b06a392e7bf5050adcf841c8ac1f702fe0ffc97e3a8ea77879e991765c2a20d19628dc69f6782c1d23ccd5f75ce1c8d81b4694894d56f30613ede0cb37262ffd109043945b9e6facc2238a5cc47f61ffa5ccd73988fa83629c6cac00cc7fb35728253405745c91a11380e476bd01618540e8443c3301ce825ee418ddcef67f09d70bc24f44b826f41db1c6698cfd4b8a046bea4450f0891565ec3b7efc550d01cbc8f300cd2dd9ce388b43a3fe94b0e85816c4b92f8c33f2b762a8521d962dece5df34e99a3ae686aea1f168d201c65c5cd273962ef517ad83c8cf6ea259c75b28299f3d9e9088847dd365240d0c0734b6bd91231d00b3f5776475ce9265a86ca4c13d747597ccc9dd0da897b14a3c5e46227d697a7ec1597d018b325e8fc93e14d1a2de9f4ff22066d7ebb8318713e3f4fd977f9ffe94b8c5305c8a2014fa309fa3f7e410d48b954e88fcff301ae85e1ba3e855458789d3b197495d069dcbe936324c46a5cf12507e2bebfcb98329c3f8f941bd2d8878f47438c00d5f879db370569e0f13740d3e1d798fde52a9a633af5e5168ffc88013dd2af1d397cc8408672a1ada8905a9d0d8f43412eff5a893fdb6a653d768199ff98020dda889ee9081c043db59e09e789622ac08b99c87d03407fa4bb6de3a7579a5d756014a710d591b516df34501135fa3195bcc6d6b198c86e44f54ba4738af34003c85ea49be5f70bc3f1c95296b6e049b8b88b7650b11665de106ceb6fb48ba77e81f07e0bd69a1a30f4d12278900db6d4cbed0a950addbfed611966407a91e0b7461c88332bb0225c2905ea7d2552b2727ac332268ad8038afd2d9685fa15bc6257f25c9423e3aa1c4b4b05db0f7b122599f2d4aa430957accbbe8a243246c78082da2211fa9073155e6e3e1bbd688058941d131c0e71cca84b24d41a9ce4476a2e290b02bb835e86f4cebe786a56429d71619dee7a0c04930af14e4bc4bf45dbb8b19e0a057e0d17469b566f450afb6dad06ad08cde0dd414c67b49bdd490925af05e1d5c08ca5d518957ce39354cfe2975a2803b6e949bd61ab16cd77db6a76c1579dac5e7a0c17165f7641277bf11b86ed5705bb66f1d53b2f40fbce1f4b3e55f8b0817c7125e4618c93b9469e8609fa82ae220e50c32b4322f0ace8bc800167498234a4633da9a717111f984f0719b405b6fe8a3ea66b181591baf4e6dd4187ee0555fddb0100484252ea8c8711728073c74dd47b45748edd9c0cb6ba028f79cab38d5c97d478745e73ee64b9a52c52ab1657a56c75cd312a9df548e5fb084b7db3585e0f0756415cae17b10c3060eb91889d66d84eb365c658c16d856991a33b2d26cae66a0e1367528297d40c93e77ec10425463c878289141df2661ad374005698e2544870302e0655a32f45007fa69cb3499964240c02658fe3735d4907a8cbfd17daa80e3afd991cad35748cd248ed4a973d3c74e716215833df7f4d130283269a48c9e662669c56e964be558c0d2d7bdddbd4734880e341acc2a2a2fc26f1bff619414ece0b6c247799d31bc6ad8cd7af42013d99eaf4cf7341580c909502559ebeed4463233df5606c6e8e169c3edf2a5410cbb0d13b5ebf02ddb823d7abf05e932e93dbee65c6aebef06db71442b8c1c4b0f1ac6098d32d017a05d3cdd837f7ffb335020fe2db8d0fd4073af82a3e40213b5421f9700fa50f747603a8e5fb7f82da1ed61cdf3d6a57bf1efe4e01f40ced79ba5426ce7ab5bd84aa783ff5623453afbf19028615c9610b09e4813b39bc662dde8d5f5f504cf7cf2b459a7f30c2ac81272904cd1ebe600ab92c0bd12b2883eb7164b4d505b2f27838b1d66398d9a10eeb9bd212736d555abae41bb441885f7a070a6ec6b68a951b37bc7f63eada6ffe696e460ced8d05e2bd6ec8a8186409e63b762c2f236e9e9c2b717e319d33a5c6c172d0e36b2baf0c677b868039bef68558d0ea381006215d09174a586def6f309a5d7d209f3ffe1685938043bcc2ca972ec7d4a1e418e706da36a1dbafdd4cbbc0b187b936af694f44962c38d8ffb3a8386dd50b44ac7dc2c7298cbf2219d94cd18019df32477eeb8f7b62dfe99f268b8616e7e7577db0c83b93f2771025280e86ecdd9ac94e940e99141778a53deeef584b99a7eda0e0def498a4bb9670fe149a97305e9c804055025720ca0821082141bec4589030148c093200d5ccd98e49409ce910b29b1821af64a1d473c42e17fa713576cb7a620ee32b3fdcfe3c3bb38c93090c77370547a5e330f368bcbe8d46e54918085506bd7c6ec03db423a7d60fe0db740796d211ecb51d640aaf1da43a753c7ba4d14666da9bdccee1b4379881776473bd994f762fcb4f211d0af5a3599f657408536ffafe15f666b3a4db544744e70f22fd63f446d3a375a77782aa2fae9c93db612bbf508e0212dc1025fd5b93f5865bdfb7fc48b7193399ce21dc97e1b3e839f39cb72d824337b1c926643e87ce835b1cff22dceb4a87874dc790df12b5bbd334f5a471b3ed70c9d44fc673156b727256c3ee0b961977936f068878fde313fb0e1e54bce3085ce62ab3cb4037bb3d57f51def027acc4c8ec5a2863d1fcededb5b38b2d94c322176edb135fbbc966f13fe2aa3e4eb4497df8684f75affc4765eb386969828de234775d3d9f203c60f81829f41cd66a1f1e523f53c702a5fe2e4d9ca33d022fd2bc09d2a517772cf978f3edb8b107ce37a00cd5fa31b67e7bc7a6a6fc1cf48fed7470b5d70d4c68fe4dde08dd4533bbfbc28bd725c3176663e1319e90792078e6b7b6672cf569f8bcd5603749053dec3b0c60ba9861f38cef523974018f38c52f7d7c3e2fae39151f5eedc5efc1b78916ff651cff13cce3da58b657774e2408ee20a74431bcd75ada8c51236e86e0c89f600d62fc1c0d81e6b983b8147fb5592c3401f0ec6a17be3969ba705785e6890c735dda9791b9d3115ebdbb77e13f13e5cd81345aed5d5d9a75ddf41ee8e17c48f9ef3c7e98c8ea57e24983283b868b6d44683ca8bc3bb347f4f6964887dbaf1aad183f2038ea4f6972ef2db371a470c8f40700ba284ed93dc75b7541c299694cdc976beab9b422a9b147a1cdeb716a26bead34ffaa7655379532637ce0aa6ef5905f9a5b229947a10c60d5efe6b51c0a5913cd40376036200da732f84f50952d1b507adbbfa489f58d49275e084cc165a62d3e3f7753a1dd2baab16536116cf8d9a73be3957498c471f407656b3e80a730a3ce280b2fe5f88242cf636e176dab073e0fae4e3e0eff8cc41b097b8225706a2ed68b355626c1c26f2f16bee2dd8f55080fdbfcab60d43d0a5683984e2d05eb88a773ec27d18bdaf3606f1d6eb9bfaf4ed973b40ce05236283cc90e50c98f0f94ce62675f0bef5d522441e03c0633a3e59aa681778b8c14672e7714886313a55ffd32dd99f9b9e7155d99a052b48b7441117581af151929d5c380ae0fa916596c716889514e6925ca2c2d739df4d4d8186d4daefef2d5add409eb980ff670166d286ffbcedff087ef85a2971f3bbf96ba315f9d50bac0c7fee089107e0cfa54abcfd8bd2092baff6ed93b621204b8edf9f3b39adde28cfca3bdcf40b2d06de1b1efe726a4d878171acd11f96c5f4d46ab9da20ec2040fc42a6d3d8ed1a7fe9821dedf2929e3c10f5590510699a2328a046d545c71d1b8aa9a953c046a8bca0115c3daf4707caaaede9c552515ecfb40e7690a7bcd10d70b435545248b87b957a74d8c4f1320c7bfdba0453ee320ad832b2a9b3d9da7215b584ad709469a89ad272663efb4b54c9b1ede8cd8977e7d9e3f581caac677e4189f441ea59f96bea003f8f7a5b22838e38786d7fab8b2a40c8ea29801637f7915ee6731535eee47acf4a88646274d3280edbb8e27706b9c92550e848662ec0daff0b1c5b274765223d376061f2328c817b6bc51c1115e809ed41e1edcd1e63c2c2a14dade148688a15b881ce83df9b4ab7d0149a3c8e2305f02fa4f8ef2e1b5492a8f97a0f82728a0c7bbf0cbfa8dcb4882658b950fda246fd78f77bbe7f1e626492f60ab6da1a02192bd4c814121cdf169af1247bcb0e4eb57280993ffad34213398eef69e547ce6a5fbb643fc658b618eabf3898b37d7a729168068c786049b9ababbbd4d964eb22adc3db0c78c514eb6360402687ac02ad42644b14d07da89f1d07f101f3a42ef5f5e02025ee41e4bbf0031ef283323d2257b3ef8eb6ab03c2eac79a2387126d5b9f4b1c8448131c169cbf3d9f814e5881991d6981750d7dbb5df57ed15da96d5b333a8a1f44d56b0b4325556c44f56111a95e193b7c713f21de7bf791cd82f09e0118b0d22132ff9a6ab406a27c82afc57678c33f41a36c019c952e4a8e0696a769bc0416667b7a839d7501864fa622639e645c764a1b9609f371cb2e18e59fbcbfe1f94b2c1dd66e62038a4188247a2d3d999fc99414b307c83d8121a80364597fb125187484e676fd793e69f9b9f93b480353b8335d7f7d8d3af8319e5ec694568e83799b2ffa553f2b8dd2ba1606a53bbab07a16b2d99431355320e81004f18c73d851504a19764c0fb9e00d860ef5ba38051f6cf9c5e3a9155b5e5fd240463cec544eeaa23f5a3b033faf9c05f2efe42f78ca17c3d63ee769c74b0dbcc8cbb10b199d4b250dcb15e1a6aa0f0772da15690f08097ef239b7492d8a1f698debb94e03cdb3c28369afa037149f8ef4bbba748bf3c47542a97cc6bee1f9182dc959e4a1d1f6cad492c8ea5a8509a0360984b01ccdd6650f8e56328c6b3ffc0c0da123e321fa79e017fd1ed75d84c446df576aa241e70dfaf73dcf8708e62bfb84ae18661daad9d9689e9c2aaf5c341b8a861072cd961f8024576615767c78f70bc63139d4f19f86e7adc0ec0c2339821441b74b9af4de5c1a93056140e5c3bac990e692a75b941b36a33404a069ef2a30db953041beed4c08f4da8046696574d7e16ec20792e87c885034ebc1c272bf7f70b5b3dbf0b6f4aec9b742f9ff569765300c9489192f01a9236798e51abeac7c5cc7aaccf4cfd142ccfdfb37ee800330ab03fe8e5df92b2c2ebf768a463040f8d234759f75f0f96b6555e161be384229cd65450961bcbc5f3ba0196283b1a2b7bc33312d396ef2466b515fdd3ae713bc7a5ea1a7b3f7e8657112109210fec788e5fd705799f8fa0b286c401ac754392d14e3eada14edf87ec881d5fc1e90e3b097a272a56001773c1b111003232aa31d44491e80d1424dc182998d79c3387b1d98e63fb804d8ac59a41b97261b2ca373ca784ab9186e9ac0cb6cffef6e283384d810e68afa646a1d528ded0da549e686940755a125b2f6488ef2a3a1d16628a31ba48a9d2788c519956598e695a983fc583011f136f64794719e7e11407e40d0c7362048550f8f2c49e4130373a2016ebda8eade3804f73c874fcd4d53a686ac7e2545c803340477034d120ef7b369b245221e924194d7f587bf62db6767f45df9613ff3363510db929f8aad83befc91e50335bb13dc8c3bc3baed84e10eed5e8d20c8d099ea33e7a79819e237ea46cb6c090cb120e8c62a59a9bafc45c69843d3f0e7cd6e22779455f2fef10f2a367abc72631e57084d20956a8217c0e0a97792105d830b85038883b0ed2d4886650afa0582e16b677c4267ba46bc3571ed7491a2ccb28621892af552c4425ad933d443e878ceaa7d1b70e16e0ddb0c78c10c63602d804ca208e3b348a987e44d36aae5f02c0a4a505ee5ca9d8c8bd5c631ba0ef37f4383aac774b6031d0fc0baac33b83e95b1854fdd261e0c45cf297ad9c931e4c04100a25b4289dc5959fd48ccbd4f426e0f8f7abbe16d32399f0a1c6325d3b6cda2ce53c73bb7f641542b0ef8bd7cbcb39c4c81831c987d7ab380b40e91813c83c561f098c6fcf7f0e97614476c763f0950a7448324e155e76d4c6f4b685583befd23575fb8684434e26c0edc04eedbfe6a8bf2e9a3b1307f29230005559742689b1aa1d6414d5b422e0c86332a9c5a857c6f1a8c5b911c85018d6b36ec6b5c83aa75f9a90c6c7c025e88d7081adceca07ab1613cb4611869e1d622f3b33d0a5d166fc4c3b4f7b42ec46b76c1e34305359f33268d43b3db5ff2800889d8f43b90380b22f19c07c7c38f199d859f41e470d4501c5e29c51dc9153b76964a2cf7c054d360e472260a363f5dc859ef68eddd7e9ec5c1cb127396a517be0b18370ee509558817a622035347efdb1935ea02cb9e654e1c1ecb08eafa78e2287c60c9f4a946be43f7d14adf8c303315983a86d7f4e4c1588a48064eb030cc05c2b66eaa3af6aa51b8f32e92f2e6b5fc55a2ead9fa72138e2c547cc18ab96ddf1794fd0bfe13306123fe54476b906d5423cd05f0dd72b90841afaa149861801cd2cfc0895d1bf2d0a2e65453a2d2ced8682e78e3d8a76255da354846227be9eb18a98b346ad7266fad0b70c9a1f7cca00484c79cbc383ae39c3c4aa1ea7992ae0e40b0c94718b95171c0c9c00273eaa24f81aa11a341e6210048bcaccb07b8c73b735ee8dbd0796706df803e2a5bb8b825991cc2dd4da543c110b6ddce16dcd2b81411f4f1a3b5f5dd1e5f7e8613e14dccdb32c215230d72a949b41e5eed74c1e2f330bc812cdb81211a5bc16b02d97a547bb3d5dc3b1521f1c308cc071144d6d6195c460c0080c54f561fdf70a5607f8a7090edab11e2dbdc60dad6d8ba940bd975438dbc1b6c0b595d1538ebae52b3860f569ea786b75cb7e4067754b2479d1ccbf9886dc2da6cfa6080d14efa3dd4a952d6fef83643ec807b383a9d65540e7f0252f8456e1095527ce440565152fdc2bae1bf1da201a4d2905021e33ac0692e300ce5acdc4cd316cf9296506fc0c3d1d9d1d0c9f936d9c0bbe7515309ce20411ef4ab77dfad988c38d96c74b1f0efab33fcabf7eba317c929b71704171e13a108714b4f45e09cc02406f961697747667d5db00adf870a115ad4366c96bd72bd009f6a1a194d205efe8adabda68d96a526d1d242856f28d6ba70b9fb7142f2a15ca1201ba572d6f61da6836c2071535accc9ffbbff37b71e5ee5e5edee8c941ec8482ce04f4c7fdafab9978fd0763d32888e9803641e18e78dafc0f89791dd08f4764140ddd6cf2855cd408c6dbbff117ecd2e82c05444805e57f703ac8222720321cbb4743efe56a76ccecfc226e4b287f75a0e58209f974a5da6a4e5d0eac8c74957dac09960ebb102b2b05ed6738adb4c24c14b24ecc2da436df960103bdc5d2d39e5d032fe162d205b7761cd49e966b83118f74bbf010cef8286790db3396ee24ade17720f2e2670b2083e1a069941b48de58a1872ab0116929b5bf1fcc9cbdfcb1d61f22a0634d91f48c056184f9c99a92d47c840469d32dca7c0bae9572c9d082604c761f0af30c1a45689de4dd4482653162324454e0d226407e1b8ec08d57ea9abe7eb3af3d5768c1967e091af01da1edb98c8ee581a6a2d3434328b5aecb9c66bff89b60d988ff9fbc9488e514e828fa6fcf1cf219f3d49ccf2805c09be8fe3a3da368f5319d4d16725733f812ee8b187621449b8a1ddacd7039d180022692aa95ada864c78994f97fc7220eb1ee1c762dc9e4910859c5d5fe7e133bab771740a063f9f71ac03d32c138b13d2872d569b58393b6cc5734201ca4a6820739df66ff041944444f4bee59ac051a33610f49f9ec1e857b61eea2ef39eb73425aaa73a69ff871be0d795f9efa9d52b7a8d40dd1cb2609f1e3b718c531f416de2d142ebede5edeedf53586e907aef3a54b333579c6b677ee1f6ea111aa616ac05d6892c9234c4f348de55abe4633f1ffd9fe1b55b59599d2f0eadef785cb0d419779b2e652d1ed6bdef3a9d57e2f4b98672ad88a3583a936e38580fc2dcd0ef8311f79ebf607c8d611d9554674dad6c27e3c05d8d318a1ce1f73188d9d327b10fce1a709c2368b6e1049d0bed51e2913dbc2de7d348a777449a5256cd1158d40ffbdbacd222073591f572df5f1ef1239254b17e50f0b7b0e88fef73a091f732733cc95d69d319241a70422e1a6525239ec872f0bf670b7337f5f785c8efad9636a666cc0046902eae8eb0e95d6e40f2c023d5a405726a77d1a7ad3ee9d6344ab4b48c2bd441c1b83410bcbf7da3a89fe3a26bdb079c9199a4067c9616c411af9cd97309b7122ef1e1fc7590268bda4837bff7b4e4d9a403b0cf78bd09ea63d5ae74df5954ea57d6d403cde7415d33cf365f921354f7243645b9de900480f177f983df3e6949b79668679552d1870c5968ab79bf751b1337c6eae8089ba8262367c4867d8ebcbfc3dc470f673408da133b67ea51bb93ea11abccd5f9e7735312454f604c2a692578ad619e8ba324dc5e849a3c8ab93c8466f0ff22caf67bb8b03f743af0e5830747c67be40c9c28ea584d52f69a869dc0dc14516a9a2b679244b5b70c1f186443f3257b6d146be78958decf68aa1b9d2de88bc908746c2d65a31a02dfa4ce02af64e38a295d83ecc49834e2f22df173c107213cd41f95e3e0453a6d536599b52f11941ef547b181e6136390ea8e8d4824321dac7fe143211af79e4903846767ee2f2d6a78433fb2af5ae29c8c930d2f16616d569d7824dbec08bb7108e9c621d364af3dd3f56bc7afaa03ecc232c0371532dc033fdda996a5d51069875623798dc94b4273ce6192e3a60bedbb3c0c7f2d487656edde1a2cad5a8ff7d34b27dfc579c8de7448bd2621ee1e47ac6d82d6b8eb90576477c209541c4f6067e7a0aafc1ac90035032d82dafe55f29264b4863ca5ccfd383357acc6b9a4c69792b4e14e30aa08f03854509e9040e5b1fc12e42ac6fa66c32324352195138f0880d86dd83d12b0852c75b5249b91bc339a5ed86f98330d064e11b449be44439a0529b556b76ad7c3405388971cbb083eeadd1b07b67c9fc660e852260e7c6bb686a2c095cd023a630a5bcf38a51cc86ac7920d3adbaf9c99a44b61033fb25e53366c67f1856aa6bd5c574c1b55f43f0df311df44b9e567a0350bc32fd8699f1ec5d2fc03600a2dce51917278f94f71e429fa273662ab5fc250af96b6854d99075458e225c1cc0d2fd5435e2126fca7aa6ba7ebf8e1a74f628ef71b98487bc11d4a3810492a14f09a113e6d191630d1eb4d44ba4d8380844e851e4881f208d21176c3d91509148958c6b5187b140afe3a8ce1380ad66c40f17b1b82a876c7d71cccf2b04b588de6fda296e84a71bad0c228d08ddd778a89bf075ed3326795343d7e0c046479c779d8013d217e559709f2d51a02024be9d861ef1bb072e973c62274c02aa6b6a49502537e90913c0e17c504fd6e6a586209b0452e6fb48ba97ff8cb162aade0f0d8542de017ad79d37979ca42e03c8ae5bc556faf2510661216cd6338e474481656d039c04d48eab98a09f7ab2c479718bbb6624236832defbe44cc122f5c36c1a178c1245b09c7bbe06ac468f18b085fd9fe21ab917d72c92f31e4a5be48bd2490750084eb4ed06a9a9d07ec6bcb4e34a903f013e7f429f25f71451659edd2f975ad0de32e05ae3364bc0979a6aafcba2692a36c79ea44041ec40d0059d5cf73466e174610de40335c9ad48167a0f7512d97a2b6cb335ee992d83b639c01d9a8fb01a9cb9598629172829f8989f70a365bddbb97d44aa990c5947a9ac07e671eefe3c8682017850d75f460afda209e387084ad8e11ed5cac5ec3eea25bd24443ae1869b030d6f41a6ff573048a19f45c463c7d122ec16bd7e32631af79e16359464da9380e800373dd4e5e844a5634bfa129e015663540ddfd8b4da3c5b9ca7f01131e151dba39a208cc56adad9cc70182205ceb56b9046ace1e2d6a244d548cfac5aece3a0077990b9915d3778b7f2441380f9509fcd89f0af3dfe54d44d05a31198716265637d75b4c5088be49930b08333469253f64dc15ccb9b995e15433d0087eb869b9e8d4dedd2bde5d1bb4eecea9b20c740f8c190f076dfafea718a3667afd9d8d9e2b2ce668ff6d346685a307d44584684982ba84cc3e8a213e005922b7777c50d8d3720fcc5649d9219d065e98d212ed5e989d10f96a7aa5921a5ca3f362c30c89d805e81f96922686377e4bcc5435b82ca75f3587758440af27687a408a0b6a15625424a9ed21c1ef58c7162d7a703bf5e36c061111adfee4c099351537b5d2a355cf4be00cb17832a03893de4543931d1f6d3ebcef84ef61f5f609ff7fb9f85d2793a787df83604b88b2ff7c6ad2f6598abd4d39596271b930005e23f602d042e4fdfbcef9174fe0c2b061f35472db3e97f10ab09a1f4dc3565231b48d39e51e002e5cd644a08e18db7a3be901b6f52571dfea13e6c7cfe179b52f8ef6423e3919905bd5e04daf08827c9bd13f6e1488cd0a04cfcd850443d5389c4ba251a3c06365e48cdc1ea5c2af27d262eab393745d1592c211b8dac952a930c3a2e24707537033da6f8d6c2e8fb07f3b0b0e6caf8aea09c8a105a8b99c2575ad3a1a4d7667e7344c1e6bc1d247c65f8e9770fe0c7a9951b645167c9cd1604abce2fcfb9b30dc15cd9993d5ece2203dc7a0a8666ce3dfe87c2ac8a6fa67a684e012f0a9bcc5ff25cb334f4fa9a76ad3514ff5114664557a32bc55643851f21ef644b0992d4f28e533b3ca43f8758cb88ea7f989d96dd91b965ef639ba60f14b892ab6de99bfba5c437dee44a6b5c485be7cc82d4a8ca08bb0de5c7987122a566589bc661e62792fdfc168c0fcbdacee2add9789f2e5ebc0ae92d0a235ecc7873ce1b40bcd74d1c3b6d5598aa0ed5b5f5007e293e395f2c2921a6e7f446a836155e4cafd075b2f3cfc60ea07893ef8eb40b9d59309b0601e3df57c2909340ce8089e9ed2325deffa7b310cab43ece4e4d0d27c773c10075228ab59e7f8182965abd0dcb6b652050b2d365e7b1d05f649b19ce7b62f47ff95a31d052ad4ee397b2cca64e292c6cb37dacb1aeb0f547ef44c998b4b1d7980441e01b7446e164fc643aee74afe9a6c5a8e69a0a34f1829750167641f1749da69f44809109a2a4c5d0312929049f38f9929fb7191ad69eb2da3e357757973d86362bc6c9511a347ce88106bce57e35c4dfe9a33d5d76a1a6d7cb74766356456b7a15a66b7d2e483647cb594768c48402704e09206985cd644a3e61a51bdf1c1f451a55910932e37e1d11e7e3d60929d96a29f93c7c382870d29deaf4666f9c6498becb1dca7a0f03a63998377b82d587aead568928700b483b71483f53a2543c1eed3b9b4bafaf2d45e10ff99da3943aa77ee5c2e1a84a6f0578654ed576171308d00096ab25c93f244ba3104662f94298bcb6a7fc1bbd7f3bcac219b574146ef64bcf0cd1feac4b74bc276102781f62ca04eb2c6d085012e0d06a2c2412ab1be32698f304d73b0a533c64920d8b33530b65e16fce14b4d615f1bfb236fab226c9c90b9448490db19bfe5d529963885ff0938a175e112d72c6c4327cef3f44702cdd7f83ba63ddea8565b001d6a23fe51eef3471f828f5568faed064ab3e2390c86882b5a6ee8782f0ec6c37d98acf968e5bd613da46c36bbcf9f9f0707590a19c28a509b27a8bfbdfb730032a64bbba14471ebea2c398a506612b785922597ee76139034cee2ff3e74a4d1eeb471cdd7f43068bad806a26c8d86412362793c7823e1e500fc4495399b0011a6988eb76f3be8047a849951eb253ab9bc2767db88e5cad1100b1fe331b68968198a2e033211de0c804009f95c409a4e13210a30cf12d725bae62b6f6ca513b8e7d9ca8eb0cb9b646bb178847fbd01b44b4fe1e6b15b0ab7c2962874d5fdf70a455f03d2e4454163e89b20e6a1d8033870e1e177cc6d856457c3624dd76227bf6b5d4f0c2243dadbc11235dd58a95a272903af1ee6671b636d8f47e90bc36d5c6e070d9067e7d81d504f566d040c3f193aee5b48195fc5910ecc4926d1ebb121c2d451d3e99a2386a69104bb5c15171ddad5caf8e09d7a051b33c1db342cc4ec3d81e59ff6f19983a44ca609a48d86a053d635ea49fd90b0bbe55be786c28205cbdaee73e6ed957f8e93b8f2a9209555609d3346a1b044750221bf76b3d1c10504ab8aabb28c0aeb89fbacce4dbc40afc27e0b3b9e86a3913cbcc9bdd343045e0ce24e72c6f52b6852683fd9a61533e5994c107ca65a0d81d4ee5322c0f7168e4c2b32eba8a7fd783eec9ef68d7f20950595988bf1f857bbd3e184aca1ee2e7f5f71fd30a1075e59ef1103345a8835806dfc6a8a246dd79b7551c26a9b16a7628dc65ea8c930abfc7a7cf04f1e475664e1373cfe6ca0396d4c750d114081f22d33d7981eb536e60ed2d3ea5fa72cd046c5fb110fddfb14466f0d8c35b2658eb69742505e7d1d4161f1d4c1b9af6e373fc984baa35f81392bbfb56c201ae31e2b6fe2a22680802d51d9ef98e7ea830eefa07667879bc93921d4bf4f117fe0d328d1586ad150b58685eaaf61e704a2fc6ec854d19ac27a9d236d88a1fc310c23255187c88a5e2f7684a02098fa671d68a592e2096466c5b87191efc3163193123feeaaac57b4fbb7d78352b01f5ddf29563946a45c325c74c70e2af45167619af91d10088f0699695decdea4b41d43be14e0b0fc95c150e122d56dcb90ced5efa1ec926bcb5eb55287c855f215bf7dd0159a7cfffab8b8f897ecef49764928f3792c5e100c8a3fa3044361276a2b987f57624a509b2a119c12a678d25791101d182af43f1d44749055116496fd0ad78598cfd78bb185d0a5b02fa302f3559a5f952b9ca6e639da088792470e04ce9d76c431d94f0633b7c9893c3a72e01289b8a7573e60cec814bd2e2a553eb1728a9f629cc9324f93f7e440a11a39a437bf3ed1ed4ae684904cdc4fa3546e583adcd55d48a8d0ad228e4eca534dfa0cfe35dddc7b817f9a01d9da79ea45cc49915a2935d6226b7e0b99417c55e29c070eca6c394f3994026fdffe5382a05ef7e348dfa260c790b9549d5ee560c7a83c1ef7f413a46885df2a15f14d81d83d4fa51c709d5ddd62a34b6a8d531e04c48920e525e98875cf188e8c800876a3186d081472c3e3c5180eb3f8b1dfe1b2bd18f271262211601f7b0a7ed0620c5dfdfcb981ba7a33b8b10af6995bf36b19889b9938a460659e2454ab63e1e0d423ad0d984d4d3c0874aba21623d301d12f606fc3296c427dd4aa1bd6dce71c0f8b31b1c2be774382b8db3f518ffa883b97da0dadd6dfee0a647ffaf1e89515ca8a6adeee729e35a3a3b43cbd4826397b4b7b627e41ca18eede322eada538090e4b924104dfa67909d1a4d4941ac4448d911e21c6068c8d094fe3359bc8a26ed66962bf618c6c93f9177d5f93527789b89ba3d68a9e1b467d3616e26af40dcf7e8e71e6fd1b88aa1340ebea1c0e8ed4400f13904be8e90cb4af01bb7be6b8843da9f878948cd705f2db20ff131ed1cfa4695d6352b78a2d9bee47e45f19a30cbfeeb561853cbb0ccd307a5c95c9824001f9e74ca4cbe5ea9448f3ad32cba1087603c3627ec1c11ce2d2a8692240d650735dd67199fcf92bfac80b38d535d4c40063632bb63ce185a3c5aa992b7a28b761f0683010d3a3c058a07d8dfcdf7e9714c9c639cfb4de8a8ee7bf4efff8c5fa8ded4d8d5bf9e00efe6edc2ffc87b886a071b6e65465192361c76ef37897f7eeb2d13fc3ed6fb02bf2b9b7dc973d4a5239cef90bf5e60fbf5d6fe601963d3d39fc92451d5fccfe61d8873bb2ec8e386d2752f7d3d64f3f01b90f6a8b13ec9fdef7842f7d7efeabb2e6dc7a2279860fb434b71fb32eb73f0f199ed35e4832a03f1409b38eb0b76df9f885aa7d741f777b24feac604b4dee56aa9412461c5784182c78129ade4459596463f7f53eac7e8eb8b15c2747a3dd1a848c6a5ce7974a54d6ded7b739ada83925ab0a1c7cf2cd2a1871513c77fc80f8ac1c8b12ee425bbbeaad522e8670902a42a597f04db17ce3394e0a1423ec869dc9de9f790da539d84cdf51c4b98c5803a02c6141d1b42c2fd896e4cdc9b9fc2854f5d67da69304f70bc3c4dbafa5354a0b896c60de031a0fceb8d8c1c2bebb7ebf1feb0d549dcaac1ed75ab7e4e6ca0842ba63a95fb17ec222dc836f37171981b6b15f6680a67bbead684b46ab654d35843f036b0a51c24a73d0ad34104b473c59cd7f6083cf94079024b5f18d50b9515dcfb63b4440189659718bd1e288348ecc7df7d80a9e8f65a8611049fd9d8ce67e831283a524fa8893c9d0d907c05fb8ea777c015a1b707008dc17fd1c2646fe9e85df960eb1e8c00e6dec6603ad15190860e1f1cf1f61a332f2df442b7a48e59fb5914ac307fcccd938ab4eceafd708bf0fde6d8017ac1d2e00cdc3c240978173f357d0f80847edc43d1498c1368e8251fe4ad885606a0078415e17dd3dd3ffd23d1385c5a02fea1688941bd1dd7e547001ae785bed2b7d02b117b4b665ce3306a37af2017009b41e56ea7b23e8a7c0f8ec0766a690f0d8dfb105156ef8042413af70ae21d1e75bbc6cf8df1017a144d736241c745774935344ee98b7c964c2e522ef4d2f71f7560704a505b14fb90e5b1fabe63b43a17c3c242dd49c46135d9cc8e1e118d401b64410fdb89d04b851870846e68db8351733ae52d52bac06fdb52efc13c8ff37187a755dabfc3018f6efc4fa1fd06aaf544f0f2333083af3dc6f335a957824187d3988ba57e605e43f68deca71724add64be8cfb852099ef7382343ffc9fd367e881f3b0aaf60f7c4d7d5c7b38f687be5027a7d675b827e4aab8856986bf77858fb039a83dc9cc4b10d281a01b026aa81cfd5ba674039c9cc6301ea3a7f32a4f28bfb059bc530d5ffaf4192a0a9eb0fd1137380a195ee1f3ffe308843862a6bf97e4a071f00791d7e7ad63f83ec2b22e3ddf41437fe96f1f32068481d3108e6b47849641eb7dc8e969a596fa374e1919517129d9d0d6d78056508c39e0df6b9eeb2982c2d98033193aeea3ebb47d7ca0663caddb4dcbd63fa8f6fe4e64427bf2ffdafdf4b89bc2625f4e9fb31e01e894321a337e3cd977aa1dc8fc5fe3fba2d504dff188d77e898ab92cf566377ff837fb106b8bf986c903053ec44f263acd65661423d30c33821061f32e594110ff80588f33315b06f6bb1893c25fc0a031cd0cbd3370f532c5572e06dd70ce7f3e9a1f0595c6ec10f5de3b2de433dd9c99e5cf1c6ee3762f30dca011f14be2629f56322724fb5f885e3785914a1bcbd017bef734471916ea0fc99cf8fe21fd6120ba7fcc1466ed1d7d570ae0e4e5dbc7602331de15d9ecdc821d65d79da065a8744cd47f37eec88287f9a1f3e8db1fcc7c88913afafdc66298b6f126fd1bee2fc3b0735baaaba2a324d607ba179b396e830e238d8d884e8d7bc018e1d731170dd447bca53a2b5a7bbaa8fbcee62bfaac97eaa96156d67395cd5140e54c2d2a2af27a7e2441758528d65e5ec7de319fcdab11ed2500de72962e81ea3d297683045dfcd54856393162bd9fd2a25313003e10d822011f3d30df0994a752c692c6233f60b2c8b3c0529bfcc878685bdfd1aeb6028971a0b16b86dfd40f6f34943cb19d1230e2ee8be4b346748377b4dab8de3a2456c22e06e0c0c72a54207fb0896fd02538d305e53d20b0ec8b7c8d506cfbace3c43a778f9e60b076ee3f6769cb070ddb077bce84044b24d69a8bdfd91738f48b62d1cdac43f20f7f56ec55794515eed0367dbac7d45f2afb47c04491455d41c5be1b78c410bdf11d4ad2a53b4c854f9b111babb072697727aa0ce0fb907f1d500f8436b57fae4511fb63eb9f84bb327cbdc939476cc577c11e6b4fb6c02f5c122e541003e2feef34b763424b3f66b9600dccd931e38d2475b0edb929f1288515b644abc3327e2136944d835814498caf7553c3f724d4c102922a772db3536562766d0b06b8453ad78d139d426ea5f882d11d24bdd728e1432ed77c77f3fd55b740c29187dd6f2a12d94a1a9f096f5cdbd00d754f24a23efa621338ff70c9aca84e4c8673e6c20c630d3e315c223fdf15ec03d1968a67b7593d1fd6676c5b586a40352270eef173badbc307a6be314255c84414134650469a960f0978dd09b711ad5d1dfedadfe916503e9c7d2b31b170012a7192916e54d541ac5ca57a4571c5aecdb0894084d0b95fd4673c630f7b5d55681174526928b8a9159fecd90c6d8e50c5f2285c1a10de52e5a3b8d273e075bdf8eeaceb75da6ad8120a90e252d9880271d9b61469d08c8fa02b4ce64a3379c1bbfe1fba817488ae9373e425e0f1974a9147efa9ead2d4105662eda3737edea4c9a63d17a8e567aac50897a0ecd72906c28dfc2e41a5df5dd61c17d7f6a60604fad402c8cab492d26f551585c095ce39c17e0fc598a3140f34aaeb6e486b2ac5d458a2f1cd1c54b503d34623dee4b93c9dc02fcfbab72f621cb0e90256ad537da4a4afdb38ef218922723d1551b8bd581027ac79c5578695975b096d0200d48b0ff1aa6548badd57fadbc6b60648a4f8f4ac2699d98343e7dd3d0632c2a0cf7a6b3813a0e21e8eccab9cda6a91dc71c3c42f3c2f564d9a998b107b93211ba01ef7abe7bd8e991bb15daa16745427db628ce4cb393599bde748c1731260609985c0e1b3dfa512a42f051a7510dc3e26a387ecdf236569ea6cb1b469a72336489fa5e9576c3d93fbb9d7e4b44216865aa75afd9c27c3fa497910acb90d1e36d3e958fad04fc2a4f33e25148ae183579c210fa8bed9aed5661943f52dbb362e25b036aee90ea2a23918228e466e645d878670c81e4b1f2669c1cacb0585afb9db3b64b7ca2f948a48d8204fbfdb2994e1e87a7d2a8a7bc49e553f844b86cefb2a414972f2a95b6b7dd650b18298de4122a258414464a5df39e162e4cd4d4de5aa5340605813ebe403367d7f157a93793f595f53aca8e2a4d7ee4074012a02cb5323a417d8eeb494eb0cc17033de1d08d65c4a4f9ddde4cbf28698699cc94163a910f1de124935da8a45019f05898fd4fc183863573b1f0d67572e29f8b97d6f2472f8b711b7cf173ad7f50d3a810e506a2ff93e270281a5ef40dde744ca6ca4b71e811c76d801457935455bdcd5f855c3d29d2a7d3b9d78b5c48b88df16362af28d795643c440b4be67229f7ba80c4bb4881cd168195b3a196bf2e5531c60d60dc6793fcf3866d691cf8cae131be330ac3c01830c027ccb88d1527d6425fc37f48b27e8afc958e277c4113910103c7d274b61f22b4a04f7b0c975385979fb794c20dc9b2ca5f8cd90be81aafc8448fe77ab128f429740b51918408ce4b4f3c05423e52a0347bf44ef670812a7a9c1a9bb93ad9854f1a5af49d0bff294229004042f513808a35fdc15d07533ec9f551ab32623d2dbfbe12029110a141e179e4d2efbbbd6e4212fa114b78ee6ad47d68534d195204d625040354bcfc573e98073d8bdf549a6d3485c382716bcfe1ac369898258ab299ab2c1d1ec2f3d79482903038841b46663e68ca4d6a4789f0ba25598530bdafd0783c2bbcff336a54a995f8f67d092546acc1150fcdc49b036bda364c1042cd0a88a19b9aa890001178a8b63edc7b773707e26ffca4bebd34c8cde6362a4afdf31eefb46bd79db3f49ade0b7a5a9e134ec2c5d0104d3848607b04818f2d42c7f07749c4d571d550c5023442af41bbb78512eab19f8f7674a126fd20467509033cd228564d178596f90d0c55260a20e153a6f5c6da7e1cdcdbc5a192200bfb1715311b7ce8f379c802673459710d3d2682b1ace45be05d605fed6552f7f77a6de7f6eeab7da6e7ce8f78a69d67a3fc478bfe9d7fece3df9f8a13e95e0cd73cbd49b3e371230cde67bf52a34799f978baf0b5ab12e5ffe67c2f027a8cb8d59e610e749a61eafd76f165cfc1649ef71f48647b7061e1669de833b89d8b5cf95e3384ec121a1b741b13b7430ed13e5307ac34fa1fdde82ed4f34bb3164292f40a57dba8d8a34fb76816e746a2c1b85197dc3081273fa7d23c6d296ab2218b76353f59388064cca506d4d95e486ebadab684e182f53a84d851e0facac28a7863c485c039e862ef22a152e74c17b250a9a10db26196755c0938a193ffbcc80e62d93468ea1998481dc5062a3d89ec69a2ebd7e3cc9d739145bad5a421896eb7db228eba4d5869885a212a382dec9184fb2bb12f43d4a175414e6eaf22f5c77ddb3488e539a920284462ead356142bcf3041cddcbeb22cf6a8f9c5261f769f22b5cee8f77b3a0580b44a8f0a72ffdf1251abc5ed4926ffc98851e150de81ff441238c21a8bae46611d933c3fbb94fc198502fc0d639b45bb04137fee531b1cf9ea4898b52fe3ac9f0ef9004831cd34490ba5b957070f302c8fcb117076f67ae27d846de2ebf9bd1ac7aba319d39f3b362c774a666225f58652f7dbede7de62e5d5d0858dbd5c90be6aa5dda51889584019af8a9ffdf3023f686ce85acf977a6e2cae426bfbc516c432f6cfe1bcaae638808e992e1bb47b3fcab9b00c75a8173c74f589730f38f4d6087b5440f413f12ca5612e095125ac4b300e424008c16f6fe99898d129bf596bd25b17060ffb061b3b00d5c43454bb21f063c7b03d062cbcd6be4ea4a86b8deab28c7bfeb87be73c8a8959890216864a6e34a58ee9ce02d5999ffc9decef9cfffefba7afaf3d92b490efd4a83c3483591a2748b66228cbdd1e7420fd46f1d26b0d8dbd83601e15a5d76087b89fddd684d211da4fc42a611ddac22dcb3cf6d6fea51dfc20f0cd3c3eba248ea9967fd84c248ef6b434b16e0b50447329e1ed4ad799b1a0e1e89dd434cecae57d6949670520c67c9398ec2220a77efa2e1893420bfd9afb9d50b2d8e5173236941285a45670292538054ee4fdc9effbad56206d54a78ff9d12916ea0f1a44f8169e209a0a3ee0fa71fa1153ab5ac683770a1d48e59da4ff59a8feb9c364f7a99d25aa57cbfa45b76a010cede3ec6219965f0550bc1443f8c6010a8298339343a0da30712d740cbc81d24fd937674451a1993bbd75aea251f0b43e76a4333aff96ac2b34df28ec2a9fcba1d5a87884ee61645558b3074812f4f53b0884ec568f5d8517cd01cade1e903f784c6e78869bce9e2093253f6407452ec3ecd9657efd68191d648124c2392f0597022c91ff866a946a2a1fe2cf37d61b99ec177a85efc6cda315b1b3050bd70186123a8d1695e5d927ac39733d84024910da51c09a72aaf082471980db826fa885d6aafad98aa269a65cab6b0b2f992940c0fc6fc1fb6ba1e1d79ca2bb15c0580bff044d6076722dcf7f8cf5fba79411e6319ac2686fa8d19298074af70b8bf8a241076ce88b093cdc27699b66127a3031be25b6d82f0543a5006033d02d5678a3870635f651351448ad649e90092cbbca12fa80262b42a9c2903454e30d56a1aa586d20f2494347a5738cbb5aecca0d6b226a7be8a595337738b70ffbf523f47c815732b0c02682e417c3bd3f3d5984cc04daee30c98b5d497234c12f598c79040054874b3ea8e01d91b50080a498c2025cb49d590dd207a6ccc7fe26321814095f28518458d7016925b4b39413882ea1fb1b2580e58fa7e69bd549e1b0a1648440ede71cca2d68f0fba44283f498c3c5acbbd885a187888050677f87728a62493270134ee7ab158ea1da10e82f243908e25e8908885a2746b8e448d3a02c9c3d0026f36780c81cf052efd83b1c9ffff815fad11688a308ce38e52c632eb9e4cb59c26e22e8a01d8cba3148c1f7af3ba17597cd95a3c9b78865fdff3ad553d340782be52f929d83f6f4d6044ae82731e51ddc9ef43bab1e4ae9a22bdae4e98c7bd3de47efaf2dfb54719408e404da99e5c834b5f3b8183fa553fefccb5f2bfaca3a4e5338a7810b4fc1d0f97a13403c7599a607bc7bd121beb627c83fd37283487df756ea7d38b8db9fe57ba7f9ebcc6cab3ecc7d70127a0f0e80a111b4309ee7a6118085cd548e8b797bfe2c4570a9ebdbfc2c44cc8852f45d3f7a7e6778b553f42a24c6c299b5fd959a111cb9c97b73c23a13dd7e22b980c645e40cdbe23d0ed56dbc18b95d03ae0e698162182c9f6ba881a4849681c3ada9fb6c906e7eecfa39e3d43780852e42121622f84235255fb3f7db690db4c3d1fb8d86e8b8c4eed38e05864fac37d926c0a29c4aaee6f316e37bdedaf8eb1fd3d957b9d0a24197a3539bc9bc76bb314bcff9e84db94bfded0c195e28254e6042e6a563e8bd12793134a919d48d012fce325c9ba590aaef758e70bd43757a60dc65a6a9138987008bc14718be687ba42bdb10654a00bc4a644f65e00a61238e892a70ee5d56d89b1345789a6f7e5000c82e5f9763385e62e1cb0864e1a2e89a7be3073adf92c4e8854a1b336c8c5414a768f3ab4a9eba29eeaefc1f221ef954297e597ba3615d3e3792a332ac892a0899fb259039fba984484cd435c1d8bb01d4bd3e63581dc277440eb28be7f1966315312ed1500e9b07a39ba5e6ad63ffc146f1fcda7aecb8645d2a29a7a4128243041bc1bef1618de2f9746160f47c28f6852e466e2558ec0f27607e9ace220347b6ee85d443c8865c1c782c88f2cee8556f6d37b7a1de249d3a3c52ad60b4c7fc83fb3124a6c7da2a1ecc2e9a1f0a17a5568287e0404c60c9b108cb381325606fc88043352579b5e3c11e84478e8ee72d9cd989e4ff6e3aa1234f7d01b6cdc03fc7c7e1ce17bee16878301c8d2de0f64de9bedddfa953e85570aabc00df6c5e8844c4ae92b82b2001c16a60d582f960e73514e19a1d7d97fb1cc82ebeb0acac2c1960c8144a571076c28caaed6cbf89f803a96254ee3588fb6c5169025046de87a04460be98b2f22da176a460464229239549091ff904654dac96fee466eccb16247b4bf75b0e4fd1feac1a67548d055705e75f82921562f76c5d3fe9823c76702c33a8b0dde91b4e9de9541882b6dccb1d4bc1fc8bca7d7bbf5e930f94caca5a7d455be1ff3a05b3d3b61d6c5736b9ee8451623d443bf977f558f425bd6b9ffd58cabef15e5f6e491df483d4e1fa493d5415ddfe79e1de7b0a0030038ce77a30822d93ff8605625319f6d363f81d237836a2ea2eef526c53c92d7a43100f0184195fe42e0636c0d9e7dcba9f69d905094cf23510a63003a30d874f7aa670566d89ecfadfd4622987d5e4ba5223828cbb662ec7c2fbc881f427c354261d028688879378dd18a404fd82784e63b983968f3a8af2d76ac10c66f8c8cf336bcb4292ae1bcf565d0ac5760f324bf0a78084515c034b32c8d71fa53f670d0cd479c43f16aabce9febb19edab9b60eed8de41cd5989a2ffbb5bbf1e32936f805e0e3845abeadb68c05ab6b8205c860b73f76572c45da3cccf247d89249ba60d3c72cde0ab3ff9ee1b025b3948adbc6e02179905eb6365be80ee3954ef3a69bdebee464ad0f1a49584dbc88c496ce703b18436259bee9eeba3596a13ffbdc222103788ab1d322749016a8879f4840f52841f018f87f09efa5dcecd0e34932d73fb92ba4b9580261d82c63eca0c56b0f1a6d25d01b70f661fc0f7d7ddc84f14d0addddb46d8cda3aaf5c2a47152135280cdc9bc45dcaccd52714e209f848bf84d1a43c37b8fdf9f834334f65e4ffd1f6f18bd3a0479d008bd179c9ef18c8baca0a2dd1e4d79d85858304dd3b61aa7d5a69b3910780cb355f8d01f33e13f4335a95a21a6729857bf1b2397d9e2ad270739354cf8c06a3137d6fb74b872ec25ed45534881cd5818e044ebf5b540210913abd90a0b29c28b69d1856f01e01eaea889ac3f67e058ea8d83d5395e3ca98a89da33eec58ef9ae016956c3c913a647632e87bc54810966d85442c0050325cc681b9122a2fc5b842860491fec9eb277a0dd3ad7958d2ecf91bca76bc0374bea8a1caef412040258e846f2ae86aae6edf2c96c7f6df29b9bbf1fe39249cc791304b4b8a886adcf68af96bf853c6df5a1c3d48289ea36041113896880178dcfaf39f10422de08a79af3272a95621dbe26231e5e5e9101d632291231d127fcc4481f6238640038bd525ba5dc0e18b57661569bee11712711cd0bc4a70394f55207e7464c7caad5888e680baffcb2810c7d1e95314e322b2dea7e024132565c788b293162ed4b586cf327b7c1f3684161d21186f0e3b87f160517884e9f2c9340a8f485ffef92129024c15cfc82226102e74560bd567fe2559b679526acf95f6b8a75a0601b81a91dc6382f59f912bb40211609d5d8cc201b177e7695dd2a8b5bc5fa7c19cd92c024107dce6a004d031f217d68d86fe4685c850abc2fd0f34a6b6bcc654317b0b125be78b863494ddbf6519b245f927a4673869f7213c1458717c6fda6b884862e74b6523de7af15634197513fa2b4706da848ab6bf8280de1fcf7dca7d7b127075bea38de190a33d7812ad6bb09f05dd500f75fa9a1d3eb76a31262f690d359d82e437d4b752c4c904872144c847f0c7cec02ceef18bbcffea4f7b79a966c6b566bd0d5278d48ba69a2e8e286422d8df8647a78755e743e06bbc19cec962033237b3134de52fbdb470be8c6f2de7fbcb0619fd679b4cffd9c6f830bf0f2fbe19fbedf0d8f9ce85187fe26d90b3af33de3c6b7d269c1a6b474088596834cedd819c29566442d925481809a5c6b4177062179634d4c22ae52cf659f28ce0b52d79ead2b242e74c2d7e4e30ed7839639a4222cd41b90e166592ca0659449e6a6634cdd459b24487757dd2c21c77f6df3ec3bf3ec2e2d387b0f9f7115e3e834e4b58fb3143e564a689acb3926e42062ad3d4a4e6b0a455dd81427059aeb8977ce9f16e3f1b51cca924d96924abacd385d081cc1435d724ac695957a0094ad3d1b2ab3ee3d0bace40117453a2998db18a357bca834bb394230b9b63192b760725a0399956ba348d452cd889640fe592d9a7794a91810db18c25bb831c109c4c539ac94ad6e92454a033859a6b0c2b5ad725d004d954b4540293ccf7109555739550b61be7fe4f4eba0919a84c53939ac3925675070a4137194d6c8e9558b333a880e69434a791acb24e17424750e3bc1aae00be0f86ccf0699a5264b349ac63c9ba5aa0074dcd764f39ded9f7248184fbc73575917b750977d619b7d44d6eaa8722a0a8ad3148bbdcdf5cafebdcab4bb8b3ceb8a56e7253dde1ca2a742c21ee650c6ecc1c6e9299b89629b83393887f64ade69b7fd83bc9e214912771c7ffc63f1eafe0086b5bc28f082e5ec50dff10d7e8bc8fdbfd092eb178074f585b126e05b0b615f7fe437ca3f33aeefb27e147a3e5a5f873c6c4fbb8ed5ff089897770cb7fe10743dee166c6ecd51490eddb390e4ace73c4fc1f6b8b8cf9827d49277cf18a93cdd959fde0010f925ded112bd606cf3044294d4948489649e5bb3486bd5ab11fa6877828302ed88ef0f3ea1012a9c89bf504ec3ac41e20554768931d9f4d430457f5ee328c897c4a044650281a3c60d1a0effd447f59460569a605c6fde96aa58ba462391e6281c55872fa8c1e5ea89deb10c3b21e8a41a09d52a1cb95c9bbc2b30fef0a0cde320669cee13566186bed2ec1385ac7208d6cdfebe27d0fee09dfa9e73bd6594a84a343f41b6369736d6285a7da9cb5c97bd1a77b56ddfeea0293658c6a7ab8a822d0d928237b5376fcb37fbec6101e2be50f2ce0dc4e9a390a62ec6793e8a3ddb5a52f6a04e5a24611d3526c86af495d23553a16564e4c47e0896825eff448b00616d7d47323d4cd928b92ca942754a2e3fa3e0025dc855e2cf29b51aba72f549d44c1f737bb9c2c5594a073c1673a9dc41777b3bea2e5b7e01f8cffc4dc2344dd65b5ee655a95a741f750c53c74b722e582528b03d3637f78f320a84c0eea6dfabc1b095776570d3546aa9f695138ee05aaac3ef8427320ea2d225e279fd407b68ba4e017de3b4957a877b1080c2f051a1c6ba5551048cb481dc419bc348900023f1a70d2ce4fdf84b8eb9df6510e60c22b079c4043120119812f49dc52926d915c2b2169114b646ed29e0609211eccead0076a416c002b2fa01770a7fa0615ac3aaa63748516f17f0adb09130a980a3838aeca18bc5dd77954a53e9a51be0a3df0aebace697fbfda8fe68b65ccbacddefdcf601da65de797beb5423e9a2f5d12d2757eed2c530fbcf9ffcc5b2bd4986cf0458d243576d4f8d11da57ddc9ae961c6872e70edd264ac65ecb3df0c2b8d308d6fbcb75b9155cc26b355893443a58b31a7f3a75599fb99e50db65579976c55da23a4afb6a85a6aab12098d1b62d2194d74b7b3a60b86c12a366798babdd9ea9756b42a9192eec3eeed06fbf0bdddcca042c50c0194e18332c69421a68c31baa3cfac4b57e1f06dfed9ec3ebf024ae424d10f61cf6bcd6a7f6cd17b20d2acf6a7525b43a544d622fdb5b224a5f740a42246943c8c2fd1bf50cefdbf507873ad94087bb652229c2dcad67befa3f7655f53de2532fb2db942352c3dfae3ca9268a88665139c9344951261fc9277c9d1e37fa1cfe10fbd4747f86d32fac2e13b9917dba39cfba29cfba2f09de0fc4993626c33d89add274a193fce0edd3fb22f2cef926cbd24fc7368ca6f72add03c4ac2e13bc9b92fc24119cb218cbf7e964393063d96b5a30c1acdbfd1bf59223bab2e9b4c5e5aa593ca3e4868565d299b31a234be343dc2bcd336627889a1274615e3bb6f6ee8cdf5fde6c669955aa14fe9055e3aea2cc16c86e52c4d01d304992e3bc4a0bd048004448aa040aed140fb73fbecca9f245bf993e623e54fba7d76c5f09dc4f06dd70a11a28ab7cfb7cfaed8c2dfbf13bf471eebf44be7579cc5940747acb2455569be4d6687f287def7dd3ebb3c9974abfc921e83eefaa4ee3d5ca64386b8cfac0bc98f85e0a77456892b4539ee1ebcb9f3786d4c4d95272b8d6294b31c83f2e806e247aad62659fb82b1f0537ce34cdaa4200d13001105036cd077809d04e80691d66b2608d513021d700446741b33ad0292259e4075d37ce145172a480117345b74d3a0a09b468b6e9a2c684e1047b5b30faa61d9048661f6fe1518be478ac49c556cce07fae8eba32fdc17b93efa8a2f843ffa8a1fcd9686b3f3a3af88ef7f1fcdbef4b42693f53f2b3f5ca9a4b53cca2c27bef199f7387bb1f7bf5dcad670ae4275f39344f8a32fffe86b562a44530023badba34cb24799cc479af4e768d21f2c148045cda4b14963b02b76982efa933dca247b9489962068a2e0497f729228d664927a3497feb119efe370a6b59fb53cdad9ec2988f112ae56a852a248672beb37f9615b338dc63e866996f28abd334cbdb791c6eced66675428769dad46422e2197f8c8eb0fe524518cb7af9428563b9b579542799486278d75391daed6c3980a7761ecda8c3f0fbdc7577ed867f6976492c7db673af384132350a17193fe5cfae596645bf4f466d86799edfdb2bdfdf43cfbc13e10663328619e857d36cb608fedfd037cf46fb5b7cf343f9616e67d1f8865f47eb0fc2118bb59fc38f9d10cfb8bfb0afb68fe6886658fd29e1863bee7655b675eb0712237be11e360ef8fc49cd50d4bdc8a0600432278234b1a5d9233b11fb8af3d941450a7a8544a7c94782a37292acf7e88c5ba4679f643eeee754a023c3729cc0727c0a287133a28f199bdd7c3014a7a9c086c7ad64b05783c691f61643c651713f9f06841135dfaa7ad364de41e22a0e32b25f169f38148691b1080b9e1e5026a1db580bcc7739336089e0d088874cb1b0e1675b4f083046614e008160d267cd122c7e647005f0837365b70c084b0e504590a5bba1b8709299070d1dd3925a460730c2055595c701b264ac0305aa5b78ba6b100cd966e1a22ba692ad0365e9eb0f1d28566886e1a217ed050a0bb939e38f9e40df67f73ec9344fff7f533dfc6cb0f9a0948404b10516880c8d29386668a8d07c8687ce377cbc9dbcdc603583acaa4cc03f3fe9267f1916e1b0ff858628d25ccb0b7bf36cb2a78b3ab70ae7f731a5fc2a73d4fcaf08d5dc2609b2e46d41061d3c547171edd3518982140960b588002361d38e24193a569f5d2257abf9f1fbe6dca58b6d5d2283163eae59957ad27417bffcaaadafa184bcf28ff0d53fe244182eeeed136493c914406922022891cba553e74f4721f7af272157695d2276f3e7fda6e9a1fba697ce8a6e981c70e56a620510211708056e5e5d1f11586f114a1cd87cdef217600432e3cd02aad34276f2e7f89cef8e89623061b24b2c81fd84356c59cd4d0028d378de7d96c4cba6964d348a16e9a8ea6a3194233e48e3b66ea98a923d2c0984b4bf3a4774b867e5a4d805a4b908a94b098a167ae68a1674a3053848117bef131b06383012246f4002f790a158d80626344ca8801747b9e04a2c99beb3d2a976455f23c095495b22dc22e4f0281718110ba3d49b34f4fb3c0025eb28d05ac74871ead66d95f1a14e463b3050536450c4002a12e79fe937830c5f773efb323f452eab1d9d2753b2be64be6d36832f648f7eb7ea8f32ca15099ff4ecf7e2aec2e7894e577954e11558e8e5feb39ee954a8cd155465c55c4553a523ac73b478a449dd0cbe5e4142982e9928d5d70becce7774790384dc63c2f85ca4f63b1304dfeac1ccb661da6c9d80a77f73fef6bb6ae4be938919cbcc5146c885341b912254e45c789b84d4a8755f865f4e6bfd9a7b9ccb79b95fd7579b514c85df06871b95a8bc32ae5833c5d94165152e7337bffa3defdafca5c65be5f6e89bbe091266319fcfad96796055bd1db6398df57cda75e71a6f826674c93d42b8d5641b779ba3068e9954b8f1dc3e2cc7b8fca8c3b9afce96832469331a79e57fad3d616ce49222966b2348bf58a81b8a6052e4d0071c40608158008a1fb87357e28e3872b9c855fe52c95b3f04c1126f460b32442f704faa186279512c5ae739bbd028e8b704e12e516ce0ee57e5617bec92b8f28ac064c0f45b8c70e363c84a184fba2595d93c6aa92cd606bd29f1a356c32571750c436991dca49a218b1e3ccfa5f52fce8ebfe0cf2585f56a98dde9c52c96b515e2d0dfa4993622871d7ca68fede869dfefc5969cc257e3a6325ced4c7424984729f955f9f954b998a27e6d743cfe3a4313086278da9306ad298e39b9c7bdcf288dd7966754c3df0b63c1e4df993946d912d9af68856e9a43fd916dd97df24dba21e3c39393aee13c3ed630a9a7b1f533cadbee7c99aac7da030df8af3dc3e671eac4489cfacebc4af16e623063ea414c9968c295048531540492fbb5628683e4ed65ef36ff25ff3ff8218bb80b0007145cfc66e21bec91a715f73046de8367b9c2fa34b5caa0a72f9b5b90b65337b7f26a3332afb0f4fcfd6bc3ace0e29d5bf62af90bd7f004ab4642f0dc2b6a85ef0677aa085a204ca0844402a105298a94112e166670e9b8f88951e423e5cf727bfc62ac015dc2cf2c4be9ea8c8128bb50a4960135e0943c77110c98300c346038a40e3072c68f01044b541b865279834e90493725fe494e86e3bc1242c617682303b416c27886d722f2cdd17d809da092675c4b15902c35953fed03c66a15736d9d780b08cfd9d5958aef6ce6095c232f8d1d8a4b10a56f0b35d9a15fcfc6110c39e36c12a39992bbdd50bc696a82761dfbf0d276b19276b394676565d33a3991146ba5608e36ae9fd59959e26f1cce893447866942b7efc1403fd3dc232cba099d1acd449f8836b5836c1ffa13cb2b1d6edd259b541322664ab980bbafb15b127976e577aad385f56d0339551efcb2df1886f3878cabf1ff83689cb5582367806eddfe82be2c7587e61ac52da0eb1cbc91b8bd5e1ce67f3bd866593297f3c0f149af2c706d9972daa4a3c623550f090000f20a850d12828b589ca2b7cdb6781800089b406fa570b622c73ef3b3a0e8379cc347604c9ec2f52473149c1d2f8c6088038df558332e078d5ba4d37a0d1dd9efde48dc562b16ef0a1bbc3b7b13e4b0f577973f7096e40e1da283ef8f4ec105170ba6f368946abfc759e1c0cd66082e2a5bb3114af4d5088f4943f4d4f74e81e8a0003e2928d659ccfa7343dd8bca691297de9e6a1bb7fd4ecd0d77e4a44aaa5d8a3b61b58270cf6d59a5e4768495ce01b33786b05212347072963420729734337120bdd311060862071e4749038987490388ae8207184d141e2f0410721a342072103d441c8c43a0899b08390e1d241c8c0e0841d4e50e60520657c20c34577f78905091c7577cfac208709a94b371bdd3c4052f57c7af38f0d5658ca03e887470f8f1d1e383c5ae0d1c3061e4348613ad28126073caa697c230e903146471adfd83e42cf65cd50778d931a665e5034c50d13d11bbdfa51d3240662d8877fe65f7bef1213518d690807d350adbbdb5947aaeebd9965ac1e7a354c3f21b92664e66bf8a1060538eb6f569641eb2a9f954aba8469f78c0b78504dbc709171d5c01586fdfbb96a7ecfa4a08689104f7525b9869ce50ad2a48d2667b8ca95a8f09b9a2ca0490e98fafd0f66330ccbfbb6080826466c214573400b031020f4c5fd25a3d09b42551a87358a889f1e59729b91c85064f57364c97d566e6429dab01cf258adcf8f859eb13caa2e5bf4483533f0aed29323a69878a2e8eec7e050d1ec322c18acc8c89584b4e4098cb56488204c3c4033409a0ed30c3998173cfa015b70b48ed841d2d5b06cd2b9aa4a243afe8519e59df4bafa1bcd4f73339b42c5ffc20cebaecd3229d4d1e80cacbdbceb7c663d3a69d30c5cba632a7c1bcac39cbca96cf689abd2ac4a33ef936c110c06833d71721f6662195d0a7ef56f13c66306043128306104129c2146890148e8ee26a10914ec8c5087aaf66303b2813811471620345657c1e5fc280304c48d12ccd8c93b2fdb4ece4e096d4aa2834e12268915d9da0bbbb71b976e8cdf839992f81026c9dda624af243da624deddb0ff599210baedac5258994ea3c3f49b9064e179d24b7723d9d2f829121dbcee4672d4b09acc7e4876ba1b89b70c74846f83c9904577c3bc744faaa5730d93618a0c418d6132a01acb4042f791326d3a32e648982360ba1b049a235b1a85361df1da6d9ef2a77b660440aebdd787139311312623b51e130c5a4c4654353324311999b2d35d93a4bb064977cd901a196a33b12504a2bbe6484e4ebebf54adecfb3a10ae9874dec03762d8fd0f859a15e8fc2fe4e972f2f682f7782a276f2fe0584179f4658ee356ba2f3362ac718236e56409ab94535a2ff43c82d276d718e9ae2952b30202681c60abcd1a0659acf91567f4d3167552d65da3d35d9353b3020d269c283d2606b18c68648a44daffb332ec1566938f930f85860634ab15cc8c11868a0a26a0d04256c159a6339f71c244a4892e6284fa1a3338009165a6015f835313bbbbeb98d4c033dffb98f45a33249850748861425d81da407763eaff85d883504add5d429b502d136ac7934b30130a05d3cd0f6ec62ce9ae5975d7a84c37b91a22606682689a134c336e74175d9051230210244c3144c9d7621a23189248a1ba6b8644dbb5b3b6810986ee5ead5635a91d7f9cab5ae1948f849348dd3528a1992a6880e8aeb999b688c55a329b19d51f2c6b4add9dff8ec3e057de7cfe632c7576925c480db1751454d44262626ac1089bc9c3e8d6c00c2a7423a04d0ed44d5cb7cfaef7645509bb8432b64df21d3c985800405b9af464ba323d2a6a12667a41138da9b1a4cdb4615ac1d5b44a6158d2ee2f59cfb348b95ef08756a9490535babb67f4e8a9a4d1bfbfa4bae04fbc602c85fa2b3f7c14b1ff1205f151b4c9646c7efebee5319625cecfb628d779a3fe5ece8f3178c516cdb7d9a5478a8149f395804ca09da5d32a55455a7c57e56e3949e431e7bf791ae1b8acc7209757e93d8d4fef7fab188823c6a0f739ad3732398b81385219c5f247ca6287531e9555d0ca3c59cb56455b1eebcdcce538532131100bf186c082863b31b299e66c8b5ec816853619cd493130e85a21a4a934b11dca180c9a8fb3b32e71e52451adff592d8a14799c55aa92bcae73f993e2ce31add2d9cc76dd0d0a70f7f1e9915906b9ca553e3d3d375a4bfa5c89bb7b029cc5727f8f7e372bb3b5eb3ac7995610574b1f29cc7452d00afd25eab5be7f27b44ae50761dba6005c117f263118fbd05501f0e96e619bcc0eb9db6432cb20a41e21f868757748e7675b27fda9c940fba4522793fef43099806820aec29bdd6b62e8ae81a1bb7fd4bcd05de342778d90a7f73e36fd48c3f4434c37f1e79c248aa177a9b4510ff765fb6149636e3d70da5b4d8637e36237a31eca6aff71e7f829feeb441eefcd4c1db9ab1ce5d1654d76c14fc96375cd9f7549c69e87e9129df2e6821936c4d4800e1edd261e6faab181a9264b4fe08c3156b0032d32d0404031470a4c8ce916c8d1ad563830d8473ffaaaff199966b484d79a663ad34c4e77e70fdd3523c2aca09ddd6861f3e7fcd8ed592cd887733636ab343592a96d30750ddd5d73c95af2a62680a97fd8dc5183ee2cbfbf77c880ba03053bcab4b3e67dcb627df8654a76987ae98232d0ae6677a9d2d5ec2e09213da961d94408e9c9acffe5c79e2761303a96ba8215fc8109dd1736a78c81ffb0fc55603110c3e694b1fa3110c33205adc84147cfc7a00b06ebaef1ee1a166a56e8eea0dab291e34877c7db67174c8e0c0bc118f63c09837d36db94a1dddd3d58404607dd99e2ccd01ca9834c945e7a2f16a320ac200e3a6ce2e002840d1c62e048011c5c781873ef32818e0247149b37c6d8bce145775fa11856c359c3d94d33821ace6e9a1674370f1a30fab3d9bede4801cba1ee4ed9b80fbc9ca8b3cad1dbea671247a08bb638d2dd5fa957a91b5984a159018f14ca0d2c1550c2c68d26ce7a18cc0d1368957ef1af6ca34c1b61b8bd6d08c10adf066b0347168bd5060bed4a3c7c1becb3d9bfef41998edf835c0fbebbdb59e13bc9580ed9d7910dcb97cd608b8d31366c4051f36a69fda1f09de424910f8eecbb2b499756ab95151c9c9b95457a85de98cc15276b38e270382124331c41e22c6751e956ce7a59d491c103a5c47469cad5fd259b3d59ff734ffeede6674f36e95c7a3e458e20d1d1c1e97156b543808e907c88bf52e851ea819fe5ac9f69ec7e92a8a893429318fc2bab367cdb12a2169290db5cde689cefd8560cda5a845d46b3bc59f7d9cd6db6f869357c2753fe446c33fea84ccac775df0d8cb9ca2679ccf5b1771fcdb8eb3ae74911711e1fe244fc7f0643b912ff6886793ca98f6694cc93329812cfef05b2e658d65a37a875772833fe25ba74418cbb01aa5b0d31dd1dd5006352234babd175c720d74b8d02d8000536f8bae36ae63db66e031ad22813d3e09246678bd2d8498384ee1abca03b66fc93c57a4d178bf59a140d2bd028020d20344ae8e28c069cd1a4fb263fe31be954761ec6fe2f36430733aa98d113432fa9a8e8f3ecbd32ae28434b370dc6a08107688044031766d0c50c8e98419319b4c860838c1b9021c4adb34a79a3cd64182143003220a33b7615c459cf6bc94028063fe81203a518dcc480070cbec0e00a0ca674c72e7f52b53486de4dd6ce57f1bd4bd5f762fc9bac281fe244fc314b7ae0edda99981b88d932eb1864c6d0c018750c14b410c38a5183182fbc00062ff0d21df14f1cc69818861017d8e18211b800e602a216906941981674a005575ad0d3dd75b250de640dd358579562e881a10318303062981c84f1120601617aba3bce2cadd24a6fb95222167cc10213b0a0b6c3821f2f1bab5bb522be76b602225680c30a4e5f50f1c5952fba2f72ba6705595dbd600c96a5175478a14377ec028e2e9ee822d645933854a0c4ec3a0f63f86bf95a9f5915ac5240c7ac52958214c890821e5c8ce1820a131755b828c0166d6c91832db66c81650b225bc8ce579d12c79dafba148e2b8f187f55e2b2ce572ce944fc73222e9335cf37e9995679f31bce411b82b3cffb9acb0367df85f86bd9ad2c625a05673eb3d251d04201102da2d0226b21d3c294451d597c208b2759d49c004c77c7d0fb259cc398bd559c8dff379bbf0fcf5c33eb5dbe16e75d8797c47ce5b7ba3f3b410a587081851558502c6cae40e38a1eae306245195680b1225b81002b7e7477e4e1f9190dbd5ca3516f5639b35765894c5001135013d06002ef8eddfc2bdf9385b55502232588a18a31aa6041153854714345185460a142670a1a4ce1c4143e4c219b42680aefee88333ff7a95ac6d94b7bcf6619b4c4abc4602dbfac93996669bd18abc39fcc94ba32066f61959daf705f3b5fddce579dd792628a9b36492184149f143048f123768f71dff92a1a01918008cc1d603e00468299218a39a24041146114a88eab0378c6f6f318fcef91f3ac32b647cec23f2d51f6308d753ff826bcd91bfbf953fe6cfd60896716090a16406105144140f103858f66c58ece269db4f3afe5fc18cb59c10bce1cd3a0fb572abd557a9b128669e8d303c3f46fb2ee80d893372afbe9c957adef7da55e75cd87fd05bf6b9f10d3fd04982770e82cc312f704932748e858cbb8af2d27c2b49ce812e67c4ee951aec526277e447c932b6d89c0daacf23c1164114127829d10a4417b8f2ed12b65f83d09e4c362bd90e8809e040a4100be60f1654b47fcc5a6892f9ac02156fb8519d6ddafb6891540c00508bc74c77ac1f94ca0c104154cd4989031410213ed65092f4a5e927c208d0f68f9c0079078604cab5498e68f41f198315805b4a177f706b4a12b794c752c5606bbbf494c5561eebdd92f61fbb5fb3356c37f672ebde2e497e481214b9cb1c40596b86189205dbce8027639d279f2afdb7a1fcb9faef3997d7545d1debff2d11aae76fe2cfe0533385bc90e88a0036007541c0883035dbafb63b055942914be3435511c686103606c4089fe9f3ea361ac2a2951a6af28d17a43031dd000150d0809230908242193084006ecc88017549b32a083acfd05bfdb79540ae5bef355adf3d52ba793208dc66e12d3e9f3bddbf92ae20b7e5d18f358e990213163ebd9d69021ce6252e42a426a418917fce487b9c4c1e5082e52b81c718981cb09486cd11d59ab18e66432f749ceea609dafa2c73a8a298cc2b1eb280da5f7352c6b9efc6b67d585c40e1226f4116558dd1161aad1115f3e893b02cb6c1d71a46593ca28988401336618d0a11d031d064830c2099311b9eeba6b8196c4dc1bb113bb9fbf7cc2b7791e8ddd3f9a5616be932348409f77d223fb271790e2025cbabb94ce055edf4dae7fabf6d2a5d8ed316ec51e8a05e6b0c0062cd000a13659a0c723b6b30815148183a9889dee8ea197a3e1db6220063d0f193264487c1bdeb2c496275b86b6f46c4161cb08dd6d439b88a882082b2622a274f4bc1bde0cc6727d1abb1410caa873d94f2bd3f1554cfd12c595c5b214f6e10ee55d2a49c7575d95b2cebba4eea795012539110742194d2b23e2c7ac550133ba3b7a1e755281fc49157040a6b7bf6f93444b7c5e8f65edc3f8de252cd62b8336acc13e9a8730a3bb7b882fdd1d6d6b88af3b0e41430f4142377e1a0dc42d21c07816220bceaca09d499c10b58f662148c0996fa280151d3f9a294081ee6e1f97ab6a58d62630c50488304de0278044026848c0879b1b9f59a44abbae8bf597b494400b125a685a9e680940104f04d10501640a0208103e00c20820baee9066b686e4eeac95cf6c87e4441ce9a379fe95ade21092d26a6725f45272e93c1fcd3e5d75edcc57b1be4d667c63abf3e0befadf2c931f3cf003961f8e7ca0c387287ce0a28afe61eb01f52066043d2ca0075b0f4d786883071af000040f3c3cf0d8e14b775d90afdcbb944e902ba6ba6c6b18fb68065d915669b7fa68f66b69961964b9c294e5872c48225046048488404f0472202006021a80c01104801ea0830708f180dc035ed0a14c77f833bd506241beeab08a85a77ca45890af62a7b37a75917a38d3eea3af2af35c255375afce67b6eb522f9451e7acd057b2ced67095f6368a3ffad12057d77d20ad6159eb7c15f363ef64a5511ca40314930e26981c40c6015238408b036c1dbb178bf5ea3c097aec5e9d7bec96c48c1fc4b8c562bd3a49143f9a5755d6b06ce283fa68f6ac8df9e0994f0f6c7ab6015da84c9132c5a3cdda56038e7832c5612a53eedb09622953fc6baf06c4d0dd3e2c960fcdb1f4574f4fcefe789e9455068460c262069621b034e978bd550c6dfe5aced622e9b46901255840071640846901411d73727254393a4ec493b2cea1f82aa6507eb3b7eb72c0410e3030e570450e52e4f0c59483027208731082d3e32efc04f957235f455fc5940ecac8573e3dee4284c1c2b7d909a63adcd70ee5b996c1d674bd600ad0a2805a6d29c08454f6280de513871e74475c0b073034f6c2414b96d3438943cd16e1e009a0a39b4902b848008d558ab39e5b9b31f5982901347cb5a62b7774479f57cfcca88811f04a92e98a90e98a4d38453833852818e47ac8f5e44ce88ea99cbca1a848c12b9a8b5da7137f62597339fe3ac7d9a3b43a3df4268df14c1aabd4840b010e02381bae8437565de7333be94a4ed312104b499608005e017a31af62f628cd56e9ade1eca53da67e671ec154764186c274f304bace57b1abf4365b5d5ccdacdb1a8e564bf3f568ae14b472ff684c0b6fe9ee9609d3206bcb8483442aafcdb92ff2f161b15e3d336b7b72f6a767d6b258af6b80394c564670adcc4c565c307d68983e2e114b5a938138d5a13c530fbd6ae3f43c8aa357be4f2bb321a6379c307f55be124cd60a4b3bcada376d2dff4d16258dcebaae5aff9877e473bf47f64f303662b1eeb5f72e995557c6f6a392536452296224f7b39aea923beea0a9b2a68a88dfb5428d89c796377c10be0dd642cd1d4a4b92d400d15df3c38f0661e8dafbd3d6f8d05dd3c30e5e6c65ba25ee8b90b051f1c4649bb1d5335376305549e3892a5d5a70531559054a15a52a475568f09945b9a9ca0f13f582461163a62dd8ea59fbd5d430d558505b8195dfb54238578a69f227633a24677d5a6b6684636052f8d1179e19e17fa1fa776674ff0393aafcae15c26096d70ad95975cd02b10afacc118fe9e98158e2686de12fddd486bb8c2f99abc4fd3aaa4229e912c694ca149594291e5b7707532ff2fbc38a98eef82d2b56b0745bc9e9c6d40a09dd31ffed6693be32f10b135ddf97eef881b64c77fc9bc5368b68bb3e591ababbadc7a46a46fd124ff5ab4e6e6522a6556a8b3730b1bb69e9be4589d35d372f4c4789ab96b63c2f318fc86687ad4b77bc56565b29980a63d6b6d45502cdfe7e458c803e3e3e9e47633e3d30d80dacf4a32fd4d0a8fd5073d57e9c685bd0be8ef16fde7558ba2a762c56a5d403679947e2c752f68a5e77adc7229176518765eb32066747713c6fa6c40cca2c8607e3c33732679065c6a60859c812203b8a5385bdcffee73659c436c7f94b3c761e3f36b33e640828cbc9592d0c56e9adf321d1b152ec8cd817f914c786ba5b4e40da709249240c9d19dd0aba06743bf16b39e71a8de29ae4a924949187d54eb759e2d0c3f8c30a23a33baa42ef334c0758d7ed081083001f2a6c5031e244c501548ca880709a42c51423a604650c4ec7d2f3d31e3683c1baff507edfa11c5bdc4fd0dfdacb5791a8eb62a68ee3ccfa077a0dfbeb24650a293e48497280340e20e60058ba63175699aa602765561b4351e966b5b14e8ad2077673b66e98e30631372871bac1deb0a372168eaba1d410ca08e9069b28248852eb686b38e950ea800206142f1eb5b9cd2ea3352cbd2416eb0514fd890e9ec8e0c9104f983c01a234859255a2e1c5c6ab03afdaeb3f2976b453d54fcd28956a531dea97ecad73cf935ffd512c963c253520099624c209490d24213a7bb6aac0d47cd9b5286fb9bc6fc318365c4002363cc08604d87073b281c7f48c705fa472564a07657474e5c87674c4480d232e464418796d8b7c151fcb5c88a1f799568afb7a8b70d10aa7a21188a42092111960684888a125a7a11f273f383961c3c91627312721505183d7ddf1710d37a337cfb6e2fb9fe36a7f2af5d5575bc39e9337efbce54358d7c80e61e5bfe1cef3680dbfadf2ab0d61d662f9d5d0f1da2c9333294b723dd11d5d374dd4e86e277d6a72812651dcfef82aa670b514e52f17916b88c56a6599cc52962a42659abb89f86d762d9838c164034c8c60626322c4440421129c847c6867392b7c9b1773b5d22848bbf26775f92aa630c558a23cf452ab47422d096249cf92108272109446d0114114089205bd10040290088066402afc74f19325623b31c5adee7ff26b47ee3185f2bfd253b44a510e942a4219396bbaaf52a8228c3262dde8cb5771465d55a58ca7d6c9fbdd953815be56deff501efd985a55b47e68b14e34a841030db918daecf8bb1139cbd62b71ab866596f7bd9f451a086080384e06f0a2bb23954e26c56c2dd24feaf2a1f2be8f8f8f0fae96e2f91f88e55d5229cc47f64f7c70765298cfbc4aca9c947451f275b712a39e32dded3d61bae74bf76cf57c7dffb6ea53cf4e0f093c5cdc2ccf96ce8fa94cc9c99b94293c94a784ee8ee04d4d965bac2d5897d259a507b91cdfbc268db1765077b6582674f70c635a3370d19156e90c5c66f8669801b553a63b565a74dad9d9a911edd0b0e3dd3149991693248916ac94e48bf3948486a64bd5d6561215e2949f93b78f666cbf8be408121264104386214e3230408624477870440547b074a78c3e85a967bb1667248d93112f46964e466a30c2a373f29693b7979111124ca7969344119f8ad08a149d8ac4a0b3860e17295fcd54ad3772aa38e53020474999138e08706e3845701cbf07ded96a76dfca5ac6740ac524e203a2778a1f495849715a6d49e95cd00ae1292deef5d57ab5b39bf29b74b56c631e58eb4995864a081592eae644040d22491099272241447a9c8684b1be9ad56dc67578d29faec3f6fb4963d77a0e7f2ebd4306302675c40e29a0532ac829d5a82650b7136aa85340734a2020a0506256f7f91483173160200629a7184e80410630100183100c51062f2c717a819e5e18800b57b8705d4841081742721d23b61983acbf85a00d6bb8d847f387b3074e6ab3ad97d230665b48727239dc8c938fe15abc8a13b90a2cb880852a58c8e1c402664185130b21ac208315905881ae50e4a44299930a635410e2a482014e2af038a510c529052c93c67c858d7c15696c0524023911ff4101d9225fbd8a948c806041ae1f589416d18ce2e0dc6f347f96651464f44279ea076584fb4a8432024afda08cdcf3242b0534692b56c1ce278d5589420e28c8503889713ac18b13ae9c4e507242905301d028c00f0590a7020831010c139030a1869309dda99f1f6cc46d264aa11ca3dc71a655fa9eb5316ff9cabbb04a15add2e92aa992b44a63b5140fa088d3001230801f804e10344e415e700ad24310a5ee1ea14f41989c4a80a3041594a0815309b8127c4e25fc3865713a8de0a4c3c97512c134839309042713ccc46363039b246c8a4e36454e2490712241cb4fac208dfdf8ca5771e20c5a8bcb11488a28d6f26330dbef6b407c15511e4595c293c67eae5d79b77258b514c368d2d2669593c66a93c662fe389aabfedad9941d15c0152701e4208029027861041d9c465062040498fa34c2d0083f4e22e440042544304284a153087474a7a238113f72224e44b39e1371229451ea0865e4310acac8a3e749998ee7c9d4518ac869d6431911a18c5247ae4a619413392b7c2711678ac336e34c6f3e3db3525b085d8a42c062429f42e86208a8b645a7008411becd6f96a8f74e70005800401a27004cd1dd51fa0400d909003e27206b9c8050e004c4e7042484c6ff85299d94a7308892d19b20975f2b3f96547f84a71f379c7ee89c4088e3044218107c388110e50402cac719271f5dba5993fae79dabba99d1745d8bc293c2c2b7b158387c2791669b6387c3b775298c0adf96947b1f47f9b1e7a1ea635b3bf9e858ebd4434c0bf34f3db8749f7ae45ee8538f26ddddaf530f7ae251a61b9f787ce9eea6a0bde1c4034b6ec9fec9a9a6cca9a689534ded54f34204fa19cdf2963fe9447381eeb6a14f34359c66e838cdbc4089cef2874439eb0f8919bcd663759d0f8956362476b74e9f664e38351ba7fec0a96ba7ee4ebdc21d34b8838a076de8aa2cfbfcd3edcb3d8a551f9db922ce14cb1a823f77cc3b64b0238eeeaee0fd3c7365f9df5f3bc0d881a5630ae51fe8f3df8e9d2057cb8e3ac2d4d1a58e1a4e1d293a78e8a8f9c11a3fc8c00ff00f52c8f5e947657f577f319da3ce3134470b73f890e38b1c5de488d2bdaab64a582d0b95b1658a9489a1cc0f3260c848323b644088e38b3872dd9d42a95878e56e957c1559dd6757b63aabc37d6af51705cbd622dd4c2f18bb51ef6b705c01c703e0288203d5ddb9cbfdacd2dfc06f746fbcdee87963006f88e0031df860091fbcdca8c30d319dc28e33e523b98eff04f92a56231df0f619676bebff0ab9c1a5adc56eb11b51badd50a10d32ba636afe12f5da48a20d0474c7d412f550f331283fcf37f5faf8b8aa86650f8ae881941eacc00619dd5fcbcec6b371840767f0c0071eecece08c1d04b183243a2843075a3a666b6f8d783aee2b6c66fcb10ef7b5bee761a69fe57de5e4e4e8d895ecda9f495ca3d1da4b9b5d95a53f6d4a9a7f2d873e3d19d3a1d0a7681665099b486428d2440724748fe99883ae65ca010362a5d6e69bbdb31cece4e0c71a62566b6869adf175afd1e4263fdfd648a12785753878a34b9cbfd461896be1e02885c201ea961913a67b4c97a431b93eeaee380635e6479c4937b0a23baf79a8e413d7fba0756a4600002028931000003020140bc62332b16c3ee9fd14800162b856b05ea1cae33cc821648c21c400000000000000461b004c9b09795e5624098c79f82d8f62a2db4da8cb9481beb753c7d4fbcc0011a86a0442ea728843db6e9ea28405b06369186c3adf84313ee780fc84c70dd954eafb8e16a8e4389d4f1da75f9f31a087b47002345f0c1bbfd168222388e873103723ea63ca1e609a996a569696e0a3f60d3a08c7bed33fe313dbb6cab83436bbd4b8b9952d20984220b5d43340e31c519134d91a32b30f0eb627444280abb4106955136c0a1596c7216f454370f684804fcbee4ac7a7682ffe0d3458cc1405c2943dbd27b3325f10f50d2f5aaea27f2d3b72e4adc5bd847ac9a6dfdc7c27041afe45d39257f942171e1a335331236d26797a7863c63e830af8a3e51cb410fa0ac629fd6f4a50cbcc48f437a1c08bf086cf3f58163f9cb40e3945e635adbc8350628b3b7b34966a4b2e01bf64ec1854e4a8e051e4bfe512fcdcced905a14b317ac6a40f5b2cbc00f80c190bd944b6bb630ba7f03118c8a4273cbd22cc8f9e6087bddb30ebf7a6b6ffc12d590f83dab58de83b9a3782789ca57a4e0fcc97bf2ede6fe54c018879a7dbac20827cd0cd8d97e935d4be20f409bf7581c2daa8bb29a1107bf20d7880db2d0f844e075c715f4eb36a621ebe4123aa00c065e400bfb096963198a776e239c4eb1efe551a5c82b0586a3f708b58a917a50281ed99a5f7e1e5e2939348ff6fafd01272f01f80fb315c175e3f219e0aa4f88ee3094e8e263e282c9b577ecb4e8a2d9d0496310523ed58a01061e5acee370cf5a07ae2c32029e430d5fa0cccd63b5089984902d232b511ff0eaf841fa90869876aab058e62c0870d821609caac5541eabbb8e42da37086971958920080bab497b46cb2b4b68a49260ff7573c5c9d89804227f2851aecad918ffea473d6ccc8985ab2d3797b4b868f9e5d6af03906a6027d77b8d71126b606831de9d2036ef4ddb4d2f4ffb2f3f5b5226bc027741a26d284f4251ada6b49e69f9378b523f9c12ad2b5288de3218940d0866c2fdf16cc6fae6b6c14deb20bab4735188e10b5dd66fba11123e901c016571bc6568980c9d96d06a1d3181a3a6ccff54eb129946118158fd231f2becaa3123e968f6fdf47ba0de7d2268d9c0903346c1cfa6df49ba212a7a520e919f5ba354cdf68a17b40caccfa6e7e030248d47b152b4c69a91594906cfc4e562844088dc11cdc1e242e82275d6da4b42adad609c6b36451a54abf7dcaf966c0c7a723f476e66e78f61e497f9bc700caf7dba1933ad0f604b6da4287284346d1e129fabafdbdd33bbb7a329a50bd8a029ca7472185f82b83e1e5e16d4a754b7fa3b3ce5f9761c0c7563518d204319a113d57f10c8c30d5714727695f60e7b8e66bf57a61b0b7b0f366c1791d5fbf3ee4fff9cb26c000aff73799c37f20ff7f192b7afd4edf03f814d57e358a5c53422421669ca69ce5550840b8475b1044c1d1c204a61b61af3ea82cf487e90bb4684b045599e9afb1bd551b2f6f1571255d2bea727d2cdeb2bc96045e717d3c866a2afe57b57e43f018e35c37a231220d8c2e3b74a2fd2bae8cc5cdf5f295a3ed21bbdb415af746739cd5a5afc47932751450118c85348455d352f8d61138536c4f6eac0829a3d7c85ae1dce77e8477a2180176e6dff21007ec543ba808a8ee9014fcc8953ae3228ded0297b856cb8babf4bccbcc238e5646c06c88061f154331648b9e51a5db10e04805b189393d592bf228a6764f3fc123a1f6bcb6a5c7a0b6aa078491fd3a65b78381de1687be99254ec0937347f9568980e70652b0fef6901549db3442fda73af177c3e07d5a6beee297f16a4fd0f942a58447b84fd2f8c1d8f721c126c6815bc626cb88eb2d44e92f2cb828aeb9b5f82120e6f7c5ac12ca49461ae18b69806e75305cddd443ebc832826b65fcbbc911366466fd5b8621399cadf3f5e59c04d0bb89475b7ef43ff753e0867516c47ccd4881d1921591c9c102a82371eec375745867e51916fa99dc2ef666257c8d665e05d9098a59788388babe2adf32103ec6661d728f40c7aecc52defd20a2a640107d9963c0abfe1497f9ddebf50b0fb74a79d9f683d5b094760ca50883e0b9f6ac473e7b17535ffd1b6f0e1991841f89a8253278c4953d68994ffff131bc4d17f5f3db01e74df94e0bbb7a21b7821b5d8ab51ce288be1a8cb9a6b6943d252bdc6535a151e6693030f5faecccc008e18042b78f14b4b082db325c21f58a5c85e18248ea611059641e32f41414727278122948112a198d05d14091e53d46074dc24c7ff3b3e3ef8246c8067eb55505655ce1ed4c27ba980a17d52fb6f41352802a9bb3c69248706bbc8f96cc7b5fa03c1686a9b24f82ca13a9491cb727baa70f123f5fe141e90cedd2b2e3c112cf6c86699d24c829ab3ccbe3a4d93b891e8bb57e3dab70076e508882aba4b2e0a0235922f89926e5a59bd352b65343e8c3e2682fab57b36cc73b18141ad8d69ac1149820456831eabc3dd96f787b163b4c5c1e7c5c4e4219c14760a74eddce611a3a7be33fd005236b1242c368becff57eaee707e31740bd999bc1afeabf6508294894d57b3eb7aac9f5222ab1b1b314731cbc2b38a914c266d9ac507850da4109eae721ae79ee1b2c222c20306ab08e04d7af322ee1382f3520da815a522ba98b7a3b82eb643363bfbbb2d4492da5980923f37153be92be0074007448856a67883d0044b52c135dd7ec6804f2777ab7eb848773adafab7f2eb8b143ea30cc60c772ed3d6c97f38ac89cecf23fc41b975080550e7b8bcfb0937643968804a77d67a1757e6d004bf122554b31cfae2e3e7afcd5226e5487be980deeb679523c47f5b715ff96c69ec96f56c5cf357b06ba36c2afc30df38c1f57417ec03ed40913d6c490e4cc3f70e21b20913007c888aa63a7b39bd732ca1be9c8482b151b7c2ccd78c1db6c7483d6c10f7bff1dfe091db70b4b1903695e0f56dcbadf44e072d3dd6262732ab6d0f75264f3d6c7e05533f3988b9599cbe3c4638ff120ee6d0859ad23d1295689043680d7892691fd6444ccea62717e8e11849571c493b2dab75e7b62c3cab656430ec5b6bdbc1363b7a5cefa14ce5a4abc73a2ba451b333158e906ce1129bd9bcdd1e5b524a8d35d43c6f44c633564bd7a2ec710cbe008e89f9baaf3cdd27b83306d68ff1f85598f4139212a1daf97a94d0c12b4d0a4fdfffdcb86cb3a4dfdc78911fdf2d661057422739c62781c2d0985d66a70e40df538c2566ad29af2c75c90aab6aa4f1b2244e2bd1ac5f9f17f857f34fa692e8a921e8bd5762c74db758e23555ffcd8c17acb49e3f6fa41d3ed2bc42b8b2711fc59d0f4b787416f30f18b05db9edd3faad4db22f21e400818c6ac4ff389c9acbf67242b0da4652ac9968d101d96e2a158fcfb819c7d6a0a44607448150a16d1b5a7c3d4f53b09f2174d7f1978512d3f7b1bbb8d3d73c28d627108b76cc98403cf6e52374c0b976c54001eb770d821c59586c4c3829a9bdc765c410316401df09282195f8e2cfa066a3bd912201e8423121ce11b38fcd9853d39991269b63d2d0920cbc629603b68639c8175808f3244162b5fbce135e046104264f16a336f290bf9b339e6ea99f0e9176d0c3eeb11635cab78bb81d04db82ccf3ceef2fc97ddefb3b8ea30bcff557f003c8a464687bac1ed9e4d251cdea62dfd71a2151bca4a6bad751afd2e36fb60d4f8f69409acb329b5e58a8418242130bcc76cd4ef2eca071dd23d1c053a50a6594057e1567f4ada5be2ada1f1e206c39f7b7f80402e3da83c594d87ccce9f0fb74a39f900aaf5d8c2db75db186d4a3f6a4394f80193d7139710b50a59bf463d213963afcdb5058605e454f6891ba1d6c6a3e1d1046b47b83537a0c44f3405e81cada1bd7dace161a3ed676abcfc717ce3ea444b70c194899ccc8d3854d2d2f9445f1b2d4b0aa98da0b0c6236c8a88d1e7b0d38222057aa45e223f3927064629209da4cea3e7c5c1478817588b20202c5d1bed513a0af75aab15e24d40c8ade1cc8c066517a460d5c65d137eedfca6f9a86449a200ec12cd4ca441a724e24eed47e21c414093faae6a9c8b6ad50eb7f297d646637a810cf7f92c0d3f2cf7e8a0df0b1aa7bf03aed13bae12fe6a58bf1947ece4c780918b3b7a926125b1610b56ffa57dca835baf1b3f54c82f33aebdb9f1f0c9840c83e52b87304bda75842a3f11007bef835e8c4fa3358910e54d7c1208fc2c750bdad7e36d0bb3d2a8d60a5defedfee6fa28ccf95d430085705e03f890b5b2aa871387b1e1ec19123a6fdb1e0d3c319625fb7829b804d957a0fbb5f7f926de6ac6ba994a8e857e5e8cadc3885a4a0f57d32e040511fc3dbd86a160765cc251aa9188b267a5225cf23b20ab391cf41a126d70b0867016a3122e3f971308aa40eb913be0750d3522f03e6f0642f503202a4706f973392abeb9f161b5da47a2d82eb47ba57f4110ff5fe78366feeec71fc9fb21195d8ff39228cf55f4c0c2fafc778e1ed5e6151ca79d71ef52ae0a87f77015615658d9f4915d35a5f28d2778bf43ced6c6415981bdaae586ad182137c92b1b65ddfc9cc0ca2fcce039fa81e6e5c9adaa7dee49f4996d696204042871355126cf3d6c6cbda28c33d91fc311d9b870b6ebacca7a96d6d9467e0d4b366a03c535c1b76c11418917969af3d5040807b4588f90868a4b018feeaf743cccf0e72001013396fd42102340639c8353a9381b9a2797224dc589ab87e6d184386814651341e8aa616742e8972eb20194156d2c6a0001100648761083c26fd712a3c8e0a9167a70c6b8da4bb00f1b7ec03d352d2a3ef1399cd5cf3e9d24f970f773aab39edd1e9f6f572203d272b2a361977c81856a0291e106a298399c6b6c342f1d661690a3fd7a4bb6d46c180a99ef183c254330c2e1b0398110e7f797ff868dc323c632afd0924f195c86965285315e907e92c7603535db1ef2a7ebbe6b31266df92478a7d9547246af57067757511f6340dc8e2819414add8970566ef7b231e9a860fece979579836775f3f27af5b0a106eed61c579fe37cf4e40fe09e5d1b3b9dc9dd8a344b3baf93b51b6622b4b94309f6710c0fa7a756a2aa4d90102e4cb31b21b89e5a24a8bc4a5b73bec3a56bdfb82101f80263a25b70f83068dd8d1e64cc98e63b3aeeccb78d6c9e3878b62a9c41abda9f32e3164453e971e6acdf7f29f55818e7dc5cf8d5c1a7bdb1896fba395692a30dee4dfdc509a42c658e854c1bdba8dddd1aec59e43b8a2c0a6d0fb8865c98d269cd04b978eefcf844f76d99251a999e2df85daa8352343c13bb49a0d681ea4c513829e8131c939ffa81a34aec3aae07773adadb9660b79ee1f063b8783e5f4a833a405deb1f5a7991f87d0bd131d8f8168600ddbd0058c3eb0017b83c1b05588996f8ca7ce0562878a9a8266d889a03b5cf0f2fb63be78735f9544ab4cec7ac59c16dda5e488b185b62bd528e9e19ffd4a52e5530df56d0f0a407be88f4de93a16986c0ed4e3e6433a8158e71c20d23c0bbfc107c79526e4557e05e681754b0571f8e8a5f7b03bdcad40d29fffbd6ea56a614b41cea3942dd73d6ba7c347ea9471caa6ebf7d7ab348fcb822b808929cf9dad5741f9556a6c73af196d4bf904222325a45a9e906976e93fe647b8d8adf09e5861e510a821b54adc469d9d3eac678229742a0312da848336eb6db59955c445d3f376f6874057af80d7dec8bcd18ccf140fd6b0831148c775656fd9fdea9c539f62adbfb21f3bd978e3926f4e64fdfc806779260b650e6d6cd0a0d534449e428a18620c98e070876f9a3daa5e5c5c8363ef1b6f4bacd049ae85b09707c1a5eccb2b26e5deb424523ca53d307b01855b4b62f416e2513eb84ef45f6a4f2f4d6d03edbbba4b05b8501561907c58cf8b106d6e2ef3b4bc1605a2ab601c36be51116a617b2c2eeed06e44027bc7b1f4cb023e3aa5de92803782256b75c5a0c7e9e30ae6c0812ece56e00b160d1a47562fbfd22db64204ea2657f713ff995cfec26972b056dd41671e17bd693c81feafbad81f73022d5f5e955f13a1de1a6b2f4fc4b975d0b1b21b4a213e4c67850abd75569df51a3c516d11577f2ba23d320b1b2e852469f02d762ef1684d514a8de7c7c7c4068f2475c52d2058964a6fe272491712c254e183cc293667605b9afe4c7151d6b79d3c4eb2f493e2fcb1afe4753e212fea6daf32293597b00035c4a0d08268ed71fb9deb84b5e5dceb2680026cedff5902994a259c44373cf7b15ec29e0893994772278f74280f5068023a0ee7b7b03393f0ef95f6acf13b6f4f3d3543124c867e5ea070a4fd85fdb7ff604b8ed13bc6a747abb9843f55de90478b66a21861d86efae0a8e53d0fb17fa9d055afe7a9e2c8c3f007c874fb6814077fc863f66f4141a48600ff7236b7602666a599ada2d22e6e0a968c803153acc98959906c20e6aab31be32554f4719a89b1a2680d12e9dbec0255699f3ea87214c999ce0c86fb1a23d7dd6d40c225bd449265bf500f6d6a01d7b50883eb7e4d32e0e657b50e39e36e1118274435dbbd6b30ce624509cd7894cb910836478f7fb2a15611ca792178d57f78984459496e97894409337b6fba4fa1d807f21a1d930801ec10608091e3fd4768f5f4b513906c11c750b15e72d4e6c55ecc9f87ce9f4100a29ed52fc29d936356e72a82f24966ea33213db9ed55439f90e7fc720c516a68e6a740d0c08cbc83f5aca4259cba5b2adce4b1fb689ebfab5ac149ca9957a6c801dd93b64fc75b4fa8f9ac3930145710c63f3e78ca0fbffd058d447b941815fc38ae182c1ae539b253bdf1d3b43829bce332157d12743c76e2a30826cb9de44a8415097f876b6822c22d094d35618045444bc515aa0ca28f3f14c24f0fdc1e2d3e0f6d1410f37c88e1057c35d679908e8f50e40c91e350472d468d34590ebd05a11b88587b8da6c8a2be46370a9bef2b2d42cb30e87904b5f6e345d335336bdac3f37781f8abfe29f85b9a4cb8d450a78cdda1be4a18b3f31908f2ce85805a0365dc7677793ab8d68b8c7af6438afbd211ff9109f0d322ed630f462f2f954b63d2408c0b2847113a23af6d434d0e746ff24299f9eeb1647e1fa4da63849f01379673dcec6875d470d284a24f9db9ff890a47b1fe0b55755fa998a6b32b88102dbc84f4c5796e7fd2334b6f15335baced92ff0a4cf253a6676c71983cf893eaabd3e93cfefadf56fa4da19a0bedcc46fc5b479fa7180307071475cc03d73cb686f2de4c9fcfb87d14d1cc2bf4d99fcca7b2a3127d1d0738579b8651cc32e3778afd3c698c10c4c1bf2d64a1e41fd4445b1cd10ad427cfd91d0630578aeab84ab548d00ad4a03d85d04edc9f4e8def432ad47ec3ef7ac9d20a44623a4aa3b87cc3a07b14fb0c07d313d02d1bfc704e01999139813ef1586549aabc0c1f984d3cf7df11b3e19ce06f67e36d4093110303fbf0f4668d8dce7a512ad94bc7f6008c98c3b6c036a0c7cb68d983a3c39a7e0d0d63c8a0ee75c70da24dd1d3484904804ad6e1755c92148c51b8d9e90cb9289cfad2180275a015408d83bc7ce12207bc48a61d1b9519839e081390850d51319855e220c5df296c52f1fdb01e25db2d048866b58de962590ce337cf77f986eac751857f1392a64f79707a3ad42bf6f1186414c7514cc5a5e2394c4ce8ecafa598f4f0740b7e06c982da5f7f6c866e4f12b07db104ff3f264504643826048cf83355b0323765b9330293837056746fa9cfbfc423e7eb7e96d0b784e53679e557bd2e24199f122a1c849de09a72aac58c58a18d1f44e116fa914c129b325f1c2e01cd00a4462d68f2d5765cd794064c37e278b90f351057fc2beec202c946bab29efd3693ca8fc6904c81a8ef85de40e3c3811ac6442ded8051ec73b2c8660982b8b271145b8d68477764362962880474e9765358c39295a8fbbfaa0c74cc2a2081888d02b6f094f9d72a9f722fb1ed0731a00ae9d41937ce9a420b850066b515d6a96e209c03433857fce0c51bed1a1cc8e8aadb4a60c999b8a894e7e351f5d470c74a5ce15c1617dacf4cd5ad97d738365df496fcabcf37998b52604ac87304a0ad52b5ed19b720b66216f59c48e8ecf3dbe906b1b271826f2c5872c55778ba6cd9a6ea1a42fab8ab515d39f6c23ad107179594ed09078d4ba3c8c7e104adc9881fce03190c68cad30b398d88c24f4f801f46252b6f12343084f844ada05d13e1b33c220904e913a2dc63d3dee26f15c4cf27533a8fa7f8e30d3da3950f93229f4da06748b773c07c24bd0d59a0ea502741418a980389a52ae296a3380219457e9493b46400291732e3fe7ca259a25e8c58aa173ef66abf170eeb4bf1f2e591b4952182315a9860a117c97c3ea7fce33a1a9334d1b7d7862fefdff14f68c0f1606f2c51524bfec7d4297fcb2d3100d1a7aedae7e05333a16448d3a7f0b4829c60b330e672dcb38c356804b8790c478ef74f61fb96e3c90cd01c19f0ae0a6094b7c3089c6e1cc396573d8c0783d561b072a6a3bac71d8a391e69be35442737055691335886d5bf2b264b7771892920140ab9c1ba4d7fd81c6f561e8e1344662c1769566d87101806eb9e1b9e5eb21a7b92c2e5ddc85d98b758e8935a37dc86dfb6ec9f460f80feb6ec7c2c5b929fd6faa2d7d692377f3b8e4bb33c898942c2c23e3a332f2796e88e22f720af86f8650e7710ecaccdc5879bad1d5e46c41ff96ede80bc0b69e64d3dd167915d149ae8e7c3bf8cb5fb0911f2794ce0bc68237a881231990eb324cbddb9b575f86cda0678d39c0aa054a20b2521396f363198fd1acffcff81585cc255115c2fbff2e3bc218afc2b4da254ae2d337c4e1faefb532ee53d8065161495441ac587cfd391717979bfc274924a39e4dcd2554ba1533a888da1cb52a47ea8e082b4a18b86e48cfe3acc54d2f4c64c82d22429eba88b1e9e663304b95df3e22509666551bf971dd3cb5fc054a82b2e0212600e549b476e726010037c6615cab92849bd247046008e9dca88207ddc5cc36310cc88bb284d0836a66cd231340f36f3ff6fce1e5bbd74991ae4364442d04635e6a9d8fcfa52a4425ae7c82df238a8be7e63217ffd6ac3104c988e2894caf1a12450da86b29599c4fb17097e4f9a8e7ee167d164d1990ad117a08469aedaabac3c919c301b5babc1fef4e135382f3849deb3aba67a045319c5b2d61d8427f138d741b8179bd8f6d36e73e4baabfef963e208ff419e65143c7a028394e227c53502eec93591522dae8726ecc0d5baa77e73f5ac39707c27fb1004ae16bf10fc565d31d4eb381c6cbeaea0e4f3b99ab9f6e30cb455a017996c0fa7ae1eace7dd644f8f6ed2e9d9171461403977559dea507539d8157e52a698a82dc88ff53678c3d0af010a441bb0107dfbdf19ba403412bb3a41bfb7468b6258a77f2d1e18434b97e09385bb74e90b03abf7a9a029d780ba2cf0efa68700a99343152dfa6adad5123f70c3d9eb4c85e3ecff798e54045b3dfe0fffe428bf31c611ab08257d3a75b296d2da6d7b94c2af28ebe1fa78b4cfa6a24762cee53b29b85cf10f3c42b401846fa1d800e6e549afc70e9884237f520da7a68a0bbf765872b57f26bf29425e10c0770dd192a94944d50d6a0654bd62e3513bcfa1648e339abcda083f8a522e3ddb0b4113c5b8e955f267a5065b64eed0c24c983cedf3a36e101afa0ddec9e7a2c607abab235b13f794d2bbe88e585dcd959693a93cb0118f8392a649b26f16ef1f93232a1fd95c522c740393310ec318d5a37dc382fc3ad1fe3fdb64cf44a71053f0ee0cb60b57efb47a683eec6ef4abff5fd41d8b055abc79364e2b960376d26ac7300ee4ea583dbfad6f2784362e8a9489aef24c4ab262b63df667dda2ae6d9c3eed0ff5a445d4f031369dabc861db4a74b0c5124f082e864e3531d483c780c009c2a66de1d0be999ad2deaa746500c2ba2e4c797ed4f57376ab2d2ba017af1ccdc1ff343105e4450dc9172bcaf03966a065669982f443f3cc572154758637eb7b02f3c396249699b59a461a74df339b3f967b1269d1f5551e78c8a40bcea004cd0fd216664868867f29524eb75efd9bb71e0f08f9f21314803697c583e9384ff5da01b7692606cb6c51356ab399e3a41130e782422102cfc5a095c3a918b06b1e2869730590f408d5d478367f5721e368d54e75d727594a778ac4a3e188a44305f0c611cf1c7ba90b0f284f52a245ac0e5667b3bfe9bfa32d077124189fca04c8fb7fecdb48c99d96d497c1fff7945c48c2a13a9c4ff0885a25d5fb843e7f80e21b7234335141d008ea78c3df60959c8467398beb19c997128ed67b122def88facf5c52f86f7aa1c65ef017ee387344a35c6e2bafccd256f35d5ca195cc43e88deec202bce1fcde419fd59d3abf12067be1d67d06c07adc87eff05ef3ebed3fa5b9621f0f3cba6b19d26009952968f7596f313dcdcc0ca6a423578720a1d430582ac2908a7bd295c8992353bb14a092dbddc4a2b9c6264a4dfcbf26f0e8fa4bd8d64e97081c7d1477365f97f79f950df70e9aae1224462120cb029c635b8cfb39b3653f13f795e3f2094937cfeffca4ccc8c09b8334ffaa1ae8750024ad9fb89fdded472fbc6ab425f20e96dd2aea6acb4f2d15830889a0531cf9dde2bcd713eacc09648bb7609be257521d59f2532de6198ee98cdecefde9a1751916c76494ad36020b95c6b8e0a83680eb4f44297ce15fdb21c6be70a170f6b3813f63e1eb0ddf1b239e80e95c1fc49f25672cfc0d90f849fdde9c3bf090e6da84e30e999500ee7337e691d71eff4df09e30c3bb222a55c1850df266e1f74c2af02c69bfc06c2f46d8656ab10151600a57737588e18e3ddaf228643cbcbe77487e3b9b5f14d4c8464e7e980d406efb926d465f5cd51d1c1dbbf12fc3d8cd4e43cc8eb91bb477679b47d7288e8f0c3ecbc2e52df50d882682b8911f6c5a05b27bae01988015d59ce3c3e032ba4ef2d7c2d9514318021404a9cb6475c559344fb1450b148bd8de464b2ac2e2b89cbb4b7ef079424a5b4705817ec876363262181dda41d8001a071411987a54eee566b1608ba445bad5ea61eb6fcfbb219dbc2c5ddb90a2c502cc9370288ac84e3e6d54ed60c690f1198778c213f9466fe8dfd7037a13744da90bca11633cb1e4c23776b90383b5ec52abac49558aa2e7c74d1bdbfc58e39531fb0c312705d4ead7e6f1ecf709a7173a8ab4e97cfe5ebf2644cd4738a9dba58f948316fc4dfa301186144c8a801ca7f889ef6091c811d108ddb5721b4684acad063fbf0a28c1a1b467ead6d45d7077af719219078843f3885a8e711b4be17123ace7a5f845b8fcb4da74ae5c847ca4dd3e49b65eb28877f1c89731902bef392605724ae61300a5f573f53a74f6bd9ee47ba098e2656df384a4a1a1d9b04df1d000a89ab229448d7bc6e62f9a399e30c89476593f5116b67484c6396dbfa9c863342f2af43cb0ed016852c07a1a6e94290fd82a901d2f25d0940a4df13a90a82d156a0c6db95e7ac70e622b829da16f6aa1e3d6d50d491d735a308de22af118d610934a3aa71189c5c3579f7d256755cc6379cabc02e5941225aa76a406fd3bfff7040381c01ba0fdd52395cc6567860ae2c47b31d62e9637471072611c886201253897644fbafbeee42b7512381d3bcb76d63bb465cf8b52c0bb3138ec68f3da34ef533edee7fd38877fe1dab45308da756d0a0d4dcf636d6cd5c09c61114a4cc28548121e9775b72e9fbc6ea6baf2ebf2e11a650ace454382e9ee75185a79e4e1e8cccb83f9987b3ffd946391d1724dee56532b60fc5e9c304dcffc8fd6d7c2638e87b8e0c89f227f28962fa9aba2ec7daeb5fc9a309d00ab4e8e0c4ab70bef20077f5d47794cef872f463a70f1c2cd99f50c6a176febcc741c9c1751e9892c73bf40be3b5471fb7d01fcb22397aae5dc2eb5f86bfb5d1ae5c85a384c362d1f9e58ad5840321e911bee8c449bd489bd07ff9c7442defde6ab9827fe8b20c4b105750d5be9fef585193b761faffb2a82ce4b535f8fe30c77b2ebf178dde7f0e484a4be4231e00a31dd514806505c1c46b3e6882fc31305073cf71b490a1e6eca614fee30c70485a97d470e1711e7759fedae6a25efc99d7a2c7dffe2fc7ffcd17be07d5ed178b2611152c82c69181fc751cf77d3b7fe8a93c7a9c04df6d7f1dd3d3e7fb2da6077bc637cf9b7c880e21e1cca517f0817da785997eb97c99bc08adccfa5610b78653c20a12888404470bfc896d4d03d2d9cf6396434126dd326a97733998f11e82ba88c456a1a897bf512aad9fca3500dcb2528128101a395e63b5bc6ffd24c069b8f5efc7de3710f5f458f48e279e9daf8011b60936c982df8733c5c275fec22222a7101f5c22c985ad1b890ba73a82fbaa697952833745d871d710b49bc5730a4189c90c14dbdcc1a0a49c3535a6cba9d394480ab004d2eed7acac25ec33c0eb3cecd59e98d8db11a1d84c15b03779ec0bcaabfbe04939211e48fc40244dd0499c3e075753e6159e0df8b6cdcdf595fde2531b193e435fe0fea9aec993d7f0b6d492c656904fc509f847516510e3109bef7f93e5c441845250ab4cb3b3d2cafb7c0d1ddc6990f33f94abd402c1755ea57a2996e1b774e5ab144b454742140c7243f8fbcaac21a2308baaf947f5971ded9372e0203b26b53c379d6fcc30e86de854320554b32e889bac7396b40561e7276de5c79d3c871f33878f984af500d76cc73229b39f2f6604cd5035d96dc396174d9d854461042fd80edd7ba511eb84f5bce64f05efebe22d3d274f4947b5a12a2002174ca5ccdcfbfff1064f612a49116e17d06b2332b310899e8e04c2ebc55e15523d411f7bc3249eab2d3d0ebb311dd86f9fcc5fe81ea109f8bdd57aa0030de1cfb89eb916cf9bd81e786e29bf627709d335a20bdfcb332a7bac7b5bed134b90e9e094ab1c2c654c7898a5ead3c352b313095b55ec91cde6aec6ba0d4643a65266cb22668dd9b8cd8bb333c8765ecad3a86bca291deeca48a15d7b24ebfa96954f699d41d1b624b03f55322c605b0d3f8f3066b79e53d2c43315e5cd14b0d8411a5378a7afc745c5974799a74e13c5500e2831a0e7353d91b0fb64a2e4f7cc3ca6d29f06f489827243b0299d19665a3fbc1e1a4e3922a7b83177e5cf012601c6c4bc460676ef8dbcc47ce367e4dcb2efd596f0ba98d688e198fdcf488c94f6fef27d368a419684b3aa1024f7c7c0d884aeae60f66996b998fa8b29dd71e46d7a2c6944426dcba89e9447d30afe444f69924c9cbf9986577d0b9929710928da1cff9a8b85cefdb79c1b21bb12c513119bf9694884c8eaefba81dd4893c93e0d97bb8b70ab0843e0d36a24d17747a7e7dc26db9bb61316f94113af354b21e46b608e7d6a57b3aaadb3402f68b8151cc740aa0eb10ee8da1e3003bfb004cf0132ed0289fe784d7c8af65ebce7d86ce1e12d438219b85389e7c67d727c9be6683a12871975e3785ed7ea43f3275a3b78b08c8ff8bd2cf4c2d3dbec2f39544dc726493ab3ef2536f313e6a77bb6d623d8e8e1b925035b53a66b429ef77111b22d3f9db082c9e24d5953a28d818bd47ff27b602ff680bf42f98461e12166a841f4e1227f83a97d0ba85f057257243dfec59ff6e8a16238188228f8d416be52ed655cb0f5b8d7ac508023a981cf238d385a66eb102647e6e2185bae0c4568c1efb09af0cea464cc2673eb0dd5263e473529a6b66e643a68bcbd5a15a0d817ca9a10997debfbc8bcd4bd0ccece7fed8b3d5ef0492dfe676c7bfbea2b52e792204566c96c39154b4a62f2c031cd0761a21ac84a8a9c694d53251b544315675a40e9b407f4807b8dcb229be9a54fdb4eb0a8e58ef6e83f64e60c5397ff2e62448ca933009c111b85c53b247456a8e0cfeb56f9f0f31db62cfddcb24cf35a54951a7724a5dea6139df116a6102239d2d6b2734e0b9762b3e0d11cbdb7ecdf3ddc8262504ac39e0fc33de223abea3e3bffff553220fd7c449afe7c0d88a5218d28b0ae7c597da4638503d427903917faaf9df0df0b526bbef8ca7427706644f8fa3e83610206378234a5605d68ab44f7549f7bc04bd3786f33948b60d25225c619da3fad7b76b38cde5bb24ffbc6f36eee4d57117037851707b10fd5b2345c786340c2156fb785fd1a24277bfb9a82f52be0c448104281cdf1dd4fc44bbc0ea535b918cebccba708f78637e2fc23e67849ca2da7055977fa38c8fbf2420b3d68df73d47f10afa87dcbe4315d8fca16bc62ec365f7ef8584d43be3cbadba65bbd11889596c90d8765e5f632487329acfd2192b74c4bbce73869f68da7e364b1d87cdeccc48547ee376fd39bfed6791e9df7fb7618b71342c74d4cf9706f9a4cc4c7daf4c59e5159eabde73f4db0c164ad5b5986d614bf8263ac728375e2a722c1db8ea82cb74aa312b5d85b66ea1fdf6bf2a6c8e781303f51d0d89995364e86d46b70f72baff39b3e822933376bfd511ff4fbd877ded65a629cb433e8cb1b313333bc0336ea17069d8c460c9dd54fbe16ecb198563e4aca3f98ed055befe295a58bfe05018819cb973a327ddc44c0536c1a8aa511ca8694549dcce404661bdbd14f915df2c6ebe782e73ef988a31b96a025608d021882b68244c841ef86217ed97146e3d59e84dbc61290cb312d4c20271f55f2a8931a4b1491113401ab6cbbb94e8a0151282eaf34eb423b31bff23745a752e1aed2fc57568ce222b8efec217ab499146b3741eb9433107137326b8c5d3411ce467fcb4fbad9971f5cab0334025f33a6d5eac4baf8caa953fd20418f3a08296ed1e9c69ad84f9f726c98465cfc0c440a3ea6b57fc0f8daf160dc557219709cab043432a9dc01de9f8bf4f551a1ab22d802d66793c887da0c079b3a2daf731cbaeb58cb648c6e350b3118bcb80b5be4fd7f2dee182271e7935c0466aad2d31a12f339c0d52029b5108d0702b9ee9aad2c1d0cda345408f90cdd2d60ae9bccceff4f921a8a323d3d5ac6af26e61db8cf7e8a0d8f00c907cedd730ed13a3182ed2871c1c9a2e49d40f5454eb101e9cd94b7a805120bd0e948d34bacac14f96d8e3dc6a262f3278cf3fdcb0e460618db29ce2df78e39e83d9c7773b3ee3f8e24f4e985e78ba3e307972d9fc277b0687365208ea4c1d38285bf1b1ebd6c386055c20145d8e6060d297c67962d049aca38828fcfe5bc8786c74cc5c2c21c0424839984708b7170cb78577aed3690e62041bd03748448f749d169cac92db3e24de44a5bf5f108c379599d42cd898235c907357437c2367167ca841ca4652ef32556f590475586821270b2cbded67fc0786905064d14c580608bce0255d33f21d2949fcf1de07eeb63ac3ecf8b9e8d632fe5baa3997d7c2233a9c37bce1c3052990fe6ec4f1a84fb037fb17a2f0ff6e90efdb52eaf07a0aedb8f3facda4d59fc12611e01a90f71f3657abdc33b0f7d3d02e772f32cc8037ee3935d6b32935830d69c034a1877bb73da3844a5e4a9f5fc6cc12692ba6eecae7c89bccdb462dc7c81edb0a65a97ab965777f8dd6bbed9d734cb36d2493d64012589bd6f529b157c893c866343f17bc87021110dac1ccdc560f257483b5a9f42ce5645f4b773fa84462d7227c85334834764c2e65d0c410a134b5c82e0aa4b35ebcd80987d907ed47ddc5fc9a3552e80bd0ab51a1201e2468e1455f3e7b1ca9b63c6f2dc1641065dfbfbda817b65e6553d74a42479739426c495dd81ad0ece43abd657f564f1ad5360222c8da00d1da0abd66c88176dd4e9ccfa113630606d7817883765665dd308e3ffb5a44a99f0561c2842e3b4e0cefb64b6b01a23f18e5f6b55c04d5b2202091eb47bf66bf19093e0392c6b0c16868cb8e698554ea7683c516a9e6ca7848de0018b77e31d86dd3f06256d0dac0312ab5bdcda836ab8e7cf663a01a2a1a2fd0cb7d5c5f1a8e462519e58642d1f4ceeb9afb76f59f7635fb051e116f555b1378a4432a0633ecf57368b993ffd94e14407e35f9711aa62fef293e824035ddfc3cd5354e21fc9576326ea79121ddd05e12a268064e07d0e023828f16fdb72920c5749765a32afe788067d0a16f8d9c09ae624b065d6cef8e79bcc44c02675fe54964a8236fce226ff020aa951bd64826571ea289017e424a53997be347341abfd865baab5c76fd44475f885329fc7457d2cd24c4bef6163c0b0d294905ca9f3569945a2bf191f72519a05948d7a60937c73628493b0afcb56e30bc0cb43a7eaf02f564dcc00b87a6f18d3372cf3d795b5082e9151e0854d067ecb439a02b17b352ec0374c31177da92308e8a294a00f2b70876d962f6fa30472e909aa6ca540627c651e453e8a3dc8be31fc86b78a76d527596768d9dc328ab584ba9c3a838b71683ccf914184f02c9b86595b556edc67e27d003ece8525105a4497b75b2afa1729b6b3cc5fbe8f03bc7adf08bea68d4f1d1f0691b3dccbc1dc52b44e9247326ac61ad3034e66d3cf1f648d0e1cb3cd89df5fa1dd0d1703be6c78093da98bad0c6f1a5632415cadb8ee7e146804d37f48136a81efe221432bde0a79a0b524c56db635c5dfe6220524a5e2028cd055397a26f85545c7eca12b78ca10faa0b2d88717032d7e816bfea16f0f16b589770f19b8fc850cd2e4e84f8717dfe2433fd774ff40c53f428eb7b89e4d52010334c1f9d27944070c42c64b59251c2eb6117f574bab20ae84404836b346d8391b25bf4236dad98b1b63a7e68ec0aef40502d4f8fc7261d7f63444b7622a10816899e8b8c770db00027133d1cf56716172b3f639b59a701b37af34dc76a7de4776ca77cf504ced0257c27a57e77f274532f4b217177aa9cf28b6bb5523adaea03985391a34f67f06edb10f13e83e7a38279b893db6647c8845a4c0259883577a83e4d3e1c945f044f3d1cf1e687369c3ad036ddddca3b6eeb42dab8ee7b9b81ac8f13750324d9583a1b8b5322b47cd4a040cc5d9f823fc517cc2e3e405aea619c281d02727987a7b134a15de2ff0b560c1d0791daf024011b7340df5da612c44e5eed676debec01f2080390883290a1ca257e103f15a8288c2db9e230f0b004600f6fcbc0b1755ceb073cafe3745497d4f8867d2d109856a73e8d16ed037e07fdeee6e3ab7c67e6a59713e8197ba7a7e8282b82e7a0f9d5ddb34e82c20a3bca7d42a3386c233825b5ecae0800d821066169f955c3effc34f0badefaca5cb16723644d00e4c3e3c564ee266c9c53a66a9d9c05dc2106b15f0e4876d42e342641401e4b8730f323aefc626b4af118df949e027c187f312f8240545fe6f7f14f15fb70388da8805f16d27abfae51cdc10071cbb678b26f6f4299ac1fc26a500c0e9c5827c737070880462100695918d3560ef30a3a41df5f9b433da349de06762e0507e14ddbbd0962c245dcc2b9a060bddd3f41aea6824e2a160b4ce4983e04ff88a1e524c56677d71305ebbea4387e46b28dcffbc20fc7db41a85ecb305fd5f204320f0182fe34bc02ff9100ffbac7e0a6a969b621b1486b9787d4e4ad0543bda6c5e43debc84408268df2b1828d14d0bcfcdab1bf52051ae32df0baef37ea77a0e311b0dc817146643c1b5eeffbe85ad49c757291895b08b65ddb39dae54bb1e0639fdc948440e1a792e7d12cc48bdc7f5bca4cc98f8ebf778de000e92bc5e805a709cd9cae0f9830be3597d545561d3ec418e3a8122188d250cccc578600b39b542ea3e1059c2b563486c897842a89b5196a1c35c480847eaea307fc1ea0ec38d904cb0224d2cb8f5d53afd32b917a4c7fea376030e6da56750540dcf6cb23d760149f7c16344a42f7607e097a3cd985e2b35fd77afe22e04fdc5b0f1c2e483e6b7a05b675c9f6035e29723934d60f050c43d381d328247a17829cf025431b8e7c803561508ebb35091b790b72fa5539927a21ad090e2df510662b897307a424cd380cd252f3f231326bcef276adb13f3d52e461ecfefa94ee89fb3473afbb62b0969f73ffb99d67b21444b644e207839278c6598a96dd5fbc1a185e1abb6395ff4927d14b707a2f1d3faf58f85ddf6ecf5329d8561f814c51de8f6c1f19e07f32bf9323ac5fe6f5bace5b476c4ae403085110bd2ad0585b891208a40b54c1fcb84d33d8cc3d9c47cdce5ae1595c4f80007cf4b1df3b644a123076d9adfe65f2d6a1b22393e3d01733a7185b04cc5f8b6204973269affa3494a963efb2dd603e7950d6056610685079e5d95db507a5c935cd306f02398d1564103613cfafc2a0ecdf7f793f0c21113d2686b6362a6e40f22773b24009ab6e91ab26964612cda4a36bd20cc1e9615cd0f73422a2148ab3116537ecb468e4371b3b65c2042d5b8778946d00c8ed0f8ec2bf70e329b66bf0fd1679d88e35d1c72361e3a30495efc0ee9712edfc27f0b6d6fe539bdecd1d2ed3f40e74792e74ef74ee3c8704ca4347af6e0f44d7a622aa6c1bba98f4d14643754156300cb84fff44f40b36835090dd16e719fc99e694b982988d8f24e9c3a8dcc088d95a7862d863c426fa48724bb0b90f8f78096b59735d7ffd708754e825fe405d3a71c2dc42eebd6099e514dccbdc75ac613c194ac9f0602280a83f98a7563bdb7ac498ea6d024d28719dc3ba132f4a5087a1b8d5d0dfb10d837a17150d1877b87eb3689fb6bd60d373111ddfc6a434983b6a5c0533ad4195359510df316f64046c7117e19b31685e6c60d4d875ce1f216280b00a546b19307ad0e381d007783e58b793a3b5b0e6406c992d0801d0df6c99ea6d6d771d9256ef949b507887a923bde54b2eef110861d33c1558870321d30100af179b872abb3c207d311b7759c32ad6d06daeef3280264afd14814569eb10fa9a18a25b78ef0e2aabfe2660403075a0c9a1c6fe3cf9fe82d8dad96ffd3f407cebc7ed4704c8092e5a8d95cc2e438cc606ac1e092d6ef72758c2d60017803caf0ad413bebd45eda701de08717c768da7b440b433d42d04dd0f6f973df16506a246b2032cf24e01e0826a0262da609b87914d4368bb16ab0c35c16c8cfe3a3cc738ef4e3a56a810c8c62cdc40bb121da1aae6c911907e75f42cd5e0a756212135650e0ead81f63ac5f8c521e522d86f0ce45eb8485f9281bb138b93430395f5e5e7db556b46dabfd0b15a9a47f75d65264f5123ab95422c8e5f8ccfbb55d481f8532eeacedc4f45d86da88a31ae93a0230cd3e45ea2e91624eaa5bc3b63d74f868be2fdedd83c1fdcd24f5c9407e7eecf780c5bc74701b792c788a146dd1107348c8f4cb69a39a93081361854d99546261afa9f2e7cdcea7e3e361063ec8f54e47bfb5cb382690b5036d20e8127437e17053ae7dd9ef5f302e3ac9dadcc197e37a346b3845d95e50e2f023fb184c9948a2bd441d02d840b3baa551082bc50f6c2f5591eb2f94ccf04e2e92aa970d2a53ec382f09c3841f15452f331062e3a5041d89eaca6464d24171c394f3f568962cb119a292da9d6d0ebc0836bc6a791260feedd878947863fc2f30bdb908a479091a50f70cb363740add9b19a26056101634e84226c971caab804dda3fc9475774245ba63453bfc01d3a1ea4771003fbac56a7d9ed25c1032ad0a72d71490e9c0f1eb888c04ca2446af89f6debaa211f1cba467c66f9b6a7f4f541587414fa11df2593eaec07e4ff0702e3716b723e172a41a4e58738019e2c0c22bb8b0daa92370a6c2533899dcda7685b4878e452d22c3a679a60e4e8be9e9c57eacd51d914a3dcd28aacc2b79b2e19173d3bfbc47347ad9f79b704e82b4bb1790546763ebcc0e79965aa6b207b86922d1b24279c28ba44742bee3892b74bad562fef0477547190f8af031c33a1a2a32dc4de3cbd3ba7fa0141ec0d55fe0ddc5f23d423488e13811d9b7b03356a9ee70d1e919252d542f4534ad0836817c53a76ed9dc4d45980654952305b73a36ee7964e7f94c7f33c22ae2e9d385505ea0b89fde8015c7a687840029af9eda6b109a4890a5a8ee18842210ba13602811fbaed726eea2d95cdd7834bc9df1db190db964d058b1f985ceb10b50f94fe1a7e65747bbd457b9dff8f80eb00539a7336d2b681619eedf33a09e5f4f96ed3fe12c6e93a7e259386865e6bb8d0778ede5c08c95939e6958adc40eafd8189cfffb2c65f99f4c9344bd7b29e470f32c874fd102b3dccd6bcca3e9c191ba38e3d2c91a0801a2032907f7c2c920085ba8a71b7b9939a0961dc9f796c0187cdbd07919eee538f47a6bddc982cd49c4ffd129e76933de8e158f44e1353c704c26e42891897f58cc8b2349a67859289c30e9852c974c4030d398932ff5a1cda80abaabba6ce93ef20d8d8392d4a0a3e6c7121c9cc24190ab68dcc762df4015eef827acccf11f554ceb6594ac7c2cb6e51ec7c03c3e163b942a750bfc7f4f1539c33073c142f0b1be9a69965399411dd3f6c4df9f5d9912eb3ed09e1191e3093dc1496e88ecd03654e65bc1ecb14ed14a60ebbc913cef32bff7f8f063f43a242e4b3402afca766719d56fd5325b3f1788d49b7fa3742d5e3d1b0c8c7a1ad9ef0100035f7d10a50ae442e4d2d9c6c8417fdd3cb3919b9a2bed815238e33b1161a69aec9ca393a875fd823e8ce9221a276e2b6c13b3427167c99f768b31db70f8f56648071e73e257eaed4756bcf9ef34286b3661a23f93f41eaff6e00b6070257eb736ef34aca3e06002ff8ebfe27ef0f3d65411087707822f40139e3223cc1cad7af9dd21afc0a6632e0e87e838fc3c112a695e1634eccf67dea9e761fcb4c24f0a7ffdf576b1b1c1d5c6734a50b3621a1768330275714d8ee24e91dc05f7e6871fbf2a20b1a7346fbcffb614e251bfd8fef687dfff77d06dea4d1e09ef2fcdbeebe6ffd56f4f9c84ea71e97255f819a5ce34d406435f24d75a1e1f2454410768a5a780d6cda24647a5f968b418c5739aea413e21c8739680824e1cb4f2e23feb5c30abe397e0e0da979fe79918f4bbecee5ebde3df7ec169ff1001f6c8a8410eb5c146d2caa3d36e11c7ae20a651ff4c49999c0c76e19991ef4ebb247fe84970837c9fef1752cf4da64df791adee734ccd1703b1967cda82c63dac5979609062423bac67a1707e7dee077330c40e557b5dfedcc785a3621cc2fd423cce8928a314575b0e3c4765335e778be730f55decab94424099b482cfc29def59c8a7dd88d6799a77390c2fa3af9a6d7d60461a26be36525d32ea93333af46bcd989f50878685f61081e87d42ef12d8c8572eac67ec459d29fdcb1c19ce4429163f726b4d86de1cea274d0084dad4b33d7b1c09541fc568b5444593207912ee3023feffecf97234473aba21b7232e6a497f003be931e5542ab8932dbb9450515ce2daee75b64cdc541981cc18baf1dfeedb6d3c76b00d84f9aaefab307b02334e24f24124432ba8ca8d32b6a51e62bcdaf707689bf170571f2e647c8e020d5a7b0584127a37f91bd0577dee6cd8ce19ca30d65a629937811977d423b849c5be4b3871194d13d1c0030bffd4091944b0a488022c9de8898e1aef743643b1f9913d2c2ab610e470fc6bb314b7a80763ed6ee8f8a585721fb1405f6d0c6472c96c81ed766bd466729178dbd1808002445f9ada8057f9a205ff6d03c2abc99b34f9afec32d14342f6343a02656981d58fb747a3b75b15ad7eb40ddf5d366e548869d231f900e85fd774feafe173e23977d2211692c3a9b67112808758cb1c5a6003548a0d1dfca9781e5a9086e9c53be47ae49a18d2bd0ca12306c2ace63d73dc5b9c2948992d06b900df6206930ba312049a632877b351eb4d9846f5de4c543aea061be8bc207885324a38547e7f3d59349d1ec99162d72b78372230daf2a88bd5458272240a729c737d4da7e4779e26221f1b77df5a7cec0b8b205dbff86f71732eab9423b990f2900a7f1d7420bcc7ad82fed3c1b131fac61c6aff72897c9d01cbc8e3e298a00e480e129cc344a236a38bc6e2616df6abae7c1523bc294e4853441792d57728aa18e234c195082b140f8b73d751f18e209a04b2d246df6e50e78c19e752146d54174a780414bfcd8ac2722d1b642df67df3126720a5e47f9f3749f790db51967dd570d05a50559f8f2308532720ed24d2da0840cdf964b575daf006cb5ed43f0f4d53c03f0962c7ee926d6dc18308d79b6956489fa64f7ecf84e123eeecd35f91fe55b3d8a62828db98ac44a27d72186deeee284d268d951fb1a5413bba5d9885737b54b19fb039a29b2e493bd6177cb8b8b6cffb5d130b4c9653f1ee3046de38b05db5966be41298d0179713bd666bdc720a4892d6656f8abf3d8b0f072d070649f09080f687f63c1ed34b7384c3e6b0bf6c18807109f1cf2fa67f3bef9d16c1ea1c6bff3498f6492b1267170f56c789b957ae4561f3d123af3600beafe857d945e7c8433463dc1e965b03ae85109ce253d331dfa4e3332bbf5d443c29d1936e7fd37c0bf41226c40c0d4c3f8d923f16cb9c57db5f1ebdcefcade110f8b40406d8e4f63d89b86ef0a199d8e21b224d3394322833c95ce1c12c20c08dfe018b4c1e5dd42c29f953a1f6734af19240f4632d01bd12c897ab7af67a58e7f75430a8a4fe6ecab10b4e8bf556e4c82f1bbfbf875fb5986293f516c9fecc4cec5bc439ebfd7e16eb935f111e5eb20012fd5b9f0f9033421c1b6836560065d44084758f3bebf9a3db94e3922fd9a1e4d3fe7f334225864bb23f7b9a8597bc0e627669a8e7a748c09b83ee7b26b630eebf43485b984f6fd7865ce6359f7623d119bf6e645d34e58e2fc7dfc1c1d4baf8c543a57ead500ce6d6a38657585975d34cdd9bfea4ea6b2b46243545f914f8f08d8c1e1e15d3b0a6884813e0947ce2a3a53c628b9fe87e509a0150f32cbe9d2e8b056c2429538326e4bfb499ddc79dbc203f1fff4492d53b02eeb9320ef3195dd7994e1f8c1f293c23a76a3fb13fce83dab58ec091e667b16cc1c557abfba1adbc852abdb1960476099d3b01adadcb883c1023492aae49404338730b75e4834e12c3a3573219da09d93a296ae4f6b7f5b127dcd245cf2549c714a24ba6f411373e1ed0be7516296514a96c6ee3c3ef0a4de11b38ba7c6cf9af282cc9c45b69803e45252a35c5c4cc950bdb4b1764a371a103a0454c7d43a1f60a2ae90b246f9023a2b642382bed9b0c17342974c85778f0fc67fe577bf8d1f944fb70d7fdbc589547c5ca4221772a83e7636c87e29b9445d1186e9b8ad585f1df53b2c92f788251689fe36e5564806ee450c36f1280720d6147c027f2fcc715ebc7ddacd1a016730b03f637d519b31a17d691d0470eae8bd36f944f21317e1bd2cf1c27a8b00bd7b9040b88341dcab28c085564b7a37b6f9e9faa9416db9d1e45f25056c49b8f81520f0382ba1ff8b564f2b0163221e13b54bbdb745c8081cbf9d5a6d94961862696e973f84a009a309e5b6c4dde339dca1df35a2d7d09273e32b59fab9863c03685cbd98454817f395cb899b8e4a0650990d3bd7585807853f12c26e50e1d04168efe08843b49fe8331c15fcb0b7df197be691182aab05bd0de73f9b45fe874654213e93079b68b5515a7db956066df092ecbdb50856aa976e3434e7e0c6f93b478202574555744ad7a67412f8205e6fb1fa7001d2b8a78fdfd01455303b797b450d895829a4d5a2ba62b02a1699a8853e8ceff64576084917b0b9bd90a708d8c22a469a245c03c9c4e8b4d608535eec1c0a8d28c9d379f8908c7a87c2e3792c55a697ceaa1530f66e05aacb55abd2c5e8c4ece658bcbc22a63e6378c3b129887dca929ae39155d9d2a9085ecd049f5514f8eaee2a50215fd10ad34070b4698b316dc88a65f651acaa023288aaf4644f456c886a896ad93a369334ca22e6370f1ed929d48417e0aad3612bc022e0ffb52a21d1fde6602e1f59da088ee4558519551150a59fb4baa4c05bc67222bea465a8cda8e81f897d27dd35dc091c96972e69f0e0744932bfa36816796d83262378ba47ceb4e25915c68eb21d1d56840d6fccc3c681035d0a16d31e1581e10ac338f0db408aa5b29fd4b0ad1b31536c5a0995a976bb165d6ac0f90be033ba95716b36a7482315a9cc79e97827b1abc28dd232c2156f70c4d1a3f1e7b18b5ed02ebffaf7073bb015020725a9e26fd291d43a3a18f2800f2e61b1f38e9b76f9f062a093674dc951eea14732d2d2ac93fe0ebbf79f5882a7aca7ba57e0290a1ff0a15c5a1cacaa78d6b2a56bae3b79a1becf7f837d8e4cf9ab559e08fd3c8ec1f34e48ff4376525defefbf46a85f2325c913adedc433e2e7a4078bc5e2fe9d90b6f62f781a914127744444b7ba0bd67e38b03f38eb4c50fd659e75f6c0983368ffece99a2392fc42e23e8f3b173d7922ef3c1eace06bb8748c0aa13adfc19b2953e196aab2b7834dfdd04f0520c03550bcf6df4ff5bdfb7ce30adbb27adfa0ccd0e1d6895745a21682d5fbe0ad39f5689ececa7d1bdfc8c1b1adb9286a6f0e9793671841244b76e723e94216e9a4ee6f8d2f42843eadcddf36912504bde40e683a38cbf970af2d4183f1c78f49d92b364324dc35b0c75c8cc00bcd02b8f818ec91e1f89df735f933e4ff81fd7c2cdbafdbdfefcb6d2467b0b5910aaa84a051780cbddf3d5fd52910d31be47256e5be39e40fbac5965af40d199210a9f1b7e81ce6c47a4f3b6427ded880db6007a2bedcf66b1ba6a7710dfcd079f5176a00fccc25b12c2a66c371a271710c3dc70cef254c0c341daccc062dc7800570be1c12b082bd63d4c1f2ad7cdb1f08b5a30e1b8973ff18040cbf1320f0d0e95b564fcc94dd68d97a455e464e69c1062b85679623266e3183c94e06eabfc4332d634e727a0038e59109ba347ddee28c068d74134992420d2caacc2c1bb8cf0d8f14dcf8c930d851b08ef59fd13e3ca3c2466cd912a0091d2bceae98f102f065a8566b5068b2b1f5da2edc7406d58ef240f780b45448d432d8c780d68f6d3e0b11a2c07fd6859804c8e28ad5342c666102510082c025e0238991920be52b21a3356aca954bd6eb9e39318701395325c5930271206a7b6871de2a5c37037e45e07ad5c55624f14416063c160012a0817da1134829299e3e370e3181a17c6054ffc56ccf902c36a8dae8d5c0fb6e9ce87d395963012805995e43275268ea440accf2617620e7f14573de637b27ecfd117cc1e1541bc859cbab0158238c5b54da9561aaba7d1875a62264b7650de83b32e588c3f0d0716acb3a1ce6e6cae2a080bbbd4b3e5b3609234c41de6681ccf7f04eef61c9030cb22f65473feb7fc878a5f7fd83475dd2f4b3ed77815ac595867cdcf9a9060b71672024ae3609cadd62d5cdc24483325c58e1ad90f42f012adeb74eacdc1e8021c7470fbf122618bad4c88876ecabe808d531a85de449a8271581be4de728f73f02db9adc53e64d674b90ad61994a4dc4eb16d5e3eb8c20e997247413a366156a7a4edafe662c3c3c0801fca05799c4f878301ab0e9c9fa6cd40b3e092b26788189c625e59e2f69273501502c1a886cb454180812a93f269addf13657a67e282b59e06c27a0618c129497386c99379edd48c6b8a8a8ffbe982ed66f201184e1fa0d8141168211ef4568c4c4720404c893fffe02f92879edc8ceca3bcc7fb6bd62cc4368fd2aeeee7f88f248694e8e7afc4a5c0f5bf8d61625ed227a1b1a0b9e9358b1dcf92f2138fd483127ea5046888b389201555cee98439f08df19745a69e9792057a21b1f4e75e447569916d1301505644f0a8510e970fcf3427cf22ce6f28471d88cd6bce768ca7f4d0d449316b9581221ba501aa5d9fb58fe4d84bb068c8f5f20af2e6fe3c0a3046199a5ec2ef5d6486d7355a76cb5f8790b3415c0931ca7b32ca065e36174280a6ce2ddc919951a7464ac6a4db61ff61de1f8e6c89eb23ded61bf7ddcd32a4fc7db45d0d9891684cb414f759961ad5a41ad3bb14186261fced6feda0479b05bd99a78d639a3066df1f5b08dbeb0c9c27da8f7744e3d98b9d4082b17b10a5f0593119e0cc63c3cb48e253636ee70b3a84faf0876ef0b387670e641b5fd2577fe838c920e2e9b9cb98ac698c832c9fd54f5fef3fff77c06c41cd73346751d4a7c70fe5a4e41a05cbf9bf8fa4edebe16a52c4c11b2416d30537691bfeac03b28c85341cff57c7fdb21bb1858d14aea4e4a0ebd69f1df88770e2dcfa40450ecf8fa3951eebbfec871d043295c0173ce751af4e0650e9842a69f58b0a1a0a1e795cace27e05a821cf4dcef531d31d0ad20f14c75352f6c4e1af3d57ad52fd4f57d52992e2ca6bedf1a7d7a8e97ce59fccfc40976264c844f881b0cfa9c107f351ef2968e53f8c84ec5db6e40adce48a63ea09bcdced80cdd57427f643e15e8f862bb02adf726031fa2653ddfa1a30f9af08abff25da4b45bb8a122dd3cde29cdbe1f6b0c24b32a94fe483420c91e8c130fffcef401fe8f9ef0451036638f6650a337da5bf11d1237f8216be2b09f3c233672c1f87a57ee27fbccd3e828b72172c5f2872dc487665538c96e1dd72ff394598f69e5896dd3b81946ccbd295032d9815f83a0d026cab3c541a1594bdd3e7818b743047817e7dc11d50b0d01751be0525324fe270d7e917acb042f3a35ccdcf27b9497a9a4218908c3ba264e88b02cd43e6b8938f3c13f03b02047125f5f327d0c6e2125eef895aa3d7e66013556a4c56cf3494f20a5a6160048de5ff8e33f605367c062c23f17ce7f489e6d517ece617e1883cac7730f69baba2760b71484a2723a5cf6aedbe68a33ddc3ef7791279d16b785e094d6ffbedb998f54fc060699a373c7dedaeccbaff5c696ee854b6556e04505bfa761079b2c0cad0ab2cafbac49774a7050348adce7584e07df446ee95aa46ef26a6138641b099895f97c90120d3320d098259e62c2ece5025322272071f92a95161fccba3b73d18f0485d3ac91bec90df3b30dbfa6410bf50aa7e74364bc4f71797cfbc681886eaa0621159c0707262a705c8f42cbbd165fe83d4aa3ef8686dc3a603e7c4d192587fa809a7c19cedf30cb51ee58875b508918bc411dc98ff163c9d7ab2f12403f440f876fc5d14ae2cb624a2e7f11c15fa9f49105af3694bc88fe1b3e49997df81e58fb997d339fbbad324e626a583282279cdb5daf409bbeccbc080f91cfb4c576722fed3bf574d918f7846d857245fc73457e6532a1cf991f0c9a41429c3cd33696396a092f4f5aa84ee710d89e9020cc064c670ec4186b427f2c1e6cb6555d4822578236e48d70ac0ac04ef7bdd330ace409a38843ea2e4ace31979df7eba9fc74c35c149c7c6a88b357b92c1b75d59d512013741b45969c54bfefb89f7cff1c35d858f0f1c2a2519b46e33937f56263f6b9ea349760dadf7993966e557cde75b29ee0cfba9d3d7bdc41aa9cf9541695808b3c7833d3aab16352fd8b749e9bd5c457edbac0f92d4cf71a8d96b47682a78edaa0d467ebb9f355e6ba5e65f9c7ae668e30b29dd089ded68f6b2df10fbbe472617596fdda6c8e00e664348a25d8ae7cc4af4c7fb7ff7368f3b1ec0cce33201ad74a300c39067802f3a7669db71d875bc3afe73d429f74eb2cde3d2029fc82e867610dcb487ad1df03d3a876e38c496bed99c4bfb9eb321ee7ca7c8590839b66fc3a4ec78c335bd79153afc23b0a0978f052beb25d714aa11caacf44d80d0b8b97741b5bb84406b7a69c8d57acb06f0f064f9aecc4de8a2c2b95f1d8b8b508e9da3ef637028582f5baa7411be43cd8b143887693c4d2b0c7ca28b3efbf27bcbdcf19be83a27921ad519a6e411e9970957d04374d9d60f8f65774d1a760a9c1eef61ae07a425e66ca735c7cd0ba663982e23600c8f8e287586cd41f1a886d5c21b03a283976332da9781a3ff87885cd539b0077701406a6d62e6515da9e2a048491f05c48a49f0d0436c6a10b36708bcfe0e2df91f851d7343b05ead9dcceefab81dc74432b84687ffcade5b3a5d3ba8744fa58d2fdcb4a366191ad292ae840b9a71855db2c1c3fc2a88ca0cffbcf11cc12ff71192b37b5ac15cb02a1b8247516c52ec9e7e1a0bb82aea480619518057c8ac85799c60c7ac8fd8ec3eb611a467c273ecd9108a56d6029db0420107618de084edc93d09c83b6232f5434949898ae9f826433eb3fbbed52b1509bb48cc084c5acad143b0d62c03953bb0f0a17b7e5f7e7c63e538cb505172b98fff2e166ceb5fcc525a9866879c87dae3be5ab65e3d8d5779da23cabe64ccdeec47d382475412552e0541d460bb19a65a2356f27acc394d441341416ffbf8572018183ac46cd1280022115bb1d51b776c61e005559738821c67226b73beccbdd4b6ef9eaa10df43f76d1d4489c59eac32c2c6977ef2157f84ff7a1d719fb681090cac6702e9d914d36aa8d6b5f0cd2e6378bd83d943bbe8376575b3cd78a7ff63b4424e9a1f7ae415048ecb6202aeb020011108fbebde7cf001c03300ca0135a56044f35a84502ad4e68082de90e6006ef0f5e8f0d1e6480d4025696375cc8dc4e9fed3799e21edd5138d627152ccf01e411dca1f8a83deac5df4ae736aedeb315b8cff43add3ebc97408300ef9c59d20277f77a5b023b79f6979c1f13f8090f613de7b6869cf0366d9cc7fa621b1ea57a83fd090ef76c3daea76c43dea3c9f91b78f84da99882f6db30dc984856055eabcc486a2a92919bf9c9e261a2ae020f53d35c8397e8b5d7fc5d3534f5aa68ce4de91dc068d8fee3898c95bf568b4eb06445d9e806805570ddf189d08211827e90559cb7d4d2dd2b9ed79a2363cc42c151f3077a339b3e2b5512316f6f3e4a30921af1d37ef4c42e1b9888b700fba6ebd4c7483dcbd6ff67fd50fecce49f1158149a069dbf459618efb2d401fa8109bc8a305dae68341ecd47146d2345df8f70dade6e8ac94f78d7b746e660a1f93fc6bd68b288970c507fec3cf080a66f8b7e45ce19bb579a6cd4ecbf97976770074c45be6f7b3c9fd32caff7bca21d921ea418cf19fecfd45d2ec28f8dd8bf4a4485e4e16f07635cc1ff84a0a0baf69aff2f6df0e27aaa28e13add852b72f69979171b47ecc62669807c24689ef34caa5e960c56b802be3338cd6f81b781918f17d670d4ed66358fcc0bf4341f8ac9327f340e67e400886ef9ba03330546d586b71068bcd17241d7270886caecdd8db48b953c1a2a9d4e25bc696684801413a57993883018f478c4838160f4b193bbb62b401cc4f9b35e8388aace39f82be8a9cd704033a7c68c657bb1034802ec60969e1c3f11963c37473c68a6eff74dbddb4e5bcd8bcd8b00f8b0f0e4b8feacbcf7b51bc5ab13675e636dfaacd39606b38f1c24cae8765a027b26dd44da7ef872c20bd0d36e8559f372c6dce3ad666c805258f92336b79cbd96340710e6b1916d0f391804c9846b8860eace1a39cde73784533ccddc1be5700c608f21165eff24fcafe7069f08fdaa371fa84508bc1ce1d89c1c2beeed807e77603e7716dd0cc9824d07c00c829d82eb0da413666f2e1b0eca2ec4ae1a36c2dc6672f0b2ef442e5c0b18ffaa917fdc17d56ceb3512cca352d659dbf9b8629d292cc1a7a630c2648b0998e1b3c3b9bc169a521a614138d89f579f4e7c2714a4f57f57e38730707ef9516f753130d44403af3a277cf413dc3f762aeadb0c918fb1896acfb4c189cec9c36b4671d8803c5be4f1cc12d1c6983e95d8f10509c1f9a61b491ae689195987e856931eb384db3e9688029d3229f7fdb2f27e092a098805cb6e1a2323a63ff6c58f370fbcaac70a1daaadde1c5bc1eb3feb0fd6e68e44fa0280a9897cda9798235bc188622bddeaa401af18f60e2e9525250db8c7e56dc54cf00f4abdbb9a71e00cec93505a1285fdbbc5187567f5acfd81e5cf8173532db56da124a29938b7a60aff8fed7f4bdff67148cb38c6ce416e21c36d0398a1455cd037f537fcae49fa79de829905732728bb1ce510cefc1cd6cb389fb3e35627ad3767e89c03bd2ab8da14c69f08d76e7ce7bb97d941dfdef5061746b857d9993f835f9e96075ef911d65d05cd3cd09419c0f2efb7d9f05ee94528b1cf6006f30158e70a751bac1fc0e9c60c03c231ca8f8f24e438d3a3f3e2b39e6795a452b6d870c0b1f114ed9f5c61c267c4a377600b301b50cd29d29c585b386f9f7439b375e6876d44814710c1e2961aea3e2d23f6bee6077d02b6494e67003cde03953666a98f259340f20c7bbda9d183eca7e0eccba974474b59f00fda4dc586bdf7c3f330fa1a60464adcf296a057e58d090a406253483762ffc07996fb9ac58672030d46deea411067d60afa0aa30082949d64497def516a58d0e490c5cbc86f11d3042938f47f85cbc7ab6fbdffb418fef709f5e52a25cd0ebc5552b1ee1fbc81d0dc764b3f87d06b02e49043395e7ce3fd95393038f3334b41dc55e0ccd7cbcd4d9af5932f58ca0c3ef9d34a670956596bfc06fa066b0a1f2514c4d2f8c228a17c7ae2001c967b200d1ad2dd1f96cffa2edd21836bb2d4a7effba7c99557e14e589f79d6679eca9682f1ad1d2a502b85e6a682f3baaf030bc79814dd5cfb23a65a6cf76895f992fbd935f4d3832a668799d9c55ff087c8f07cb4faa8446f164ba5868164d647f95818287bc120077f3704d6a92257ab6947f809c13bf407ea1f26423f41dd980630610e391b6463c8076c1474e267688f1dd068b5a8563bf7b465d39d8ec1ff8f3a04e0ffdb4fe1612ade3cf00abd10010af1570a6c690eefbfaf256ee7fe32d5c6489f9796bdc46ef0d5cbe7728e204a234d273060f61c74b0219cbe5f3d8da244412230276db2585e4839d71f58c2d0ae270c02b9503a1ddd54aded0afb711f866c0f46a4e62f184104eafa95e47872f3b482b3d35beb1f62fb1a83d35440b05395d0bed173b63fbe4b554f8ae7dedfaf9b0c4a415520446e8a6d93b55ec9c418a45dcc9ac5850b2105d813b8cb335e6419e2b2e1b70cb20c928cafe61c73da6d28ac82316c43db0cb004c52a833b51e6fac2a23c4f88ec53fbe0a4ad71e6e6729aad5070fdc393ed6058b627b4b42f407c07ecf72ab91c237b6345136849661a57ae89b3ca933c68dcb8c5bd0a549ed900c49c0086bd60c3ab7f5379ff5df1148f98e64ae1ad1c24cf0e6fae5ffd9eb852a718df05ee56dac13c675092d7c664d98641c83d45c39c913f9b063670b5dcc9c5adda2103bca43e3b2e36db3d3034056e672c5fd5325e193acd69dc82c58b366769484d6fc2efefa31733418de6b12c725d15277a975623540f84d409405441ceb02a3f84108e8b17712871e22c16243300b42462384f391027f3e03955f4cfd2dcdbad39464a647a90755dcca10644858434f2a12fba3855200a6d8ddee54fe960e70195f3b20f5107e4a1be91eb88f996167dc6e232031b7bcbc85aea537d5907c2c2ac0fb13037b96baae283f6e3577dec72e23adf2de724ac59f52d44ecddc1b6fdac900cfa7528c8371e88296364d2a02c585638f1c759d942493072717e875bd3accd7c5b431045a9ea9900b9db2f06d2a04bda0088f73817ed04b7be29370439d2da35708e2898bc5d1e8434a8b4b012d971087269393e6fd0945ba1ebcd4c687c8ca50eed0767707f7c8b979ec26d2eb1f05620381cc40c537248f49b2785f133cb86d397b8257640dffc276e9462cfd1dd4233f7a95cf01293460543bbb6342ae6c798b95c94838a0bf01ea961453bb899edb63f59e6aaccaadcd2bc98bc9fb30166786921e809f45be7fdded8f031efaf70caa77b06b6882868ae0474c4ffdf190f3b364e3ac4a6fd9f9c1bc2274bd7232c10e004bc03247390412130923c92e5c3dbbd13cd8f8d4598ea616448e8dd4f8195027c241ad34a23ce720e740fd702af0cab783832304b91f2c2ddd14d68cb07c56eb1384768cdf40fc68b55a8eda7e4c05d89475419402b1c3faa212fcb3dcaf9705f55717b88629d5bc7dc7ec3b4b82a734fb62eae974ea7ec1e939372d3fd6843bf3ae60c6fddcc7953a769f7c28766dd1a9039caa2b6fbc75d40b75d87c5c1fb91bbdb8db5000978ce03f7c78aea767600cdc74ea6ae8bb8899979a6b513ef5add17b2c4aef417bf8ef80527fb0cccfd8173f562c66603f99857a2d572bf5b38b7615900ff334b8c9f717c05a30a041b31bfacb9008957f44679bdf2e8de7a12c28739e0114fd40ba21db041860f5686f40a2817b86984f6095d4c63e513550fe0610ea3be5880c43d8c48740ec34b0389a0aa9507f10c7c5640a1e85c852ee202a3dc9546ea9531e90b25c14e3e0b16709497e9ecf5888ca573b30f9f4e5a4f4405fd47fd3a6cfe3410ce724d1baf3117a5b6c8ccee50aa282b8e1b745dfa940e07fc8a8af4d2107f3d808309822774a502a8c774d0ae71b19a077bd704211756babcda608e0d080da536d642428221ed66d949b4d390b44eecb73a7ddb30c66a11f7717f70800fbd11bda5db75cffd68c001d2e7b14fcbae6475142fb33ed76a699ae0c5c2ae54f8c2b25a125d1936e9d707b0c24dfbd62579750d0d1d3c0a92c991892c88caf91ee918e458fb27e1e2fe6b462c873d84e37983fb810cd76204e8d328759a1327456b38336c22e0f34b468807ca5fc79946337fafcb87b06ae8dd93f44bd37771d3eb805d997e9e66cffad20ec9390d31c7d9335c652fb9c195d8f7708b7d3c0f394f828e25f5a653f6ef00d3eac410f65523a9448c30b9f7c8a7752a078ea248be98036769a119fc97909180385d6dd0b431add4d463816ffbc5b72cc680d2b859491499cc680474ad490b7e030ad92e20bfa2003d6f2e5614304cbd4acff73a3565a6afc5e832f9c654a53c645eb08a7ef13a635d5402fc780ac0dc541f1c593a3ce01e5b09cf93d664215400d3fa3f153c44f47e50ef988c73b4abde12e46d3b7b9197b6353204f19d91a309444a528c9937361dbf0f4df64a1e2fbd65ee327cdf5a9bec94996d30700aa5ffb25d664569f736d85cedff8d52c01f623a3f08b6160a3c6ae77734a1a098aee24e2741c28e010788f0042dec0653dadf42e945ae4ad1a18a5b44eb0335deaab18c19d04060dc417ce632aec07f8c665d8c7f40cb0398b2ad685483051c50fd7c580003130af0f687528f83cad622f55ee82d20de0f0121d0eef55d3cf194d91c074fce47e1e9395e4741021b2218996ac9ff71d463a0f083d3a013d61fcc7eb3efb5f915caf4e7af380fa968448f2f00841d904ef2422db4794771c33aae3105f6c9c4176480313f6083cc9357d424af6e74cc213b8186314a3c6020ad52a825ce16027342e446f59e58c7055633d468e1c6d4d578af479390d9c4bb7707ff4cefe279125a3613fed160f8dcff7aa44776fcc6d3323e38842910ea3497af7e54884712f4fecdc356f63752ecb27aa6be4a6ec6380a2e659be18d4f68d4d9fed9e5a8789413c52171b4512be72c62b9d7ecffce452a63ce3dbb6ffe7274b4ac0732cd900e281cdcd95b5410f126a3f6b0bf1fac1661da073b5d55c5423b6f855ff562ce88c0e48b523dcae35f47ec573b5f4d9a168bd469eb9c5eacf9f6b05743ffc6d643082cccf52cc3a94a793a844fe40da8a768c240b82f78310b1a03c5bbf230ffe3646378690d4687e4f30fc06f5310a1027e570be311b8669987c9257ef5eb7e7bc6850fe0295315275e6a37aafa8b1e2e5f93dfb46dd10b43f4bbf9141b116275f01556668f6c3000d3456f49cec0d7fe65a1e113d5893f0848b0da91b3f1208608d0452362081c5dce062d3e7c303b0b40b26c67c1a64304e8e56e1b921f7557650bad38e59faa5c9d741ae8e1fbf504c067f8b1a4071169c77a23e610dee5d87e52e3f7f2cdc9889b928ef06e4e2a78a1785ce1c3d4fae09febdec70c1fde4bc45c1530a4d106cf83b7c7b31d8b7e1f318520ffab2d78b3a68d7f0d28721c373606be86b8ed81b8e7b0dc0b3d5e51bd3b9a10f1947e31ce1514c95753893f7dff997e8f1087993118763012ee3fbca81edb1d72c413a856f65eb13d7613b2be67efd60bee9022b52cbef3bfcfa3539c987d7232770f142df7bfc557b977dfc157cc3be421948cbd6c91fbff204404172481d9d8837f127e7b7da2eda72fb9fbc224b3f841aa948e2d07989b3035c12451be793239f192bd7718137cf4a065c5e3603687393704326dd4abce59986f9c74c59aa6524f6fb490549e8e9e9dec42894d4e5d7ec5de75a538d320461be86979a201264f33f251c39ed65ffa74c57f7bdc803f0fd7b09250c61be7796b41c5e17231fdd706fc5adab8eaca494cc5f06b205c105ea8fe923273db5bbdfea62c3babffe72e5f49b7939be7a9910c55c8e32dc7987ceaad2c40001d5d458728e6a3d676a4aef296d7c3c255ed79a7e56ac50e9c0a376210be388a8676e121a7bb7da61d5ef0c6fab88afed2bf02e33854c43e8f14f1b7811021777bef2cd3f5d965979040b6e09d9fe5501c1612892933f30d23d529de17ff9d4b4718b645eae623f96833ed5452c8ed039a50779bcc665439e7db8f59fbd1325b36fa741d0079c431c1c1db25530e8cba3a7ce0e1bf48776260e1425014cf9a86ae30fe6c5a3fd0144817d8066da3b393514d49d025afca0c356f3e2bebbf8599632b4df74e8e8c52350edc41cd72687ef939269764f6c45a45a3fdd73483778f22053a1d9fd82fcf549d308a201d8dd126347e405a63851cf5554dd6459b412b3e0abd19f9fc9bb42614f1ef7d328e919991d3dcbc8c62bf7971ac9c82ee101a7eaec4e40398b264f35109cab8514d18ec1add6e04f127ecbdb0e8f34014ea340b18302538d95b8a95566678bdfd03213b0138676c67693367012f0b520bb3113645b62502929a944445cb53b08d1915a850e0b18a2d05679cc5c2c80e6274b638d80bee3f1c67572ac9d4b3e6c6ea95606d4c3bb83ba1e4945c240f5a1913ec683e8e9cb4209e3108ebe46ad5b055274bba1752fc606c2bac2b0c6128ca910499571d3a0b6e4800e42404b5d8ce49128d14f5d402f7a25bf298bd6ded4f8cc1d17bbae658bb05a8b69d3fabc7ccf20e173e5b5fe41d3b55dee8ddccb15e65b40126fe786f84eb4b036acfb74311f884548179b0d88dd246306ee42df5ea5913dbac269594b8fe1a0d8f0c4a39cc402846ce941393a7e4e45b0e2173dc1495cbea2913c46467068f06516dafbc78653be054e8d772793582e80e5573e00339041e4788185530b4fa7747d1b236c25581ab3462a235f4c7ada43f5c032e1d63651a3c81bb45f3c7fdf2311e56fd6f2b2f5eba59daf453d338ef5024141ea92b94ae2e79043088880a099a45209e52dd812415c90debe3f1eaa2a3ee8accfd405b0c06c7057ac396f2f92381b338893405fd2b4be98a37a1a9d89c5df54c54e3e049c2b1f8a42af8a58f0cbc272d887e89816b21fbaf5898b8e2dddc58f0974c68f3248ed9f7bd30b566b478d5a96f127eee13c13b787673953e0713a76005fcaf35ea7d3da343da225496a3634f65c6b6a4c8b8a393c9425af20fed69df27f6650ceb5040cff7d997c3d7e722792722472af730b8f55791ee78de29414a1deeea4462be62f9022771d1c61d30739f6db1bd0ef78f0aba2db2131d4714f7f1760a5193b1ea823aba146ebbd1f88d43f90471e5278526966f79bf03a676a11839f4a83cd80eee6c2604723eeb1b4d4b95538c6fa0067993d4bf8d0e6168c414112698bd7b3a35ec98144f245a99aa827d9121c92745ed7ae8b61475f5dcf69bd21ca4bc90c0038b53d5d2bcac6dc8225a182c671c7a3efec9b7e1387aa14f56bc9561feb09a804797f08a42dee6dbb652e90878330701cab614b1efecc9a69204ab859be2826aacd9a27d579634ab888333cfd319354c7f58c6856b583c536a5f09638506b864146241246f2dcc884cb61fe0463229d78f6f0af3857d09c022a59f30b4beeaecccc0bff4f6663a0e48d7dedcc8ead42ec383557f91d86f1e08b3d42fe10605b22e0c7280c69c4ae670eb61c3cc5e7f6f15c5f9f03a07a71ae2511c7cbe54675926f6b96c06b1f8aed70724122b720203d6b18c6af7442ac29f87d33b666743521c1bf3fd36b5d0ff730aea22e5d664280116832f8e16fd4096f24ff6624571cce426066098d5af78d06da04050bca5b4a060faa42e0bd156557f1600ad9978f52c3cd8e4a879910e7c320b971e61b755a2b930f2f150d9f548377769808a5afee0b2051a677b8b0d4d0daec9bbb78cdbc4d623bee1e224585077c7eeb458aceb118ce1bd49e43c2c578c11e2d0c6421f57380d496442893077cc7f0db3b5421d784c86c45f4ade46ff8a008467e4681126f6e4c912778e0d10f9b7dc4bbc4bbe4a0674b1dd0fc83705171cbd12a9f6467806d10a03b7c887c943237333891c99b03d6eeb0466d608ddc9ba7792cfcdf5062f06be7b6191a6572e1a8195aa1bea7abb550969534f05b369a4e882898b79accf58443d5e62c476898c2dcb768f4f348d6cb36b9aacef65658d2aa0a1297b734990108d0ff144c06ea1e9d3a7a79ed58fa3e1745da88a00b4adf3da41ed247e8d61dd98dff68352a157fae7efa20c175eec2344b088a00e030cd14225b4a4c4fee992c1fa7c64647977ae93e2474945178c9765a649f8f8549305e0196068661061032a37093f304c80e11d44eff503e3473bb1ced4d820253f109972ea4794bd0c6bc76410c1be9429d62d360b8ca2b525bb573c591988ca65452ba3b249a03c7d5199ebcc3ba3f79f0306841bdccc34623b5ec71737d06ae1ad642ec13ee393f4741a33a667dab704be3c037fd20a512b11862a897a33813738693e7920a2484f0294ec43eeda8812b5af0b4418387c53144cdc4d9198b803e933dbf23f6355e9d372589108e12f4e6c3c32155469f5eb601fe8537a67dbff3b3a24155ec66c103aa2979cd852421ecd6da2468c03729fe06b587d949835d7a8d5020d62144657cbe1e8db1162206f1790d898a8946f5535820074afc2ae6d3e81c7c7de63ab55ce460c56c2fd3bd29a366f3979e4c6c1910e9d9c9e4931240f6e8873a63c786aea91d678cd96e2d2400d72dab983c2da1170bdeda68bd359dc2f098479a449a62ac3799fdfc188afd96f8cb74f4edc6ee227c0152dae700c436082842c265fdbe48dd5d43f0e99e8b0ac4c6ac640ff3b931a5db2705a3a1f4ba6efb1a15c649eac8230930abae799eb8e2044df54f474a6165c47953d5e9a0dbdec4aba69671f15c776902794dc8557fa7513a33751b3dfb4499342edea0e97d422d196afac73e35e03dc38e2652961dc4dd1d46273574ca5c4b3d00f5b4c71ff7471e4626673f8cb59a27b9c6c8292c5606f7333a539b7eeca3a7ef2a938220315bfa5bb8f5b882723fb7ccfc19250b8391d431f4d1778dadfa3666e65db2bc5deae4a2e02e28f07aad2764d272efe0a6fdc3c0a3cf3808810eeffb207bbac00b5c0870a07283065e6fbdffd7f7fae1ef12cc9fc1eabbef49f286c28cb2cbb3025e866de0448b8454b7e744648750ca065e95579cc1ba156891bd3cd3e8059d2b861f9485e25c186263956337ce10619aaf48aec5e20a5a43524d1971b6200718c18f9a73b25e56ef73f3fceeaea66a0d3c22260f4886714feea12b51e23995dbdde924e184f8d5544a613e2bf43b1ee263a594f70ca88b33059a1d04df485e573b6f37bce6f62adae609afba7c489b67436e5cf91b6936cfe350441439e074e6a7acb27fcf33bb7bece54650d601d3f3f7fe6637504e5ea989001eeede4d6bb988ae749aebab31a933df7bcf6fe32f450b11b76813c2785ca84399329697a06aba3a4d5c69ce27d3985ed318e51908dec231aa343afca754caa305b714f8df450381e2e9ef5d8a11329228775d7cfa59c5b065f36fb4cbad2c0e7282e0db0891ff35367f512b39308b5d25f4804487e40dd9a00cfd71d9c3bccd5597287ec39405dead1d50d6904ee96629b0a3756f30af27d9ecaa8c937f99a1d70391834e10317d8e00e6cc09b4a253eca8fe42e68e1098d4ec9eca411e015aff630791ba73035858b2d043246f0139fe535fb1baf51180d553939dcc56ff930e39c2bcf6abe5ed7cb15b4c6a13fc95b0475ce69896fe20c6ad987d89e5562804f1abb452f2d9dcd16b5b7a87eaf54411ee2c48058d08073a67b21a935840adaf02496c1ca2c3b618c2432f4ae1bbd3e66b47a516281cba014abd6f58cdb590238beb88544b8c21c8b6b6a0cbccb34715fc85cda581604ac8822a1ed680ba9a5f922850f12b2a6e51ec2da7d557e14d729322db62739129624d114ea15c16027bd9248cd706d73b984cc59be405b25677b91f60d61c3aab7867078390c6a0c7165e6cf1cae47f9a68046ec85d10748fe4cacc3662f60397e856042c68f391de94d02cf0b62b2af4eff67bcef61dbfe0b62f061dfaf74ac383ac6713b4c1657abcdf52467fc4bb1b8bfdc25af84f6286864e1f695fc433d2d40d7adf5fe5f55572304c16a20eac4aa45ed8dc1410f65464b82d20b8587e0eb023324cc7709cd39bfef1f9458ce0a4127fb441ae3a7dcadb5c41e834e2c50b49297bab7df75aa39a17ae37d7441915c82783ad0d8fed51ff672f2c54e77b0da6200ed46cd4bf377248a505066426a17eac38ccafbbd40603a88e4a48bc596858dca7631d4aaa02fcc964f60c46a72e9a9f649e90926a4738292fd77f27696de0eece6ea6e89075843613fd5c5d2a413795c6b646eb9ad866303c9e953a89712ec529a41abd9d105cb0e77fd951a7257d5f668546204b2f128799dc7e662c73138deda8e5dc53358a67044e256092a5445f23be2e7c9dfd856439861cdbfc25c1993d861cc0aa05a2bd1e1a924407d85fbdac9d6eab266aa4082a751bcab26887d146e5ed381fae7f7cea4eef8c619138d470dc2c6faff92a762bc1d3465f041e1ff32e1611b303b2f4e49ebdb04744f8c96793e69ae0d27a915a4d966e7977d33af21b425132ad6743f6657d4cd6a786a7b902bfa01c038af536dbb3b7f0e5dd210b29b3b0d3364d57446d131de5510c0fad9987c8d6e0e1d81ed2a46128d778eca411b49539eb548024a13e7477d9a6f1a6cc3dd879f3cb2c89211594b0645fab966b05188b2d34a5319682b1dfe544a9453dbf4d31f6cdf9c4c2ecc27ffadba746cc892f7d2d2080ed858629abce0ef50d4771b8570b79ac764a3ba03b81436a25f4ff4a9b8bc928855c63fed5cd8679a68268751cddfd823cb78f65d74373b2a70334760bee2748c3d55a0a683e00f7f263924afb67dad80156710d28527310609baccd156f6c99629ebd3006aa8559f63448908188386caf629a05306aad3a0fdfc5135858413a3b296a1f1a75a70e1873618bb23f639d267a00c0363046d1d0f6391499d4d8f03503c4debc0e6225a40f20747ce70d8f2b00ce6cb04bec57233433ac958a570759f7c43842bd0c8ba9fa4e798d373331108f074f319f2b5f2771cea7907e9b5a8d4469b9f8966c2b803b47f080fcb5318abe4a0050d08941f0fbf5e0a20ca4cf4731f7263a1a8b7ed32281303242694f007e9846c5fb368421272e06a4af0da5c452f21f9c10acff627e0f106cf1328030aee69789010b87f1db4796e57f69118c9e062d80ad3f06d65c1b6729f6eef17017521956af10e77d6cd26985e5c00ddd29db6cafbb6669199dafb158af4e34b1a46736994323c3e2c09ae7f9340f955494e2758e5e1098967b15cbd9f46058f3ada060ee40d7191ff0930371c6c8d9fa95d593f07f1c5fe1612cb11d00d4b26d343b5fc63548d41007f961f1434dcbb0b6b9562e67deb7b13e40cfe5f2df99c26f7ea7882cf71bbd092c9022231e52feca39781e50e03c8e08e760ff9d9a8efd41f82637e7f93dcf41ffaa7276dc09a91f4ff23c0fe2c1ed4bb5b387ec18809fe5908b283064db48a392f60e03b7eea7f0e12a6dcb5c38689a8b24fcb7eea8407b8aa97afd455cc1a4b9ddfd30dde064cf35ccfef77e8034d494847b305d9598337baf0a386c909e182e67f75bc9f57c58398201a794c4817d3837b580fd4101767ab197e8484e8875ced2d2373bfc30c1a1081cfb74b6dc68046d9cd784d688894cc8d5113b9d1196afa6cb8a28df82361c53300a003000a001600d000202a00800316e91e0f0a564ffd00da939301c9295966a752d0822ff881f14a664ac5b74fdd6f1ad272e38b9576c0b4bcb3f4fa1715eb7f21ef2e4bd77cc0ec89d302fde0a587e1ff9cd07a11de95afdb1b9acdfccce99f743c7765aaad2061437e72fec1559fbf84997b9d05a31708f52d674c775fc479f78485597be3d64d87668f771ea4f2ea9ed6e36783b110b3134f1e12c4bee6e186aaddd9509c5ae859f7affc40c4161df2678c50a5f8a3b43b5586aaea1200dff9e224c42442a9edd96b1302e7ea5f1284ba70357eb185c25963fa94823fc17609e66266a5707ae96df9025570d34c88cc0fcaf6abaef588bded6f29d3f22e03b579db8e1aa0bb317896db3c237893a0323d9c7d69d6f232e807bff04590be0a8fbd74486dd8be2468966ef68ae1117ae4003cff53facbea2eb97aed4c0dcf57b225ed0c3356be957983021d41c14bba73a785a8ca7ab3fe8f0c3889fb2c7a1c96cddced5e6040f2bedcfe41e98442a4683cd01550f49939b4048b432009d36ae8b4997b2bc783546bbc569d8ce64c761937b151fac08ca1a091da6e21a3ea69940c891facb6d6e7ecde2b55a439a56290e2efe33ad89a10dfc97197486f74e900861d0caa6e747a47f9224d33b9d6492d87f4bc42ffd9759247add2f39080e72e8e01ec567dbf83b708d59a351ebec73433a1a19b79e3f3662f3b8ba729afc4a57007e80f044bc4349a880616e71f1c16002b6eb08f9f01a61793b8c133e1f72f1195f30a8a7db4c037cdd850fa1f5014edbb0331863d1692d65bb4039d29d812373d4896f70420bc6a3744cb43d0aa2d5f4f643997ad4fd98cf81081545c2cbfbe90635dae8e75eef2b695d77578390b350fa1fa4b64f226b4be7c54b066db735e4bb027b6e95900091be946d33e0f37ce0337a01e0b001e202d80c8e6bd3d47d89f7585f0b668d5fcdb66a48c0ec63a76ed0766d576f695e1aa12725a40ba549b1bca02a5a9db5038407ecbfa2f05aa8e62f37bbec4ba2c60b43b29d5c3fed2c8ed617cd6165afb60f3696f04baa98647eed8efbb8687523deb0abbb48a85fc20047d12f629d84826dae0a30c7bbcb3bf3787d186d86d9c518718dfcb49cba91be9ce48fda10fec5a78d40037fc7078a23c59e32c8f5cdea85e02e24d989370ff2070ca087a1617fc81867bf855c986b26163deca5bc745f4a25295a061db6d4819d8047cd5545dc66e67ecc24af55fd00deca200ff46dd0c43aa9383ca05e924dff743af420182621c34a3b465cb7b5557d3cef8caa1a8827b1795c1267ada3f2f37a0ec884daccdeb1ee61d61edeb3677d2ebe59f502e99ce9ae652883786c1fe657887824aed13fa852c1e0ee5c4706d583a0d884b1b2ee26a030eb44e9863b5375823f4c12b980dae77dd7c448a0228aea726f4e5a8e11759554f8e6f72e7592acd47a1620dc3883d4af230ca975ca87f03ea13a7569f4aa63687eac28d6dcf83966a546487797c452aaf7d9915a649d01f690bafd8d8558566bad9abfed313e293023d1490207ce971d6d85742566568e1f08a802d70f4f2070acd5e2e8f691b955fce22b59fe94d49bf9817a29c58c74b91a16c5ee50ce7bcff5dd5be05bf250698626f290b9d943fe319d563bdfc712fab57ea2aac38ad57236ccfedc378eae9046b09527d02b5aa5ce8ea38b6b3586e6efa2d7224ab0de5b2f36cdc05d3bf5e6f9ad6921d1671918b8653033828fea71a9bdd92a4175f42c9f634055cc4a89d950e6809350dfe8940b137b3c832672ad4e5664f9ce7213e3848e3f7d5ee88be2e9fc17aed09feda48a3a3a1dc94c9ca45ba0e500f9dbe231e90bec78f91b89336eaf70a90adcca493b67b44c37f8fd6a7f21afd06d6164c8e8af28c726460fd46ef795727b8fb030e5230ba72312dd9abf5ffa5099c1d3d156e1ff43ee649857417fd628c53ee78aa6b8f7fe23fba21da1bebaeaf183803f4dbc7e4cbd9ac0f34a9fc1aa37ea20bf8e78348beadc665d08a469d3d40ed5c307a17140c418ece6fcfb70aa1e43feec6a49762c3504c7769f27679e5fc682a5ecc6749acc043fda3674956f67f1eedaf65b29da8bc899db6d48decb44278c3c0fe17de1b663f4dec396d5d5d1fd352ffdd5426bd2cfd4fe38a026c32314c11deaad8548ab284a6c20abc53952dff633f99fc6163e15778f72314984d5417106034b0a671ceca2ce9a2dba12559014c2039c19c399cc55a4849ab99c565b14b74c0564d2c49f04b910054af2c500516ec8f22538f1456e06a3f88e7ad5f3675bd422fefc03405d594171ed4f9684958f092a8d16c987c84c3862b900a46b92444dfcc108ea44e249a6568dd0b4567ea70f375a5d98c25f4b9ed900bd76eccaebd9c376ea35bd0e073ffb2b7260509f3fbdaa26dfc1c1ca92c0b6f581078d86ada9f1a539a5f36e7f587534fd73078eef37bcc1baa69253fc724d6336c7e261369ba076b2930f708615e32e6e52ee8258a0390d242506cb011c85766f1111b00eeb07a6900b9c6b44f1eed594386cc0301d223a919cec928f18de709dc7906cc6cd4ddda0bafc40e6147381413d96932e97f607c9067754bf0ba9578bb21363e445626ded6c67766f33deae67cdcdc97571deb4eaf1901deeb8f8460ce4e1830b6bd58035c1869862e5e3a55ae10387e61541eaea8d4d6b861e3d4d4b8aadaff30ced160074544f941648e41b72c165d3160538113e7d031de33d89c4aac68c714c13b32c5ac1a8673028090762cc6bb893853f5e2c5dfa13e9ecdd6cf990fad7067c60194b585a11a5e68a767bc8e2c0f0da692d2703e6bf1044a329210dc9335291db43d619a9f94589665dfaf5b59adfefe88004575dc67276a00cd40574ce6c608f0b963d53801030f0323bbef84767988951bf0075d5ae995f86c8affe0bc9898e33bf4496d10e00528e30d669095d0c0bb68ce281ff04fb968bdb2263e8458ed215dc3bb682c916b70531fc9c1e0c157859f4f8bfe18d65ca7676299d2a06a3aeb1e9829a67c904902eca0183f5aaacb1a14ee40898651aab968ef6b3a3a94a6b3e86b8ba60318c08721993225d72840d7e651f5f014d87d6d96e3817a3f9cf899a8a6f40fb39287c8802825df1d0e0cee488399646f1f78a266c8226a73111a6d60b32de2cbf0cba142a640496f90a72b44df20bb41a179ad6011af9bfad62f4f5681104eb31393280558fed38752617d6d43eb2b7a02eb3f50261f4262225a1c49b4bf6ff3f6ebd6abf3457862c8369ed1506f1715d2a901c633af4cf378f757747ed57ddf2c17c445b910d7f6cbf789cb609754c21beedc871aa5ded75ab19f994949d93d06b880788353d908854c21f7607480384b0878fe0d2d4bccfb9ac9d7f65d318f71d4524aa5adc74b49306d200cea4952d60af73b817151bb5119effb0c3bf9429b77c11b19087e89165a951b743e7a9688a7c5a04d813d414c90c78dbdc0425c0bfe9f1293052ae88df1b9a60bab5f7b883ba0c312e234eb026fc0c9869898d982a3ffb2a98da8be4b5308e94e1cf72938a5a7ca0034cf14833731a4959cd18aabb95177cf4c8a092609044b4dd5ccbd49760b6aac03edb8379d53283e0e47e5719b7596db74f6d946d72a6b2b523ad5162aab117132d50ba338c69be3ef4a1f62f50702c18eb62971b8fe5a746beec7b0582c8c127ae69b07f7e6a1f8f4621fb8c1b43b0d157d47f948c1a7b1250c776649ccc9b3310f26110ace85c76a04db80604b6dd06e40000bb691e6a3890966dc50301df01ef9b2c3d2fc4f419f7d72d711bfb9d68963a972859b7972e1a0db3e392b576ca6e23147a232284ecd9494a75bd0e3cdccf63bfc7bec32404e22524557ac751b2dc6aa33cc3929397ec75a8d290c2e0a82527eac5b62c43e3b384ce3eb5dfebeaf86b55fd457161df2004d4c8419a4b3d24becc1f17aa2e288db63b55a96b3416d4a3b2bfc9a6c6bb8c7c1f1cc0a94273161888d07a91227387c33d240b17fb27c8a8071ade1f11b4073e6e869efe27347e52aef54cd3d482767a30b846f6863c0ce6d6c026d835300db594635392134032552511f85b82150d57ab3ceb49c803916248c1ac1cff59c1cc29504c1017d1d7b6789eb8858c545c2f43f8493a4bfd3d9ee9894a5d6cf8e5400f8f97a69414e680891cb32823e8113adb154ad77f02ec3174456121aeb04d15046018c376791155f2e45f02f1c3aedcdb5dc9d3582a3487ccd9a4cc71ed0e22b41079b1b70cc380e5f47cd0d2e627368287346d0efc433084183be92fef49e0cfea93d7ad341f119dd2feec3d19156250b7a31133e812511f34dd00019c5205934c22527f2ada2e0f99d875e3a90a0ca489fa42ccbd732aa3c99939183dc541223fd1839c6601ef004f51495ce73e86b17cd991086d7969f2b2768c1bd5d72c79476d491067443438ec60548737cf538081a99ca9338353b5600db9d4a3ec8b9d0c568965908e8d44f3467aaf654f592c9b10ed32b65e690172fe34828afe285e994f115291396530f30c82e8c283dd8a8f1b60427a928fa3a756c19a676a519c079f0a573d58551edc3dcd2fe69c78cb735dab07decb3b2781e6c41feb573e1f823ee2c13dae7e46444e892cc49ab1808c3867bbd6ca0746be316138a0b7ce661a87d183b083b75b922006caa161c5c08dabc0da06a64a4edc96c4ebd47545f378e40831468b4dc659a272ea6ad15b7e891c090933ef3371d9166b7dd71b0c4281671ae24bce535ddfdb13e43d64d500b1c1ca476a362e189d34ac6ba856c5e6832fd003e6783fed439026c0d2eed3e0df85fed9bc6a4b7f1647f30047706e58dfeadae31d0ba81d5db7c2354bdb3dd4f4dc8c6448a9c3a054a93268f4e1010f08a3ae782dc87c35491ce8f784e11da89c83a80c5d90e3aa1a20cd9e38d46ecdefebd25838864d442d26409117168fc07a14d71813ca0313502a8f3f9935d3c0017e56a77edbd19147296020709ba08fc083f94c955b36539bb7bacb187167b80792442496a923d747b00fdbfa5bb9d3b7bd5e38b1e4ef45092071a792891072e8fa20f96ba8cfb69f2ed636eede3227367a7585b8bc7173c4cc0c3c42368cb8ead345bc02da557cf3d585e7ecf2b9deaec73d19e8414940321241588fa5026fa438788866a0734a6776672d5cc0cec3d5f2edb3b9d65747cc99cd0b1778e4d3303cdf2f27099c3c0325f1f9615e9cc7f1cf378e6ce5c46c7d7352f121daf79915c0900faff7a479cff577247d6d21d64eeb863c97fbd77d0ff9ae560fdbf1c2fff55ced4ffefe424fd57b2cb11b297ecd0c20eabff5bb5a3ea2b931dbf7f3b68a6ba3a12f0f55647ae0e10eac8311bc5af0fd37155e938a2038239103047d57fed73fcc431234e135f75712c90030f39ccc831460edbff5f5d1c69c471260eb2da0b871a38bafce01082838237aa78c3ea1fef468c7778c7bbba07b41456f91e73d58d2c37a6dcd0a00d30da30a18d0dd810830d16d8386283c87f25733f9dc6bc5c6692e293dcdae7eb3db4e7f25549dccfdcedd945eb14cfc0fd3b6919cc6d1228cf3d074d1d058e01e05c8143548d7047b27e6252ef9c797127896bc2916b94fff5b5c61035b27e4113d79d1a55bfa3064e0d9dbe4b6a9e9bea91a601e783b9591a61ea2b0daa3480bebefa0536bdd9fa7f73555fdb74d1a6375c7e466f92fe0d7d34e2fc1f41e38aa28146d2ffde261a05fcffe3805863cc773820d667d470c6ec8c0ecc78f335783985555377a798986165860a5f6fb51a53c5ba9c1913b8d17243839b10dc7c196dbe5a736f6e69cfd645310f962f6b5df456a332b294b15406080878a30e297d66afd2982f9b1b78e0c185b26420800c27c858818c236db4da00d1864a1b2063dc31069c31c08c011c2303366cd818c18604362f31ecf8af3e6c8444b7aed1a8e005c2f4595eb39f48b1d8cd325dbd63b1255deb144669b02471faea7031a64aa7348dc26ad944352eaf2e9b99eb74e5dbda915b2b064e0c08b4cad0eaa245b4c6cd9aab355cd64cad317280ac034c1da0270c337e08a5d5ec3c1b199dd0173eb9e9a22f17ce5d1d1d69595e1cacd67307c7cb474b751f3b0e97b94ba3741b0ac63b5dcf3e8cf92e5896b7e3feffc9ef8401465613ba4d30ac4a30a66660b8bed8fae2ea79367b5fd88efb1117ed5f50f3dc6ad0d06825d550a971a901e285d6bf17545e3cfdd71d2f78febf6661bdce9b84acaaac5e9652175b5f499ca37c971878bbb8aab60baa97eb76d105fdca05b9378f7281f55c503d142e92ec6c01e7ff2bd98d6c81f5ff556a8baaef37975b24556bb7e0f94f03e7d360a569e1abb532998c56b4ab15e9b053afdf1e59d68265794f4ee6232da6ba163c5968fda6ac3d77308b25b2b05f75a6dbe4bc2c807c182361610725710e8b3075ebe0a51a0baaaf583cfdff6f2c8ca071534ddca55434366041834343bfd64ef2bb737ad3717685d68cee4a7c8597285d7e7245ef8a0bac00e3bf623ca356dc6085b642c87f1d476b2d76aa028c2aa8fe2bdff49298ac42c97fede4ace451aaf8af5055a854a17a4685182a6cfaacba29d89822ccff578ec9dcf53ed2f593da296c53e85ce04c0a3752c4f0bf779a16852e8a09b0f2c052022b0b16396ba2e3d010144d401100282e140a3cd1e68584fa130738a184134027ac13449a98a309ad87a242c76ac926e8993767ae3843c3192c678aec1860ebffe9489b68ac7724744442ea2b65800898d062020526624cb8aec6b8c2ba0ae2aab6441f4b44b1b384124be09608a2841925a0fe5f08f338fea12f9779713566855e38a11ae47a9a29591ab34a3d738f98a7338f635fb7424196ee68fa1c7137a374bbecf962a9ab53827b26a9f1d8318f6325252a78592c762b9ada4637895c1240ae7e717ba769b1d8cd8c16334264202103124596a4658931797d348b7ba6be74ece9cc1b553b62ea88a32388bcf93b79df298059000facdeec58dd600582d50e01b42040553d3a72d52ceed72816bbbf608ef69e7b3717ad7c739d5dd4c6a8b975b9b9cfb74d23dc30228d115346d022ba28224c114f8a08abc5fd5aa1201f1b2f771b840b72535fe0b983e70e565cb38bee1eecd22eaf06f5264fdc7f9ad4b8a9e30ec4ba4c56193265a6ca2c512a83001158b59b3b445461222c113a4368e9f41056bf330497ff1f62082021dcfc5fed08c1e5ab105688255508325aff64ccfc3f991ac824fd7fbdc11c19d74e105a40bc1404d54e1049fe7f4c9cffdfe7183463c8e43154ff558f791ae31a03e4ff81c8faaf647680a07a2034c6393176ece0d4f5c962b11b8ec4411c259391b458ec46d27cd8a4c562371f36f300fe872d7e70e107a31f787e681047b94cdce4030b3e3cf940c364852153751800f4c0460f34f470f64079d88287137818daa18d1da8f87ffbca26ad1a197d3a20ee75cc77dd8773b02cc9cb31df1466764cf28e9976180213074c1260b28041327193ee0bdff982bf4ca043153ae8a0c3eddf87cd0cc47a27073239ec7278e500e49f6ed3d2a0e9c5031cf6c081091c64c041041cc627cf5cf0cce932effddae80d40dc3075c3941b10b0818d0d3fecd8e042b52f17f6ed8eebb9027db8675a8bb3e1470d65d400a686df7fb5380a47f14d69808306313450d150c00c59cce065062019f2e842063132ec6428ba3f5d2673ce52580d9e395cde7e06cf1cdd669510552d544955415545a90a12039a188c88414a0c406f7d96c2acb61416f3d11faa712e667bc71a0643171892bce0e685245ed02f8c6fed6e53174c178ab4a0a6053228b430a585a11682b0f086ef1d16645de2e8f244972532f89d2e5e9ecb175cb47009dae14264cb165bacfebb111f36f316ba821d2b58b1020b2b7cb002102d2c6861d2e2da518105159a54e84923052f290420052b296880821d2850f15f2d7f5d9792de0d0507042080140420446507950f54552a8969d5874d9c3b61eb8434279c7042d25f18660a809a9d009c09802e000fec98b08709624cb0b5f2d2ad29edf9eef26ed39789bb8b9ab8290b145978960c4a18a3041e4a482aa1032c6760c1fa7ccdab71b9db5876825004b30461c121536d4c113175c314d554d15712e782d707abbaeec3666ca6c4fab0992dc53025b0d8ee08a8052c03840262f0ff3158b5b6779c0f9b198731dfc9e898e49499e7c691f9fe2ec73829a0ffff73ebf4f2ffffeafd8c72fe2b812931b199f55f7914f36be4668e316d5f03663007c41a87cd608ea4e1cc6f64c6e1cccd7e4da86242583335b3c091254c35739991b29cd546963b4b922c40be92b8842d96c212aa4a48fa2f81ab81c5eaff93b0f0d059504d904bb0289cdafaaf98df5e96d77769309c9af2f23f65a78404734031802c84c0df3fd055cbf2fa3adddba4d966525a7c2b498579a9258ffb792475c05712632cb949e7738b93eabf9e40d59cc2d96cb3dba1994d093a9cb093397b972a77c7fc5696e7ae93df25e07038d6bb5bfef45f494c7b374eaff4ccac6d57b45c09fab9c9a56e9b94c4e5ee7aebb6e6b34a86bf2a0ed35bf378b7dc456d29fd4bf235092c5bca3b73bf7047b17f9df274e69b735c37d04c6bd59da69ae9f17d6c7c29f8824a389803758eaa1dd998e8cd5294ef5e3bc7529787250fe5d37f2d79f6521496e5dd7217d54394ad5abacd282015eb4d827944c209ffff34db8c9ae5ed32fc0bde623159bf49b25b51afdf244ceea3dc940be839d19ba147ebf1f0e4e0e9c0a3c20bdac5f9d1e2acb975596e3df21ca9716ef4a9758647a51d98ffdf25c16ce01db00e47109259ffbdeb8bb38f0fb526dff5ab336de4944e8ace486e8d9c0d39107249ee19d789f05eb943f787edc262b11ce0ce0871070871617022e08258d9c38a15cc0677f98399fcfa6ea71877fe7cb7d77b758cf57e6985b27ae3283466cdf276e0e554679f4ad22324e6714b7d6abf3c9ea53e40ec4387d47dea3649833a1fa94f235aa1ec0f85b2b7d7793a5b91b242046a0d9497af3f6cfab02e77a8a1ff7f4b75de65cc7f18a94a5905032a5c5099a24205c99433a6bc306584294323c019a1cc085446d879d2aa50169fdcf45d12fbf0ad665c4d9de92eeb4b2994d5653316b354f0e9828ac52c151d27b7a9bb519e76788222a58fffeaa27c5639d6c0ddb30fc9d7b30c93fb88c453b7c44c25e65b174ab1752914f0947a86e224c67f9d39dd9c8e38f544c9238a9830cacff24b61bb5cdb6d4ad238b6349334da8d707c7323bc8d71eb12de8cc29baca98da61a6aa7dceabddb556fbd7937172c390e9663c799efdba33d9316c7bb39730a6b29cc7fb1d88dbcb857e66bbe9ae8e8e42269a43e374fe291678ee3b2be92215398df6da6a9af4c400fdc5a0ad37f15818cc63f8ea5b69226119644789a8900544500f2a1cd46c35732e36c08d4d8d4ac086b586a4098ded3b684c412d5925e02fa587f0529bd51a2416904a520509480c2a5c248de84e39d6db216eb274b3cd13d59e0ff13f8f0c9014ece70428493149c3439095ea0595eba7b79c78a6b1e6dcf2151589fd919ee86b41e6859425aa6edfc7f0c86ebbb4cba4a7a4ad2811202560821b83e98e3bbc95613344daa824d9af03071f353d7c7c40bf63149fa0f320182e4e6bf6a1cb5790ec9cb77a4a719920e3dd23afaf2ff7db7cdbecb232323378cc2184d31d2998151cf1da43498d39bc463e79bc4313258f6d059389332a3397797785912b524b6040310b4fe6b4f4fcfad5663022129048101d91ab21e42991559051f50f1726d4ac30f46f80082a233455bc2a2a8a29ea2204a7aceb775bbb414d6d343af92590c8f308615cb12c66e1506f3000b0f82a107503c3820c913611215ea8be2dcc5598a73b90ee074504407be0e8088d80889b2422217be9eba7bea3ae6d61acd3808830314420ea6381832b4c5d0008644181a82440d922d2192a0ffeffdfe28ce71939e3b6871bb8d1b427da8b5f487da20ae772138a110d6bf9097ff1a0a3d09b978fc066585415e8292aed9cd73cf80f8d81a076f2800c180747e041e39e2083f82a4ee51771b977c9bd7d21ded0ceb0b35090cced7cef386e9de83fac25008614d962ecd926eb4898912ed469930d594e8528d22318102657644991835d1924899109293a7334fe711ebffe48d5575b43fd2ff89f3b5bf30df3e170deafb1366b77f7a0fc4bbf087672f853e702ae953f53ff3b17df5e1f97f655d20c7e42c7c51e5a8694467aa8486afa7af7c53a1dbebff9f977bcd850574bd50cd9e4b59d1d4f515d152c4a988920dead8a08c7003321b3411e183c812447e4490febfe3123635654a595efa439bec58e6a89a0db223ee6714dde39dfb0e29c09030e110010c5132a4811e37c21e227aaa843d409ef6b3b4514a8fa6d01f3ac555ab18766948efa057140c2d814285148130833032a0fa7f4a474b61c172f3607ad311030f3058e002362e18407881ed8208849021c4082150421c1823b4404d688196d002a6d0820bc20ab0082be8153c5500840224420ac6841448a180011e2e7882b3232a31ea437ff02e97b49b3956edaba9a976a4c9ba8a58ababb4c955b3e3d854962e9a7b367f2f17ed14c6b7edb69fe58f8ed6065118b5bb7cf99e1569dc03ea7c7de1046e3e9cc08bc98d7c38416f827e811f7c28419cff0f253042825e28c14cdc0ac5abffff99c8e57f168abf2ada44a09ba813c609432d8c9b8464be29b4ffe3a74eb7c390e73fa82fad4ae1ced6ffbf041fee68853b56e14ed5d71d9beb77be2902381170f155262b9ada463257eeecb9b343f0e667101ca0f326d429a383756ea18e90ce030fe8f1c098f0812af505d38d30cb64e469a781588fa10354a103b6d0010dc206daf87f007cd8401a0d50d1400b0d9cd04053d8001006900819a8aa2f1bc451b8586c577335bd6c94beba111285d50e8391e5a6d66673d55e40ac5db41b099a3d77769be33cdea541dd1788024cb840090b38850b1c102a7044a80009a102b250019d8741d9bdd3342ae4f599b6dac2203b0c32250c127ac0411092da010e81680981140199204c008e300123c204aa9ec952582e4c20294c80e7bfd69e3b7b8880508840901001044200688500480300600800a010003e0784718055b52f26fa439f5c354b71101dedab89c95a8bf1aed697cbc4bcfa72f5186c9be7a53897ef0fe3dd2ef3bd9bddd0003d20f8d080364203aa080d20131ad0141a208605c829404c580094f0c71e5f7b13fd9d662c76b33e6b4baac4c992fa863f7ae18f26e18f22e10f0ac28f23fc1dc2ef127e511f73fa78f34fbc3e7afaf83957735298639bb3644e057320e0e3007c70a99dfed0266b852c85551826b70e669f6024bfbd9e3b3bb54da40bc62d138c52db4403fcfae868fb2cce56128d8305d3671ca93849ffb5cab1f535cb81f5726cf9c7c921c611a7963b0e20e29012c70f38848083eaabe5b1d8367376b769af3c0783e5b9437a63cd1b27bce17a230337b2dc38c18d1f371668c34c1bbb366e6cf4c1061998f34c831948ea4b06e99847dfa8738977b4dab9c6baa7cb3b1aad5b6fbbcd5186b1e4a658ec46c7974c26bb15f1749eba4ae62e9bcc5d768ec98b7b38d90b4ed6c3a98263fbafc00ebcc1f935b4fed7a046746d8daaa5dfaf71fb7ff2aad1871a6dfe5f8dad46086af0fc63a9edfb5d60532c7623c96bd338401a574f92b358ec66e2591ab4341a78a3554d7dcd29bc4d9b0de228fabbc03755e11ba537ae8e0619684c8186feafb6fb2cedddc45b771c44c3e88c38ff729dd1cff0c08c2d1c745d9ece52179739ea3203cb0cfb66e8287193e5c6ea9fcc6e7a5fbbedbb7db91b9b797d3d2bb90152fb2ed19441a68c5e19495f06fd100173fc63c90878820010c868830c2c3254f851a23c9d6d647c1b356d826883dbdc360d8c71660c2fff97e6ce265a6f6cccb0d1c286890d0562a0214612627431686234a0e546cb072d520b8916903575ac19c01a14d620bd92d1aed63f60aa313149bad198c48c90826ccff66585e80732a1207d79eec5f7cb0ad122eaa2353c009230f608638e30be84f114c61130f800c30a306000e30a1844d27c31c517515f846a9a08d574351a7841871761bc7812666d3d45027bd9209b750951a35dcdba68474261f4655f41364b47fb52a231eba25d88c2846c76b7836c19ef603d93678d846cf48706d11f6ac44f3b443ff880be5cb1d86ee376e7b1d8cd5294d69ffa53492c952f90e29cee76bd33e6b9cb37a96f173c74a1d445045d24c005125cbc10858b0bb808c2c56fb1660b14b6b8a59993c68e304d99344f698ed21cd1420c2d7cd0c2a605d0ff636dad92ab666db6bc9472a099b9ab66e9685f4a945a1b106b6a97ac925da23fd4a6041b5279575f5fcfcc409d0e7613f3ca7387f9d541d96ea95033e39ec9bbdad2213558624ebbb6487a5792e29c35c546f4977b1d4b658143165ab258ca22812c7e6041c4ffaed669cca7fbf4a9ebb3d95c351af3a1584ce98cc592ffaf4634163c51d0c4f90fd160fda321533b1a9d06628dc656fb4e86e68a2bf20a2ef55783ed25aae48a5e7805fdaf405c2c760bada8590142156b54f1a58a5d154e55204085175464a18257ad893b8ee3f245a3ae198b59badb2e5a5f354bb3c1dc1dc912fb7254bfb7a87e4d199410122a781eeba6d8fa29acc229aa483c4592ff1bd5bb146ebe02b18eead794428aaaaf522491590a200fd35b875184717f19fbee5254bf375914bfbf611421d60b2b0b8bcc7f25b1a8b096604960a9d0a8ee0beaad6732ecbb4b396866992c16d365287e2114c127e0fcbf9ec07aa2ea770f183ef1abb42792c462b7279cb8aa79e704d557276c5f9d7055276e1356df9bf8fdd7739b4d2cf926787a3e03a7d228ddd2992b7e86ea4cd21903a0b906e08275ee66802524135b4c50e193d1e8c8040357695ca1f9aff6cabcfae08a678937d5363d29cbdc97a05aa20325e850828750092c4a24a94a5e58eff2528e8178079c254146125e92302109a1248a98c93223e6ab6dc2516b668a191090d8fa4ade1aad7d4b9133247a40a206246648547044968fe9887dc4ee88080ad04601c014e0cafed19ee9ad1b89d2f96cb2978bdff2cacaf21ef528dde5264e675911f6e59b6ca4a302231d473a8e244d26e36637bb912226dfbfdfd9713afb703afba4f879a4b38fa7b38fcb64b7225cfece5b910e3b9134dc35228ede4658fdcc88a96a499a35e2a9067194b9978c20229b6231d9ef37eb9792b422cc22ac14a153268d32539479a10c07968ea31d6db794c911264f9eb86ab632a92e6a7b103fb14f98d01ffac455b3380546d998e4ec5846c724671199711ce31fc744e8f4c221dcfcff0f613544d5104f43f07c0d85d8aa24e6098146882a2184709181f35fc9585532543adc69bf3d324f644232f4fa82082308aaaf63994d7de61ec4d3fff374ee4100d97a0c1add49eabdf518aaaf24dfbbdd1d637f0c2d331070f0145e02e2ea4320aa2a1053ff0fc4d3ff3b01e17a3170423157525ec4f4fe5f2726898ad991117120945496f7680a939af6dbdc3680cac60f667ea8e187da0faeaf967c59ebf281071f62f001c90707c2b8e1d4e9e5e472da2676da26760aa31406d6431ba49b73301896d23cb8e101061ea2f4b183103b60d9e1680720601000060d982b606c602cf8d2e6cbd5971cbe00bf98ff3e3e58aa0c5edfe5586ac3603152df1d4ddf5ab1b9a5b636cbb3c328df74dcd1be00d5ae83153a78d161b799e84b9b394891430e403958e0058d971fbc287929000702843854f5dbbb4cb72da56467bee948626b39c661c80d5aff7fc3ee06211be8b0c1cd5b5c2c26bb15c5629d7e407fbfd0869c0d3a358c514398b0862e350c551286f90f47031b210d6668508186dfff101a1a98018b19bacc50e5eb8839efd25e5ecdcb4d333020835628830ea10c31c850f4d5be8236d0d4550d5195a5ca15c318313011432e860d609803862560c0c1c0c0e5bb499020e5ed3d9ece96eece1cd69bc4658e5f1d0c162c83e50b48bc90e505272f087121cbdc3bbacf2b5bc9c6c18bebb962734bd9ede482052dc4d1c20d2d3461610e16c2b050020b1bb000a44b01c22e2774f1e1d2e675366229ac1bb13dd3b4a7a5e696c2fd2e792468660da3946faa2487b90b898b932d7a6c59638b9a2d02d8e264853a56c0aabbcc7167b2244fdf6a07ee1586b4bcd1c2464b172d4fb4e8a870c70f2a2c852a8090421cf54561fade9ee9a2f6f573d52cced158497176ec7987b9e5e97ca9de3b5a95c2e6b9353fcd8e83b707afc6e6760a96e535352eaf997f578724ebd8dc4eae221d7632b7d491ec6a1cbcb25b51efb25bd10e67f6dd1b22ca587267e7616ce24e5c96389eeb3e5cea868832df53434439c84d8d8590e4b31c22cafdf4613e44943970731edf4344d9d4badffe0d11e5ce7326ee57f01051c65a9bbc21a2dc4fb31c22ca389eeb9c24ef10513e8788f2ef72a010923c44944ddc71a5b9f91051c662ea8688f22e0fe1424872278788868832ae3c7d4344b93ca5323944944fdeeb4344b963de105136310f87f5ee976796b7e338fe01b7c6e1a86be27c9bd49793e5c63cde71e5d56133981bc27197eb2c15cc54a16c2c86cdcb75960ae5beb393fa52286ba990d804ce64b7a2a87e6f51fd9abf6dca8af4ce26c6923bc692bb4c96a4f3d89535aee47025caeff71be10764a3b165d84ab41b567a048d812fce9b66e6408ea536b6b493c1d2de0b84b2b15854bf375f50dfdc2fb0d3f166caf73571184be636aadfdbedb8c7cb308c77b01d895d514633e35c39a375ebdac4c489765b6272b444a32330cd6e4c966a4ab4b6d4ed91af26fa8a945c94c1b2272aceff88cb5fd6e9cc7fbb97f7776a5add3d58eadd336d6c46ab99e92f47f5cb6fcfccc09c33a770bf3a9d9b4499f9a8a7a82424504102d557acf78c840c721699cb07f57d41599b92a5e2a21c2be5a7cfaeef6d7deda1f9ef55fd7ab6b35f1dce057baedef3f8cdfa7f1e99caa3faffcab3ff95b7e4bf8a3c9dff17776f7ed7b41b773d580d9e0143f90a041494548d90783ae736ff515a7905527eb1ce5076bb475f2e2b927890498864183289fcff6a84448325cedcecd9c5dba58f1ebba575ea2ad99ec72051e7842e8c4ed44d904b53cddb67d602b1ee3adf7acdbb73622ace776adfa9c5dbe526b93f360c6b8275e1bec059e19e7042ac6461e58c951e30bfa8505a6969de71d439868456116a0050b52a7d8855e254d1a14a09558a50f182ca1154aa7cddbcebebb4badb8db8974ce8c94d6cc79e477d72ccad796e71ca11e21415c42948532818018dbeb8d4970c8eb080f894c49395a71f296048a9bf0e8542599cf93be98e4999f91e8fe0deed37ab525e4e7a38b989e2459420a230450921ca04e2ad0af12680db78b340bc01694aa2c98ad85484290b97386c6e1c2e868496a54f4aed3cfbf8dc6a4b3e3e4386d40d3429edb9ee97ab48879d38362fee4c404498238255b9f9a637dfdd4c04aaff1ad4358940e4ff83b960cebcb8d3992d8eed8bcd494d4e4dccd79e9981dbc49ad43bb779335aef14a6789b1788758d4b2d49b5966fba1467a9ccd20baa0a952a2e1fc7becd77afbdb4c4f3d55a7a2989315752bafaffaaf4fbda332731b72d51d2f9af50b2a090f99f9308a50765498502646b2d7d323d21f364ea959e24119fd0cab1b99da2f4e54eacb6e8848b139f131e7b2f2ea948eb342f2fd27e220db869409292acfe6b52af2cf72f842842582184104200a1091c4da2109bb8f05f5ffa02b18bdad7d4f5b96a96d298bd348a4da6262f2672884cb4b699b7be3491c91626529814bda2f4e5389d7d4858482820e99018f8af51faf2bc39c612a5af7991c423258eaa8eecefb652946874c68804233a93f3ff412fcea818ed2b88627ee998631607afa5b05b734a1d072f0ef39b9bd987aa32ee1e904a153ae35982f55f73bf4becff9913972cd9b6253a48342a82c0e6ff65b2de6f7924db42d68228db1fc08ade145151e4138b9a8afed25e9677ecf82a794154b24509d0ffd77d448c85896931e6daa2075b3c30c1830d92b4e1bb031fc40e70073a446210392112e54054428ea8894804fbdfbfda2d7596c26a59ee18a9effed5b2dc3f3af67bc4d3481c407971688c212e434890e481240b24412444840e206445ebf5e532854ea10cc4a03541565f839a7a262dcf6210d5ff2cc8f6ff391168ebdf5e8ead080444f55fe91581aa1c89e3089afffad219783919e4d7d467b9a5ee91a96d1e713a52b134ce2d8995cb57a0ad1689b03e6056db0ceaeb824dfdd77e05935784b96ab97754cf9c8ae31a2394ceb3d11c5db573acdbe2cfd6bf5c74f663f5ffe20f95167f6cfd3cf2e3f249f37a5d26a20f113e4fff7afb344f8b3e406ff3f9e7fbf562f28a4074a1a93c4d45579877f5a0acc65044174f2d6f9f1981639a7bf75f0d887591208a9c453610379043dca0890db46c30f424d67993a0f1af168b592a3e35d31f4a49736bead371cf14897041248b480436248c2154ffb5a7a7e3a82e45af7939e517d75766aab9781687040d59a027eb2b25b3a53ebb6dc92cf61cd1404b832fa20651a20656340842dff8ffba4dfac2e11c6e9bb8a89c897d9857a1b1dcfb578362d8bca645caa04d062f881910612007063a88184c61b0c3a047c4c0820bacf8ff0f5ebca0cb05572ea0a2103942d87411b2a3512c79dc3bf6c2bd3ca4f2285d8ea2243633def1bdbbd8d7bb49a3b51bb14214b0c08c68410dffb5dc9b52d102a50af0a8c0870a441029d8a35a8b710772dccf600efb76a74714740a9c8814bc441e3e78c21079b2441eaa0a65b1addcfbd7cbbd6d531737757d306aeecd34c19c09d64ca0459c60e8ffcbd24742594b652483b8b7f53612253842022ba20440a2688688a5b099cf2b9d16659769751cc7308e182a218655621882b8a3c70e13e24e0ce2ce0a3b48c41d0d2248f32486e1da35597d8ee890fa1a39d6510e411f22044188105481800251e78da86346d4b1a2ce4dd4f9213e50467cc0870754b0b81fae64d4b40a65a94f858ac52c15da7daaef42a13ebbd3dd7de810f10110c4079644076a70a0141db0406ca08fff11caf672ef1f2df7fe755c2779bfa54c762bba9c49cfcc527ab965b7a2cb316e6b8e4b1e6f9b38736f2e2b2a37c7e6353bc7220329880cc082b911e31dcd7626932d90c5f3e20256e20253ffe20274b6801515b8caa2025bea2d881931c896204d82182002c1128178f9aab798801e62024d245042024e0944202220c78c8840504420c97f0fd5992cd28f68de4bf580350e10e200de01444403f830e08c015844034ad100971a05882940271640452c80e7ff672ffe58f3238cf843ff18c57f437c23c4078acf81f83a5f3766d273fd9dc192674aea2b85f671c11c3ce6383107cb9c2a73329813f2a1c587152af061850f217b68fdbf6e6c528bd03eda6c2dbfb406617b04d1830c3d5808f540410f233d84e441471e0408f3a8210fa63c20c0830d1e5ff050c19cc23d3ddba419caeab214ce45a96327f5b9e34568b0dc5ab395668bcb5693ad5702da4840990470a9346629df774c71c70d77ac7007d21d3f42397bc8b9928345ce939c0be4e8d8f1851d63fe6bd05218ed99f5d06de619ad3db3a7278a6f60b6ed7293a0295360a4632c265ba2635291ec725c47b00ed7ffd77be940d32fc7744c753a9efee9006e4dc93a879939707324fd575a350eb135a8349545d458aa19000000500053100000181c24140dc8a462c1644cde3e14000051b47468dd9836d1649d54c81863600600004000400002775171a3e24156cadb10d96309df5fbd43f80c71507817fd707ca2c8e3c1d2c485cc87081f139026bb817ed313d887db583c563a9ebaeb5bb7465f92774d263f8d759f1c612a3d75c6e395969eef524e684d403039fded06032b9b8b89b1ad7039aab78d2d2f47692ba1e789b1137e6601a9be07bc8d895542b0971e36218b2f75be69489b84c1fe475eb70f9ee2f68d428f3f249d366b89df4583a6d4774b7559d03c9bdb48ed737f74ed6c625826a0006c7c21caf0724b86ad3fb229fceec5e1ccf3fd0df78cdd3e6eee6eedee483f8216b7e4a2dd6eeaaed2f7bd3bebe526a482e5357980d1cfe321c504e645716361f4761349d1818a174b657cc657a067877a3280ac2c82cf37b81a2e08cc74e7f20c4ebb55bda8aa05e39fb1f2864b60b424dfe96a000b7f94d84597095048a0101dd28cf37f4742b1717e4d0aa7bb14e47820446962155d27e3b9561af6014400add1ce74c2806852823a07782bd282522c52989188714c5fb60f17be64700c3be4e71ad3e92bd352738e569f5248fde93fbd02fbb45553ea760903b68a5da9b11740df2c9d7b33225a60b18279bc82351f7f7f974a1683920d4c981b42fe32395ec054b2949f6a39a3ace33cd21b2e356352a0a25343403a9037cc39166fedfce56c18b2b50eb9f6b8dbda5265a0e0bf4ce92e6aa0aa908736a8a57602b8a15f037d8171cc42a87db3094f7166539737a90b43a84344dc96d1377cfbc4f181761ebf30d4837fed1d28f2f0b81a23b59bce23fb8fa34a3fc048d61b171cf93ca840a4c69395ac7b4fad4e17b519e9ef94c1f3936f56fdff8441122b00ac6e16696e10909e7aa894156852d1bb91404fde7f1cd93c394617050641433236c309b9e31659f93dbaf9c143e3626036d248dc84a010620012796412a8d6661c6ca4a8a81a74b82e95e32a80f3d7694369ece556fe2148be8bcb1f3e2d4ee2f78501e83e229e331eae567489bfc48ead1b6133be1b837cc62c0e0de3a38a284ae8509a241e0ea0ca3d65b2d287c6e38d0ff6c5cfcba3375cd7cfc2f82f6f26596c900524ef851710d66d278b03d4903f76b00ddaa26ef7fcb507a889d598e915baa8ed2bd0fc63e68aa2cc46d96b8fb92625caa608a42be71154f9808222a06d62f4941b4907235e5e0cb6fc428297fa84e3914fcb8aa21f35eb1f4e3efa48d33287e4b1c525426070d5ecbf09e71ddd2051dd77b7b0d7dedda1b2cbcbb55ab504970ea21c04635b7f6d536c203f3d8a4cbead514548e382222dfd9723479271b35cf7887374495ba6a099ee386472ff770698215588c601b4dba6a8c981967ffbda66cc348bb3639bb1bc38bc1bdd7048808057c1bf480403ad0644329cec5701f92c861dc4ceae7755e8b3596465cfcc913fdfda9e5c348a921d980833cb0aabf0d0af2d233ac23c7f743e86033314c91709ba6756b364ed92bdc9d52003e54953eb85354500b251666f971fad0521fd20812e405fccd99035add92a61782ae43bb93141232aad6cfd8ad71087158ccae296b54447278c839e8536f0e86a55e2ad13e7c86df20524abe12cdccd9d45b144ef69afb4d6b1e0d50b99ddbd7187b808395f77aeb60e6f042c8fd21e501147273969bc6b4abc81744d90005d96c48a4b57bf18b91fe7c1d7a5258fa78b2879d15b9c6491b336d8b5b549573ee5c65930f4049f751bb77edb9bbf836980e34317c2314a1abcec9b11beb4209ee15943d8bff61b0b6ade42d640bd7ebe44ad29394855fc3fe6b19ac91b4d55002d62887bfe83fc7f431fbc3b5354f67e420a455fae7b4f572cd23e2d8a22cdce13765a36c309f4356977becf04e0cd9ebe9644c939131b62502b0676e4b20201db11e5fa8758c84a2f739c6ac1da4462c11ea5d729b9e3a3109166e6da97a247816fd0dfbc9a49a4f8996cb12f007c272c1dfe870ed836a4bf793e00ec2728fc7f1ed814d35f9384dbb6fcad0cb8eb2adfcc713b22b5b6f167a81ec187402bdb1ba080fe264942221a2b9067714f6e71dc0067b42e36b36ca7fb45cccb6739f75c0773b45bfcc395d0435eb8f8a125d7f1cdd1897982fb7c2b5a18919b3ee6e490276fcfee9631ed40c5ce0d822c663d996d823da258e0405d234af66b570e482f394e9590dc7a45afaff02e5bb4531429ab89aff67d69a376a83b935a9ca5b8157b7549994e1a09b8ab34bc5f63f2414f08829a599f4578deee8e044d68354970f442b13b9614a4ae3b7781411e13ad77fd496a91b72490e754019a7c78aa39782a073d3cb5efefa9d2f2d5905dc7873499e343c3e61aed628795593bbb4facec798bc7a4496508809e61e024b2b314f4fb35136312fad3a05e615c1b9eb706361f10d2bd919d7f1ba457288068e3db68386d44569b480dea4d8cb86702440cef39b136f09491398c313d2d8696d268582cadc5ad2df883eaec5d9a6c72eecac5fa2358745988923db4f7d585aec6ecafd99015b41fd0c2e393d81f081665b89158d5f39410969ae340685babfbfc2049d2fae3a3e76fcad774217616dc9d659dd46ea19b43661991db2d885eeca836e411f7ff34c034102160019910e831b48f54bc834c59e19962e952f6fc48077fcedcd2695dd280a687bfd30200ee1aa37c8448a0c56592691e48d459834139723e0c74c8bcb420a655a3e8d01f27b54ca76e627b8df283d809da70153c5483cb63e9801cad5057d7d1e846b512f6efcf1fd9b8a00210dd8cfb2af26287610d0e7096d6229e759c87545c77429723e4b9b1cdba6d30f74b67e46d8d73ecb0640e483c4007b1cca60ce7618a8bcfd7ca907090b16dd427fcfc114f471bfa1d20c8faa9c1d37bc9d01bc9e746a58ea6304698e5c5cc9faaefdb5e0af3de64831f63bf00451cc1bc4b68fcabfe1c62e029ccf3f9d82edafed7b461a2adfd28594cdc9a18e47af01d18fc6c6154cbf9acecec1d4d94be7769cc7f00cb771f49cf6463ce00169763628784172b58858c64f2ae55561ac55372d9c8ed559df71f0d0449495c81f12b77fb3391c31cc647d091c59c437387d3e0c54b4a1d3cc93572d6f4b942e41c83f67f8ef69f30fbf3216acf155facbed553df092374dd21211adf1331561c033774120c487b4f8880993e186ceb209eb0f2ac68b47612e1b83851d7c0ce61f90e669b2e3e364659c236871ff0ff5ac45d51904d0bb8515b40d6cd2c2f9acd54917957ac0dfbe845c9e9c915a753c5c33ce3b6ca9297faf3d6c8f896c91c1a58492b36934b3ad9d0e29898e4353a958e67a98c8c5a5d29c5db1f9f28ad797380010600a83d13176b3800a36f1776b94916e4d9b2b86448587a40c896b1e266c4648e2ba99176ba86e42b251df4d19ac48921d7283c834d02be558bf76b9c06ea22fcfaf6a8f7a3540c46f73a7c2d19dbde919c49c1f6f5ffb217fa421fe838393009cda0d358f2bc242293f1c0268eb2cc6a534196bdcee5c5358662e5ab1db400c9441768d050bd24ecbaa0f426f437e03b94ace7124ac2d667106dc9df2e05923249cd50e949efa52ce557ad99edef7ddc48831be8926dbb9d63299e09a350d4acdcf7034745fb19310180b52485c68306ef97d896f010ddc39012ed162bc5c07e5b47d2fa307974227a5cd3ea77b1351e0913563034fea7429e3f0aafe13a349e4ea1412db71df8200fd5fb4db617c34b842bc1b500a60c53153893bc8c74039ba79115da275b0d949c9f4c90521b9337972eaccc7b2c007aa5416e163665b51d3b22c405e61bbeb2465196277331caef2092e6f155e8b4ca1a8fcab8415f3d73096f305cc4502bbab3503cd0e90acebbffee6f1b5d5cb12c8c37cabe37e2ed497a4c9ca3475bb2a4c285638979b04adc41ea3405475cbfd9afb140bd7f899c855547b425dee813824694dde1038c6f22ab98cf4ae5923ec5709428eb27eaa863dbe40fc4ff665edfad82effbb1e811a7fd2141a9c46849f63063689aae5ade2d51bcdda66e335856cce2e66b1385d372393463290cc4e9a9490bf7843ab38953d2ff9437eb9d27690c0cc955b9ba451ca57a628ccf47709ae496133c905cd751494ee9a4e68ac14618c51c11cb1e6e0b83a90547542073e137bffb261c59008a99f9ed693070adebacb8409c5c4bdbd3d27303f4477e9e2f4a1cd4671b03836165a42d5a18b7855600ce7183eababb78327e1b097d06df400a421d9cb6afc5d5c9766e943f4f2c867d4761d840f4b4e09d6cbd0ee7ff11a93d81a7b4103dbcb8639c4e2bbc198667a3af4413f0fecf1b029d757b60d9da83476a2ae7d4a688b5c1b249e3b0ac94f3b97d71b690398ee64a46bda2edcb2981d1dcd227cf429693721c9efa560e9fea303ae39671d1df9132ad776aabecfb7866319c38cc495ae9568c30100ec09e02cc67353e868ae452f3f8683fb73a630ab79ae08dabb7ff4f65b683e4423ba5fa1abd67126c0346e2c80c77f0f634ac16539f8a994cce7ea77e7f3d21cf518b871277fe1923105b6bb9191e50595aa81c0604eb3c57bc109927363ee99f2c4db9ddb7af2e2bb166aeacd4857962b18ec4e1af6c5398b7e2c97edcd1853dd1798e86900a7406967194318f950c7115b2277443e2daa6d1c028af35fb91d7268857122687b0fe6a8b21c962fb1e371d6406b390313748dffac8394ef2be8e7a8a5409de1790880aba4d90b33f9974800c05cda41e21d43ac572d5d9dc1d3d301064d20acf204e6d44d8a6217b63a8c7b095cfb5088f12e2821d6d61719d13a82374022551b8f6bcc56c1b502b84eb9fa0deb0814eedc76308e83c3d2e493c2cbe96d7ebde5269f457f3511e7efe6aba1db8e6b1a106ef1f64c5845d9d7cf099e5872bfae2c9fb752a057057997ca3a0a203e12308d67fbfd4ae34dd9bd03c9fbc1184e3e866f7101569c4357808242985f5d793e9ad5cf91b69be5817c2f2d4c8f01757861d62f4bbef7bdd043f8f8fe9ee1fa8477ac2d2da62ee0cad16e9aa10ea8a989c909c7c5e48dfd7d5e500721d07a527f4f860ea3c92b3a65223646357bde4d1d111ac009c065ff50e68d6974e32227193e0c0dedad1e35853ef3761b1e029babd9cca9af399e0061643c11a65c42af84f2f8e3f38aa69d045a7f38cb18302b5817eabe5248a8b8a0d5d8a3950abe3ff237c4de42d16d2c614fafe6f3007bdca9ec0d2e57a4e82870a06bb56bbf3c1329535beb2c1773e0f347607daf93c2771108fe77073d8abf04379c17bdd89f3fa896df8890853ead950a3b40b1d200d72ae57b999796a75e3aea310c8a78e7533ce3b9467b9b0ccdef0c506464d5d1c46b7ea7a4d48d721375d5eb1b055dcd6c676562305f499efe83791985f600f54dc91ea511b0a93baa7e73b856ed206e6321a3df0386b1367e058292818868e4bea19f844bd6f6eaf38896e568190de16a487086bf3ac279e482b0913d5a2b4c7d0641bc48ce918d1ba6bb1c7e9c0d58a0eabad985efa2cfe7b04bb23f5d800d0de0ffa4c6c5e5061c192d4673fb5dcbee650739aecc817c64792f6ed9a97705d75d475b0a908aade2948910662f85acba260d617191717bd2f8326d92fb1728e93198d130b6006c25fda320d32351f912f832869a9a6c31af5d04a038160b194a0480f692195dd948f07b848ff42b3c378898d22b7e33d4f8e8f29a083ba70a5ba7be9372ae7acf753e42f5ab88762d25e0810f847d7b84e5f73506562bb5c6d815c74b84de897e8989dfef4330a7c0d06ab5a52fa8fad16913d2df9cea779553a35c2e7f41c6177108fb31fb5bf953128530674d1d37c8ad88b7c60a051af84d8437a9491fc8ca0311e78646af93fd18521f3e609387b117d4c0207c35d0efcdea697cc0ef4c96bc7cd639c9fd21619209f11cf635a92e17fe39df11cd2a0f68d87eacd8967e5a0feda6e7a7b18ebeebbd82e5521f98aa9f50763b981c3551b22762ae161805a1223a55c5e919f2f36b4272c0bc229fe64781a91a46e0e96256754f077e090855b316b7c336060cdea31c33433a5b1c6ca7fb66a2ed8ab44a8c6e5196458403d36aa07d1c7f0d678c632fa7993b20b0880d58e611b801154ded240fe3e649f264bfb04bf377d89d5da43e14b07e113b3770fbe69a3ffcc863133f826537c62f555f0ce22ffc9d84326de3592ff116399453711b6978da7dfa4105a5c2e7518740990d91f297002b88718ec311988a157922a7407476925ff325bffea886fe94e54bf5a993d0d9ea1c02c81682ee1d388a08579d75de603b53f57e78d5d53bf0c5927ad7891c9921482b5bf6892c995e6810109197b42db6f3e2e72f8563c989a865370f050b332209c40aa0b273d0c923bc440dd7c1708ddb98e1f1a3174e25ca082a96607728e86c97b666696c4e776430f37f5a754ea6f5a73648f2462bebaa5167c23ba80065d244cdb18b10fef334d91d1969d8ca27721b25e90b1233e8b44b8e4894912bb698b6d680d5f77dd81c4c02065586147f1be5384c36824d550cce5194cbf1c42d3925fd0cd2e816ea209b06bccbfa55dac3e78ca5a4be0e3f33181e3c6fd9300d9031608c73927e06285bb8075bd946a51785dd4a9c6d5d7e7e32768070797707b9a70d45d5d2c74f1f708d0a69eb50c231a501c6702639cca7f5c09da976f8d914230ab5c5ff788fc1e4a39481a49d737408d115a534c344c763d94b459879174a05bc8240b439153b372f0d516f89da1a804df2b65a78611da9d1ff922afccab36b82f6eb30e6f850998fd335afb9d29e4ece7709b55c7f7bf8f5413a0cd236e3edd0187dbcdc9455eb35bf0bc69d785d5141e92c219ca33d09d64b4c14ed0d58d9f02233dd4ced9a379993c0c8541c35c79c1af4e6c60982dd8cb55899a787534a69c72c00822bd0df36734805a65419fa87c7103fad03264e11307d06e614c1a34aa0884ad300dd5e5308000caa68e812df133ca6f14a8abaa185ede3ebb59041f6c566af611c46d5fb7d2b24b1e0b92d9ac577007b8489e6f41927a38fbd5b8690328c73b8a4e65b65c3c01c205eabdc3207c167e07b08b88a2b7b26b01fd1725cff7774df0920f5b76275d8fa2e0d9272494260db19a2304c01aa9ecbf699a21e7c3f6532cbd6ce926fbd4ba2c3a58be347304dc97f99783cd174f42e53eb8a9e2fee9dd2a995ba554d7d4e8ea475725390888cbb95119d9012b2eeb8e64f749d9783c902ea548d2bb17160b0e9c34ed05e1af04ae9403d7cba9588b0f8eb7684796e4b04883fb5580f6142338d970303248f4af43fcefeaa420f86b4630fcda9d6bef478c15dfc0fb2e733f16cc0848d3660bffc3443543c2c15ddd13e7202260bf07e6070f035cf4de1bc80a33ac174f870f8c009c64a57c4d4e34a3a62f5af16289c8a69550c1bbc7ece2631cac9a2001bc1400549b4138a051ce3c0f654e360fd2c0dabcd50b0b7da3bdc421fd7434b0f43cacc242138f5486a040a41b465cdee75ee1b27733260234e164e529d10e187ab413793dbc35b3a1979ecaf13528bc6e65e358dcb53c725de6e3d370aa58e04f6993298b87adb167187715659b2e85f755dd0648a3f3f5f009cffd81647764e134f8f32cbafdd79b00764ec24b02dc02c582b23fad89c39ac54d531bece98929bbfbce8c08f4adfc8ebc0911c5988cb0df10d8feb99ae89fa8f77e0fad3a8228d2299db807a695632a047f8457efb1e535990aeac15be2d6573cdaf0f0cea54c7f0baf0d5280815c605470119add45f0ba2cfe6af5c18ac63c75377891f458381eb3a14cb4ba9ccc397c67584e41a7ec17ee2207d48dfff7060125a1006fb1a2096e20e94d75eaaa0771c567ce85aec684b405ce200cf429c702508b899c74eecdc95ee08005d0fe2feee3b15b07728535421f9ca401a27336aec80bfceb6872f72865b07b0f343909ee7457d4a96142dceeb161a727d23811c0b71b986bcd54d17977f1996eb6b293b896232303aa8200c3f277cc5a79a465e127647e49134bd2b47b7c6fd1e2ff863dcd29d77363deb15f098ba98d8ab34060ee7b3ee1e16edb09f83953dc9d3202763e673a0653aae4a5006e39edc963045ecdbab1495c2e44b36dcab62694f35b99f3552ad5d3af077dc0f040ad48206eaaaaf22020dce7f33153a31f1f1e2cd3d6a85973b15f320ea047616cf67d2007e6d0e8b924fb660fef00f2451bcedaf860e5389474abdd8f06e40c1a7ffdeb8dc6c189c1fb6c74ee7a0c726b4f86cbc39ff1cdb88584be247c17e4bee8a1baeb82fff42c1fccc3efc857047e721870338954cc167473ecec22a7552e3b16b7d7da5b82189fdff58b3598b892907ffb496f919a471590b04106454b22e490a7027e449ae3379193e4670891575e9dc661796a7063ff789883ffe7b9962bb92f4ed55520a14acdfe49ee9c563fcb1cbc006097f2a2da17edc7a04e07d17137f3e483d29f0ea420143c9c43ed173f52a292aa941c611f9f579e0282501e701c5fdee386bfa7e5b12ed140bd15be54913c94d358d95405402762f0c4eab6e2f2c21baba3bb722887ef0ad7eb5157ff07b865ee2525f27263101adcfd8e7c9369304c8c2bfb4de777bff0387e31361c5e28a65ebd86ab835e89b22c67afab7c76e7be5fa8cc787e40348607c3e0b3cc394e1453d098fb14c9e8973f5501d72077cb3aa1f82178b461a087430fc62f06163ff6411eabd8d7dd19f4c6ba6c274b1b1b016edad5c87f6fd16ed7267d70777ef19b19d8d40ad560d5e1739eab8cae4792a7b5d89812ad88e7c475a8459d69262be10ec1f91c3af1d67fe77a6fbf7f0d4ebe6ef0bd166de7dee03761812e2b1858fb944d7cd5a22edb8fa5ff83fa1be9c5ba8b8e549cec2505023d195758713388d22c7035dcd4354d7aa33767aebb2a2c587b7f42f70e337791afd9547af8fc8c4bbb3f80d2f36a96502ed3e965fdcb566fea2c8a2253690b9f5a3801996887af6e44c10340469a7e4149407de51ee2e9eab83fc854b5612c13d0140c2e4d01afa309a353d007e0442283e07ad6fbbdf2507d9cf056ab1de56c919c71823a43ad718a1e45221dc2ebc43873db969d036e0950df683d9d3273c08647089cabac97e551e043fc87ee82edca133daf03c00070603a4fa91280d6c0366f347795597334fb68ea882d87ee1f87f4d1a9f638429c3a75637d78262143b10903ac4123bb1e8e36a56473c15d490fa19066d17e9c45aac5c838cf4c8ec5ca20208553c672184ab065802d4c959d3c778ab4c5d1bd93f5fab5fb0e5cd2d12dd21cffa1c1bee77e29327577669c64f005ef8fd06d8c3a44e0f032b233c7fa3fecc6bf2ecaaf87948e82476a97ff2ed5a830af9df585ce74a097dbf04e306ddda7926c2217eea6c8daaa92a33f95799daace88acc0b9aebf0f85cde627d9a58c003821ceaaa52245c59670bf8de4190600af0283af4d86cfe59a8c7f90edddfbd9ae15ea04021f3c0b8bf9259f6cb9a454847708755e370094511e715aea178c5d3ce2fb3afeb3456f891cebce1ea3e768a49afc4a7f6828d8036513cd1bbb5d07a327af24af4c7d8daa816a3c1a9be94ed60f70235f30ae3f3df0e469eddcce81bd2c8473c1f8d3ef02ee93f93367e53ecd005cf440cabf0203c08b06cc135c0e892ea47e571af10615223522013ea483b35137b6c8eb492810c6fc6d2c2dede45c22ffde58a3902cc5be61cd95100c3ac9ccebce100a3ec1c315640abbddae20eecdcb04ffbc9c58b7be1c60a40ddb15cd640b1b048b6cb462bb1c0b055d29a27d1391f7edb4264bcf52c7c9a9d4ee00e8da32a0b6876e82b11f41430552363e3eae4c4435218d95a09deb2f576df0efd71b34410df5a81ab656d6508814cb4702e1b0ede6fd08c03d1f1499ad31e046632a0472d03297d86444c3c6bfda8a4b8bdb748c683edd71551b1e28135637e064f00dd2720206e471813d9908fc5303d37916a54fe4509790419baebc00d33afa6aa037eee719b95f8eafe38f0f1267393fe60c9c8a1ed911a1443522edef65e21a9f4d90068a878b7855c1d50fad3ad9881bfc14ca70c4602352e27fc991ece95b7be797939d6f2e79e94c41bb70c2cc7e7433d907dd7ef882f38ceec0a43e648b072b19efafb84e60be3f5ee478c235385ee3fa59a1d6a324781a1b39fc67c9d1a956163e20c2fb873e057ca0703f6e1dde9acb60bcd2668b9b4a21763feac09557918ffc4f4eea0a4f0d9cc44eed83429381337c8712718eb9a927ce6cc0293bf836cae45d535a45159196d4839700f6cc604f3d2d8c584c0155e30cda8c8c01717fa0c7e5d9984c4c14856f7767a6f730538e669de833de781c1b271fb9a232d15700145b955f363550b57452a63da8dcbe91e46a1216106cbd184e90eee6ff0d15acc2b25ec79a74aa4f71d6b3a5005fedac263db7ec10f5071ab64bca6b59eeab8e7e035c4ee68396e79c28f6cd911798ed0d31fe99f342178078322a49077089b0b03864874150a5c689190c20ff20d2022679d1fa1c6d34f62e26ce115cdf2599e9cee7c1793f4dff71a43a40a56effee4d863233d7a6241d1fa82146042cca02ea20d2d1602c6eae8f158c5a0a60be1360dcfee05a38f682a99749ef3ddbacad74a9d813a1248a4bfca18e199e164b58a4e919862a287aa91a63fc07f7a07e88d0bee25109f496f98955ca29a89738fb5b3c33a96f1d3ad6b2ee027dc4170a339cc9b6e0713b9bd628f098fbbfbcb1a3ddb9436628998e592f72548ca331cc564ef923f4d834cccecf4595071a8c235c1fde16ed8e6823b9824be2b7f030fbe256b0dc35f76eabea2e804fee81c82dc47e262610a7697e2a5a4bce754320d4d44c30859d1f49b848b0403ac81841b9c660718ca144d3a039da6e17c39375be90011f2099cefaa288f46623856d77f6587688b8e38a1a8f5c204ae9c2d88ad622e89ba77061484eb283625d18aee09a88d852db82dec238b5803ba6a9309297e2a37ec457b29273b2be5218752f5f77c2b86e755a05613055ea2625b407018b5d088328b92e49408263f8bc8fe4ff2ac70b06d4006baf4c7afb1f389453574bd72162b12cc8e7f173226d165af8735e024371b730f4674135335e63f0b25d131294b529d9db250a9fb84b16c7373a116b8c16efc1ef63525e89a9063e09d8547781d7a24126d23bf138049d621daed9bf5b70e522760cf5816f23b2031adadd6ed6378beb1d596f3f7b6c509eb57dec36c6f78decc1c7b4c1eec68941ecf57ea3d5881ceb14220d00d948b310348ffdc03557fb6184df31324ff5504c56f22f8c056410f922c32bc1990d1ada45aea152e8a52aa58d591da4c0562c8aee15182408f5172f8f5a35675a6776610ea051930b52b39d65da2d341b02fbcd7e84508cfd7e0dcbb5b4054b936d41c91948b89c00194ac8f23d087ecdb4a508ac1265422edfdaae7bd768cc67fd19fe75b43f70a8bbd913895cd59899e4f020a1c64ef2ac2cbe46064637873425782790c2e5ced53bbe8e83ac5f56f15de66ece8a748f15402bb7d32bd70246106f97f728e36baeba596034846168b81921bf21f90e3ade509bf411c9576281979e61851f85e5afbb1b5f591b0269582223dd51b073ae5106852a4dfac27e953fbb4fb873668aa015113c7613d56bc3524fcf02918f95fbba16661198676028f3a0229a68883a6ac6c8d8c77e878f73fbf84f54c5ec37bfb9f25ff0aa00f76f9a96217d21a23a3d627edeff01e39000f644e6bfffd25fd1cee1f1bdc3de6bfd0010708781f28d2bb777de718c22a6c5e4c6189a11719d2f24a18b55fbd02b3db45270d4488aec686b5f6a7afdc2d132832f6b947cb6250a0e82204d31e3933ea780b8e90b4238a3ceb483b0ffe4760b0abb85931f74f3211c33cf486ad7c7b8cd71d6662479c8487b6b92bcbd9c81b3236b497911f132d8566d0a07de0f8e6cb0ee0c80bbce90dd211bd0439d123506de95391bdf26379c641d8e577281d7719aea805e5a0661576ab69740fdb5b5f578eb5ffa614b586c7bbacd7269afa70ee2abeb92ba1587c5c923aac7b87512589eb92d1bb1404c9d1bd2b4c20da610242f4043a7628c21827249f6ad51ec799c2018f0a6c3c0b501008f14ee6942152f843569b1ed03d866d0945e3b9fb819e4438006513695915a3035e1158dda51a6393f7be1ad08ef88673315be260c55a88e24a89dda96c308afa221e0a10d08d933006272088972d2dda687f27a873311c30a3de6573ae3561e996e4b0b688e29e6f40b32f9481d70fc4074c975633cfc71a966f0eae175e2e74e3026fe4c9bdb1ad0734061de6d7e0c52af17648faff0b7b861f19a8c50e6fee04514f4e4d2095157f5366ab71a00495ac353a5ac6f6b0b0c56baf80080356e16786042c5e72dd5e29da871dec73e6a1f68148c7117a84bf6cd00116143fc865b4d431edd449b5a2963e9748e6d88908f7909318cd48fbaa201dc64b87d518596ec00372cd3638f7fc326b623a1922db1a59d77be8148428281aeb063b60dc8e0a91df3f2a49d2304575a5f6d9c2cb8e7baaecbf6f5961d3117436a829a43620b859a8ac4de1f2e79f72e30d08122fd0ae2e3447e3ce626d4c71f1a7a7c126a4f55ad6fd380ba9e99cca42a759bae5304d26f4cf1b53d88cb112269b547d035d482990125688a3082401a8f1ce8e3d20ebc5517888c4d180d44c5108ab013f55106fe1c8eeb4f4ad70964ae6dcae3b82c73fcbddefe80dd4280ee3818bd4ae4bde751b8a3a0747d921577ad0f7936019f23378bf850a6501e50b19344e7734cbc462e0f492c2d36343c13785988e617429b96166108c12920791a3ef18b7f5e252631831a9bb7664391a998455aa7d742fa6b49fc52c7c759d63e36152ca18c6ab846148dcb351b07606333ad7668a6ed69f1d6acc9a6ecfaa8515845469a426d64dc7483d4d90b1e35e14386036d680bca3632e9403f385c2b463dca9799a3e57cc31fe40b633744bf5f7cb818c5af7a8f27c9a4d88b7daea99ba1db01bba02002d2bec3075796991db30ec1dc2f98929c0ef031b7446206c9454ad111226f19614f391eccad26ca805f039f6d3a4d712528a00408045172bf502b5a5c90488c4f2c6356061f01ef33fc04763c086600e37b65f581bd24a27c9b39a7f0c71b9cea5430e36e091998b19afed0741a66bc5c06089689e8111aefcece76e8a5c3ae27d1913ded3d60f17c989d019146871c37bfe619287d227453baae9b2020f46910b821c276fe3266707a64679050650a22e2133aa7a0f16b34345144c4ae87134dacd0e1ce3abfeab8a55a7f9827b2d0892a369d4051bb0c3cbac10d04f254f0215cd1cb4438293c497cd186130cf701ab42e94ff165ffc43310fcdcbbbe5fc60a25a8546e4274199d30d213708a60f739f2cad37ee467e370a3d517bd96ff405cfe44406081334a00ac618c80a681892d6c77852a57ea8ea3a0184b8891f25277d80e941b4fad9b3959fd37e1b4af560bc46c08c868fad56c0d58baf73e6466d959e7fa4047aac032ef09550d0c748b045fd1799dd0d5a3274c272c7291af54c621c1d35379f8b17b5ee8ccfbbe94e88ce136c3d990b9c0e2e7b2372080532e836a6340f730e95dfe9d74f031fde53835a19943bce7b52baeba50b8a2111fa65be1e97b5b46c3c0f2d16105a191dbb0ee22cd5820c28e8020bdc2cc92b0021f5bbb78e8d5b1045b43c482107b6cdf2d0dbf8f9ba124e761136e84a1ce9b0094d484fa620957fee615abcf8441348e477fcc64bb493316b0984de66e4d773e620a22dc8919621606c78ffe63b4d3db468688a05d2c5060305e203ce78059c49118577d4548c462680702fb76910eaff70e27419cdfc7b1054d1da93677efd4ca53a0becfea6f2d33880c4c117e2d057e322a7608f7ae3323eff09622bbe1430ae4eb25f090470ed5684ad9d119235ebcdb3e3c2724f2acb589d57fd55feb38c51f5ec2343e1305a75e0660c875c17f820675af3a382e2c3e47e5347f4172610855131b863296a1f0b887fea519a57bc76aa6df23d7fa723d3a749679d5f599c76d130c9f97a66f8d83313ee7c0083fdf5caa5fd459a72ccd671c3d54cdc681cc82f6982ae84989e663caa45ae7346adeb36bb7abcc2736b763c7d827a97c2f701992f11b90791307ea793daf7b20d0b57aee07b39270aa754d980a0b600946fda80db8432f6dd1b420ac08242ca0196960d12c2a00719c1ae439b94d43082d2624046a2fcdb36a93826956b827e1c2e6ee8aef4a5c5c2d5442252e5fded5a2fdb1715943b656dc2e84643bbd1d7e85a66d55d6678c1314461a71ec7635e8f94acf22c15f903c2fe3df71203d07a2bcb2b58f76d9b74f5ee302abd2a0cac12ae0abb0ad118db49ee5632a6e3afae38cb07831e5fd6243aba101d532e58c70c5f5c4e6f9a30a7600e75254ffd463fe55f947ca6d2af283cb5f3c4e7cf1afff93c707c4d037723a2be99b4bc39e9940d1247c9f48752772e23344cba813cc76b9ef7dadf50b7f51e76744be74741017857c9357cd9c09910f34ac55caa1b5a768163a6ebe8b289e315c090769dff8c983ec2d66e65a8b3131cc81812d8336c5170671867990015d7d66d12ef26c17c48d40d686fdd8bc8c7f07e87d4dbc7583a7c3fc85ad396adedb961bce0d497c2384fc8432ef0ae40df9b45d5b09e811a1aa5152035b26d4f396c54cfa6661389b37ec2a48711c33dc258a1f53ae0281633a98083bb118a7508d114fd003156cfab92284225f7c9f2c46db167386f730d7ed1e99508baa14c300e337013a6633fea98b4a52602153186d869acc0b44461b8face84c3be8e1856d51b71f38908bb1e57b09aabadc53ea2445fdca47913840bfcaf2d2854a0f6a18adcfdf0167d4ccf450e4a891e3485f6c58837e983a25d310392a5ba70acf99ad481f1f830a3b256ccbaee8b8227a35e8a58b28dc0d1da29a6be70ff16734d580b26606c1bdf3afbc7d2bc21ffa79928e43bbfe1ed7fb481db9d1b1cf91c7d7abda83ff750db30fc7e8bec7cbfb369cb36fa021e943b6b47ca28c9c56765e9f7c61e5d5110d898b05918bc778c726a64ccf68a60a4d446e104a32af659fe3eab134fbb3193f83b3a9fd39ccb32f2e2dea2da23cf9abbdc8f15f224983506056f87469511bff2f01b18f25c9228f152fc2310529a28c5553eb71a7a6801aab79a472f5800c4608be08b59f9e7152e3687309383a2d174abc57ba9e7267c834e657d741e73709fd77f76c0289d568a7adce14331bfad9a0f33a4ca976804efe23db5ced0182e968224ce30c924bcd082e995de57faf2573d4fda0ed66eb3ae0d759865d4b7741dedd3c69981fa17dcf3b8ba773bfbf75cd2445f642225fce3203d24f965ddda233fa41d66710b6f154cd4958f3a32a819881fc2ff1ca757d3386d408db447a20b68774b0820074fb97481611ec3bf3e264b01481fd107025c770081994c532457386e1dde683397b8f28d8307af81e240eacf4ca8a513044dee4ab9ae80e89c23cddde6ad871345553c3cf5a80cb29106d71839d1085a4b710fbdc5bc82c37f033d2be65661c56f33cb905bf0c12b4bd18c8e6f786430357e35daa5693099d67aa69c036416c37519b933c37ffd02a5f63e7090771abcd90f7d9e01bb9a3ec919a7cfa5fe38c82a80a59f5ad1ada62613959a2c1f3a7a0b7dcc2e5a2a32fee6e3a8f58eacd2d840b624199f7d1c640bacbf639708e3c9025d19c9bfc6794b5c0bd33aea9a67d79897e8c0e48f2854e44a3f0f6a9928c3ae400e01afc26acbd75282f76e5024b7af66fbd3097f6081cc020d62773e03438bf0d1dcff01f272f36ce1de013cd8e10b1279a375dcfc95bdb1fc8361cbe102636390cea7808793be4b2a13744110a504731e6dd06f0b740d556ef5891368763ffa4126389fcc256ab8cb9fe69d47493a615559a3cfbb059f7eb8e1d77859acee4b6c48a401b60123d4ef0296a888fb67afacb9cc5da7f09a912beca4a62b023f10b3b64bf9e28138d9c01f87c3757cc8b3cfbaac6358feb11cf8fcea8515b6268c34f672a3e83de3ddef7f2c5b5488546d26108b28e01074f50f180133bf2ab757ce0aaed3793727f2e987fca522f29e8ca35ae335544a7b9cde7b8013cb9992d660560b5e3a603f48907773a405e70f56685b9c36504e30181c096a0386a98a03aec70705033154efa3aace2c4f39474751e3d34b084f692fe2b0069179f0446d012fa9ad591608cd8da48a7d9c3eb06721fdd00e020618ca20c2e9ac66c59777c38088cd9ede70eec1ecf8ceda63d73edc792cd4414360d684bf57241b5d2dc6b81b5ee4956a6313fdbd41ad6d5e8edd070a21e755d6528af5865961676dd9f5940749f36c41fb8fd8e51b83e8af26d88358526b2ca4e1a2e1bdb7f9d361e6b33dc095d18ed43be423d466d004be044083623d22633eec7af79711f9bf9b8b18b47c4924fa9aed721ce2663a7dfa4bb13f9138e28f91c9d05cd88c698a00e34a80d0cbc402a7abd1f00457f2807404e5fea8e51103140ae88a2fa49d76bd4f72d6152104376652bbda6fa30dbab2c25a0e3417f61c985d7c90af74313fd1da67ce86adcee4955ff4fc502d24b0bc33cbce06ce002b7e5b367d9de5ad44f6b676cf27db35901071670afe22d54c9b1233db73836c4e6ac1cac0090b5c3a7f4b51db9de5d1734c631f49c18ded45dc2652548a659f19baa99a63d62f5cd9608b8661cf7fc88ec0ade0d13568d8cc4091e07c7d9d8544b4b521e616ef141187ddcb9f4caaec7be8d90cb66ec8ac2f78a19c48f7d18b55d596b7e6aeec7c3ec1222fad3d5bea16033b6ea9ff30f94e97d661e922ed23bfb9557fb1742bc36dca6b202f9218b2ff870b64c7aff4acd74513fcb8b1d456c917adcc57a88b77c978093edbdf8a5d4cbca0319f731c8b61253a77077ac2d575cc29c65290174ab6bf04860b8168737dfc9102903df24368f1f7b2e178eb3a0867537fbf301dd75b00070e5715cbbbef5f1164c30a0acaf78ea75b958af893f4cf76def228bc6ab77bb89d7977d26dd07a957c35321ebeeeb8e8b784947a4c5c44c85ee3dfbbcf8cd429f2cdc161c5203aab4b572b63f2e40759197311856661a94e1cc9cd9aca266af0489cfcbdd653a6ca84cd1b70175bd6ce932840f5f9dfba39dcca3b15b8bcc9d7a077cf38b4e43c2d0579de4186a7db488367c2afd8cc8732d7b3dde96a334335804c0bba48fd0465286160129e4481274a79aa46b54e93c2e6b557fb2d2e72de5f0f92079de6b46cbe80af70ddc05dbf944d01ef405129b75cfae13b113425d8444715edfb667ef8228415d7cecb2402405401583d9a714aaa3b8afb9b9b1626e82f85bd8d23c1677f9be212935ca19d341b726495f50d178a17db767ee7b105b922361938e6ef2420cf5572ab2cde3a05894170bb10df9a35a2e5a0d9d5a9078f169825924259a9ddd8cf0b7284105c4ac8453aea54056c6ace84413998ee6af9e2d8cdfe06af335164e90275613a357765f1c55c718d037941820d837b766264d093bbfdc7b703e589a9db324df79612fcef727e912acd2fbc12c395e98c7d8b1278d5a3facfb2093ba0a2cda2da673c300c8747da11574389142f859a588005ddb716964efe42cab10f97dd4a04a3260a5a6026825e1129294e92e7f63388d2923e31d8fc74a34a0a42857e41f811d3c74131ab73ceeba63417c3b902d4b962a476d8c8323527f6af03dd7aa2add82d9e5d21d4bd3b63f8d5508cc8ccd55c07a3e10328ba0c51bcbb616b001141ad6ccff3f3a52ae6bd1fd4ca26c6d2c219c7bf9e79d7a2e3c7ba9257c7b5092002ecd22082564eb9dc786681fe513752b2064882ce7dd52c19c32984ee445399c00e09bd31682a5f0d7b8268c65436ba0d4356018998a8dc55b108c85c25e42587866757b4341222cb7008dfdcfb23e80fe0352dff5687b3b98ef8289650de50556b045ccd304befccdb98564b82a096291f8cdf2760984f3a5290a44e3871e8295d4ce30812799d74142d12bd245dcf315f4b211384f454e489e87a80855f5edc06f7ba2ebd1219103f1ae4c0ece8e9e7b86b196566358b05cc4df733a2ca3e5cab2fdc14807f1aee61f8988252b09f975d591c82c0db757b294e16594f050824badd4fdc72bda7cc0988fd65817435a9cfc5ecf0ae843a427087ab7c2fe99ff5d4f65861a8bb8355a059593c638fecbf1e3a826ecba88b6706a738c63869960964139c3d0270f6aeb4a99ebbb0167d39f4efcbc8d84c7b0935899900a22b627eb81ea21dabe8162c59ef159e27d7df1fc13d46305e063c08e38fa1a2f902cf4a1ef9a9283f950a2b9ce2ef5120a77f313e7e5b123c64f0262a11e2327d243d216cc9c58fb1ef7b3994793308c3bd16c10a0d18170e663db168a09583b91ff6e79fed89629b3d856c6602382e5409d99925aa9c119ed429896044003f6012d99094afd538de684beba6b7652791a8a5cf3cc39ca1b042183b72a4339e9ae4c3196da559cc8660297bf7c499e329fb91e7279aa905c9257458f8b6d393ccb510c8b509b941841e1961a04bd5ae7bdad1b7132039f8d7582d268021d3a9ed91be2b17950d7a099dfe963834521e13ec9a009ef037c908b8fd40e854dd14a69eb2352ff34b3fc44cdd4cfdc9b2068c69794998649b60252d9ec53ce26c30323a7be0a1d9d2e8dcc9241d66c4f8baa1878a66b4c3d20318f7cb8119a5a146e839291b1a2dfb1c458b63f01f802ebb396276b7631605d285d631b0245bbb3efd7c7e23feb5c8456d6cee8430299591b9e3b0aafeae5fb0349ee5408ea192cbb605ca9957d1edeb73c077b7f08c421c4156c966017978faa5301bfeb8174e71731e1e08101ae94f23449880823d054c3666ee2df0d44641641d218e3c297868bb7b60d9d0a92abf6f012d403850546b74f4f85c1a0b4216084a12aee858dcb42edffbac874d5913fc4c19c893c06c9afb179fea443106df3a19bee90a03da1acb91ce8bb69102d4fa1c5c32eb3bbc478b459ab40428ea0d9c4381b988e037b5714621a2c733015a3240eb88844075a1807ab8be0318244ee29076f71d9f8a70c88e30f36c992efc24cf97666adf3d19bea51ee0ec446856ea36020bab08b7a543c33ac54794be993443b9eb60dfd9e55a112f64bd2ea993a220a46dca03c0d79634f6c63396940b1e7b052747d1651306971e7ef68338af2c5dddbcb20bab86bba3c2b43348c31284d9b759c7a62251a47203ade31ce59a9ad379f21f9b8279d35140f3b8c1b02a0fb61d1c10f7f5f8cf8fd72dbf29fe5c51026a557d536d2eeb105b53aea8897aa7a6f0670429f948c44e550a0d914072b73a8014b2010a6188e915f83981895bd3f5a0a3493aa1dc054fbd7f9d990dbefee196519f2ca6883d1118b85ebf42c4c50b655a7201d13516671a134d50284bff0ad0bdb09a642f3c149d889eb587d73424b1a32debfac2e237cc88176630518141aa95eff800710bbe87ba9ddfbb24defd95f2b706d96acb173bdb27c42d9b5a0198b2eabbc80641676ba36470a070db368460d3923af702771e00ec74270081b3a0bd4e043147b9479bab7ffc6c287bb0f5995a3a23f49393ee0dc99d4f9c6368b10a22322cc73741c13e90ad22260bb6e01ca9849e002338e225d1603c0f31949bc406f8a9f5594225739d39227158c362c3708b0ae5132b2224e2d300ee728f9917ced6901cac10590fedea57360968d08e0134e50abe98d3a12acfd393e93a526b6d6cdd88673aabfe7bdec8abd88bd6d07db79c2025e19661fbf91cfaf830f763b38f52a7448f1398926105ba8e4df2ec3e8b5089a60e6be3b649181c949eb2f9852670ad6ab8a9b538aa4b69d0ef48b3b10b06dbc8723b837ccb1d522a7ae506a04b72f50cfa0d8029744da6d3633f95df991d2dfe04842c66a06639cd166f741b5d9d5c794fe3761022f13ccda325af6e7a574a012bc766bd3f88ff20248afee366c2787572fd475d9f2a9ece7994603213016e52f93b13429d82d26d13b2bc0b584a4b19a5a664cc0bc5772d40f77cb956666e67125ffa396f8927a77bd2512bfec6ce210515b81ed8f56b4be8b677895b7e98d3ce2eb7959aceeb05926b0b2cf7c47c32c93cdd464445b4983eaab69ced1c1e33e60aebf28cec6e2f957aa635516e881ee6ef78862e828859161a6f2cedba7422d1f6b35d7c0e6be66e29510698b73d202c52dd1e043800fc32ef58e5b08312f240765450dec88fec10d5131b04d83cad60f921ebd85b7379cc879a4f33ff23ccedbc7787005f55e0cf19c39af55315289af8ae296630e85ffb7b3df5583ef8616a39011bf9e3008e02148d589c904ab567674b2c46ab30be054f9ae23201323e58848f789ee48d6d5f335941c3bc54de838887327dbd702d2bf7fa861dc1421bf8e76bbb4adf1207a43a155ed98cfbcee1cd6cebaf351d124c1bc1179d3eadcfde0f22a88c8989aa9db91cb79d54171d796877822c3bc2e1562a2443edb6ab0a2a7f37a14e76b028a6590d4eb80c30ccfb5bc114b4c4153b94e122fe565db7be43655d8b88053aec5eb9e2f65bdb00574971e4b59cfbe40db65204821fc04df783278f6e99e77e3888ea5591bbce12810546d759d278f5c90ff46e33d3f2ca3651533e0122c5e4135590d486ba75d4c6c8013cbeae0786b06c4aac200f828c8a6961c6cd8b538a82717996fe49da94f8233ad294faf9c3b24ac6737ac755b44be07c2fc96f6b688663dde191507238f2bceac46760f726fae51697c06e7ed9b5ce2cdb499388067a40388f59f7d87e37cb9053921ca5a26ac03b1f533367da375c4a86d21d7249a66257a9f6bfe3ddc0c35af0d945d7863fef070007f71c151e1e22bdfd4494841a7f405a87fe6214276c71bac5a6f959e7439650973e4072467a063cd51174a4373ee278e1eedc3b0525b8ba74abbe0a51bd950ee7a0b7f58503537e2931561179fd81f6620d96d05a1ce12a9588bd09bcbd0adba6a3b836432dd3950c29c582df42872e21225f9ac6c24cc0cc81add0518f518b0745207957b9fd8d4f974dbf1367bdfe6a757b33d6a5507c9ae0bd8483e4a5333d164d88fb4bba5d5280b4fed5edaeefcb198ff614ce7a3cb765dfd6a339b1d1a97f9a3e030d8c540eb64322bdfd77ab8493c5b1804716a94e973faa764e88fc59f9557ff259c8cdcebc62c6630904b0baab372ed174ebc92128aa5f1a316defeeb9768d8953f195a8a08e29185c56dd5624f975ba1c500d9e21e82ba46343579b241998baecd1d9d840bd48f094573a6064612bb4cf730c76199051b14fa2d882af6c2bccfc70d068deb1c24dc1fc5ced0c4f967b5b58408d1e0475d7cfec7395c6db77ef08abb88f7bf2aba975dd7f733362a3b61a1dd84d0af2fc069d688b404d460a4661767b7c6e6ba056ab141ffd59e5a39bb96a6adb350989016f8c6d3dbcabffc3958cd035aeedafbb0f1e6976eaf42cbd8110a3dd8b6871779a9b4d39618158b0876567b3306c12a1027ef9a5c4cf74f486c6e4d4f808d4656625e7c05754a54b499ff679b9a9e37d376f67b15f10001fe2faf3fd9f89a8df44ac7bfa64a7901fefc5a4232201eaf8d61ffd33814e7012e51a7bb1847a35849f522a00889dfb618038d4a35bca21f6a1292a921e0e7c6b87b77a73d00585df2a559aa3061a851f40c4d2d9af5e398a792f09bdd8b72dc03b4f648cd2b31ddc8610fda382040e8a8262ef3100d158888fbb2aca235482c1fd05fbf5f15ed8ee0e39bd643f3fd0963e77669a72f86f04157134a56a59404fccb4f288cdb5dd6182523db42524102b46662d011f34240237a017dfa56f857e732c8071e6ed4d9abf67ecee7a78162ad46393da94945bb9e91291b15d63871848a79122f652cd1311b5fb1d6e0d80b47c27e9179e7fc00651a8a4573a71ec348fa120d90f27041cfac1a3c3df5ca7fadd77b0c1f64f6bc62af40f5e47eabe93e4cbe0e25442b0043e3f55b12fbbef210827b03518f8401cf9f7d2090fe9c889bacf0c3b35bdcdda4e22a0c098a72fe06fada48220109f57d30a56a498c6b5060629f5c0859d98a5a6735067cca06c44e9351d531a26362006264bfc8c1bceec9111b157d582abcef55cb9be5241628dd22bbb92931ab09409f9d5e04f90250f9b4f16c648781d4abdc988716255429a068798c64497d20b22d0e380afe29ba2ffe570c9f2509c434964869e4800366b89bbd3950233276fb561e940276338d5f485953b494c32509e14b7b584002c889fc6cc68a92f262498fe9ec3288d2eea7a4288377af78e6f29a8e2f68501a57d0d9cdb246eaf32240cea76f93145b24a11289e0a143346f4f09285e8cb6d64799a83b307f8ff50d83c2de70e4d73ec80fa8bd6f689e53e57a6f6e5dad847d0c0a1168eaa546215fc45ecc8662427a15e44c356809791542719226eb21977f46c680c0bdef68f167b67af47b7d8bc8128e0d5b1a28e854d252f5b20b081af2a86369a710518a754092d1a3330205341c31279a96fe04a60c9abe2b97d9b953978cc7a12f30d2a0226f8898f7807285da2268f78cab075cba6b367af136caf5024d717a5cbd15f99e441c3afeed3c6f92130c2960ba487250a1538e7e1aeff17ae4451a27d51bc6943a6b1f56f41b27a56c8914944ea48cab098496f35ea1b8d44aa81615db4328fd9aafd9488c5fee59d2046401312dd60b3efebfd18f8f156168025c9a1bd64bd79303716913f754b8f8ed156f617c0bb6c28e83998f2414b0a0daee0f4ee3b0ca936616c638f3022ca543508ab108d4bbd342cc4845def43cb89beea0106eb8d748b6cf4c95c017e4c4a3ac25d17df8f525dcf903650e8eddf931554b6de7544e8f751ed159c2e2f5b82ac46240f60867cf508df4a693813f89ca9d0dc54851e27491527c16f83e5249e4b3f22c2433080ef379b5bcfdd5cbf44648dd9559b52a95dbf395aaec5ab17a56f213c2c69c2bed246f161dc1cda431d606573ddd67de005b2c9dd71c9d1a8f2da9bb7d928d76431b95f3e60fffc6c8a52f4c534489db616f207aef8ace4b05e8267287cc63357e603e2c377b98e2470a7f23c7fe7e86e80df2c4768e64ea4952c696606c15cb24cbc32a69866c524166c55d0f81e3e46fca39bd4e5651eb01c90935ab000ffad5d6735e85005b80a4cd24214a5d6018ea4b858a2e745c7552d999ee40dee0df7ef9a01208c0094b1794871dfb90ae984ab469038802991a76687e68800a764ff3123b3f0da7a4fca4540b379c45a779a0aa9f935336a453f87ec801d8b00063867ba8be5487ba668bc881a6a704154756625499248912dd347e642bfc949f7e801b158a3444cd9f06473cdc06faa139a002c3551667f22136dde5ea14fd273509d0ea69e2a14712b6f151357c87d1c558c75586f548c7b871d780a425c14ef869e8816993695663e226573700d9006aa4f4535aa75505f2a144e2ed4134376485125184b1f73a9da58a34b12226c12265a0ee0b4ccf682c1b3779af32bae8be28c10822e4a027948ac60f2b20d974c32afcafe314563a939ad2d1a6bdd1553685e0ea359defa12224e5eac09504edc2f99e3290860633f9e4b63bd8f6e6aadc1ef4cba681aaf996ec242c3519e4a2fd64d78c9c8de488e96853f347dbe9ccd45899765898c7b2e59ad29ce60d2b43c5309fb2a70446e1c0e13ce58bd58ec20a69911d7655e984ae43f120684b0f702d870c6a92c349d949e9b39fde5c9b62978d4eb8a43f6bd41a996b60af63218ae4403cf764210cbef154efdcf6df34debd58f1b66ea750079f0a97b9368e0a360d92df70e09365714acf2114971e912dd5777d1ccbf2d2a83d5c038001846bb18837b74ce525150e30bc0b079a353eb1b27183372b1ac0be01fa3e7cabd34f92318d3fb5d33e6e8936787da645c29c41fb3cc03a235a85dc4bcfd6b4e98073ba2d51f14a70b65f5e62bc007e76d90b3eb086e91b6bde38fdbe70fde2dde1272cc665b4677ff6f5b6da432aa221ae4217aa11be315f9391895f8c17ffacef7630d64537896d79abe4731db3dc2af978b478f43bc7bae44e06b61f9a528035598837062f7807e51108822fce16e1727566768a157565b7369c6151113804459f7495c46648bcebab6d8e187149d98a2e3360a0ecbfc0b607ac48160418bbcc1864f2845f26712844c931945e73fd3c819ddb145163bc851ce598770d4fc1f1f298d2f32d76709df7142bf3f29c5697e7c421b0baa043de67baa9e14043062b53ac425091b395a975447678fd136746f08f592427478487c1e99e52e9c75c2d362938bb478333b6a3a60d518f8f15222cc17690396145d0d6f8cff859aeb94371b12cbc0dbafe8e7be138020143c1444d0211f3aea1f861ef1922d8a15380ba9b46e141255c15e9e4aa08238d9039812855e3ac7bc5e9bb84dbd3016c94430de1a0d53ca5ce8c135db3fffe2a53e44e1b0f29b629bb4f52fea7c0ad3dcb59a72b569a43bfa804e05d07c20b7813f1902fd2b07983cf9a13fa8655fcfd937344779f21d62651b891d95ec9520a409d2eac6d3d22466c8be96b65512de6fa4f6bc0b50b6e6c48394bfbc4362d3334812f328ab4fb6b372949f6a89a23f8b2cf6a1e5447f9e9a8854f462853215981056e5ac11ad9384ee58cc6e3b0c79b72c800288a01ec4a0172092cba8cdf5df0787ebbb0830296ceb513f491f2cee7e01c5744800d66a7436f01106966c5d9ae21142efa1c065c260d18fafb302f3d46fd7a010c6c546318021e1ea4167b89c43689bb7ecd0e6f6cab5fc5fdc489233b6ca24a210eca212d9245d1edb0b80df7ea805d6564a3ba0271283dc611e72cabadc61848cbbedacfc66a08d84716c35080f97988d217223aca933289717ac1cb605a80f7e692225504f8951b3bb4bd4cb2a7f62459bf2b7c976f4044fcce3a76ac1f7d4bdc5bb0b6c5c2976fc84b5e1047cc41ba48093ccd7b6b23b43ab8812d90dad273891877f558444037165d04f67b00027340f337e0ffd68836ef72be029b6b089f8ac86f46f9403b40cc267b7edc5e2ddb7815bb72bc220bb09cf91bd00828a0c3cb79d11b50e9dfed861d15e38b6341957f32f5f19222a18435d9d02bd3eea26b622a74bea261164a8130d977000d9f7b855c4ac05f6d50d43b79336d5bf7ebccbc5fc42f9d5049035f92e095fef308da00045dd39c8ff63005e488b29f6f7683210412896e631445f1d7fced33075cd402215a67d31767fb5c0eb2edf1df30dfdf6b73c85b3ce93cab4ca9be8c67d9214073e821d9ba578f059ea87c4f921e4fb7e69110ecad840ea011e9bef87ad8de6eea178256ba846b0916e1eb12b724e04014598780899cad7fadd7505c3e55d10919c0e0613af22f526e42ffe9b8d149ed9b1b003587d2f46038100cb3f93b4f2eb618df9f49af6e4f322183db85644a1bd13f333c94d7b4a05745cbdd8ca45a613c6f6708a0d7d0ed603b03f98e95f33a923d2909804c77dbce2aad0e329603fb034ae7ecca9e006104d4650b3cd5f81886f219344af5d823712042a726a88b301d5784d40c67f20f8b1b2a8e4c93dfd5496581505204cb7fabc772f393bb4c25117b5f322247d585cad46044c9c3069904cf25f91af792840dc898a0e8c83df7039bb17c433d929a5919a80b974c4b1823d9140094c82cdc17a800ab6c1c85127f9b7b9accab494194f216763c21c1e1297dacf22fc8f2f8154a9acbb2f662d032964aad0c98de9320da00a936efcba3c934657f2903ba410718864d78d13fc9014307a74d05cdd6d77ca055831f94905073b51dcf0b571360728311f78ea3cdd3ac35dcbe453e28906f9dea726dc6181ef81eb513c993ac531138935a3c10856c9418accfb0314997413689337e22407cd0a4a188778d25a5b557e141cfd5cbd13f5d9dd114be4df070bb01dd30377ef16675defaa5b2634d0f9f97987d9a33c5167e3cf8226c9c2f57baa730d3ea8b3be75ff9c7f51929643a770c87150365b7a15bdad94f72a526ead36c52e26fff75dbb967b9eaa1161746e80c62192cec76a7d5538b65f15af7580513134284d11eaebc2c83f319e5475980b605d152f8b5c0a34be43317ef664f4f00841135a93f1336bc9240fe752d4564ab1d83bdb21aa396d925df00975963088900745b080a8f6ac7217839cc4c8bd65e9e862160a12dc36bed1a7e0191399839c92099c85cc943a8474c0e4f0bf8ea04a10b36bb9e65460cd9306dd48258cd62805136a6dd0b5cbb9def29852689e3b7aaccb20cc32d14225f67be7ec94a1b739ea941455cf04b96931dd1d410387b557f9949b18da4ca7531d311c75b5551b744f20181513e250034d67b528ef99023d58a2431a8e0ac195c3b38c6731b279eab0bda61e37c29974c14ef036c25629c674eededc587df5bbd40529f3ef9b98b7f889ff056904943b07b805479bb1c6714dc8fa83c87a16091107cc1f4a3cb187f15c074e071d96b3f5fac79af477aa3b8b1a8691cfce9784a0bd73eb8bbf9d8b881e44fcf9e8a86f1167a97bc54d2a7f7b447398e33be97fd2ece3f8ced7c835f2aa344dd2a74f6ec88b2b6e177875c74562ab6567182bfa4abeac052b6afd7ab7bdd2fc8d364a87afb6efefd2d7eb26227de664942bb6b6f0191c4df8ed93d3e5d5c1932b08f6234e3a62e1ed49ff07ab498e3417b7920e17eaddf159a7417e6ff63cb6f39eff3d4ff5ca89a21120611c5677c2c0efa2b78ed6b35a444797dcaae8539c45a2c83c843dcfc35f833f10091418bced14e3eb7fe4c7d2baec7ade52b2ea1ed93f351bfe02d4389f51ffede91f5c33f66eb2693d0e98bb59581da7cbe951747105682d232e78f46f578303436c2c6c078afcab31957af13d60c4c31155cc97894273366230e0bedf8963a423d48313e250b80270d828ffa5cc9b50a708a4be5bc2f0318b80d5fb5de2d8425d5de2dd5b90eb5127a8c2c9956388391387954b1047aa056b6c94b13bffced9075ab059ee96ab4614bdf341e80abd4efc7d122f28787ca53ccec0011deaaa704ccd2c4215347cc34da525b4f937188dbdd137df599754d246648e0bc791d0cb215ed9e88f70246779b7db2985270a09812a0c40d4fb92522b8b2155ef3f5fd4678bbd58767fb7d7a5a7b39bbbc7be735d04f61887bdda5492d3a9d645ad176b47cea41a26b120bf9e2c9f79c5d0faa7a164d383852d3fb7ae5171d8eb4f77898f1c82d1a244a3e2aedf7932d4588f267d9b10b85fa06dcc4473a8ddf1614b88445a2aca1cf7e9e9706bd5aeb9b000a0434fc835404421a97a938dd017a82c1826410b590f457b284bc1fe4de550c692b20a31223dcaf1447f70528630b14a19b85d4fb48458f75ca34b478435fb83e13edd79c1eec1de17313c867bf4e00b61b56f633540552c6f0b9ed45230eeccc4921719c470768c45e48c209bc6fb6bd4c38afad91fdbe308fdb24a5df4b28d739902a4a7094286fc968438657e99b1d7b621f0c7affddaf1c1938f18db5ab59907114e60c06a24b861be315bd0d15f5292d0d9ef43215a895f00bdb1caef75b614839221d5445966ac0d030d9a4233b4156d8f0e83aa7860ac7967e167b21bbd7586e4965ac9e6a4e650b0d9aedb944d9862afffc270b5c6792c84761ff155047291402ebfb7d26b906b65c371e07a4a065cca6f8bb7f1d4183244c0c606bdc9ce633cded1cceb6347b3670433a942f2c18a877c536f775072677e25ba9cb2dfe0642d7cf8a9db7d628a69e15d4c15a28e034c8a3f734046b3d09c57e364b0ec4b2ad7bc36dda91f0ae51b0545a1a8efc67d5c13936358bfa6971c14239db16d21aef2944785371bf53c47669fa39a30ab98865b0b007c6eb1095dbedc267a820ba9e56cc3b9ef24f7a440ea970e0899b57ea5258c43b2b4dc02ae620ff65f0c48cf0c044bd330fe86e80089976599c3d91aca0a6a08f5f33df86c8e10abd7ecc1850b3e7fb7946f2dc763d6fb362db58a970cb7dd82e9aa6b81f0fc2f873a204bd35b6d1fb8bd43cb79cce8c6590e1c19e14154b2becb426cb685a6b0b98ef9e56167374979120fc4493a4275863c52bc41033175cf4e94aaa6944722114712cf0f6a571e31cd8144866aa2602879659a307b675f0e5793877b32b6d6db7a2db3058b1471cd270fd99ac88cfc09996f18dda4f9bd73d08cc72d39d8e54bbef4509a2f758cff232611f0474e336a6c42ef9779de2adf6c8361048a39cff98ac3eb37ffddfff2e68d8f1387678d839be716c4af8abc66012702f504edd87b227dfcd4dad71c1edc236beb3308d4a237c990697e194a0aed0f8ec71c140e90540bada5e1c19629d76126b2c858ab6370914598e68c71b026865df9d6043d2c591a2fafa927212b69666cf539a47b3906747d9feac5fc6e91621454b08c0fdb43ed0c0899268e16597986216fc83c79e1b266cad42b32d324a0fbfcd501ec044b6fd181f3e7142969996d0b7f93e6149955e0b9309fbc3f0a4d70a1ae6389c6688453b4c4f2a85e85bd0130ffc169b8583d694d565e58330d125b22d0d961c8638919991084f6d9e96fad1cd844977596160083fe7814ea6d4456389982b24a0ce6a39e5df2c53d1f7162c93d8335bad25b36898ead329848e9b6c5325d4a8583bb3ff00495d4f138f7733816b208edbcabf7a5543fa1764a27995129494895d02de29a10f12db1a7c950de8d31c2a86149c1e0396430a1658ca220a6755333f9178b21af295fcc8f5474e081d83e47975d77374b325f68d8e276672783ef168238e7683c8222e1aa601108ac7edf415980faa1e9f9820dba28d4403e2d78767564a3231241206ca6d12496f30314253b19a1a477e314a16dc8c0223b60ed823606d2a0638b19178a8ae690bdae60c403c76e46d8c97b1e010a485c9f062f0cc4b88deaeacde714e84306296eb837140a8785f9dc6acbb879de001f2517801d7e0ece131774fbb692932c2c92be824928adf23c24a68e7240bad554aa12a44fd5d4efed2d96b4766b4902bb57ec0afe0fab1a2ee770b191efe87395fee284ed376ca7aab68ab2370a6b52e49e10614dea151465a99fd761f89f31392da2d55ee5675918b0c1b0493a83970e6238965149d3bf6d3c16bc093863db6e293f31678f18073938584531608ed6e5a1b11c3b00228af7def4593494e9b00bdb3390c955554a6874267852b4b3d5eb5547fea55f2f77f868bae4ff4b505913147da4f4aa125540f128f9cc9b2e557583e0b1511cd3d8d1a96ad4b6c5d90629780f618e6503356159ea1121702eacddf6094079bef8e1c792cf95fa2822cc46909bc4e6c2c70d4c0b1075e4d9aeaca18d2422c1bc5033c84a2f2911dd956b7298c26f1f17f41b0ec0ffdfa1b8dbde474d647b59ea60903f00c33b44a6d190f388bea577c344c753b47766327b9f7bcdb80bbdfd5b309ec5ae66917998478a3c116f60c389e00b380f9b6a80ba3ab5982be8ccef39e30c396517050890d877c72f816e541d00a7d7c070bdda59408ffd23875ec2a0ab036e8bb7090b7c7e08e83d32924d33d4ead57010e4024df5fe4b02961ca44117c246cb442071c95f664ce7692e4be280d625adc9d3ff73b846a6bd0c8f73ae0a417ee7204433b5f92bda4c93902e46d6349945a86ce54c2b3b96f3ece4d505d971c0d73bae610f82c6f3d7d61a3fb468b8386299edf0f5c7ff3bd3766bcbbc8ab09195a66ec34c41c251ebe3460aa3c1eda404d33d5e910ca065b363b8e5de961ba3ba00d74dd0d5943f23b1c55d2a45e657e9c63833c2ac3e5e80591770503957c9e2ecf8dea6bd3d2c56515a273ec8994cfea9b2a9cc8c27b764d3d230c9af37f03d5ab0a57fec6cf4913418d66a8a92629d34183be83f700749106a22c193416030072c9cd52656c8a15dce2bd12ad638d1d9148861aa9df8aa1b57996af1bd945e52379a333d1702b3ee9bd5e73e993ea99ca998aef496b4b1d2297d329490ad0773e89f5ab38c9f31ebd7d136ddc87f4183374e182d6dc633f9f28de847ba364213c905f323dc221ee8f060e99fb33be1d767afdaf25b084b012850a0c6117e2ad7dc803806a5d2a983bbd3b2a23c0199cca25af2b66dd9f4780f617aed3e8fd8f1b9d73ac8f59415db5e97f14c204f710ca44e42139bd39eb3971e13e9de3f48772220b2f1cc517abe155643f8ddce3bf437dbd5184655cd8f737047e88301ce1587d069dd7f0521e476f29a9856def84c282c3f87103e0a51b9dc997cda9e24890a496869e1c444626470ddef2dadc6a5a2bada4ad2b7d2cfaa7d539c6c738ccf82df2b1627c28e9d729e49ab071459e1fdc2cff09cf47bb8a77c2d7375ba2ca98aac738f18a55b372b122eb19dc42d29aa356fe18e0ece19ad052083ead5f1a17e996a40ecad1a68e683fc9b419b27d8c00439a1448d8482e1029e6302b525070b7b67b4e7aaf3cc6b9ce703c04671c52c98689b35ffeba2aefbb6d6279cd0b6dcc88b9180d2a8e0a663e451ce733850a38dbb278ba4fe9482b2508d9eb164fd37ef25964e9ea7fbc8446b6c4346d51eb234b08cc5fb4e57d49f737b9b6119e051c1b0ba3d6c19c40ce64b68ac04fc9f8a97ef972ebaa0ccf017399af2e0815d518b8ecb4d19ed7db30e99dd62064b136f2d96e66edfc55fa20f1cb3cb70a12868ee1e5c17722654c15e74da7641728005b2a138e9f4b930668ab5163d05081f18290e766d24a4e34609d8b235a4d420dd1b54bb58d90e3b8f4682250b8f4f66c1ddd8dad6cd992f6bf1854a8bfc3a72207c95dcdaccbd2a971d28ff0cf690387235f7203a1ed4febc7e8a69e4e73b58bb264b7c010139df84c80f6c8b63dd277d0ecbcfd8ff93d66d66583d29f778b81aed1c11092868a1bc72e4634889b65040830362cd2773fd2efc38bc9457c8c75bbd110a079130f38d0057fba390e750dbdcaa18ab8fcc679664cfb0345eeda3b2f90f2655e8592b95426e1a91c2a00b1e91e1478d618c3a75343489e00789b311aafd4989291a0f95b05b6c642e42eceb6be82e3df95e1b1725db9c3dc5d4206686cf390f1875bcca454b5118277196222cdf04c5d8cb1c8a23f35844aa329ac10753d02dda2ec5f2b0b6283c4c91125f2b64c9ccc000ed29f756128551caf52368aeca6465afa203550640fad21c79714a3d0704a13e2bec7a89efb63c57664343a3192d43caa2f04e49ee2022e85bce5bd810358fbf90487e8d4011227c199a9c483066e4fb317dd8af46c6187e55b70762e63bdae226802d20f3ba3f6055e197465aefa6537acd2c0dc78c0aebbdb2d4e8b34ff00070e12ffca9748dfc6764482b1f0937080e4111de8b9a400e03590c3ba718e1fb7568d627b16bb52603751f80af05a79024ffeadcb24879fa013d7210ec6b5985e63ece346c5e7ce9713fc40bba189d7cff44e5e228a01952785593b31469b3269f483e48a30f1d0ef2ca75aa2fc2b4ccf56fbc90df8ea4cde65494e49aa01748521a6763926ac71b1ec8c26f66b06fbda6e8b43e43bb8368588bc61ed09c5efa72b309ba37076c0a0a4e1b04ae2fa4c2140eebbf19953639284b67cfe6ee3e0ef1c82f154431071650620dfef846d2bad030e6624b8392fb2e55cb192c43d2f0e373f7c694d1416a3350ad63f58858e5935823efb44ec484c5db24c452080a90cb24c7bc9ec20851b4c1ab90edee04e973809be5f748cef2f0758cf95ba89a71ba13d27f13f2f1db1885a56447fb79e260c56b8131905e9cc36ca676df6b4f7ed31b0f3f7413da720b83161994d5137459b0a6d6f2193ef3717cd14bd349f8c9c873fd594e7c03543139071053086089904502e0e9a41eb1f7f5daea68212ea0d6e9e42e04c0542ec969c7631ba49f3e443aa5c7ea96c40c60bc2b175abbed4e0ceca35945210f22d773b15009d9f5a616dd92946eda518b4ea9094c22157bbe71d4b0371c88e8e9dc0a79fcd14e4ca484f1eb7521c0aee2053d83f0b47864252ee1e983dc92bf798490fb7f2149d5733ce3db3fea3c905ceda983833cc07f319e20eadc69351407965adeb381ba45d80d414d21fdef32d062233d4b1cea905ce29676e99cc79dcc002e70d380e60f388d51acad0c5f778107c23e500d47371c9ca5e596ecd715ec4baf0d630a23a4153af80891a4e3b4cea2342969a125457135710796961fbec09a93075171ca2a98ce2391f02d5c4bbecb530930aa5671ff732d6d330d42eb82dba70d7bd5799b888026d53c5ab0a5d63c97ebe2ee2ac6a7bf3d85c8bb84d481306364d7401f76ed5e040500b462780616bfeb441ede9055bab0e410d2f676e68d346a4e5297b8d3ed0d239ab155728d5e67fe5d722154bc1cdc60f8597d7799987c0b1021a4c82c9f826b7a7e0586079f36d3c184be6796956aeeeafc66a0e99fedf0b6fb5de52c42de6fdc1e6d5c013a43ed71be613ff6fcfbb2164dd16d0c0765cfa9319d7fd0382792d3eaa2f2fe778e949d62a8a1c9aecdfdee82baaffa125b5522a0ed833d06b20aaed71f446628125747db601d03c7314140f8aa2005e64314f522ac6d7c82e36f2a6265467827e2935ed017e610229c13b575f42b2866658a6c36374b975135a37e9fbe7db4404ebf810ef7066f7c62ac1cd8c8ff3c15805370bf3510f2fbc87b34b4c557b11db1804422f332fccbecf0c39fe8571a2be6f35692121d960c8d8bd8b76e2b2430c9990b4f818fdd60b00337ca18d4138dabc61a40d575ef55510215882ab47bcaa44d5a993a16da446173d044a06e9511c83c2dcb63fe08db0127759b4cdb011cf1cc5d68f902cad88424948ab067009ac9bc3ebb62151e3b9a5ef92c99243f1d795d8407f7de18354da2b8c9b7349cda0487e7118927ff53b2dcc54a776222d502092cf93c12ca50910cae16bc8d57bd42b489d87865961a7f769bd325c4a068c36be9505e65500e453d827ec9b6effe16901479d78b74a816df89abc9ca93ce477c3af117fb5975bb11deaf0fec06cef16b86b68ca3f13f7a60b1e4929178d35f5e645def10791c560c8f064e3b8c3bc01ca74bd809ea5b74dc685b42cd0bc8b3994778cd912511cd0850069afa8d23162dc8609c82743ffcec06969408df0452437deb35614db518556999ffdaa311586cf7173c55ab77db58570168c273b829072819ba2c2ae355484aa6fbcdf5a553bcaaf183d194fd8c6ce51df58e0e7746272561ab3478b01145c5dd6a3746551e69a5cb75361dbd184a6fe3b65f92bbba6988cacf130407e37c99c6d57fc276eff14c40b3045862e63e63b5b9fe81f583f58d03d02ea1ba19c849d429aa79787a56ce6e7a86322535aff634a644d359889f0475f8e6b79800471304cbab32f0f3ebb733b325f5edb8da95df78ef6ceed92377486a88ec81b0c37257ca3cd9fc769f23088c3ba4cd3605f77ba308e97f7fa6766dd4603c7738c356e1d7b36f56d41f3c25a083e72dfd969a852fc190891a74214349fb69562ffb68b36aa231915512658c3ac9f2a2c1377712b50cbcbfe52403268c1f401a565a0641c7bb1ced2b6fdc09e4cc722ad83a687eb995598f8af59720d3fa01bc85b4833563f096a0caa683741f4cbfde885dd89fb7b20aa2d7024987bd188e5416825301d86123bc0ef39135c7a3f0e28dad8de98f4e02f0240554d25b5ca6bd76bb6225fa5a08e49244dc9988be5d1965fbef9e94495a6747298d7f33b9b8a84e2a4c9ef6af4b802acac18526987619e09ce86e900b64e83d96ddc3ff6cd375ffdc58c9f346cfe26d2f7b48563c37796df6593b274ffa3dc35a4dba545f20ecd6b2e4874bc55bcda23ef12f39c2d1e7ae8015193df44460618157465b60f6c1c2c0fa5046920504bdc10ed654508c4bee9e00bbd325f6d5deed02c32a53f3296e5654372cee007721f13b916e43657b06bfa66ea1eec0e2afdbe0707a064bb731f5e5802ce3900d8c0603d1eb5ca11bebcafa0bdfd3ce04ddae776707f916b27e8224557f806e17745ae9df26b413d83013ca511e96027fec11412de148d4b673ceb0c5df0d8812c13890d2e3cb10ebd460f9c620e1a354d58a36fa9dd6218ed939b02cb23cc04b087a7d4aa6d601d36f2e6e1a03e5f497c969e1744af136b51761eb3423f91c20b35b9f02bfe6d0fc2af37f68aa7a494d86118295b40236dddfa892dae337cdd2bf1cb1e085deee0d256d723d979fc74fbb7bfd8eb2a2eb4364ce95c63f5b742fb20ff7992cef6f2a22defb1f747959718a0269faea870ab64408df838c4c3edc618e18af691445f64fc382f84bee2f8b46b6285669de8a5178fcbeae44cde5affd52197f54c8ccfcc8468a53f1d66024a870b6c71664584f600e83029839cb8284bb774019a173665ed29f11cebaa0b387c074d6803c6e52bf083a5d2a0715f7f2d370baff7b35de4f753582daf8c247962b1c85ffad6c891ad7d2d0e22daea491500a4cd5a0cb525ac656598dc4c18f8e27eb90f719f8c3f6e7844d2a477767f7b869ff1045f0e6e938810d02a1bc4103d7d8cc204e075e65c47881b10398a570b50961b4cfdac2769c6ac3d388ed4cfacfe9587e5c4e209549684760b9f7d78afd8fcf98ec27b63885ffd7bd74a329919a57a104109562df538d05bdc067cb4ba46ea178ecef47babf30677fbf34d273537726702d37a0f97fae6d17443908af94a4ea457002e89d9d1888a254bba1eb5b364f4c80da30050484b15bb7935a06056143f32895225ac77119d25b735c1cf6d71a71f101a0e0856443170c834b344c18ddf5323e305d9db55f7ec5939c711334dce1d64f44787aac03a04cdbb0156729b40b64ee7e3e570a709b175cc5b8a7078bdf6257a4d434fc332df57ae191e7239899fd77cd598ec0fdf31460d4b49885a68c97a1933aede95cca5614d5e34c4cbd26c372d82f883ae3cea8a31789941f696edfbd6c7525db9858e0f82de4d019ab06c2c7d89a52db13867b6723966029b6dccc12ecdba90843093845bfd5b4a5d859788fd1d7ce6e28cbe9a5b40a8c3d508cccf6361512f47878637af8a7810fe61f306dbcfbf6bb88ea8d7f16d2ed48133c8600660207f4525fa236fd107983127afc36dc24bec5649912fe65fec13941415b07ec3ad859b05a695e7b2791caef5e49d0f508bcfe4783cc251d8e1af023c236c10456058439025a342962b3ea7e763610cdfc6b3a7c96a2b945cf1ff2cd2c835485bff97672439f2a9fcedfced9f106c32b09b59bc60703f6470087f62dac55e77ef457b53cf326127491e571f026c2f8cde2d3604c52e2a45832eb7647a5e2039d0d1f509834ec6e9d60e26ec5f37a2f7053fa29f1de51e47d130e5169de4fd461cfb4b76b8f8595c36b7bfaa07a0896a5b6f466a79f6c5b4974750c7762a8c2213ec2176ecf59ab0cc2effa63b113caf753d405f610b415435aaa3da48d75b2da2bc2d1dc3dc3c7e9a63e3cd6375da722ac5a73c4dbb05ae8fb602e85c67ae8d393b3ef70b5f8eda7eb93ae4aa0f824d540de5e91a2fbc4d25bd07d721e5e629084f71a04f1d67416b7d7185c53fe39dd436ceef9d01241b4cca952c3cb46bc2e88744d153f34c0b591fece10640a87211a2785892114c0f1daa26e722b7ee39cdcfc1c147f76efa76a07a855f3c3f0955ff9cd65fce8e24e9c973db5887632ae7afec037a50e17b02a799f2bff9685c3f5ffeee702e99c4dfc984bb58f51d1658103acee5873bf82cc3bd516e2e85dfd7d3d755988223f79699b9eb3125c3fa95ff5fed1500ed2b8b0cad571dde47fd5feac1b9f776d5c9414ed8d26ec04b2651e1e1f0b10e48119f9d59ce38dac16b6b3a80fed6fee2cc7e474400abc0e5875da4d4c40277101ff9cb3d6564ca8f6f6b5d4dbc93eb17f586283a6c2c33f036bbeae61e8008c62e7fadd92d151a50697c1f23c697d7a8ef38a1809ce5a69f39ba69f017272e7d34e2ad7b7620fa1baaa9e764079928bfcecd5c4af617d4ce317d562ea232a23b1b19d479d083cd57e87f55053c4120317557b4ccda8e3b19f815b119226dfd7865bd680644aaf2cf33a47da526a0e81c78c20910fcb669efe452401c53b57356afd1229afccbb99c9331148439c93c643d72b861838efc8adf50006e88cc8be415b64739d55786d83f641c97b685426640deb180c454e1372228c6fc6167e80e01777b975a22e3c48356b74ccbb399994a6f8e4fc32bd91d26d8f0e78c91d4c853c6b6ff35c18ed93a3b40b71789d0b045299b29f37828daf43d3d1ae705526f0c3d3b42083e847471f66de3cb018c3f50489aed65ea2dda7f8e2511eb6aa12388fe80b68d897db7277644888d8f7e84e39ff78e03bed918b727ad35269335e68555d822eab7c7b38639a4139645f8205fe88349326c8faf2e7737b74c5d60de2aebffbccbcbacbb58df7a5a74cdcbb5ef90aaf1eaafd862cc67f42dab73855011c043c78873f3af0216ce1b2b8032b5c23b0a43e3160f9f0e6e3e8cdc07457c6fa3628dd72b5a150cf3fead105bbe9b841bd72c9d801227a29e7fcb7288f12289c730bc2a1ee4a6878e56c9f4bdf2516239026b88d4a5ab0817411aed08b12dfadbc76e53ddbea7b615b9d87a4cfba4f9ce62f594aff1a30721dc374c2299f390fd4e3e9108bdc4281f85f068b8a832fa9bd64e712544b2315b42d81c3e335f8e5759ca5865a1b45ea466a2a7efa82d7f4d421960fa6c53b11215839e20d65ac47468cfb7fb1f333b25292d54f7842290e8ab22c8363d3212f0b0a165237294ab91c6f58bfc88300d1b0baecea2cf5babc3b6ef0f973a234ec80a7a2f5b559b7d91c99f82e45252e7a7b63f69cb0d278269bc523e16ef2d7dec10883f6a2220b1c849dc731d13abb920d59fa4bfdbb18d8a9abe71e2a31f3ee7175388785d1e44a69fee27a820e5e7785031a05333878183a4168480256406890125fed87ca77a1fab4ef6583b4cbecf1e7b2ff2b604e3860bb679a11e2ddec7f993abf2905e2ffd5ecd9b5711f0ee042f22240af2101b5e29e62985b1a2ac0c51c7cef44b62476410e3976b326db1fae13c4ab4b5f5fa6bc1e20d14d2c83943386dfc4040db1bec83fdf3e7ba7e1840ba74738355ef57a8aaa2712822247c9e3aff77bf59425458445e4a0a92d6992a7d0c22b97564f83ee6ca33931389186f759faa395b644086654bb9affa5301604f708e5f0af2441b269131eaa518572a2272d148d9ad32f56b5ace8dc96093c4def4d2a91c33998e0b18e09cdb1c2a228946655f69277224fbffe10881dc344261664ab005d0224308ad77b84c5e9e7ee7b71cc40cabc7a201086572bdbdb6d7a5b60fd90e4daf25778c7da75408336e93e00dd5f206a2e2de0c24284fc1b0a5ffebb36b766c8298ce231601677bd04476a86efe144fe6e580f904020ac9d228061f22136a093a343c3cd442a54428f0e345849345af104f6c0a83cb44f1bf76dda9ec758a4623a120612097c74aead060778dd39629b6c4d772116b48bbc5c7ed4986eec15465201aff25ec63332beaefbf9235e2162e9cccd5c9ad9a08e58c96d4075488d5e383086d58f1c2dc002ca94e09339fb901c3fd6cb747486cb37fe1f20041f9c6b2ee6cc746d28590a2bcd01fe04a3a801f081d6d00769255e584d7af5b1a043e4d700b7560d405557c3af67f1838427c01648847cb29d05c1693d5ea04f4ec3bffcd8fa938cd6de857a49193dea3a1050ecf5a0bb94213abce7934f31bf644900e9465cf865868edffa4ef92bdba5a16dfd48a997cc6c4b2b6c4d616b4d074c74e166dec166c9a4af99ed5692aa419485cd3d7953e18698e139ec7307b74d8b84c9f94d86fac73a5dcf407a2b6456f4a7a111001d3a48b06a881e15f171e6f7eaf519c813a9264a86dc2a7d7d45c702101366752bf26ac066cd33a337c65c76c93f4e1ad7fc9bb0f0ca43a83ed7cf35fe9ad932d7bbfd79340c3fd8d61f84b8ad4c2cb605086ed891c5fc3da0381183866a179fc955b0cf88f1460e620b492de69d53a151c8d44e35c3d65bdc527f1855f13b07d45bc49a48362a2d64512432c362492671ab74b91337fa6590b739483e3a3484a2c8eeffca75b0461628471c25da07ccd4db3909b85e5a20df09d85ac01fb545625dc9cbdf78806796b5a38e21b812851dbbecd92e3b3594c4bd8f39e59085cc642e6b5a1b2356ae15f4a00ff9cba9e7c3ec742e760e0e4520d9c3c014b677cf5aac74c1c9500b94b02a98a106d0e8357ecc2f2ab363ae0d8c270c9a543bb257f33469b02384bf1a4843890ab329943bb10c43aae35e5d358f97d5a2a8f2ab8c36b86dba276f9fa4e607b04afdcc308850fff9c3eb46bde66026c08c89dedc7b6d96e91bf5fb3eb550182b39d5c89ac7554bb8fda0037d29f89f9392a2fb35c35b50efac2064f9f540fa8b78307c3d7db0a0d292c602f23f873220bbc5a34c2d3b6dad603b50b0934c05a6db27e46c0f85ce7493d98bb7ac56b24c54e94675d5517a5c73616e178eb40d5833d1f021fa8baa4e86160f1bdc8ce0d0ca102dc2e36008b27493dc0436c97d6e2976212f229713a2d33ccbd13921ce0decc6adc61bd398c989b4b37208cca4d57c9e524bb6976d7042811595439f4d58a85d98a36dad3f4112aa44b13601d13e0e97de50d4922c7d6f3508147c93e620cccd4225c750a539c3e482707112e5f8a79a89980a3ac6f993f7926f7ea959970a7c94d8a3b11e52658019ca09c0e7d8a0aadc2f322cff6707a65bae011430323053e7d3807a01f0cf0a1dfe6b2641a0d3e2e63111e29805f95cc3475ef3115dde93d3b0933b2de91a73d8df686730cebc7942549476e81a42bbad9c89a9e21a23c975f09d64bbf2f92fa5db19f2945f723c5e6d825ddbbd6ad9b96dfd7d510f3092755dfc7b759d08f8c37aeb1ab8bd445af960d8296961397e063418a41c36275670e65e10332b7a43088218bd836d5d3b702575a052f31d340034f64a2941f261e537a311c2c56ef4fd539915c0068d7f1ad0145acb88af7daf1c6ff510b76a405644c84babd7931bf1c09855705f2985beab880d6a30bc9e29317e3321726d4e10220cc70747369c4cf71a2fb6fe4c4c8efae276959a4ab33cb909a1ab307e6639cc65641a55badf6eb734199213c440289a37a1d936d0ec9c870e7514c6eb1cc3130ac659382816f6dbfdcda1bacb3b0799a61feae3be4ee21dded12048e4d5766a3ad9237d2aa62cc636a761a28868d1be495206466d1a99266bf9ba226ece9e8484b8c5676f1e1cc2986284aebb60c94b19c531931e2d0c23f09a471755ea49d3d53c4e3e4125db8cfdd7a8a09ee1ef9a7b61db2fe122cdcdfa9b750dedc7c2f329b574ff42d0934dd58db76f6796fe59b7312545fad99b929f4e29f4adf3f5c68827b446a87e28398789b9c98125baecbfbfac1b74a0103bae63d9ada8bd72c8291c22432db0935301e7cc2cecde1ca47b27e80f8677238210fc86a624c34042b68e1589c71df730c42a3bdfe2ac27d7ea7d15ab1249d07c6829bd2a9bfe710f3df6a8621675d990677b2f3381fe1bb132cf352a36b557b608bbb0bc13adaa04eb7c196393fc09f5339afbdc647e8b6de66672fcf9a9983191de3ba96133dc3bc2cdfe9c73e3e8c8cf133f7f46e7c3f2adb80197dc272ec44f95176580b6eabf159b6cf45776244f3e96c8c64825a6eadbd29e3851ee3ae106dfb0ea304b973cd4a136ccbd70dec7a5a6234fba70b1ed1b5a676b78fa5e187d8ffdcdbf9ef083fbf5f0d1e07028fc955efee2d7d1fb0f94716cada0a610c98e68ccf903a4ef5ac467173e8e0771d39e1a2267898171d47365f2c2000e6d93930dcade2c56d3cbfc620cdc2fa9fd29b960c2aca0295fb176b1618cc8352a9b6f160f80d29dc29a3c1a5138d39c97c36c9f0b59a1a5769ca450cbc24067dd65000898b6ff3dc41b19bb9a17548f2c9cfe2b30e4c8b2a1eda017e22f884e5697aa8d89640f0ce10dabb4132c001baf860f070dd2581e1b286c89fa8eb84563483ce08372c6cf1310218027c28e2272e927f46be9c444dbf78e27846f97bece201152380c2bf774aaf6d50f68721f1044f3e7837d864233c4db90b40163171986b574b2b258070b6e8b1284763613d792c257a12f5ccba391b6e26d06e7c5ca7009ec4837cc0f98192a9875cde1ac1d229e59a765260e8fa4807629311d01c4fb0316ba857fe05f70ebf8e0f260177d9168c8c03ba732f4214178f0f8df92a3f0fa73962e6ed2525dcb49768f51dbb442768824163d6fc7ffb059a75f8b8ca49eaab4a2aa3e03bf66f4ffd91307d75c6f1b09b53b29f2f2773c4641e4323296414a83d773913d15f593cc07c0c1bc4abe486aec3a48c1de23164f2e229ecc48a24cf20581ec64dccef38491511f79c852ac2d5457bf003e89662a48cb5dd23871aefe21f155fc9ed8bf6fffb6c9e613f7289c68b3dca1b0ba294ff6b0b1aebfbb05fdd8a65424cbcab8e11736776ca3b3aabd27044f949b970509b732cdfd4c9419e22f18c266172026148d51f6cba006608e035fa1b5cd74ad4666e463c87ffd0d58c173d02c2c9da43bd751415925df6cc2ba6f68d1894cd38e44d079e7f596d3a42d135b5f700188dbefb8e25dd83441e75d385006d239978968ee3c0d183bf9a60efee98fb78d1a50347d084674944f78781216ce77ea4f52a0cf0aa6c264507f34cfc54517b4e07a13197e3c6438a96d76571c93f0e040591e4d1ffed0f1c2807d2138269246da0cfde522f38edf7b370e03fe1885db5d3539aeb0444a6176f4f24f9e4c482aae8fc4546870b12f1b9e418abb48c41e71528a4474f881496cfa32d968522ac9acfbf35d9104338a2b2ac0387d6f44677dcd5d3453f318d89ae0abf4c88a724eb73a803aad84b3dbb4a355ddd1eca51e3968df81ac779318bfa61585f3c38c30c026a08e033c06955f1e4b743b38466b6b7200f8c9252de85ca491888fb5d8195e7d11d8596e4b583a4e63b576443f446b9f46d8f5bb4949c756164730496240a54f85e416a5552c9a93f38b5929706e2903f52604077f9623bc7927081cb9722a66b81389462b8acd7781e10a9880b41a3882060036acc5a371ee26f7e29f71857d73a257ae7b90c09ededd1044da2dbb2576052ab6a51dc44f00a6982a7b38a061c59ffd588498bdcba05853faa5d50cf4d03960c9344be7fdf68b44b56f65328e90972c10f748a35251b4812bc1b904d68e9d055a6d913860a1e2ab4a0e87cdf0bb89c67893cb8203784e2771183a3da68a319a39e15347b13af0b8d0392dbce9dcb6903a4f25b359131cd9e956c935b0494966438932bbbcaed34b374730e0050b5ca3f1dd82e8db25ae72db57d744c1741f18b7377e17c6e9186b6c71a9d46313fcfaa0026b0fb62b601f708e860742e567df188f44c1597ad8ea2fb0060346cebbcd0387c1f17b4711abb03f3ae1afaf0f59df867a4fb5767efd9f8cfa9259dd29f5bbc90cd7873cc076ec1f6c3540bfeb9d723929c4aeefc02f265d40f2ab9c35692d6cb3ad954cc7021cdc2cf875084e3dc548db3a0c774dbeb2315f3a9aa3221c33923fbad00b692bb984f67a437dcc4802d6e93a801442f5a13234f487b20bc40c5edb0d280878305bfb8370dbd5b170adfbc6392bc396106b3920bec0d3a63e2c2033c9fc41cd79282087d46019bd0bb17bee52c833d998db6b18100a551887b080b3ed9442ba30af4382a5406abd472328602b9f5bfdb65ddc37b6eb419bea9d9d3756f8d384820af18ffa02e32de964e0a5d3b1b75fd048da067390b5fdffed94e5a53099495c3d5d728d05cff30658ab80eb752a323b4b3d1f06677e858183d0a433c031451d9401a9afbb0bfa563ecf11cd2922513b52f79f0852a07daa5fe6941863a300160bbe07cfc5ea23d438fad2e4aef262ff687dfc376e850290e472ff55ee8107d4e8466d68c8654c9b9fa35e1fbf31e1865c5d1a5ed887d13b32076d49b83099286c420ab69043b9db3ed1810d97d372869ccb53d4e0458170aa6376c4126be983e4f3211e049619299c475e2852ac42daf960112153a46dab256f426463c17629a436362b00088a953ce04566d900a2a0cb62634d9bc22a05c84791fe1af05493bfe59243d8024ca91d7b0be5440343dca336a71fe205e02b3ca39b5efa0979f162addf292e995229142f627437e5882d92fb9d541538fdf9cf9f5ca790272e12f691da14ff1c3b5a63d44f8e3bc9c693e68c0cde53ebcb17b9e30fe44f4866f60e23632bdc692ee9cd6a8e4e0a00db836e89acc0e44aee9197261569cbb3766c6c619d376df36fec7a77f083e6343de3121c4276efed0c43340559604083e7e68d21b07f1349483918cb0e8c651e1711406e492e567cbc09e3b8ff7eb25de7f14cd2e20d1aa2ed42eccf34610a64d2fe3fe11b723a8e36e3a95036bbbd5b8ca76bf44764f657add728d678a598299e53ca7736ab1c1a39fca0b72c3f40fd74c6739f5caabfa60afa45f463d5ed7fe4b88fd92bb5a411de657efa3312b001f2edff252677969e19f3b19fc41eaf9951efff7144160968b7334f93241b6074ca902f16d780a143c294ecf0017b86d03a9bca96822c93b9dd085038196fb82e81bb4393c175cb2a559c92545c48d346b5974816a67c3017688439f9c1c6d1263b7481eb83ac581a88005559d73ccf6f68382cc7800ebfb65fb879b4b0334235195ad3e7c9c3c7f45bec6d044c54b3cd50e8512ca2bf71c10888baf14f4223d7455ee945b431e23b01ae22af99b58422731fe1cc4dbfe75a36da9b44c0cd1082e941580fcc7b6df20cea445ec9d5c8ff0f318f03c0506fd330d5db6cfbba23c73d296a4e49ed8b9226b631b7e5d8fed080cf5139a1f8842d25c35c5a5c23e34329fee4e64a12d79ad01697ae68596979b1016bd13d6cd40803c147a5224d2cbffda67902751a154ea2dc10ebc20df068d429472e697b60b67ac023d8ce6723f457c78e93ef87be2421df03fcb841f8d2ad5a5bb9e2ccf9d3032537fc9db16e1b27df119aa30713ea5962850213164d32c4175e934c56c5a00613488375c4e39ae1176606bf79e1733750fcd8aff65983642ac60d1271a9c4365af7d48b59bdc1e062eabf4904e181646d49eaca52d071d65c4bc436d00a7b94237404953b8b444933c3c5ee9b4d0e18de4813b507a58ae86a037aa94e81f4e02b6b5e01972ee68d6a711c85dca653a5799247a10471d5b0fb48dffd0f3caf5c90201262df96240f0848c02f5ce82dea42eacaf96882c58dcb4999c35753b733d6009ac80e9e49f00cbe633b7f4249e947bfd5618130e150d292b12e36c6b3b3fe8a0b8159cb5a2f160e3ea9ec271daf9d3ce3f82055384b3bfa081b78486cb6846601341db24867505cebc9a508214e07167bcf6877ea901102ff99b0a21f58c92aab856ee4e5bd579a34b026cdc24d09807177580afc0f5d5c210ed795707c6101d119c5e7ca334b4b3d7e7cf664464994f7d44e74ebd8d1d6e0166e75cf61df8a3bfeb605b059bccc8b0ca65a20a6cd62f9c9017e62a24bbc887753fab15f5198b1318c4318b57ff4f6ef39f8c622fd3491829350fd765e3268684d40510eaf26bf68b3e1da64e26e60dc1f402dac64f6e3517ef51b37073c321db4fb956c14adcd5f5620d565dd8e7ec916dd14ba60cb51ce861c55b7d31892a79a8e0c42ca9c32e0bb82a239ec55ed3113aaecf5172492f65d26c135b977a4f5c3bb396c4a2c33355918da88d80b852c39614db94a9a3b95d4d7b16c813911f5710c38fafb709966baa9ab6b2da64b8e83ca4dffb33ef12e818405686cbf32597c04e114191fea64c5b398c0901a17d0d540cdb23df83ce0d94cb8d2c1f3a24df6af2d18776114d16cd48a60bb674fafc53bab6ba2f75e1325f3148ac83c0c91d02d02bf81f86fc01ce554e7126966a937cc7597ce63f86ec46cecedc21f1e08d3f7992d834505bd0f1f9ef2c2e03b063feac80bb57da9b4650ba5eff7759df73f074a0a5f3b54630b080448845e4a90df898c4ff53d3c02b91e5dfa4d67acf9f589cfba25d377407b79b49cf7c787da3caa93f42cba758a78a42797f387394a118f87d9efccf2c19be11653275633fa849eba096c8886c845fe5f922a2001add80ac2d6bf306b55a8b15db4d0c1b1c39ff25ee7b8407aef21772e783e3bff3f20be2f7441c6e1bf1dc012cae1873f57389863dd240c335372605a187f05f0b51dd69678d452714e151eeb2dce75c330186ea3e0c9eaa87c632218457c4cd1cf2316e70717d81cf5df97421f6d3a41cff1916b89dc4f50668c665ff0e873254b417eef95a88066b2b0a1f1a2801890b5a78e117d3ae1450981454ae09c7c8b8fbfd1c8f92f839bbfa2f89d816ac57d12c97a677d8dbdacb261310099b5c319b00c49ca4d73afae45c12e30dc4d9dd39f0e5c5c7aecb3df1b9db88a07577d01ef7cd9ba172eb0bbe82cc97b1dfafc8d216984c7e24c3bd6ff2eaa7f3003b4abb3b689b43c81d423a1d80468ce64115487d9fb35098cfc4fc6547664a582bfe94a28a0b97c97a839b7f49f4fa3aba2e6adf8e38de90e46a7ae1f7a791cbdb318777264646f27cce7f8fe9c0c73bbef402bfc7234238dee234bb1d1119828359a5665cc715b511f9aad8d3810a925fff520615a6f687d68e20a7bf56cade375f389b700adc6ded373abc4a979fd23f54663213c91026245d1d814a9bd63888e3c78d0a44d00f6fcd0837f651e3cb3760ba021e151cf9825bb0608a637bc47663e06bf29bc3cdcfcd8b0f1ddef13f684cb859cd9af580b17b0c4485e82202d43e92cf8ad3116ac168562507a57c0d5789e525dc108160f8f20c43bae08fdae24594b8351f0705162d86ab3ae44d511eb11052db378c42b1df8c22ba92315d217cbfb95188d58bccdd13d62deeab55def673caae5271cbe55f020fb7993320054d2b4d27ac59334efd384de0c953f08be8d05d437a831aeaeadb1cfd02d49371000a8af56253f53877b1b353163a020c3f84a68218cbb6c23150b6bdd57da061720e3b70d26efa31884a5b627aec0fed84cbeae4ef97cde04cb5a207a96b6dd9c7fdc06e54ffaa9f737306dc943173f3d601c58fbf0f6d49152e4a6f346b67487bf6665c3104efa6ef4830ac353a9ae0bd9bc3f38e96cb60dccc99ef8efa64d43418736e5a54304b9f54eaef8c625cd8f5d46dca98fa81178cbd12eb52a2b74f4c9993bcaca369af876fdf36f64c717b0fb0364f27f3fefec4d9e44fa666209d5e21f24bb1bea1ff16d4d776fb35f9b731d8a5a981f0cd0a413ca41fb8f77479faa48d542889368251969951ba302a8883b511d3b5e37dbf8d713e579e4dc836781d3b9ef11f707b708e7839720fbb0b96d48b57fae034fdc875bc5f6682fee2cb87cda41c214b60ee573e9240071bd34b6817729c7ece5122ed824131b1c5b3f25a879c3aaed700addccbba69b3cc1c830462733d7e2f489d511766038c2a30b982c46676153b1d7d6e2470c8b7e90748f9fec97379f5b4e37f9bd3444f7e9565246f3ebf72e2cc45166ec2b4afa3b94fbe0edbae7e6cc19c5eb13112f864a28da29760e7831779b4af8c9f0e44d6dbec2368c3d1e3fb6a7cb31e3c3246fca7a6671080f355fa489f2b7a1e9c9e9e9ef17b909b3b7573e3c089bfcfe43395f5e35ceb1bd5e99a1c7226db1edb4ab89e450c49a29ce64707b2edf45123baa59ca9f9f95988bc289e3b8f549617321f055105a6d94f6faaf2164517b99c001dc9a1504aee55acfacfe4848951e81af6adb30bcf3d80aadaf1f17337df4aba7560f0787fcbf823c9bf9bb8b761f16df481d6a36e574c28a5786c7acc41ad994eb74584188f57290d01c1db457e4f49ffdf56acec90fb95737722fdc70cbf6e286b37c985f63e04bc1786f2793a929fe4ba9b7344cd1f47a61bfe4b8419884a42084c2bc6adac46ffe216ea2cc2b487ecec9c9caa2c4cb35b5dbc3b29882886452736b6536e9e973ef4d4bfce1ea3229f9442034d148be8b1d00099b505b74cec877a6d39082da9a35b96982d5d2d894388b42d4ecd0cc66bb03daddef08ecdfe4fe53eda98d70ae0a236c2038ff39e4bbef85a79fd1a75dc4aec1527484d31abc09829951d01876d9c777abce7dba14e04df89b3da386516e674b95bbedc8a48b6276476dcdce623da8fffa36dc95bb6d22b621f017d544404265c8a9d81912a6ace036db7d5ec43347005beb8097a434662f5f56aef1a6c717a9d98fd0d4ff958a8bb2ec7517b715901d4a9e22c66b150451682f3f1f52743eed71290ad60b41daccec84abfad4971c5a0a91b2d39ffdd199cc32f16161ba7978f447360257ed3fa6f01fb61836dcf9ddefebaebf003890d54173a07a2dd1d9d28d1a8fe2fbb1936e63687ed46a6cceb1610cc57006e48b58db5ca9d1e6df1259098702b692f7bc04b1e357f78920405a181f95f726e017849b17939a7ec6f494f288fd003ac2f430612e4fe0fa86cdf358228fc0c9cef4cd07759a54721c673c14fc220aa51a3ef9943422c88e7919580f75b6eb4dad912cda9781cc3f549caecb61c387d2cbacbc842c13a8a6a3cfa8f16c86cbecd3fe9a238992301a8c42024690b88ab85da58a1d5ac5861a7d7bfe786f3b8c190b202c3893c2a1f8d53606984964fa073d6731a241e7729d37bf301b063674fde91b5007dabe42e897041081184c4026feec0313873514d6a218d6df6c6f09356ac950f237a65bbf48c47d73a038f210a1f15ef1214f4accc5fdbc8bf1449c390697b12a9e3d9b99db6621a356ea273d833057ac2c58ae0ded3678c378eba5e4885b4266441fe9f82e3c119ad0118bb940082701f7dc057ebd9385a42cf58cda71d1266ddfae9a1d2e185274c7cc8c006ab160a9cc5d8a77e263bd8d98703daa52ec1080cf71251f70cd91efb74ed4c439721d8d7b0504ce0e221f2c98f6bd60e3d5a3f002f91c7588b5d20aa8d9f7f6fe25680c3563279d903ac8a27f69de517bd162a07ea64dc775940d1594c7fb642f8e258c62eb9ab176a8760c1511e67df24267084f1165c137aca9150b5adb093275d9a5978904f79eb638219568df090c4167dd67cabadc2a432625d5032648b2d7a225dd36a031612326cd45cad626255d2e6b66dcaa4204f8521b54ff25a91d29e5b64bf1400fe25f98fdf1476e1275ca024f811947886ba212108f63403151e4f5e6484c3230868390c45833821890c36432197a253c51918d26e4a3af8659abe29b807034a2fea248ee37db7dc064ba3295e4b626021fa79a16f124e16affa3be43d057ff8042da38d59258e50db158bc553707ddef8fd270a0d8fb431992203e768a174c81b58f96c9a61cf8eab72544ba42bfb0be70b8e93c0ef131315b12d0893d3982dfef0df5e80ec8c231546eb1c066a830840c7237097f307b3bafc85cc1a4fc92491f601af9eb9c3ed07d767b92c98ec53ee9f0f0e15698c05d985d06d3c022ff0a8a8f17a9b14747da009c1838901b2ca8aa4938baa8c8b037e6644150d4410a0b5491bc3300c268688858a0aa6e8608e02b668392d8b1f5b631b8d9a8da5db4aae6ceb002fa27907dfba4d782bbe000f8aecdeb4339254de1dbac0bbbdfc5687fd24eb0f9e0cf40b27b08d139806494279d00c8aad3a5c9c401e9c402128d5e4fc4099aaea6bf5f12112f31eb922faac502f1a3834831084a8a2b63011e232e19c70e288609ffa2071299274d2d438695438693a3871429c38cb89af13f62389ed54d01f585c1c0d93054ffa0b4f883ad6c409fbe184c120a1301038065c86c704d3b448304d2440a1003b610938d69a9ba1288f50441c8250c3f2801ffcb0871611c5a2a1c415419024141e705ce1c58b66103d7898cf0c555b68261045753279608200ca8ce763c5264eac70e3c40a2a7cde43c50e1ef645d5858d2124e1a48a227cf1a3d0cb5dd9802811e599a128204a745205c70d921bd5733fb40849b2c4668a0cb02469faf0c614202a64bf9811816250088045057a51e5630a4754a1e9640a3948523592240d1f2449e3461b7b58430f92468d3c744213094a4c07e573324514275340713285025078509101314e9e473a799474f242d2c91b4992c60e92a4a983544d219d48b106a90627526c7122451927527c21492c28410a20e210070d1d2449330749d2c8210e1faa23d2c0419224cd1b4033f422499a34dc20aaa82d4e30c0058d1b346d90240d1b2449b3068d1a502a7081345688b2271710802445218828d488020c2751f4800349664c9219731285e8240a21ede1c0c49e1c1440f9745072447182b206499242a00f553f1d29c78989154e50c43841f1e2048a1440bcc1861a7690431a6b38031a50cc2a34d5cf48e53d749e40f14ea0d88024492b404149018a67164922d1605f860a8528fa441c9e08c2133778c206663c918227a82772fc139a930a2441b20f060a16d5dce20b97203980f00372949828c2119a3224a20f6a98d0c1640e6e300943124ca660c203144c000092244d5fd8cf7f811213fa509dd9f2269c1309404992649c44401392f41272c14904825085a659759470128129aa1308b4a00a4d473e238ee8c867347d94884e1eb0054962142b8ed455f93839e18124893e584c4e12dbd9545888a88f2864ff379bca332b556931252b40312314847c3c68e6658a4fd5794bb180a80f16270db0815485a62433e68ad057f652568846d40903129172c2800e9c28208d0cfc60499258208303b420018628590423dc2841a3840c2709f080a4714333831304a821499f190d19d080a20331384180002449bac401ec19064822499a31942000094728421124c90c498a410c66408624955146a8c7013a1d901a450a94cf7bda09013c484a1620090e5042ba42152e70841f42c8010ea42152910e50e064001ac9ce0fb91b09400492d448363c78542702c81180499c04000e2a50785c8a0a7d4c47b4b91fdaf122430101557054bf48d501f9a83aa0e901c5740010872405917a9c00a0049274292af4c412de529f138d0f4e341e49a2b6902aeb99eff2a12133f43c78d4ce0cc9d65a6badb9bbbbbbbb33c618638c31d6ddddddddcdccccccccbc7af5ead5ab57af5ebd7a758c31c618638c104208218410bef7de7befbde79c73ce39e75c6badb5d65a6beeeeeeeeee8c31c618638c757777777733333333afb5d65a6badc531c618638c31420821841042f8de7befbdf79e73ce39e79c73adb5d65a6badb9bbbbbbbb33c618638c31d6ddddddddcdcccccccc8b237cae396be62266451a01269a2049d20cfd15b5633f44a8d01b99f9a105c5224379fe878609a1e78844d4e7a3c1424b45b1549a2b4892c60a92a4a98267cad0cf4c21148b2469a820499a29485290a459d92a2515a0cfca8746a2d1ed4c8a220a7528caf4f18caaa9fa0a0b4aec9024cd1992a4818224699e20e9a084e40449d2304192344b508224699220491a244892e60846308134b2e2b2d1426ad1051016b969e9a2a5456ea410225a74c1c5c60b1f5cf8f08c2cc5051cf58879d10a0ce5e901472bffe9c07c241049b44092248d0f7a603731a3ea3d33121c2a764892541201a9e40c48a8a00a4d3374535513a49141621f0c142c449ff9e9c4744231a3cf3f220d49b222029a91e4197926b505cca7230a51d407ca147a84171a2f44204992a429a221f2224992678aa68a23b4901a8105fba11e709445fd2226f0e69c113ad0c1082f6c872ab2f220a8cdb8a84494a772375a841792f474fe506929623f3e66423386f2c82402119224c95c217ff8f8214af2a32e3e2b2a2d459ece1f9588f2b8d8a7c2dd08a50a77a31df552d40d21516929b2b92124a1078d2a44b8808ba8b414516929f2e94ccf10974a543dcc10106856a3ea7308151068f2e0b1015552891f2428c40089219214620cb09988008c20499209241d847843920a1137f693746e10e2708424491e8f6322a11fc283c7065411c223499214253ae288e30d499274c43102a90b38dad0a1b079535d5143d515b523fa1836723c8223d83c723772d781636510660091248d073a20499e5048564d0f0ef0a7f2418940332120a24f871a8281900f04310641481a0ae808440fa27080243d118427cc084403a44a93058fd04bd20c208af007454892540249072056159a38a209ba62468485864715fa4e0b0a473441281e8f0f1e3c74fc21057e8843871f5c241d6fbc21bd91224992157cc8826bbd850034259274828d1b3d9024c944d2c1072aa4246b4dcdd3b2c20ae7064842e181d2c5152b6e0c91dc409124e93d9d3b61664c1b9a9024e943a40d38d440d1b186019ad0860aa436bc90daf000cc075aa1f242c48a0fc582d286244928f6d339c286212410c5c61924141e5c8409483ad84832a4191b3b24c91dca870d014892f459d983225078d88e1c244972d1b18726ec218924edd88310499276a07cdd438a24ed40d1b1870668828a1d6bb081c223f4301d6ac5435923079224893e6b8024141e6b7821556baccaae610022920e3dbc21e9d0c31f241d7a1084a4430f71d4cf4865e6e38362599124cd8e25499a2ba4c98a07cdbc4892a60a49d240f11e3ac3e854df22499a0d5492a4d140cc7bfe41332f92a4a16214b3a93a92a4d93185243d6846f4a099299a97925334b5d0a1468a068a0c4892a491423445530b22208a054451422449937257bef248fb91a11f7725f443923418780f9d2fa11f9fd04824aa1f0251212d5c8288dec5332b25e4ff25886752ca1344f42e2a2a2e10ba40e842c4b10b9166765242dcada8a8c0662bd0b1bb15cd05242e64a8501049d24481b222491a2844d547923416a88024aac20043365b7916214244414441840869b6e242e483345b09030ce96ee5598408110511051122c4dd8a0b910fe26e050cd94c4524a4998a2848339517d24ce58334530143ba5311e24e4514c49dca0b71a7f241dca984e101d2ac59b39595951517222b2e4462ecc747b3955017cd56429417cd563c2ecd563ccd56c2f00071b712860888bb9521ee565656565c88acb81089b11f1fee56425db85b09515eb85bf1b81009715cdcad784222aac5dd4a457d346bd6ac994ab366cd9a356bd6ac59457db85381f1741e883b9510457954dca9a888aa0fcd6771a7220a81e1e24ec55a1fee54e8c8c59d8a67c4853b15241d2ddca97c3a5cb8534932b970a7f281e9543320d00c89a059339715222e2a445cdca910a99f51e8c5dd8a8a16cd549aadb8ac107171b74204c810772acd5456dca9ac346ba6e26e45f421d0b7b85301b284396041b21398c3169224e99803cf410159a0a0f0f80284c24556111ff6635eb412a2427280220e3fe320461cd6e4c0a18283186cb3111c5400872924a99a28554c33ffe30d492469d38353cd2d50aa8992451a37e022312fea4892460211903e8c4e15f3a24eb3d187d1a9dc504992c4bc38c2e79ab3221f1fffa1649e45a5ba1f1ae2a1286abf873b2af4617ca8cf045513e4c28621fc70598321d63004697a149967594305663649dcadd84fd2510951a23aafa53aa1ca7efed38179eae97c0d5194e77244309f0f813af55b5ee4121a0199992c3fe3230d6974a1230d4310511e978fe9bcd84f0704f6d339928646431c68d041094494a7aac27e3a47485082890a2beca7736487fd748e5c6ac60382cfcad70972b1ff79501792a46180660167c8e20c28d2190a80c22326c60c1f141e9beac87cd006049a3c78446ead435519a620bda8806086783a33468aa84e0b7d117826cc0b7911757eb8d64766ad0f02d77a725c5c6b4ae6a599cc6c6126335b1693992db165660b6c99d9f25a66b6b896992dad65668bb7cc6c612d335bba6566cb6a99d9125966b64096992d8f65668b6399d9d25866b638cbcc16c632b3a55966b630cbcc9628335ba0cc6c7932b3c5c9cc9626335b5c66b63099d9d232b3856566cb929946c8e0802a34a181051d687840071a5248d218a820d98f1955a339a4fa8e8e31144092c4700669b262d8c124aa3a42445f8961b2341b8188e488e34554910f513294108a7aa97218a21092247dba683602c30a2429471c3e3e44e0a83e3464109294431081908e544a8865f91025aa428fc443b9f0e0f13c4413a5d90845542728e66d17782049d28b2824ae39eb242ce43f140b17c2f080debe67f36174aaea61266853553d9a8d389b23758ab6108298da114d198f6756fb9bcd666dd6a68baccdda7091ea5b3c1e1fb5ea70f1af22aa3a9ed07790c0cc1f1f335d3e2b228a85a5d9880a7d7079b11d205c641569c6a92687534d4e911e3da0cba2191177a3b754eda1630a389024984f67c6e3f141b550a224a5240a0a40a0128ad8c2195638a20a4d1faa138514e83843ea3863c8342945015285263aaa2469650a1d5090c3ed702a8903137540c1495568ba42f4e98c603a54f4e9c074a81531a34f35d7c44850b8d0c113b220bda07c3592410e9327c427a048f6613e2820aa330249059c8d740227882449ea694d8c44fee8a2e5c5e58b12a9e48c922a343da542a2aaadf8743ca18f59430f00804d384392a42fde43911ce984ac98f9238f279f9615309427871a9294230f9294030f928e2688710749929078286a088b085a26912042beaa7c803a2f9d992454a80865472e40aa17899ece1f445cb208514078f02080ed504578f0d86c389b0e686e4055d58335379bf6139a1155dc3df8916c1c3333f3e2089f6bce9ab5c86233b3983fb2783a29f5928252cd2c8aa05401820d920f81cab084244d2865a0a1a38c468624c870021949282af428a03ac880128a8e17c02149d214eabca0063a5e9044aa4414cba4e3053b2617a4f1a1444f47d7053990241d2ef8a2ba940be01896f850a11f238d313e44cd9719832349938e31c650c0141fdb411103109224e910430b301f2a242302205d7c18a11e3d7af4e8228b2c92503f363233546d92cc98169ca0058f0570b0e0062c60010ba658011e56f082158460053b2469a258a62355473402cd78aa98993228339f593b9f09f2783a321fd3114d199499cf7c99a8a876a06840031ad08087a240339ece8ce7fe87faa8600d1590a1024b89503ed6043c787c353943be2001ca4b0a38a1230569e8484111244a3483f29c2f5270002fe808630961804047180f987184247d159b8dcb0c0f663a90a410497ea023892889053e9d19125d41a1cc7c6638a2102523aa461765a68a992f3ef6333955f8c8a272a95ca8f8fc40a9c2435174478f1e2f59802811109922320298bef8e20b2aaa0051a21d4190b071a49a2324399024c98a1085c40b898241b29084466c48f3c7080cd38fd18f0020ea822846644575081d151a3c78d8f6565e2e4ee765bbf282992f5d94b8415af66e2fd6eae8fd67bf0d92e3747ce3a48fba4b091b9e2ffa6ebb3e1f7df72d1494ac414edb8eb60b19fab58e3afbe98000aa213216a35b2e3277ee3984ce756cb7b9da1546cb6ea64136aeac513bafc7dbde9a25689077756dbc286d4a2b7d7b06591fba7a6ba3fed331ea7ca5c40c3246e896ede6aa776dff6590933ebba6d768db65bf22192475d335b3e5a8bbf442ca7930f3659a60a6689a426f210b89406848469fad85cf698d77353a06c990c51bdfd106a97b3dc52097d7ee56effae6ebdf711e83f11b516d292f4152c0901fd37999a6f788e8a89aa669822c4636a39230c4e55c8bafdd5b33d36b9bf2b2b539eec7791ca2904ccfddc0743c9e779da4040cf241cadcf47adb85d0310a5d48c917647d4799473a632fbab8398fdfae1620912991e843a2aa138a2f255e90eddafa59bf1bc736db7bbec3f248a8d09069e2caf3968ad9346fa2a40b925dbfdd26b79f9cc7d64e6b7d7c3ad41039d97f420917e4b3f6b167df5e6f8c31dc826cef3d76ee55e8d6c6398a619e1117d304f150a205d92ab5f1d5d78feb43879cc7475a8602b52c48b7de366ef4beea4d9f711e77362130aa9ea67783122cc8cb6ea9f37a70c549a95322c8cd239d04255790cbdb9db596ddc62eab97f3787137e86ec4dcf3a2c40a72b6af715a5b5fb3ed68398f8fd409f2364dd3e46e344d9ed9097d354df6f347eaa4d3e4af648c922a48f8d5c6d870d6f88c4de63c7671a1e999b57247de33ed23098daa6316239b1e255490f1ba696f2fd666b3ed6f0ab2b276bfa953c6d7be8342a6490af2d2bb608d6f756bebc209af88484854b96331b231414914e4ebafb459c7d67dcbb4e40c16978d913176b4a785c2bc7632abed1032af469f10695bf79cb9eb6ea55de204595d43b69e9b9fb6d6604913a4dfc996564ae9b3bcde723762825cf37fbdb858ec5fffbe53b20439e17458bfbfceebbeee4809d22d8495ff45b7d6b976dc664912bee37af146e670f1bd8ff3d8dd03c574181224c7492b638f526699d796e4b708bd0c25a44f507204d99885d727bd9479d53a61e747094a8c2027bbed59c7f9f3fe6a91c8344d53cce8e3588c6c684911e45bfd1fd9b6ae0d3ad8bc8408d2b9e172d4dac7b859ee583204e9b8b1061f5dd5edba0d398f65fec342ef1212b930bf90a862254290d7d5fa2bae7819df662fe731bca251ac60485431561204c9ec91ef751c2be5c83c66f379cfe31910559b6da3949714da4629b1bf0408727d5df4daf92cbdb1236d404a7e20fb56a66d5fa41432ffd91e28f181bc77ce06a9e3c990d1c8af07f25f84cdb117df9a3c5db49ff71f253c90f72d7d1effc5d0260629d981ac4fd9eb59613773bbfc79cfa69b16253a90be7c460a99df3d2f6e939303c9d6d25ae9bd1cdd2e4a398f1d8744150ea4c3e8d0b6bab8365a5be3bc4dd5698e253790ce46c673c567c8ec98721ebb8ff9c6cd6fa9daf98cde074a6c2029f5eacbb1b3175a873124aacc90bf9cd6d6e0bbccbdd8d44351748b921a48e75864af51fad1b1edd53490aed93aed5ccb456b9b35b72e99818ccdadb9536e0ed7bc8bf3986d363023fad6b50c24bb9fd45b5cafad5d6c82d854120339db71f7e2777456e8cd79dc2a2530906fc61b9fc2f916b56b3dcee38ab2ce2d24aa5c4919d25a16abab75fd5defee9221636d7d2d6d2cd2551fbf921748c7b5f65ab0a3b3b3d92e71819cf6db3facf6ff17f7978c21ef7b5debfdcb1c63daae084ac490b741d89aba836f396cc779ec992198177d3a1b3b41cda1174551cc65b005eeb11dadbe2a33ea9cf318368f36a2980b250b2a8bcb276d934d7addfc7436ee461be798d18709598163754eafecf69d3777cee3e6a202e91c8cccdd9eb35ddb6a5d2990acadcfe82cf79b963dc679dc5c98a128e6b22cc2907efd51fbaca3fdda99398fed6f9c5d8b3392521629dbd6377ab3d4f9b073c83455143a16231b9824325af85c7de83ad6c9e064ec3e44d59691357e7bee2de60b23a32e3616231b2f9044760eeb7af4d535db659b91640fe16af5c5791bf2f5888548c6e5ffdc8496357bec17e77105b9923ca17b6cebf5ca666dcf79bc61dc42f2fdc256237deb9cd1c89cc7ad0a3024f7a3b76f8490b9b6064110bae7acdab5ea4706d9a4a4f78c7f67846ed95bb582623a8c7e248c77bdbf8c3efe5be9c7c8e6f7da2ef66f6aafb7158f7431b2b5b05d643c672fea7e566ae733874cd36331b2b9472473ccbafb4e069945c6603e9d2371a140b63867b7566db4b42ba48c29d71df93d59c3aebf5cfcd79a3916239b1270e4336ef7b95557a3d72d384d1ecfd769fa108c74b6eb55f6eb32e89e3ae7b1ed6c1e34237a1f1fd334f3998cc5c8465ee97445ff45ddd1e66c9df3b889626e065e72858f3257e383d3195bcd5609acacf7fa7adc7ad2ebd3b2247605e6335ba6e9612a10b5e1c76264632b7b6c113ad8cd1d8b8cdb6f33cbef4e4ae36ce6cb79fc222425f4a29569fa109069b2df0151759aaad08b40d3cea9178744a0122550099fd97b778deef176fd50f1eba78b8f8a831588e231253f7b93a17b945d74479d6331b2d97cc19c37165fec059941e8fe9c57637451e775b1a61fd98c3b8b914ddd480b1985cfc2a60e42fb28e7f14c4804ab1af2e1550d4d8f2c46367e02c79039e6e6cd7163e6a037c3d8683b1bf95deaed1c13c86fabbbfda573f27a7e4b20ff3d3285b63d7576d049201bd7faec3ae8aab769dd76a88d6331b2198111f9e89cacad5a997afbdb11c8f8b6314abb1d737a1715e285f479eb57766f74ccb4af08e4aaf7c5c87e9bb1d58ebdb4b318d904292299766cecececb3aebd4d53880a857e9adc6331b2e91071ad57ece7e05b8b6133638fddebf2e833c2c7cc3072de344d93f3900884821779a3d3396375adc6089d398fab23f3411bd1c344b7d9bce748e86336f4486dcd626403c4455ac8de7270dd672b6cdb388f31d022adc717fd3686cfb5785d0824e3d5b6a1bdb4bae7f035f3907f0f176d96d77cf53b86a00bdb5e7cddacae56af375b4bb5b02c06eb738b42beacc245570b1792553759b3fe8f596ad9e43c4ef5d80979104e7eeb7174cbc276efa113c4ed0202796773b4f536befdde6d9cc7202a64797d405e861fbb317e0621dfc979fc1fca5337cdbc22df3adae2bbfcdef557e63c863fb6801c39bef5da41f61673b39b6290783c9364bb38cee66efddddb9585d1a94621ea7a40319d6e31a30f13c55c9920b2ae8efcacdde9566bffeea53651888a7c11bac3d791c2f51cab52327b993a870dc6bf6d4db7a285b4d32b64ebae5b61b376711e6f18b3e3fa48581740e46448adbdbed8f3d3d99cc79b66278ab9ccfd903e9f8b753ddb45db5f1585a0077cc8666f3df36963a4efadc579bc79d003d21d3ebbfedad93e1d759cc7905f072483b3ced5ebde1827e4c9792c438560b87bc837eb73cbe6536eb0c5cb791c12551c90ebbbf56dcdb173cc59e43c16c5dc48240b79dd5cded0b98eedfa659cc71bfb991bca72bf116021a7d7472b37eb687b66c879cca946a04d88ba1be7230548ca4bca8b9094fa45ea23d9c8ccd06b2c46364278c87fb345c8a8e5efb72ce43c0e4d4eddac491473a7e9435415816814b3598dc5c846045142b771d186937a3be793f3e02845a52525e54548cae6d3791d1281d887a8ea2c46361c80b23e6be37d675ef9bab9388f43ff79922e08e7746fb5c5ed59ca795cd9ff6a237a98c6292f41aa99b27a94e2324d33e54548cac6238b914d890c5c6ba0d9d780f3142c02dd14159200531a5812b8c2025304aca04015bc6303ddb91f025d400313a082023ba498428a0c5c408a2b1aa5f0e6391b0a60c05d052e9084921159200a0ba040e15a5b80613307a502cce3e91c5914986202f3433b2430f347aa88c01410d850f10051688aa638690ce880e954513880ab993fb2d9d1002918d03154dd6cf8886b0daa5fbd056c7628000309d86ca440c026c98c81e20030946703f3e9c0c0509e081820c60205e0cd87ee2644892605083000dec44c118a00a80880fdaf36170040ca13950666478e8dfdcc8d9bf0460325bca93a9b8d28c96340f249b0195028f998f9d1b0197d304001a4365ee0081fd20e7490031cdcc00666d480063390410c605006192f70c11862b480052b50410ac298495285c000519f18cf1114743830f76da5f38bcd094c500212181981a4a30966483a9aa0891c602880d4861860f02179200090e8801d24a96a44079248928402820e3c213d128a7aa8da630d3d9a207a981e64f4b05af448e9e1000e48e2840376c082c4811c70400c69118a901c6892cce2115920e2d3a158baf359c902491643b2a0408924652161f1862461e1052ccec02208120a0f2c7220cd7c7c4892261188a82e92260c27583880872624942a78acc1c30c28222a08ca34adf048398421a4dae1804254cc8c87026d409468f347664c54c34934038507caa72324824ea2094ea2072449a26207ca099aa2a2024ea02002af979d8bcdb7f84d19c8da8b5a56d946763dd218c8c9f8dde7bd60b35f87815cad39cea6abf172ccbe0c19977bfc98838d23fc66c9901de17d089d6defb5f802c9eebbceefcdf68db2ea02492b6c18977d7863f71c4376743a2b74d4325ff7e51043daf6bede8790a3b3d16b27a475b4c2656f9bbcba3527248bf1cec88d2dbe90996e42b2c5fe99efbba76fba2664e4ebe65cb523a3f4ae9909c9973e65b7b7c18e7e3121ddaf576b74d3c2d96d5e423ef7cbfdb3eff64dee6809c9ff0c5a0717f51befad847c37ce76935aafedce2125249d8e272fea209b4cdf2464d759f9dfa22daefa4d1292f6578ed6c5b6bf6024e45d8db2e59a29bbafad07095923bceece603b65661f21a765cf7fbeb8e0a5d61192d5be7fd9e4c9b339d808f92cb3f1da767667d76b8c90d1bd3dda8f7b3d48d91721fd397be7cf672b5f5a1521dd178dabf93367b3c144c8cacdaeb5ec22651e9b2142b69bb536b798ad6ca33d8464af56dbcc3c9b568786906d1d5aefe6f6d9dbba101246d6737e74e8cd1242b2c81ddfe30b59838b3b0ef9b87937bef7d1d5ecc22163bc4ddfed7d2f3e1d846cc8b7c5b6e074ccad1784a4cf5ebfceceedeae61c0819db9b2c5e7ee88dad0a0849ed83af419e75c23be31f24f3361f7f74b4b9c86d711ea7bc04892c46362c30f1839cedb2f5b1ad7b591fa4c74b1bad9332b6be46ee37e46d9451f73042e66ed15a01f11d98f0c1dfd1775a1983f4d5673898b821979b96dee66e7d5f5d534f790992c20d0993362adf5ad9fe7dea1a3664c3ba76ddb9afbb6e03fb0c267b90ec8cd558638bd0aba56bc89f8b3e9cf3d2ba685d1c4df420dbc15f347a9bafad852f4a1335e465bcaaebc7deadea6e1cd20224c5ae4cd3102129ce358b918dc919267990af59e85875f6dea06d95f3382482292f4152aa0f202922aac8e763545a5266422faca182091e24eb55fbf1a4eed141fa9cc77e0749996de62284ce1ca59529133b48bed4ad66f0ef7ae89cd641ceaecc7e2f76fb68ad7490ef6e4f7e6ea616421add03267390fd60843edf84d6a77b4b0ed2a34fe66b9f3d52bf1e0709d74fdadc2f16ffd566e120e36cc6ccdab61a69ac363233b4ba0b9337488f90b6f8e6ab2f360b997f98a421edacbce663c691d947a90b17dd5b98b841faabd1328b3e277bf33658039336c858a3ffa38dcdd896b187619b206c901056c8bcdc5b5edb83eb6e8ed4190292029b47292f42a6a95a839cd5a37dcf986b0fdbbd1640305183e479eb6c0f2bb5d6d1c7d2209bbad66ebb471a29f5478364d5e37acc395a5985ebb9c919e4740cb6eb7a5adbbaf96e4ccc205fa5b6b93fc75665ee1e91212665903c394e37a9b3962f7ded8c98904132c71afb572dfcf518da24d48f69b29f84a2f0814cd0e0dbeeaf1fd20b6f739cc7ac81613206d7fcd88b8d357d7ecc793c93840a8136949da6bbb1764e5367865e9849224cc4305fb5b45f5bac3de77d6854519e3a4d53e83d9e182aa4b21e17c2240cef3a5e70ae581fb4d13d189c7d733ab53d2b6cfef00b72b1757d7673fd66a5de9cc72ee525488aa7a33245c1c40bdc5c65cdd256a985b1b92e3c1bdbd23abd4dca1ce33c4eb16f9412d3e154139214fa4629cb45265cf8371facb71db37651cb792c1ac540d1888b8f4adb026c6ef56aedb9c6dd1a64fce69bd746e79abb4e3d9e59fda3d22030d1826ccb1a3ac8fc9f7d6fb61504932c4867eae6643a2b6d079b731eb766662c46362e265890cdba069d4dcbcf98f6e43c66579071c2bb38be7bef627f3d04132bc8ffa737fae4e8f7f97cce635705f911367ae15f375d7beeaad935c82c46362a26549073567693f2c3087f7af4782610b8e13805792dc765dd84cdcd27749cc76b39becdb118d9a4984801f2eaf62284efc1d51a3bf77dcf2d8fb53637635b6d1205e96e8b7771bfb7cbac3e108b918d8cc91972b1e5a08dfe1e3e9bd78251b90f1328c8cb3f5b651446fec93a4ea59f201d65dc3d2f53187d42a6448e7a6113274886f6e73bfb8cb5fa2ae53c8636860aa938c1a40992bdcba67dac27b747df5db498a669f2314dffe9dc3b231bc184097245a78b31857cdfb4fe388f3715ce783cf34e936b7d642d41da67b638ce18e37cd52fe7b112a43b07aff3ba2abc0bc69504e93f9fddc5fc39caacd6161324c8f962534b5dacd4f9b109f31f47c4e408b24e678f56efbecdb6d08701999a1841b2b35add63336be6fa721e7f189de8854911e49dbf9a6bcfd1c5a87d8b08d2fdb2d141fa66753152e63ceec7ae932cde5c5eec5a1f7116231b6b3204e9bc5ccfe9edf2377bbd0c132148e766ff73778cf673b03d4803932048369fb2d71c8b6d326ceeabc919f2363c630204f99cbdaf6db40d726bb5b24cd334b118d9844c7e20abfbaf7eb698e3846f721e3b2f9a890fa45b3a9973ae9741d87f7b205b7cd545fb2e65d6c1f64c30e181b4363ed61ada7676f19dc90ee47aba28eb782174d7ec7520dd9dd3b665cf368ff152931c48777b2783eb31bb22e3e7e2c28184ce5f63ef39dbeab3fb8603931b48fad839d28ebe9ef5d9e00e4c6c20eb8c2ebef9dcc15ae77aedb118d9704ccc90efbeb7dada0bdfe4781b6e4c6a20df3167a753da9c451b8398d04052d88cddd2599ff31856cdae99c5c80688c90c6074ce9c6b7f10765cabad685babf1aee5fe276b9cc7d3544d5343622203596f7d75dd8ff73d378c233089819c7f9bf9c6c9e8746c2d0c644fc60ee374ad52669d97d1984318dd45778e5f5bcc316ef1cdf687acb137a7bb266448baeec3ba38b63ae37bd85108262f901c63cfc50dd6875cdda10f4c5c202f75edbbe7bafda86537fec0640c392b6dd4b67737da7cb69939649a360c6d6022863be7b55c73ed357c9039b8daba8d5aa72f42cee3fa5564b5a413322ee7b35576ca9abdbf9c57411e259c90ce36666e578d1f9aa3209e394dd32446c926243f1a1bf2b51f1dfb47ab91688aa6695abce15a7562dc394d48ea169deeba7b4e48d933212fbdb1cec66e7d7ed15ac79890942dac7fffc218ed9b7665c3979014d2e50e2ea74e7b5d670969af853e9d9dcdbe52d69590df78be77a947cbeebc8b12f2c50b639d8bd955198d91eb570d48ca8b9094990701e4c59bda11813a1ecffb24e4ac8bbd236b6c424948e7e9ecff47d79e832ffe480192e223e545488a5b33288984ecf6565df48ece7dba2889f4e1a20510394dd3344d90c5c8268c1248c855175cb0dd9ef436b695f338446d440fe30c1fe347445f0df9f973065fb7b51c33c7d5f4428ed4fab5f03942ae76b7e79ad3679bac9df398ba9fc842d983665e5e63414923248db0b1fb63f3dd1779aec0c808591b65f4d9e95eb7858e721ec7182aa4521fe4b2796c11b2d969335bb718b5cfa822646cebd9fbcf5bf516991b414922e465cf1d72bf35bf794f4448f7e5fc6f63361947f74a0eb19c47e8abf98aaef16bcc1e650cd665dfbdfd9ffe40506208f9d37943d86075866f7e2124acec99bfc5bad967e12384847f1dfeab1edb743ae390f317736eb96773f6b21eea8628d154490192b292f222240534b99826d08ce9324ae090fdeee9a3cb32c83aaee83fa41907258390fe587baf7d9dcc7fc21b75502208c93f21b4f7576366cee683665e88501208c95ffd7f51870d99afc8bca9420920249c175a667eaef7f99b4c09257f90b5698dceaeed175f73c779fc1eca42d4104af45999a699d0342d1794f841be65b66e9b8fceda16bb73913ec8ca13de1559a3b732636f2624fa4817672a94bc215d64fdf74e36d763cb66737c908de965efda581d8cad35b8dc90b5fa7dbfdab1c6ff1c4e9333926962deb03312e656286943ce0a61bfbf8bcd7e3d1925fa6c28d1a7232a61433eba383a6646e9622fd23dc8dad6dfadaf1f46d8ba398f535e82a480a89016d304a242769aaa69aa8f6495ac21efe2ff77bfac46dace2ede70e56644bd84123dc8051775d6ef5ecabed52f324dd334239aa6b678c3a2cf9b1135753f9d2794a8216d63f1babecc2a3f742b461894e441de6bd9a3ff36ceaef7effd16519dcb8c123cc8562be5fae0ba6e417fb889d3b4d9cc696290c5c80649c91d2483d646da5f27eb1669b483bc953ebfeb75ba66b7da3a48171964b8ae57ea3532ff50d304598c6cc028a1434718577bee45065d2f876f45c6b6319dccbd2ea8d292c24222d01c64335fac358badeb7cb0c941aec8d7c26e8bcde96f31ce6318070929bb93f6b4bc22abfd70906dd6196d8bebbdbeb0716331b20941c91ba4a55eb9b11aebbc0c574d43062f4252364d038d23649ddf3e46c7ae1fda081b21a3ede9bcfd2dbb191a46c8c8eea3b4c50b2df5e7e822a4ab8e99f7b20e6173c740a3084967b5fdeeb17395e1738990eeba375dadad5dbbd5102161eb0bafed09b9aec9da2124ff7dd3f5c7387dd96a08e922e5b7babeb5dc72ad0b21ad63466f7b3769a5712d186808e1bdea8bdcdc1ce1f78cc3c5d68dc1c8adb1e7eeb56efad6a5ee3dd8e87c4e82060ee9ea5bec5a7737725bf7721e6faa8dfd7cd556a019847c666dc1e7bebecd5717e7f1a713f3a4a011847c3376376bb0d90addb46ff10d348190ec56fabe3d63ffd673721e57b5534dda0c9da0018484b75ad7ec6c43e6f15dcee3c647429408182f012841f307f9f8adf8a2c3469dcfc63e0fa2366f0a1a3f48db9a2db7dceb56576bd5e480b81d15347d90adb2e50aadff7d96b5c9dd469a37e48b7ff9b6bbfe3d5d35721e579bc62548d0f081a7a57143bef59e1bb4f645ce63c647d0b4215bc3778fb6ab76cef63c76e5d3c5345547288ae3adb5d6dcdddddddd19638c31c618ebeeeeeeee66666666e6089f6bce9a81a4acf780462f294384a42c6631b211800e346c48f7f6359bf0d9e9cfd5f620e3e2bfcfd56b3d46ebb8d9e84dcd1a92ad2fee3a6f63ad3646f520ad3b9f163e74ecae7b5ba3c6f2e570b1079b7384f1c566462773eaaea3f63e7e1102aaf8bd409307d9d3be6e3cd9ecea98313c48c69eb7fb6bf6838e69c71f9a3bc837db337e34fea26ddd0eb23a735e8fe9a477cdf60e68ea20ed73cfc566975d66f05a3ac8fa3ddd5747e6bfdeb47390f15e66d4c27add5bfea61c66f3652cbaa6d4c541def637c2e53436beed3138c89eec208cb3ddf6e3c741346f906cfb7eecf726cfc830529f4d1af2b5b61c6cf3ad7aafabd70dd2adffd7cd93da36c87f7d216dcc3247a78b970d32b668a79bf4bed77adb5d8384cf2efbfcded76c9dedaa4146660bf6856cb548df6f1ae45d0c23a4ccbae69af58b06f996bb95ae696b3357d73d83a4b4f65b77c1cbf0f6bf19a4c7199b7de7e5e674fa6590917d652dda66f6b9854f06f96ebd75dac9dcc70ae1a3212b5cdffcf15d47eefe31c8f90e63abebc1d8dcdf8a4142eabca67f43ead1551b06d95c5c3fdfa37ea1756ac120affde6da8d6fbe38e9d72fc84b678c76c667de62acd50b923ecbb4f2ec1b99fd58bb206d6d8cb5eacb7573ec552ec88e76b1b5d3f67df55ddd82ac8f29336fac6a41b265c8bee9ab1f6d8c340b92cee83ef247cb20a38e6241ba1aa15738bf3d63cbd12b48f7e2bdf0557bd77b736a0549ed62a6edec73dbdecf2a4846db32ebd6b3d7cc7d5241be5358df7c5fac7ae3390519d775eea96b8fe9ec35a5209fadd459bcd4b5e7ed4641cef91d6775bde0d7f6ce9035fa85f0d508e1bbd7452848c8ce27bb6af92db72d3e41c6b710d6bf3dffb2d8a213a4bbcc42a6def4459e2b3641da47e97ad0ae09978b8f09d2eb8490f56d0f2e415a7f73ad1721bbf3de0695202fe409d9196de7983b4c82646eb3ce7ed6dfcfcb201264b3f9bcd9fa864790d1c5b68d56462773fd4690ce1bbbf894de7efab408d2dfb608e3c70799ad4904d9b53edbaaa5add9f8e21064b53ee76b8ed54b2f83429097b63b57d7b7af36c320c816ad5bfd2e7f9bec1d10e4ac90f9b6b37bcc20fb81bc6e3d325ff7e9a48bf94032e89ec1f72e75ffdc3d90f52bafb69c756c35ca8f07d2b5f6eeceb7dd4819bf1dc8f64ddf23adf6efbcebe9404ec636c6c9de0bfebd3990b679edba66a50d69b338900debb2eead577703d9ee3eb76d5a6add84cfd940fabb5f6ffd67f75b73ce8cda6fd1afd0b9d740c29fb0b27a978dec6f391ac8f6ae5f9fbf9beed29f81a4f02b6bcfb1177b95817c7ddfd7357945b89c8d8174cbd8ec4921bfad0e0319ebfc6f6fc50927645a86f4b71c4f47a3c775a92343fe5fbaa24f1a6d6d97db0b6465cdda36cbfc2d57ad0b248dee35bcfdcc3563c8bbd8aaae27ab1ce784ae1143fa5ddc225bff1af4e8bc13f231d6e072d6debcce35e7847cede8dbda70bd5ed57d1312b6ed78617f378cd35d1332b6d7ec3ad8d5bd789967423e6be3ebf6d31b42cb1c13f21d73f5fbcef64bc86967bd7e2f83cc685f4b4867e17ff7476baddf5a09f9f7bdbeeb21656e394a09b9dc3abd0e7a7bc70a27216f3fdb9a316ffd6d1f9384642e3a73cae6eafb5cb348c8fbb73e67f359e7e6650609e9bafb3aeb1f6b3767f608f9fffd6ddd9d73b62de608d9dc3f658d6ffb657b1b212d85ebed5a333257fa8c9090619dff6b45d61eed22e4735b8b5abecc5fe354846cfada6d77e337a77176222465eb229df0355eb3672342ae49ab6dd0a77de65cec43c8c5abf6834d27fbc9b00d215f5bd6d969d95b685f77212474b4ba181fe3f5e6432684b46db6dff65ab3f5b28f43d246dbbdccad166be387433eb62c9b0e5aff87d10e42366dad5d6bad20e473eef2f4e8de318d3110f2efdbaf8fa3f5c72820e48c6eba7f0b36cb6dc23fc876aeedaacd9b3af3e60759e16567ecf274f7f0f39efa48369173f441322f8f6ebab518f305b90b39de9073725f9eab3666bb32c879cc2c0d39f8209f3d5ede7aaee777bdc779bcfa0b39dc9097327bed2edb7541db57c8d1867c2eda6efc6fcd79a333a79a5b8462b828401672b021d7b42ed6d77cb97563b59cc7cc43c8b107e97f9fe1b4eded5ad5ddb5de629aaa14205ca4ea6ecc626463801de458433ea73f2bbbcfcf1b7cce79dcfc1239f4206bd7e598ffdb4ba9c3c979ccbac83475403e9ecea7936eeaeb3672a821e18dee28ad2bde08b945cee3694a22da7cfdfaa13e33a24e10d1344d53354d4080cc70e1a323aaaa8f21326d804cd334d5ce34b9223f3621ea6e3cf5ed34857ec30800478e3c48e71832ff1bdfbc8e617540c88107196df51a1f8b34b267ebba83b4de0e3aeaac33c86b393bc8e5ee9dbb8bd55e16610721471de4bc17d267635cfcdcbf4a07e9ef5683973d5cd6798b73905c9f33b57e7fdd385ddf20871c64ac77ddff07637d74d6fb221172c441fe6cb7cfaf9baf3ebf8ef378f3e01839e020ed8ab3e19dac2b85eb43a056418e3748d7adcddb207ddc2b726f380db95ae40bd7a4d429576b398fe1e3400e37c8656b75932f5dd4c55579866eba3f44a6c9c275841c6d9097c1fb1c63af45ebd41fe7c9e460836c7575f3e8b1b24a1b6d1bd1c33c167d9098630db2f1737e6b756bcec5feaa417eabefebfb6c0e7fd99b06b9a83b5ae3a37da1adeea241526e7459fe37fffff93d83e4659fa374da7663f4f56690cdb3f931b33ce7b3d396415a0bab63abebaa707a5b32485ef3dbd5699fa50ed9a221eb9ad3ad6d95f6dbbe750c92b5f77c7dde39fd2dad6290b12fdb7fb4b938b9d21a0669179bcb2984ccd6d6af8241ae3aadc73add3bceeaf50bd2b2ef091757e610ba55bd20ed74d4b2f7ac1bb37e6917a483b6ce7f2dde8e943ee582b45da3ffe2c5fafeb2740b32aec8625cf6368dd629d582b4afed6b5a5f73cbb549b320a38bfe91f2adac9f6b140bd27eeb369f3b579f2da35790fd2d7a5dde627c0b32b5825c6bba8eafddd51cfd45ab20179bb53e66deef61a4930a9299bf76e7699db519e714246d6daec6fe4ed6e6d32805b95e83d456d74ffd7bc628481a1d5b67bd315cb1bd78866cf76c2d7ece277db52314e43f87d7adc77139641c9f2027d308fbbd06676d6b4527488793c6f7decd9fa50f9b20e743f8d8f3e89c65944226483b6765cff432537e0f2e414ef7165f7b87b335e7500992bddeeb7132d88bdb7d49909399478e70fa74bdfa2141be175d656c99df11a4d3a68b3238e9f5690469d7a2ad394bfbc6d56811648c8c3a868d3173ab3a11243386cc9e5617d7a3ed86202fadd0b6a5d3df53674d0832bae560bb752dc3176310a48ddc6bc1bf0f3a770804d996a3cc2ba476adc6dc7f20fdf662d3ef5bee6f6b1fc8fbccd66b117275372ed6037919474bb93a7b7651ca03795f73436bdde78b0d7b07b21973e5c8e2ac1e2bd581846c9b356df3bde5eb9c0319efbd8e3fb275f7d98a0309e1f467b13d7803e993ffd6cbae8dbf9e17e7f1c7cc8faf20870de474c7663fb61665d3a319b2df556be7d7e991d25703f9dec5c9fc60b3fb5cbb349090adfa1de15fe89abd33901e9788a864f0aa959196a22086310010425949150063130030302c1e8d86a3f178a2287af203140004597e6a9a422e124983c1500ca3208a82180662100008208410628cf14a116e00c03ef6a440347b8ef61d6168e8db8d0df74bf5611d510b68ad2dc4e0fd4d8a705a5d0d20b5810f42cc59fd9f4544d406b19c83a5727210ac88d8bc7a22b7f22c74413e376766d43f788a5fac2fdf055a20aaed78093242b7db8e7c7791f20bbdc6d4e1d1c0af715c1647479b78593ce764b6f93fdf909a5f5470cd7fea27029f280eab52b3137df15661e3323b8ff7015830f651a7273da49283635c46bc941e5f330b5c53695aa4c414c740ac11cc8cf434f43e3b88e4e300cbcf1b5855b61443e3b7cf3ae2f58cbe3a857309bbf341814fac97189f5c7dbbd563afe96028de3f4d559c637775ef79a3917c321f061cf708f244ab71818e38d3693729bbadf51eb454afaaa0d78a3f4d96679d2e21ac2a4724c040e4260cf2b64988d8840a4f118c18df8bb57c4b12d0f1378c3da0aaf7845d991ff3d2e20506a60f0aa374a0c8efa7fca2a0a21527897b47f660b144cda256ae37b42d3e55cd014e24343b0245cc00d7683431844a4d024634b43b04256600d928b43186524d014344b43a0635a600ad515aa79145472ba4a62af3940685bac813d562c2a15454487a9b188e64b508086641d025fc6b50cfeb6ff590124f46d0febcbdae95a0e40be251b52f6d3c1b83e9328941be548d995566b1a4bacbc4fb17900195418442a82bab0fe602070a2030b4eed4f5c941dd598cb823f387efd4d4632b82e965e5d48ddeaf1cb73606e2728a1388010a05ff86fe03848fab7cf105a2e4006f4b5c860909ce6cb43b4223b61ad1179b067e396222d21b4b2fb206a21d49117b35622ed207421dbb22a26e4e92315b2af3bd8e0d743812230636e22eb60c9c3ab2a2440bdbb494cb10a390de587a252ed413d3d1fe6ff0381dd8e5b83a72928c8a309d4fe6985bc7bfc47c0397c5f1dbe276fa2f2b5ff4b24dac5c183104e9264bedc585d4cb8a6f1d058ca38db8ce6e276d68cbd03a55b24bd0f5d2245eb95cd7fc4bcf0aad93978332a153c81bf241fd5a11d38da48bbd066e1cb1474aeaa8c08413c8ccb8a5ff1bcd97b5b4daf0a490ab1173b17d4c28bf2cb7e87f9bb272156230d209961ab214a29b2415dbcb2d9a5f38cb4feb8525bd6b510ddf74b7417f656659966b0d9b666a9e068e85db46721620ba2f4a001204182f961cef46f5396b295ef6f0725dcc37df84e951d1da2c706a31db74f290f1fde9e2a18c31dc6c1ca3fd8cd71ea7da9dbbdcddb6d970c5a861734295122e6558f85afbaa0ced3d0e494ddca948262bb01a15bd7ba69d12db681ba49b30cbdb7d19f28eb2710b7b85237b6b0e9d4bde82f1c80913ca03e9e787d9c605cec4ea6c6783853117b6c7ce70855075dd0ab7eca674514177509eea1601f155d06c868f7e9ac0a2bb4a93744b2c62e552a7df82c7c80e9410c6568f1a106f5926ebb53c43f68cca8287dd6264cde7ba3e28ed5506c217ce7c270e662b2b5907a87fe8cbf965efb3b7b4b5c7729ea531371476482df40841102bfaf61c052b0f0f36752491b2b48b4d352190a2dbc7847ac805d697dac9d4db56be954d37ef10cef6614f290e60e834e594ac79483a807ecc9167e8b4e7fc17e64d6a7197a7ffb60324d2c0f265701d05829a8c5336af89c889c2e384a5052572bc0366cc4b3f92f636866a4a0ba2bb4e41041b08b68d12286dc13911d2ed884c9befd5c8c24c76df134c28b0aefdbe99986f8071a010630bccc416981aba14c7171b16bdf4baa4b93303585488bca03c33f1fe37cec4ed5e7db1cd7835346c12ffd18a84c2146237de06cdcdc199f64778ebcd2534020bf1c675f868d73e0457b31e2ec063f3fb0a85487dd6f5fb2fd7689e767eed5744420be03cd8cc05d25fc961271803032f9c728593fcde6332a54317018b5a471ad5e0677b35288e62d90ee373d126c2e93a29f3d340ac41f68f323ae6e2f80e81b27bcb68aada9e470c4716f437ef848369151d5cf6d460e9a38c27532573379c0dfc3517c428f52d4c94ae18c3bc42254650002d2c6493ba240c163a89265f3d0fe4f44eb282f3933c701949a201f1c217b46b28d8f5f9340f6559debd6f3a3d304a6926664c1c7740d4ebe62c7e155015dcba450bd716f65abdc3fadd02458bd856f5b550c362b9c58c56695bf06b51864573ab2e5ad8b6b8d7e28755e416315ae8b650d7ea1a16d32d68b4e06db55c8b362c9c5bd868f5b6c55f0b342ca25b55475d0368f1440ef9b59d3bde22f31d0369abd85d6897557dec455c4cc6a0f8559e314a7dd11cc36957b5cb6c70c7015b75b281a61ea2d4062f8d92666d82865b3a2cbf715deb6b0cd46144d7a1ffad2adf582b639a0c6ab5f23da3b046c3335cbed52e8d6b32de6aa03d6be437b4d3509f63115927af415d07ff1a85606d26c3d91b16695c36eb171bc86dc4df0d3dbdaa93b17e8d9977d0a5959b8c626fd8908db0ce78dfdc349ff6e9ba2abdbc22ab74781919c3ce111c1ef12dfe652bec7b42cb849dc1fa7340d3d0f77f6d6337f7df82cc9804a31b7468715d4016c709787d4f16e4065c7fdb19eaa279bfa83073765a6739bf1351e54f40e22a2ed8f7640d0d477fa262f489c1ae543fa3dad1213ec7a51a841cd666717e315c756d879077f10f392723212f29cee423df9a0c0bc3faca117b65bf562bac05247f0d95d89e1da5722ffc705194fa79afadfc08b0004faca5e3497d63ae05195f38e2262756700962671bde531c050685e24b445c7a0a995f26d17990d010f188d9b926e1d70e778b0f1e1411ccbe087cbf73c6a8274169ba76a8ed86ea20f28f3d7f27e94f2ec26f5cc5086c13f48d54e9f2bb5f0e7aac40cdc85e5c5522d0d07c8f135a0d67d9ef80b87cb7c8ef882c1dfd379e4b1ca17ff286936d98fb4fcbfd43a428c3b888510f14cef3f8a1228446e62f3ebfbf801a18e91203712601f84ebdd7b4c8d53ee143988c3ba2e7cf07f0de2e122141d06a40c9bdaea3c5d667edada6624e0edd34b520922ba36ff7db796b15cd379c03b7eb0cb5923ea410a9d47b0e2f223fa4fbd6e450354e7f2504f4df5cf4e1ac89b8eb197ca51d44e6c78fe0d066e627ec8ba4549b6f0a72cd04ebbfe59cf1eebb1e294fc75bedce8f02c3c5ce6769e661f1af1366453ffaea09e3e2c3dfb8dcfbb9def19990f05124a4d3bd87cfa9959ca9249122ee17e94c077ba424750a2a93e1fbaeada94959aab5b77616f436a9c6f788db4ad2b58c1a6d0e684530461ea6ec69e43b661f939b7b3cb02792844409aee219971f726fc7e7d87c8991a18a7adac404bb843238f551c28e41c55f69e772ac4eb54eb212708e75e70f176c0addba40d31567ed46f177816bc26d2282a95b7ef80148e8d3b689cc47ce52f679a1435fca2503b85ef17607876b7f92923f06f74b9908c0cc0730b031422c631b3e7a26ad511bbebfe0a908d334737b839333df9933a4b779edcf934dc68fd7432a623579ebe5cd3188f5d1aab6bf1ce093dd198020139c92dba6a4850e0596c1f317978aa487884803257c71fb8e8bbfcb2566fc88572d71082992bff27a380da0e51382d7f438f4070dd56fa239e89f7e4888d5c9ed7685e2290dcdf23aa6a8c02fde2cd94903d347eb7033a77246c998f064b19f922b17855903e69d553d04a0f6713a78de139d6d1ceb4694ce727a5c27ea06a3249a2e2a559a6da78357142b20e0089356cc31a9083ae55380fa68403147bd78ece1123224cc9f5dbfa4b07e09f8bd21a114249896b4df49dae845a4cb3cbecef597936000d8be25cd19e5b88bef4a557c398606e21965771450f4a5aa10597598a6fd4bf570190c308bc353d738c6f7891c4f12ca7b93a81fbc00505211291fffb4fef73b01f66eaac5a1bd1756c0818ea8abdee109d333e504cfa007d240142d0039dc1dfde81437d3e7add96ce88716644aaa3e275acd46d28fd9dc73f345f6b3f66ab40446a8c05e06ddd0845d07bc27f6b7cad87d00408eae632c5022bc42de5f160def1dc433bb5fe55d7b31e80aaa249219f58926c5970b2ab480bb6f8faf43ca4d833d97708dd997e0897c4ffe2efcb8f305b23c2015a9f70502c42bc0e5242403c3c90272dba7f8fd2538604a5e6221148eba4566f9af324ef844bee428366f32b279beabe6d0ab7e9c63c7dfc2e59e9281e8305eb71f0391a8aab77a1fb73db179e58d100ad2b09be65dddae58f344dca000f71200e27b63e0e7f9563481aa3dd2143e80d118a5aca32c02782db66e6439c9cd38c4fba064de9bc0de147522bc701f2e00f04212ef34ed702e017b623e53f9e48fd659da91e073b3ae75d2861cb3b41a77770d3627fb83aa75a2e88907482fd8586f69c8acce86d35a3b515a06c59c3e1e16c5944bf4e1718d6261d46e2fec908b569dbee34e195b6d751e188d3c60822849889cde55ad4c92d74111e0ac2b65a50683789c797e0d99f3b8b9398b744d2cc17c366868104f1b9f9db742325431149ff2546b7a51514829021be68331d8dd47b31d3799476ea3020419016cdb2c37e6a0a96dd19bc7e99a554f2a2ca1227f5db47b7e749526c9d65fee40e3fa2d6c0a6311e014c430a0f3220de787861ea4478eae17993b2ed31ec8fdd1ca7884d399f2e31fc8c9fe12c6c38890fd6b190bfa1341e63dd6f167abe9986a0853e59c434b30c1582a5385524445e2d7beb7176add1b8ff3d1a26b790c03697bc14646b4429f71eb20ae4db9a8e6b2d5d05e33fdb4e71caa9d35f692746242f49b6ccf5d44df5fd1011df591a66e3b9ce474156ac66113d8332499af1a49a1bea188fed2952c70de93d1b7779edbcb4c5e51b5c3816a728bf0308616e637cef76fa6d26ce824c5930dcb0dc1782df32613e8c29dd89461967585c0073f204507682b1cfee09e7cd246e0259b780afbf3fb8e297b2df2898245b5ff37a816a148cb1c185a76798c4209ea1a6cb3bb71fedf267553e622a49767e724aa594ffe57652577ca2036b1429cc9e7a528185d4302b9dd17fb3ef69c66a59620a9ead454d36ba99a88a8b94b1ed22a5677ae3fba95d37a179571f3d4ff693c1de9a1a36bba217ac15f026aca5ec0e28188eaf2cc243f89bb9591c44da6b52e9d773d17112fd1eb4b9df655206a6b87a074a0c55fdf375e319590e62e4969c93b0e254a792ee253a25c9dcff4d506ed53961066aaa304f2b18d2b4f965137a6f668f9e2914cb8b78f8590d852023ef7afb80f030d5659ea1613b2dbecb41277da0def3deba0051b1f5e71c93259b7dedb64d844c61cedd5a8c377739bcf408afc50546cab65551eb3d8615e6f1a500fa5bad569a772507b12a557abbfef016b4e869298811cee56683b7be466088f2050cfbed4cd922dc70c3753a04dc841acbf06d11da42057d57e45f57fc304edf0d22a84a55d982864e8c754e09c40aa1fc9e134d83b965702b8a48bd9075ee7108fe36a68d18034e3f5bf1997d1bf273880807d32fcd6487bac28315d286ed401814f921de50198bc57604ef4674175d442492f921d0630d261ab364cb8057aaae69a6b334aa85d99754146b59264b6cf27be1b87cf894fa7fec72f3d4be3dba0344647c0dcd4fad9a0dae4e20a12947f02c2f33c9ee5110e8a83a04871538980d9dca4b797ecdc32a735a53528123741455be5d05e27968fdfdd2cb8f81e75d2d902be426a9ce9e718a14bf41134e6e93b4e3e0ae32dd9e1bd8cc355dc6c58095a4e90d8b3169fb0ca5cf2c76dd4b0cc5bc19b54665370d63e51191cf0c7863d68ebbc1f22335fbd4247b004a43ff3e5e398e32b2b5b00fe91668af55a94639d340bb836564cc5503e18b7423fbeb9204554b9b41987bbcd82afff1362d666b76baaa3beebd58451579bfe256c66c5291132383fe50a489c041394b6fa2501b98b8d9ad5cfdc79a3a7172817c0be07f91154af6125638cdfa772e5390a4014952440686d30e572d1242c68d4fa8e1e4c381e1c461badaeb023523944d8d49a3599240faa8fa8462137b89c17a6117f2b3a9114421b9a1df747148b22681bbe9617c0a6647584499be813f8474cb31fbcdca8572c269770c1fb721ed75c3be1d0bf36bccc99d533b007ba7f80ea2035f5c5587f28bc08da9185a7e80d62ae65e90440ea2df604b2fd2b747feb45eee25eeedff5ff7228f4823e108e7506d4b0b8a864065686a2fda4f6b056480ad750c41e436abef1444eb1201061109633e14a99f081d0df7ae3ed84a69f8ff8095191e9f35e7a936588e3bb6e518b1c8dd5520ad87c6662381c920a44b1368709cc922e0763574ad0f8a5e106b4ddf2cf90331013318ce0698922771336c3e778b6f92f8298132ed5519b82bc9354544ccc3d8413997b4e641973c296e87ace7f9d26cd58a13461fe902626f4972106d7c1d4b87e6d33ef76f8a840572bd0762a0273f4beb07e74e48e481c7d61f3c6c54e758dab93d8525966583625153c95e472d537b64b9a4f532a830b43043700aaa39c8a7f6e54bb1f3185200f12e9e24e31a827493a4bd3e44849844d0a617b77304829c986729ec4b6c0b0fd58e2703e6fe4d990328d6c30f0ca073a0c8da671939601005ce50221ba2c4a5375e525649a12d86d8d6b67dd4dae83d82f2d4e519410a4b7285480feb5919d995fe3bd95752841aad49190b4d809316bc0b658fc75b9979931c9eae19279e5eb6fdfca417fb9a9f6ea5efe4ef1a370e67c68f2e98a3695be1d099f8731f5aa5f5a14b534368a4c2b4c7b5b6b542709b4becfcafbb8e73e6ce0f7e61edaeed2dd28736ebedf03e8132c3cb8f3c917e0f95cb08c28b3c93fa4e1a468a4e967e8058ed173fff9bc1135503c50e7f6aa0b6a80cf1941a4bb1216db06d8a5d9387c8451f889420a5b9d78485512e496d262e2c170c11a5fb88abfe294daae3615c6234a3b443ce6b7ccf98abe848e7cde138095329f9bf2f591a577b1954dd07d5a278048dc3eb1a1ede406bb26b7b8def94b67fa1e91a49291c11225936b163cb38a1b24b1b2504ae35ed4afd2010d96493f0a2e8c791a596fad4dba977ade6be75e6b32d8592363b0936cd8f9e47c9e3a6addcfa17fce0df7b885d27a64dfd6d8b2ab779470afb259403799c9e50a4e2024d09b92b43ce18776002c5799925932a561ea3512179400436170d10ef6866a5f2cc22b97c81cb495ee77379ab21e4c2c4b5d150fe7c5327dd6bb1ddb9275bd95cbfbd760462a798f00a2787c4366d2942fa8c2a779441dfa3a40387e1b5831698a9b4148380d157dc25f9186455146cd767dbbf4b858576b03dae235e5e9bd54039e34f55a7e26c60b1ce8a51b4de9c0d82fc766a2d032de07011ea0732a90431bac4dd5abed3e11979e3ec6614d12041bdfbcbe16e01f370e9e2c3887385e0ea7f50cbc3cf6e4f2dad01c243cb7bc6267e100b6c01ab62b2f0f0367287fee581ccde8adf52ef81fa2a5eadebbdbc21888550bbbf25b110b3e8f17720570f2421e5ccb58b3c687f050a20eb9f853c9d23bc9e2a2c6a79994de238040d1dc7a0f266158d08a800eacef02346932bc301f712801f068d24a095192310014fb0d1723eff46c802169607eb100908fc48247546758569e9f109b1ef79c5c4a72cce6c4e8c841a88e3b278426c307c76872285f3a058df5271786c874c2afb1b2f8b1fc73bf7d72d1b341c6585f8f9fade1b6e5d6bdf442ae84a7de9d14c82a49bcaa16aa217dd2a1b625812c352cc0ad60a958382702c2f6903eba36872d22582cffec8d4885a1f7ea2a8c21083effbad3cf83d14b75e746c67a69c74fbe61d8c6be7e5455a24f92c671b98117011c1ac04dd9afb756cf440606a1ac349509f3bdc171f51fd526b06fa21d41a74176fc92029899cbdd311876cb419644facd1d5765fe24e06926bf1c2a3c5549383c287d49bb9340eafd09642634eef18504e04ddf0ccf62b06bfc0455f1c52e2978cbc105507d43c5d391fd6e60e243005886e9082a488c80f09564067f9329d546ce0b47435484e53bfb8a3010bdd463c8d4901bee7d2d21222f618e5714580eacbffe81fbef2739c0713a880e8ba7111650da432a158a6ce78f77644c1ecba2cf34915f50ce62d35dbc9103e0aede0442700e43935d1732f89e9816ddf920a10589d0502ff690d3e25663d2e84f49ed758909865c01fc44c568d0b8bba6e1f9bf59e56a146f1dcdf4e4d458e841d5836a6a57c2ae2e04f2f195093c09372dadf73ec1ef940daa4603f770d4ac65338e0f90cd03d758184879c4d17767de5108b0d4f22db99048f10f46f0d321284058a158a716c077093ca5215b380644d1d922e1145a7dddf716015dfd7499a381be17f4ab2ab9fae43d5db30973aaafcc5223883940d0d3b0a7833903c90904293c4b7b88c7e6a8fd734f5428f0a29c4648c418a6e0357f2ee9c883daa3135e28eca0a75e06b18b51f8081c452114027f1b64006c59047e96c200505405f048f82d108130e593f0190a0241d0178123e0b64200c7a9f2fb40a87ccf1755ebcbb25c509d595394aaae116e3932d53b538c88877f023f9863bf09f61898f179bc670cc5efec43c1f319c1ec5d8d737aac1a2286c1787242140145936f5397db79dc31f8626dfedff3101993725363c3246bbb538d2c454bd0a25cdb69cffb77f6130391dc204e6d7f6fd2bd7aaf37f649bcb48f6ebfb5533becf4bef2e5e4530a49a088f88fb1581d153fb0200621229d9f3b6f87b6d007c589f3e4b8241b5549fdeca443fbf6fdb5f3ca34f45abf8368f47e269ce28e1091d392f91a755e647abcedaf931fc270206837028b605727b0649f35f92ff79edfef7f31971628892ded4412ffe68702577d8c0a008cca2b21dbbdf19ab264a423fe5105c80880bfefb1b04635a45ceed05040a342edb74ab7298c817ec3a69fce4c638ef407b43a4c022839aa1542ef34c57aaa6f86ed755e9199adc08ec0cca900b9f9055148b23d931eb70a9594e72389e1e639be4641befb49b6283b53a6fa2b48a27bbc90dce3cb532319a6fbfbb9c132e1ed0e52ebd1d627472098d895c0fa10cebf8d7f3a3911e0e5def01884ceed6d2bd854ab07808e83942d3b37b0feeaef0f36f0a87abde0620696634cbc1466917b0d2ed67e70bfe1e5e0512cfcb27c80e8d7537cda2a9d93649fa61e2fc705df0b6ad3f520bd8faa12140f6b52398b8b0c503f11a6eda3977e60cbcbb8fa55b389668d5f6e258d8123608a591d9cd52dc24a7f7f8debf662dfe958de04526bd4a9c8c37b8c44b1b7ca7bbfff6357afdf6edf047f13988dbc4cb4641bb9003658e1c763c8fd206b96821d1281d2fee0be074c3b4b56a48850e09a8eb9c165631d805d85f6d238b847a676428dc9b04b3b742021d7dd890972be0a32abbc913558a4f417fbfcfb396dd189dde5623fecf9af4dda686518cbd2db55081fe335ad8081db398c83421cdcf230ffd96721e7d8d921092d617cac5a8bf4dc3380c0d83ae6f739318fe3936f4f021b45bf1a112c9167a48a04079b8422ab4e70bece405eed0cc0626fc4bb1e5a1cfedd31c74abfc41b64ec5b6a9419a002fd88959e72a605ed59206ae936b79ec0a9743f981e128e05f709a83521d048c3fc2fd7f9607042476c06ef5fabc2332c583f68de3c4bd884da90996ff53012dbd7e66ed4ae4ca24c7e5461b4221fc0e77752678ee914ec852658452bac7c7320bcd5c11f32b09bdcfa49f714cacb9a2076eaed96c46b1b95806c65e154516a08832d769fea12ed0504340d1f8799d77757b00f3a568fc137fd06346bde0d06ae1f50856b6c0d79c1ae930ae85c9b9ce4d3d83f6bed66187a6a6703346b27bfd6bbcb0daa3801ea1cec5af7b85a76c7ba94a3752ff3d6083e5f4ced826068453092c869d74847ed76ff96f6b8258b2f752d1041431af1d6847f51ad9911ced22d2c5ba38aa970f63c7bc28aa647caacf1c0db3a612a35d5fa9b74efc917b9cd8d727ecda576f0fcd0f92fe45e0be1fbcdeebf68f460d09511647b6448a2a791fe7dd2a392defcb3d7e461f68b0b42ad999d645549e2411ffba6f120fbac7af9c24e72d818e96a8884a75b72eff36d0e081ae6b030de0c3037d1fa22e8a99b29616cc7924b0319346d756f3ce9eb78f62d9fb0a6319845546cff6f395d16e19e0f370241eabb1cc453170fdeafbe019f0130e28814890900510e15cc9d3bf124b28bd4eb0018a8aad49acc320cf08d58f7d32f486ca56ffaa2c83b4b394cd1d5d9e39cb303ea2de75b2a3f5c7e3261a345f2a3919d2ebb0e1a873e9a76d568bcf8eee7649b5973acf71bfea9e1be04c6e36f387bdbcfb65e5efe2d1a73c70e79c9569724cc3fcbd7ced46ca039b2140a46307f86a172b558eac56da7922d72378db41c14ba8b0d02f70c436c623a01828d724342d000c5d6dc9e70f40013035e4d1c9a5559ef39bea42c5cdc001c6e735390bb9370ec05e99fd364b6b5068be035e66edbaa08b38db961fa35dbd2ab754f1f8dae74c8d07cd36520447eb6d52e78981a22dde389761af78e72d7bfa7ede68cc5cd4f175b5b6817119e9db4c71f2d4298bcd5223f37d85d72e5dedd8466ce5c0679e85ec930bc8da509be3654062107f0dbd5789fed93a1e2ff2398ad48317786135b7b10d3b8081bf62b3bf4e26f338bc7a38b8727e083d4ab3839bf8427135efd0d82895bbc9f9e5397b117beabcbdd403fc0c9f13006e2a699819352b73a9627ece9df07df6b4e93e8f8a3bb9ed5baff4915f9987dacf7a409549f77ba0f8e58b5354921994b0bf061a32ed41e3dfcf4d534caac4356eccae07c98a338af9245ae7e1addd5dd6c0c534431875f09cb334c344966eb19066d6b150b8198f40a6d66afe827fcf8cef3f3c3b2f3a31c992e11fc1fab3f4d802e4071ff7f1dda64cf41867347a4d25d98fd5557a34bfd07b77efd1796d92ffbf28bcdfbd3b8ebb38ae393879a8c44d60b8de1095c7462fdf67077bacc80117e78ad2c4cc86081f24fcfd1238d23d1d018e194e50f287ed4bcbc51aa836cff51729b9a019fba6ebdf979dfb7656ac33a5ebf12f9524af9d68ee00f30a1500ab688edcf2e495afaf7504a8e340e5bd8f9de33878933691f04c72feced32bbe92e301d7550b2217708cffbbe632094beb5a9bf23cfdbf5aff71d9d8556ecfefbd326ded2d331e1ad67f25f966e604c89445dd307500817c9c5f9d5fab86d44fa7ed2737c43d3782e759da40ad9e8418cbcfe50cf36ce4de3e479a4ac5e23c690b73b69f0d27c5983ba79f37d4d97ac3dcca5ed0dfcf65f11b6a7862decce17ed67689dda38a4df71563a9329991b1db0887b720a30f28a74ece4e1a96ccac07988f462854e67f9380c242ef09a8efe95a6be4c02706f8a767765b67a7bad4e8bb479482e163264db6521dbaf70ed6bff9820e3c1f850a06e9b37b2eb3ef95956ca3677c0c84ff827c61cd492197eb3d2e0f8910ffc73639e9fcc2978aacdbf453d307f5788445cd48e339a0bcd69e4693bbb078598b3370fc9a99ebcf8217dcbaf1e6df993c58d2450c4747bc637db8d7d6fda1a3beccc7b28ebc28e5638da8f94a997f6aae02aee6953dac94fe83a89800394ca57f00c68c63dadfc0476428cd7ae79ae796f362a21dacbeafe232d827ea9b47b8d88cb18f9ce661148c2dcb03e7aa80730bbf4b12c9b38e77db696593c1b9916ef633bd1bf164a9e70353d5d73bf6d4ba405b24700917aaa4076f11d10e9ef21268077620b0c9fd4075feb4d4c298bd85913b18d854e909371b241a2b4cb7b6bade9fb7fbc8dcb67f631955134197d30f13b7a8fae12b7307426f55ffe6c477d4bf90aa352da52402608ad4e91f14d414b0fda2cb9567643c8f6994a6a0d1704e7bbb38e9007cb4c52748e7b575960bd572b1cd338a900d8dc2f4b95a2029c99579767e666e17b98f2ae3e438f99c46a420404039b0d0cc2f151fd5359a1ce6290169a330b9f5c73b7fc705ec9831fe2b949869b74ec5e83adc3101a7a6886a72fe89a748eee48d4f270dd00f1e982fbe3da37c00062b8bccfef710dc6a81764d66f5d05906166d02da05b041e7fd4e890bd5fb63d717f5e03f6f8271f8e32fa46e753c073c878a0fc3d2041f4b1233e3f077e6e22b99265becd40641939794df96b46c6c78810fc45949b6054245f53ca2f71e2dc14f97b32007710852f07930f0b2cd3403c85cfb83e58e073719f05728cedd3bb2b3d194f96d3f0bdaac5fc67f3d782be3c84c7fba177c6ca61f307addeaf3931510fc45d31b9504ecb6baa7c2dcfe637eebd6d6203863f3fe95d91a5010dae7950ff0b6c14675dd77f8e2bd9b37f727af36be013e00dfcec11044c80db44288f783507e69878ce26e0a9c6e29ad5c8e0a181ee9ed023824f12035680596bb80758461dd835597f05605ef5756d2edfb8f1ede05351ffbc2752e1f3b016ad321a362abcd28e251fcbb663d246d1402f1e10990faf604733ddf1ecae3a66649f9dd3a1b9ffbd132d4bb1af11ccc174106ba9df3dff558c03489ec2446776e2e52190492e3677fbc865a4dec930d5a14d185ab7bc0284581a4b32fae82e1b384cce3317d7e8409fcc7201be49c10488e2288694d029070641f57a0f8142b559a5622366c4f91f8f41f315b905711d18a3fb5b310107d5d000dcd57a641e325a7aaa283301becbaee001fd693f2626aed0ddd517d39048fc4abb656841ed2e669be012e160271bb44479cd8dfc83ab08427e845060c849ad94db01bd5156a8de42c76f236cf1920e56b8f7798c61d1fef9a4588147b4dad0d3ddff7afc5bb02f4157ca808cfff53b0dd86a760db56cac048ee18e3b6d0887aec975dcf9c1aed39ba265ed82ae74101403e46784fc96f9a7af9444a96897f7a73e2d8718abfd497ec01215e841d0055022443f0d9ad14a05e097e12380a508e89c760496aa059c03ef8104419aa3f0292f801b06e1fe6c81832d45054316c108901d5428463b35885f8a87009726a1b3da1078ea367016ac071a0c6b8ec45154b369f48538087e65089ea88a4147eed153813ae0dca8722812429f1ac56f4503e04bf3d041650468761b3a8b56ab3bec12c0960d73d3631b9cb6c664d30b1b886b0d58535a0d22d42a2890e4319a7e519017c1c320f662000729470184aac04f835c000b8bd09f0518a0141500be123e0562210c7c927e0640a012f420f00ab85408c571e0097a9900c0cba003402be2d02329c68025ea6107c2780a84746a541044e419b9839fe63a538285e0e1ce0686965d0606c5e0fa05f9317632c0ec2a7f75871c9b7ff2569e640323df9a8af16a107e6382da6bfc5e59533c6f14cf03a726b0ff07358e2aa814a3fc686a78aef0fcaed790115fa9eb3264d024e82abd80d0f7a74acc55c46a8cdb3c593cf95fd08a08b6a659bf02883543418f71d2607bae5fed5207952f5efb13ee73cf597cb8c58306bb16d4c7f59d25c23ced72a6bd04b0ed87e2534233e2aaacdb04e9c0c77ff20235d952b1d9e69c8c7abbcad49ee595ead1070fbc4a5938bc1bb1b4ed431185dcd7b92dfb8d04d4fd0ebfc0aff2f17faee4309abdd8b1e6f2a995208112a5ba83d4fda2cd1491bc9e199bdca4223522bb30dbe198844c06cac52414a4b7f46c8bb4e74c9d4fc90483b53c348ffcf9ed89ff800ac23b2957a711031638dd9b21751184fb3b7c3924499647a200cf88888c0ecda7d14584a7eb5dbc02d64bcfc80eaa3d826c087a67e6c4885939f74f23cd37808172a3ae9b79134c040da756d6da5372617d339c8b5fcece04e71cd32cf605d7cb84fd98a3f5e10c6c844207c88f2ed27e838d0d243af92d9e7aae63909863d5493ce0e0ef45560a0eb9ae0243cbc74da16d171fdd1bee46a23f07c300cb726c60dfc23e271b623f185f69049fcd4921e50defe37c4ca777bccc8174fe80797015468743fa65c57f7937ed54214694d69028287e7c7552e976a23bd5c7c46f7f52d1b3473333e136048abd60f26d47fb65d7ed16814a7eeec98e44274b5229637ab96295fe0a579da178f26631d94cb012e0e1fa37facc309a51be243292a89d42412680d2329ec4b901747be18fbbb520f9a4f6e6c181881ddc5bd3f773658c9a160475b9b520a8cc5fc6c470c55e364bfe6263a07d44c8a7ed0a045456992116ba411c0caca952377d9b58b27d6758c597cc89ee5835f0848af387dbc8de13765e2dadce1fc03fec394a6d5b747e8040e08010ecb31ca4db31c7d985e7d054823549a65cee90d3d9afd64ff61b97eb7c079fc453a55c4d97379bc2bea3b92c6496a265b012a3d3f35ab0fb09ba21d3422896065612374eaa805ccc417c6c2f8673b9c33c8b5717b042d9e0db14b067ca06c4ad2f42c00ca7ee9faf87c7b6ec446f8243a85f218f4fbf77410c8d741d3b0463046684fb8e1af3991fa593d1693ac60d1ad5ab4e6b0982a20abb6796f9acd62ddc323d76d24f03bd37cec28fbda6430494361afd74eb3dac0c9bb227509b51b0cd5e4a4303844c641e091976e1bdf707114ecfcf919b0688038a73a1cfa39c050a4a4fe8c1403fefde5d778714353f6dc412f727c54a2c4a9180944588883af17b55f6644e906f7c5de55da56464604c647175a2e28a290ce1b284090410b8f990650c1da1771adf39d831f2cede93121b745fcb95b8a20d428103ff73d7578cd646f65c869b86b44f0b2be734b387fcb464ba2c3fc7b59f8cb0eff2c6e92e05f6cf5dcb173df2c5084ee3ca5606abf07222b057178d722d3fbe2e4f20e277b6f9fb3b4de253680b48adb05a8bde23242d1270b66989e17f365d2d3cbf7c22a1b11b52d023f1ea4119fb2aeefebe5bf7b7d949e8abd0e57048dee8979fa8f9a923689f5399e2691e91dfda84737a3957358218f174566e5a07857b28656c36f624bf23d917ecdb60b68fa859d0b0fcaf511b0deb591e137131542ccb16436dbbf1a3aacd7008e7237e8d58f9d026932331668169d0ddf7ab3a9c8d73c1fd305e7513bc7f12f2182f9b58bfdc36a56c6a6679f3ad8875b493b2c5bec3aa380788a6d35c4ce754139e74f2627efcba8b838ac86085f3ef65b12df9145656a71bef88ecca6ee97a02b79e2691e0e9036be9d6a3b10441186b426bd7338ce79e806733087a7b8f37e22bfb3247ba09399f6d696c85bea41b139e723c22f918fab56ac96075961f4b787ac1d0a70bee3736d882b95246ed29075db870b758b8e07e7e87e6c4852e3d9b45ead8383c59a08ac2851b403557dbc3b2237556e211e8f50affd7bd30c6203da89b9f022e4b267c037bbd31ca665aed9eb1ac7dc62769928695cf1556b17d16ed57ecf342bd483fff7f4e80db65973fda918844498c6fd469154941a01bb734d950e6b590129eb34626b0e3b2f6079ab3c41d6ce99f330d782e76499e23e6fdc18535eb0889759789ddd6b3a7af0a74a96a5fb37bd9a3c6c040ab2441f66a4d0ed1f14d1dedfc59af3f68d8fc3c98596a859033d7bd3c167e4313fb2d12dfbcc7067cd2308f02e73bbed98e412059b129f5a73fc6f8785c1708654789d09d28b46c747021d65f592833b9a7768bf3e2df0a397128f9f090b3011f5b5eedf548c04acca020192319d4c3f77d95bfd5f82ef1b2f96b37471c2fa0dc4a5a7c1cfa211e378e223b495d0f9cab91d85a2614bd1d6614dee8a37d496ab68538f788d73d28c9cc3cb1eed4a5b76b0f335c9fca8e68f37ea8d85783e7a935248f05bea224f1522797347a3ce77445a3434d5dde8e119f4fda400cbb37d91e6fd6bdbb6cbd441afaa21c1149b5fd04fedc83bcd6caf7b9421462761a29187c126ec80c68bf04de83339e9ab521fa28750d5bd8add669ba83d2fdec9a1e0cd0823f263e7fa25a3ae301731c4aa98bdea0d95bd4eae0e0630b73f315007b71ef4ef344d7ad62ff171abaaef55dfc82abf1d53f136cb859108cfab933036d3372c1e5b90d022737efe30c27f2e1c833fa655ee2ad5fa0cbfcf895e60dab807e3fcd3861b0467e3fb6f007658af778aa47a66a5b18ef77897989756c0c99461874aea86bce804951f47f86cc8e33da5f59f5565806f18066aff4ceeeccadb52e530849838132516863e7c44c6138f4f428cefd85952202805b7df9cff2fc7a9d502ffa304763b8ff578efc5ab580dc98b4b99fe5f090ec1c58983e340075253120916f10e6e496142b1f4d6627434094f714a40fcf3f8524de0ad21584b982bc3d74b3982e3c0647d59b471906934a7820f8e65a48214948b4b3f818b5af0b451b610cfac76ca0c2d301aa7eb92000a894793a057eb8020883764718075e0e2761cb025eb0b91f5387eb73c44c800038c182604cc096949dd392206804592ece1bf5ce6f195d83aeff758142cb7a8d19bccc43b4366ab5834638b1db7c2ba783db2722bc811b1ca4c61d0b14deb70fc48d8ab896aa50a18581f37c38dd83254d6510694daa145052c84b78a53020b2117168deaf9663b72d18e6adc5306662f1005a338e0a04020598bdf848894e1687f356fad082532d63e4fda3f5c796c836a1f2441b498767a52cae94b92207349714b4a7d3693e8b6f0bf7fab665db7ddb0fdab158c4141a7781c8a4c672f57838789925b812de7aee0f053ca88232ad582e98af92bac36f3943110cf07f96c288d3df98d8c515a29777ecc10c7aa2b976b2490df83365a5ddbf1994991e9ce856958325024411c9f7f78d02b50bfd9e73038af47bb1480d10c2729cea20cf4df1979f3535f3ba04f0b63c352d62230ee4e14c7032078026815295d3f8014b0bd366f2bbf5f91101fb2388f7217b072e05b41f270739d5bef78bb8a304265a9f32b153feb9dbf16be6046a15c917445ee7eb310b12b2871e156e0fc5d0c9c26b6e571caf9618b819e42c33a82b583fbb44c150ff333737b1439479f12b51352983688060b7c29cc260962432318a955ef92b2e811b2e89f6e75f20e1708b2022414f8aa5995ea3a15b13c25c6d4fe327aa2654b2798670ea8752c07fb5a47c6592fc2bf5a9a52487afcf7a2051d3bd53ff175527a0015553335aca6c03c55c177d9f94c88dbcfd3efee47bee84d36bb7c282354c7c0f84716084b9e754c3ebee02f4d027df4b08131311d05504f5c426d961a24e72e3b8bc0d717f6c3dd9a3991bfa53f4f2c448a18701cef19701eef285d252a85edbdb9b502b196ea77c5a95567de216097a34ceae75aef3c1360a84a8244381cdf0edfa73b0fc76958cbc2394c54c11417bf479dd86577dd4a8cf4bb52848bb9997263cca7bc9dd599ccee7d0aec1adfad55aae38cd38c5eb40dec8ae2518f48851129d3d7e38bd9d767b43158c59e3bdceec2fb327857ca6780227ff3937d393272989a76592999db2ac03af9261a0ae45b6627872a0e528b6ce6921c0e974678d446887821ae38a8798bcba3706348f6f0ab780b6c77f50f2dfb0ffdd2874a30b7a3ea6cfe38b75105bb34a7fbf39b482e9fb1eeeb700bd1a9316f6acdcd3ebaf6349460924e130856920974aa020d9c6295f11a6b1ed8dedfade8653233aebe4e2c0399aeef49169b6da154cc2471694a613ef60bf7cfbfd636dd1da0b5bb05b259715543710af6126a5242a6f7d4d7e324887a08578eaea5c8938cab7c5e3e4317122e7d08aade36614c01a1f5d36a54ba51abb90a433add9247384874e7d2e001d86c6600b85b2343b0f690e310b825873f91141921d9ca266afe4c63a244023150a57accce96947b3bcd3ac39125d4ee472e75bc831becc3c7c651aba627976598f72bb75834ab83ec878012c8355abe7ad876a2ef96c630fad84c96f710c1ae2f59613d3c52d38f054667c0d2a9259ba576ca828c9fd632178973386afb6e845c8a2e1e3194de0af1a16a4b04349c9d154a4ca39a3f8d8c4f138249e41d77179a99da09f9e4a73b64041ac16073472fdfa89d1ce7c15946595f0db3f9972484f290a9a91fdffbc3c114eeea4106dd91b89bb5f5f99d01323385ed83d1b5693733a5140b06d832977036e2d03721c7002425845bdff156c3f519d064e1df2873fe72a8375a50c6d5bf290249fa7a4dea6b840f0a6fd5a8536ab3238eea474bf74ced4771158a8c7ba1f32401cb8481bb0fe5d259a16abffea8f8ff1d15b0542122741c32088b7a705e96178daf28379ee9bc3df3ebf3e624962493f6750cf6e82009636c24f15cfdaa8620dbd945875664ce01688cbf318c2500003e9246495a6cbe91a0e0a41399b4d9d6c26509ace35442075ccf188d29dae494da195ae388e3a6c8bb29a1dba574a1130845468714d97f91b879ee28d2768960740bd49d43c94b0c002e93a52e3d07e0a447b26193ebffe2a10ebe0cd518d0221a2b118b3f6c170b0f185780880d7a2f324ae4e207539dbeb167b6b840281de93191ddc5d9afd9ea50838d8c3c64671047c4fad289d5b887889ab42e50731d16cdc1e630742bb263bffa6a56cd445766b26d1f7640f296a4bbac8404307cca0176f0a51f0d8983a0a7560f38095ae39b4f588d0de5ab01f419454c424064854ca8845cd36464300e277180bc74933ff8b15e0d4d47344e9dac182d686a1b4390bbb68b10ad57394f83cd480c4c50510e3c9038f56bd4d061e82b60681ee5fcf9a1c722a4e39c7c01d7ace184b9f2c38c8bfd5bd99682ffe893650c6c0f83178d0064a1bc2ee449e3ef2e71490b473eb80ec1efb95d5c3ccbf35fc755932528351aa6c50b38c9ae5eb58e8fa009b47be9279b5c3fd1bad68b95960841197d52ae062e0c4c865701f26f59ffb419571fc3c056cc86dc3607e4caf2406fd5154c064118a3482dca517d7dc6f6aca2acbeca999a3e5ad0df9cde74b1e9e728791ac3180d9fbe230c5ce73ebc309d457532e1d519a20b491f3c8282cfe2ca6765117c4c92de8c08ff9fd11abd2e5e1b186dcec0372c8ef3d1542a7cfc6a5344b15a56d13b06fbbcf2a8f9d416c98ddfe04d96a5d9f90d8c8e9e575d51b772302c41b3b16b9bc2ee9f198863f8f5ba4cd7603135dbc961cebfb34b473ed4a367666adf2b07e44ab1fcb10d4217441fbabea1eb428bc7045f6f1ea9e8aff9db94971de26ff960e4226aa964e21b8804d5536cdb05ba46acf8689183624aa0ef0465c2b963d1ee809379cbfd6a98831af3dd9d6629e3de56a084277f132b67c17717f385cda1566f9dd9a1daa4def780d35d5827dc0f6ac2639ab78f7f08a480cf18bef0e69e34ffe825884ea2a015fa5c5ad34d1f623f0822e9a15450cc367dd8078bd75b71002969a06734dbc47bf613a4c744006e8aa37e01eae1070041b5a119fc7e0d822e04660b21a16af8c077c59ec97d98f75350bcdbeaa548c3006da4ef08918ab26f16e1f8bb5bb88638a899514e0009573196ffbf510dd0981f02c61c569aafba7f025602aaaf5cbe2c517cef47a219feb00eb44119ba8b2df1109b655f1eeba3c4e84b80acbb2c7b5df040c303f311758e22928fa2ade189f1eb232d4940ac1361d17645ddee7fd893a1095d866e0d461201c08d3562b32ec556b2bbc2fb01db08c671fd1ff9a64d33d082889a0ad53af4d7ac0f02e01fc703a1baa7145e2e9123f903c5a185fe094233cf3bf18521960b11932b412bde418e556843bab311212ae173b6f0c1e11e2192d86b89ebe484df2f7c56215151f96afee4eef2eb1f225af644dc578821727cb4f28e19046c0a0d1be5c1bfa490fccb8ae8478d2bc424df6d34d6dc9ed8387d41702720df1c382c0d44bc3fd423cd4be54281e88a6da4192870c43c1f2eb7fa711fa4442a66543ac4f81e90483fc3b1bc5351c11557552732ebb7ef0f4022ac4d17ad720b3975029daf394684242fe4a03e94421608fecd0e51a0ecaa5d6e82e08485352503305ac470adc8bc90cae9820008abd270a8368871721f487a6c518603b7a5adf1d1111b2fb34196d35286003d19dd652f979e137d79ac4932d541ab2a0fc05855515bf0a132f6d5b1a3d2a21bdba1e6732c65f9d5304853839942e90184c5993db5c4a4a22a86cd323a89becb26a4a6de39b09ee77b798957db5a105c1212514d3470fe412d64979222d99c49e6114b1b7bbcf2a2531f5cded156292d56923e7e673d475040bdfc54c4b6b95bbeff75c686dc8f57d2cb7d496686e896635a38627e83c8de3abc2787f0b4e801ffbfbcf33628e816baed975bf2c60987ea3c6babc4c723c0f9c8cb91514deee9e6947246f573e4c7d773901e0ef79f91796abd65e0d5e732a3dff06a842fb4d2c9d8b8a7629190936c3fcf779fcf36f1451d55ca24194ce40fb5472cfb10cd51c69dd9a17dbb6ea4e1ed34f668e20ddb50a1142bfe01bba81df274a3855aaac96101f9ca0ddff95549483c6ff9abf6960f94b206c1caca4d548acd3f97588806bbf432f73147ea954bbd07f457292bca089de8040a0e69277e07e69a1cc9af124c1ec0318372cb8045ec7a52953c346eb8728acb1e2117f7c85401f322834281cfb982f24af2431b447f5f27ee579185f6e7c6e34579bb27d13f53b894f9fd9933ed8d1318bb06ccda1221af2236f9ef573e7ad68d6309f988a028ff863c275386c425812a24e18a80b07df91b9a316228a92e2da39a3e9f48213cadf61dc25647a16a2141a996a69d755cc458c321aba0c748f666112400d473d4c11617e70765e82832b0869b98528a33ef8ace312d96f3f4d25be8f2c1c66d07dbebfede4169174be0153cbfd9568eaac4f9ba065095786a87e5004f07fc71c085e2f5740698113dcef8f07738ebfed0dd736530d3931dccadcb8b772f68be37aac959a3ce9dff6180941f5ce55665c13324a037185720764f3737eb0030294b91ce519c05f3467e76f8164cb9dbd287e034b6b012f93816d221ad8f9cd68a03c2e3a8ca14bd40d79bdb1842e52e7cd094c7f329efe79e02981def22f01d1117b0650b0e04a2be79ff942c6bc751309fe40f7b7fbade6585f772c617e3c890c2ec07f980b16027a68b46c926bc8f205613c4f1acd35dc9158a4a6b136f745cda0901ea9624cfec022b8f034c55ce20a3b1a3badaefdd5e2602731221807a2ef05c68dfca3c88caeaeced8f973ea59828fc9b50b997d27caab83936ffac0de02e87b25b9e1133f12a90341c7df3917d21fa7ad18eb89f7b300c75f10fd712e6fb217bf36d3be4d8cab614ea644c026ec29e7f087a1e93e19bfd371fc035c519289a1004ba914541b5ff63d786680f7da09139a6397226c705bc6279239afba6c7f53a6416556abe8680eefddefe3795158054039e9c1095d23da86022bc49b07d2abfd97a94574d6626026768b21148068095d7669e8031e242a81582f8597aaf31325312bc2b8c975020743d0e22c9c47846e3c42d65d4bc26580d1ce392f629568d11d74456aa8609ddb3af0d9f72be147300a636c97b0aff027282e449361fb3c3d5f8f26978d09eaddcfd37327684eedda2970b8bc13608a547a473fefa09a2b242cfe106b75ce3c64e6c34cf1c6ca183c19e4fb92e146f50496a4764c443b8e7b2365b7a912454b50b02015d7244a0f66c5bbaa9a50377524a0b11e4a349f3720c2f4ad2e64a749d66aa9ba0eda7194e4bcd391ebc9977855fe2c9cdf41bc630180861a18f308727dcbd0d39b976d37a1dfdf904f77a6a40ed06dcd94738374389027d9018b1943c5b713325dc8b5de53e34db7cf819a55ea2fa52b11b05b2f06f31084fc2a47a1f72d6098df6e66181ceb8d40135120f055587a63e793dc731c3439b83d9ad34d6c234c11b5e368983e17679ed558a5ea7a3ad33d5b9ebe300778ef8b8f578f6e30d7363c82cd9f03e1d780087c4c182d72e0319acec822f2de2a32f24733f57413f6d2e693c7de3ded77e48fed03176d03e7629c68662b9410a68580f98cd4c0ade2e5d113f8d72379e78ac6038525b81ef549b934f85e4714425e6990805ea4f011f7e62088d86b11de93ae990135dfda335ba4d6899d2cecc2c7cd8497f5226b3c59af0264c537de2ee237fdaa594788747be37d35c4755475ddb9647ceb63b2066cd4ffd3500d1c8e023e5a04220b3760b46439330f0f0f0f0f0f0f0f6f4337426a6b9f908494a454aa976679010b644a29c99492d81dbc0b9c99f87466e2d31546ea6e34e301030b400bd30a89305b754ea153cc4f17311a1a68000d2d6854c0c3c3c3c38bbc0c801a6220c214d28388d6a39e7fa625c4388441e5edce5e2284ea63058d011b39ba7880214c3a9430d9298b440b63214c21c78b914df249fe104218b465c813f91f15620cc21871e5d6434b9e902208e3440b19a91e545479a2102310264f59952647c8881a157284188030a5e777f438191b6b016084187f30968e74f988efb9a75363edc3c647c12d4b31fc6076b7083243e465059386161e1e5588d1077357fef6f47137b6f3c1246eb35ebf9237ddefa26b68c06fd898c0923d186de28afa0e1d3d18e5524823aa3a7f3a711ecc5f174b955267a972120f8697a4b4aca5548086062440e3430b1ced12c891000b2851438c3b986ad27c74ad38f91b362002e50a31ec60be0c7df524667bb5af83792dced6c4fa09da6e3a986e5baf64752ef5e139183ce54ccb19a24a29530e26a13e57b8a89e9519d6588b83f14a456cb56ec2292538184ef6d8c9d0918488a637e8337975ebd52f23c70d068ba6bba5847e25f3b4c1782a629b087a7ad5c26c3089f59c82129d3c8851d7b09af89d69d3fbfb5d0dc6d1f62762470e252d4d83316757528ea8f360123418f542e3924e6a44fcc81990e79e7592d8f0dd88194c9fafebc329b7951896c134e23e23f4d998fa6430bd560e39a29ab0a4d9180cf2246987284292490cc69ff9d4ad94264cbe6aac1962848131b7d9ae20536c3430587a3cfe09791771fc4272a23de2880929cfc70bac96e87069d927e98241e9d349674cdc0575e242a272ac3d2f919152bd05decf52e447e9f4c1d58229e72454dae4f24ad5c982c94212972fe88d05632491b3d1174a4551b942fa4b5e9f1a1d4f3c620593aafcdb1dea296787ab7067cb13dbd46a6255a86052e6de267ddd4444740c31a660c48e14d192429784670d0f69267f4761d7125365924b5ff250307b2425df15c2ab3e9e438c2798cebc4dc6e52427182d8f7c87f2fc155d9b9078710fa1cc4c2618c4b6044942924b583ca565c7d2217fca9460b6317defb0d57f214930f68dca496adb87f00e09c6f8ddf3aaba54b61ec1d319b135ce74881ac1b8af27c1be6e2c9f72885104e34a34a14f45869e1e11c1f41971d72dd4ef538660505921e5e715e517662198aeb346eaa80ad2471b0483e855f79c4a6dafac4030999950b9206db573f20786f7efec4e7af744f6d00d317c9068a6965ddd0373b6c41c939397fb82789007af3e53a24f9c24edc024277c56ba206f92b6bbd8a26f241d982aabd675f47091d29203c385e7eb087bed220eec0992b642527cd9788353fc38ba446f820ef2183630ba6e8b6e0fdb298aceb521460d887f49f4d7280f950662d0c02ce6e13cae598ae4a1b121c60c8c5e3aa5a81246eeb8be8618323069cefde76f856889c5ba9f53d8cfb8d2aec1c254276ef73aebe597e91546fdb4edca31b57923572073ca9369bf4a2b0ce7a3d4bdc8c6fdafac30dba7baec905ff2bd5d85c1e444e5e6fcc83cb9c69aab1960a8a2ce761792852429684f2accf196653997fa4d1f15261d523855d7317e3a790a5418cd4ea2744c5863ad057e820fbe1b3672743185b13b6591f11c23966534b4a0d1821c5ea0c07451011a5ab4e0867f71011a5ad0f02fb8680ed0d082c69db5c02340430b1a76c39d8bf3850d2df3a251f05186c3bd68418e2c85414f7a0f8b2426297b5218d56cd4a8f4f1f6b98fc26c71d2cf88c5eed35714c6d1e965fdc36fe49ba130cfbc8f288f7b8b2c0185617f3b4be7fe2cefdf270c2e4946d0155a3ea25d066078c2a04694e5ed59d7fabf138614f22c56a8958afbd4d80d745181b3829c30870d911e72e6dfb29cb809e39c271d99a142de4713c60a7af1223444ae8533611049c5fbe69e4bae0f26cc79c5de7b74ec10a2a55cc214b45c4d9f92aa22644b98d46595136182ead866f2004625d491cfcfbb1682caa2008312e6a0ad2797ee5b93e1f8c8f3008c49186553c4055d59e46449560086244c1529ec8e55d808ebdcb8617608302261aaca9ef8b521b2eef187106040c21ce36e73278b47109700c6234c6d415f0e42bba9578405301c61d8b7aa2ecfb7b0145463edc373d8e8e24e05301a81d0191e3e6444790306238c713f62d1e43efdeb35d63e10522f602cc2544ae26e9b84a4425a6aacdd5084a954764cf86c3fe67101231126b566592e665c46b4cb4c000311066d912da851fe41a4d3210c7f61db43caa5b9681bc29ce467e498a51121a51026e5392e9f449810061d134a790ae141982508f5385721447a2c087329ebef2f5526e60f8439ce6ea464656af2640161b69446be8d8a21e4db3f987d62593c6bedec52fac164f7df69fb631f4c29ee6e65adff50b3f1c16049eb6666df8331458ed029b2249f5735d672b4e0468eb5ced10bf0e223c905400c30f460bcb4f1faa542ce53aac6da471d0d0d34600b30f2600a6982a4b82e5539b45c808107f3e81e93b318dec1a4646858588a4e96658db5b230ec608e26579b3ee26bfe5263ed830d07a30047af200130ea60103bc23f8556eef5c3737c617e18061dcca924a54fd2f75ba489e760d64939b722bf65cfa68f3b030c399844d8a9a0ff63df851e0783a58689a7541bfe4170308e507955fa6358f5fd06931033ea6bb47583f1d37376b8d5aff8691b0c1adace3e87f8b922369882fa4cf652daf4e49035183f2d98fb051d6f92d460887339fc7327b17f8d8b56810d143806baa8000d2d380dc6d02222d553f5ee99ae00030da60cfbf57af7703eca3318b7be26ee8752224c806106f359678b37eea9457b30ca60f2cad24946844f8da700830c067517ed83b4fe1ba53406e35ad871cbd7e3b29be50186188c5a5b37bbb67d9d3d6130a7883fda42ae99f6d100030ca6b3b00b914275ea60d6ad01c6174c9d5444af9b8100c30b86f43b97bffd9e7459140230ba60ead4157ac7cc264b8ad987a11c6070c16cdd162752f27a1bf9ecc3181030b6609049317a1e52d809920f3336c0d08229291d26ce8fc9979033fb30b5d2038c2c98f7e2f3558a8afee75830a8591ecb3d59bd3aba82f9564b7b5cefaaf3b582e9b7a3e413d23e29bd55c1f4c9b28856bb5890142a9854e764a53fbedbea3b05534d2c7d77aae2e7775230a9a06137d2ac24465214cc7d1a298d2c75d9520e0a062d93b297d095ad84fb0473cdbfa5abb658eed209c6d5b4504ae57cf6779a601a6d61ff45878ffa2513cc6992b07fff8ba054b40453ec0bd3d6ad3fd7394a30789add480bffd22d4a8239e8a860fb5fc94bad483089bb9f117942749b0fc6114c49d99a488bb011cc11840a2a87d1cfdeba08e6ad6c6a3f4b53438f4430682f11b211f3b22f3f028c21984bece28afc8a259df6f0281f8607430826ed4eb1743e136f97140473bea978d6ecd68d05046377f5886b7a7779f903c3488ab964a13a9f181d0e183e30d75d075d6e32ebb33d30a8b9a43d7479903d2e0fcc155ad41d988358499ae02af945d68131e4c73ebb49c2c4e57360743b717eee57a16aa3a1c58729da62cb60e0c03c16225cccc98c8ad40dcc1f44880525f46f5ad60626fd172d47dc9aeceed6c0702a3e8a4eb942be80410393b4483fc2d7e4251d9d81712b23a7bbd58fd41d18323005a97dff932a8ec9321646d3f2380f23a2bb25b0305df06afbbbecc9f4fb0ac39f4732f3d167b1a72b4c62a284dc5e893551d28286161690000d6d81005a6148fadada644c92159b5ab10b08801546133af5abd492d72cf00f1b393ef05620805598c2da6ee7d3a810376320005518e4e7be3ecd5f68130502488529ed2911adb40987aba09d8b0f1514c31b282801a0c294ee84ae4bcb97eb2fd2028f403985c9c427ff298b98c26057396e9696ecd1430194c2204566e6ff7bc8be202408801426f7bc6769a476d2e6e8628b1a450b0f8f519852f8879265f7a62ca6111e672887243dfc2e5f7c7461833b8ad0610643d075973fa67a8f9a40e828c32a2a3ce928a24e7e51e82003ee5f5e9d25954612133ac65072bb2f31cdd0b30b5c4287188ebb9611cf7f346ec1f9280b833945131efaf24608dae90083214938713af79c599a5f3086e917ada2aae7c4e4dbb9282fe8f082e15bc4bd54e42e186f74ce695ebac73fc905d3e4169973514af6f45174d0b105939294f522df4d9cc65a3098ce2945c991d4a0230ba634f12a4b92e4ce6663c114f428a56c47f44b7e35d6cc6868a0010ea0a10109d0f0f09041c7158c3a493c699153b357a264830225d9e8a2eca4a500083e0cd0610573a4ac9491a89d26710f0f5e0a80e003004ee8a8824175ca972e6fc99354d5584bbbe139fc861766aa800e2a18640979f398e421775330a470175b17b4e335cd8f0e29184777ac185636f3f9d482060768584002343aa260f41c6f25c5fd8ad599066868416303342c20011a1d502874864861fccc3f5d634d058e010f0f15f80db41b39bcc043c7138cdf5f51527f36d52d7e31812e18e0e1e1e18163071d4e309c502297969610c4af8e269844926a0f777e6a7eac4da18309e6642a590ea1b53721a9c6dadbf8e0251872a86441f95a12d26a724a30ce4fda14f14f27d54c82797420c1f82bea25071d9f4ec5fad071044350cf11092a4a90df8d604ed1a5478f96fbe6a4a308a6ce26259d8aa076f5c2367410c1e861c4e517216774ea5e740cc11442cea3152d76ca2d4bd02104934a95e6d564769c488d0e1d4130c7121533b2e59ce3b36573e80082b12ac6071ff57ac2439258213a7e608a9d8492d9fe8a7caad436b8f0f020f6611b880e1f18d447f47fc7fa5356730f0ce2a942a57c499bafc63c3047c822c67284a9b2c80ecc29fbe46025569e832e9fd0a103b3e7fc1232faa774e7e4c01cf793f3466a383084dcc93a25f5ff0df50d4ca9ca7476dc49133a6c60b29bd1f7afb14b538aa1a306660f22f9626db8458e7ea18306e6206b92a3ed857ee5ac858e1918420a7bd723c553493a6460d0f5c1f452ca1efb7d2c0cefb13be446fc8bc8c2c214b73d6ae7707e2987bcc27841ee5225a929617b579872aa3d15272e88e7a915e6d6cf9654e58ffb5cb3c21037437755ffa2e4945598ca3ebf8f885615a6cd946839e59c0f6aa258c82215a6cbbcb99c13ef0a59a0c274ebf6e154485b3e39a730c7ca4a4a23ec1b3958604c61f0109552ca1f254afea5308ff04ff18d70fe1eaab19611c88214a6109479ce3e7d7a71bf1b4116a330e58e7b1a4134925b4a8d355118c54f951eb57f2af13d01b2c1c586c270e2262d9bb4acd0528db58f0485215e4acac38ea8c6da65208b4f18546d3dcd495acff2d558bb40169e3065ae97764f1225bcacb1c614c8a21346fd9113ec52a6fd7d1865c1095359fc4fe2d69ddb6235d69c8bd37622c86213a65431d789fb6ec9ae1aab09e3ed44f5b62dd3fba61a6ba90b1ba78b2f361326bf8a1f275d5f5ca45a70234762c26041a50b2b571344446aacb5e0460eb2812c2e61fa9c3e2e2ad5d60879324b98455987b058c23ec4a2812c2a6108b2697fa6b273ccd696035950c22074ec7394093126ea933086850bd1ba6a24ae92306beaa591266143422261bcb738a2539daac41224cca2428c70df0f6a52e711a6ab18177cf257bec91106d331e4771aedf183dd0893f214e18410c16cd28c30e78f9dae5f21f5b96511e65329fb9cc9274f9e2ac238ee3da3edb7f2fc441862a7a0ec65e554658908c366cdac8dc9da499243982cce9ccd07194bdd19c2b0ef49cbe6c2e4d9ad1006d51ac22d564ce6a93c3c70b8172dc8714701107c0400085910c214a367e4d6764b8a21b40fbb2c0661bc783d31fa395996bc200c49de09f123b1cc4e0d8461ce65948e8a1ff40f0863a898dc172d79c59559fcc1fc1366accc52fe3c310b3f185fd2986f044bd9c9f7c1105b2f2595a4d3635e0f0f3164c107b3af873455d11e4c41c8d1f1e446bf4bab078369dddffd8fe855963c184eb52b5be8feed13c18361bbf3fa95f60ee6ca932d9cecb0371766610753aa121242aace33f70c1059d4c1d439e88837fbe5a9743a1826c6554ab24d5cead11c8c6f66a3abd255e4242407d39aa5aca7a7d42774e2605c0f7796173a7030ae99d9870bf274c4ce1b4c25cbb446abf55da5dd602c714b4145a9ba384b1b0c1bdb299dc50a1bcced3b22ffd55e94145d83b14ee48aa74feab35435183f452a593339c8b7d360cc095ff91192a48b241a4cf9447eca10e6198c6ddaaf636c9abf04cd608aee22eb352c9a50aa2a64510693ba1b212f7e4f0653ccb17062d26ca6c8c76036212df34b2589c17429a87e1ded15742585c190d3d6efa4ead494243098b33e77309bcf174cd9c4a28a27715e757bc190824a3255933f7ba574c1ec7f9def42902729cb5c30968e6f2642b21ecf780b86a81f2584b0ea13e3d582694dad72fcceaa77370b463951a5f379b29b5ab1601cd5599613a24d4af30aa6b0fd59478d6905934cf68f3b4a8ee79454c1b835794d7627557d122a98ceab3f2b9a369192640aa63d75bd5f4ac63d77523056866817f92267b68bc2b1c32585d1eba0603cad94ee53e8cc0bb227183e4e9e30fa932cf79813b4b2cf394284ad09c66d131e572a74d29d09269d3e2c89c6e5d32fc1b027df4dbd64cd1229c12446477f3ea1524c4b12cca9a76a5fb3c2642498365436cbda826b5716904216473075e990f354e794e36a239846082d22d4cbcd7f8a60d056ada0e324f5e7b03040430b1a75011a1690000d2ebc40c1166a4430afa7d8b6cfebaa1492c30607ee3e90c5108c3732d64dc7ee3295f5f01082498e65af74a3ab1e4e48c82208c68cd1d9a16a9ea51d0f0f0058210b2018eed273ec4bf331e12106b2f881219ad6aca4e2f4f42a3e30778970abafb6d037d758c3f1e1c517391cc771163d3077deceb58dce4ab68d1c669b050f4ce13f7f6d9b8b0ed9da813925fd6b5e39393e6f4ac84207a6246e23f3126543c63930e851973c6f527d4bd60b59e0c064f5a542b05c69a7a31b18b4424f87da883a216f210b1b98438e962bfa05a5b4493c90450dccdf9d45c4cef7d1b28d42163430880ba7d353d6f0ad9c8442163330c44b5de92da84f56a2608b1b5e98690bb2908131c3b5d4d743462ad5be102316a620d1935cb45426b65b2ec480854925c7d3f5ab6b4ac5f415860b2a26bab64711f95d61cacd4a4a08f1126df256182396f8925e49cd66b816831506959c4e091d4ebfa4508d3557c1163752a22ebe78d2858d2db640018e5e0189b10ab3255dd3adf5d5afcb3eec0f315461ba3c2b39ba92f2a7570b1a34b4a0a1050d2d68380a9a021e1e681f568718a930867c864ef19239f10dff40157cd8c8f171e3867f78d12858bb618018a8307f2775dac6235e7ec8294ca2d627a414e6bd448708314c618813d383e4b7fc214f928518a530a88dacc84ff13c7c0e298ceb4195cf8fdd859895e9284cfea984509d5fc4a5105198a49afe05a11e4e3ccf408c50985c4b948c999d94da9a82c21c44d0cad974fc42d0ca270cda542cff7896212fa29e30c514d33457b7c61a0bbee80f1c5be017313a71c8a6ad478f87306d08313861cc104965dc566aacbd9f0b000e626cc23cbaaccf36de1aeb802e2ae0e161ae88a10963eca7c8eecec494adc69a2f51c19503c4c88429eb7b4ae1930af3c13a25c4c084b13be7e87ac1930c317b78e4c0b105171e1e1f312e61d4f6bb0a8babae33046258c2786f229ef77fc66a8f5109b39a9a4e5ac3ccbc932861d0e72762446848b10b8e2d100bdc930d0a4cc210627f98999af02177bab0b1c547310ec4908461562b89bc7d593ad901312261300db38ed725499bb68504562161160f13df7212de1344ec1e508256805a0e1c5b70413a10e311a6bc2345655bc8b11c2a00f010c311c61f75df31ae5de72a1f311a6150dfeb104ae9deab1703311861fe523e267f2b4a387b11a6ee7c29fdc65c6dcd0f1c5b281b6228c2a49f133f8450d7d93e0163b06b542449d1830c311823a41c429a2d7d1f2936920d1961309d460e22d7c608e1078e2dccc820030ce6141d11f4b94ef81a71f105938c116df11452e507820c2f18fbeed674f67f4f3b5d156474c138f21e724bbbc3f7cc05639cf7288b2d5241c6164c414372f658bb5acb6ac1706739548a3769cb436464c1b43d49697dcfb7a17a1958308f34ede929ea46907105e39c1c5da645a78ecd56307c96be28afff6cc184b6c8d1393290041955c827b5d5cdfc766190410563ffc80e1d65259ca59ce046a3c06f988c2998d74cf252302421dac3bccf838a7714cc637fb1f6c2733e8f0d20030a86744a6a990433e1697f8239ccc5eb15a1ca3b742798723aa19d3dcb92ce5213cc7d715c46a911b1549860b48ab5adeb4b1eb46709c6ff5db7121f3c5ebc4a30e7d332d5ac78398ff20b2f92606ecf2d39c6073569511948309e8e246549e49bbf1a20e308a68b37799b2ef2846f04e36f4bfe2c7f8b6711ccffb13e92ea49189d08269d3121f2c63f04839c5d8a13a24230574e9ffbd485ad6406c1a0276a9927395a8940306d9747f8c85eeafc812927a1b4cec49095d8fbc06413a4a48b1727e1ef81219dcd5dbe9ede34f1c0785abd25f72ca47c7660d8fdfe55af9589b60e0c96a735442cadbf951c18e4ab465c9771604a29172e37b981a9cf45b72d5be77236305a4842a5ebce6b60aabbb3b3f78ff697cba081e9f63c47ca5f19e272193330859a9d9e58c182da65c8c06842627c7e512c0a69a74aecd30916862065724e69a7571873d5b63582d29bbf2b8cabd7a9b5a6e4b8a91506a5ea2fc590eb131756989359a7ca4dd6d3d9559824971e95f2f9a4e0aac294525a8b7ab721ac4d8551542f5c695d2935418539dae81021a354c7083a85c9545a5c9e780c292253985b2624a16c4f98cea15298f2670f2aa69228f9218571ad757f3b72eedca3309f49cb232f8bc2581d3af2e321556c4361bed355ca666ebfce939fc87e9e13b3f309c3856cb933e2d7f9c713a620d16f27db9d30f78ccae1479eb4b339611c3f933549ba095376bf2d9de029775213a648e17c6e24ebca9809b3cc5c7a8753553a870973d4eaa4bb737d0973c8a7ce5a2679b4ad2d61ce96ee44c5889c9b75254c7b41b99d122bb25953829cd47d98e92761cafe9096462d4f58256110d671a2e935ad279648182bc951dbda175b59818441ebe8d121afe7cbfe11e638aa6639976fbec511e6942645e5c4c8e26a238c9246a5b1b29326228c305f2e616245a87b087911865827743eed5811e68bebd039f57d3e281106af78ed517911612e4bfd126631b6c73d8439480c15a1d22d4eb88630453c3121d9a38414dc42984c77c87b77a5d7c32584a9e4984ad162e5e4dc0ec2143a65975feb69782b08e3e7efa7916d0361cad0102994a97aef0161d0b19444134a5abdfe07e3cec4cb0e29723ec90f66391d7eef7592eeba0fc60b25d5bc2f760a5a3e187e2e74a5e0374a64750f6611a52d29554e4f52d583c1c7bc52ce97e6c16012827e7d13d5764a3c18fd4556843e1d3f72f20ee61aa5ef2ccd6907e3e98513b93baad696753097ee7ca53da659793a18b4a50a573274b2d8cfc1ac716159576ff1477230d7249b5119119205c5c168a64ca8fcb1a363070e86fbb871153335b6bec1f8c1e3578a923b65d50da65c7a674ae9ae0a4ad20643caf993bd7472b6246c30ad5bb8e59b4ee126640de69a942729495e0de65441271db75fe43f9206530e42a913d16bdb7244835135d3eb3ad8095bf10c8637a1bbbd3da2f58666309bb4d116452b8341a8532989d1cb7e2a2483c1cbd446b8f14aed8fc1bcd75eb27ff296c4c560d24baea5544da91c0a83b92be8d23679bd94c0601ad393e2cdb466eb2f9845ad83c872ab1cd50be62de91ff183e75eba601053ad8b13b96098a0a4e54bb9db82415bdcee24cf23f93a2d18f5ec27dd5e9705d3e44fada15d39873c164c5db2464e5e8d35f915cc296f217dac3d9492158cfa15b154073597ee2a98e6d449b5f8ad242e5430b7685321f2650a06bd9552ca69a1cf220573300f3337d9a260b0f568d95c8382d962786544b9f330da134c41c48f7d2a6d04cb9913ccde25545b5049c653d60443d637cfbf9ebd6f6782c97eb4ab5eec4e3f5982e9edcb7427794ac98812cc27d62992b44f72912418336d6fd5ec73f61009261521a84d2b1dc13c5acc549424a22719c16ca67eafcbdd4254044390977366d224c98c08061d41ac7efcda11a91a8239e6c4ea24a2e71195178221659985e510776196060c2008e6fdcea346628dfd2801c15c39e4fa32747ed07d095d42dfd828aff8c0246fe16ff969db43ac0703e8814196840927c663a97d82ce417630001eb8225a3d5ef54d773430801d68413582ba3ef5531778662e18800ecc152c7f0ab2b4f7d44bc630801c1854687c9e5c153d49968401e0c060276458fefd907be1220ce006a5a553594e8c906766060660039365b913ff3c9e44258930801a18238e4cb83c5e0106400383b6163f378b1eb4245be011c0f5a251d00511c2006660f814b157d953ae08fb00646098d892b3e85d64b13716e652da7c929cbd4ab1030b738510c2bbea5252b3fc6f91f6bae315e6385b11827b52f1f3b7f3b000d0a0c315469dd176399c9599b66ed8c0f131015c0f74b4c2a04ce850b245e67b973a58619e1ba533f12ec9885b598549479f12c24e52d05176a14315a66842c768951f9dac4d854962a90baf734ad829e585a10315e42c3eda6ada276d5c1a10011a5ad0708087470bfc0486868e5318d2861a5f516df9c46f0a73e84fe91e762d8579cf65ec53ae3c499f5ce8208561a4f5a5edbc7aa98238bcd822070e2fb6c8310ab3e9edeb1055e31f3f36d217386ca42f7088c2f09f3b848e3bcac1c51625c8c1c516250885d9b3529061a7ce521a14a65425b98298c849253f61dcb1742aa9cb4a2192c40a1d9e30ae6556d76aa5147fd40953ecee70499b862ccb55a18313a64b6da9fb713d04d111063c3cd00d12746cc22827a731c2df83ce1a0f0fe42cf0227468c2149a963aeced99484c18ae3ae4ab8aa2924aacb1f6710963f5966ecd975029a325cca6e47ad2a66b47e95f09730cd36952ca497815d558434a943aaec4728667cb4551cb4fea63b44e5de77f564e051d93309879b614725ee949649230e812b2fa431821b1bb8c844977c88927c47dd01ff6a00312c611b1b3b7a542627798071d5af8cd9fb7c311c6399d45ed9aaf97bd1d8d30fb8cc821653f31c290e298bc93ba16473bad60e8588421a94b48563271f72b1d8a30c7b3fc20b612ce4247228c1d45f4e6a868a3bbef408471648a3839d96bd9e24a42c7210c3f5252929b9763fa39011a5a78785c87210c9ee22eff25154eda1d8530bf5e24ed22272f84141a5a94a183100629a3a346ec5bcad5a20047afc0c30305387a056410c69c18395cfecaa7a60bc22841b74cfef4d1d080046804c2ace2b936449dd5870410a6ce1dd15ab1c6d5f407d3c8fe68932bc70fe61479da4542fa60ec0fdda253ed7c307827a184b49c4e5edb7b30c90cdd2e916b7e62ebc174f9913b87388d3cca3c184fdcc9951362c2e50c0fe6dc503a6cab750753a40bea2145b285f8d9c1ec21ea46f588141d3b753005a9262aa2891d7430d8c955cea542e660d4d3d972b59a6e179283219e69b7bf98f99cd7389827ce5d7b4a952d7fdc0107a3f587d0e9fbeb7883d15484f82f67ca725248e87083a9b3b9c90921049316521d6d306b4891756a724e1c42071b8c23f4b9a4fecdfd0ad0b10673be7b48296adea7c4c343081d6a30da450e56b9fbc34b5a63cd16e0e19106537ebdcf16561ae72b1acc957209f510cf3f6ed6582b868b2f7038063c024320c0118c1dce577eb535ac7335d60a4d312010c008e6740bd1907d93f6322305018a70acdf9b134135d66a060420827962c65c14496241da359653172671f1858d1a19f0f050f3d3c7120438a4d1129773dcf25e40002130d97ce7b2778a1408100453c4c9aaa7957b963c35d63e706c0104536ddaa6c80b277bb25c70f1c59fa96941801f3cfa6dd573f09c6404475a00f5cb81003e58b4859121ba3dc90e6d81a38b2d6e786a77400e82003d30083d96a654fdae530a1608c003e3a57e1aa154ce65fadf81c94d52c8d949d781396c7c85b86f93accd81d192d01b42c6a4981207c648f941282182b211961b18dd2dc6842023cdc93420800dcc791d22972c754ab849011e1e384a170850037396c7fbe42b42bf9b6860b0da89faa193e9f119025120c00c8cd9a7eb525b3dbe3402c8c09456d2c5504ad5079dcd8885416b4cb58734a977e219b028686f31939fa4bbaf4862c7965ac4b4f60a2965862b4c25f94cac3a74098b7e71c38b3b2d683c400b0f8f1b284001eb0f66b4c2f82571acebc4f6c7ae3061062bcc15752429d5a37b3f5aab30e5709e52b2bce7742f0933546130ad8ff73948b7a4c244c28c5498f75dc38490ab919e438419a830887deed02927a475afc6da47b261a3041f38ba066f91805232cc3885299b8821c26e5e3efd1a6be8c3b9485d740db4a1811c5b24bfa18587070540f0118055cc3085b94d564e720a95752a2508334a61906b5a5a1f7e56749881308314062df1bf54e5745d4af72fb8680e8cc2e0b1bb4327468a284cf1f65772b04fa1e357426150e2f4e99fb662300314a63065a1174f277ebe19f88451c6434fcd44519d843c61566f730d99e3cee884a93c5aa4582a48eaf2ac31274c17c73fd207314956ca26cc9dc27c5d8eb6260c3f6e41e57abd9f0433615a7f397539ed56ce1f264ca523a83c9d439f50e94b9854c6b8664d48726fb18429a730e51bebae61d14a183e68d16e71d348caabb1f6c10517fdb11f357278e18905332861b034361e33cf547d28d38c4918c485e420fcd4b6fe481286b5fa8c74fa481845a724f14cc84bd69119903049ed097f2ab47a9a6bc6238c59fa51aebb43f423331c611e376634228dab8f90bd749fb6c61ad96206238e9e949794d1293fc48b22331661d2f0d1c82ade9e29a24b6a62673b2dcab22bcc48844124c49b0da12d9de820c224fa41c49ed8ea0ef321cc21c4eab54a13dc3f8630c4cbfec82ba33aa46c214c6ace62d5fb87ff132184494e777bc8f191a3c8660cc210d4098f587922fea28d98210853e8f5f8c17357ce1c91dbc38c4098b48ce714237eefc68285198030e77fd3d5bc325d769236ccf883e95a457c7ffc770b397e30a62761f1473c7f64990d33fa60d6dd970f337a3e98c7a45c9e442d0d33f66010aea53c6bdbde776f01175e78d1650333f4606e13af1fff5973d619c18c3c9c2b54e9cb18dfc030030fa694ee1e543813415dbc83b1b453e70e9f676a5a543b98e5834417651e4f82e4f20e33ea609891d5ad20f3437b8c0ec64fe95325516225a56c0ea69ce3277a2521621e948339bcf7a99b7822c8ea92e461461c8cfefb123cd672bfc739cc80834967ad9cbbbbc68bf4e30c33de6090ba1ecdde34bb196e30674b2929a5f4a53af56a6983c1e44d8f7eaf173d6283a9e3e547ec3416ee396b30f5e82aaf48a5238d5a0d268b1fd352750d6d350d06b127d4d5040d0d660fb338a9973f652d04cc38c30c3318923e09162b46cee69d32185b82969482d0ea53693298743cb9fa57650ce6d8ad11f546e66973c56050ad7c51e24388d96130a5759ff0b62518f0ed9359f51232e30be6f7d98a97e45dde0fa9c20c2f98438ee36d2945081eda195d309a1097c9e15752fde68249bc24b595976405af6cc130b952c5113bd24ab45d70266668c160a2257e4e938967264f624616cc9b577e73dab31f52f81133b060b6d2f37e21be2e9a0d1b37881633ae7045b2deaaad9067d5580fd7c2bf7174b1013531ccb082d9459e4ade39fe96622412985105c3d749f556b1e449785430078b71b361af12664cc160da258267b887ba902fcc90825154e5d55139a419a5ce8882e146aa8d6da90e95d70c2898f7edf4c7c64497bf673cc174419fe9a0ea7f37c6194e308c9ae751e1cc53ec74130ceb26f6d7a2a9347799607ed32df12f397ef2dc8c2598cf530e167b7a12523843090613d367128c3aea3c7f98f03f2b418229ce4d90f5249a71046368b5af390bd2427f8c60ace4d9a9ae7253f45904f38f989f50a7afa31f36127f31830886e855317b524b3afd0fc1204baa8948faa6d46f2118a4e4594f25bb73c26843051f36d2e69811049376f80f6159cbb72540306957123331e5a5c4f603c38b49d28bab3835a98fd445670e66f8c028294e6be59ce6b39e7a60d059462387d1f4d12e0f0cd73a314bed8a4e4ed948773698b103f39f8e45d62b91b4bd3a3049d9bdd0c9ddea7fcd81415d794ed73d75ea331c184e828c982af777756f6010bb11271baa849cd00606f3db53e9f35ba5e86aac7dd8486506336a60320b0f6926b95a142d0d4c6aae22da08f5fce799310383eeb0ac9c77a13b5f3364601259fd2c87285af9cc5818bc732e1fd7de6ef5790d3260610e336184901ecb40c62b4cb51a6af53f25774bb36980d440862bcce1d923495e6798c4c8b6c2f4162dc952ff64a6a2066e68218315a6decbf5679ea6626833a0058d08d4c0d10ee0f3a25180850220f878808c5518649f24990f931a6bfa0719aa307eb07c75a227e1625f63ad0aa981a36b905418dcde372ec70e16b373c3c6ef7d51010f8f1a38da013570740d09f8163734208174c3e3865742062acca2557a45c9ca2c55d758ebc286c93885d9920841a514bf7462de43c830852958b63f616b6ed9ef52987bb393474ba9d4682e831406e9177ca2ac6d895491310a43eaa0b46acd9808f9230219a23028eb683149e8b32482a130dfc5ea4f3c29aab2ffa10419a030041b4941ded7d3e4e41326eded53ffd6371bb9278c92fd846a9b14d13ea9138611caca7276b9cb71e38449e4544984d39d4d94d3bd7388b264d5adba0c4d182fe4c497efece51241cd8439f6e9a03bdba6cb24e5c9c084b92ce7afec98923c65370d322e61f034d244551239794e3901f21a5ae04186250c415b2bcb8b76391369250c7a4f9ed74bb4d0e651c298a15ea2a85c8892a449983bdeaafd841589b949c2a4567e31c5437b1991309fc895b311ae23d117820c48184b5cc9d1e6e31e43081f619c147bbb43a538c21467fc83887b712bc55690d108a3e58ab6f9113a5e486284a93a8c2ab91c3ac8b86f0a321661c86ef292ac07f3b858086428c2242c94fc7c0a41fd53321261b820395775fa3071224106228c1727e976bcec9e2dafb156ba90710893c5d0115e2d5d3ccb8d761c1f072d20c310c64e592f7a59b6c46cb990510873c81d2689d029abc70961f8339dd5889eab84103918c81884b1439cd50fe5b1c2bd9a2a9021088396edcbba957e3ccc390619813029b1772d3934d34c650908a3e8ac9dd35a589c5f760b32fe604a26f258a7dbfc60aa0f594aff6ca873530b32fa60c8dad1d2980ef3c1202c8eea76d0f649d92bc8d883e1e36513225c8a5631f221430f26257f2e6c6e555b8c0332f260cef693e38308614cecd9041978305ed6f3964b09a2fdc30419773029bbbf102c9a0aca4576300839313546e6a9520b7274f1059aae40461d4c215a58cde94ff16cd3c12444ecadea8fde1964ccc1a0bba154a7c98a8fca0153461ccc167496cfeddde229d37c830c38987b25560ad59fb3dcf806e3b9beaec7d3718351444e7a4433de93966d30d74f7a75ef9c9316b1c041061bcca2f4baa9f5f7e764ca32c858833174e4cfe78e3ce2924e95c544728f3a4a83597d3f9dbd550e6a3b1a8c9a753a9728ff0cbdcf600a71d1d76fa10bc8308339766ad3dff1fdf288cb601a3917226ad4ba332383e1e4255987ec1faedf1a6b1f3612a640468d87a8449165d2581c0a8542a130280c8360f97514c3130000000c1e144763f1589e29bbb20114000442343056343c1a261e141c180d04e3703014068403623028100883c1a04028748ea6700c8b0f2515e935d557e99e1353aeb200d9fcc6754ce80c44c8119e6eca14f2955f0f1c331dc4d506d130fd1664da9c86913a19b432400ec18bb911457805b235891c4adeb651768091eab5d77e70eb293d81c4e3c2663444d433d431c64abd4c5e2ac4d1c74d6a65fd83cc3375796e80d929b7b27a360695d2efaa2e71a4927e3b85bf4b25079595fbd2e12d5ce34315756e9604bdbcf2447447949e31e3d015327d4ce6ff8eec4b19a3a68e3a2824240fec1a0c4a29cd8fb6e60e69df702c238fdf5a221c2e4854653009cfbb492309094f03eaf9fe9a44aef6c32dd154193276cd51fd88a713bc8bbb72614d62280f0dcb61821b33646137b0ed5c93179a1cec5d49039ba3d9b0fda804b3e9f375fff67495c531f413d2479b2969aceb4e308b44c3595f73e813bb039d68c5d33d15d074468af96cdc2007853353c5e872a516d2328d89ebea6ff34492bfb54746e9263e4f46a88595c6eb656166e4b4ff48995183760f133699db9bd3f91713e84bb02dc6f0280d3e028b4cb443aae45f1c59f868e386af9883444a0c3313163316ca65789f5ea051f15275dba98b19f039901ed51a26979dcee3cc0057f7ce0115f98c7c50561c4c70dd3a131d688e4dc0c8dbf95b9318d566b8c1a7f13a1820aad3e997bddc246f0501819d6dbe2124d360abe4e6f8b9d368b0e6c9650340fa75193d3f6165c390a5645f63a4630ccf23d0e988b646e2c50832237d45d706f09fd133460c2820c85317b4b310e3f2236f200911d6195baf9b946e95aec8046f4bc1f070cb369f9d994f47d747526de2682ce100b47127ff8a3d2a827fdb6947e9232edde09dc9d118655613024789b870a8c4c4a9023cfda92f006b7e5c2e9d26fce8d3531917d86936c09666eeb5d85105f5d96a1285d0cdc6088b5a9ce6545550fc62a55a82a94c5285fea0553886d36acb8394807da585bc695904589de91399533c582c41d09282f086e43284ed66f127a2d58407d58c66b9c3921c44d3fcd24a15ae568a5ebe6f550257005784a41641645c8feea284388cb28047a9670165e2f8fb11a03a18ace39a114bd716a330bd6276af016d6933466fd716a0dcb83e56b4553be0fa08563cc701c9322b365491d6fe6e067f4ba7832e5027bb66a42fbf982ccc6768fc7dc748ef8e74372037cd9c2e33969a5a8ba4340cca6fee1aac57efee90f26fbc8d5fed521609bde34a2c38edbe02fea1995c710f46cd8926f3ee054665cdef666013642696a1346dce3d9b935fa5dd876d522b5059bc58d8f7019d0b133494c18577e1ade092c4b7e1b12250b93e882fcc097e2ae27503b3d2c0545f0c15adc5328f93e951e631230d6579f7ee5aa358ed8e6ebb9f50ef288a543c45723ff414f08891a6362b4a7c585477a951373813a589b3513eb6ddff4d1dc76fbea8e4b03563d4f4bf6943bedc84aeb862c66254a72fc70a4f997d631840619285488e43d4e4b0618555bf6ff7993cd9c8e83d4507d42df8ac79ecbe7cf2cd1c8d51ef005c319d1b591c3dcaedeee1909129f14831e8e934bd575423155c6daca0b333ac0a6288eff7190aa94d26df0fdcf706f610be82a7eb81c69d36165f0f97435d6808c0b1cb38c91ef6680f65f2e8219ef2f12c2412c80db41909203cb5da2bf76d21f0a6edf92ef1be0a0dc7415ae763989a43686280f82c90a64bafda406d829e67911fbce7e50f8c9d9e4886e2a7f0ae954326d3c32b98d18ea3780d645642a76850a5eb633d09e8062181b846d2463553631a11bde2cb0b24489d5c41f0e67fb1e1e37159e99ea2a66f93fdec382ed9b60121641223d50946ac1edcf085e577a3607d2305510bc6127b38b6ce897021a09a554ad83c3d3d47a05f281ba95037af91c7229f145b624c8e36600b5adef16bcfb3e1760774c91ec83d9675d492ff9d7f424f813ec8f343d935f6c7aa7cfc9c9ad4a26408f147452bee24bf1154bc1421e669da917700f30d08c8414dc3d346bba0a73dd70d13a03c2c17d109ed09a71b79f24fdb04b7e9f589a33452ea6200b3c60c75e108fb01d33f2326022f2babacee5cdd3a9dcd48a8142f5dbb74c2bae7f146779f26c070527dadc04e5142a12d06ac44d69c68bf20ab902b3411ca990c250ab4a8295f5cc92e01c46c6f8313e0730889f94103c04a271a1a9668810665cbd920688daa2ab0ac9089d20193eca01b8c49e910e2df46f68e6512ad5385b1363d016da0c30f6def5453d35149ea2f30c5454043b06f36702f4f07841ed2c652a2d80f84f250e051e9685d1b9bf4710af4ddd3b1d58fa546fbb26549d5cff4339a3fe2537e46cd1a727a1f3f31cace2767cf447b6a6c2b2cd19c2803d01efa9f731c22a779f5235b4e70027e715161c209e23408ee15fe61c60367185730afa1e881b4be630e3034af70f8f12a7a902977fa47676b95d68889364acb145d639177c06c402624e91f0de01b5dc7d81f6f3157d71c7eaa54ed09b7247420260af12fa8d1892c80b00edbbf50cff96ad8f070f160ba02ccb6cdc5422419fe282f4a314fc05b20ce8d96f4f17d56882b6ebf66059e8d107135521051db202c099b7307e57c67fdc9b27b44e03c49d1c0c37b7104c0a645017d4c3bbf39d81951e3fd03ba90de3ac1ec34e460461009e16a010e9b2fbf61e8843fea3b97269bd44ddcfcef2e4f50978aede1bcb4735261489dcb44ede40c69ab926d5ab441563a5cb5cd513c0f480d8247ae1b730a9ca315ebd32be660f6efd4c762259ef8d71dd7b3c4ccd7b401479be91147834f9d154b8f4bfd1783a4174d4dbe951736a5e4549c86cf4313a4b6b52a8645720a692995df1f2ddafc3fca341e4ef76ba08b57f15a397087c02db6ab1caa3c75426fd433828c9d4dff765c57a22196fac2487ea9d1ec49c876ce273a353ff85278af8ab87510bd80ccd5a2a3672c6a1cb965cc4b0ace8757a1d06370430b48829dc1d645c47c30e91d02628f193ebaaaeedfc51f1bda6474eadade3e208cee33da6295565679256c4d188b64fd6f31b0614a508f9efe35e025317eac65e6f1b12ff2ace24a92b0a39e8e280de735f5e47a8ea2a9cd582c7c9f913def21a62c0d9708ac19216f7f90ec15018c5730f95e1f3b416bff1787514842ab6e6242c89073f69b261dd381c169785b0928962393eb211141e728da5a1147b02fdc6cf000f60b8adde605862ca16e03b1bf6e64e4f14ce7584274415967a248abb91498aad5e26818ae60c8a3985822946614154bf53ec087e4a9f42de02d4853b0b5020a2650ef0c9a8866091ddb41163b0f292e0dfa48426251b06f839e40f4b681606e5e4991057489c916c23a1a10f111120ab4a0eea4af2f70dddbfec18ac75d8058ad40d59ef704ab045c8e0506e67794163844b9efd8016364da9804ac9febbadd75aff27748af82661223f915301a21dcc370238d0a602024bd1fd430247b53534b3682fc35b9613ebe0830be262ba420825ba247918cb8c481a0ee359042660b38e7d2380f61c534dd182473045b738b3bd88d0f7cee823bf926103a40ecf63ea4bd14bba392e0725d3e2de186a6ed7d2dd18c0184147882d7d74f20a07f02b5e7c63e49b7d6586fc4b740f60a502dc94fd264c5691ed44e71655069eaa4c6a162a7f9d272abc898c52f2ad1c7f8a443fa2a18d66d8e95035cab372cb177a34618be41c976fec7648352223915586f21a5d968298ae628298b34cc729542169ad6216c156184b117d152582cd571ac306c5c6660a165f72e05a864c8b45e53dd71d6e8f2ead82e65944f5b7e0a3d16f1b608bb280a4b308d129e041a9c05d1260d01661bba70b3bd1596006a0c0e61dce18377031308211085a10ce0fff7f2d175e34669d839a773b2ee70f4e5ac544f1096d2479c1c3d1e8a42363e4511da8a34528dcd5a5986636ac1f1e55b5597d2fb1c7e13d4884e80a663246dba34bad54f32c81aa4d1a664651e8ab850c73c754189b59218b021d5a99db2c9cfd4eda91acac26cab18ad5c587f90ea981b8b1105e91d4b568ba85fbeee8fa609f2542566d22726436032c6f448b381725f00d7f6da63a51e2e5bb3f2080587eb64acd05a38f7b5faa830ccf81e6d83f6050098080b0a6e49c80709e02b988b6e97c2361c40a28eb5bc1f3cc321f7b2dcfd0d10510144060a3d04a1375e0e0825c87bfc2fc3cad2be668e87590abb907db333a2fc263766775c14e4c38ac3f41d5effc1410dcf315b1eece3416c9780ee738e6d0855aeffe1097e9329fc55d3e9ca9087c6df6b80a17e541c786e827099ce4b58a149e602dc906d8922365610ddb843ae98151c14a236db415ee677135d1b7075275f7097a2c7db3723e3d758d21e5462c54bf262b17e835eb50eb854909cddb05de2a3a42e75a28776ce6f754706652b4c3cefe11a91deb97fe26dd998a738d10a1fcfa3090139df3e1792db3d3792fa1a799d5734fe19a0ed81c15a46af7498e80f9e7f4dc8631d6f358b4fb7ce6d8ef496d391fab6b460156a256ac6030b05a1b43bfa89002589276a70e0e265de40dc84a84e5c84865a088ac1bee1ec06abf228b313a4ebfbbf9dcf8943699c0804b3d9fd09db19451f95616d378139a8d9e406c494208525791223aeb81cf08efd360eae8862706d403ae2817bbfe4490fc935d1bb4936a7d0c9544246d4d18beb20606739bb92e84044a44f27a1510d425446305293c1ae2b314d6feffc1b34a3638c94be379af27d12542d4045a5dfb6c7b559947740d5a75e6c831d8220cd4cf8a7e00e3812dd86d3fffdd6d5fe91cdcc08177ebd8e7c15948781be697d412d29e519ede69afd26dbe7d7d40ab6ba565eea80e62448f634b9b4d3ec6d36086214007381ce5e45158e66f6c7617970635580749fbe6003ee96cc3aa74de57ea131aca128ed9258365d8c49e1ee9bebad8baea4ea045a1dd445eb9aa551bff1f35282303aaab55f3159c040cf6999ccc99b2c93d965f3a952315162c2efaeb5d8ba5ab5142e77a35ed59e91f440cfe965d6baa7c729cf219e87ddcea44aab19f5662da7aea85642e55cae1cac810e8ce49c94c99ccc2dc588be5b20033a670bad09ea292df6aed65a28001fb8419a0bfa913f9782bba69d9c96ee5889373a889929155af5a87cd6e6dc1f13309c0064880edea01dda712c0691a2bcdc7a8dd32fd9d35ca9da2a44f5592f67f7b44a519e663a2d9c5b17032bd5cdbeaa0d0a7d36e035512c2eb62b309c35cd4b9408d1eb36395c47f42b69baa83781988d9f9af68c9693d0a2cfb19685467f9358aec464b1a400e3e90b4d15b63f6c618fb20b0e7bfca8b3d223538cbe49551a50f524d8696eaa54bdecccda325b59721ad7040b6dbd9576c28307109f07c6a45f02ada4021f4c27c70ad3fdd457f540fcd71ed041b90b96e85629e021e26b91fb357836a29e1b315ff8a662ceaa88cbc2dae08ab4cab5781264a89cf863656dd54a781afef003aff17b00b649223c61b3fc4ed93210d4083df7f2054ad5f8916a11250776bb474afcde8070a03ac3ab4642a82fd0d8b21d44f9e76c5f7b4306a0afba43828c8f86006948b2a39793e2c5fb67cd36e357a3ffaa4b5257f13d519c84c7e16c014db804ae7b16153b2ab5159a22ee0a8e47d2fcdc0c58a6f5bf55b235a4d367064e0e4dc820982ec17c53e9e09d18cea18a9b89ed4cc921459958cc60da9cd53225ff0e63ed03076fc4a041e6dc7b4e4a14fac99986f362dd8951d8eb7b95d7a28611de36663c2ebd3653358ca005f881c3d9897f0a16e74072eacd5f118d9b2a5efdb9b7b9e891cc2303021a86e3a6c8a1e43f0838d29c226cf57ff04dec33e016d6a36252c36ea35849c8b21a9763f5cfa7921011dab0f91be0ad109e2b39683674bf07674f3dad160c776503f680490ccbce93eb4cfde05655613ba1a611f364a06e2c4a0adef6d9cb79d12ba577a44d7295a8af828c9cc08b0db413592262d83227468524b572695a32bb50bb6910c25f221d1e459e6ed04a760fa838d2590b68433940f58691bdd1f17e25022389e0ea0ca70c8f94b603dae629bccf5a3dd7fafa071d093a0b6d97fc204a4c3ff45e90b1cab00874ad7d756880c480951be64e1ebaca40f0d107a5da0f1d81d059d59379131bb2288c1dcc769827490f4395a5248b4d40e0a1738c6c7df960866c8818bf2f167307a65a474d0e4e7f121d42dae258bd8e368c5f2520a4508a530495d3ac908ef6073ff0c535069c532deb887e8605b2867889a39b28730467b1df045fe000e47e3989990ba1981973806ad4085165a4bc1593924bf2b67ee49a022238f35b57b614c78c50f3931289fbcca9cdcb914d25302c994c1975870e2564a21ce049701d4afadc798e713ade09bd7411a40a316586aa3726e33c138cac22a532ee7c0a93fc9aff14bd712bc8ac13544d8efdfef2e7843779aae61ce72de0f97e7088bb782d4c6e9896a9a7632a536222338323a231b66e690cf92f64e8bbb466abee417ee2713178169814ce3f3cab878fa7828da4eb68c204976587778f0215358ff93d86c9f1da7f5c1a562049cc6a3435ba8543f69b84b781a58a98b04a45c9bf1f81e90db6a5a88153d7df27f23b4aa50662c433b04c5b20103079344120d91c52b119c1bb22f9100ef26cc7d746f92798e276ff8e7ca09fd068f4be423fbf992c0f99d10f7766b89b22c683c64dc34cc33b35d65871eb3f821d063403ed749c9bc0d400fea5c9ac30075cf431c69a462b1a7b219f68ba782255c7e683e988dea42343b4deada812efc2515b95025b3c5dfbd1e36217cb6fd280f0ea49fc22e81368b2a4fc6950ca1ead6c41c756b5cae6fda17481f4cd02a363b2c0518ccb650065be36a855cfb91c4d72196970e151c9429e25dae3d601bcbdea7a8468b85b3b2dfcd06e1a4d8f62a5f9ba3c8de9dd1429530265b37467ac74b3bd920d6b3e86f06b758f4fdc869a213da35b2971ae65609c05d6d6084e0d06e1c838a1dfeb6e0e9981011a7c0e427f88380cc0c2cacdd5752df7ad611a0f182d975ce17712a9cc292b30aa502c2ee3732fa41b2645d9eb76b7d081b0a24508b99c398440d7745229514a28e27a06002167b822982287535039c5f900163da00abc06453a610cbc71752de78db24f47e2841492a81119999032364964a5acf9aec81ee548df6ab3843717447163c286d6a084cfcd2b9f4dc1449111b2b9368afbc09e623171271f8f9ae165710844a9f0f6990e8a43c38f90d9b11560ca380ffe0eb9b59b411e0f65e1c8c6e28f2a1e8049692e181536899aa314129127804e3cbcbb56df21fd5be2c03c036039669bb355c3162a2d55eb00a0c9ee55fdc4e397f151504888d76721fb5d70e156d8a1258c6b39e6bf19bfb5130b57122f4b3b31b9d227e542b7e08768b3ddfc906067efb8909062ae0341bb0bad6e9bba5ed97ed8236beb408a1c2934f37a12f3feb891a1600f14ce07c2e6a83553293ff1441a2ad5e526a9a1cc1bfaa2e0a0097797cc20903967660c36109c602819029fc977350b10d505799971085720cb2c3d0cbb9cd37ba15ebeed57a3444fb18a2c4a10f80bd3b80240124d00086ba8f8576700a84c17239c5732c7a5725b5a22133aa23f7a9d9de905924c9c82c19adb4ea2c5575e61bc497c11f0c04cc23a32fa9a003cecc551d9ea042384a5f201ad5cadcae108a6f41aa960b032dbbaa1a7911ac62da0d819b7e744fa4ae22652b694bcd044772a323281d90205c5d5ee210a6b82251577c6ae0d70c99148ada5170d36751954b0bca23d7de4e8d8a033919278656d6e21f25c700041b0d6296e629f5bb8405c4ff6710ee00e56c1f7a4763b50ca5f061362ceef80e1904b2c69529064cc6b7c5c548fb3b64f259dacf03b36d9ddf5429993ae44100a51d35c2596505a5c040501a0f17751d00d8588369aac0cb8094d3e8b7ae995e87a88d9a3154c393762ef11323a406759431b868e3836867b8756c5dfefc509d8b75ba9614bb62e242becc15a7e25a13b623c0906d6a7a563835424a72c5f0f24e9e428b113c6901459832a3faa68543122575a27282e598e4acb2094be296b260f68461de88f59a22f600c0d4faaef59df66ba38876544c45fbcba2a6fca748dea1c57eb3c297f76d2004cd7a6d2b1001d19559189f29df22407539e9d4a396221bde703d8a83f2aa3c7650da8986bf9018711c782343210861750e0a1ba40ff641ee85635ca4642a6950fe3542eaa1fe3fd05ebee38f56d829c4bb9774623c4aaa54368ddc6509a01ed3334701e80e6774965fd7ab1199de4f99261b602e0d6549df6895d56053b1a492eefe45a0f23c9e58b5eb4af03fa7d6d9ef477a23754125a9b0a8e48f0253de535443e94eb0d9603118d0079697ab395ca1f16c9160c73600dc4bce18130aa31073abbcc1853ebabd656e8d6b432b42cd0597b70f674ca63111b6a2ce57ce8a60fe24597b02aa58fed7bdb80f5343cdfc92a225e8b02fb39eab7c6ef4e5f3cfbbef25b67bf053f58fa5fdab7c4ef565f2c7d4ffaa6a45f91a57df278f53447bbd75979c211925976126a1b251a27ffafa7fb836955d0e6f734b63f4ee894414c595b994e62f69f5dfc09093d7a5a1b410e66c398bc6f4b47a5a07eb14f0cef5a8ecc660f3f7dbc9226d7409b5368f9eff0b0b0c6bc9ccccb0347cf5c992bb49e631458b127ef44c6eb62acd7b203d62621f2d79834a481646816d50cf5669330dbb8d911dff6730373b8ff0674dbd82b6d95485bbf93ae338754ac7d72adc3897e2c41f29bc42a124704c92725e30bc7044937127d24963712a3a2929a8442e22bc9d625924d8c4625518955ca5383447c24aa2bed158073893be9c9b8b4a6a3edb07970af3dde535c082d078f661e3eeec771f892a7c89c475d1e2d3d08f5f8eb61d543aa87411eee95c77615349bc7c523a687821e1e3decf410cee3fd75b7ab207afcec71d68fed4d21c73a7b1fcb800ced88ae215601f185c8a9718ca8e8fea2304402136852e773ef5cc6dc38154709d11ba8328f2ef69ba23cd8f38e0917f7fa4d3b17ab02d6d936067d42722c0d223844530d6951c4dbaf4996653eef3684b379659624809d510afd6d024c57b6b4c4f39aa96ab5c3f991a6838adcf05bd98c7e00db019324c72d133e925ecab8105380e606e7c17f6d8a48295206ef1a84518b7de68e2e209810874e1c2343320a40f1b4dbc21222d3592f062ed80ae8c9a5e8611c534ddf0bacc3d815371278144dc15602ca81bca77d12b599654bb48d3530dfa0894c2a0cec0a33f31ce4b295491e9a716dd2b44043f846b544cc29aa5b420353b830475b0ad4b1d921f2742b8475a9a468637eb2c9724e430dd83247386e389c7a2d01701a0b6127b160a955e938c289cf05c7b3e2973a37ce871adbbdce2e1ab2114a9165a43179437663c6b010db77abcc2786499a1254e337a76a58360c54210ac9fc503d6965dd62437cf0db39b438b5e38219f22413635d0cd2dad7ebb4f8f680eafc362daa48e534991ee59ff25e409cc2097a5bccfdbdd82d9a0af8053c796d80d2482e28b3e47db119aaf2e005942160600ade7379ecacbb61b786d23e501ce9966c9715392a9d0c443b8730c07e787bab84968c58414d7026386141914f631fdbf871078f5baf3e6d6c3d48e16f255407b8e434084640a7c4716e1215323c2666ff88b68b6bd9b11a482cec660cb8586519420036266ed50e4a46870e6a49be064bb1b2fe617a189964cc13a7b6f55350c9e47a48efd563925ba6b746bf34499f85b1ff15f89f6e9148fd39c4839b8f5635a0226184a7aa63ee8d26ee9b4522bf0612846a6fa71cf5a4e7e95cb70bb944e8f0a2083ceed422ca8bf291d12eadd76a283194730311534b83d63c31940547b61049726078751186f9f6808515936e46c375436d78272173d80a20eb96efe242572ba38394724d3b7affe1894e34c4a46804be6ae3a2ab85007f400f4d00ab092d0c802ee0f9f1936b154aa37814cfa7de1542c0e2e67328d7387b3b7615ac044b68e4871e358000f4eeb2736a2abbb01501f26812c729f5cfc034b281e89c10709c5000e29e3ac5c4e65a59f09e34a7257560d1493dc1669ed86e005e7c7c2eed258ffa9cca61f7ae1d1cdb600b5bdb86d501c78206f056b279093c4bf34f53a9f7b40f8065ac623c7389285355f30ae06b429715bbe542a81fb1db278e316c68bd5818da16cd9d2ef063ba5026008fe0acc34c156710c480da7fb0e34668e5876c504a10e8fb7c4e6f07238189ee2d273f5b785ef929d876f94422645f08290457f9b1233676e2b6d51c6525448b06c9988d009923c72398bf297d971300f41a14f2951fdb95a06c4961c8ada0df257e68371bd16c010e43fdc3789c5f671e770f36a86440c45758b44034ff1b16454adf482ba65c0f93c26af075296796c02ff744904ef533db4090cd329522b2053bc24489a4ce527f9c783c227482d9110368caf03081ed464811e98626af381b718793196e6a3439e036c48d52360bec0432f046263ca3e706345dd7c283151ac431b06a5d9bb21278077bb5be32ad756c55b166646b427c3b6a6adad3b7dd5225b3b9af5a103a7d6e99d328fa5b986427b6484f5c5b1a92390a2607ae0a540a68a1f5e8d9193a4fd969267aa04b919c53642753ffe937704d27d1237467a8bb68d66c881de86f74dab694b0cc64ca809fe4a20bb3b4433a4e6be4a3e31d41d3fcac39078512da528f71927d7dc0494c51c15a490fdb2b8eb667cf9e5898d82f57384295f5aa0d761a55f0915048f53ada6d1515c04f93a579fa1d761a47a3639708c398914f465a8e4c3061367ad1368a6cb4da48f46070460b041ff58eb43d232355da918aeb9cc26f9cab1ccee9352a83eafc84869b782abf711c145aff6ce44d10b77ec1ef51193420ced8dfc141387e2c201fda4046f30455b2ab0ddb4036c4541a4a690e27247c29e9e5bd67e90c1627d3e7302ada05801bc118252935b9d6fd729c58552b9d376586f1308bfcccef22092febbd8ed82b390135e3154ea3e7e0fae5fb6a088f70c5c0f2f1d711ed60598fb8e9f5bd388644d7b493591821bcda8165c9ca8d95185676b71ace0a438e9585b482291764a89f3a65e8d499d080b48c851b94e44a410f8d8c0407daa6485209474185cb1711ee3a1cf3ea0dfc4645478157512e0433e4256a180698ce52c6e88255648aff76ee29943c60257aed1187b9a5be03a71ad645440c5c22a8a4542ea834f2f1362820b55135788acba4ba50956084a7ef114a81f87eea84e022f79cb9e30175a4804c444969a55df25710177434ea8a4fb48337c90f0dd289c71f3d0f00214229f0e9a381ce1d38bdef8056cd6e2a67e19850308a02b5743f285e818c56861ea407b2313bf1e9cdf8ab7193622de702fa0e09ee882b84e3c2c085d42f326fc4c423f783fb2a93b223540823bdf4f3762589803485210cdc40243ca219683c6a0a372b6b32ad5fcc174375c931adb1a6d58574d697e45a66fb79181559c8b71f816a90eaad50be42f3f741fdc7057f5e9544c8b8e4d94acda85b543398d3038c13f3de1697219b3407b3519b8d266024ecbcca858d4c3d2788c22b0ddb80641b18ed807b5081df10982f55b10c4cc49ea976c3cff91c73449f665d5134702930e091782ff2f88d7e5ad6582a0d0492edc0f9c3a52f57c00fb20f08e607104f2d0b7b96e92f97211632e90998c85d0fe722d2adb6524728f97f6802c85ea002f3b884379fdf4f23f8dadf5c0582ddce2ef23de637d3bcf6fe6e7b6b495e266b758f847d9d83dd179e38705a9ab87fd8fefb850bb4038a460987e2e52746597357b121ba6ece14ad9c14dcc082aa3d8a5320ec01bca340dc06cd1269c3bbf151efff71bd48901bed4d84905f411f3baf9166d0ab7cd4fb7fd40b20f4cac7bdf161bda88f355d7706d8eb7d4ceff8b8f11a2fb87a852037dce3d5c5bb25d156d5c92020d886c6b87290b213a4308548b3ba0cf6de6190751fc72032ad26acdd500fc1a720eaa4a762a274048c70c3b8cc2c3ed7b7deeeeffd78ffca3dbeb3f8ab9ab7579653a85aac3ff87e421a62fb19b6d99adf42cf146c9967a0e52381cd40bc2d0b75b7098c37d644725581eb609e8cc5c044ec81dd044c49389bf0a85165ff8a24e593f4fff04477fc0c5bb2de8c423ea20969dfc5d95a4db48b8a18bdcb601bc2a6d66c0700d63790155d884e9b4fbad5242e16906233635d2d8d453fef428faffac90deeb00840d88de0dc270024710739a621fc6ac206054925b84a81d3e89f10961b21482f37482a0fe20d0842ef222aca9c29825469a7d5bdf6bff881e13124dc0c6e5402750450015da19672fcc76340c634d8037cccb65a58caedaf501b047c3c69b794a256b5857c0881648784ef3e1517033a6d15e33111774898956608481597398027af7cab65075a5ea87f8909b13ee98a0cf1a40c443a51fa3c0bdb119a11815c7ca9f7ef86f13a1c6fb4c8038c7961c2320aeeb4e758895cab3cddece684873ad1cba72cd9aa3a40283c117722a0d197dfc71833ba3b1bb164a52f4fc7a1266b8a840937a33eb338f33360d475859b1d0dfff8dc3f88190fc4a014cb31b55748203c6ab6fca35a1b1e64c1a3eb2217454abf630c48495fadcf049f13e3306f652bfd225de84581b01bbee778bec44af16e551a87014bf6a1037c61f558233492dd320cabd87ea73802b3237cc792d2e520fd46380fc17e089cab21a84e3aec1e561646071b01731ed38f298e59c784c6a4625ac65c1123505716eb5e22e8ce81d03a7019465d22849273208a9b5b722f77bd37353bb48349da92b91bae90a610fa435d58f7b4ada1e16909c43de3fc97e094c3de8843355c5c101c60a0767679c823b7d6b3b7bde87ac1f262c0196de7c755e705b049e05096ca9a6cdeb141029844fe645fe976a0553c32ece269c82370576322f4ab1d4d8858dcdbef59f6e2ec0370d2ba9f39b8ae1e30766c2cae199eefd86c44759c75f43a2eeafc5fa4092f909c967278f689a8384cf8b0dd3e040fe03c2598811955d1301ebf2c6985e70ed978b83e131afa13c6af2b5cc25ec6e97733292be305b9a537430c2a6ccffcee4904c813933dfa228373af89f71aa0d6d216dc6d2ffff748a2bc0c527ddf106149a52584fbf05a780b8f179782300377280716d916ac67813ac52964e22bf1f453b7595487f3fc078e24c4f35cfac6c27ad708ccdab1bf84a8969bcb8243482399106f3b0a3d280e4187136b8e65811cf974eb94ab2fcd433aac47ddcb36e61d4c0f79f2cf17269290f1f3159e2f75b1f0dee813eca4021e89848e0b3660b46439330f0f0f0f0f0f0f0f8f3121b5b54f48424a524a4aabd75247e420a524a54c2989b7483b7a3a33f129a6c9de6498f8040402c20bc70b970ba529194ac5532a28a5c7a80127ee5f214f5a8e783a3168c07a4a297878ca32d5b663164c8ed729a9f85bf27ae3396c6c60c60ccf614305336698a143166c66448d9d4468e4d7c4823fedaff9a34760c189ee1077731c9d745baf489ecac9cdb74757703a42ca15730a19947dad605f82662425ace3dbc70acecdc3d44790ef216dabe0934a4a664a4e1e3cffaae0a444492b62a954b09a76a742aca8bd6799252a785bcb1d59a994498c769c82ffa484d211377595af29f83af9a33f9b8f1252540a6ee2a65df797284953527016f4925c3f4fdb3419052f2612c54f6645c1f9c5dc94dbaf34e8e0e1e80805abab13420e25bede9b147480824d136356ebd99fe0d4fd57f637992df6da800b68514ed0e1095ebb2477ddc43bc1c41c925f98162dbe419c603d922e696b494d66cf2698e469a1e67e5737d2046fcaf467d7f6c7c8e94c70494fd9e40ea57dbfc504aba5c77b83ada714d24b30392c55292d1ef27f8a25f8ad0c169e1daa76639560547559b885790a756180c60942072598bc41529c1c3ad4e23e6346c724b8b5f8e31b31eb3eac1a6934eaa6d021094ef848c8e1679d69523f414f60c60c2f3a22c1d786082a9a6b4ec999ea80049783526d2288fe11bc8ed021c7ecd31bb2c8118cd60ccbef49d4434f7e8398024e021d8de034e4dff43da943ca0d6211e86004abc12b8bcceb9e47c72c82efec5082418722d8efec7927294b29c9b023119cea94dd4bc6122ef22e0e175d74d1ef051765800e44f01526d3c4cfd095cf6c41c721ceebcba2eb4e372ae83004fb1ad2b43f3c2479ea1bdc51082e67e57491a5ec20042ba23db5ad8e490a5dc0050e1a9e6c98391737726cc720f00b49a530e5a2a26e0c3a04c1994ab595f54d67aadf11085627e5b2720b992de21d8060628d797b55a9945448c71f38a1ffa305b788ada1593fb06fb62629546366d3fbc05fafb7794bb0ccb4f9c07e8c6cb2d4b4072eaa78aa4efa3cbd5c3d30da79a2e7f4f595369a072ebf267597206944060f5c063d12ef2f596a767760b4268d3177ec16f5abc30e33e8a8c349765042c7545a0ac90e3af03958c88a215f4ea2b21d73b0191d72603fc9d131258fef9713904003a0868e38701b2c8d5c1711b225b57ff1451b1278c1c50d0a748e66c0052420811933686400ced00107f672f2159d624cea94ae60a1e30d8caba958a752c8775d631b3adcc0d7c7e049d3db36f023838aa0a116717b3674b081754b225685a05e035b6eaf6ea2a63ffda506d663cea1be7e3f8f9b062684a42fee6890adf92fde810636e7e5a4824cb3b45981a0e30cecae8ae6bd3bbfb69c02860e337071623ecd1c3aa59c45eb858e32707f994d591695e4c46fd0485e943b420719b84b4dc143b2181c5c181f748c814d31668b90f24bed88e4b0a182d221063ef2fa8dd96ec4be48018d30b041481229e4244fe434707071050c5c98fa4d41a62b499e44838e2fb09147d4d94775f111aa91c44a0076d0e10526ad28d15841684613e902a7b5b784a4bb50a1830b8cb6eefc93906369e8d802a74bea5f67ffeef4592d30aafbb3d783e7deb4d0d09105ce72d04188125a648c3916b811faaf6a21bd4d4cf10aec5a0c1dc945a9521347d46105365db2fe0ec2bf62765805f6ddf4e7385eeb163aa8c0bf7d324b093a22f7d33105c6826e0da9e33987d0b09156061d52e0af920ceaed453fa64a0abe880257a36e528aa8a3aff324b0fb4107143ccf5fb5fa136dd0501e743c81f7495a993a7a9ac72cdde81518740263df1aa4c5b1bca63d4d60438f6bf64d4a3d05bf461adea083099c1cfba469540c9a7a61435b80bca0000ae00e1d4b6092d6e49a827e97b549097cad779654913faba808858e24702ad47b2d54590ee16f193a90c0e71044c8bdb1f65dcd11b864392d57cc15b91742860e23f0b529c4226e4811f8c937713787e7583a18860e2270c94a9d48d14e770c818d1fb1b5e4256971371a2dd00f740881fb4fc24e488aa03cfb0e7404815359a966f235d79368aed001045e34770a399ba44e3a2b56e8f80117fc3d04b52153238d469d0e3a7cc076a4944a074fdd7b658db416acf5800d21966bcc53513dc303fe92f6ca1b32d4b10326e6a6354f162fa7d420a043077c090922ea256f8624e580af983daee4b30bcf260eb8bca8a34fc449bd7f75dc805352d3fe7609ad4ad7068c52e33b4144936be91b1d35e03c09fd697f6b9dc65423cd4632d322030ec841070d18b7affeffb190aacf8213622a88adbe27adb194051bb7bf2bc62ad3696e0660c4824d69539e64ab1efd9297b0e0bdd7df630c39e60418afe043bcb451ff25e84b2379d1e45cc16669ab6822061932556d048c56f0371e36415465d18c16197080b10183158c655325a3dadd5b560b2360ac821dbd92ae2fa7a42a7acf8dee95ed2e239b0418a96054a4c9c9f2078931a660a082d5089e9d95a2ed22458b2db4d8420bf30818a7e093d2eb2d49957809cd14bc4f4e5bfae40499925a0abe45e69d2521318690cad17e72dc80410a368b7efd90745809114f41c01805ebd9eb7df1828aba9fc11005fbaf41dfeb4b67356d8d2c2c1a80110a2ec4fbced39b2b928ae2e8030c50f0ad31c7d4aaaf9553e4e200e3137caea67753be9a2fe8a400c31368cf76b24dda453db5000010303a91a7eeed0ac13c5e3f80c109f6433669e5c1e2a9bce826b86c3a98ca21f754a4cf8dbe40299a6033fe691f9591f2f51f061899e0da6fbf52abe4fbab6082d3bfa395426b525a62b904ef19724c97a24e12ffa025f810b47defc40ede2eaa042754c794bc36e9a98a53820932fe645162b973266b128cf726a96ff142352b0926ae7ebeedbcf4b596014624f85361a75b7df6714220c1a5b6f4a4b324791e458f60f3ed72cc19b9bf73c411e40fc1bd73f746b0a63ba3a6902445513a23d82a4b5dea679d37ca4570327da9a4529922d8a094f64f113dc97037115c5e6f48c132e76bf8886025e62b7dd63985db7b08ae93e98ca9534ba5983704db9382f9067d39f7ad8560ef93daf2b28d105c3013135372b389b006c187875022b778cafb7bc31004bf195ff3dd0895589a0ec0080497f306cf3c75a1f29a040b3000c1e48ea5cc2649f78ca32bc0f803fb9927e9896b69681f0330fcc06898876c17327ddabc0f9ca5b6564ca67225e52f1060f0810b9195d734e7db905335d26ca4f4018c3df0b962e6deca9962d10327a964f2daf423b7926aa4a5d4346c24d30318796062aac98a936e6cadad9176c34990bc0bd4010c3cd89ff95fb91e52793968b4e0468e25598071076e82d4d518d592aeb809fa0476605c84ceb7119279bc8fe100461dd8d2a419aa555474a7d448ab1bc0a0032b1a24a49cb2eaea8e36ca66ccc0d145172968c0161478038c39f071640ae197b264f2a01a69280032c090c3b61be32565eabb730088028c3870494d44dfa4a275f90b07f6b3a62447ff7f48ad1a09da8b968041018801c61bb8f83d312ff807bd34ddb08132100b186ee0b754a760992f681c2260b481d32f4fb596db647c370301830de5a997875f673c598db4b741e3d0c05803a7329e92372a4e022fb830247f80a1063ec4cfb94d6ee6268ba681ab89f599a496d22112c3051868e0feb285b28d24e35ab600e30c9c45aad211a692d000c30c8cfd071962d41ad174712307a2d18243a30cfc7b99672b4d3949ca910c30c8c0e6fb243379cebf51df301b03373ad2bd2e26494a4462e044040f7d15d1a46a65038c30f09541c8b6104769a81b0c7c753279426e48a9319906185f60fb5fbc62975a47fc6b24046078e11113c9fef9ffaa3287d105b6d428d51f5754d0ae6aa4599a4ee003341200830b8c48d7a98269ee18ca13dcb8a10005cc98817c035d74d127c0c08c195be03d87de9e28e1e96987a105f64cc7d041ec3e30c0c802e721079545971a4d8f1d16d81c796326216f185740e74c1db28bfd56e02a66f68a0ec96973ea0930aac06bce6221a6f206993354e0aa468b0aadf89a9d62028c297039a4d32a414bff27111a0f6048814b49640ed31b31e8e93900230a9c087a2fc4d39ca43d9f000c28f06a6a44ccc94d5d502a3304184fe02ba69096f49350e67921c070022342321d794253232d078c2630f9c72af37ba8a568aca1000613187915d4af36a95cf5daf8c2d1043096c08e96fa9873df35d26ac050029faf3fad093d31a5bbd448c3190148f068059bf52d66ef901923bf1eace06aaf764210b9c72a188f9ff4c5948c9a6378e0a10ac62a77084a4491f719d3399a0137f04805e321b2c6b0efaed31954b06dea962789a6b47c993123070e270117094fc16a8af92586a89a6448c1c0c3144cce7275f3a8ae79548f52f06ada31a4d51f7539b413f8000d16789082bffd0e3dd31e09ccf01805eb39da26ed639d5b4913f8008d2f3c44c1a68bf729df42279d6a90808b1b34be7037c1bd4728f8ecde762a4ba06072e61a75f22d9fe0f45bea88dc9954d248199e073c3cc165ab77d027833c61f94eb0773a832cab0921c939c1b8baa88852f24df0b9e2080daa214df0216c7754b724ef0c658293a63ab2a6ce23f53e98e04ab9e48b1ad44ab29c4bb02151c342ecd38b765b82c9a92a497ecaa2c6dd4a30394d7e68087aeddba504374a2d26097253fc5d27c17f8dd08ea0a5e67f4a82cfa8adf7613196dc4d24b88ffeff9699ef7e3190e0e4782c31cf1174102a8f60d7f3a71ba5c4930c298e6035d46628651db61d6c04971db7da649fe7df8e119c4a4149b3d32f82ff68317ca32711324811dc774e7921672d119c8e57e90c4ac8eab210c1a9a484d4f862a534bc1a695e3c0dcfd13772e038880bf03804aba1f7c1c79432dd202f52d080b3f430041b736f8e9c4699f6da0bc1e656cb1d25d64ab6122118f91d6f7d62e6cfb11e04eb112be9d19f20b80c49738f504b1de225106c929982b0af511b6904045f7921f7e9789327e80f8c6adb68c7eab7d1cec30fbc67045922aa059dd85d7af4e13cf8e0b1079b31060f3d9c7aa33fa5e47469c005b420c7068f3c9c071e583d3df244526d2a6f907fe1450abe01382640812db4d8a2015a64200236328064021fa021804a78dc81bf0d6e5b25a2259196871dd8fdd7f50e0f41e3df1e7560cb5377ba9c3f5552e2c276e0410746e926d55deabf23a95c0088e03107ded42c4ecc7d3284ea75f09003ab6a31d364c7a0db937160d399507949b5396c64408b2de0c05bcee94ba998ea2f21273cdec0e7ce23fc94d64d88f90624e1e1063eb5e670ef5c9a438ab581bd931f6a4c4653a173b4d8821cc2830d5c9a103fe4f4e59f7d6468f0580397b32521c57cf2070f35b0d92e53f576877c927273f048039323270d41b99fb7bf72f04003939406952f23581eef68b4c1e30cbc66cb6baa63c94b9d3703139210adf71d595a82cac0ae07e193840613912032f09d95425afd6360f2e4cd21aae7564c550c9c309df933b7835fb084810bb5b2d21002031b9a2f495745be53ff17d8531aa4b5e6eceb96e405ee5255b6d6dcd0340f011e5de0367b68470f1616e20227d2eddc37d85b6074eedc9c72b25a60ec46d5e999ba9c72cd0223ccf46de5a6e9742616f80f5a91b3d66fced75c811f11f57e5267d660b202a355edfd29248f6d972a7059416d95ec8c1a3c4205ced6f2b628d3794c810f3993f6bc9162324f57800a1e52e04b480b3d5513a4f5ed110576f3e514c735a45d4e99c0030a7c861063984930eb9026e4c2e3099c9b4e22688eceaafa3b81dba429a7984a9dab83155080210ef36802579b635df44d954ce916e1c1043ea46c9aa563e6b10446dfaaa6bf4a15aca246f050023b4282dede5df90725d982471258f7fe2816af3ddd5624703bdea7bfedf4678c68c1e3089cd064fb7d2d2286c9088c29c91a52d4eb0a1e45e0236e6886c7495a821c041e4460834c49176c73fe7beb31044ea60a91738a71250979088111312747b5adcc23084c122aa44cf22a0f20f02121c7b6cf4954f7a6465a0b6ee45063c3e307ec7dda34fe39a66e30053ee0dd83728baf21dd644b8db413a4007909bce0225be0d1035e350615834e212c49be465a5981070f98aca24b34da6ae51e3be07a5dff4ab958238d460b6ee428393c74c0bd8f10aab27257ea9f032e5db598a794e57a4133669475c10307bcc6fe2e3d6ec07e5433559bf2353da7461a0d1b393c6cc0267d6f955c9e46b68b9c056e47c3a306dc45ec51a5ac2d66b088c08306fcf7a6a74ee9e324b8d135d02c181d3529ffa859324991460e1c292841eaca400c59b0f9a2b3a59be853da353162c1c64cd66964ff38a41003169c46ba9c41ad826e88f10a2627156a375fd4158cf4aaec20820ac973e7857371a3015a6cd182193368588c56c46005e36b1152455256a95b05d7d93dfb3e6570f71055704aa64bb13685f869cc18a960c7dc3666f7a9fd9ca382dd08c94e6737113b055f6661f984c590738818c314b9aa55e52c8dd1d42ca34ed618d26dd6766aa499c0868d1a34ba485cdce1214629b8bebc3751ea79498514c4f4be979a6246c19b50eaf52b8735d2708b1a39b8e0008e2eba5080165b68a1c5165a68b1851618d0220311d0824999ab808b220a46a407b5cba6a2878c8682cf2721265332573140c1e51845e5ec4d9a945e7d88f10946342519ef3bd4b37ec4137c0ac9b736d6a89887189de064ce75fa3ce48f5613ac430c4eb07e9e3668d9654e21493818b07088b1093ed3636a11a62e52e50a1b626882d7789692a9a568b14506228003c71a62648295e456ee27caf276d4628b2e1a139caa84549a218f5ec9b900a315625c828d25d4a5b78cf55a1e4b1435a9d363414670e4701c38ba125cb692b13b6f55de2053236d8d023128c176fab84954526692a449b0976152d46411de16920497432af9313b4511033122c1a9bb2074eea0b682ed376cd0b03b400c4870d952ac8b56e3997edc0208311ec1a70f96a643e893f96b4770324cae5725a9d9444412a3114c6c537aad47a6f4d931825127db2f4af61c82ed050d37bb8bb10836e91c159328fd0e221e85188a60cc738a693fe2798960f74d88a4d01ef48b880836289d79b2e8e468216bc1a1c145dd03ee109c8a95b7c4daab9166086e4da84ea9757c73eac210a310dca53549323b48fa3d1182f114abb64f445cd42fc41804af6bde71b437444e108c972a59225e786bcc6ba4adc0861781e0be53d49ecaa2e91ebf91022fc40004bb9e556ff1b56bf207fec2bbd2c8bdb30c215088e1074e864ed0aa8c90009f10a30f5caab547d172ad97748d5c420c3e303a6811e11b74759a7a0facb85568868c9ea3c50fc4d003a3217bd6504d4b22796083f0902e3f2fe95c1762e081dfd35c53c29412ca3bf09691ded36f8e88fad8e18b22aa73fdf31a69756077e455363b9d0e5c5fd4117aae29a84cb939f0f6197244cfb9dc532907ee2a6dc516370b1762c48133f5a733f7a4706064ceafb42b421be30ddcc4cefe65b981bbe0be9e326d884fbe0d7c1c25b24ab610830d7cee0a524f6de7b57e6aa4ad81fffbecff4ce376e5500397634bae1d93c68d1b9e062e9d4efa13cfa0611349f7ca83e40c7c9410724e976fd4c5941a696660e4e94b2252ccad0c6cbcbe9c734ceb924a9aa3b9487e821b9ea39c200619585189227d6d53236d0c671e59694a2d0656638ab9c9ae114134b00431c2c089b098f64352cc3e18f88f16169479ab4d8e91408c2f302aef87d2ccb92d6e9404de8517f80aa9dee3b9f1235d606c94e609e5f5e29ae4029b73cd6d4cd828114f13630bdca9f10c95a216626881510b651d923784329d2cb0164310125c628658e0748a3997a8a0235ffd735760b29fda6ef2743926252b701f2aed2c998a355e57818da59134354becd1392a303a07d3f9da3b2429b11a6953e0c2c7ff3a6a6ecaa954234d0a9c950c49a7ccbc4446a2c0059d114385b4af13d51a696b218801052ed365ccb81e47984fe0b2b86aba31218244538e14d810410c27703a66a769319926b0a33d3f54d289099c6b6b277dea2cc325303176895e9126253022b609ed96df22799e04fe235b4e503b2652be91c0a5da65151162e63ca523f0f6499d5263954da79206621881cd74559335bc7b3f5223cdb010a3086c5ace7fb7691a3452fb8d3b33821844e04352a9e933aa246b085c4a7731e9979043525c084cfed1e29152a74d9fad915670e4c061e30c023182c06afddff5be8554c2d222030ec84004baf8e2040f68c08c19ef89860abc701578c176851840e02387b5c6d5fed1efb161e302c609317ec0a59b6c95b4e9bc9bef183ee0eabff7278d27460f38d339692d57a8a044bb80183ce04d73e59113a4694b290d2b7a88b103463d3b2fc92b0fbb910ef84c32e68831b988a63a07fc6bce5477fb49e4d662e080338f9c5493f73760e409adced33f1862d880039c08cf9b25e97bec3e158cecf15477f5a5af4605233f6ad67442648af6145c060dd994b6ce501353b01edbc264d2e9631d96825196ddad4a65fff6470a3e68d2ae4e3712832419551b05ef31d7c79052ca8f1aab91562307172b30d645a30c5170a9628c1d42893a335d8db44caa38b6033242c1574a224529710d3223a0e03ca449572363b02ffd27f810ddd5d77c927d9072514c8b0c38c0bbf8220519b8fb820c4fb0994b748a87529f773a199d6092f0fba821d489dcd5066470821beda5bdffe287fd65a12063138cfab497fd2fc752d76568824d21fd6eb5780e299a8c4cb041bbc44bb2490626f8bf20e4e4d23b694bc4041997e0fc3b8752d7cfb5ce58a212ec45d7915ae973ccdfcba0047b27c4d742f2c89804fbaa27d126e73224c17f8dbd28cfcf2163908c48305a2a5f8f4e823e2a03125cacf348224ddc2ec87804bb7efb5927e9e4b9ef0826fa9b66cbbe89a7f346706bd9e79a274670275f456d1cb9d79d5d04f717ab4aa915c16529a5cc3b79ca5ad944f049e93a11dbdc537244f05555715b63a52c113d04679f4fd4784ac92b260dc19e7a89ff1d3b3db68560624413797dc44ea247083e272539550461f69a41702129e5a9b7b1bf4e10bc650a2a684fd5039c2023105c7894689faaad363f37640082af3e15f33af687468e147c91e60f6cd75a249d5367b3f2981fb8cc1941c4d5f4411fa5566b5bf36b5c1c4f34bef01b5fe05824c8e0039f2b55ae05197b40aded84f25e53da480603197a60edc5f4b49d87a4cf3379487b9bb4ef3c92aea3810c3c305a53e4a9ac216e6db0469a17ed5d2075818c3bf096d3856429febb586d59906107f66a74529d3585a424c70b1975e03ce4f3b428f21a8932e8c0765f2a33515a3f9ad648f3c2067216b8c998039719d6d9e46a726074f342551c292a6e70780964c48153aa72678e1fd93c66ec0a32e0c09a0875a5335310163926e374c173bdafe3062e89147c9372977c226d603de22515c911e375cf062ec8f83942eef1d0a0b30626f88e760941d6e566d5c09d4649232ff4594cc934f05d2aa297ba0822f468e05cd2441749f2abd233b0e9af36f456e4aa57ccc0e7a9feab495f414e481978d33962b628f53e3224039b33e62c495a8b0ce518380d316ed5e8ee754a254186183820230cec6d8b16111fa920030c9cd20d79a695a1fd5f20c8f80217f2b95712ea933686d0f0027b1a3e3182b264e0ae0b8c12215510dae45c60359dc817af94b6c0e8a79c1bb5d4f77aa405de5cdb74446ada484159e0765fc732af57e70e6181bbfb492b5a934e10cf15d8d4ea262ae5ea50aab502174be68d21a8282ae25a054e0615d5be4f757fe7cc9821830a7ca84fc1c44598104a3a05b65b35f6a4cea14d4c2970e3253b8ddb8b484a13053642b45819dc4424cb4081dfcc1e2a8ebb2425234fb82f67ee04eead83762a4fd9bc824d6024e75869344529e5392694efb3658d29a42c81db10aa4fb608b5053294c0d6ededa72f51d1b93256909104fe5350a1a35208a9420709ac67b869485abaceeb2338d691d35ea669047ed4ffc9319d9c31e32c0e328ac0b85f36ddd490085c7f705bdfdd5279913104fee45e0e35bdb0b31b0d3284c069845c55d922230849ad749dec723287175c68e0466f607b200308ecc4aca53f758a3925f90363704b63e6dd1753b620c30736c368c8e841c9e0017bf162d555b56ffea406942063079c576b281d7453788f3366b4e0460e2f3c083274c06a66cc3984a0e1a6fa1164e4a0dc7e3175ff8e051938b0b345634e704d31fb1b6c29659b670649deaeb962c84f8b20c49b820c1bf09ab531638990242765041935e0b209194b8aa56c70c181e43366e0281cc8a001972a7808edaca79de41fb3e084243522869cee8490f990056b23729b50e1971c7cc482512958127d13359b5748830f58a0ef6a820aaa57d493b49a3ccb1e4d57b062a7720a3ab5db0a45957a48c242061964057b3a66cc1fdfa4986a6f7cc102fec286aee224355228212fbeaf0a540a75de49fb099954e03f298f28212accb415c93ae8ebd66e9080d8ad59093e4ec1a78ed0a31282d0112153707b766934c54e29188f144b7e1c99b4a73429b80dcb2958688ba697320a46e9fc60f92a9ed3591fa2e094c79f9433fb72d3130a56744e4a59e6a0d1e20414dc79a83e0fda8f4f1462b28db1fcdbdaac91863a840f4fdcd9ad734d35937a1df8e8c47d7082ed3ed11925d5fbbbffb10956c5f24788e62e2ab626d89cda749490d68f4cf031a78da619417aeeeb295070021fa021801b7c6082d3a172723b9d3ab1342e6ed820c11962fab804671da3c6eca492363dd6489b3163c68c0e68b1851612c08002dc1b90fc4af161092e5432f5ddd6a07f3c3492175cbcb1c0d3096cdc6840f2828b06ac81e2a3124c3aaf9843997f8e9bfda0046ff6edfaabd19304f524f8714dfde9dd2aa693241811c5837787de3d8f44824d31b9566ae7cfbaf003126c2ea5520ee54978883e8271cf9e2cd3bf594efa8723b832fff8fed9f4d108f6745a8d765c8b91941f8ce07227b73f09f93a7ef063119c8668ca4c89a24ead0f45b031f55b5626a5f9d6fc48041b64c829e59cb4acaf43b6f0810846e555d08fa1ea53c80fc16e4e272187e8c3106cade68e8a26f4c4d41f85e0b3a6ca9244559b90dd072158cd49f976f613fa921f8360b527dae9d54b3db9fa1004db16d4494c8baffdf98f40f0e1c9e4269deb0720186dadbd1d524c117ddb2f3efec0a80d322c5e103d513f303e29650e1d7699bcac83c1471fb8b8d7975797e927311a7ea38fe10337d69de4988a8468e11e78afa0df752ff8a107ae23951069554bfde9e0f8c8036f1f2b92f094f4c64e1b5d9032047ce081efca6b394dcfc23bfd710756cb4d6f527f69418ee6c30e5c52634adde8e8764ca903636d52cf723a912fda0f3af0312499f39afe1f7360443a2d3f21ed36c6fc871cf8afa064b2ace79dcef2110776829d483929992a09990f38302a26a1e3e8c9b13ac67cbc8151dd793ae712a279ff0f37f021ae96c689e2973c79818f36b062ff914c722af92967cb123ed8c0fe27fd1b542acb7c9f123ed6c0c9d8a984d20c491c5cac171f6ae0fa4d997e0abdcf7da6c166d4f840032f9a9521585ac82dffe30cdcc99824050fd10f33701e296f44cdf6471958eda02455068f9653f0830caca4d8c9d5c61f037e8881099af268efcd787dfb4718d89c967b9f2fb5ed8a3ec0c0e7a0524a8a9b4654ce7d7c81cfec761df32e5161faf0026feafea22eb21f5d60cc3f075315838841eb35d2f04a0d3eb890a6a45649e70d8eae912ff8d802efef9a4ce87e2c65f91a6938b8282df8d00267e95e35d384605b1678ddcb11fa29c898597917366ed0c002ab959b4b42eeed3ca181830bc2828f2b7039989652d32935d2f0c30a8cb0cbfadb552724c96ba4e1e81af751052e26f389969ade23bd393ea8c0a99c838be7f4a9c24a16828f295c7d4881539ffe63786f28f01105c6b5cefb92e9943bfe8002e7aba669924ccbf59c65c2c713780972926f50c92685c8096ca520d4468914dc6296081f4de0ececedcc474a0e8f0be183097ce50b3afd268b153f3880f0b1044e54b99907e996dbb4317c288153f25ce28d6b08258307868f24f01394298d6216eb24db850f24704974344fc1f25d2afbe3089c9d7b56a8a7f248b9113849933798e5ca471118a51db47eae8e296e0c143e88c0462e2d42771aad1ffd6bc2c7103895835d8ed61b5397324008fcf7557592a3a2dc844a193e82c0e8ed52f23d8af85b3a0c1f40606f54cec1e266d3d3e7fd80310f26477f1041552f1f70935f538af8d9470f88de1953cd552cab32b6c7f01c644a2aad91f67ee3cee40a3e78c0beb678a50ea6c55fb4032e551295830c2a4a2cb50ed8108fa2b565c9d19e02143803906f60c68c1933707861e32307bc85a45719f69124c4940b1f386063d6179d379fcc6a9d0ca4143e6ec0fa96fe98a23fb796f861035ea4a80fe22afaa8011f16aae3ba994c42947af8a0011b928520ae51fc63d299055fe7aa5e2a043bb1943b78c88211a637c87a3b8b6beaf400128b42a98d31e8178f153c60c177e8535afe1921063d7905a752b44eca21befe8ece156c0e52fbce621c25540c034660b246ea4d3ddfd6eb8bc0d9e438923de514ab9e08dc55bceb9359a6213f04467b7fc8dcf974d0bb10124a973e08bcd65f32ede974aa4060358a12aad631acfc01db5bd9da366cb3877cc0dd081935633c1573ba075c44d752625a76223c60bbdb2bfba885d276c0be08116c475d94d4ea8037e91fff4348b12d25079c1275e341e898f7c5019baafdb5d2ed4e94dc80094a8688faa7367fd006fc8e8b0879a38abed48051a35bd9399632b402d080514987fc591b29f7b3e072c71169b34f845616acd768659094c782334d4987da0e0bde4d4f92a04fc96bd32bd8db20fb3a23858b5cc1c65115720e21d3d256306e4944922521c81c6205ff12ad5acf4c4d735661b2e0aaa9fe2655f09e7f6df487a454b0112fa994dd3c882c49a86054cc1e32675db749d22998ece1a7b326b72c319982f5b8a9a28fe54bafa352302a640549dbe2dd3a22053fcaf49b0a32e42073340aced33f9b3ca5ee4b098982b3e0b14a864877a38442c149d09031e974b92642a0e0c736c5fc63a15f1ff409eed426a6679ac674234f70597db5bcfb62451a3bc185a8ee649ba29e082758f3ee64ff7b3a68bd092ee55431b5e8e4f94d4db019fdd3645e6a4f4d2638f59758ba2ca9fd10136c0e6e962aa86a3ad9253879273f830a5132a7902538b7aae41f9a45f44ab09f16646fc85322bd9634b7ae3e097ee3faebe9cffb133f9260527e3a2d6a234acc23c175d61c2dcfb4165a487015761f43c8a052c4a047f0174476915b11a3ba8e6037e6760d2286598c368293f43ee249533a9711ac9e5a456c11a96d2e82ff4a4205957e2ad75404a74ac6dd74ffa85f2682913104d1a33aa2990c22f8daa04ee792f9c6c2738833ea7bc7102891b1f772af5308c6ef53e9484944ca09c1760c4aa8d1f126c02018a55df42475775abd092008cee4673a61fe23e4a809100836bf490d299dd4baa0260020b83eef0d162d2c784e13e00f9c68eec578e9cb33dd04f00313f226dfa46356fa7a1d40803e70fdaae555793d8a960f7ca89c83c8a7ddbfee1ed8533a2664ff27c9ab1e182b8d225cbf4d4c9a07269e59b020647b87140f8c502a478a3596fdd33bb0218ae6ffdcf4aba71df87fdb10849e75607469124ae7ad9c7d3af0a92798a64f4977690e5cd0294ab6502b21c6c8811bdb507a648d3e691cf851b5a3e3a83c2584032333d3a95fe40d6c599239ed836ee054ccf27b450c53b10d8c46cfa4bb535e8e1f1bf8f4c9a37e1b9244afd7c0ad5e4e509e1af8b48dbdbb9252779706ae425c2de88b896b8d06fef365c59c381e72aacfc086dc78d994a87863b51958fd98eb4fbf5344be0c9c9b4e4fe6fd92813753e331df6fde7d770c8ce7a9241649589eec8a81c99fe65a22dd30f07e2295debed62475c1c07632f97f7fba5d93fb052e6e9eaea02c45af5c2f70be25bf53ba0ddd97ed026fa6f7d2225ab0bb2c17b8b4e69daa73251d25bb0526b59b97855467fead16b8cccc9723699294b4cd02af31fa885930192fb558e073e8eb9ca87fbafd15b8ac29079dc5ad2e7b2b30229e43b036e9fdf12af0df1dec6490ac224a546033849482f9a46c6a4a3d8d9b72ab921478d39bb3461b09ea465160db4fd53ed8a649395060b37c54f0c8392ba69fc089144aec4bc809ecba68f8abfdff649bc09d90e857f1e4df4799c0a88adfbde5934a2f4be0fc42e5de88d257224a606cd773b0d34147962481cb4f2aa2aa4dc5844860f4a5dff020fcb67447e0f45a963693a4533d3502e7172632fb7cb3e6b4084cd0542d2aaffba54d89f07b4abca0623f0436598aa2eb34a488bb10b8917ac1f4aa53043d089c85a85dc27320b0eb5b17cd6bb3b9e807ecaaa94b0cdeff29f201df22a3c9c8d9267fa807bc5e92d57f5726f38707ac8a4e9a4209ff08393be0f488a42dd557c8bf75c004616aa7195295da39e0324b1635ca2cb969098003d6c6a4e8e9ec66eb12e0066c90b4b6b6173b882c016cc056b0983ea5a8854509500376cc2f5945ca909f24000df824c382e6537b92ca59709ddfa53b424a7e1759b076a6b641c60c522bb160537373b5a5dab44ac28271ef60b28469935992af602bfa75bafd75c82d5dc19767325532877c3995ade064a41cfa3cd5534a252bd82832e56e92af828ff77f27732d784852056726fdad3e948e3f4a05639bf756648aa71ea182cde5162258c58b203a059f47f209a5644a326f53b057693de7c71cdee952b041e5181e942865272505ab19644753fb9c951c05ab9e7b629be40e188028f813e969292d9a90985070f9b369b3112aba33a0605df3080de9ad82b27c820d9ea2acbcc4ed2a9ee044e3998a1576824ba52767df585fae71823b4def29a538b6fb26386df17b4f838d95ca6882179d420a3287ce142d93894e49ddf7cd31c18810fd4bfee8a5892ec14634f330f75882d5887942eeb7128c58048fda796b2f4b09d64b42889663b77b26c1f6ada5e7ae24f5488251da368904275cafa4e45ff59c16487035bad56b928f607f37a7183db9a4be388213b2bbc472d48c498de0f2ef5a2df3525c0923389d2ac4f4b499b4bd4570a579d3798cd1f46e8ae0bf2fc6cfd024a94f04134f5d4cc92cf305218249e199cba2e6113987e0437f4fcadbcf905531047f52624816c5358d5e08c69274fdcf55999a44083e95a70f593364af67105ce6da82e0b296ce9cd492403021e8da8b360920d820f9435dfa482195e40fac961a651994702d11e2076e74aa3269e5153b47fac08a12cacf54be897d113e70faea6a748708d1f13d70da293394e4e49a263d70d9572b4d06397ea53cf0ba397a7b42dea04a78e0255be5a6e5fd7fe80e4c7c5f4d13f3b8396407fef692a759882d1eaa03ab55c2237be8c046fdfb1c648890b4670e5cfaece6e37127e7550e5c921d446853d58b6b1cb8c929489cb89722513870912708cd19a2e4ade01bf894e9bfb265ee0626a8a50b12b462d0126c03972a752911dcad830ed9c0af7f3c0f31e5f59ce11ab8ec987b29c343c8ba1a38fdb739bf47358ba932810fd020c100d2c09659ba9037d9b77fd0c0a88c1c84ec4dc163e70c6cbacba944e47676d70c4cfcfc94acc192d4b60c5cbccdf849cc92a62819d8bdec185449ddbb730c6c4a691e1273c8184d31704a2b881034ab4ac78481ddea949426ffd5b380810b1e3d8bc48e6842f805be4b822a617b810d6d3a5a43e78917ec029bd449cd1e6348ae8f0b6cc564a62fb40546a50b6943c94f0b40184016d858395c6428a1165387055eaf45555cd7d45777052697c7ee4f1983bb6f0546a89b8a14325ee8be0a5c341d4beaca2df26e546034d9d88754973aee53e0b54308b9a69b774752e03c6ccbe2e6f5e09e28f0d1ca74a6fa483a7aa0603372004fe0b3ed56d079438a41ea047e937abcba4e96f45e9ac04f2e254547caa55e6502e7d9efd746dbac93b6047e4350358f9272168b2981c9196a2f51f34a12b124dc5929a8a44c6848602c990613f61ad2373b029fafd6f92a42cc296404f63b6e124ae494497e8ac05f87b2e021a6a63d4418c0106c86090620044647b030192369d6741058fb1e151d2d6e9206022f4a74fc983b6e08fb078c48e2f53d9a6f93d6075cfe911ab3d268d5b607bc9a0c5db595079caa0afe41f6968cef809131bcdd2c7e854cea80ed986e25541c5d269303de2f8ffe18623a6d161cb039fde37a5ad0a32637e0f7b73f834a9d43fa6cc0c9f7cd394ac6b425ab01b79b7a5574a6d8e901d08011ebb4ff2131df320b269d5abf0c9a6a4177a11690210b26554822a5309142751f0bb6ededaa72c5b0711d169cdf7755f06841a8ca79057ba2927e4ac2e30a3e7de6f2fe2b916de956b063aa6962bcb4b7cab082f532939e2d5d7bcaaf82932cc1335a44ffdb8a2a028daea8b4b065e2481c0e0643c150300c86e193d305f31308001838268e4582c188a0c9c27c140005462c344e3c2c121c2222101a87832261181c08048461302818080302c140201c0c0fcb0a3d0f70ba8f5f491016d864f976f89f9f6cc6117429941c168278c0b9b07d0815e404de0c45821520e670e1e5139aceb8b6510afd64fcfb39e4ce9fbb2013ec291e18f7c24987cb2b154ec964de2daae97b54239ed0ad7351dc81fbde55ba856e92bb72ffbb4b17c79a275646910142c82f341eaa41b97b68e27ac682828f1c8ed83a29f60620933f7c98077ff8c5c23660953125fc191e84fbe020fecf5a5be93f0b2486d02df86a78cc77b6b8e483bdd94b2bd69876c18ea9c3e9b8bfba31ecae906f3034c80c7a060782f402fb1d7c662be5a09df06fa80eac0bcd3aae9906d46562c1d45d0c3f2365681b6c0181b3efa37c48116e0115b0e65eb8022fc6863fe4a0d763f8b5de50f777e8caca8096f7719c8de7cc137b751e9237f21e3cb55f4d48255409658606a28d3c6a5412880d5143821c3e4bbafbc486aec342c3dd306a78126e0943c014bfdf2fd9ffbc768945ee80761076b837d7b9864e17dc05fc42e2a17a082bb419ea09417942cfc88b78ac3db41025e402cd790bef83b7f570dd4f45ca0b35c1c20e81b9503d647a987a15f4198c099742e804a3b7063ce5b68c772b397239073d8303417a81d3ce2f0b9484a6c08f50cbb5f9557c4a2e5edb70cee08a250e1e1d374c4933347bc6402890220472ac522af9fdd060527b71f1474ca16e3143847d0023a34a28333400f989c245b4c8f72f13320909325cc1b101a820efbc43c2d5a4655adf62fdfc53d0f5b676af1c79cfd00bfe584299a141e82fa41eaa87cc1aeccf08436b9da1e03067d8273c02a7287f9a631cf4bf3f725e3cf5cfc5a076eaac93f035292c16b610ca441f2f119c84a661ab00cc102774121209354308a15dc876c2bf01dc48c5f7512f285ee09a81af08882f2eb120a570cf3f5ac4e9a94703e96d4c5ff2c32cec270550797e4294904b8893a0065426c1af2b1c83ec1fb8460349ff27e0a12b72a9f95304a5fbc2d961f4a20629415cc068e654e214fdb546c9c891cee1cca69d54c4a07118669813ce09e1de0915941c0ce00fc382eba0d3a06cb01a44014e82766a5c03693bfcd7112c0272d12f62eea86ff4ba7b6fbec6b15e309a31356a6562f1ae8badfb56acadcd8aacd5cc747f2f97081490c712fa1c5f8a39ee7d7220591a2265feb4b715eb5f0e2ab1021add3c48f29fa4b4f451ff77bb3c8e8c08701c73b9772e720a97b8b690712a86b69c3875324825807cf72b681be8888ad73b25e053ec152aa72a4f20447dd9e25f5c4f910f5f588d9d391373a491bd0f4807514725378a640fb1cd88b9917cf0329a302a1ba8b535dba013232201cf5a5b87e2974edb1eed80a2aad8f699e8aee1773c94cf5df2063acc00c022b9b900db54fb2fb96be32f8bc82d66c77e572182a8c777095db6e84879f0c1c4b91fe6443a4ad5e4722d66fce13534d8d5ca091de539a65b59985e316c2a8cae81fbcd2ab326f80100310aafd155cb3bdcb99ad4ed9a9cc445b593f849c592be9de4ad3ae91a3ce030e74ad4a5e55c39d6b5faf9597614b79322e58f91828ee657da92d9531ffd93648bbd4a812cbfe527ba142e6b4ed819d968642400a1d9e8150b031e7653e983d2209e94f8ca91f889e33ff6f624ae3989c830eeed45ef2dba9d90422194841513e6a2d06942a515724111cab1d812ff6057560d02cf3f4a90b1ec6bb134bf5bd0732520061c29e3113b1bc40f61607a7a08c1848c4aa153b27c708c8aba0d9dbc9f713e81d7ca9032d8af3396d032ac59ec61a1340280c58d192117ea89176d1b7b498168928e093ce7eba743e3ddbadb95acadfb5ed98da9af6ef7a3db279ce88a6a7e6af10cc06cdd5f2c32456c144ece54c0cc9190028d80926471a24623796b91389ed04f6c6f566d0e16ddcc8959a5fe8e509032ed539b38d83c9c803d6dd24db586f08fa8f2eafdacc79eae9d06860c42e295e0d05bae40b3e46b314daf094539bf80180c0ba573ba1835ce2964cac961a85e3c580c5feda6dbcaa94b5ef2850c357302fc11dcffec3860800fca504e407b72fd923b7cb1da25148ac503fc90d4b3b4e0464ff9ea0dcfd58dce9ee043c6f49b1ee0c1e9239c8c5a547805f3d4504601493944bcf9307907d60182d40802b9a1feb9638a14165650c081d046848e0811270465a18a423908f9115236217d0a9cde4f16ff327c68710f33dcbbd31c94af58edd4686cefffe637aefe4b6a405483c7499cc2e07035820a26473d083cee6b561c953d1a211529e8d3ebe7407ad73fd316328e48918d144de870ac3221ba37b63165834b3125ffeef762e3434340140c7273b808311b62dd835191475fcf0f3aa7c5aef663dbf6b955b3308e298e85e3acea2ec7bd46a2f949d805b6492bfc63818d05a83342614185ae81061a9a299a2851a5aa490088d80cae64deaffe05217fe3b47893f8664bb16c2835e803e8e9d1f3927aa60a5aae012d3c5751a50295019481192141219ca4ae09b6469bcb85f67ade63210000e10b4c56ad48c66aec4a5b1575b37196a23dceccd8811d41571ec328ae81eca57a7dabe1a4f635b5a10905ac8b32611af6ec7c027d574a090559633b95de2ece656234bf422f2f9621e535a6e3940b322791f5f99c4ada6ea2e1ec3e5b4e715a660a646a68faf9cdb92f5b36dd7d602bcbe6cf4390bd87626a1618c7d9751a3f40a926835c23d525905d5d5be3b9ad2c6d8d02256d755f6692f0f710c054064598e0df5d47e94192eda34b7c1603c8732d6deeed3e9efc0ffbaea61e78ecfd22a2214e25ce8150096d90ca5296849ab31629926554339c101dd6f88160be726314416170260887ee0f4834dd18684c104d2fdaaa53e351d64c034a730af7c3d3c8ba64a11d6c121aaab4919fa90994ae1d7443dc4b69dbd7daa9054592532b53c10e36207650e0630c1776d9fde6ed60a97a248e40f4a42ee555341d860e64a3d13955da00c5b9eb23d732a908474ad49d80f2b0d767f813add37042b30ce9dcf8eaac7d7f82f1100cbc123cc06206fd97e78d155decae172ae58a8f6decc3103587d4d0cdbc87722ad7f9129bb66ba0954f4649f4e224c362b9e31fa9f88538d8c39d3051d2b68475da531fc0094ebd60c52ac2e38170526fc4464d64cd387fcbd677f3ad0cf71031c179b0a5a1ca049b86bdfc1b55b8d926340e1e42c7e6d3f9c345f84ea7e75d7f9247f8e116d9099db5cb9d53ad727af78fe0347643149152c5e665b8b402a76cb87ece60107201bcccfea7e5df35d6ae7f075186a3531c667295af6ed3fad1e528325c56f5dab99cbb470d78c502bc7c43a63199b0b8c573ba3a3b6669dd25074bfd4e3e6b6241135cf710f0a496b9d9fa1f89d68504cf65da483262183aa10a9080a7f95c62ab7743b55754495ca2361141f35b27d13b898d4fe498819789574301584fff2be37d434f7dbe30ab11d8f603c4648e2a1c5e36ba86d0252a504ce2408cba2e9338bf0791b6d2c2a9af4da5d051c906d5b2f9e86cfe91abdb7ff281adf8f188dc48c15624d70f908f33d9d7e288797b41b5846f0dafa65b4bc8d81653346ebfa19f3cfec6e3c743c8306255eb6b4addecd55feb25392f55597f20db544e1dfd3260d71cb5d44563c657a365e580bf8df1f9fb3383ff4d1923012272d67427be0d275922c2da74581c7ca846600397a2e58ac1548343cb0b75d07a946ce2ca9f2cd5fcb7d3c642b1bbc4a9890a6e16093d3cc641443b7464584612baf3bc824341458ad8e02dab48a833d4b6bc32822a4c17e03ed5c4b0fc9cbb95968acadf06880ac2c6b14492faefa1769b704c89b202b27f93d3d2c675951c6f32f07d9f96718d55285f25be7973aa2adc73aa46587cd51353b1f17b3f2f5279aa86210b5a9c3b18bd4fc878020f0d2ded14f6011188453b857bff2161ba5a6c73040350dcac56f6a0611917619bd9ad03a4ab53ac8e737b327a2fcb86e38ada87f115058f64b4fcf2798fc10e690bdb636c2adcf0355b28b9b09110c19455d8aa6d7121b28d76797d2cc6ea797dab4b87c376127b7167d51d2dea67d7f288b4d10442a9a817140106ae9e3ad25041c94e40cabd467172548b8e5190b67346aaae4ab45253f2aa68cf3104d3982c2912e086d4681bc55984be9b8d2e578e69a8b2364dc8c56a038cc7d7783b5f67493afa521eaea6092d4b764854f9946100ddf67b775b42cc84245dfd1152893d2d11a3a28c42d52a72fcb1a833654a8a548d5854e725d4c09611b9f77b0356fbf4cd4aabe061134a0247667b231855219b21d394e66a3a8fc3b8d330aa1c60fab3379f1a93d3c2323c2369da8493e746af60547391ad642ca084c4cf179cadf35ea3495c2694ddb220395bfda006f6e31119c7e02d0d66cada4dade822b09ee8601c3a8d0867b09b7a3155749df05fa874d12ce557e3c9f9fb2518c31a1e86686e597e20a72b5a3931883fe62671244b3ebba5f291dbefb72fc9190ccb37016c40dcb635ceb0587a3758a6a3cd6543d5182f1982f0347fc3da6c69bde2b636f27cea1a4fe6d8cca77e0a54e961bb4a01164223862dd4181010e353d3ac76c414c3d2ba574f8a12e4d0da148bc0fbdf07f41f85b2cb699ea24c5ce5e8e9d0ec10cdcefae347e3c4f2dc7e1d36e182f5c3539a68c01c94a2d2616c626236b221ff9280f9840b299b1dbddfce77080029fccecdc0ca27826ecf4a2d2463ba44f33e3ab6069f4420ef133784a51e4c966cc107274824dd62d0caccb80b936b01dc483035a216c6c4a3c88274d5a2179f93c3c06b42c1a44249a90c60de23bc4314f3b316a0b0fa55568f668a8aecf609d501e29e9277aec0df93c809f9638c40d766a7efa69528bc9d140d52a9b453c939f6571c095113098ac0f2001c41968da1529510d14f915c25bb1a416e5de4fc818d5769f94a55746ce3b499e2199309f965543ea3a25cc87d35d8aa2e8528e878f0ab7bf9eb488947eb87551dee5515f45554a98d2edca78186a4127ff35ffe5b4650e75d940c2efa3f9da2572afac1bd884f1a87a6ca08cef4d44a065392a0264612ff45c229b3818c49a86d28f9ed3acc37400a2aa010450f186d58b103029dfc21d456be086c0f4d8aa5c8f75d79209c419c3a92dba7af37604205779f82885c043791c7ab58cfc4e5686fe831e62cc821d84a7a992d3dfd1f5528c0be028cc66a8d13b58b59a959639e428cf79b244e13b0143ca6c568e55000a3eab6220ae32552799f898ba569dafbfca371507e0799f2a873f1b8590dbfe9104d72f481b53e1264eaf7d617691560a823feb120806f0c38bd6f81d6b029e9fb0fcf1be4c6e8cb494f8ee4894ae4de48857a17a64ef09a06160b6a3cfd373f8b0564f6e364625a9f698e29c4770d7e58aca888c1d351af4496ecfb33ce83a385656ab9cfd8250d6171474f91b92a3a9f57312493ee2f895f6d2b591cb849ca9c4ddd2ea73e29dfc7a3cd7e70a9b08a601ca7f4efc11b1ee83965cfd819e3323459c45ee23bfb0914bf361928412b0956a130d3faff8a0a135eb985c1f0c2847f1a0dc1160072b506076f8c347a7381852e0083ae2989217dc6b735946d5e6bc029041e780d9d72b757e506cd4e08f5c7640c65c997ff331ca9a0585f63b76c38715f5dd4f15c82ad2e68e1e450d5ce7ac2df7d175fc65a0158dcd02dff4565d8ac24ceeaf885458782c7cdbe78407d742c099b77d4c3a3405b58223959807db53f9a3e62d9c1ecbe31fad605cfdd4778bddf4a4a8cc8eb5b4bfcd18880d8407bc56ed43367b6e37332ec8b9b293d5abd92aed8ecb341c526874dfb6d3ada44b409caa6924df96d72daa46893cfa61d9b3cec66cc096d72e42e2721e7ccc2ab7d88e336333af546f03374c357f152153c391abc353c518c94055d0fa1c072bc965cefc95e1c6471578def335f1a371d63f62217f7423ffa45e36d678c97c265598ee00701a19636308103c6bfb6bd220e5d7b71bcf61d3e83f1efc7cf9169ba9330c7a6330cfdc88404af186a05be6e90e38524a116786b4585acbd4026c64ccd092acdcad81640469df7fb0c38d690a107e1cf7a305e41fbfda779e4aa5a3cf11d3f0cf208d4a2d4676e2d123be85a9b62dfc6205ee359cc382ab5dde4ed6dc08fd39666181df734747610c207b2d71158fe8e75b64ac8de9528ef107d27ee5bdb83d88d6c328de948c0d92b9949b6c8e4b0ddd6662041da419b333240515dd66718226336a9728fb205414515bacda8e0e8888833465108b42a7da409b4faa1f4da78b611c31ea4d42fcb76736363e651c0ec1a097d47561b581ac3f580378ac39db31f9eb50cc7978e8a95c76dc702a4f83032a80f3721119e1a5c15aaac7379272073e52cc7079aa5638e3b0eb673c46c6031a58323be060cc195b388f0e45b1afc45e0df96f3ea6e2a955d97a47343e62ad193f6391301541db3ba6301b3faa16466351582d9444e695a5d178ce842a6bd6f7a92507c2f9243a3425df218082be8ec18f288384d2e073ecb93a87212bcacc7f4bd470f2ff7a4aa70597f52b4812cdb4ce79298246b2a6ac9b1114db1674b65f44012c6e204b51476eeb1879683190667d6e73a00158102033024595516da4def680cc36dda08f18113e6abf6124ca84915a7a810125d4bd0b78316ce44c95200b0f154699820411dcaf534c49b214ba6a218b991be40ab3b5e383db51972fa382deb818902d46f16ccaa034dbd26601fc97e33b5b536570db41eb00dea66509be6dbf65fddccb36194bf20b4cc371e0a080969f0fd65060bf0e64ffa3928e36ef5fd246cbce35539384836efd3d5ca23c473ebcdae16a36878f3cb06abe378975d565e0390c76bb50065d890dbd386d07a044f6354f9272812b4eca08bd9365b47b1be49de0e3b3886f16cf314641ffc15fc508f46efa923a094f3a8bc375bef412450298879b4d748677208e45769e5c8992e75a1f5642b2aad4d544bf02a72efd5d55e7ddd022f8b0909962a9b16600607cb0edfdbaa4b005b6a2468d51549d2cc3ae5764747baf8fa223eefb5e73fb886bb6b4137f7368b34a78fd16ac34eea55d574c339c0b9b48e37acb522f50dd37859a2573afffdd9664864ad496e8bdba7da1b36bfc3b626c5d212210360872760727ef30da783335fe8b1737c41017bb00ee45401ca59b5cc4eb9e92153326314b4469b3556eab840fd0c21e8ed94afc1585c2b84d973d007ac707ccfa198b0e02a4f1858a10a54a08fe0c67c9d61c280cb6a0b72649f15b6ba258e1b2ca093042dea9c5f46394541633b2ec86a17014d230bc6ecd105b753cbb8cbee3c07b59bb7c800fc0ebaa46f9dc0335ab5c13546de116806b87a2bddac8e24c609c0b6f5f9304197a0db82d6a3a4eb675212685b89d1a64972b656dc9c6b691f30713708628147ecddefc1a18aa860a8adae579c01463f9a362976d069aba9ef7580866639c7481b9ca9933445abfcdc445678839157645b6f9949b0dbedf07447f3b78b9352915dcbdf74ad882ef2c6054bf4d017e90383a1817fe7c9c5bbeb3c7a1976b22874570436d64332500b67a511d6308ccf9f1230493dfd1cd4cd03d3a6cc3ac93e14145b7e368d87ad4fa8b21436071b910215b571658a16647bc7b64e2758ac084821ed9f00122b45dcf4bac0039020299be99987d90443e68be7def628975479732951dcc4ad6b315e19519b3f7eeb329adb56f0af64168e669a8d7ffbf51d49a5e20c3a1abd676e73ea6c0ea9ac04f7cbe3efbc8007f219f69d05deb114d617d0ad815833ba716b12c16467222189bd1102ff13ffc759ead670b5b18868dbdac8a85750fde785e1a23def49794d5621f06f9826b78c9de596cacbffa02fc41a3e0797a160bcac175a710e61ef9e5fa8e3df44590bb2951fe048140cc9afc38a1db64005f3c8bf8006694eb8abaaea883a72ec2b002eb82d5d394c87e29754bd29403913b45c39169d5817400a1f924614cf8573904e5185fb575d2f4ced6d452fb7e8d5086676cb48efac17063a740666f0a2993ae9a816535792355a989eeb3265a1c62dc22de1a4a7648672edff76dac6724b005471ab3f62cb282f46b975eb4097ec61a16b15491a77c17fbd01163f5f219c1df6b953e4ea807edfb3813c1fb4ce69caaebd4693e26089392e6ba5b6ce4c8173220bdb8762ca36d53170f2f1ebc4cea5e71a82a1ae95f4ad34e81d1b01d1484fc24f4e9df436d0c73a204297ec1f716922e77e7d8769c61e94cf51c2ec5da082290d98c83304fb0060bbf7b33abc96cab0ab3117b1bf6b125671e7bf5a6bdcc532752be94c66d0c0090750151f90bdeb24ee63c26f540fe6020cdfc12fe2f9eae2af22a68d70b15ebdc4fe626a6a0de792d8a375e826f45a848181888b855b1b73b213ee52e0843550179e1c0a68c6b44b0ff3c07f309406e75fbe33d904d2c3c940c24281a6a46aced01dff8ff905128a46165688380e0c2d209a4a109b1bda1614a5fe380d4101eaa14780a43602c14358b6567ad16b58007a1a23d433124b0d888c6ca6fb8fb083f403010f3911c06a034c033b82162f797a4f99b7ea3b6a41c00053b4df0b803d0d99132ca704882dd4d20ee014e0de7e708f83e974657f0f6da0f8c8adc6455792fd900e69ecb115f9185c626d200a45851c6ee269deef68858dc0f5309ca795fd5806d6aa533e1d03383b27e1d6b300040f2be852349237d1ac4fe182cd00eb1a5c73155c35ee949e4846f8d95a89c7d556d082427b686b4fa1e5ee01da1a961ea52b7588836b10e6101145db80d327d0584393592bff00c37d0c7b0f6cd9805d1ce70bbdaaaf68c1753477dc7a175ae0a0254471ea334755e8f07104d2733614802d63b2772c7727e22a3d2e369b53f5a8eaff865a68f90844a65f474e0ffed6b9848286a93331f8b57a515b1aac94f4b1c4b75e99a7c5140efdfb2e82d2a4a08adc40b01779ccdedda1a5fe6a4727829a79a5d082742a0771ec88b82813559635d0e758ffe82c80991473f724bc61ab29665c8480e53e4d4bb24ec1da62984b4b7799a3c10a2c9bab4d2be89bd0b971d8af469aacd0673280eab61fef85d8119337f68795f02571822c5606e764a197534e24140636ee8299a2c66b4bc1f019f9aef0a91619cf8258f972182f4f0032c9dbf71834c41905879c655c06dbdc0b4f166f62b38f711c86952943947da12603096f5c51573db48947358515143519087ba37c1c07d6be56312e29414379b8c4e1882f82fc2da19e509e400167febf68daf17409795f045f1b7aa9a09a5c25f7f21e0074921f1fd0d0f7a35dcb91a0e1640aac10c004c131175f4e93fe877897378ee476b417b1d9a682edb8b454f803d681e7df350bf4bd9e082b48ad60e02a032df1586a11a4b4916e19803ac6a0e9b2f2ec24a1e0010f0b3e12d8cc7a97ac360c586bfda0b4487e1ac1f08601acffbc4f291016d0f510368a51eba8b8574ec2184e4eec92c311c486bc1188fce8027434708a46c2c3f21e23c47e5d5a49e9c20448ffe258c98258ca7895d89905580a01ab089161b1942121c3d2d154492e6500c30032b7f1f0734b2f007f97e089e18f657d045c830b8c63b159a62c0e2360a3081bf0668c8ad6004dd9ace0c15601571b14e954c64e65133f07cc87497d7548b2f2f0e484e2e7f795cd067c464686548668f8d81723b8427d8cf85d74ade7e3a1a36cc6d140f73cfba4fa88c2c7df0548762eca7ec398889d69e9440bfd278419e7bf81350c4ccf000f2e83a5e069e2839a6fae4f8faf1f1f24be217e5438456928a1835a9f5d02df7973421512e1c3a6f7712b583ebaf2c02764d7faab2971645037b17731bb2f3d408e4cbe348a730714c7942f01ea9e8187ac4b320e20c24199ed9b549900952562fbd7434579645c1788b96ee5ed4be32339a3135f52b209c90ec0fff9e1e26a908642c74a17b79081190970517153859d2029c148c457c04fe99d3dd1bac1961faf4316f2f30c221a26846cd1b0486802e63df3143f6144555c09349abe3b8521638cc37993c48555ea5f60de1f857bc366d711f17f6890ca5a22fd6ac4f332cbb5ff5ca6a9c1e77c98c06741a34499f44d8c6ae074715a0909283a28637c1f24f5e7381a1293fa8e753d36ed198e9ea70023c3cb8be0e7f7046382b1d38d61b4bbade954f907d3c420066a9cc37c5906bc702693d6f7e9d0666e00cf7d66aaa58d5097c1b63b20e192e11214076b048b7b23c765f3b379a00b0471a5a260aab879057cd630cf0acdaac222a53a9866da61c97841ea67050addf463d95efb1b46b06a21c717624864a23aa2c512c1087530c60549623a53df694afa694df24e9ad93fb6a2b5637a187d9bea30ea90cc0a245215d26bf8cce53497bd00441e1970918924562eaf68b547b0ab63d760205927ba5451acd96d80c51edfc163247c3f0d7fe939c793bcc06601dfa1ddb2274448c3a6279076df8b779753685122d2794a99a2598b14bf2c65b7a1645aaa157a8d002eb3304a59e0412cf8d3f8a08c283ffd9ed82409897e26f5c4828772659ab347e22d7e921a3237b51b16ea134909f332474f7cbb3658a9f57bd4f5e45812aa9767b7683102993db0616192740fab0809566d6fbeec361733b792e475d96b7b26bd2553d603da20f78400fcdb5965fd0505473174ef239cf39407c7fabae2f6662ac2e3ed5f07184584dd223bc8fe908a4ef10182ed367f32da45153e78851d45175cd954598b5263540bc9bc4dd2c14ff169b1d5aab148f0d0777c1c321776f0d5dea17437a111371e9e61bad0aac889c7a93f09693fb519c96b452a7e486fe9184ea4e906a35619ac5fa45ec82e07870f7090cc049ecc624f37516cd9107920635d45641b65b45b4a1ef581807774a49f31104e6971d4c71413156b29b14f39d5fac2180d2e6de38004296f63410232d95ca1c11dc9f5b546423f6e9dd8c6e4bb659b254bcc2482ad9328d1c44384237390af69c2b65413d703c9c9e78d588adc82415b55933a0d42d1dc4e4232039f2d369bb16ab214b2869ef1ff231d7f26ce8456fde1e559af494db7d3df30e3beeca0eee54c180e6c9397894c6afe93330ba25821e23aa9edb29aa939cf10eca3754da50d4c6038edb04adbc05d7638680318f5183cbed609baaf7e3c5bed654fb335ecfd76482a56b98005c10206fd665ab4407a525087a29b3d42e122a45c9e3f743ba6ceedc466a3e86305a39808ca56c04cae69a7a205ee907b72092d4fd889c33e105ed56e485e14bea958ba6f6cecc7cee673cbc84bccddc662c2bdd334752d9d5b1f0ecd19f8e3a01e69f44a27a7ea05e16ca234905d243840303498212a83f9615a984abb1309bc07eb64ca98943818b9de52ecdba481e46d4b64f61d471af7e0c7b0e054be389a8b1b7635c284000085d81684e831b977f0a18532dec98d1883d7561293bb43949408d057815d420fa1462d3b16f417dbd0d94474016c3187d406a14dd9cf2700f21ce0c4dc7d44e8ba2d66c911484b059e29392c845c795f5e319ae10bd6fe2815d6f15787f08118fd6047ce993441f85648e3e44fb802848429e33eb08096d46aaab8807f48ef8e3f79d602445b4c5816b9942fd7075b7336709e2232cda8415e9e224dd891441bf4849f9678362744983b0e2348669bda4206986721d6649afb7817dcc1f6a50f7a411a2241984425887dcc03b83ae5f7ee320f4844dbab088553f4058b2771226037ce7faf4073f0f6800ef4a8d69f73256cb707b152bba4e470d57ce9c142d9f288fecb1e41418f2ddcd5e9e5e4d3bab4a23833d5086bcb4d97e6b549e465cf603643e3a523b48e83b7ea2f3a2d1fa015f2779fe271c6e7efd34ff100048dd842571ab924add6256e142d5ff9f192349151ad215129946e8ac62263165f27cb58d0fce9771b92fac71305d48e56a627c91dd530e6dd2d8dcd4c2a8ce0ffdb1038e75d33e7cfaf85ee3fe31e21ccd5789d53c9c9c8ac0e58456e81ef08203088b04656a2f6c1772158a21602fb5ea06f5b9a85e3d033d9e616343ce928487d5fbb87aacebc0323bcac3b41faadb610aec92a3777686a4f7e9b72d358e7c388055975afdf31f59935d8f17d9019d41cd52a4da3755bcd07dee3829ad370808726e96a7689b34f59f2b93b0331bb068990770b5b7a74abcf79241788ae04de0a3b1225763ceb094535841f5e22a9f26efe845601e7f0fedf8c0fc8c4dcaba9c98a06b8369132df4254de30e5e641ba45ea43acf7c410001e92a8dd1017c4ce3bc09fc4f7ac11769b14e0a86626c7106ec6c498ea7bf3e975eb3d090bea00ea65dd7c91808b0af15221fe364a398b81bc02c4fa6c5976088717eb70603163870be2e2010359a1b18472194128c47782a80255c8b1eb3486a8122aa1a2c80a870b1d24842ed7367245a1177a0a670dc2e9195354056fce67b608ac6dcd930e007c873bd9a3b950b9cb0b634b7fd8ebf2027fbb1e8eece5b82580edc5fe1fb5be43fad4d1c4c235f6b8376124423c2b2ada5cbc80b0a23a3f571d6e9f1dc132c3a6f0c5bf03c5be074ca18c266c9ebcc9da170bbcdbeab38ae09d061c7060f1886d5c6fd292ab2ff6b0724683b17211393e8832ba08d6544a4094ddabffd05cae4206813246f5c6c20d134680dd3a46285810fde053033d8eec23708a709b17218338e15af105d5dc938c2d4c52bcdd903815630bd56ab09e79a1c0b437343d4dab09e282a56f8ca297a8fb409e3eb22c561ca4b9a30e5aa490f90811c5b2718c290d9b4f040df8a3d01e70d4d8a0581103cb713db0d670efbe41ae8dacb2408d2d1987938651a2c2e850f523a642df26a789abe881ae21d07fb17059c355cb0a945840866309e783c5b7590829ebb1363f7d38d0cee8f05c27e102e268d47c7b52f737e0a8f942846c1f1abc1a9ef4a099dc2893f1ca01a0458735765c8c3f08970e59a4a33c3f41b870d23d9d772956e00aac8276152022d503d578927b9a4d0618425d3de94bb6ff87601e68630b21ac07eef95c708b0ce07d90a80dfca2ddf3c7213140cc1568a30596d1bb093fc64f85ee6e68ea7292d435e11ee041e13407c11015a6a07386ff668ef2bc26a7af9467f672845016982dbf37d55f368487ec60853ae288ec3037bbde789fd0102a4b25d120210fee66d60370fa2c5cb6b03832094ca8b7c5db58695a0f55a0e8b80dd9cf61ad05abe543af922d094e8f9bbace9a66b18e8185a2717ba8f94cb303dced1e55cd0ce2740a822a44a71fb5625ed980872cd5af1f126f16f7dee4d591b142ecfc77ee26d8f89b7aa5eb9794dc1b98c7e3e473a04ca094ddc8f92abf9147b9dfc9939a32441ddb816c551eff16163beb5ac283990d0eaede71a5b27803c4e21a3fa87c633242ec80af7d21883296180f29751338d08a21072cf812ad73e699ec72b1f5d7e2c62d44ebd65a7540a0da1c514da27e5ac6d3cf13ff5499c161d86876f49659e7c90043b2324730db637430337939a52f5fc28fa2b870066e3b1053fcc6b80dea7b5a179233b810443514a2bf0b5eeb1f533ce1c9c3397cee5b0f5a3cde27d6db953bdf87a7c99d10d165ebe381fe6829f61f2c60890eb7956218a0d0d6922033f802638d5076bb9489a376b6aa8ac67f25c4280922dcad9819583756eb7714b40b86bb8adfc21bc4268a4f8a1cdf12708080b7b98456eea30a55a6ff8bc5097b9e586382c47db412e438de5bfad90266ee2639c3dc2bedb3a94f9a84dc0b8859014486015382105ae040fc2514ec7802fe4044df0a3760b46439330f0f0f0f0f0f0f0f6f706ba4b6d6368024a49424a9adf153f8ee9029a594648a8433d417ce4cdc86362184d1863fda080d02010bf40ac50ac9f499f2b5ab325cc1873eb19c2fda07c868051b529a92123cf3a59820b00219ac6064bd6b472a993a476d6ce001362820011b5bd880c0183256c1a43e4f3166e855c1c61acb9f53da53c19f680d31c8fc7ef9265430f92ce750ff21afa77b0a268e8a96c73bd8b6ada6e07a77f46be99c25eb2d059362481a6aea99de1e29b8ed9027e4cadd28f8205a9265d09e29658b28f88faf7fb6d9d3a67c2818d3f8666a440c146ceee8b952b0de0b1df904bf25748af61bd2a8c83dc1260b59c53f923e25f24ef0f9647a8db69fa4fecd093ed8a866ad64294a3a6d825162c9dfe228799a9334c197902c5a24277bf55226b88a215a44537f25e208136ce4d0f71f73e275e55c82495a21e85e907973722cc1badec678997542094d25d81d913975ca5ea276a504b739a9c99e96d6f58293e034b7492b957eefc59304a373d25ff59e48f06d25421242de9f881448705192529f839074df23189521287d9e2a6d8a992378492a85a44935b6ed8d606c5c536806d5604284119c34d3bd2d79a47fde8be0840a26b2e45c5322a22a824f1354aede4ba52e272611ec560a22654919e97b47049b738a0abefb15aed14370c13268bbff6032a225c3109c14d1993d665536e11582cfcb9f9d2ea7641082dd2cfa73cca064705707c155f610e99a5434b5a80c41302a7b6be39f84a4ab77b1c3e8588055faa2053bee003202c1081193fbf9aa3db72203106cfa9317539ac4913193f107ee4a4726c30f4c12eb517a55374ef4f481534ae8535dcf49883ac4088380a12351061fbe14f5141a5e3a4765ece1f275d3b496255af0764d20430f2729fa2495b6cf6a870e301cf0001c376e84c1011d1798808dcb01cc41461e4e061e4c9b335a1aef0e8c48cbaf1284251d499b63c781c0160c68c08d1ba87164f941861d72edba183a090bfabd066edcd8a1038c3176dcb8b163c78d1b3a5c0c2efc04376e7419c3860d6263d84036c6172d6807e0d0e16274c18518a70b07dcb8f1858b21c6e9e24b11400c32eac06fd22554acab15931ecf72d482430162635879e15e14d70146ea800c3a3041dae44f8d7539863e073ed6c64b6152f3c80fc981f59c444c1b458e4c37f9e313e8a28b311c1007d6439d44cf312c37e370e0369ed2759d296508a63770c2bf4f3cb6ae471cc97003b7f77f9db263ee48a78c36b0c17c63f9a70f2d1dae2081a12090c1067662b59f8c41d449f30332d6c026e9d95c443019fd35db21430d69cdc135a5e995430b87abe0ec28c5011969606c4dfb6e64730c32d0c05d4a2633a325b520e30c8c9af2717dcde61b649881cb9644fd5f1acbc0c9143f5bfecccb5d2237c820039beede4698f24befa731f039a434e919ba83369535c81003e3be9e3f8fba06adc130f07d26ec5e82fbc43791db820c30f0d69fb55792f294542c878c2ff0232baff6f208870c2f30c1d4b485f4503131c8e802bb49a8532143e9a867c220830b5cf4283107a53c684b627761811b374c05376ee028db838c2df0d9f71b3d564add6b818f29f7c40a53da3cd959e0e249cd8c604158d0278b50be279e2bb0fa95449f2e09c9feb50297525636f7f414f42a70f1541236ba1a29c5a9c0aea9905e9ffadd53700a8cf29ca2c4b558c890023f4a5f27939f4d57d089b1838b09686087efe080b9848c28705e22483b9d37275a1e0aac5b9ff6e54cda4dc99fc0e62611c72d74c6de9dc087bc632da2e926b0aa96eb25242526302a3285ab9bcee8c9b40446869434428852c93fa4045652c81bed6d97174349e02b67b4b43e993de206097cbc48e3e621e608fc85749aa2c734b333023978deb6089c58a848a9b359f24e128111a6eec992a8f5dcfd10f820737da759c960260aa1282a276da7c552818c2070f6616363397dcc07029b2b22ed86d2b92ffb03c6f622e5ec5bdd26293ee0c23f72fc1b0f41a67bc0e7f8554a1649a23b3ce04df379695bab8f15db01373af39bdde4a4a242142043074b126f2d99262707bcfb6637355183034e729217e468ac0e326ec06b72b7aab497954d9561032ea5bc9a9625dd0695caa841b6412b92d438914103aec7473b0b4e7eedc8d49bf290052bde954f23668dec522c18bffc94549f62400a1eb0e0a44790f69af335ad690b1bc40236b6b0412a60630b1b840236b6b0412660630b1b4402362e30011b71f078059354c8316b4816cb84af06011eae60bcb5caf2ae294f9e6d05a739ad24d3525975b783218616f560c517e25b5269d15d8f55f02571729694d23278a8825dcf6717d306a5673b20031db871a30b47810e1d607c9123edb871e375a800061ea9e082ce6f2a29e615edcc0e555470ff1949b54474d4a46a233c4ec1abee6866f28e872918ebd4fbf020ed8292799482bf0a0dba5ba204bdfc48c1c90e161a3125798c82cfd5a33e652d3d9aa31ea2e0f562a5fa1c52a964268f507032db574590b955bdf40005a78334117358baefec797c820d795355ec8b1e4ac9c313ec8590d247f1d31325280f101e9d605490da994da8bcb93225ede0c109369552d1941d3d356463f0d804bb492413dbfc1f3bed9a6025d9e59dd8a273867960f0c804eb6bc18205d1a14665c5e081095427a9f157c97e31868e9580c725f88c3e22c326059d7181c723e47d06cb4be5183a2af15bd8bd9f679b128c8d92b9baae3777d4bf1843076ac063127c0e91458a5bbfee3872ec18230cc22848470c0d6c2567815f053c24c1061f69226ccd4830ae39a4a037dc8404a3e2263565ccce27fcfd82c7231889c1435bef7604b71f6a6b6ff75386c9a3119c4efdaefba57a30820beeda9b97443ce0b108268888a87f168c828722d8a4f4c42da12af7c41c143c12c17d9b56e9b859a445cb0311ac046d77cfdf158eca83c721b890f2cba2ffb628bffa0d1e86e0a4d999f87757082e87c6ce924e242f21941e8460d5d3a59844dac9fb111b049ba2c6cf79d46c33d382e094509bc54ac4f2897904824f4127a154f4e7c8db01c1d7a63a9d628e1172c5a6e0f1072e0909aef9727f52e843e0e107ae3704d11b5f1bf5ad471fd8a0db69d48be9b4343df8c04b0ed3bccb252478ec81cd173be8d254e5d0c2d17d881d142430bab00b00103cf4c0468e6629b45f27ab5efce09107fe93c633bb94c9e2c8f0c0597253bfd1be6c7d2167048f3bf0716d2d2d640e4916ca2d78d881cf509d2ce5300d95d781d321655656cb972548b4e04107ce7474bd749df4e50dcd811f75dde65532d6b8eb21073eaf280bb2627c8fe92e81471cb8bc9e665f59d4697239e001073ebf9a8e1435291e6fe06356341942f28d091e6ee05bbd72fe0db24c9989a305270c331978b481cb394951cfbfb1aa325f78183bc6d871e5c10626c9ec5e4a94aa1ceabec0630d8ca757f430b563297335f03149fb8e96df64d27f0b113cd2c0f765fecffb3d61d2c62c78a081c9299a9e2382d23a4967606db227a146d89ec528163cccc0b82921e26e2c9f1cdb32b0c92f467190819139e93791e4e7381a2a24f018039b338410d67b31256d1bf01003fbaa498a5d503a3b84b6048f3070a66c930c1d47b55f0a0cc7943456d25d2bc1e30b6cdde8cde9b1cb74ee385aa0430cbd828717380f6a7dbfcad3877ebbc085d7a9884c4a7bd0e102a3694925e854b7c0f9980e52e2297f53d10a1e5ac0721e0d2ae446aae091053e6fa8648d1d1ad444565df0c08297aa479859fc0abc84a851cb46895edb0a7c90dfde2e32fea9a90adc066d7df24ab8fa54e03e43f57966bacb4e81f7493172aacc4ba18ba5722995321f055e3325896aa5172bf350e04db3f376d929a1db7f02a73a7947c545855cdf09ac5eea076dcda263f49bc004cf5144aaccd39f3e133855f12c4b0aa1f2cc5f0223dcf472c9cddf48be12b8142237affe78d6f093c0968e65bdc89b62d23d1238b9e329c935f99ef547603f256bf650933ea57e47a5c949d34a70e71d84ca9643095633b86bc8ec24f88c9a63e79f940497512d491db7d4798491e083ccd9d245de8ed53e0d4864aade848aa351748f821d670336091a8f60848c5b413c54e7c71dc195345de7666a04bbaee7495e75868a0a23f8d0ab20624417c1e7a9ba20661d73f614c1e54d0db6b9f636b49208b6d2e9688f31b6f588d84c6434134a7608c6538a489fc97208779b8b2d0c0d43f0d9b549ff540aca275508464e4a8e11062118cf214cc5d1bdafef0f824fbdfb510b7241b01e96dd3546dc1f650a04772a846c49f99eb6090204ebc14699aa49273de9fc81f3bd1391ef3c7ee07774e3d5fb875cbbe9032f31644a9bc6bccc357ce0fd5753cb93660f5cec8951ebdf54e8bb7ae0aba2a8542a836ef6681e8cdc12cfd7e28907ce92103d31787ee91eefc0c6ad1c4c5abed8818b39cc3e3de705cfd78157cf9f8489c6b4392b3af04175ca7819a4258d990397530a49454c264a47090b68c8819598c13e84150736554a4ab27a8d5b88c3814daf9cfbe1f71bb84b4ae78edcddc089decda0cbbaf74edd0636a6ca2fbe9577da64d8c075ea5222d89fda5e740dacc8f6d49b37a806de7f84083958c839669706ae2496d03946fe247434f09b4262d299fb5eac7206ae84f6cf6f1e4a249399810f22e554d9eb32b06e19926d940e19f8902c54ee8da977947a0cac78796bce9253a5b41818bb3ae175a623e78ce068810e31c2c0ae47fbcd29497eb01218b8cd67a9fd93f70556f32af57684c5909917d83ae59d25a9c9415845031a5d602ba5a6cf6a4ae988991d6870818f9c45e8df36dd10475e70b181a5b105ce6dd369519a94e8a4a3052ee5e7fdd5dc1d92a259603746caa0eea7e244110b9c5b6acfea96bf026312526aaa7117216334acc048099ef267a5142f48a30a5ca5bfdbafbdf5b594a5025fb16c83690795ad73f431ba382ac017d098c2e291228aa8a454d19002b71f4443d787a8161b1b78c016c50b34a2c0a9260bd1b41e9e46690c1b1798800d30d08002175fad339b526a39b883c613381135bff42e7a8aa71750aa8481861358df4f0bdaf55354d57194c181461318b5cde7fe1a732c956101183ac240030d2670da83c48c6e4a62eab30c3496c029ef13eaa9367b4990d15002a7dff25f3bd2e4895d0e2d1c6178914a79b145175f58804612b8d4eb4a492b8a042e289534ea0633a55febe31928a510a071043676964ef622358268530a348cc0adc44b7a22e6b459544da051044e5409cf215b9e38a944042ec35a74d774567c1110680c81afca49bf52c4abcc7aa9020d217039ba74090b324fe62a08ec290926c1454408518106109890e495e89646aaa0fe80afd69c534b8714ffb328d0f001574aa594f4ea95e9b422a0d103ae57af92a6d2ec3b960cd0e001db63a3b761c98249d61c81c60e78136197ee62e80fa937020d1d70e2ff3175ac0b3adf99432b0c2ec45893018d1c70390691d73dd3f512061762e0d811061762744103078cf63bcdba1464a040e3068c77648d314516c976db804f4b216b9b29dfb7c91368d480dfb6d23ebf4d3d7dd1a0011fa459fc6c153263169cdd9bb6dadf376139b2e0a4de8e48f2cf34cc8805ffaa9236d7f2657fdb033360c1495131534fa65598f18a728b4dcad2d9aec00c57b063dfa52b2c58cc21cb21cc68059be49769d0bfe725439615ac66b4ccd851447432550366ac824f5971efb23e6dda76862ab8a483074bd974275d71093352c1be86fa4dffb9fd928f0a46435747e750b9b5834ec16d52da1d4d57bcccd514769e1434799fc508334ac189ec7699b71bc2724a0ade54667c932034e576678c82db6891fed385cc1005a73972758a9332a599a8302314dcc8bc4d2a5eefa5e08282179dd926a9ef6b62ceccf80497530eb641095d5a3ff7049784befc38d2e4a77d3ac15f5bf747b335c78e0381d3199ce0b46565fe379dbced83a3ca143163137c648dfc9eb7ffda6a08d8d8c2c603b6b0c136b6b0d1802d34c1675226f72f7dbe3023135cda10845ee650113bba0516666082b56c11746307675c820b41648af9f653841cb28019966062daaeafa0aeda2cb4b1c5186654824f7192501339c434da86125ccaf190439b9981044617ae6312dc067da6a794d6955f39b4ba0b303e083324c196de0d495e3a1b21ea48b0935da2e64c419060b2281d31974ee2a6848f6025fe577a9852e121770413728aa5f8e9d35a4b663482d31bd916538f8e3083117c74cdd74e593daa25bdf02202691b8043cc58045bd1ef2aa9529e79438ae03557f2aec816edb42711fc5a0ed9d6e2410463216ed01f2166d6c839041f84c4cc546117ed5a43703e7a3a53e45821b8d0e3993cab4fd54d08ae8450bde412f39f84e81003479587d1820d2462c62098a4b1d2d5f34316e90b82abd49bd9ed8329b33b10dcdf6e0ebab29724150182fd78edede611f278f40f8c69ab3f75b671d32a40c1e62366f881f5ebeb11d2ea2c5abe0ffc55c7cafece5515743eb0669182f0f4901df2b3072ee5a818214e1296ffd3039b4c3f72929a5ef3a57268a9000538fe781738ae3a2001731580e185eb08402066e481d39a1154d2a022a6d5f1c05d06559363d21a9edf81ffdb20313ceae474db0e7c2cf1d7b2cb1dbda375e03705b5187445f5e9920e6cbabe9873fbd69412e7c0279945e65dcf4cc927a6fd8b15580000aa98210776426e491294478ec98f0327b2e385507582039bd386beab3db7d1ef1b1855c942afc8f7b6f204ed08c60c37f0b59752c5bcb95deb6f0393763d5f48b55945c6b0819359b2fea8fd2079b26b60b744e8babaae8d9b570372848c41e8e86f1a1899befdf2539e986488063ef6e556aefc9693b2cfc08714da64c87631490d320397829222476b4f2caa6560944ad31084480c79f46460cdabb35e8292a5427e0c6cfb65a618835c0c8c25193a657dc8b1b31e0636a7dad0621a4212afc1c09eeddda8d2a72ff079eaa3a36e4a2add78814d7709a61642aebaa60bbc78784efda6e102a37394741fbf32627e0b6c0c4942c98c76fad1d50223536df69f8e27c966814fea937ecd251618cd88a9be976f17b91c5a27f8828b1d3ac648ae03479da0991414665c81314f2999e5106b7df3de0596cfb002a736a7ca88a12be860e238c1175ca0209d40877b5186821955e0728e223d847ecf2552ba382ec60e1c28e8620c946563030f6080316f30beb89f41056e83dc8865a332ef1de98b8303477f7170b0808baff2850e0bfcf109883106056edcd0e128d071bc281670f10503c0831953e04678091169d3bfa2648614b8b4416dc40b966d6b93436bfbc60d0e9828b0af395993ccd2983a0a057e948aa62a3fbf3c98f1044e099154b9b27437955608339cc0bbea086d79a46e507130a3095c08491e6b27fead884ce0a3ed77e7cededb9212011da7033798b1042645ae98aa4c6707515202e79f747d103a1dcc48023b69b494bedc9954f410099c52724379d01dcaab37338ec086c474ee1743974660527a8a418847af084c5caffe4a3146df9815e30b2e3870e346210263fd357a3b9dffc5b0cc60c61038916b4cc4d19f35e9940b6608818b7d2e4a853ecf82c0a6aafa87851ac78e436ac7026a3003086c28c949691fd359321983193fe03e65f41cfc7447440f0b66f880519372fabc37cd49e66c1166f4804fca247aba8a179ec61d3ce0c7bba36fe48ad88e2f8e0e318a0161c60e184f6a21aaae9f04e1660e66984eaa246da9949e03be72c774e1a9ce269970c097dd56ff58325def8a0d66dc80112965b1a0aeed554b1b705a7931e36a8ec9d3ad01e76a75f913626d0ce50c1ab09ade5a33a485560b0319b3b0c3b36f5398c9dac5f8e2a4d3012bb2d08245b5bb78569b2423167cde18740e1a3b5870a79dbf6486e0173b5d84e185eb0b64bc828fb12f0184e025c5531a565223bac6c60626604307c1ba9f106db59932725210e64caa83d0fb2924ff77081f81e053df5396a067396777820f40f0f9bde6e33105b533b1e1e30f9c773495f75250093efcc0c7902fa5595bd9c79429193efac0a7d7575aabe8aea0c307c6fddc4a3d85e41e58d3b4906741add6d71f7ae0cc3a06f724624a4247ff9107c6266ba747dcc9a22d1f78604c44fecf154d738595430b8cff820ba2838f3b70aaeee65e5ffa473d36b610400e3eecf0f9a638d16dc4b31c5a2b383a70b8bfd58193c147a452f1e4093dd1814d2e6abb746ce7c0244d49f7a27b7268e506bc0b14e8700a9c073ee4c0a8512f21670b922cf42ffc04a6031f71e084ce6bff4da1b36f0a072e2328ab0a96477aec38f0f1066efbdbdb424e11512c6ee0bddb46787814d16fdcb831838f36f0e6c9d2fac95117526e470e1d384e7050290a6480021420a6c30b2f8a083ed8c0e68a14a93dfff4d28f3570b5d9be59faa5e5fb871ab8d6f02c916b64d221fa9106bef7bf9392b42f42d51f6860474566919474d37bf6e30cbc89a6cc6a29a6f4261f666063652d4d32bd9652df4719d8a4845f977a503a499e434b4df041065efbcc1c03dfab66427e4e36494d7268a1e08ba303c78718d84b217ed9b985d8df8ae1230c6c6b2e4fc1c3b4cfd361f80003b7a75f728ed61633828f2f90f369ce21626e8ba60221c0d11f5ee02bac56c453e66f497268e1c8808d2dc84717b8d2bd110b1d65ee1d2e702a9ab698d47e47af6c0c1f5be0a2a5b8a9152adfb82106185decd80f2df07ea3465e8be6230b6ca51c44b4ba5ae4a4fdc0022337d3b78eaedaef908f2bf0315f658ba57b52d2b815f809b2cb3dfbf85105c63ce43abbbdd2967e50810b5a2c988f58f89e3ea6c075468f9e3a434a7c488193626a9d7a2df2c75013cf131f51f880427e31eea952761727c761e0e309fcf9dba64ebaf7e39999171f4e602d87aadcdf4a11d9a2253e9ac0240b21656b089120a41f4ce04784452dd31c423c1502bb858f3bc894d2561c9d600ce48de304607461f0001f4ae0aea35958974aba52b4c24712dc16d3bb112a6e60023992188701376e4ce103094cd011a39a7dae12253f026b2e4994c80db5592123f09573cab896828e6b8ac0a9acf02836e61584ba880f22f06d2a78d6d05be1ba6b213e86c08798be1f2a3c4d16c78381c330c013172ad0e11a2895237d7174a0cc0d7c08c13d7d6abef7dba1a4021d636c91868f20703ab3b957a94060af445bd03b293d7d82838f1f305a216d52952e59d5ee0336a88a646eb71d64090350838f1ef0f93de7dcc9d6d5d7c4032604f53cff705bf1b1032e8390a20e387d4164c93622fb8765630b1b5bd8e8557ce4809718ed3bb73c53c50f1c70c2f4a4511d947e52495ff8b8019354445f93b89fab83884de0c306ac4d7a2d21847077cd7ed480fd125943ca8bf9e18306dce8186a84ccd8c13212851ab3e0a2272931c82c416551d240b1430d59702986d6abfcf70c41e9c51874a8110b4e459219a9d75cf72758a8010b6e75344c52a6e41eabc62b38e1f527b45e7ccb9e0b430d5770414da9c896bf2b06b7157c6f1659fa9e55f28face0e257084144ea5bf09c55b03569432c846de88b2ab8eca0294216eba81b4a051b64c648769a6dfc34d77109811aa8e0f5b44e8ac1cff2f9478c8370eca9710a7645c6cd8ba233342e7c8c1494aa610a3e7752dee57c8b399dc610354ac1e6502a8927350bcac73cd42005df159a6388aea87aba35d41805efaeea2198f47cf92d68a8210a26ed56a5e5e46d691dd60805e3aaa6f9d53e3540c166f178d27cdcdda2c62798bce973507ad74ba9c7136c9e7cd283dcd4e804e71233aa44bfd0a12ca8c109fe6d3c27fdfb20121cc46a6c828de6f76d3aed4454870b1d61580d4db07dffb9419b094e78d69ea9742931b939b42a063530c17a301df3c70fea4daf4bb021d8764848a633420d4b70aaae9e63142537422cb750a312bca8f98ba81af364419460ac430ea62fae5aa831092ea4cb9433955fccadd790047b4ae8f04832640bc2ab1109c673ed8b9ca06cd4cf1c5acb831a90e0afbb228f0e41dd9258e311bcd9b8aa988b0993780d47f0d94386beccd22c4ad70836dc4f07651d32a10623b8fc20ef42d59563c721e6801a8be03ae7c5494a868fba48166a2882cdcf5a9b58969e16fc070347f72916811a89602c2421d2d4acaf236e705003115cd22023c70c22631138e3a2c621f8ce7b4994d68a212b65085e23778f2cddd933a61405d42884f9cd27082183493381d15b83108cdc284ae4f4bb210779d526a0c62018c929df7e754a923e6b420d41f029c2bef2b90969a5d608049b3428a549281b108c5ac664b5ead6f803dfa731dbe990fbc1ec5e55bb27cb1a7de0b49bee7ade4e1042abc10746b48894d56a4165f76aec81b37613ab7f6b7d8d1c126ae881ad4a6f13cfa6b684d40535f2c0a778a523a2f7d608ef0635f0c0ad69932631ff8d3af91dd8eecc3156d96d07264b0eddcfd014a1461dd8607282bc92acf59dcba1656450830e7cd6c5a45d42e4dc9e9f0397eb94ac499e9d9bcb814b9b344aee7890691407b6ec4dd7f78dfa2c7138b051d34949195266c4dfc095e63d91e2686d44ddc06778528d1ce936f8b731d69a8f121bd86a1d393a5f7bbd92d6c049c8912bc4a44d7c4b6ae0e32569a93508a1cb4a6960359d8bf6efe7cb104203b7eb163347d252223f67602c47d2bfdb1c3330e66e5b1dd275828897812b2522932599f44b78c8c0776513ca3f6dfc0d9931b0daf9b2978f0eb14e2306565f5d6f57fc523313064e2591d7bf3ed7791c0c9ccabbe165ba52b4e80b4cfc1b61315a1ce99ff50297d466536eb92ef0a7dff6c13ec7acd4728193d183f9a515a13f64b7c086eda5c62879d2a6b5c0698aa2524fa259e0b76b6d62c8799983140b6c348f9f4765afc07af6fc31c7d3a52cb4029fd3f44893a0a24d50abc004212ca7ec954ac94da9c0bec88d39a81016c934054e66bc1249e74b152229f0e51a4183c8a54382320aac9b8514445fb9a832a1c06da860aa2c9d89d69fc09932adaba43b81df51ba62cce1e9d3a8097c85901acd6702a3a642be3caafde359021b2fc6603906d9b7a612f811b58ff9fa25940476336af85eccff202430a24c9608d9edb45a47606d2f7e0eeaa1e349d008dca60da57ff222b0317f478e9913811f573f9554fa29350e813555710f293c21307a27679f9c267b8c5d1058d5ca9072b4030267ef218b12964564d00fd898e29d2afb33a7d0075c6ecf65b2ca2fa3567ac0e9602a7fe2a4f080c9a93b9a49ba3c95db0ef818546dd7064df144a603c6bec2444595d4ebb11cb095cb4a72f6ce66711c7041cf7c736faddda56bdc805327b2a434211bf09fa7314f87906fda1ab0935b47ecd574f4b4060d38f513aa2f79c5b59c053b6a21efddc9a8db952c180f12e26de6c7b87e2c1811d2858a9be9939081052f31497c2f9d4235f90a36bbb3baafe94a52bd2bd8a0932e765d48a14c6b059393ca9a83b5ac60fd4cbd0a3e4b08376d49a7cca2a30a4ec5dc935372489b924d055b7d49988e39890a36861092734eedac693c051ba92208d19ef89f33145c0b6898820fd26209095ae3dbc74bc1fee668427207d34c2229f8d2f9d1cc64879c9a8d8253dd88792354be4314fc6810c9724d3779c943c18b44fd4e32dea0e0a2c5db20ba235d2bfd092e7708393c8614c26ae409763be4ce58163a69e67482ab144bef7258999887138c6a93d9e4ae4896acd9049b2fa6ee3e32a5c78c26f85839ebb3c61e69419960a4efe51482f4d2d7c18449249172df622ec1c9143cb3a6a5aaac5a821149a925510a82e07d726b5ef2afa0f94070266408f393f963ed80e02f89a0a1f4d286eefc819164eba15ba134c8f88109f69e3a338d7eae7de0542da7de562b9129c6074eb4a63d95bb228910db03233fc7de4fdaf4938e1eb84c5a73c84fefa0993cb02ad613634af1c069c72da59eb9acbf3bb0f97c82767fc60eec06491b5ca3d8491201831675e03a8a75e79c2157254d07c63206a5936c9115f4670e8ca7ca206388460eecd686cc263c64d17be3c0dfa8e6e63af9164385031b24e80f2a47c9667dbe81b549b9574fd44c65ee88e106f3fb6fe7f70b2e8e18ae23dbc06ae44c3ea2d3a7feb46003934efb95fefabf3f79166bb1063e8b07f3ed77b38fab811321e58a1213d1917e1a98e42243772cb9a7bfa381b7092149db889a75e93370417bd0d4694f3b7333b041a8bc1a2f8bd44b3965e064875b2acd8999364306fe4ec99222bdbbbcdb31f0b931ad7c6442a08518388f563fc294302d790d037b22af629b0c4174a7e4d04a2dc0905a7c818d18a225a8e4926397fa85165ec01468d1852b560c92cb2aa5985482a50517d2a5ca936184716e145a6c814dba6b9fbe628f086e0e2d1c8866630313b0b1636c60021cb05068a185d3220ba605160a2981165760b466cfdaeaea6b8bb50019066861054ed6d85dee5e5125ea2ab0de952e89f85a323df4002da8c0569508caebf2efd42b052da6c0ebfb69a5dfd96b74a5c008b541837e8f16b4a951e0b3e429b5dd5947d4f5042da0c0572af51aba792d9ec009a54330a1326f94667368556d2079181ab891650c5a3881b1caeda7164f4de49bc07a0c399e3909372162029bdd2445123a889ae812b8512926124f25707d2bb12cc34f021b39078bb1d4048fda2381efcf9e11d7fa2370595a7a1f9407f5593702ffaf49e974bf6b99ee2270359aca6dbcb75a6b22f0515f84a5ec8b7eff10f8d29f6ba38a84c0c6b21d592f9a93dc04812b619bd12a757e3281c09d4c3a7b24d30f98a0397ff04a412b7dc9077c8e7977cf54520fb8f748f5fd67a1829e079c1e57b318f1be436807fc4911ef5c121d305a2cde955273c06e0a1952c896d702075cf95edc4b934ce710a4c50df8ab7c93b694bc95dcd1c2065cfef71ffd8a17f276b4a80193269824cd7ac9bb375ad0800bc1268594de654a6d66c107ab7849c8989105dfd1ba946c9360e9df587097b935686adefebdb0604f8b8b77d03b4f7b5fc1267da3b39ff0cb5b5a57b01a74db238af58ad0b682cda3fa94d0e21f27baac60b3630e65c164ac28d255b0e1a643f2dc54f1185505576ba7b3aa8d7e8d9a0a36e29dae1ed724ed525470273db55fe490ad969e82b118628c94276a0a2ead56cfbb735ff452701e528a112dc7cbf9430a2e4dd4d39eb37b8898a3e02a2fe535756ab52b290a2ea58f299d122ae2d6a1e0ae4774c49c82f60c0205234a63f013fc089946a8969e94a3e8095682870ca242ec049b3f2775a2e3a7d61c7282cb7173b668fa4e63ea36c1778908955384741e2f9a50d2c84efe37964cf06b9d930c15b1eaf498e0db43ec4b37e61fb376093e76c8912442fccaad5982d1c8ba5582df929a9ac3cc7d45a3042742c42cd974c658a29a04a34aa62043ed2da8b52451b220e3a75e5224b8d6984bcb74a9535a48f059e47be8603e828f29b2950cd13cfae8087e35457d75e27b758de0933c53afdd63ed298c60a4879c91b2bc736364116cac521a74288b7944882238cf9932af3289cade26825395af6a3bc9a415728a08ce377656804330c9e42695f431c5c43704972ea69cbadb7527e71482136b112253660eb23d842863b0b89a9b6206c146755d13a1a729a95410fce994d74d9d8e869d81e08465aee6d1699f441010ac59778cf92b850afdfec07936d7bffb2a15acf30393d44573d3362de84b1f3899f4f5f8ed5e755af8c0b5bad5e40bd97e54ca1e38bfd431b5edb607ade881310bd3e6eb77af21250f7cda969cf8e9747a53e181abde9cce844a4d975377e06ceb742b857765b4ecc09a4850d1939cd4b2aa0337d1dd4a3bdd4a448a0e6cb699aefd909a03a327eb5637269996037feda6e63147f41ac5817135dfcd6295f47be0c009fd953d6955eccbbe814f4a081f0b4ae77817379823c970cbd136f01d5b37828a101014800dbc29fd418479148976af81b111af0a3ae389f4d4c08a0c7da79a9a543594063ef44ee6cd29b134b268e0f55465ce9ef9b7ef33b03af2ba479decae1833b0d53157363dd2b72b65e02b68d357593793c4c8c07846091e54ea1bbf740c5c9fea335da23fec1403a7424ffe5c4a448b2ac3c046cd1959ccfe4566c0c068d0567b4104bfc07586e0f182bcc02599428a88f9eb02bf751593d2f47181b3a4fbb2ea2d85e5650b5c65fd20d3a4ef85a4a205bee295d41331bb4c2959e083e5f62fb10d8b55c102232c8ba5ce26bb0227b3d4fdd52651619a15384d1fcbdcb2d9e8d1aac08d4833952fc92a591915d8da167df7a4a64c3d054e7f571231674b0afcb6bd8898c98927ad28f0f9233306f118a3490b0a9c485c09b9d3e94bb39ec0f5c79c325dccf3f5ca09ec579b664b3781b5d12d66723386f299c0a9f79026ba2a33be043ef8a4281a456c728e12b898ff944e891f1e9249e0a3889853c7cb413f8804b637447e5fff09ea1d813b5da739067d79296604764c453ce6a0725e844ca000456037d4c9899e2775d7446093d0bdf5214534290e8191e32124a4f6244d6542e0b373d404d3d0be292e70b818a71e508020a0955a52ca2ac1c3ddd328fdace06b4105000277a2471c42017ec096674ada92bf55aed1d1a0003ee04e64d0a1f35e6efd0d0705e801e7a22663c7f1b8800b0dbca0003c602cc80c936f1253e5ba450e0ab0034e9dc64b29b5c69c357643074cbc8b41df69ddf353b7a00039e02e549b524205c001ff1d430711537c741670e1050e34c60e6501175ee0f03f0628c00db88b1eb3fb4325e8e5779c85c059060a6003ee227e964eeb9492fd05a8011be2e94f5a2359f20d0b4003aead455e8a36d3eb3139b494371815b8fd9805db29e6383abd6e0ed5f9172b00030718626091057cc8828b933d66f4ca3e69f7c1b1e3c610e368a0051fb1b0ac7478081e3432f880051f4b6b8a93ce7292267a059f49a72821c6f6c3157ca5c66c6923ed06f9b582bf2b1d2a241f3f58c1e6a86f4274b86afed22af88f3c42574cf1c775f4a10afefa46a78e54f57ffb2315ecb95d7896d2ec6b3f2a18ddac9b60ca7d349a4e510ea6378a6ade146ca90f1643bacd91d4a8146c5b881ba6695dba37a4e026b5f9dec56e14fc8d9dcedd561105973ce8cce9db534c950a056f6a5f5782bec9a0ef0728182147fe5f0eea4f70797793fc623e3fa1798289924d4bc7ddceecaa13dc7a2475f31141098f7c7082cd223429996983d2b13e36f1a109bca13a3e32513e30c1c5a08386c4bc5832dac5c725b864ba7372fb6b89d1cba159a47c5882bbfa90548a64f5163f8756a9faa8045721b46b497dfba0e91c5aa4b6bcc1f8e21af04109ce638c28d174ee9849e863129cde04914d968e24d88a112346bf10178f5d043e22c1eaa411adb316f7ff3e20c195da7595eca08f47f0df1b838cf963b6baa00f47b031e45831a51311a34a1bc1bafb687dbc5c9bc6c2850f46b0934c5dbca4758c41868be02ce4adb4bd493fdfae08fe43ab55501ac2e123119ce6fc1ad48990d32d09116c12c1f7538d8cd4f71e828dd949dbfd53cc0e1f86e0b4585a2abb0d19ca367c1482495ebebe6fbba5806fdc08c38b84868f8c7da8a4f024e25824100883a1703810808102e603e3130000000c201486429160248db465fc1480034b2824443e2c16281c2014161a8744c24028140608038160180c0884c2e15048201ccf84bcfc23fccdd9b69cdbc999e79ce7382bda9ccdcd90cd990f73c6dcce96250f9f0356c899d94abb0c693383abc7709596ce29ca2e9d82b9731b616f895c47837bfccc3d8f7b0ed169fade7f1c99303dde814deb1166c45d17335dcd2866080662750208a22d4acfbd8604ee0af7cbdeac53946a23355256678910c3f65c9854a1168be89b326a1661224fd6afd2d834fa7f6ba163cc780669085c05be215e24f785c3c2091c82a5b011009d7eb9d6810d19965687ffc01b79050cb0f5b5b7672bd90d49735ed176c4c29d8d26ff6656b1e3c5e68d6ccf32d42c88053d878cb4d89b356121c6e908fcc0d1bd5cad382fb3b985f4189e1093ff24a0eae6b9fdfaa5c7a996959b0743986eb3e77bfe4605f12f85557bcef57a853fb8c2e504163bad26ee3c3ea29dc52ec3ff66161d54e2d4b1c5b3e2b1d8a8fa2dc1c74d655e42884f4d4810f3bc45686720a124b49317bc4b00dd21d03b6a1336613f500a90229a4311fc3d17d50eb0280a5da010519193d28dfbc28a775be808433e704c21984d39b0c669aad8aca0c5806e182d8d867bbadb9df3cc3f86b525e4119e3efb0c8d706a78dfc143abb1fe5c0be0c2c521203af53ca677c9ba39e3cbeca9f2bebc27be75013e16e90f2092a199c3f5820ba5d5bac5913d05fd3d76a36e59f99f62c3158687dd53c9fd0d0f09e2b431be040f1436264a3ab96e5837d71dee86d59bb134156ffae571f766bcf11e8cf7cbbbf24c79a93c40de9537d1bdcf1cfd78a3c48f9d5008e8e819fc591a6ada7061db859d298fb06cbd5afe4de88881a8bb791ade8ab79f6fc28c1d2a15f31502527a13e44de07ae7b742f73c0bb357ef11f662e12d4432e6e2d6e105279c8059942ec486ca86b68539070ff4f306dbbcfb8c7f6469779a9ad6d6e460e0f52feb163274ec32f5c5b8c9815381b6604c5444de1d710bba30f9860b3a6929b90bcd9de75673efdc656e3f37b43bd4ad706fdde56e6f37843bc48d7add0ade9271a6ee3e7ea8e28d8a169d272a257e744752c67b9c5f0aee9cdb39a6a5d6a90aa9876c593a757dfb6dfecde67b6c4b5627db17d51a7852e9d76d7a612591324a962f1a4ac820d435cb2b47c6a3885148b1210d1152344927471f21a5202279479bd1ecae196a8e968b5c85bba4161d4103d0b8457b982332b1d19553c8189acb7927775e74317ac0cea48f51a9c552f9a8b1e799c1364f4e29153cc4d252a0a897a4a537a33a6f15869f05fb91614864694b76b2e641ce1cb30551477508c0e4932148e488940265835ca018c424be145e26d16c32e43082fb265165683c125a0171f9de345d0d14722bf73ccf4b5eef8122d7d0dd48bb5130da3845b20b0ce1fa21a9263f386e59ae48006dbd0dd2fb4101030163b313ad0e806b7b9135c8d4e8b36d2784b8b4ecba2f7e6fbcccb7bc7e71d0cb1288cf4d76b2f0555c40df0435082727b1ed7296cd0a83ba6c4df8d4210763e4ffece9e133f723b60a90efc57503079ee3f01ea559081746cad4dca70c00ea7d8b58dd8bb06ee401d05e25a05eb73dd78d934ea15db7a1745097603af90d5070b4aea9f052948fb361c7552134e496f19f0c34dbb77ce8c0d557251e7cf83215a122f935e5bc50db6d668269bcaab06e8297114998b432c92ce1d823e6783c1aae200af5ef57218fa4fdbf9dcbb015d3dec5fa2e8ab104889334ca906cf1b43a385ea9c07b2c09dfba501a262c88e36d4460cf7c4c3d4c0552c389c1010e92b78295a6b1e5d9495c0afa9a342b588eddf8989866462f8b5b2440cd9061b5c69132b7c48b483c7767d28780369438019d780586ca75b854e7d910baa21e6ba3e173eea11140c9b570b904fd41ecc560a217958c710becbd45d5389aded202876af974244a67c73952ad23619c2359f59b6dcfe3bf27b20851dad1a8b476a1b437443670d596c9630e547ff648a3749d2e00722ef2c6299394c8e993409bcd1d51553acfe093925723dea2900c7a55f6702e5f1f0a5cb3a2e8c8da7eaf2b330320861109db08ddc8add9f8b2359bb21ad000cf634ac2f598b19572b5d7708f19919632f6ae6ee4c0193252aba1ef3f8d4c441fb35297b165a611137747194e85e0883819076371a71099cc6adf9bdf4987f02ef182fae849bcddee61fa210009f1bcf999142eb03226982cee916ab5b02d44c8638000b246e30d3df95dbfa1be68858ae49e3fd0867190ea866edc860df2691ead54994a8555a2810f9e03a87b8adb40b4b81bb791cf86d3baad762f010aeed10190c80f89f19d2021873ef935831249264f3377a1dc9c5696c5c95484ec51fae7c0ed7a521bd037d182153ddbb0943d6f5440c1bd0ceca4fa4256974d0162b8b551462b1798ef7f3d5d5f44ca9d8686cc723e80a2c24496afa0f1d15133b6cc24fe824cfcb0e117cfea3abf962e31cb05af36e1be6dda2c07ec38d240c0761465fa1274c1a449b078c7b17401a5866d7a3468687e756852de462442d1c01cffab5d7f9e8b21c98e0b29fb214d56d44b4e6d41485d8d840ea2496a5dd51c30814c6f83c87ada6c8dd6980db039807197050b388d3433f6c9168949cd2d6bd8e0a70f29b7535694a600fee9b95fe98d8f1a6058cf8907852b3ef7ce25afaf81217188683c5459a21db11b15543cfeb47038fe752987062bd70956d0fa79660bfe538c4d10506ce825ca1ec9f18ecfa36c6a13045040410b2442bd5599307717502e0e697eb016517d8b090b497808392dc5c5a9bc8bb9d83fbe2f26606ab7520f9aa8cab5345cef62ea4796f45b325e6350c351528a86e3341311be125464182f4c566fbee8a3685c6452dec82941eb71c65412a74d56baadff13ab9b36d9c4a1ee4ab0575c83e8b488d15d9d3da1728f98bf1c840d621ffefdc294422da4424ba5e55013bcca5d07a3841724605c56df971d060567a16871405fcd63ae10619c3b8a9cc4f2eb4f6cd9182de961267e1278c9d59e06d00f5ad3a38fc3f19846635b4ac57707981c1b374094bb306cd73a02b7ed97a5d36822429e2c784579fda9809789b0e06ae0114ffd2da224e36f20126057b42f015905e4e28c09a4c915f4e2cd73f5ae06e0c45fe7bfd04b68f4267c235863e814c442c77631c9f9a34274e0b1655836d63fd7febf448e5ecb4e9aa1f103b0aa1bd456b3727e60278f5587faeec1aef32fe58362324bd29f6e4d061b7a9535998d0965b54089746c243fc63d7214d999f9a7f864c94e08ad517a298a654263eca4912262adee08a1b85a85cee0b35e8b67ab9c8e60216e5a5b67aef7004410477468d6126bbc8a81240518a7332a8a205f5dada0225f270174a03284325868ff7e2e03d05ba1b6f23533bf0a2aa8b73ed36dcc197ef27d2842010caa5538c60eaa81338aa1a559043f5658963ee5da999ab2847181ec470666b9ddc796797f29e3ab2270833f833991556e4b030611163d0ec60baac74a6faa4fcc27c226b87b2700c9e5a6cb4379bd3aa6b8cc9254b43eddb05e61c43ee4a91a1311891fe33f358e8da3178d6b49eed1ce3ca810fc8f4675039a7e6af4340a0e8440a270e14efdde9276a19776a26df890ee0ae41030858a7bbb4db02d4ea274cd846b7151ba7449874a4c410dfd89112d21f9cd127049c80c59dbdc98e8a95e8d72bfed25cac77c01ffb338001e970f2beb1e1a1c812977ff27238001b4d8522051521641e1e23e3480896fcfe198f6855ba8f995bf1a80e8bad7f4adc6143967bb1806abcf8e590abf26801b2244ed42b22fc25a3f248712961bece57c951a7e6485e4e4553c6ca8e61dabaf409bab6bb65cccf867e69054d0f621b84066e0f219d3f34702d9aa420139d0e421509e577c275e59fcf874c6acdbe1a741a6102e9f9464c478da4f748e9e83c6c3853edbcddaadc405b6e3a0bb8cc7f1d1dea915fc142bb8868bd9366b852fd7918f263e359a32d118704358481daf2149749c8e0003034d731cc498f0963b52301352e8d97f3528518e120c98f967812fdb5e7f95c63f57ee74bcf85af6a46e4d019e8a4b4318db5023c221cc603934afb720f8d4dd6fb2ed54700339a87fdf43379ddb237eb207ae32695bfdd68bfbc8fddec44cb117b95a286d37e49eb5ae809417641b0e7ca3fcfe6c6eb8ff424da114e99c39f4674c7c55376e0864a7e7ed14fec299d727f223e655b7caec63ab0535a3d14e349cd31247c70b4f472f4577851691f627052e77b25932965753d54abbb43e051ee0082c9026c74c450ae42203b2b47bd9c6880c284a0530edba2796dc8962b62155ad3dd2d6f16cd0e7036dbf80976396180a58f54e25c63a46b57cea14db74d96ab12d3c5e754a7e3991a0f3081a5105e3ded0cc4f3e83e9522a11daf3d8876f3b1eb4da137463fac6b2858bdfc14b1dcb80d0cc2dc3ea1e6c4c21fb0db448df5116580b6e05c40590d9240cf3521a4e8c2352f837302892e1a94c834369f51057c6bd385d786d3c97cfa524254632d407bf81cab69648308f9bb6c81c8b1daffecdcbc1a04c040d53b12e167491343c9244bd10ff11e5f2049c6a3621a5dae536c88de633c4556d5253f9d170516d98e0d5b654e72527d520aa0f16ad7903bfd25a5b1837c20d7ee24bb6e640e5cf09d0f98883787fb0cae54ca35f87d3bceb2f08857d634e990ff8b2a32daff820fda3a9423459f74c0e662ec0ec29b68fd39ea8c840160a94ce98cf1dcb405cceccea4317f8c473c0ea007198e6880ed9a8fcf4a83c2fab7ebc7be473aa0136eec07269d701849a0563825409352a3b0104a167275254581b92f503b8c2ea8518f386c8b19623ac78d146d58447b1d32fc08a355c93a9e77e16e99a9ccbff2593a2afcd7911958e9c2a3aee748522a3927e5e8712f11214ee84a11aa37e3a19365e9fe1746c86a377a96207a4615932a55eb100ef6ecde046f73e6a2a5b309e53ddcef26ebc6eff80d1e1935d93590974c0f1464dadf78ac48c95ed67f996f064bb784496ccc348cbf448aa313222be76bf78f292e4b81c1759dce6bb36adc3548e6825e20dee8fa622b0991e17915b8f8c1fdac02257dccc73dd8f002ad3061af9497b795b4d80b0084f9db04fe7247c9c0ffa97804feea0a05bd04ef0a0d7a6287c003033e40aab411300e60405b63ce460cbb452481bbc0e7c4461c193649fcab96ccb365aec8185e03c7c7cd79c094ca87856ea12ddc224fe4a8652c8a4b7126d55c0a6b7d70d25dd3b9e0711489252245dc9bda42d29c1290e7d5744b9225d4b01246aa98074f3b4c08a8adad3d23eae3cb180425d25d24e5ba7fa1392e3c45e92c7298e5a96ca3a719510ea523a78bab45a4c5d484f9ce679f5c7e26339f24ab053759973a37cce2bfecacb5361c0c0bd66a9fd3105b61f8e6837a87f8a2254a290ea10a9fe2d1aa7f228595dd2fe6f670b2389c9baea04359da53f024271ef25fb93fa9aa5be2662a96fff3e7696a2686435b2fa4a152f89a0e49a7cada4b30ad8c48fb003fc8158cfe90d24b314a0efee034d51f831a85f8c61e2a596954d4d70803ab3f3ec1b4d73a4bf8e55ceff43120e5c184d88d16054f6790590ff38ebf95f2201ec106305819f1beec2b43288995be639f3cd3cdcece819ead019ed9c7ef93eb51cd0d6f8f755420932c72769ce1c4fe8f14356b5973fc67714335cb4f3ec88ec299de90970f5dd73418865d253dada3aed3f96b829bf2284bc8cebe52c673a8f426b1888e493ff4cca36103a1009f3ee2c537e14fbb89d81313347fe8370035d514c86d40bcd159683b6053302ff9c96d679a30d68f33e669c78a1dccb4f0320df894e9a9b54f462d27ec08834efe3e400d221869df9cd1c220c1d7e235636327b4cdcc1d085beaf1c2113c30cd4e495f32e2bc5cc99d1bc637da41ebacdb543a2660bc70f20e9df33a1ad3566dd64a21b5475efa082e88b0bdf3721601ecee0025e3dc4deb67907de0c53efef0de38b020d7a42e71d4c8898673d0e1125f36859e1f51c1c6c185c7eca1c289c5848350a435c048cafb790486545e2c1e61d0c5e7071b13e7363184aba2434849911e806b327f01c4b58117ad9a256c56112184b25ff1187f54210d93ce74256f0f48ef5393044dfafb6751e71d80bd8822b506472b54caa1199a8817f15dcb0165e31a1a9039a00f9797536944cfbffb99801671d38f2e91e39a4efd422a11d0d8715c17562f42613a7461f90e6c42f9a74d86309d63c1c4e4877bf145f5a7a25256d64268cedbaf2c7661ff060d8207405e31db6c642717a6316b8cc16c27e944218dfabdcff02df4ebe2e7e7165934937e983215e16314a0a8d79cdcc05286cb6cb894de0d24c0069a9d852e14bcbffc6077234a2366823ffecb7f598eeb98c6ee7039c5a890baab20df2a49e4b2a59766d26480ac8330726356198778953cdd4b3eabb5400e8ba341271c30578f3f7a18a7a7d6dc6837beb0659bbf598dc4109112b7251148c97a06ec0dc844dc50f234e5258b828d78a6f364ac3261e2d2a60ba478c32af82743548901490f42eead5dd515a1f97d7a11704265f15fb8b7af19608aa9fe8509069d66c172e09f8291af272c3effcbcab6ab9fca97076e60628f93afe921a0b7e52b3748f14d5e1e3491281a5621d096f04a066f9fc2e306481aa260acdbf4067db93ca7a3f06be8e6a61deb5a681befc1d310b60a86c2c443a03c720aae06a260ab51bae780a53bd0222ba3cb4f927769fc084178f13aef04b12f5d1f74e43c118a49a0d519508a115f5541c00fc904813f3300135cb35c21c806ae3ee9111634d7f9d8f3c5750c4f84a06b5f3c9e8744444e590137f527afc65c7a02750d0a08028c3321594737515e0235dc67f10339f949467c24a7c3d7e21eb99a524dc37867bfc1af616ae3613652010d5cd4d05b67e0b2165a472b8e07e0f35a4914234747f81c67fb88684cd24e66ef858f545da8249b63525d37ffa641c20c5d6bc0c7b14e0d3155cb7ac6c0c5bd7955de2430101f3daed09c603d7350df9f4c09a813579cf234f9feb00117b9507bffa46135451bd260b384aef457b1bfc377943099bf87753ba518e0a2795d583b017f217ad58e18ab08ef3c2757d81348959b1630a97cac9c233790d84ddcbd94a404001295a34115cd63ee9b6201c79d720014110c08f40664d240566c2bda1b5c7bfd148953a56a4f16fe1fad1a26eae501b49962a50157555bcced0460fe98e3ec22c20ad98b7c8aeee7c284df811bd5e36d021a8defa91af1177e25391ebd5e27a6946184ecb8845aaa2951f2370586cd6cfa5fb55a6b6f9539fec900388af9224beda6a9c2c8bd12d56f7431dbf5a701948eee12f1a36fee9997f7d1067ef4accef88a92088a254f5a39287927a0af24c43f9d9d54bd043c404182d8a7eb26a7d6c5406cb700b7b47ee9d0f21d5bb3fea7dabd72a92a41f5b877318f729aac74abe9160a25d123c13af47541a53479e17158cbccdab65970878ced3a0ec1bea7c997ba7e9f174d73bede04133368b3719b3da7b199f52df3696e6f7498992c79b6561aef1ca30198f2338862f1df0a60a61c10156025dc54297d3ce78d848d3209c8f49b24dd8a036f7e6f96b1755a8a19802e735a6fcac53c7fb899137b083b22e5edfcf46521b0e6530995ee7ae497010c462ac6d2ebb119a04487be53dba42083180fea1454b32bf2dc165dd439301729c7d0761e49dea2717f64673411f439057300b270c39fb61596b13d0c10ac48a4cbaad4a80c7ce6e4f80ecfb72a8504071bff29f75dba42709c43424583f4f57a6390c7ff28c4dd06b91eda67e497bfe01a8edce4f93f612149436c2368c82bdf9b7426716ac4fa1cc5e176126f9a76516720f119d6637f51bf27e1efe978aee0b513920d151fa2928031df16644c42b622dd6158f8274e2512872bbfd6feec1e0f4215e0995db5c179d4924bd25543a7bf9906fca12504fa3a394d413a56ea2c47a4d492c343f1e5b2f90241e66455d70d44a01b214061f6f862689c75bf0d80b1ed54b150fd5be1ef1757fddffc72716ba1fcce9a4f62872cbda99d3a80cb7f213ca5356ee6f36c016c52259fffd302f28b13cb23173f0aa0030abd1412f6d972ff5590f8d67e45fc7bdbb6ef9850e9b93b40caffdf968b97165dcf725c360df884d2810084e85584c8374c4b5c6f59b75c4362bd01d2ce02d2e8c9d662b640b0c0bf76641ea4d0b637b0a97b2e958f0160bdb7b17062151805156602f7013067b7541e85119467240c9124e239861246fc75108cc77596c5d5e63b143916f5e221175ee1295ceda8ba483f321bc5c275aed647182b655a0311b7fc4da579f939bb29e154340e7468adc46f198c96c64ba9e3914954704dfd6f831cdc13da1f1281a46879db31a3a5a9afcd38ab73d52521ed4a191a7df8d845c3b2b4fbc06585c322c4e4e0a2c48fe3833cdc6818b6f02ea9cadb61619c8525302055b594db7de4db4ded5b1e8442bdbe73a756c65d56d089c9535301867813d5c8efce87f78eb745981415a0963208730d204e2b912d1627c7a48e4163aec994d160e678a7bdaa9ea4e8659b924114cc5d9270d57a6e4ed1f34dc5889846260fb2b16420688ecd0408268014ebe2600eab0f03a790612183687b195f9f6975fec47c3146a7f816d1d1b23dd610a23acbbdf419c90f19c8f7028fb0f3b1c9a64a25cc2a7944d9bfd29f76aa05473cdb075de04f6a1ef2b809d816080d885c6036c25f7d26f9e903e1c18d9a2c690f2648d8bb050da8c4a2e310a61b3e61f9cb08f7fc46528ee248d68174c490fe837129014ca4e0ec3506b9f0efbdc38cd5b39be743d4a8e65d5063c1a880685899f465b4784a730b496863443775f0351153267d71300d5c79013346b800889fba4d8ee1512c6f92b317e50d753d76e4c373c4cc2e10cca8ea7c2c120471e93a32de5842ed50479e062816ac6769f781a2a47fd5d25b9dd7fed580f473052298b8770e50d566d2f9c0c8ff2aa036f7d6a48a1a538a6258e581a5f0e2cbd3fa9a0249c8d6122b60b6aebc811d2797b44cb80ff27825e2120ceb43e045140449c346dceedfd4f5ccfbc488d65ed8e282522b6b2946f00fb59979609db58ea6d03c2569ef1a1340719cf51190368b557ba40fe7e21711349b696b1dc0012241c428b9420b7c8811f71c35648c9da23ae92f952f8226a848ed611deeb90a5e209ec843d614df82dbc0a3fc2acf05fb8164e0837c2c21ba24cad43042d08b6824f43da41fe44ce9a6bd66a111c7ecc8a84100c0990f481cfb0b0754382a2fee9bdae163e7f2c24072fa4ce9450782f33fa58c63eb75bf7e2c420476291243c88091a2908a405e1bb17f54f675d08ba7c96204fb097dc26bae259b41ae333a5ecb4be0415020b24eaa09301498f69898642326b1d2aec9755f26846ee8cd210e5bd491d26f8cd808656e44eca690ae33e9b6882188fb299327dc88753a326de4eec2913dc8e5095d7dcd963872dd4e95cd56468a83bf42586b56803cb0063cef42b41c5c863e691d22102acb329ea2d47f5018fff59caf96ef219e525abd6a29599aec65dc650b04c327c1f454af352beda3a5e22ba32eb0fd7e44d34fd07b5d2b311def030f7caeede5acc3eb5536a2242406faa2ef235099680394da385e27887c3265959f760f80980c611fd3a9566627a32ebbe6a165d2c00d84aba57944f1ea4cef4d592179623fad97d2880e600e635311233b973952902bb62b658a7c5184a9b21b370b8fe60d56c3236b2a00327f4dc556746a4323b0d17559d6fa49878230f58eb4d01f3faf44d67e5009c7f33dc416c018a58c551848797444988cfc275a8bd2da85a363b0a366d9a52086722cfec3b418f8d4702f636938caaa574259fea6148382440c79c528fc09190b0edbb0b9cd02c8dec1fa66797405aba8d05a58328e3ce3b37804b24300a9f3b875a2d8345be2d0c30d9e2e40a55a5e0760de9f02a66299116412797f21adc770ee9c3e5edea62d19306284a0b9fb72c65f5c1d14b54ec9cfc9a5080a5c162be5c52eb15930cb3faae303323fdc3ed1780bab1721fa05c9b72dc1e01a1c55dc7bf16a804b31c75b800dd1c6df8ca914b40022f0b26130c1dd8083c79fc546fd6f8eec49113e4ace29fe1dd1b40a255c412c0986827e8ead4a25d0b36f993f9e187574e9167adc002ccb28fdbf0f1643a3a91621fdfc0d7e7de909600e8d9cea1b92abde5460f34b0d313c219a13d62137338ce96cf2c3dd6c07601270d178d1d95e15d042d8ac35a5f2f0aa294aecf4c2a681fcd1e92da2e6b5081f766f281bfc4d27af3152b82f1b1427a608da3dd2751b35b1c6f9ec44bd57036974ca64ea777ca0e7c14fcdb91942455d7dd46300c973166ce03631fe6bc8cb7b801a0a171ea0c086b474bf6786d7462ce501a4161b1cfec274839c29599c99eb10b2542a10f52fbecb082e9ad4b52657afdd8670043a599568836db4903b1f1b225dac8686c7fcaec5c47dfdbacf94954e506fb6013f8decf2620b111829b3902b0b1b5035b2d77c3d79ec9661378b51b25d8016c670a4cd0e68813d9933f8d024e4d4eec4ea895fdd6966ab3817a726cbaadd321f029015a4f65bf482190de98a213feb9815a65caec141e687189d56712608243c6982c7eb0109894967865003a63b1f83f84f39fb1031a1662b3dcee42e2162940d082a25bd3b3513a31b20c2530e9477a4fe9c056a49f9f5ef36461daaa946f289485470061560fc4b7bc110253332e90621105bca9d8d9b386a7ad1a675b15206daaa00a2887a7558d5e9e3656bbe83bf25e8964558dbdd2344a0d7c7ac3ce03750bd054a46fc29e8bc11d97c13cc71aae8cf9fcd03d8c784aaf62f39f438689035ef0f96ff406b4e4b98473cce6b9579eb16ec327bca6bfbb6499da22364a10312ca2bdc5b08344e0e1838797d282b8043aefdf2611f84e3d717e06bbe1f6a6357fa453852d4a6407f4f52ddd4537c6982a9e2af7c5901d2c43b1092f0b3819ef564f37a6a04a8d4d81a1fa7a346187a5ce904d719ffc6efb74deea9ff6106e4907577022cb1868ee833b23c5cb608cd7b0fd308f207e4b9491538069991c3d1e00861801efa834f6a89de69b647fcb9e648c231ae80cc31147fadb619e1b480dcdcb43b58c1cdd65e0f8a25cb4e25cea6123bd5ca4b60eca7b0a56a92c4bce58463ce48223e21de84747f308097df3686e91605f7971e1e108eb7b974b29e48a93d903d65bfca9557523a3140b35d7e12bb234064d7dd704e9e6df68785545f580356d1cbc54e2e53ada37445e24b173370a360ade6dd16cb994b1cc358c9eae0e1a2b26361b66ca45216dd374f8f9d13f6b8f871bdbb9de2445afb3b04104b479fad1779449a092b9f154d4302cdc0b02ac5f660fde041baa3162988bdbcc002de380e0bedc3323d07050045dd3c6beb68208850196fa08e75719335321bef6d8bd7a22b784f4e1712167cee0e471223c23a5c776ddc9249a5bcf4a71090dd04e188f7949481e21f33ef96cbc8c2b96db23b55f6f6358347108e185d2e4f93f0f1120aaaad6664dc4079e945f02553df450e07a40e5f8ce3ddca2ac00b6fef4e44e6a805d241a167ec797b702a0880220491597fff8dc17b098f00bbd13516c9469b39182610e92e02899852cbff53c19325697d34d573cfc48c76771155b178a3cc8e56de086fdb830923674d9707a3186b617b29e04466c919a8e40dff20c41baf878c79bb1b11c7ad855de0864a0d5b7200fddc769a6364a32d9c922c56242609419430db85a12518c273ae4f42d96cde2c90d7ce90f47b92d44821743f443ebcda91c62048588b59c67db69e7d45355e3727cf0e42ee7a87382c09f94631427b910681c5330aa82c01cdc0767ae4c9cfae5467371bdd3aeb85b2ad2ce4f864a9db95ce6d71ce22d22188cbe5b4132374ff52f40495c1e914d66e03ab65cf49f79dae419102e5c47305c04e98ca4da7c03ad1adc0ef4e3f841068dc861f24aeddb62b85bb83884f1aae70fa0d6100b6a087a25ae0098821562d46db8bac21b4c62a9d093abeaa546543429f02ef404f1a7d32a343cf94f468bc9798a594756c889aaa314beeff83a9a44de5efcf9ec528b738c7ff559be327244aadb8b67bf8868ea7a808a9767929634c99556563cbf7a72813e570ddcca3b8b6ae791702e38d793270b2426dabf358627e4d78f777969adbe8af345a845d211b3a44fb070cd139ca575ddf27f2d25f23b67d48444357ff888f60008015f90336a8d59969c005e0cbff7686203a95207121f3ec5f691dae75e29a5fb83a82d9fce81cc6e1f641d445380deb97657089c741218ddaf56d3b1c91493041bd1aa3386feef390d8f724f0c070da501787f361ea0be9914f7b08de820ce6d645199cd232cfdfd5df43f76d37ded55b4c512dead744f18755f300aa1676cf627a08ebb10e0b5645493d57ff93381ee7efa7eabca6410d49666e884f89ddb65ce67eae91725133a9cba5ccae5f4ff8c3ddd9374e14e9be46e510c54cd9abe842384b8973010117474ea30c673f0e0d6928b5ab803de511a18d851dfe4253c6ba6182d9398c2443fd5d26954fc1c60ef59aac3f7b9ba9bab19796e229b00dd517d61ba51a1ec230af124a97fd046ab2e9282a8d14915145020663b0634bb8f8b29c0a0e86c23145514597781bea48cf03f300daa13dc0c5256806f8beb40b65fa3f8503a59af32ba7fe79939ecbc315fce4f73ae7d539fc51df5c81f208094012c89023e1bc4920a3150b46439fcffffffffffffffc2836884b6196b5f2699a4d43fb65e9f93292599524aea8a507fdb7bb1adce6bb700705002370ab70a2a0b6783f633402006064ec07808a3a48a09795f762b7b3b7018c25829438599f9948102331e8d11b081a310e628ae57faf7251fe28430560a3955f2e8312999580d1c8330b9a85c29994ed23e98046112426bde9d754e218a0261d07d7969a132cd4407104693e521f7d357d7e40fe60ba2e53fb473d4b2f8c1a0f7df345674b793d70763e86041041737efed2c1f0c6bd97abda71bd6e93d184497ae9efc5c8c9f9881430f063993f263296112dd1603471e100d0e3c1872492a49b12d6b2a5d068e3b20871d8c132f3b826ea7856a1403001638ea6078ad98aedfd9a225a7834955a4eb9a373122c77d1b9cbe2570ccc12c794be95eb864acd982430e5d8bfa49b2f3ccf19a0c573150b0813478607860666046197130fe7552e3a1974fdfaf070e389842ee11d52945d7f7eb0e1c6f306e8dbe9896f466a204898101316c6c1b38dc60125f5ab6e106071bcc7d7593ef53487a13101070acc1bc9ae57741d7a5d21b047567dc020e3598d5c45354f6e3ed48c958ab71e36f50461a36520d1b67a41a957fa38c1dbc7f192420f63ab091032788230de61093bb64954b86771a69f49a064ca08292010e3498b387c52461225f83be41195380e30c061dfe9e2fa4b012a5b4c1e9b7810dfa0635920d1db0251b3ac0097098c17055f24d25a70b950f9571018e3218ff928509db5056bb93c13022fe95bca4c492c818cca9bbb2b7544c072c050e3198a3e536e1f14708cb3a0c869c336554cd4704638ca154e00083e16db4d5271d9f274f38be60f894945f9dd6923eb91a26070e2f985267c7e40f2a2718630ce50347170cbbebefa72b574fff655c000b1c5c3057906a7f41733150000213ac81630b465739bf1b09deb5d7c0cf000187160c2178fac64d9e4923e1c8827135ecc297b84ce0c082c12ec7df8591db298538ae604a5a44856eed2748638c31ac803c930bf1c55392048e2a18c73e575e388d3d1351c1246fb174ef6939a6604a634244106627cb13a0148c56aa74a9724f0d237044c16416f1da547fc4fd13148c21737f36436f434d4f3007a1baa7f3236278de09c6ebeba4d7e72618f29d0863ea7cc276ce04d35dfce533a14e657909a67abdaf0f2b17613d4a302515d45e5e11d2c328098657fb71fb2072b86023c1d43eb69eb49e490e3a828d911c46e02882f9b292522907217663250229824ee710cce152cbbe49eef0d86e091c423059ecfb750ed7dd49b5040504c3a419174f9d4812820c0cc35f28ebd2ff22ed41c55cd0fac258226fe4dd30f5c2242b2cee93887861eaf91369e3ad257476615041d72bf2a90b53c8ad203ba48d94839d0bb27857c8d6a6ed1e63d973f4c5195b3109e38db791831a4b06a100bc26cc7b5d8d7c0b835249453ce7d71686fc513c4c8749a6c5ad85717f84cc979b1626d9ff9d90d35c3ca9b33059c8b8f0105516c6f8adc94959c82657120b73b00ea33fe8242c0c3ff163c40e93d542f015a60e5a34b55baebfe40a536e919ebf9f43c9cab5c260c9266dc6dd5cd28a025861d0cabe52d12ec53dab30a8d9114ac7fe51492e551844a6bd455c3f1559292d3fe975428521dfa7a97e855c21994e610a23fb22a27fa776c9146657b3acbb5ca6564ba530a5ad0cd1331e24a94f0a43f851ba4cedcd24ef4761f2944d5d52e5a5f14561cab75a290579288c7212dd84b8bd8bb183c2e435c2528c2c132be29f30bdec7dee2f1d2996ee09d37e6f558ca4d309e3a5668d50677db23c9c30c7a9d23fcf3e56a9b30993d25b39450a61f683ad09739a8a093a6974084225135ad70861c23023f1b2043ba9e1954b98c56554f6c92c215ac268651523e71257c20c5921e694982961708965563a65ad09f224cc7e966c7410af2a2749984f6915356b3d267a244c324ab456f81bbfc842c22897bccff28ac7fff61166d1eea0ac3ea7a4e408e3248addd908437e4f759e57216c4698e4979658f2751127a1bc3c8408524598724a1059eceee74b4d84513f449c7017f4e85111611eb9172e589ce8f9218730cebd5d9e53223f7c8821ea1b359284b4904298e25fd8ab6f5d8e242184d145dac5f828a195e7208c25f47da49935152a8230d998aa88b6a022693110a6549e4ad47f29b32002c21841cc6bffff07c3ae78ab5ebeaf1ff9a130eda7f4b3de0793e88fd65fdac34b870fc6d221f7e2a9f76048fe313c62877f5b4f0fa65879a62f25793089ffb7d3e21659abc38321d7e4ce509d456e7f07838ef2cc0e66754f2289b7eb60f4a03da4b42cdd4ae12328800ea6ec10dffad91ead3207b387d6940fa2a5c4dca2003998fbf24452c943e26048dedba17f3412a4a100381872342de35b6fb9737a83f9822ef5f0e979544e6e30ac6ec9a888ccf5d1b7c1a443bcefbd9899be3e1b0c56c235363cc6f885d660c8b9f9e94ca538b1e36a308d4895eb2fea6930aee47cf1a9ffd3480e1a4c419c9ff9aa256db13e83d9a468c93035aa92d56630ccc752132929cb099532982607bb6b13e299a3420663878430a6372a5a5dc660f4dc29463c93c560f211b978fd881bb286c1385b51422a3d35b925188ea12a1b694f5f3077aa7fb76a7fc88f17cc4976bd85a958296dec82b992fab64acf3f9ec305a35795aa0bf5313ebd05e38d57e70fb95c49755a305e3a799eb3ea3afd62331490057387e4b69792d2e94fc282b1ce3ac9f8f824e2e8af609219aaa532defcceb702e2773c96fe1155c17013f594d4ff3c21712a9884d6c4b4d27fe94b5330e80af627642b05937cedd25e9d4c27d95130bb752cb3ef0a14be74332f215ee509865b8f717146b632632798f774cc12fd9eac83b609667935ed977a32c12cd721844b30b5b2e112cce925d6de3a68195109061d52e6bcd905e1d725c170fed1c727f9aa9690604ad53152f8ed9e9c1752c01130533119c170a942d07d9db37d9e8a6048c27a72ca257be3f40a208241a28ca88a91ec29e7153004a3a96895537f4a59880284604a9e7a3ec5d472134ac13088a50b297e47c0305d4af2c182d07809fe2f0cd739d4e979331dfefac224b353a530b7f6c278313232744f5e9842faecc5d83995f3db85715de5e6f279498c5917866c15c53a56f8503a940b53ef4e8aee2192880be2c214645e97e81423a8945b983dd6fb920ed726c01606196d73f772511e24a016c66eddbae461e634946861f0cefb984bf75039cdc2a04c58ce963c5bca7564611cabfa4c11722c0c631fe729fc75953861619411ca530435aff6ed1506f9f3a554541235b62bcc2dc122a41ccd3071b5c290a3479a5821ebfb618549fddbdd53bb7fd87e1506cdb8930f2a5bfa5d15c64c4d0941881cfa74a93045319d5efc42a830e9b85fa9273f8541f25cc85cd85870dd14a618e1d44ebf530a8350572e661b290cd1f6dd763dcde8708fc2144edc4589708bfdb6284c7a31bba183426190dfa1e6d2749e561914e9c4cbded922fc09935dce8e16b395728a3d614efd6a3bf2e2f5aed2099356779ccbe179d3b39c3049d4ce92266fc298b7d6313ff5cb6435618aa73aaf6d24874f66c294a447b1d4b09c53b5983025b30ed94a5bf457f412c6dc52625da3a3a65ac2e09f442e91c4f38e48254c395da570e96927344209e37dd09fb2e53909e34bfe78a6e22909d32789332af983a57889846946470d4f199e119c10f7d596c118caf4f2ba875fa64a06c3ac794839e1ef434cc760fe9338b23ea204f5a71850296c961695c530983774de5fe5c748f20383d1d2b8cee7ff0be62496e339a8a4eb6e73720ae2b36c85e5d105a3c950932ea9d86c2b3db860b6d149c731d3cdf46f0bd97552ab164cb26eaa652b5ee66264aca14fc306b2db814716d0f1f3224c23076714d3838207164c65256e94e7b42d3cae602e65639ea2468ab896580d53567858c194f74a4df8ab2c964c457854c1f8d1ffe2ab04ddce122a98cbd2c80efba78318a76010b2ffca76c7837b09111e52307e89119b76f9fcab838228182325fd9b955216f0808239c78b9e9f72f48f952798e5ddb479d0179961999d8387130c3329cae51cd63bc96f341924f06842422fcf621c030f26982485cacf7b48a3a3843c96607a5753b31234069f87124c51547c3cddbc637cc85813030362dce06d604c108210f095f64882073c9060be8e707b3a3df668c97dc1e308c6fc7cdb17ebb81c3f848711ccd142463df582ae24d2078f2218dcf334846bac6f8ef2e0410453e7aa4a975b1a824988bb4e6ff11082e1a4ee894a21f4c54e308c7dbb9643b9db48f081618a37b12e59857e61cc53162d5b12b71c725f186b3ed552c3fc92e5bc0614111dbd30ca8b0e49bd041d7288f0c2fcb7b26f21780a964cbb30d90911b4a4a02da8ef7591e75cd92b9872a1e5647fb612c7854105b311b9c27912b2dfa21c31f2a70eb3a0c316e6be9c5417fc24574a1203033d18630c5344472dcc9d35dbce9456684f6961d02968a8b78549299a85c15625260b53ce49a7d873652c4c3ae5ca93ccbaed14c2c2e069da74ce64e5f4b98e5718bbdacf7792fe77571dae30699af4371d2a3ce7dad10a434cffb8dc69647f7d32f6d3384304638cd1c10ad3f864356bdb5c0997d3b10aa3e5fba0eea49e19e85085e174e8f939c041a331023c0f74a4c2e4329f7b2c8ad0b1827fe84085a923567708ba7c63c5e544c7290c9ee6f24254ccc841aa91c68d33c8404d7498c29436fa417c45aacf3db071c6096eec253a4a614ec173e62d78e578925d1f3a4861eebf35f959234ffe2cd101d1c1758cc2a4c39d48e695547aa92c0af328e195b2495d0c1da130689c27bbcff2f711ae403b40610a955eecf3488aa1b78e4f1824071d545937de0639b041b6c3132695b835264f85d1a771e34590fa848e4e985e23453f51fe196798e0d4071d9c305dcbaec7db0f196bc4522a2303d9b10983f6a94b8a569d674732d68e8d336e9cd6343e043b681390232de8d08439f27ff220642713863989f712a29fd26562c2f4c94eb2dae9507fed1206dd79424a8f9c3aaab584aa23e62b4db850072a61d2da2ed692844cd94dad83128618579683b02c49e19540c7244c62419c4823e37fca27634d67604619356ef40cce6a7448c224af575db2bb3fde8f84e1841ed562112b749f206188233ebea894479867c4430425b28e30c6964e12b2cc8d3027dd2108310bf3fa3123cc5d2b41ae04a1e5aa2dc2a4de2ddc27e6841cd48a30b6c77f92e93a237212619ee41255b54584b94aa5646f7934d0061d87304dfe9027e6892779ad210cca2f0511b9301b3b29842959ff7e8ed157f33c210c9ee3e792131984399a668c87b06d7d16411864e4a598b33810a653eff944673e7c5e0161bc4e3aac94bef422437f30be75886a79b0cde81bd4b071460d2074f8c13cd79ff695ef1d731f4c3a7ae3734c5d7c9b3af8601eb153224da7875adfd0b10793698951e93f7e435ec618a30c1d7a30bb8952ba9245ca65e7c110d79385c8e94e2733e1c1bcf93d16376542cc10093aee602eab8a15254f7630e959f96c48899192b6a30e86a0b23fc71ef5cb693a982559306de95547a890be3107d365f5f021e7b782fa7230460e21fff563bca6280ea6b9601eb7930eda3c380231306028820e3898fbb44d77fa88625f7983b9e3267c7b753adc604aeaed6404a196efe28e3618741cf16f3a5eacc975b0c15c322a5b6a0f95f8411d6b305d57c8cbc147fb5dd5a106534e1777429851c971471acc29b376245af09c67d4810693b89265f1e2d67106731e0f3ab23aad7b3a7598a1a30ce6da8e961ef9e62fcf64302755af9274cb8ba5948e319894a5d457d9573d9a5087184ca6e25e59f2bebeb07784c17cc97b84ea297580c1dc9e6309d9d1449e24ecf8823154dab0681ee75e6387170c42f99ce890e30a7474c12caa62c988ab7570c16ce15497752a756cc124776fbc453fc2850e2d18af5ef74d05fb3897ecc882d9c4086175fa43b0bc93b156b2091d583078593ce541e92041d664dc681bdc48cb29745cc120b664bd8fb0a07492c6df60247458c1249e9752be824856e111dbbe362263ed460dfc8c8b4207154c4ae7f34a7491ef9b2103c10180091d5330c9deee284b21edd26a850e2998d2547ab31ca794fe3f0a86ddf2c841c72c31f291a1030a26a12a28efef70161d4f30d6571e7d227a6adc103a9c60f6d4a347848409b9e464ac1513424713ccee275e4b54aa850992b176b68b0e26984daaff4eb0cba3bb97e139406f63cd3a96607cf5908210da11a14309e692ffd68cdd8f5a0d6446d748c3df868d331c2f0523400108c6188334a22309df46d01e420551714400f0840e249842fc3ec9ed18255f54888e2398c37b12570949449f91fdd0610493d23616ef2f8fd277923d7414c194ac2e2bfdd9b67ae8120f1d44308b4851b27c5e8e4a7a1d3a8660f8d7c8edd194086b4f87108c592167f4ffb94bce81812318c6eb30c2e2538255489bc0010c773e8ee46f512612387e61b60a539dd86eca26cbe10b47cb9caab16ad4c0cb40a30c397a61baadf06217c6a485928c355e9894f6961c612732d65e0767d84083ecc2fc966cb764f9cea582347270460f1a0417c8c097610305638c91460e6e705217e6fd78b23d89989eefb3326ca060066694a183336c90a0ae56c0910bb3e849cbeef87061f25c953c67d3e9d3ad5b98635bf0cbbfda41cdcd610ba3951ed39da487ad7a6a610acbf38e60e1bb744c0bf3b55e165521b48fa924143866610ec26b525239fd39aae39085d9de544bf4b2da4a411cb130e8b855faf6179f4212072ccc398654911b59626285e315a612ad4ed656b6256b0e5798f4a7f411b4b3f5c511472b0c2ff973be37915cf671b0c26c2f42eca8a45444876315e66d13fa626dd35694f4c68d37430f8d326c906abc0eca4011b8a0060018c1a10ad3b6c9a7f6df6eddd2e04885f127d68358c5b67d97a83064b94f9f2646d0959392e31486590f49e280c314e6f46b4a59104ff1692e85397ad22108f70bf9107c3446200606cc18811818e8c018637090c2f8f964a8a8898ec214d4d57a5f885887958619efa2c83cd6fe509834b6d4723c670f7080c2587ad243d076291f1232d6d0b6031c9f307b780c612d2f7a47ed9e30a5bb1c1fbd7119a70c393a618e3197191ec2bde3be64a0208b15383861ba60e931c7b38854934d18437877140fe1ff276263e0d084b9e4e64f503365c2144786b22bd3bfc08109c32753b1d2449aabc0710963aa9ff097fc1de32e352c6132a1449f0877f996304725cc5f9574ce7f2ea38451ee2eb6fb6dd44426b1fa9998ee08faf693b1468ac02109e388ddd115f73f1a9130868cb8723a1f27fdcd0109a3e9f0ccfa0d353a35ce40c6f10843d2ce12ea16ff53ce7a60e3735002fc0087238cf1d9b2875ce2137034c27c65daa5df7eeafcba4119c8c1089350a224690ba14347ed171c8b30c8336d51fab4894990220c23f52585ca3825a3cd011ac57024c274255b95c389c958eb341a751a9d81b3438439c4d39c471fc958db47b7349694868d336ed80dc26883594250dbe1c43ae80f1b4c6147aa4593abf1c1b20653f64b951f5613c7623518bee3dce28abc9d531a4c924288cad0a2c198163d480b5b9fc12022e6b283f97d4a9ac110abd2fd7bd3329854dda4d86d12321852f27ca3151e4f98c6600e537fcdcf5a621783b9de47fe23c9ffcd1806f37cfe9f705142550583d1de4b46f768651208e30ba6204743ab68ef05c3c758b270739bbf6417b0ce26d404317a4789b96012c9ea2b56ae101e4f1b67e409630be6533284058fe4215ed1824988faf82d162b744a66c11027cb4e106e95ddf5c0461a663c89411858305e5b95eafa3afbabac208c2b983a3c5e88e65ba1d5b182f1f397d896ac963ba5aa60aa160b299274cba57c2a1854851439cfc147dd3305d3dd7b1493659182f9530a2e63428d82e9f4d9aa7cdfc51625148cb17f925372fb1df5134cbdd96155c24b92f085e104539ae7f5b0939d208c2618648616e126526ecb1e0cc26002f6de273bff787f7210c612b09453c4f7ca49fd26c377108612cc7d236e7e157ebc2c1784910493fca9d3eb9d173f289d9186191d20411848205dd6da35753a0c8871c836d240037b10c611380c23a8f9e93a240f855104d34e6e65ec056da4814618443098dac9a93a23faff52e36d80c60dca40038d36e30c33126da481068631041286104c31a2f5f3920e1f3a45c69a06c6182318a6687d6e23929c6c42fc018c740827dc7d2f4670f0f10b435061ed93529523a4f6c317c6f413c9d94ea6ff5b7a61ceb7f0218f5e0f0b3f2fcc25ce639998eff0b6efc25421897cdd7186c3872e4cb32729ffc80e2f415d7261f6eb38aa53e592f4d4bee10317862826afec736b753bb730974549410715a4a6986c61aad634fdf079265abcd4c2ec914ad6a79fe546190b820f5a9874d8b8dc7df92c0c2a7fbf1fe7e9e2c8edf0210bf37cd995ca444b2cccb61e210513172c4c25b994b6607dbcc2b415f9b5a4e7ae305ef0fce941eb34444a2b8c559b16174197a710b3c21455a26bcf9fac45c9c72a8cb2f19fb2a8bea99c25636d35f0a10a735f9a9790162d7a0eb1c0472a0ceed9f49da77b8d7f64a4518619638c199851061d3e5061a7305d4e397448994fe2ae0f5318258e38f9a92b87b7df818f52900f529852860a9d6db370d9761406d7d1511db449a9f0210a539e3d95f2b89e1825e64728cc215c298b1eb2a030b75558fb089553840a0d1f9f30ad87917849a924af2f9e309b6993eda4e2de32a265f8e884794543355efb635cee8313660f3e3a8470f98f4d98ba93c8ac905532d66a70a3d3a8516ae34313261d43cbeba578e1b3f6e02313e63d19c1d2363624e8c607264cba7d9dc289741f3e87c6c7254c29e5cb49930ddecde81b669c618605c618037530c618a800c0c587250cf249a9eb590f495755ad84c96425bba4bf38418614111f9430ede8a5511d2d859c3a0e1f933024cf8ff6f24149bc21b14f99928cad8183bf71fc1109d3c70f22debb95c6dfb861010c70400c0c8821821168a007e4111f903048cf8e2621761e611295b91a164e7bb54a8b0f4718ed3bd88e078f7a154d679c6183046cbbf868842145f21073f11da45a186110f727721e7b89f7e5224c4129ad7f31354518feab5a23774c909927c2a4cbe646cfafb347bef081088367f7202c99de05391dc2982ad142fe4ba4da785cf830c47d14c2a4a56321f444de9e9d091f84307fae3c158bba222ae4c2f0310853b84bb5753da7eaf282308a7653e13c53d72a1bc147204c2d9e1fc94b8aecf00c3e00610a6d12dc428c94feb7910334ce061f7f30ab8974fa6247c6a9948cb51ef0a08c5484061f7e3045494269fe763efa60922c626fe9bc2284341fccaaa6246509eb88f51e0c1f64549c9fb01ecc67b222f2e7c98339c99e252d953db6bc2a7ce0c174f59323c7506175d28f3b98b3a5eb10dba10f3b1896e245855d4cc6da1563c1471d8c15cf4c7c96243a986c52994d1a1532d66e2033fa0c4f061f73302595836f9c0e231ac21ea45106213ee4600eae5931b392ce103a7571c074459cffb87e95ac81c68d3a7cc0e1f5eb9873667d51926f3042ce7c3593967a4fe30469860f376479e27ba54c64d369433733f14f48bc78c992870f3620ff392c8d1cf93da99e1a3ed690b2a85a97730a4a203ed4905645f0f865aa19c23490beb6f78325870f34a0426cbce362e9ec1952a1ef74e912bd916f0d1f6628ccb9fccb778847e7f0518666b7829ad193c2a8c4c08018221003052010030377f82043aa6a219e923de1630ce82efdb394b23664031b29e0c387183c73dd0b27bb33d20b43aa2f1792b42fbe3330986639463b2baa46cc8ad9ed21044be1e1e30b065162559e23e9875fcaf80037e1c30b0695b3e4eaca5df615ea8239a8357517a16142ee5c307ae9f270a65aa9287c6cc19cba2d651bedf92ab5160c9264f68d05350be63c31dfca7a7bed2c5830298df89aee6123c6750573be8e2ea2a276df3d2b186c4bad2aac5527cf84955f86f041059350495dcea65bef43ff988229e74a9db746968241626b8a897a11d1520b10a2828f28782962425a8da806cc28a36d6440a16008ba744cbace4eec0b0a1f4f309dae382295b897f0e104939abd0c611e84c930d9f86882c707136ef4e06b50238c8f2530e0430908f84842033e9090808f23dce00602f830c28d1b6f038e8f2214e08308370881013e86501f42b0810d1a0310f00886033c8051a30c342c40820f8f5ff8e2d1195e2307bd3880072f6c6083f60178ec82c3431765a06181303c72413c702186c72d08e0618b5a24c08316350ce0318b1a65a061811b3638e30c122cc043160ef088057bc0a2461a08f078c58d2fa30c342cb0810e0f57d428030d0bd8f81ad83863056178b402fd0c66d0288880072b6eace2c3431509f048450d0578a062001ea73080872908e0518a007890c21c2ce6e7be2c29d6ef0a048f51182f646ea5e4c87fe692b1766383e0210ac34910d533b146e68743612efd1a4943e5010a939795bea825f4f8842188fd91952304356a2a62a06003c80c0b8ca145f0f08441447a4bdd0a69545f88e0d10973ed6b856fcf17365f74c605dc8c1c68608c1f7870c29055b4e38ea4cf61271e9b30d7759ce84e5ae5c08d31c6f0d084a95b2e4fe4f4f1eb7c076760608c3170e0910983b25d55cb63c00313864b52dc7c3b889f1cbe81c7250c49dec48a914535f0b08431aec4884e1da37b66b5107854c298a5433db59f2861aa39cfa5b233099387081e1f4aa83cc2968479c4c4c68275f49461240ce7f9644e529ed35307097358f12e91bbec3995c7234c62f27eec43ac7a3db081027f1d98230cf9449a96f9eed108c35c3c7d532fad6e71469894322daa1ed4a5057711a6bb0fbeef29238ff03c1461b2e8e2315f65e75d7924c2b4399ea59727951ccd0311e6e46e2a4832a14584ef3d018f439836e5c3488d943394da109efaa8daee17c274263f8f28bbbd9315214c725ff9fa3482463f195c1e83f0f36c49ed3c046188752a0e84498a560c08737b96f18963c2e61a68dcfdc1a43b82be9b14fea94a7e30e8181f6fd4c99cfbc4a30f06655afcd26ae8755ba306fd364eda7af0c17cbd22136b2f1278ec616d78e881a453120b0f313e66f8028f3cb89664a747ad8ebefd0a3cf0606338c2e30ee6203ee94f6ea2ab82871d4c7bb22fb6f809cdcfa981460e6ad4e08c336cb0251003036280400c1480a0e8c0a30ee630d76e6b13a2e731d1c17c67d7af1e3c8f39182f088ba24c720a49a1871c4cf2f9ab3a2996a59c338f38a4071ccc39b669b57791c71bcc7daf6e2a2fe8f627b3031e6e387d8affb63b95b6e191a4d54b2663cd6dd4e881196d34f06003713f69d3b6ce93dca0460fcce81a37ba3230c618376cd448e346bb193b3803a1c71a4c6fb9748732a9c1aca743ce97143cbae9a4c13c729d2a4c84892829683097aa8f92e44b1471ee194caab2ddd26a78bfb966305f4dae907a30a9bcf72123e6e1bab0248456c933e1c19c435b78cf967758558488d2d9c15cb2ab73964f12ecc63a98828758212b6685db74308baabf8e16f2dce53998e279793cc92feeaf1cb8582faa447fe26090951622fad4a8b41d1c4c4905d7fbfe10cd4c6f400303dc601cbd922d0989b5c1f4e5397c2fc60643983753d3f521a4656b30ae88ce3ad594d3613598dbd4bec47f7acf621a18800693d07be94756673044cd0d8b55fb494290016678bf52366de16bff736528cf579678f82af7c9603c393975f544977cfa184c97ef3987cac8adff88c1c9f1b63dd49f9b1e065318e97a39444f0c910306639d99327f516ae4ec2fa47223e547574b222fbca572178c3e422871cb664a81015c684f655d4656ce16ecf3a0f447bab72d5d0ba5f39c6654d6d6fecdc2215bd6c8cd8b61420103b080a7d71c193978fa5ca1ec17747c39252b14d143845827a2df2a5c1e7794120b217fa860921842bd6be9cebdff144e7edfdad1df3a2978c1647b4a416707191085435ab38a58595a3b28607f3259d4e49ff8099f095f1d8fd59d7342365676a31d235e450c6802327475989409e414ecd4bcbdc29b4b40bde3ed56deecaa90014af0d2884a27843a71332009a59633292a0c4002f2e3c595fa08c6d1972c45eca4b4b2114cd1f524c50929025a548a949245b1141b110c42c9f814bd7df259870143b83d82bccecb16b34d0c10824107d321b32b899fddc140e959f809d3253960984388da294bf40b436eed911dd717e6f41c948efc25556a2f8c1bdaa176262fcc1f734df4574ef1bbdd85af615226e88aab31e922bdf1a32d5a59bab97023e9911b5e41f85db8f092ef9f0a8d6fe155ce3cb3493b3a5b90e7f5926ab530d8a828c172e4f7f8d0223b1d2a84d12c0c415285121e4c59a43a480b1553fa7b2c529193fe38963b395868a63eec45ae3a855f61d2397c8ed45859dd15caa46829e47a044b2bf8be5049c9911c2b5afb4e6a3f8818afc289a9754a45d57454e1f75bfee55829ea5498c2e6a4921d0b3d294385c14743a7687f293f7d0a934a21757eb1cb7e2f0a0b30853959c69c8dd2fb29924a610e93e396fd8c07ad3f298c7edf4994b83841c43f0ab3950896c34b4dc4f84551bec88c89cb6b288c236eb3a22561541228cc412b451555a14f1892cab7b9e81f12f5e3098350d9bbde743e7afa74c214cf6d5bf2af085d36278ea434bfbf4cc90d954d18cd752d9c12b226cca73b2cb4d794fe8b33618871134e998e1f36c5c47a1d2c7c76a77bb92f61d44d3d6521a7a4476a0bb0844949cb4105f949859754c2146a65f2c84b29615e8b2264a77021494a27815097f36479fa699230473bcf22e6bafd4a9130e48e94cc2f04a57d26244cd5e7c1eb7de7477f8471c259289323c62b4447742179b776c5d04a23383bcb95ad14762246182ea2c7bcd32dc29c6d5bde4f8a307f9555b8f6dd5b3711a63f53df9e3e879e1043c42bf1b4f34e0e6a3b4431a9bb5f24ba7c0c61f48a704a843f1d6654089334bb4f2a2949f22d5563c1b0004218c2e85c219b31263b6510c64ba373d2058f2823fdc2020491cea8c9d9de06820f12a2e7ea7ad30362dbeb2c1ee1dcc72e7f5075745ec9048ffac12054bc4f1f9e758f9b741525870f86a0e452b22e8fd41dc28505ec61ad2d9110947a3858d03e696363ed3c984f279d922aad9d85090fe80c2dfe3d498864f13b20d2da32353b989386fa89abd317aeaf83a9374f74a8d27fca79d405cdc188223b25650d9197433237794b88fba97148a86ca9f0fb210d07934edb177c674d5aed6f604288a6748aee92f2c80da653428a9c644adb5cde0663a968699122365c2aaa04b9db1accf9469efad8cb97ac06c39f48a583103969b83a42b85cf30a42868682cf758844abaad319ca67deb1b47274a66d06a3e5b0f859d75b6997c154414b87d2ebc89029b12be93194b52fa6a9899dcec2627075c6b32aa4340a83414e6acba9d34c4f70c1706c91ffa6fe73aaacbef004b112c7d2f45e30e55877e99d4a871d912e6031c4ca88a813ff5f2e9c75733f29fdf3d942e761f611625eda73b5902e2fbfb2c05fee4fdee69d16164c61299994246bd7737905254553a3c368d10d1d2b982d455d2e76a4492b56613797943ae8a95088262c23a94e5f9a42e2af33e3ea2c2d05d3e94b252feb8b422259758ab8bd408124bb4edba40fea53fe09a674314b926c277095527487f0f14b13cc2656742cd6ae5a9609860b1aaa4f65848fb3b6b0802598c3cb271de4eb93ec4a30bee59b28426c88d1a62474399ea1e90290505297bc69c9439ab5802318f3928909cfed93928c6038b3f8cff98b6048dda5eb927b2298820a226b5c12b1da1e8249567cc9e593ead287162004931e35af95b907031193456e74c0309d9874e661ffc220bc4e47febca4ea7c61b6cb23ed428d900b61f4c29cce4a5744cdca5e7861ee8bd61d17f35df8f15794c812d68549a84baa77252717a60b49b2246dc28529277b95ba5ecea2ea5b9892127fded52962a98a2dccab7a9a922b5c0bb34dec54c14dbc63480b438e53114b446761d2799de593c3b996b228ba08eb148e852a13643c448717c1c2bc13b322aec809dbf815a60f4946da89743922ea0a5365bb9c9cf89c2bc55a61aa5211b34e1d46c594156978bc8c972454d7ab30e54ed172c98dc892a20a9398e0ed274723473415c6f6cad3a67f54186b52fa58e74e61b214bd6db9f4fd2a628af7de53bcc25218e5ef629e5d69e47c92c2789f5ac9e36514e6ec14bd7f3d8ac294963f8c94a82a59140a7d4ba48fe381c268ee6945825be8b57c828b5f732967957cf27ae21e2d173d24ffaaf0e984499f595b291155173d4e1884458be623b409936e957a50353bdfad268c575244c676eadc936782ff2a516d499830a9bb49f95a5498fc12bf889b0ec94410a9f49628ae47bddd5abba412c6f932955a8424fdcc28717cd3218e9787f52661d05177bbe32e6f7e4920ae3e4685302281c93ecb275de6d49030fba85239151fdd6e91b1f60843bec8a62e7e658c46df00c370049eea6147dcad2935824932e691bdab2a8597a0a1e24f55901f7ee5a61d86c10893a7702742bc24f36a2fc2e83977989a7db310175b43188a30277d229e68b1a04350224cc9f3bc696d57080311064f41a9e77ab50baf1cc2f4621e57c3574318249d909594c8dd33b24218b3439dc97a09cbde12c2242c5bbb6ea73cb9a04198b2754f6586ba76d205611256a35f94481372cc0361943c4997d5a5957705100639a264d49af40fc6d1b553f14ca77799f8c1f8b9f6e1aa651f4c91e25f25f93a1f4c49a848496c3c5c2e59187b305ce94e6a4bdc30f470505be16ae4e9f82935100d61e4c1702ef36f957e9382e4cbb081827c4018783068939fcc4324a5aa5abf096e9c51469211c61d0cb1f77cf34cbb8248dbc1a0643c3b9c97e585b80e26ebed1cba5e9e1cdc58d4811f844107931239e9fc217ba553a13998fb23cb049192eef33a0c399874df53437785187799a9851107a3e6884923adee924e8283713635df82ee1094fc6f3024cb101e1e73371843a7f0ba038d5ea8f4af6652612012070402711c045248f51e00c3130010404078441a8bc462d13c0e85e1071480035228245836301a241e18161808842351201404844382502010088541814028240a27e196ec03ab41f74a5cbda0cf38737cb323ed8a86cfd25657810116da0600aa70ce296e6ae67566a0e70b7654604720f737c5c02e0d64724730741c6dd034c8fc50bdd6880191268b4923507cf74d766582231df89f0599770a1a4c373db28b2b0e747984ad00cdafcf08c0997b5e6e6eea267b805f181beeb6aac9539bb444f24f37fc683c9ca949faf447fea535fe479cd290c5f455fae73b6e1d5cb06d98220fc824eb376fd0c1de1d4480b743ac6e74a47b0f15e183e4091e968a808de6f2541c8e584911c341ca0bb86b6c938d8b7c3c2b5b544b4461523983f31de5922592fc69c79a4524c8671d92de4c7e09eead39f5d4bfe1a94df4278e6c79a33ca999541f6dbce7ee7fb86a418d54ffd00120f8140dbc9d22f28d7ebb9f1dee3f4a15f72cbe0681c61d3aeb4ed422a08aff1d39bc1c530481076fad49d9cbd92fe58118611efcd5e88ef6c36e3b08c946c14b908e148a6dc8692dcdeb6d7b5af9ff140f940d20c69f84ff2d9748fc199f7c0bb77bbf8d53bdbeaf305628eb7dd3512db1809933c2bbff7a5fda6e21c36a472e0fb4d899ab41175cdde6bf844c934671899fafe9b6045225aa6d60af4015ccc7662a87ee9f1d9487cfe553ed082d1c63fd550089f109f0a6476ec7a7ad90111def3be9d273a26aa725beb92225aeee3fdc6b4808250dcc5ef550e2b79094d8a6a18a5a039dc6d8357c3774190f03370365a7a63c1c251171ae9748682c174f23718da31b7e8203a5cb808e5808638d4d6dcc72b807b6a18fc51b26e5a233c7511c35f57d8d463b36ca40ebd4c484661f9c80837d1b25c6513ac446e73ed07f0e9d13fd339a4114886b7d702ba65328781a479cb5bf8ce1ec300d47a91f363af189a59d4e56ea9f7b9ab99c439bd905f5c0513afead53f1b3046c811da319a4ac96a56eb9ed2007c14672098d6710e0e0debf6e209b3fad0c9146ff7e6189d501df8bdd8147ca7b8f21e8e05ea2633757f4e6f3966859bab60e8bb5c1be05cd7c762000b61b38b6124703725a8808cc2d040efdcba015185262701af9e67ad7d2d63104284feb64d6eb5f0993002109aaaac1112dd71c4e6e0b4bb5e4d97cd9d90ceb3f0e98260ce2bae69033045f53409dfbba11a06654e95522090e53da0559ce056640975de35c9600f50932ddd94450109a84a94701587bffa21592dfb9e65012a0fa4773ae11140ebaafebe8fd28132685d21eca13d6a10f9ae18ec94421dc56084c35c66e1d1eca59547ad0fc0ec170bca1abe70d078bd5c17ed7772e7effbd63e4f37c520dc8aa5b2a5dd5887f3a93080e6504827ece1014f80622f0586fd0d16ab76c4055f7ffbba49c96edef3a47c98e35851308cc6ac01b602cc3fa2f23a0a7e6a87f02ed28826339280daa37f1d8ef6e28012ac3b7992e2db11c3c4767b0fcd54f9a2def88673a7a85ad3d44afb14ac087e5771d3c87903115f818a8250e062f6d1ad0383886173cf08a56cfa2c142da31174c58dd48e3914fd2ed80b49b531898070f7670907e822dc10d19421bb4d67440104da63b2a891a3ca4d51acf651f3c71e361870a357836b71da9ee74a069a5b6fe107877373c9b81ac41405283634e106755661a13990f1ee43a58befc278558c50436c5fdb45d569720729a7cd112c1206b628641d3171a19641d5baabba99dff00902c5ce031d27cee7732841421211974f0002e3e1cb08387e50c1a123dd7dc2192f560b40032b1c1f30413780ae0e0715db9d5c76af008ef83c6ed0f548e6a0d9ee7a387811d6507cb7d0d40fdab60c7a3f11fc20d9ec428864318099b40e6b307b4583b067722f1f9925eb156d5cb4058ea76f8be871624460c1a0669aa32683c0155cc4212c68ee001f6e97a29fa702c27bb2822a855719d837524ec18bb7678848d649a3dfd5dbbb5ff0073e0de55a38c9edb7b04a6dd12d71b34ae2dde6aac54b5f57a3d1efb775e284666143f458a510dce73dfbbec702304ab4c344334181886046dc4a1c66408a01c8b2d70ae21424e16835246a5346d0dae1894169478c33b565351cc4ca7e176a8e929d2614cc0e98350313f9e1584987a8375820e80054cd8f0c08b0e06505803113633f98021e3e088628c88c30ad93daa20163c484fc40afd50a4bb448f3255450e5a401a0ccca1b83c4e32bd7500e94f16a58d3b472e6c8135694c26de69913c7710d4a8ca034d91f673434fd44fb3e5691d2add2003467379045a5d75f175d3db4231ae012afab4ff57c033658c6d6d4f6eb167658f7e374eeff55ed2110f629d4a028503cb647c662edf598abc8f49fe9b384c8d9fd8c05cd5af0d549d3c550297945fd5da01e83faf6967c602eef7ba0baa36279709254d0da59235a64a31b69feddba8ab7b6a2354f20fc11ebab08f3e441faf066b10ad9b25ac15ca5516837a0861300f507c4e244389afa32cdffd7e37eb3294eedd42b1644ccbc68a20f37635b74cb661cf4e9445865d99cbe342c009b91a06000b63ff24116ce487b11a992c81c9b9db81761434f949930bdaba7bbd6c038d55283df3271dd4e7520c73008b4737df2c31aa1888f3b0e1878b1cf0b921327884844bee5a714bccac1ea25bd7f0f11e1b6e82f1d96dbe6a0d516fe8d1912c35f05a0e75b787ed813de8600d653d9c6b17aef7d2fbf5809de265ff366719869ee80311899639a26019e509f8151c866ae27e19840b2463f1408e11784a253050c4c141771070e9670e5cadc8b03d1c743106f610bc51c4fb0a2262485030b4fe61fa81a9fcdffce8654d55a8bf96213db4db24e8f92428c50414d5f7ea1db0927440a7dc31df51da2554386a7a54a906b88bc36eae965fdd8d8657d007f771fb405e35798557e9a6009aef56e2fa71caa281d2d71190a7f09990632fd0a90bd39bf0d50cf8b1a02981f8f9c18c838a061083a9012486c106702d84a56ae81d3e989721f1561cb282b4b0546640a1ea5e6d8e9deb6e2e06331afcc76b697620892e0bbe5e4b2457166de73ab573f8611a1f80b5d6c422c073900b3116aad24de0f3a0acbf719c3ea3988b7180b8014c160535c3871bc6433615e2ece25ae9c9197d090c624b1fd1c0c0dc0a8286d070801706b12c6e587ff95a8150da4896b50d079916720ae98d2bef454ed81c969944bc4257c8bc372dd3d215fca33f816fbabd579cd2d0d56a60667ef624413aa08a31415638ea0526713b34d30661cb537620f9b9eca9e0ca09059b34e11691a16421408d55bfd206f0b868ebbe6e9524a87530e7eac8c8f0a3b3d3c0bb8ec320c000eb4b950a0f5a89df0e3def45b0507e33fc65716c7f25654279a5fc2bbb791669c662cd9ee4aa39ce62389a39c175a76af872761a0d7d6ba436037aa1d7973c62eda5b1aba73eda03bc29ad35bc4e73cd0d34fcc2344ae4f6ea2aa4ceae9b1271ca760f1dbc0a03bcb7fcf552b02ae762167f9c545a6a41dc9e7235f35e42152451c14d5d2e2473e2bb1a7663b038cf79f831fbf1a41399f39950df4cfe176f8f416ac5a09f7c098a38c98b3c927aa2fbb697f0b31de0df2935f1839ad316e184e6e2be53b26e8ecdd88875969f73193c263885ec0f809f6c4fb0f29ccddc163e0a369d4e211774edd66eca0ba7a4c3ed95a8ae40cb70c1c693aca14c43ed386b24c54a524893515afc1fe2048b2c806b04fe8bb54d320e34542fd88ba4066eb37e8d2dc3d1c5c0eb4aac5923d9a76f3f610fbace67f550b5203e31a6e93cf4aa2ceb2eb429338628bb8c8d74bde9079364008916d853126280f980d9dd74e40d3449a65c58bb6e46b691fb52791582cfc56098ab40016e046131a182c29043533120706c40423b3d7a72b4dd417623d49548ae078f503e39425e044db78c0b65a8d5c12b5968e9c4897ce33b75f273e1bcb07ba86b3680161be25abeb05cb2cc30ea5db9a3ae57f41bbe72e94f6d8cc3bab49f93700c6dee1444990c542a8153af88f10494ee60e068957e8b5f945f483b67ebadbff389931e049939a0f6dca66cdbcc92fc7af95ad9d400f09e415a5ddfaa0ab5b495c9848644c8e68e964b84bcd453f28e142b58ac7795d12f3f0f47f20d966f0252cbe851e5e4eb152a546cb880600923287414314b9bc651bf76675cb6ad8129fb192e57bd2a386935e1352ee250d3c45bc5b02d53f201f0dd3199001ac0c98be3486af99261294008cbf83dfcf5f828e8867b750a5a3f6199b715c2a3b2e9fc78cf446b11896e53a15d74145014079aef2c1db5058166d1039dae12685e5f4e9caa0f973d08ff0af43e3310cc9a5e573052ff7610ee045b505e49106ee9dba0a4070bb03f007f417c967e3c455356bbfadf7ec49e47a40c2852004a5ddb1ca97bdc04e43cf650f8ea68bacfd829b8c90f0c9b24186e76fae090eb90a0007ce265734afa1281e43bc97fd59f05333e36138d5b1e2f802f83d18091015291935b2a23e02553970a05e55fa032c6cd4580f27fbe48db17a7788e5ee6f4074a331de655c63453a71d691f68a42db119e9c977de2b076dea2d63df8373c0090794bc017fd689b3bd7bb3205c9c8b9b60ae44a9c961ecb7a156be601ea334a873a870dd16fde4c2239bb8ee4788ca3b839c18cefa2c59b21f019f0b37e30471098be5daf339f81e07688ebea1e0a691e6851d422d3b74aac8d33de2ec6894006daddeb1b0448d6165971ee04a4bbbb7465093ab6950bcf93a930e7c00f8356e9d1bc67da851e07b79e39a579b39caddf4bda6333dd9c46c2a72cb2b034b261e209c301231cdf434b7e828f2c7f4464fd6cde242445cfc43c77ffb02800603bbe3c6fa133f04203b8a7d5bd1cec0efee9dd082435a3fc13bda89336040fa736b11e51fe7aa5ccfc20a6507c42bdf69fab2221e1e382593c02adcbf45b5add7a506c92e9ac02f51e620d37209ad315b59ef012ad22d3b3bb85ae67a4ed1f1a4a12c901c2549273db085365ce1f769958c7b0f1bc64a66dbe8318deabdb8986dda60f4b6e648a3dbc21263225b8b325402b39f406b7d23937360fa309a5bb68c60d1beeb67800a3100047ab552c63c5f777bcb8160ed2d0bcf74bd604723d4fa42f45b13874e9e95186c43835b3b80b5b1028acec69d0f71325de08d5327e9d8e0009896f340190fc7232b2991e1bfd1ff4133eed2cb74ee46c98f7b2a170847355a911f9cdd4c6c13ce38cddf9f9da12caac149f750c72238ede31641f0183f2e2bdc266a4f1823b01a888fa2f31316897220b27bc7461daa80cf6fb489f53d9f93ff6482e016258325523d4ea4d421566ed4384a3db5f5d987aa9401250734f5b7352f0d87befc1dbe41c92a4101e73a0b485923ccf99520071d634a14a8ae50d44d118de7a11743a78e936444da33f07ce3fe6a5bac02c14ee24de0752df349db8d0b2c2ac6960b8c58d3338be231e3a0a641c3a8326240b4593cde9804adafed63a74f0fd12b1a9ebefe57a445a814301addd9afe8522aa3f6f7206153ab1e480d043dbf52f9057adc890c5942f3801b6852d1c845a1a404a83efa945e3860990f826ed5a85574fe5182748320a55046810e52e0494a23c9f83c18c71a462c071b3903cd222be6baa84c8b9e44de1be90a54b6ddec20ee637e4e31337b734f05942b1e473d587bf3a711b1ef22391f2f6601cc8ebc6a92d684e72b04a1c99113ba37d665d60d8969bc13a5ab1da250b79960f23c0eaeb799deb659c22b917ec462a4f3b1a2b332cb16885033f106b17d04d12d2821d354305e6d04ad0b3bd3fe31488089727e858e118a5882e59628975ef7ad0529ad1d58b22b32c00a852d7d66b26225fae6fd03791dbe0992d2079d71b5e93972423cc1c3b2c4750307e12b136b6826583a11450877eae119c3ca6d26d6660276bb2a2d51445e14743b1465846471ebc9a8820750ecdd4133e7956a89034b7b6b427c53840f5ba764501366f91bf26cfcd4f040c261466dbbdf1880781125f3c2a946a936a61697763341490cd65e0ebd9c712e71224408a6210b7762fafd5faef3a9a5871585a6e94033c199d77f8e7989771fee7191990da6b5afacf4c3f5c67e0fa39af96315f44d15a02fd8e7cfdb37831e87e930a81e882b293284689ba1a5c3e7126bcdb4498c5b045ea120e4abbff1b4eb03c1799ab56e1155ae0fbad636244d1a9323b50fbe84836e19a98e52b5a490432a88d2a294441f4d16d1ced6ac6e02040fc82a20cd1b63b2a87f13c12441a199da27b8da08a9822f645096078ab13d8ba83e78a970596418829f786a24274aaac14ce13bb6909a26a42ef5ca50e4af0810e51f85df0ae9e7e0f9a9e749fca9f2a7cf816abced80cb722490ce22b75e441342ef96ca933641dd02cb38aacf8820ddfd85b9385822e5bba7ae972a817df8a66f96db2666e915ae768423ec88f188968a321d62a8178964af08cd2e104d774a34a87c4d4c00fbbb3834cd3a8b2626b0a015b393137efc39fd82249d0b67ad09043473ed2316bc27c142f254b0edde9f1805ccfb2986eacb51f67499b124b08405fe7f824b6b39651a4f9d9ba8753a5b525b5a8ac00b4ee70f035ccd38751480f8833a71680278183e8aec9d776283fdcf445b71abbbd05a6896653bc7d651ba5c680841eedc27a3266ccc67411c0b458d67f63d5a6daa13f31b62eb8b55f5c155ac30873801bcda2c0c6726a64f54527511838329578edde956aa40ca0d8db018d20f5a86401b8f2f8d5daaf7edad1667e97da463b3451f9e66606912225bf228fdc820ea3db5d6baad7c2905e8c22a22ab237645cdaef6db8b36dc9dab536a4edb4b8fdb24f3f250daa0e47cf7924e1a51b890ba5d81fba2d363dfefd2e90bc52a74320796f53465c2683f8be470ab5b2739c76cd445a8f2004b672115ecb8a44c7e29e2a04b4b5c07a8201c512357b4297056ba1eb08f706e259408c3fe2778bfe47e5cfdb356dbee57a0b07a26938e43e3c81147328375778c26e7706c74920e3f8c416dc7fe525b4b781f7de7cdcfd38649ffc9e71914596cc2a1a8a9b1ca11d31346982a5e898880c5dc092585f19bd2d171811440a85995b8fde6b4b1646a1e6557e3f4c95479690c1357fda02fc43e872074bdde2f020ab9647e6c9210bd745e1fd1f98974459fbd8cf1eacef6fa9b5513f524b8e203631c4d520f7da9f0f27153f976a125374edcabb5ee2b6024c33b19fc9fe2bdde45f3619a50912b174cb0603c186e61e065092a62f25757e7a7512276899aeea41c12eae45a019dcf612222d2c1b0d273a91d172cb93288c354e1c76293fcc7d6058169a804abd08557db0a5ab1086082fc9a4b7a74bc324dc109486c8778003825dc43babf0f8f04e2df4c69863747907ce3acf49944c82032de235bdb5a32fd364e9f13812a59aa92c1471b97eb3374791cce52a1e011231eda1f1b87bfd4b192eee36f40b89ac4525f0803108f2738964fd711cc844025c304b5d48020616223bdf31aa4dd557ca1931068551223374dbe4f2668037d05c35847884055e3ab5c152ce466865d15a01d3d5c7b02f2b1c42db858cb49d39143c96426b9a6551b5b90949f2eb87631757679534d21a64ddc36db3dd172c3ab28e378646efab7b6562cb76fe7e54c62ad2d6f12e008c49df8e2525a89169d3f409075e6221e99a93a6a7f463960fd47524e9954df4f609c1ecbf7f23e546e6c04dc4144778635ba1794c04b00af52d6d32684898e9317bf3b460627abab17cd42a055b28fd6e2d58f5a9c6df5b70d09596a29272329197c07b0433454bdfc25c02934e00115eddbd462e4577f2ceb67c44982d2c2b84eca7652f46466548bcd9fb28a60eb0f2941fbb67b2bbfc6d21be3379eb37bcf56c6b44ef24f50861b00de64cb3d6b0c060d955a50f5582222eb2802b3ed1de51bb8af2867d5989c45a92e7c7ddaca24fa404690d4bf5dfacfac2a22c0b7252f2e57fa74e02eea5a1b9797490ce100e487ba3bb892dc693cc60c27720aabc9e8057dd093b5d4d20505918635a1af736eada7b781254afbbd4a94c720f1f73e786d85e86d327affc96c1ceec4e1b28c70b113a61d5a5d15cf859471f435ec632113db6421abb55c6dc95fc8740acdfbaca88f5f52cb5bc31314b791509b1598e3773dae0ed3df6e13fbb43d6b91a1609c5d2447dfd4c8032d083b921e4939953e954a1c63393416d3a1d83708a9cabdd1e34b7bc53c4983f97742da9345aeccf5ee4f454e77c883d2314b1c6c65c2a926b63776e5f2b2c95f4d47ffba37c9b6f722cce51252cb2db3d87c35d2e5d25f778fbd950aa35a99f94556eeeabe5e60212c8484b072b5881cf2791bb6e618d4a407ac5841d1d84ea0053f32eb4d4e4abcf8dd8adb4b8f7d8e4122b4ded650b64245d8363663f109ed85315872e1c3c94dd2fbc6841a0845564b617d80697d51267c3827b588a48754838bd51abb13825cd36b931d0aac8503dc661e99c20946f185361dddab351e32385fa2ceee6c618b20a4d58cac8efd71478dc3821f1b44ad685a1a1d05bfe0087ca31db53adb69e9a4373a72901da6b761b2d5b04a470d90d312607eaf6189a71e1bcf7f79b9a58cd4a763a225197c3578f2f79646cc8b05c435740658e9065bc7343a21c4accf676c40cee25ac331941db3d2b6e27b3a44f56a9019e9e3586225ddd1bc29dd9b1a29a384e151a30ead38fb03236ca6dce87d46081ac9c54ae9254f70d22773c86f2b8a151e89f9c1fcd37dae479dac3207c03324a6dd0a65bbbbd76482a02eba30b02f58699f2cae7246e94b5e2a98c206435dc03cbd31989c0a83292745c8b5c57ff0749402fc3ca04d964d99a6fd5797b90ad30eb9b7b31f30b9fe80e2e666475596f17709dbbfdb54212d0d1a99f1209dbf446610e7b7f15370c45d8af465fd70c690045e0540c0500a3b81f489fa60bc869957c5f51f10e2e3d38beb1f8cce010c5b01ba2422421045219cd85c8ff667c460c49a519b94da79332ae6af53c562859499f8be6ed3353721e334b5087eb2ebe2c1114d9180e9779a9d3f1ca20477a6ec856c423be0232afd67689ba661cc549e08f75ffbf24fc42d234994e66470d1ccf7c48f86a1b48a81c4d61181019518afefcc281180f5d19c45e3f59d138f45c6ad546715851dfb6b8def5c350c277d8af1b072bba022bf1b1f7e1f8806df8ed2d6f6dcc4b30fb6e1700ffea230b5874bc89e9102d1629c0056438c1dd1e08c00abe1c62cd1e48c001535326ec0727683cf4b60bdfaaa56b9f5ba5064c2973f1fc833b148588970712a192edd4ee483f698fbf81a6fac471423c4b97aac52119ae5af7846a74f314cf587c0d12466d756890513df5c7fa9fc18330832df9f8473bd3ad7c1bfb6e6207ed183e4d015c58b5fdccdcdaa1fe1e0dc8b9792fe1c8c5933d91811967c0fbb31678d3fc578f5546f90389e232861b30088774d8a459628238c8554700599994923bbcdb7137eedcb76244b70f044c59f7e105a3822bfbd42e8e8b73b93f2c54cda5835738b51af0782c0b50fd6464177b85e414aa2b50071e2949954e43b0d24711abf56d6e800ae6cf8231d20a8e9ef672128b1fd670cede931905f65c8840b4c0c99d70ddef68663272021b8bb387ff5a0d7ae1b24ba65ab0783ea0235f20b54bff568acf1ec016e7f6e4cf6babb5914c03d55299e6c6b84bba1e92d4fe9254b982bc3f76b53c2bf3a007a6be2e3d18c0b3c860392acbbfdeba93aae1d1957a59f1a809784e3a791f428a3fdf6183f99039506c64624ed6e74f263aa7c19e14d1dccd51635f5e004343aef20ac11ec979936715701a820f9b34733b456e29e0aa83e6d540ccd037f7eb10c9620686ff5a27ac04f2a71271908e01c6f6a07bdb7cb16d84d8c1c7a18dbed310c2610bf5695c3e95dcdda912cacc075c44714d4bd0a775f65d6a69ffca7a7e3977890c910ed3f27fdb2cc51605f7446d9fe5a7e13c48c921b2f34d70f8e84469e6d9b43b4ab2854a1caccd81ff6c2e0d963dea992902e5144288d16d0f8188d01ebf147849a7b1e9b2ebb155f8f2902cf5a7f4cadd768c25fb907b97319fd7e897ae89caa42d9b6a8c066fa17e3b3235587a0376fbdc7bac3afc35c8e16cc58e5311ac5beae69e8ca7e94100268553f4f76a5619f0c8fdfa18eb468af8d51b8928dd351ae37813c47afe73c7cbcf928c808df3ed395a0e7e0cafe003dce4eb74dd71cee51574eaeda133b1fb0a3442e710a6f06ab29e7f33c0b31ca3e3b41094bd4c48548b8cc1b4259c8330dc00a3dcd8ae70d0a77a6669348ca8638c116ba91e9658d3e85866f77bcc16400438b68a58707ab1fd4b02e53814a3c7e2ad4542224d5c597e5842224290b9ea7a9edd8978a8c8fb94f85f837ed90d075b7c04992a167b3fd3e1795b97d87c3a225961b1f4de9d41b6b81f11ee6963a14bc8c048e9e54260adbef3537cbadc8df12b7441ae6e1aa85a58130094f186aaea06607c1cc3d4bfa06ce304b5c268977b03d6416a6748ad3a5d9d4ec8fe94ded80c0a2f0d0cd3d052a69b9129b217819fc2c4150192bc729b5edc0f7609d31a936365ad1f306a6aef0d892ec27834b9ab217dcab7b3a82fbb2dc5c530dd7018b2e102b7ea288afd850f1aca3927042afb6c69937ba552121f5b2c9a78fa21321eb629054e83eccbcfa490ab27717d435126ef5c9eead9964cc3376221e000aebeba8c9d3b3b041be98e9073aa21758b5ea04d0832f04fb99377872248c54a67ee7bcbe82db24a0edf465d8654d0c3ab5c41aca66a2533c05496426e11d3c658e0a773ef5358348c3526acf586ed06d857a2b2b19044522f2811db6f5e35f22f0a1edca8c8d49ddb32fd47443d2394f556ac9710c510ba4d793013f3063c7e78973dd0681cd02a6209a0b4202b596720e93945e4ed1ccdfd9490327a9a90d7fbc23832472af6e420b18759fced10ab618baf16c4043e94d84e296d3e5295fdeb09fc8ccba2dcb41274cc62b9af00f95a0885d51521fd9727c9a24ced543ab38e436ae80ccf88c4fd2ab1f58046132470bb8058387e098aba5b9361a140c824c8ef3ff12dbb787c9d6b612eae4c248319c3c865dc3c913a47efddb5d202d61f45f921ec60513803fc70afb50bd64777b10f7d649554eec1335b980054a1beb72416d78a871fc323589a6aa87e89d07b6f282de5190cc753336c96126820326dc6755b92e70ec41ffc6a47856c204223924c42f7d67ffd792fcc3889e63f5c600258311b93510ee600a2636b2579357ddb745b8b6489dc1ef29703ea877eb23994ce8afcd7f6e66999f30a06431f46e14e3e5c1417918ed5464d2b723fd0c527ae1e8b04e8e6f1dcf5e2b03cc89088f7271ff5317d2239d87f25df731f2c829d2c8c5e57912ebb159b72827ee8389b35f75f29a6ad16fe6e16c44b555aa9944dbc96ae0b1604d59038216957ef4fa29f452b5b27657431526f66125cbb6c2a03610ec9b82d0147c6cc12643f1143a270b9dd52d57335adf404945093315295d7f489ff562949a17b3bf97e1c08ea044dc581cf06d94c156f831e137e0c928ec018772da783813678286d8a0089c0a3317bc723dbe8bd8610a045cf614309e0257014c5b4cfc82688b70127d68400c8a3305523bf276903dd471a6a6da0faefaf07f25d0d97c713c8c1770ff55602d7dac5ab097b26206133af8bb155783333144b45dcbfc574900268996e2477bb7d7fc3b8171a2ea12d04a465af84e82a42f53a555cfecdb13fe2526bffaac5de2197c3a5dc993ebd7c9c77f14b3e86bca1579f2eb38a490b700e7db049e685b4bf6ec9e23fe9910db357521408aa0978d43b66ad8d80d075f49f89147287d0c3089df397e27dcf21f70f82567237c0dcfce6b85f3e3333d4eeef15f53931641e927830e7a0cc89c5d11008edbe92f904218d371beeb1b4f9136828205f1cb710e29b8115da70396a815cd9112791a240742ceaea4caa9a962e5cdc1a7f753c20cc7e89026850a342758d04ee47b4814b43ceb5236fffd5cf328758afbb723f92557fdf70bc16b3cea036f2deca4f4aa2bf7a1576b33d79b110a09f6b292885bccf7c6805e21b0748488ab60025184a53a47bbee1fda045ce314cef2e9067044434ef088a12b3d6c1ce270f2b378ce9436736e21b38c92ca8244ef3ac14a3af6e5803cc8d4c81b0f3fd412f3ef668e34ed585b62603322cef5bd8fa79aba174a447c8f9498ebb8a951545a2a735e0662bdf7fd29cac541eaffde43b21c429ffa4b86ec1725d6e832116c3f6d979641778a698848cb70ae22b03b26abee697fea238f8f6c61649aaf73a199e9d72fd3e4d4f126a17c802a286803884131fc5d0b14e93fe56586d542ab511f6d0e762e83560a930361e55fd88e1a18a7cd2120fb875bdd120b73f3161908119fa0d709f304f9d87efc53a2ecab3f9055bba5a70524a91cd3e0ee610e55bf8be531d90db4d7d949ad615151ae345f0fa522bc393c02a9796d7bcab3fedf26f1e1662e319c12173b200c6f72cfec3913c7cfc58c395d3b025a6ae4a8135baddcacb538e195a2b5750f1cf7e90227273684425e01d8fc67c35dfc8ea2f3d768c3122bff90187d207738ee4d758b776e53cd68da3118d063a1d0f5bf2a2234969fe0d76416fb69693b1e6badc9160d6b36dc69121fe2661911e704be85a8ee1c6d30bed5ddc3398bd76e0180674c9aac92531dc4ccc3c3c1ae721c152258507a92cb3dce4c6668fca56ccc6348c7cd6dac295010453725ee9694c2ed040525f66690b823115f13838bf5031d7827480bba37a936e151d4a45215c147655c01b5ea6b2cc3928c21dea76594baf491c37ab402416b2a244c99b8ca18046c01d22a03828946c15246708e70106ad758347c2f6728d04028a278c5bb2fa491b30737aa950fb2482bba68b5c12a7435a515a8e890088c4aa2a615a804cb91d968af8a7a82ca2c455794fc53b357193a46b1ea1adfd640a53250b7255d2eeaba76095e6744c7463c031de7330c3d83434d8553522cf484a1c3a19750c5a17a5147a51e88f5c49183cd373afa0afd1ceaa9a3b094f7afcb701488a8655431d4b9a827ee08ed15531e869fca16a84cb08cff9f241f4ba8f0285f65c42f346ba943ab6fab32d1a244b1652b6c987374c8dc11c732ec5ad6f2fea1b02efb1dd8cbd1cad184519ce80fd09c1b9db7ef07ea3f7432a999aa46d44d422bf2c4f9ade8553db582f75f8fb2e47566af15f565b95b560d37a9a72e109848ac3633f5bb2a63d00c882c1a059c18ca30c54ba7284465a04e75cb4a8c8e4695416dcc018d169f8a942168a6d486701d30f79b58279cb5db3c09a632c18b06369a218be336273cad4b0fa10a9dc27d41bc0386ace2214127f248d3a4f32149e19d6e89dc6af5f07cb95bec3cdcf3b884982df87ad8609e354a6c08ca6bc63dc526c36e88adc6c68645d2d8ea6192838fc62780eb764ee5cdf44f346d2f41cf5ed6b0dbf0b46dc734563e5d6730a2d92ce4bb2a5c2a8e601923ae4604a3e05c13e81eeb227c5a918498e897caae8a611a735141f7b59ca22138fce786949d91d9c831b418506c1ca03fc8bc5c0c63da50c17a462d56fd724401c0d66e5c85d05a5e48ca310c2b4ff902402288e3a3f6030b26694086c9ff4c1f0d4e066ec3024aa10591d94008b37af412a40d7a0c9814b8095871b0d817a845a4831f3af5dd66be611c05b01d7977c159a1431f0dc1763b774f348c8326176d47460d8c9014bfc7263c1f32a0284e12c6a2b17e64a36932d79cecbdb41a4feb715524f2011b14790be7073739ccc10e38335c4566fc709bb14a447a85e0444b9b597196626524361376c9ef1038ba6cae039b315e6ca9711067e837c3661bfe0a721bd86d2cb61191d80cb6c5d5b9bd91136c399ee3d8b691d98d65e51887534f6c6677d6be0b9dd88cc9a128cdae8d7af166dc144cc86ae5c8c6b46caa02beb045f90d3660b0a4900330333333333333333333e3ca4bfde37ddc1ff7de8d60c232aa45279a9292921209ac35aebce01de7e01dbc8377f0d67401ed0da10d1f0e4d52abe0757486b10e62c41ca48223a94327bb321b1fa7e055a63ce1d2e660724e0a6e8ebfe737785c565f141c4b1e13738c50991e149c94426d5585f6a5ee098ea47c5b29c7098edda4077297396c821f2ed492e698473a98e0b57d479bedef79094e698ae918f197b1129ccd6695ebd2243829e68b15296548706a3e6bf050691b2d3b821f3ebb9d49eab09fcc08cec713724cc9f4712c5911fcee30daf6690c11bc4f93de1ee45552ce109c24b143ffe8393cfe08c10f6311d467d6233d4170e3fb6e36eb2b3b0304d7b3cac52c5de59a1fb831ba6a5f7ce085cba91a626726b7f4c0b188eed1d5b47a5978e0a4c6e6cb13d9819344ea6732248f611db8793a3e87b4505a7e0ebcf6d0217ad8e3c0cb4813d66e3c546ee06d9eb7cb1f04b081ab5d9eaed2b33adab5f082d87f94ef83acb569e1b6c725211d3c0baf652e4731480eb46659781b24180b2779e6c95154d58c312cbc489a7f2bb2fb8faff09225c9a1f9871c6deb0abfe3284a9fc754af6d2b1cf97854438ea7565b56f81a3de4f918415388aec2bf64112d4398e081aa0ae762aeb8acfee1a334156e69baef49bf7df944859f3a948ea297a7f02579a648ee5c1fc4148e678f9e2d867cab580a2ad4c986258f144e942011fd330a5f2ddfa71ccdc7cc88c2df509a156982c71d0a377687f7e8e0592d28dccc21dada625dea3ee1f49d947ac8924b9927fc8ea12558b020695727fca05eb3a74cc509672285e718de849325a7d4d392ae929af0439248513535d57932e1a71c4ff89c35740e0f263cf7499b263269adb984f763ee36d1e953ca58c2dfd80c7fdf1e5fa412fe86ac1e49ce9a630f4a38496ab2cf5c26e1ada698aa6221ea2b92f067b3860f5f8984ebc9936d65b5ed0f020947cee6db72103d6df2083f847f2c094f911e71847329dc86d481a7a46984532187240b33c2bff0d03767c851c72ec20b29592ffb304554116e064dcfaf19bacf4f841f7bf0a0437920c217b90b73c1e3104e6a5bd6beb74a1343787fa163f3cb66e1e342f8d32621dd29ff84f0c2079d6254f6993708af3abe93f1141fa515841b358636cf5aaa3981f0ca3c27bff1f3ce00c233ff20d774567cf40fce775027a942747cfac1f50fa66346abd14efbe0fde5caefb1458b1c3ef871920d8f7b0f5eb7e414ba23f5e0556bd01a3bf3e066a4d57469ed2d9c787043dc6f23fcc776c13bf81f77297898e2b6038d29d9071d07621dfc4f39cec17d460acdd1c1f19caa3e883707377c10739a433e084f0e7eaa34ed992da5b7581c9c34ab9eb482e58b191c9ccf9c2db25f8c31526f707cb2ed322d37b83982abfbaa4fcc698333f39f4288f6d92d1b3cbf98c2aaf755e6881220630d6efb848fc963560d4e48993e30b7ec3fcd64a4c19b481552485ff5c6340c178421030d4ef504dfd8b0ac40c619fced38168fdfcdbd449366011966f0524e89acd441a357b803168601a30c4ea4cc1f59c425bd5eac066490212f937073e943c618bcb4b8decc95a1ff3b2eb6bef0a28b2d3220430c95e6b0fea3142eb68c8c30f8e21ea4b4a9089721cc850c3010c3fb2455fd525f30a656c6e688be1788e497e6735e0b97404617dc0e1b724c4939d892b8e057e7cae361477af1e82df8ad193faf355ae1046468c1ffb19c3286adc6908f0c0332b2e08cb77a78cd1d7e230b032fb20232b0e08590c1be821ba92e68f274d1257fc8b08293c26a32976da4bb8e2fbcd8c2aca08b14aca00ba4808c2a781f89a7d45e953cbe80046450c1df4e9273e8925ce00432a650351620430a2d230ace5cd0147a26c3c5568d1a99749001053fe23f3e5189b858be31c87882d31756db2d212d4b8e2623c87042d540818c26f89299cf238fc2cc4dc6c542c074f1850714d72083094e6dee4de7d31a1daf8c25f821cdfc440c2a5143274309e590638979a2e3586720230947e6f6c163acb83a073290e06dfe9c25a40d1f117304c773f81023a6c4b88a11a86ed5f4f4be087ea8b6cf1fcfa9559808debb89440ec9330427cf769c329a840bb1230427b84b47f3b17e529d20b895dded953d40f03d677498f0d19c477ee04555e9fc99c423f73ef05faa8349ead1d7647be0bd664fd61ee450226878e0a4c8a5327b074e86f71073c4e69a4a077e900d39c8a164fb2293037f656cc35598780f0ebcaa8c0d9afd22e3065e4a15edfd4b82e71c45860d1c8d1e5f4ff239f8e8520b377264c4bcb1ce63b2d0c2b9f6d68aba6916cec9c7b45923a1a52bb2f07290104453864f6d2516de7c8e3b280d8fa25705168efdff6726af8e1e79857f96a5239f9493d4872bbc1e4d4dfb186a3d6e8567993e9ccfd979b4125678eddee5a1a9526125abf003adcc9a93d554481255f8697d36e498c35a8224155ebbc75a1d9ed9c1ffa8f04c730c7b4df6e6fe9fc25b93e41d95b973646f0a7f2cdbecc77394c20fb12395f0b1a884b449e19ca71ca588ee8d3b7b14defd871134ea3ffc220a3f720eed22c77328fc10ed339c87b1fb9941711c99c7c19f70433e881e8ba6d58678c28df6618794365c6ad209d7cc2549ce0ed207ff72c28b5531df99d4a3fcbb09d74396ca13a2ab09a7ec2a64dd0489566d26fca0fe3ce68f35cb6a30e1d9bb745f889472b0f5126e1e0d9fa36bb58417251ecbc420e991a795f0dfaade3e085f29524a09a723db7d32fa78949293f0dca26886895312fe69ae0c1e1d44a66846c215d90ed70e2b64c925249c1829ddbb33a5922e1fe1a51c87312186565c948e70a23e78e9c923252936c271cd718a5ad4a41c898cf07d653a74e416633b5c843749b3e414b25d21a1225c4b216ca203a99839897025367ddc610e39db7388f02a5d468a9eaab4ce1dc2ebb40e54428721bc1c237d20e139469314c29b4a3f214751199226841fd65fab669f2c293908af2e0495a0412a624e10defdcf0709afd61e7781702b4d88b163f848313a40782672f75761d2a4cc1fbcdf902b85c5f62d8b1f5c8ddc954f34d607ff438a629b35ab6ccef8e0c6584915d6d91efc389e1cba87b3ec74991e9c98825d5a87fec9c195073f84e8108b1dc777b6f0e0e794356f26bf15c6ba83ebf669726059b3a65776702d6cc2255955072ff769c7b9a347e242073fd688ec40dad7d3630e5eceee961e678b62b71cbc4a1a25a925264b8c83f3f663f963ea8bebe0e04cdf5f86fb4fd5aa4911e30d6e05cfa8e571109f330911c30d8ebce6ded0b8b4c19f2ca99307f5fd5f151bbe10ce53071d522cb9585d839ba14722db5c4c88a106bf7c443ab6b7480d49831fe608319a79ec65f5e130c44083a795455624bf60887106e72fa9484bcc47b61dc30cde799aa7e89872989ac528831f2d7325cdfeb125916290c10f324d6fad3706bf277ffa24993e77c7108317a1e53c36c86676334618bcf940ae03cd124cd3c4781c31bee066180b2ea576c966e20557d5524564bef34f7e17dccc79429c489ca70c73b1759683185cf0da5375852017184c83185bf02ace3f36cdac11fed1829bb53abedab6919c3fb64819c4c8821fa2e2be3d858ea7432c3897f264df1837358f71b1750557d42c64a4325bcbf62186159c1c6677dfacf6f5e657c1dbd8398e3368baf78de162cb84e1c50b900831a8e0df98faddfb7de0999c8217db248449a99e66f2c59182638b300c181d460c29f8fd3972d49c3c59c48882575953d7696da9fd5070d26a44e592cfeb63f182055b1ce58a184fe8b25648bbe49ce0d5f747e9552fd61a89d104275eed82cfa4907e7ba18156c46082779b43878f30cff0314bf0a254f5775039281b318612fc3084075feef9c24d6c8f102309fe4fba0ade59eab23b48f0d6fe55a5031569d31cc17f995eef0c8911344611300611fca822fb3f6a6d086eb7c79184c41cb9c4b5841842e8da73bfa46304c18f4655e5644323463e06100c125307296ac80fbcdc71322dedf8e08e37a6c5b85c7a40e6efcec14cf43cf02a25252ace63077edcdf9922bad681a329f2479be53843c639f0b3e6385eadf78eeb4371e057faa0b6de73032f8b86908378897677c6b0814925e7ca746731520ba746e2dd2c2b5af8173dad66e224327766e1d4473ff389b62c6e93bc19258e85f7e95aaeb18485571da444b779b5c89157b831d53be6945f336c5ce1a470999c337bc8f05be1c7d6d829bade53dacf0a37a510555931ab36c7b10ab7831c72982c5bc9660f55f83163f9b6844db726a7c29c474af2c72e7563a2c2f73c1fd541d3fc3f7b0a377dac1e3d336b8a536ed55c653e61624729fc6c39bf5d7497983325851762dea8f48e2c878f1c459646f2460ea273bc280c8b312387394a6ff443e1791023e4b01550381b3165a3867cc2c9f1519e34d2e4709527fc888f91d6f368ca612736fb73959c533bc6092fee4f5bc262ea33da8411368410c4e4628b0c03d08413d22f7d1061fd539e4c381f7afa1c652dd9670c261295d26097bf84f3398a9d27ceea52d2125ed234c135bbd587a152096f43fa8fde424ca97228e1e7d618553e3aae8f3209e73aad87906f3b7f1c92f0b27d881ac3a5657723e18739bd234374dc390909673407a17a62e411841b4d6e12e308d77ebd3e8a4eef5d69849f2ba78df0b072dc3d23fc8bd5dae571ec5c2ec24f7f4babab15f1796ccca525c2d97c215b9e17d91c8a08ef3f6b4c1faf5fe6104e84b7dca14c5b6df20de16be5cc8a9d42782a1d45d4ea3808e1f6dc6f068d109db3c7208e209e839298d22505e15fae79fbab74200a89946eeb3e8e2640387193ed347bf41fbc8f43e71cc9a3fd4084f475b91493c7edf5c1b5e47f29a7cd714735f3e178fb38b6700fae87afee20c5d5839f83d89a87b3bb8418427454f93e78f0ef53761c5479e41e7707bf2c2c7c799437c729b583339a3bb68cf920eaa30e7c8c8a31e530c71da3832ff6ff9dbeedd5b49c83511573dc217b8614b61c3c2fadd70a9e38781f793c34724f761e0e84a87cc125f75418dfe0cabd44ccb176f5c92c089552f02ca4bacc32a131c22878193af41c778c1de51c14fc8ce91d85f4b149774ff0c3d08aec0e7382975324f6f6868ba135c1174d299364d487d162821b7294a3e4b496e049fba87c903257564af05245c8d2ecefb72a097e10527aba8b69ec4307129cba0f59f5417290d271042faf5fba8c7418c10d1da607ddf3299d53043745998c670fa34d0e11bc645d15dc21b81e955d730826590bc1cd1965b662d9cc1b0427be83ba9f9b6513089eac445947da4af0fcc0f95c1f7da776b3cf077e84a76fca6678cbf4c077b94f159172ff8407de5f8e63794739c00efca8e3ad4992538c8e72001d385ed6215d8b79b8ca0172e0845039ccd9c73f29e40038f03dca41c2a6567b0939c00dbc288fa255d286f2200938800d7cd91c2b74476ae1d846b9ca10ecc3e769e19dd684c7f9cd3d3c66e14713424c394e9bdab42c9c9e0876962b7786742c9cc91bf75148f9c9372c9c35efe0ad738ae9ef573817c3c71b7c57f8693ac5a887a487dd0a279d44e720e5ce97352bbcdb8ad6395ac50a5985f31f4a6d6c8d4aad0a37668aa7cc4a332ea7c2f76023a36999dfcaa87063b6edaccf1fc7fa148ea57bc8a19885aa4de18d67a57043fbdcb3fca3284941858cc20df691d78c2b0ab7deb447c35068bf390812ab8282abb168159a7ec2f598bbaa247b2a0f4f381a835cf8d8eaf46527bc28293ec55776f992137ee8f7d6482321addc849b3caadf9837849b50135e8478554e294cf62f139e4d5d761c478bd51b269ca834d9a1b2be8413a2dd87d1375eea6a4bf8513a97927b9f94a7ae84eb2396ae634a53c20f52ca42e6ca2c1bf524fcd414434e1d95fa7b24e1a5d4603967ef38b427126e0e326f4bf44f8b1e4838993654f6795f34cf233cc93efed31ec5fa8e23bc10b52e6a348494368df02bb52a4c76480b1b46b8151a953ea3c2ad6611aef4864647b5390c1945f82942fecd914984137a2a6787c84abe20c273b7b69aced4f09743f861b0581fc28518b518c20fe672fc9bb3570a2185f0ad627658dfa7f21342f8311a72c60aa61d4c06e10762ad599f20fcee50bff271a1251208afde526c594b5c0f083f8e393a8f473a4dfd07a7cc3ca60a17c362ef07afdc36c264fae065c8c79afae083a7c9c6638cedc1f114317f242322293d38f741d24abfec6a1d79f0623c6aba089ac3e8c08397af4e43dbb22ce60ebec4beede0fb658ec3b5c73373d781d90ea483db95b263344799369e831f74bc695210e5e0c6d439fabcc6c1dfa4b943f1150eae654e92f9dc342a7d839721d9f12de5414abac10d1d87d9632b5cfb6c83671a39c741d2d431996cf0ac5243541fd7e07465956c1ec658caa9c10fdb614e558fe9bc4b83ff7190c3b8d5d67cd0e087f07392dd3378bdedd1a5ac0e0f33381bdd7bc3bc6570bd83f5b05346062f25e6495932ba778cc17b4bb9376214afaec4e09d057b0f6518bcecc8476390180ccefdbf77c871bee0877e29671b8f17bc145273f061da2c962ef85a23bd31fabbf35cf06354262651f1efb7e0a5ec142cc3460b4e6bc654a9e262684d16885e1d7c482958f0b5dd6247989bf82857d8ae6445836a0527749810217c7b1456c14bde6973850a61b64205e754623a4cade1a932055742beec41ecaceb102938a15fbd3af351705cbeec3f780ebee3a1e07b0e3b753a9fe05cd01056511b6639c10f2ebae5ac9247e99be06594778f3f8c0e1a2678b933640a1faf5b64095e84d09b11568267f769f1ef9104a73e7f1c7ae88104e7fa7344f5f4b1cfe3084e0e3a42bc4c99dd6d044722653bb06c566a17c10f513307496622f82769a93ea423cdd921f892751fe628c71c9b2b0437e58e62f28c06c1f730735467962e4305821f27c96129da64f8c81ff822b612c2f47a10a50f1c33fbc91b246687600fbc4c3158a69b8f5b451ef87d1a6fb9fb8314b20327d9c7a1e7f9dbf6381db831776ba596f9162f075eb4ec100f217b640c07aeab4fe6685bb63206b8819b5fca26967d5c6f001b781df97a35c7e553d7c20bda21244d7972eecaf058c93ccc5acdc2f98aa134e7cd70175938123d3bfe2894e68b36165e7ce8133d473e2cdcb056a1736d7988cf2b9c303194cb755ce16a85bef4c85208dfb4c28ff6917fda9e70b16185d71f555cfe2c4a8e55f8d7b1c74345b0e05154e1ab767c3908396ab44a2a9c9e994b97b7a35454b8f5a9c25a8edc29494ee1e49bdc418839c8111253f8ef963c8e42be147e8c9ddd36ea34073d298a985635cb1fa370bd3ecc3c7fc4ce1ea2f073ecb952ce23147ed0724b33d11dc530289c1ccea3f9c935a7f127fc1017347bc7ed61c69e703e481eb6aadc0937b9698e6cd2478d2c27fc568f327290183ede4de09303a9f50fd584dbe9cd639294cc5ccd8493e67148ee9a63d78909ff35c2574a6dbd6dea25bc99bb54b163094f428eeea38e2cf66325fc28b6e3d764172585126ebabece7c7196fd49f8c1d29b6f4f648c92f0d652847f0f6be922e1479914113465c57490f043aca6f031e5e0ed23dcbca5da97426f089a23bc28a61d474937b3aa11fe878c9d3c3a9ec830c2f30e3e9c644c49c3a48b7025b2cfa25fc44c114ee694e1eb2b89f02d3a8ec73c06117e0c3955d33c840e630ee14a6ad792397b558d219c7416ca3fe410b92b85702b7518c27f74ccac10c20da95f4f7910aea5cc9c42737f50124178919e6adff1658e914038e3b1fa67f324361140f871d47879349dfff3073f6c9c5ffe2d898e1fdc10cd41a3c61c65f4fbe0c72c6ad71d84d2f0f9e0e5a9081297ce93f57bf07269d80e2b7f90cf430fde86ca520dc9fb3f79f093bf44a494c6833f371e9d64b21bb3efe0aaa7e710edae1df51f695e8f3267b90e6eb7664df1618e89321dfcbba43ed391de4a9e83172cfbfc6a2c073fd2ae58173193e5c4c10be396fe29c2fa0f073fb4ed896e1e43a76f7025888a7bfe4e9aad1b9c546fed39d536f86e1ee788930d6e6cb986e6c91b9daec18f3247fd1e199d326af0a312ef8e47ab7325d3e0fbe5b0535a5b0f3e687025ddc392cd61a9e50c8e4d6a0e22df9ce4308367f159b56337ff0ecbe0498cc86f6bca9a0c9eca4756f48cc14d93261e3a2ec570598ebd30b8b93e0a9ed181c18f88ff5af7dc51e60b7e468de778096abf17fcb0a93f66c4f4a1ed821bf37b5cfa7555d2b8e07768161beb3129b305af42a37cc428b5e0474c512aeec982df9192da771c2cf861e63c76472186d4b9829fa3beebb11cbaf35638086fabe0a73877f31012fba6829f72ec9b704ec189704b69a191829f3b9889cb11053f7b700b4bf93ca40b147cad309adda33cc1cfc93d4c158ba810e204a7530869823716b3b2e4ac262d61825b2949aae6ef9827bf045f353a7b234b18f195e066f449bb764bc93992e07de68f526c4ac99303097eb2aeb5f18ad27e1fc17f9ff6a8e41d7dd846f0c27806499ddbaec345703bd88e93c6f441083311dc9a8ab89b5893cf1d82f3eed2df9999735e85e05884c672d70e490d829fb286c98a09047f237bfa851ca4cff2077e58e40c8b613eaef481272947c991e981abed9653a2689685077ed0d1367fb13f0eb2033f567c681b3d126975e0ffe63953150b90032f6cfec366bfb2fc1500077e249d9d255d3e89ae0037f023bf5c3612726d8a15c0067ed8d7f93cad3286ad165e6cb41c2a365af8519a986d9259a7c92c9cbc621d694bcef943167e858995e314652bb1f0d45f2be7e8520561e1b84792263d5fe5fa157ef0dfd3ada66d63b9c2d9f48186fe68ec2aadf0d62b47b0b14829cb0ab7630f2b7d24e12adc8c728b2947a80abfdea227582c9b8e3015be5fd545e5303a8e0851e104fb0efaaa62adc7398537f641ba2bc7b1bcc7145e6dc4d4ea94c2ed305ad424e37711527895434f72f5d1dd3c0a3f4fca986286cdde8ac271f30b17257d6a86c2eb3a3593cd80c29fa9d7f8cc7cc20fedb183af142a2d9ef05288b38b3df127e984177f412c956685ac70c2ebb1f338a6ca26fc186e42c2868c29af09dffeb2df8924136e6a8851e22398f0256d5427e98ea27309cf434b59f5bf25fcf451c75399551ec757c2491f6d78743c259c143cca3a940dd932092f3c580a9e9364f99084ff31855812b6634a1f91f082a58b1e3cb49608094fb3624e533fc289fefb0f153bc24d1edb46f8514cc5a7d258b95e4638eea369c5738e83f02ec29f0ed3be43c7b1ab5584efa16a089dd22a443411ae660e55d4a453f78a0827c4b4f92b26a3c27a083fea682bb93cb5c7aa21dc786bf5ac4d9e2f2d8497aa766df3b9ce4d42f8a9b9d36b3a085f543d8a09f2d6a320bc694ddd51bb2731108eb8e4b8de83b4660908c77fe5d4a492471effe074a8e17fd38c4f871f9c4a135bfbc32746fbe0044f95cc32ed933c3e387ee1de7bed324bf6e078b40ea2a64eafad1efc73efeee8687bcd3cb81edfe6af110f7e0cefce76d91d0ca9af52f6783b90e6b73ab81e5ad6e0db35b92a3ab81d8748afcc1d2e85c8c12b8b2995dd2671703d54ef8f573a38781fcaebfa63c8aa746f703a7a0e2ef391f345e70657d2628769a80f2ca60dce46878e1e877414346c7072868f6369bf64cb1a7ceb0f79de72f5fd6a703d7be4a04572be701a5ccb7739c75f4183bf2134a4671a0b5e398313a33d9121a6ceaa98c18fead0d724a40c8e89a7c5ecfe4af927836f39ce399aabf1a8ff3178926e2ec4f42858ff6270c2fc6d75dcbacc1f066f2a849ef09863c607839f525ef320390cf71c5ff04337d5140f29217d78c18f3653b8ea4bd1bfa30b6e48b19c2485b372990bce59f684a40f72d0f1161ca91cbedfbde318b35af08367faccc741d566b3e046bb1cabc574d074b1e0d55b78c755dd26ed153c099b434d9f90c9572b3861fba543e47ca779f1b0a3b4c20d99a952adfb2353cb0a672efdba8ac52cac5556c5baff63ac3437d31db3a10a2ff664ffcf9b6e1e7ede48859f2de95108d73aef3750e1c5ca1aa9627b8698be53f827d33d99f2fba5e698c28bbba41eb2f3c4985429fca0f597a23b59be5092c299bed8162dd9a370a3e40fda91dce5ba4c149c654578b95876677cc7a1ba22caebe650b829ddff7564c7b61cb2010abf446c638aea486df9849b428ed581470a911896c28627fc8f5bcd42a8eb550b373ae19c78960e82b1c50a0e521d48c20627fc2862c71d346724e0c5c6265c0f1bce43e227c7686bd4d8a270b0a109b7f307edd8983f7a8f63055e8071180e6c511b99f083905c6d5372112d4c50e92932965d1313193652f89770babb2445788dd259870146172940b584bfb221b572b08401461717d8a8847fd9f5b17b4e295157b4997d887a7a109d7b42cbbbfc4938eefeb9225473f6201e614312cef976678e241d093e4e2675f9e28684973aec3a5d5269afec1187f1b87338c29678abb0b70ef70a79aff1289bd036c2550be92b98472184ba1b8cf0e37c1693a6cfb1087f725853eb137b091b8af0673d7c18d357527f4d02612311fe9a7af9c7f9d307416344d84084af6934c2a52c1b61e310ce8c4c276d172f4def8621908fb78314a23c0b001b6c14c2db681e2ce6d01d734cae0e3608e17b1c678cae0c51a9a941f821a4b76c99b3e5941e82f0831c66cc71777ebd06a292abcdaaacaeac7c5e51228df87ff600e1af44358b31440c6697fdc10feeda2b751cf7e4982fb65081f9628b177451aa0a3b60c30f4e4ecb24b12f52c7aa45b0d1072f870813d9fbaa2ae40d3e2053a7eeb61aa6d26de961f4506334dfaefed88d3d781163aaec9f8fb1adc2c596bd0bc0c0aa056ce8c1ff402cb43a0cbfc8835f21e4683347cfc596a5800b30b6b00ae374c10518c9800d3cf871b9a475de1cc3a4f9c25841171f3841170fa851a3460dde2d54e08215d0c61dbe08030c177cb1c51602d8b083a7f59623dfd47182159814a4e0e08264d5c129b79c3938952437aa0ba81eb0410727786be6f76b71f54e7080018651c1165812d8988313527ef3205c7a6ffa2a0c1674f1052939f076112f2257259135356f96b3db448c1e0bbce00d6cc4c12fd390d992c1a3a6145b75a0cd7bf1451860e80aba48c11746058a81116c711b70f0f387db0c39763ce98336dee07faab75f8c1eb9e106ada32eee4ecb23d4253eaf8a791c39ecb68f8160a30dbe7a6c99f6d1c63cd9dba20b161c5b18166cb1050646b0458d0d36f89f39a5c89363b61e8b8b2d2ec070410a4e5a60630d6ea6314f79324586ac1729b80d3538d61dfc88bc5e5ce02eb091062dadd3ba34c35633cdb6e66290b953b58f49738213787101167881811a354ee0450af2be0883051b68702c9ac6a0622f7e1ee4622b055d187206ef425b7cf6278fe09a2f5a00066e6c98c1f7d850219fd4d25c9e8bad93822e0c29832713730af3fa4bea09e38b1680915c8471a80055f05da4003130822d686c90c1dba4314721746757c7e08ac66cfa7d58a11d31f8315b8e223950cb09831f7f0ea2b6e251ee3060f06cc6265708ff3959bee05784201ff786f178c10f7334ef318dfb8574c149e96f51e9a639940b6ece9ec1453ad58c640b4eca665b16ee344cb4e08b5d6a0a92d2a5f6f70e26fbdd7b2c389d992ed9edea9d2b38b235bfa1d6a3d86105e72a778ef139b09c1d557033648d930ace9b7cb470e9297819fee73cdb43b1b414fc9118b2759028b8b6a93f8378471681821f89c6a658988fb59fe07ab86fada8589ced044f52da48f1f19a857013fcd0c346abc87124394cf02d36422563489163096e481fc12347555a2bc10bd1c166869cd2c649f03227b3489535d521c1f5207fdcae1259b53d82e3a5213ab5467053e5e8e314db577711b4cb9ed006113c3b931ca6979cd9d3b3310427a70c95b4729c3aa4674308ceca7fe4f164f74fc9b3110437f465cae1a291f3793680e06d0e391ef994424ef36cfcc08b6079e22323692acf860ffce875de4198e46dc1b3d1833f7bdc23d1e581131e9f63348c45b703e762b4f7adeaa0a203bf62088dd530078e7814e9adbe0d1c789fea714ef213a6be8d1b782aee5d975ed299b7610327b79a4711e9e31baf165e8a0923293b8ed26b87167e471b169ebd269c7666e1857d1c449ab2f02a3c76670a1b2967b1f0259a8ca51c62d607165e0e136b3ec7ce9257f817f239ca945aa9e50a27e714d3e5dd61c76985b72efd59c3c692301d56b85146e3fa833a4f59859bb54d3ac5b8146355f84126c5d87452e15f7d6b06f3a0c20f16730aafc7dd6352f630a9620a5f632bd67df4518a29851bf31e2ba490c2b3f0968c12320abf26b4c207d1ef3f88289c4d3d69fa0c853715bd4dd3dfc620289cb6912c62295f0a924f7829115390a858a9239e707ab3739035b30c914eb81aae1e568c8e909f134e5a86ec3064aba0fe26fc38720fb1f3cd4bea356125bf9cda1f4726fc54291fa61ef1f83c30e1a6ed914b96dcc2765cc2e94b1f65d7b425dc0e72b678f88e1ea42be1db7d18d971e71c7253c2d39c695b622b33d793f07360725922857f9925e149c664f69fce718c23e1c61434e7ce3239430c09275e24856675966b1f4184fbf3924d1de17d14c226767dd49a36c215f54f6da119c62319e1a8c744d5ca1e4de722fc8f467324e36d174e45f841d2491eef12e16f4c1aa53ffa87194484f7e92d857d203165f0107e040b734136fca268083723e51842f028c28485f0936407a9912384ff39b3cd86078f633708df0397bbedc958a320bc8f2aaad53eacc50a849783b07eb36eeb6105083f9c995fd6fcc1fba01a2ac37399871f9ca416aa434da60f7e289363f48997d0c8f0c10932397d58397bf0a37a771817a207ffe8c129cb9d3a79270f9ec6589f6c62f0bae0c1afdcd9730cffe023e60e5e7b7cb183539a3b2454eae0873972f4317d70c1a583ef992a31690ae123998363f9a3935c297db372f03de54aee7d1c9c742ba6ea3d1c9c90439979a455abbcc153f5a0b28accb4ef063774fac852eed82dc7d106ef5d3eb00ff9f38d071bbc8f4e6a9dd3327af41afc8ee73ebe540d4eda8a9d95638f2ac569702673e821cd46839f912c26979cc149e7a3d616dac3bc197cb3dff064d3e797c1495531f75493c1b3b33149511d8393c94672faa468a38ac1491be722a1d3a34bc3e0c9e4e06d29f64a8e04832bee41ff57a4aa07bfe0f95a88d029e8052f7b9691986ace65ec829f6ae1e319f34873b8e0e7cd9888892db2b92d78b93fb5848a18fd3a2d78136361426c89e7c8826f79f278967df96b58f0d275fc5a26623969577022222bf67c58e295150ec248feee5855c1894924a6145eb93a54f083c9db1e3705254f8e9143450a5e6a5289e935240a6e482ef2713c1628b8c96da225a67f7c79821fc6c58ccb92db3e27b8e1710ed5e2728cd56982bf39ce379b63cd39c304bf54ec249c4bc8d6c743865109558ccffa7c26094e8e2a4729a305099e851ca61c5cf9d7468ee0bb472166e690b152c4085e08398e6899c2f5e58be0a73c29c7131e4470f37d184663de48d13104b743c81d875e087e5c21c79572e430dd41f0e3102ec7dbea1db440702d8450267f3167f3074eea891dc5ec286dd83ef07e26bc6358c90c750fd61c2a5a2c0d0ffcb2b90e3a8ea6fb8377e0e6aaeb491e1df8a6ea1f07570edc8eab3ca590c581f315be56ed0dfc1c269329f769481060033f3e0a59f2abb570724c4134a6ce725269e1c76130d114d159f8df79629ab01256230b3ff020f7e6138bbac4c20d19636bbab78f21b0f027d8b5759a0c96bdc2f1d86453b694ef2a5de15a8a11e71f6c851fa6ac3c6b6485133bf038b29854307115dee638248f1682670855e19bc731a46473ee38970abf5a3e920f2544a643856b1e473139fa3cfad829fcaebc21a5c542d29829bc4a2e5b29973f47560a6fc42f420ab7ad4b7286ef706a915178122b872963f7077922a2f0abb327c4431f0adfc3467b8efe79c20714be450a0bb93d5e8d9e4fb89a3656e4303a5a763ce1dc8da758673ef1309df0443458b0bc2177a4e1842fd9999673d4ea39b309b77275889af346b2144df8914afa8e3a997574c9846fffd92c5b65e5ab60c27331b32c0f3999e6124e4ae7ccad29470cb18413738c1243ae849761fad206cfd9b194f0d64cb352ead6ecc824bcf4edb61d7e49f869528cc9b28f849f72ba9c3e9258110f48b81d2654943693db8e47f89e82aa457438c20d29738e432c6af67423fc38fa2883947b349a66846b729eb2868ae93e7a11ae67d9e09f61430ab5229cea286bca5227c28b219484b31c45af20c2b394fca14b88d6371fc22d93ec3996cdaa8a21bc0ff5d01f47918b6c219c1c4426757c84f06bdc73902a1d8493d7c3741b9ded422a08ffdd53e64eed953b32108e66ef9421de2ad70908cf23654df0f30f5bfec1d7709f29b2e32d1ffde0888ba528967a7cbe3e78a9352b92d1dfb6e3831ba135dd6a5f9c670fde64694bada93175f4e098650ec5c62f36260f4e5cdd5a0ea3078f030f5ea61c6bee204436b63b7875de1f840caff0cc0e5eb40a390ec283c831ab83df1263989842072f4a4a39889123c65b3f072f7d98e39e88c90aebe5e04627390e6e4e6917f5beeecc818393c7db3f9387cfe50d4e270b6e91376ef0ede5235aa749744d1bbccb1c25a2648397c390cf18d5e39e5983e3817f320feb715d8a1a1c1b19f1acb1b672d2e0fa892509963b98074183932ae7e6317557cc193ce94e131e7d33a02165703c89aac5e8f155f964706ce63c8cc1f2e6c818dc48313c3226710d8bc10bf9d80e3eabc5360cbe44c8299face4283bc0e085c9e123491e31a45ff03fec0e2486da94587bc195902991a84f92d3053f4afa63c2528ec25cf02a7d18973d795bdd2d38e9fd2b47f7214b460b5e856745084133250bfea754b9b3ad58f0b4cfa34a41bd8297572b86902243f2482bf8d27188ce548b746615bc8e9db3974905ef3a96d95c15ea564ec1cfa27541baa30b0335d6e04fce29a79c3e31c73fd5508313323b7a98bd37c7d2a7c14b52eba1e71c26b9bfa0c17bcb29575a98902c6c6a9cc10fd45c2c977c7e50c30c4eb5a492d58eb5366519dc4c1a1d7358ab99f385b9e200c7e00bae4106ef3dc8df352b1d440f1a83ff95917cb3e5387fe41083ffb16dff88ca61f0cb6d523078b162eedb58096a577dc1691993bce0c714d27f7c1c540cf9d305b727d347b221d6e0829f43b68fffac3b47c6d4d80256eda66a9fb121993a72ca9152430b4e287f0f5972eef5ecb2e07dc584a5a5160029d4c082d71e76567d566feae4158c2dd917b493a68615fc1cc40edb516d35aae0dda58739f8daa648550d2a781d9322f685cad86b4dc133f1b39f4b661f41ab21854a225cbdc4b3db3f45c761f25ca79428f81efc6f76924f179f5f64208c2e9e6a40c189317956091ead553abb45d1a0c613bc0c691f9a48ac66ab38c195892cff0c25175b2970810ab01650a3095eead452dede71b155bc50c1162fe8a2aca67d00b7c81a4cc0446d6525cd3d6ea4da530ea15e61420ebd1a4bc0c3ab3a66a4bd1a4af0a3e418d523bf6b24c1ff782e33410d2478c95cca23357bb8ce41358ee07cd011933a0acdea670d23b839cdb6f2dc45bf0b1d1f6a14c1cdad31720ec254938670c0053588e044f5285ba7ef188217a3d4f26d9ba7206b0d21789b377e7387641070950bad2f0f0f912daf48d95463f0fce7670d20b8f943baa5af508d1f78317d1c8597dc686c54c3075eb28a3c217610f56bf4c08b1dd39e03ffe0f3b13578e0d6467d798efe0ebc4ff2d7b93e7fecb8d6812f2556fea1c8f9a6a9460efc157b8f21fca4f848db410d1c78221f85b49023dd6adca069cb3ad5f07899f56ad8c0e9f4183643566ae157a7e4db964fe7a9a685174d93254b9b5938d1cc276f92ee78a3280b37326bc5dbe5dbbdf3a20b1ab1f0b5bd43f4f54fb1013460e17c14ab932f890abcf8420229f0e29c8374e0cd0a345ee10a67a4a245394b5be1c874960929fa8b4a8815a0c10a3707d6410aef1e1e73ce2afca02124e4281d55f82d39e88b1239346f4c2a9c50cb7d11613c5f5950e17cb04a1a73189e39f23885e313293964ba47e9834de19da4cb5c67bf1e79b0145e5aaa0b69aa228597f2f45cc8d1cf7ab246e1895864b5dbf049461285e7bd1d9235191de51e0323d8a20b1aa120bcbb2ea6e6ccba5c3a68a59453d88e06289c641d2264dfe827f6ac4ef35a8f9334f3ce41e5f65852746c3ce1b4af64acfc49245cccd3e8841f29a7f021f6458f5f2a3438e15ae80ed3d6e6686cc2f18f5972e4f46868c2bb0966131fb287d9c74cb895d583a68eee474303c384118307c48006263009198d93b4cf6040e312cec73fd541ce1c96a024625cc2e34443e6b633ca448e9b5425dc4822f9bd2e4c0947aa7e23c3497e406312ae6a76da761b25e175745e1725e6bca69a48f8a1e55053884820e1a58491508f5a2585f8115e24f7ff984c39d2d83ac2f7d0d73c8e36a68fc735c2cf41960d29d46784f7b92ba5c71d5c4461e6716791f1e63dc9c358af082f04092a398eee72611a89f043aa7b54f76977c88108673de6f83dc474eeef1cc2d99088507eb51ed6c410ce673b0fcb3b3afdef42e4b6a22a59ef6521de5539a8ca1e2ca5c8488f107e1c7b5699746d15680cc2f3bb10fe628809c297ae18a5a7c3e88f628158a542522542d6ace63ebe903420fc384b984e9321c337f8072f49769c2b578c4f1de407277fce2629c4e8e9238f3e34652d27ef51976a5e26d197eda5154172f0c1fb38233eaeb2ecc1b1b4f133db512fa0a107d73bb67e992a4d1eb677061a7970ad2622bbe51c68a08107278878184248be71e3d1b883375152d49e790fddb71dbc8be930b2265407ff553ec8418e2a74f0a34d26edb183e409cfc192ad16abd6146f49096b8d918a395a0ece07a13f556d44230e7ed2ec5959b4c39472c0c1ab8fb6159115491de40b2f24805b031a6ff0b2e5ec9d3199774cef063f98921c42da1c297ae6f8c2ab0d7e1ccb79ccae90d35684061bdc482a41c634668af28a69acc1cf1e39b45384d5e05ca808b229d633a6541af2940fb9cbe9a3c1cd39a38f7a449be990c6191c09e571ec2853766c9bc15b7ff140727f06b9b60c7e871c7a90c10fb2cdf585bf1c61b531705fa2219231657116eb115b44424aa798187c390ffad42a0b4356b1769e325612369f11338520f6c126248a60f03587f8ccb3e182b8c00b10f080c617dc54b38f1eede8b56337a0e1053ff9df588748a769ac9d018d2ef8bd1e7c5bce2c122b840b4eff871ea22593c61614abaea9913497d4d8b6df88f513a9a6a105377518bdcef3a65c9366c18f5268cf97333b738ea48105bf2d6a7aceafb973ecb98213ca254be890ade0a5a70eb36c39c8c6a80a6e5db8e49155f0cec1a854f0e353796c6c7aceb1e262eb042bd805d098829fcddd33cdc4b8d88ac1910253321540430aaed445394b9675952e5170c58309e91edd52b98602776b5b25325257e3a7bd91557c825f79c343a8faa8734e2f136838c1ed28933be6b553f9c409349ae06a872d1afdeb3bf630c19199edde90f925f8e9f279f4b14b3494e067ea4ce96147a6393322a09104ff25bb47c1e3be889e41829b7b735839a87d84466dd553bc624decbb3607afacb9e6e3a36104b7d3ed338fa56814c1cff1f44732398a06117c8fe9e3991ccf07991c34a03104ec3e4aa6ee89da808610bc770d5e1ec60cb1262708defc4848fe7cf141f00061ab1b95956c2dab8de84afe33c973c77624971f387296d2c5687e631e4cc3075e0ee222a5b1e84d6147a00b347ae04a38891e476a8e83845790041a3cf0d64224a4740bc933b883c2d42a3ac6cb5536694c7a69e860cbccb8cb2e29bbcce8aa8c14c22b648ec3252eb668e4c0f9d06f1d7ab686988368e0c0b9f671f1ec48534e1b1a3770fdfbc4a3d9102eff84860d1ceb9cd13e6ebeac4f2eb6bc08630b3050701816a05560462dbc943752c59c6bb2f7dc8119b4702d3349aa68698d1a555f80b13366e1a5cdd41dbc86b270227c74f5314962e1a47c9329c33278871558d4a215dbd52255ee362f390e8f36f2fb0a3fb4d0719a438e2bbcfa9446f36d8c291fd30a6f4a63ca9d31acf05346d8a8b461269255f89dee293586c7fc5155781333bf59678af4b1920a673aecd248539f6c54385d696424b8f97d4d4ee106af0991a9d9716a4ce19987ac3158cab210520a3fc8ca07673e299cdad05bb79d517877122dbb32224f8828dc1c07eb21797684c2f758631e73ea008553214a92b3530f72e8f8849f2f8f6f867464966a4ff8416ffed0622abadf9d70343dc6b2c709bfc62b4fb466e4ab78139ea598796e6e4d38e79fae836c33416b68ef50fe03137e0a2657de1ea690f5125e4a0f35f96b7dea534bb8e13dc8c16f07aa15530967d5633bda9794f03ab68fcba1949370726945cf1463dab428093f8c65f9bd031ff1c922e105f7b0963fbe20e1fd47e4d4563fc2e98e93da54d48e2b48331ce17534b39e7e723ca311be79d49683f530a964b88cf03b70e9e8e55e04d9a723781ca33314e18b847ccb895ae78c6724c2c951da76d0b1bb72cc20c2894f95b3d2d9f57bee105e0eeff5ca1355529a0ce107b7f12ab318f3854e219cba0b3174744f88b32c4dcb3be32ea55bac374d06f3c8caa4c283f03b44f1118bc8917546108e66fed01dc2a7e5c01208af62d68890feb2ab2a80f0eb62b68f273a3089fcc1f5c966b13d9ef8c1c9e01fa3f45dfab0923e38a5d1b3797ff0c18fd44335cdc27bfa7f0fde8f45cb79fcdbbf7d3df851a4bf27f67fc5c89107ef3d0e9d2b4bee8c4be3c1eb309447d25cdfc1ad30f93e779ebef1d90e07d9265b07e7435d75103ff2331ddc181ea6e093d2d46673703cba972441e33f2296831fc4b4f153afc9cddc3878722fe629f7a4dc3170f06efb93f4ff779027fa066f2d438ee4a7e330f36ef0ff7d6dc2e5c891c56df063dfc810739633d8e047464ab9af424ed98233d6e0686c984b31c786f7c0196a288b787cb2b5310dbe475f479d91ef230dd1e084b449a288f765ca9fc1cf72c93c58c80c8e7daa9ca9528644580667d663c911aadc543a32f89d345888cb6163f0358568c65c89c1f14054b335fb6c0e5918bcaeebacbee4e69a0383ebae5e31878acb53d517fc943c0a1d741879c1cdd453c9c3aa762fcb8c2e78ee1b62b5fa9f7a79580833b8e0c7305ff9349b2db82eb2c12635a5091e99a10537cc7868a5edde285f169ca9c98e3e87cb2fdb61e1b2aa78cd4e891a29ab37b96e91f20aae45becc71b48c153c559f8f3fa70b39e5ac0a4db47c4aa5c7cac7bbf95f7d67474a05cfd3d7490efb7328dd14bc34bbd9f00831a637430a9e7b581dbb07761f75a4e1408d1a4e9811052755858d51161dcf57a0e047592343d966b7cf3cc1bfd41922d3d638cc70829783ce1f623af72c95ecc0173498d104472c46df768f8a89640613fc38b630591fc7db12aa194bd02b2eb6eab2264e666653c7a8b4fe511382194af0725dfada0cd28c24f4b35951ca2f8404cf2ab8a85a08d6a8b10500dc30e308acc86c744ad6c59667a894657694a24db2314930c308cea44a9875190bb57946115ca9243ed9fb23bfed10c14f512de5b29bc71e3d660cc1b35c29b7577d9c334709c1cda1c3346bbad4586d10bc105d913768464bf00082ef71f420da27e9f3b366fca0ecb2aff77439f5d841d4af48ca9fc2337ce0c7fa383b6bf7356a7c61460ffc9aa88f1d59c87970171566f0c01b9f0fe5993eca5d31e2c18c1d3839fd85e4b4195386ac03bf2ea4106be55353c766e4c00f36513296c77bcc51b89881033f4d4ae49f1ce5067e2c19daba932c821936f02c4c6c6b8e8a149d530ba7430ef28dc84f0bacbcd2eba3a3dc453c347314a29d2db3f0e3e389d948933b458981116cc1820d5938dd414cb5a33f547446868d58381d33425c7f59f48e030bd7facffb7e62c45ce52b7cbbf2f90f993762785ce1755de8988ad8a874e903e3e10182c161c16824128642c1cd7e23006315080010541a0c45721c0792381c7e1480015d1e1428281c1010120e101210080a120a060806060008060606000006080685414276608e65301f113ffb4f02b53e38081a6d1e2e448eefe4e0fb5cc5a118bde5a418ef40a7a3ff30dfdc617d9b34ffd7f073d12f7e1bb37969b31c27467445e227f755a721f8aaf4ed82f77e9665f5bdba1d93731f72fa089045db88560ebb5199a4d834c95ed3e0c3d8e404d1a98cdaa2134f16942e68ba9fba41b2ba300222fd6a06b1c1ca6a896b6ccfdc63360f18c3951a5ac915597f4aedadcef6e00fc63a94a3a3b4606bbf1e2970253b672a21a18e88f40dbbad6a3435a3816bde8308bdcfcb169b680e54db13f5408b1acb24ca84317ee7365b9dbc93794b5579120d7a4ae5240048fbc521b26c423e8706e73d5f85f5ac1c5afa60d3a589fa8152aa14171dd0cfd013d14c91443aa1ce2f0943e1a836035a3a983c7980437517be3861a1f8f79b8c1b1f927fafd06f66e40a472275af5edc8473ad22bf37e0afda5492f7681368876d3003b81786e5f64530fec6c8e0351b39ae5ce166f2c92c2e4f8d4fd982f223b601fc623f544a3904486588ffa2f4609c33ecec7c0752a2af78c9e50cae72365db4ffd4d9d5f034c4951e28cf8a206bc3158b8034ff1d147bd080f7bb7c5215e1bf86aa9ef40ebca6dc46c27dcf35d31a2dfed9e13d1ce94590fa7dea84d510c01c78a5e7e03ea7262b79991f097b16d45de0bc047f6bf565f0d5bd5466c0150d689bd21e2d4b011cf4ce4d925ee8b33a8c9d29b4da52c74554706903e0f8fc281d6b396f1f8c7a4b0b1c7cfe80e15baf1acb1670945e9f36ab648e2ae2c6d954997c2cc80ddee4ee6cb41a4e12ede533a4097a029fa4a4ec773ec05976c55a48b8f09349043b5b4a28a671479ba5b3069e22dc94788a66345e7fb8cad240ac505a4a328a82ff007dccce8d4ab68283ef9412e3b125435a8180fa0a4a56afb162ed4be7443bd5c403962e29574c00cb41c01e849dbb75a9b68cf96f89d63dd92d6eed27aaeca953f48c9963ea2c04db3bbe3d7f183d25a25d32a480d55f84827834a980560455393d3e25adda443cc509046da84f6a044407c91208f7a3f80a52b9c0cca6f2890cdd31822bc35c105bcbce2f53bf4d765625b1393ed2d44b9268cd2bc06ea4e09b5bcd5bac084fed0034fb21abf6115ab06e62168ddbf0feb95e4f7a4ce9cde2074d78a9d21c6d57f7b5cafbd44dfc3f328e2e5b5ee76589391d0fd824bbd2c3b7d43c9e8a2ad048888d77a5745e48aa7f2d7e3a12101ba7a136ec898891e432fcdabccf66ea0f52ae4d4dcc70754345454b8a8052b684ffff4a5f26add98134ee9460064916abf75c718583de2c9ceb02e2b4e15afca12711fe70cba5fb1bc39e9e95e39764a8cbcc907b0dc5c5486c44c9ca42be1102f402ba5b95234f000fe40158a79905ffbc14f40d2525f74f40a85166555322f1f70d2f4c06fb2b339a1b2af326ab74f181a4ebcce5d6e6b32fb9051a7e9cc2341be933e18cdf05087ece950e94975163b55431635214680eb1e45e63900ed1901c956df73349cc845b2a216ab50ef40049e2a0fc6b9bb803e146d5aedb7e4fdc789983c9ed8817ce44849c0ca78be6c68b159b3b8a97609dc37edcd8158226241d48b004d8f039933a65a25fa0236cb4cd74d06c94966ecad561f4a198f8487c8518669a09c76cc6ab824819b89d942325ca44e8dd2f9795174188ccc62a4d2ea26b5656cf15d7b2eae7a0bca35a67ae7e83d26ef945f036f900b1030d8f107ec1700caa943234993983d18a22a49650d76d20897aecbc70e29c154e2578275b2af301cd9257af42bda9126b6a1b51d3cc92457abbf8a38e9985799d8b2d951ba41710f283d174e059d883163a0ba2b624bc6ed34d99ce504e31b2c173a342ffb6f102b49e09585ad229b0cb2bed38bff2f439d37a65995dbae0abbc1048961f62ee362f04b4d213c22eb7ac8b7342784be7ad77a30a41233bf7b46ecb290f83833f9bc307696ea4777580c982a941c4f715fc34589d90f834a4adbccef9fe469290344b86a7dbbea28281f0561a5fbca2408e420ea552337eac8227057a935e8a52d892e0334c9ce596c635652108f790a16cbd412e96f3a9631fa5b76cdcd1d096178a1bca5a55a454cef8f0f51949590312af52fd24695dab7da521e9811855046b01d728180095e577b1c659665fd3208243b8f42903c07959e8901640a990bb89bc9cf2b1b4b0a014182183ea944e1013da0aa376dedf002b4136c0533c1ba5ef98054da57ff75b7a8ba7f0905690241fbebabec72460bbe53b34434e4158a7a4c3bf7172ab95a54de9030704f411f2e3560e193d60d7a90b8a68fe143a8300f0acaa5600cb64c89291f98d8fee99156c19c26cb4a4c86c005c5b4c74286f9e05416ed8adc9b0443dabcd45031a54a44c719b5c5a3f1d93b47208fd96a81c9eeec29d02c5bfb7d31cc52c95b380ce2b6ba652f96cdbd52d64d609064a090f33b5bf85ac07c2019eaee010202013b6c352bb2ada2e5dbe4661697ff80a0678af24fda14cada154ad109d768a34502f1ba57b035dd4df1e3662e40725b987da53ee41685d6fde29cc4b342adc9b43efd6690cfa5860dedef1a4361a948e62504cde6d35ba2d5a49d758aa21ec1439e477e9424faf023dda2409af5aec88c2b99ef188836c7cc52ba3de648ab5b38515a91687abe5cbc924935d37f1407150cd2345a7b90f4f2e6af4e259950ee6a07676a7f2301b1c4df9ebf65ec7e7ad5133889599596d0595e355e213ea6e7bf67101aceae57912a7985c82d11e8475c5e4aac4c3f01d09cb25441a6adb32a478309590a6e8a47d3a1c16a3c61608f78e70e7b7dc2dc45af4983f1c94cccf2669dd08f98f5563319a15505053770e6f11298d706013bcf3c60e3c727feaf918fe988819a1a215ecaf25eecb1cfe29232477c21f97a611945678d33728766099f9a14ab62dd78cc888b346b0709f917598638607ca9305263612d850c308be4401402ec908f0454546af35427ec40a97580069739498b124c803d968a10f1b8a7afa46ab84ddeb2511df72a696ca1b55be0bd0ecd58ec06909711c7b5a46a28d5547ee7cf1daaffc286401a45201e09ad4cec8b8c0513b6d0f097de3e6358420431f80bba358630827fa5459c551b535731f80e59c2cde40c25e39b763f82009b4f293383d5ea81074fda72bbc32106e688a10450914041825764d7fe7f58f2e0081c24e56152e6e8e475250be06c6d984c32752011dbcc593b5fdffbfeeaf99c5022f40395dbe3bec2e11dc014c3c3a2311c13d063a2f0fbad1447b00f87c80490824da5260aa5c85ca450b745158e263cab2ce3196d4056012db99b102a3100c6a3f99e76a64bac0e628ae133b3aa59006bd2d43959391a90cdacc87ac62a02ee4288121dc11159330b63de8a333fc1d77968bb7faa4b0166a25258b4e4310767b7a0ae651928a396b91d6d7048fc6fc437a0ef7237ccd4d4e66b00dd59f29ab01b4c9373985b515a7d989d8d5c8afad9b9b72a3054e9f8e195ca45e01c234ecb5d4994779a860d15374497bdee5f84618095df991e2744ae8a492ae497b846f48d88e4b58ac7ab46dd8912832d97873e79925ef0151a2d5a415c4b0de7a664f6a342d0ee6b501e22044b6513ca105ee3ca45117bee4e33cb01d461e78ed9c4204f99127636cd27ad2995adfe6206925d63354a8ce1679490e0f7895f46a9384f28f8c4a9cea12b48b675492948cd1c96aa7a9bfd9cb71adf730b7d83b4d41c28781d44e55213cfca21db544ed4b01a96fa24c8bc80f16b34abd5dd1b6899832e75082d3261a560aef9aee4651f5a150498e30e4fa30acb318fe21e917892ff3621c4646ad88b2cfa491a410c4a19df7c6091657125e3f49297b7a29809c2f7812f1e64a04c27dc801b6248c043de9bd5094749de19cd6007d97af0837aa29414e5148890be1f44c0550d3c8259770c19462f8b426bc3daa66ec46d72955555e0daf4245bc289eb0c5213e4353d5014c141575761f954003e3d0381c8e60a9a3fbe47607feab92f4dc8c66a6edad89ef52645c7d3f5570a112f49ec6a26e92761f6242c5ff7634567ca4fc4754e64e5dc591501639fd3c03633e8a2c63ede1316ff31e4988257ef77ffbcdf7a1690f58f47c9c42d73bbde0e289a73074cea6149252022552b3fdb1f9229034ea5747a56744e6707b8d03ec9245596b9a45173a2e48c1e24a0e1c0a6c41201584a458099c7f64a85d894fd7c98d51e352810e725f2a163144a8d3750888740a5e9439186063ba8a88209cd34aa2f4e11e91e0eb79f4ddba3e334eb5128b6cc825536940f9114ab0de49509212767c01867d7b918bd325894adfa7fab8669db630a03f7f3ea91182dc083cee7c83b1e644a082ebb1ad0b8ce949f2a433e7986f08c20120b1651fdc53667b0d20fb9a07bd532eae5700d443a401ba89f34502ed9f5477b62a52c0c666ae6daa0a4c659fdca3d28aa184d1994b09d9ed5661c919d488dd708f5cb11803e6132da66803d121633adb0b90039cf3170e0b37e65862bfca93611503c61074734b1535dabe239326746a2902cd32a92a0ad1aa9dab303e5c74bb4b2713989cee1cdaca26c402c1c868eaa0249d7ad4af0d6795e3d3680d09b79dfae20410ca217cda3c1760ff686ac454c08548961c5996541617154f0b87333c72a254de1e097bb386516a314600e041c444a7699478e5b34b5385e3f226ae00461af8b234efbf4b22d1e89710095321397a22a98179e92936eb336ba64d1bd35f7f30d84d07f344595035b30978e9b6c9311cd1f7723ed5346591f1188be49540c62932a08be7228441999a9d7a9075efd56ce0034a8c000928ca8416f91dec4a7433f24562e75db5632be999334f2a6726b557845a773768d8c9b8495dcdd4e474c6913676198968f139478a3f7e4da5a1821e8d25c52de7dfb4358ab744c8e44ed8a2e70a3a749341c53b230937dfde8cade31f4db385b80239c1e65f29b74681c5d8f5e876f113aac87ddaeaf0da31c1ef83695c3253864d4d8329a37741aba91c3e8a664a83f0b7eaf34b87b879b1757469a62410d51059f44711276347a1c5a3c7c7678d2f3a75368ba8c0174a1f834c1dce04c7c4fb5455f438e008cc656046e1ce7ff5c44b86bb6ac6fb3438021ecfa34b8e70ae7718788987121100e52f5b6300cba7ae0f7e0aa7d9326479bd643658ffd181b0c1f01042f5029cdc15d051113fd001a253c10c78b3f10357907dc7407ce136c8ce71052a0370a826f2a24443da59a7a38c2807410f999029f05be30586edd3b7c76dcdb8cf23e5c88df94e1ef26c93f972c6cc04283f90b5cc8f3afe258b0f4f9c19d1791ae707eaf9df318fdbf526fa68cdb9756d9f0ff85c2b923ef549a5ec073d4ad22f2904404ec439f7fb5898e27ac347a2b0b1a1500eb7f6f5635118d18852ac24ebeb50868c9efa0ae0cc5d007a401778f2e11cc6877d98036bf02fec3dfaeb801338993300e53cdb670634730fc83c9e28b1e80f620745123475ea5a4252ff5d807aa8542fb0c6f03bdb04e8525d45e9e91a3ec93c55016a322d8a7f02e8aad2b2be3d708ddd5d779dd63b89385111d5d8367e74d0f44be72d9280c1cc985debc2eced4503d3f7878fcc9b1bd1904df9d9f7eea4bd11db68b4f84df69ec13e23c66896b206517b34ebb6a6faeb6aba287327fb50c167388ca8f4cbb17044f47e952efcd10cb1e9d56c017fc51dc959d88c5bd7fdaf374f10fc9dfa2b812aa11ff6a9280e32e91b4a558c522727a8faee1a7ed56d7f00d2debcbbd3d9f15524c4491ce740c818957da3f17700e6f4d2deb0a37628284503368c8ad4b4a753eae0a4d637eb130700519400d800ea3c0c686825aef012d8b8fd9c8ed0b896918621602d239a05e24b81cbf602b9d0fd0b50000d3c38c7af7b035c6b9d8b56f782f5957386eb766d713d478c5f23e3e2507444eb57a78817369d02eed898104002d7ab2bf811f05f0bf1ada8770030925b691310db3640f63a41718a91e4b5d5a811222daf022e668e0cf15b5f4c1f40f4a14d36c3c7a7cc897444652ea15844500d1bda8c6fdb8c9946f094d1a96906f6970711a21cd58598c5d391bbfa0da0d26207503a85844ef6c57bf8683cc2400b9cb0df71e41fcd2ee2263fe17adaa5ef808f106af4cbb2a01c25b09da9e166c64c1749a1a04edecb6ee4285d50607d1a546040e8f2671d9a63500dc6a8577bdae4e8dbb37371dd292589c2163477b47401eacbb169348e6f2f1d141bc0ca8d7d2aa6246e56a4949c6e45f2ea7a064eb745193fd209a9c2fa0f38a01437e719a862ad0dcf6e347ad359e0685f6c6f138a069fd5dcc685f5d50031d0f9607636e61910b61e9e17ddc846dcc89508ad55a9045e1b13b70a17097c4022216d2102e80c3c17b6772cdd19298762b73ebbade239b2146a8db532eaf6d378ff39acc89550a640a46bf1fc8d04f7105280a6858c3e9100aa50b2f32b205fda164ed2faa9613b8b34b6ac1f1fb26e684c67f17ab955bc686244bd6d2e4124e669db06470e9c2f1a9804ce35a3eb54d1b3137bad0f8cb6d1140f8c235c56955be911004ca25d810472a90df4b6d1d0cc026440b77f83d2da8f0a17e009b6286dfb5d3f32e22bbce92081572c018b5839740794c965b8586434577dd6b4ddb3b36c6a77ef30745dbad89608b3ffd70f06d442ec0da09164087dba36f038c2820d3e6088079a6821e09bbea05c14801a2e787160dd9db28b0e2414990c6affd7ad95ee64fda22f1e1c8d6134e2054b4a7956ac8d46f5bf2813e45ecba3690cc10991684333e92900dd4fc9aa2980a385e63174ff12f998cdb4b39dd3cd4a9aa56d61e93c5113eb3473ec4d9a4c3b55d865d770c1dc69a5b6a8810e91c955d32f3208a86098500cbeb1628c9a93aa33a9b4eb22af1f7e33e77822d73b0fc3059bf9ba5f0f0726fdc4f4b639dd30a748eb8ea9e93e55cc35969d6d4b6c70345c5d16588c8233799272522d2725a20f106f60950a3eac8fc60dacbbf0e0e270a38f44f1820697be78ff8630b20a7386fb94a5ea3c5d11a0cdb9b79a743bf6f7f7de689ce18a6eabc4cfdbc4062011cb263a198ad9f06dc8364a2997469db87f15c8814167893b3c7b649e7c27ec59825a18a57a6c71852b23968563e12e22de09ac620a5db2036cb0f2706a4b579c523cf402a5b41fba1bb72953e281c457604453293ea7001e1b9147763d2a8ec587c2563f7ffb65c8f217c7395b56d648a779bbd6170c0e9b99337bbecd9ac8a82ec1b880eb4b33a5ee01484550b3a2456a02715a5b77c2c3207c6c158a69c578a549a82c495e5aabd192f10566b7a9c85941d39c1c580a6883081eb744b320304d88213f5fe64b0012f0e665dd7d62d9be9c345334a2fa71305ce4e2964071a768b7506f17b670a94ece1ecdf2e17691168d6c3ac277e2347525738196b25356f602a42400e3682b33f497062515a4bf4fed6db51d3933d5f4643451866922918cb67fef684d5022948a3515f854ae1d8e0530e84e7f274041d60005b25faa0f7b9e6137c68ac301c8dd56771048f7a4ab331d60a333053a139c4e21eb48a94dbd80872461441c324a515d0e0fa6c52e5d70e7d7e1ff1bbf41ab3d5b778e0e8c355e323b6fcec0eef53063b366676e23a35971d3510999cd83d27721f01e6ad1bb7b26d37ab5f8826089200e8fbb587294f18deb220b75a1b437dc747e26912ec11934b95e1551ff8b22e060eac8c09ae15d0b4b6be9afc75dda0deaae5e190305338f758410ce49ad9b9b75787b61a42e5dc2cb6a147a351df04e35d38c992bf985dc1cb8e87808ad4e54012de7ba0d2a593f142807ca71dbd6c5706194d3be79c0c6c11e99a2190391dd1970c88e76ed7dba93a5913e8bee33e0b6b32af9ba6f62399a6649ab915e1d6381083dea087d142d8171496ca623808337a0e0ebde307554e19b96ec224d0e9fdbc74834bd0a4a5f8dd8d93582a7634660a9a11252200046da4066c5bd4298b9669ee185d029c790179a43fc08b2ee170beecb6d3f57b19ef4e9e14720867500f83ece013679e5bc5ed613b8cbbbc4d5c9664aa790984b227b2d8df330200f0cc0a16c167a0225d6e1c1bbf3623e19077a6fb8fd20941a0b9309196968477e8c9a12e6cdf7c8ab8498509b2e7baab63fa76c25c4a5cd06b5069e952157203c83654f51f5a2b2ad1a434ed5ba2842e875d78d06dc8f595d91cb28b81a0bdff4da939462c9e045f8e914be8d359d3eed869d42da1694bc64ca56acf6000486c8e844982ca2f8ef43139f6e93d76016772d40177d16d7c457651ae4ae45dd2cd16a30f8b8f2dbf30e942c9011d8a2ae5d2a2dcf237e24e84445b37dd8435f4eeca89af80e7f6c0386824180445586f6ec821d866ac93da463624e075f1772cec40b134eb44786f78a96fff28e09468c81594d4c71260676f3110589e879aa0731a888f3b14428a4d5422d63f4076c15a77c2862b420ab1fae43b6e5f0c2f831379bf3af7fe9748aac1842d262487139e3550d54e1801c63445ce6602a56b4998b828a0c49a5485a24d2fe624e86b88d66051cc1a01b960f1cbdbe3cfdb50290400132a07819de3ae92c500584fa09beff22efd3cc9f6d7db49e5516f0d70b15b4168441806c818284fa3b031d5c5251a70e21e28e4a6ab554bed883d617c2418cab0310a7e6aabe1ff60e9ef71c54cf7c51e99d4ebdd9b48744321d5d1bbaa2ed6e2f6de673a1d97bec65d8abc57b7a749727fb5fa28cdf46bc475dd2fcb79e907491f6b935b90eeae631bb3f9576722ca14b48b7be2ee3aeb1d7bed76e5e5ddd6fba1f5dde7441754de9fa55bacb8751de8913bab1ba82bb94d37b5e0c1d5cc7baf6d3ede5bc2a0b435a4e2bf2eeb0372daf015d36b5bbfc43963ac8d635dcade75d23bacbdf7895d9ab6e48dd1ababf5c776b650691c7e9c2d415ac4b52d74e177a5d64dd8abaca5d8d783dea8ab3dd42e87011d2eeddf4d6f476e9f5f0f2d405ed9aeaf5ddbde744cfe0f9583720d17dada1678d5974ebe93ed4fdd7fdd55d10bbaff990675408ede6f4727a7d7a817a35ebadfbeaafe2a66b9be1facc689bf8c930abca77684f8956c0b8e719c67893cbcf07acd96a53f5706355fe36225188880a23fa090a0983b42efd80622050158e645920a3a3e79efacc0535e24664cb277d86a2b1fef7660e7f141ad76eb950045eb3a80522ad1dc22b8ab5b73c6c58ac4cbf9b9b927bb5045957a867ff01ed2017181711feca5a8f8696be0879c9662ba0fd6f2b3215cbedb600ebce62638facd876494f9e84ed5107e8e6d857f580f4dda4797c5757f5cc8de220280f2e4dda454c90ad1cb649e5d9b0d7d09af97b2e89b99a867dbb64a904208b51fca950279fd0046d09cd38f5ee40d794bdca940b38025a5181532d6c071840baabd90215e000569667e851533f098d91226053424ecc8565812c46ba5997b570300abb2970f659d5a6d528fa2578736588e78d00ed9a2555573bf7ac19bae4736776cebb85ef1df6f67cccd87173c51671ffeb0b2a98163707103cb76c5f70a4a6f42e04897f5d591c2bfc5ede80097d1e43b30ee56f59ff5a4fcab15fdd6cd74f8927b0eea5398ef55fe7d3f44d6bd641fff3c20beecee4ec0903f129e187add46db60bbd1da525e3033eec967aabcf936b6c2e49fa8003dd1574f501219874cedb2632f573b05c430c4b21e2471ff5086a4e74c6934d3f9b8368e66a687fb288eca34891b8448e8e2cc1b5c8e438694800864e62d8bb3d1cdd1b9f2ee83b8a2798a4a75dae39b5d0fdb0f59b374aa7e72ff32137519b284623602b02a7755a09f82ee10b290d1be4ad3d814fa6db0ccc198eda902a1181439a20f48686f5f016d9334c4c375937569b7ffd5523c106436c2fd8c75bb943e57a0c54429001ab84a78230814c789c1732e94046e8c4c017a30399598114fa5581e2126808ea8e405601b80c6418e6bff97423c83457052f37c1effc7dacfedd74108147102fd5fa380bf3ed070668e82ba913204d78e6272d5ffab557d7347489406e20b7e00a411a0532ed422048bfc0af00a0b1a80be0815509bd41ba5ee0270aa55ec546810cbf0722e39a7fd5ffc005aea5af11c90288bc48f03209ebf526023c0f64b247505134a600e5d7a8e7194f18a3d485c363d1b58e491b807a3bfc3748fc143b25120b08c5999247fcef837a74fc149423b1c2485860e00632697ac8ab9eb3c1aa39f64b0b029001c102d902b6026481a4106432498381eb0f049169b06924441db2e7ec6b6ac12881c31a647cae7bb34108826c1f85c3707edc54d8f600ad48d08d505c0632f9a6f40035952023490215d22cd0eb60a01e373c18580c2ebb02610f4d0913f8410135124ab856bbd78c8f1a972a8155098d446776e00d44a7a92750511cb095f42fd205d9ab573352e50bd60dac46cdfbf659e1d2c7807d94fb0ef2360932c11d28838bc08f1235000187ab2ba806eaadba654f65ef868daa2029c195d1347b68a1c87aeaad0b028ab53b972f4074904207f003fc003fc00f20db0889adb5de499429c9f2a95e21e722534a29a59412c3d7751fce38df70a4d134079709030935092849abb65d984491a3fee424a6e2a73fd8564f1746d50dbd3b9fb4051d2acb8569cee2bca8a43f8ee75de8c00597d4c6926c49cadda2f0b4af6ab999afc3166693740853f7512dcc277ff20875afca26b409c0600436669420081f1011392cf01a20e03043072dba55adbba8662db216c654740a22f24255981db3308aee1953e2d9202965094420821a23344af0e8a4ea90c58c1a353c7410e2e181808e5898836753caf4336ad4400974c0c268723ed9548b5f613a59a7962b2a115718bba40e2f4abea9e9a81506b93f71fb16962459b3c268b296ebe8f419f1d32acc419930419892df249d9781d44e154e03e407346e38a02315954e6a54ad96f11e21353c6c9c19666a830e54e8f2d9a6b16919e3a5599f3f5e44ce969119a9e314a652f9920a6d4ba2b63264809c8083c314a610a679b2c80f35cbf9606b187494c220bedddc454cd0771529cc7ae55f41f4543d3474002272a330a6dc29b92d1fd5ea1685d94de9c99f243b140695a49473d4149d3a897fb0257a07288c2e6245895ad3232ffa602b9f30594a1f269dd88e12c4f484a99389df9315d64f8c3ed8d63a612eeba4dd7992d6a5241f6c8513a6b53bef52ea3927c71f6c3a3661ca3b2a558a73fdea8cd584c92efe255325ac8e124a5948d09109f3ff870902e19d0688475a0a3a30619244d733bdbea47b4c973069359ddb36276809e21f7458c224b99f08ed163b23229530a7493a8a50514c5aac3ed8da2861ba50a92f9ea75caaf5c1662be89884d154c7bc18714ad8820e49984e662f947685f3794522615272ca387d79623d27618384593fdd95595ee41e61d0caf124b784913dd20fb6e208636f9a6625a1a4dc973fd8ce4464d89801f27b35403c6c887434c29cbda2ab6ea92ff7920e4698edaea477d30f223b7e4de85884a9c4f1f867e1977c4c14613ad1392de82ceae78e44986cc484e9a65a07718208533a5954f81cb7435cf77176b9adab62a525cf8ea13f091ac27c27ae5fe5523a9e2d84693e9c924d90760721cc5616b4bd9f18b24feb1884c184ba98af5d259bccd621089312ba3a84b0a04018554b892245ff75923f40184b527be6268a8fdd3312aa20082724e4e81f0ab36929c7cd9c5bae5c8c396b3bfc6012dd52a85a1377f4c1e4256a46f498d09d8422aa821a78c4a0830f26f13e42cdc9792fe5eb83bb6f64c8888d8e3d98b34bc9b65e930439793d98af32cb3a85f3141ec22c74e421f5c935b4db1380c1086ac08083e30735424254d0810783ca89a73b18b6949ae06f723c39be7630db6a9f60a5e44fcb50471dd00c2b4bd72a97b36c2ce4f88f2a41caec4e07f3dde75d0e6929f44c47c4d1eb113ae6605e7d0df5bfe35e6b92834909ea5f64b8dd5f34fde360f44ee2cd3e880d07e3096af4fb83e90dc650928e39a69d6e2fd5e106d37d3c21f465bb0de64bef954ac5b4adc36c30fe8d09195f4b0d5f91d0b10663854b265a54e6684b01e149c0c10184ffe85083f9d6f32fc813e75d4f3ad2601a17bdcc4b9ec3554783712c8993cb44cb9a1b9dc1d42d26754bbcd196630683ca9757fff94cee313bca604a2f222da7920e3298540e65c9dc741a135f0d19cce81883297fe7fc8d0e3118467aa83269fd92d8274e4247180c3aad5ce5e9f99ca7c4c376091dde9c86cee9c4afbf600af1966eeef582f9c58210e7e9a4527717ae7ab313b7994dad98756fd17392439570c1a44dc53cb1e537efa28e2d984d7afb8fa6e374bdd81d5a30ed7d527ae6dd3fef25c4fd7cc8143ab280a990915dead21d583049a635cf4787c99620918e2b98326d2d3ea592d74e4ac60b38389a091d5630763895b3b37c324c4a47158c1754c5d7621d543029c9566684c7765653c714f28ef3ba9495d7935f171f1374523ba460cab1f4e7a81b2a5ce88882c953b0f6b8b5143aa060b279cf13f3781e9c196a19e8788271942499605234f931718269b404dd1e4c124ac95213cc3177fceeb3f466e799604e41899730af6309e6a09e942cda2525983a9cb8f87e9a76720a868e249884db074f7e695b4307124c723577e4ee925462551b388e619254924cad9cfc388c61b439658276ecb929f972068e6298b4f4092bb292fbe89318a6f333311fbd3bfe05710cc334b7a2ba392ac9deaa55814318a693175efc4d46048e60184c67c9057d9ae3b10103d31d3c484be62f4ce2e2d45ce9926f2ff4456ec9526d56854b8b7535f2b282f7961ca6ed854910f9568fa6b2534962c3c6c13b0e5e98cf3e872d41d8c6077b17297da5dd84fd521766f99225d989df0825cec59ae95ad1dbce62c75b5792df4f52427333345c98bce5c63ea5a758da988170057de6020e8e5b984ee85293db45877f12c400872d4c7262e1653f6ab8273fd86c80848c88c858ae85b18332d554911686cff6ea9372b8291316c72c4c26e7e027a87d979c9418b42cccd9a4a44b905ac99249e2e00003472c4c9296539694c8115a270fffdc12b4b980031606a54774897657c2c0f10a634813b30f0e5798cbedb35af34c5a61bbdaca96b6654b75cf3ccdeece25439f943870b0c23c6641fbd4662e5c3ee158857176f4a792649978b104324315264d0f4ad250f231482e70a4c2b4ed39c4ce84bac0810aa307cf3ed87c00e21882d2672f23b951c34b1a9ec224dc67c9ab25c94a6599c2ec1dbef2f7e875f1bc14c6b724e50b7d5143059b145bae2eabb4bb751d9e3da51c1e85d1475b4e59beb38e2a0a8385eb9653ff3842612e25fe277f89bb711d14a6ca72594cbcb44bd18280e313be8f4eda4c505d042e9880274c27b7569cf8160c383a61545d51d144ab50ed45e0820924270cfa4d7bd28fe979d2de84f16af446b343cb9e18248980b489c80019b9a109639d0927c73b7b763765c22cfb6796e34149e0c084a95285ef94175fc2f4a6cade74ee78be74b1c16109c3e79874262c8921c408a4122629dbaf99f820b231250c26464428256f1abf4dc2b4b26179b115d44d2a09d348936276e364ad9f6bc9038e48987a2c6c54ec29c9b40477c00109c359b0923fc1843c1765d4a891c8e07884415e520aa9e15102b6906f157038c224267928f996653fe97cb089fc071a39333c6680848c74c88848238c96529ef49dd6f296bc0c3818611e7195f7d361fa10bc8d20c0c0071c8b30474fea55d7ab83072545184b94a45b4f8e2311a68c7867a87aa8572f5372124ed7111019365ec08108639e24fa872d9d43986a4457f024491bc29c946423f3528927d7c85108e37bed5a901bba73bf1c84307a0c7593d5ef20cce339f24e0ce5f971148449a92027ad053d9167b22aa881c7e1088479d4847eccabd97ad00d191f62839b0310a6d8d9d3e7fc273d6a7d8c1c16e4c7c8b1914118b9e4f80356b1ec6a634cbd5bb37cac73107e69a2fe317258c0c1f13132c2c1a17e305f366972beae9bc5f203ac0a93f6492ac928413ebfa930bd68115969c162afa830d8a7480b699652509ec2a0e24f990a52f468690a83dcc53b154f5c44a530e5ae20eaba39290c361f74bcaaae52988cc21cd6a669aa4b4461f435dd925e234a6a4928cc967368adb1d4410a0a63c5d2f1e3efafcaf409835dda2755923c6192c4c4ce897fe9a64e182f48d1b024cf0983c79ccfeb1f59d9df84b9a468116abce25aaf09d3499fb9519e4c18f6b26e9c7c4225318409839e494ae91111a7722e61b293827a7ac5e7e558c2e4392a8ad2719ea25c09838a0aafcb2f254c6fb735df97c4f1741226f5f9fbbe939230a53f490811ad2261bedd5d4be13a90308993d352546d8ab6ce23cc335b7ada29b47a92234caf7b4932370b96fd469894f849a8934c18614e25c9b13bd48a5ece224c175774db3d5bd88b220c625e45ceec449872927e6f225dc562441877f4ca2cff9e68da4318845736c9f7358441e4986c117ee9e52c84494a55c1e5469f1426218c6d3909ba94386f826c10a67c26eaa912cca365451026d94356c6c711e656098439fb870061b6bffe2cffa46256fe80074f1955dd0fe6fa792b15b7b5d3ee83c92e8e0e9bd712223e984e3a0be1fe274c76f760d029087562c57ac57a305f8b1e1d6b1ecc2e7ad4f9a5783099ec15bff6bb8339c7495a449576305556f2ce72296bb93a183fc95972796c74481042c8f3cf5acfc1f84969a8d5fa3442d57230c9a8feea8dd697521ccc696d9f544576551c0e462f9d4bd408bdc130ef234225533d4ae406937dbc29b96b0f0fb5c17019fa3f8657149d840d2651c7732baf3598048f3e7b6aab4e76d4605092da2739c53b7785d3609273eace1f677f5e643498a4554ad94f9413d9f1198c9fcc44d39db1190ca7f38c302fd9affc32984ed465a98eb7e1276430783c75d24236bb4dc6603a7949145d256230e5d35af154faf76c87c1204b67f3e3c67ad082c15c2697d2299a4c4ba25f30765730c94b5e30ca999e57f4e4ffc92e18ed635fb7ecc5ff920b66bf4ae2762ab7600e7d1d4a27cfa99d168ca2738e995fa7f27559308a2ad9e7795b2c9884efe027bbed15cc2ba78458e8c5d69315cc41df8ba59baa6052ded9d3b253d03351c19caaa37988eea8ea4fc1d427e90bbd242779232918ece4caae2ec9841645c1dc37faf7f6de2a28818271ee5a4d499a6ae9f20483bafaebce75ae753bc19c3c44aa5eba09a6607f1f3d9f5bd033138c6a32c593bb4b30d78f4a09a61b694a2979b424985c2d0927f2c63d8a86048392d23e8eeef079bd631877cf4ae82842c6620ca3afa969db8a613e41cd7897f061b4a6c43088948f32ae0dc36c5eda73ca63e8662a0c734e1f2b6997eb9e4a83612a532a0973a6835e1230cc298791a7f3bf308d6c8c7a2f6194ecfb829d7dcffff47b613ae195f37249d0d6f3c218ab5aa12e8db9eabb30fdffeb29f9dd924e7461d86ded24654f2ecc23f37bdbcbc4380f2e4c2a9e3b4e7dd2fa975b18fc527c7f4ecac2a5d8c264e94185c9762d4c6a61f2c930a13e575a18d552f6f924ad5baeb33096bc54a7722a0b9370e287fd598a99672c8cff7ad2c68dde37818559c5da4d5d2e5f613893f2f44f922b18b968af1ea65698479aa5ed9c7e84fbac3097a68a7dc719b39c5518ce7e2df4e4ad68a20a93a47167b25352618e514f4a33541843c9d36bda97baa44f915222fc047db229cc39a6e9f025a514e6901e3474b2f029aea4305f92426d864661364918dd1f11da9e8ac2dca33b4ee8bc1439148651da4de78a7cba101486731925cf7d8a957cc22016432825c7d013a68bdbc104a5c24e184fce29e72f53c1c7424e98e3bd5cbabf8739a54d98d5e4fd88993ea9e49a30c82bdd75c29e09839fbd27956f553909268c71a5f774782e618a919febbea48f53b184397e99c99dfaab84f9bfe4fcaf4b09f39e64e296ba9c525027618e3f5acdd3f98f1825615215524f4c353bc945c298f2a9c4c95d694d3b481854904f9d5a2b9cccee1126d5f93d7ad6aaef758e30e82c3541da9b6c5e5d234cf9f49274ae7e57d531c2e426a7ff1599351b5a84b9b2945cea278a307c8ad7e9de245549893056aa4ef52375748a0883c9a57b74c58a501ec2a0a4943f076d6782aa18c29c93bd5ab6ba10a6de17b724ec57384b7ff1c38330d7985c2e72bfab654198f2a4104a4e6a5a9403613e91f1e649de2f95058429f8772595a42f53d23f982d4992a0e412f4c48ffac1bcf9bb21d45c929df6c124d42939db9412cc523e184f8af638a54a1247b907a3a9f423dea2be45d3f460ce4fb597d3cc83d96d47b6bec9fb56e2c16079754dafab787f7607934ecfad346d3da9991d0c3bea92f4252c8a685607835a4bb24952d492974407c3bb9fa0533acdc1242e49a1bfe29efe921ccc9adb41e9949224d57130557b9f54c2c16469fed2c28434417f83e164f9fc2eba1bcc7e552a4ec5d0d1416d30578d8e2174101b8c57733d622d073fd11a8c9f4b2853f293ba6851832997905d49fec84a953498b37db455caa2c19c4cea6798588207f70c86ef4a65a7543318e4fa49eb6b49977019cce9aa372cc5899592c1fcd94c6754f2b4dc8dc1a0c45f0c79366bb75589c1247f0c154d8979f5255961307dca5652f894625fcad7402f984281c1b836d23a97dcbf605dc6bbbd785ac8c5eaf86c51f289ac4982ffc1565e30499d8332fb924baeb5aa0be6926925593e8f6122545c48aaa7aff821ac2d187dc6e74eb9a985bdb2775da6c6abd7ebc5d3f569a9da56160c2346794ef9ce55410d3c6a5058306a48efe05a5fc154826acddeee18ff90154c67294ec6e560150cead57639c2945052904851c114dac376baf2d3693b3505c35eae79de393fd8a4600e1b65926c4af688b61f6c782021231905c3b5883a412e37c6836c302828982bcd9224c90a5a6e4e912718f54a3a4ff2f555e96bd47082b93dc6a68d58dca86e06d504c3253968cb6ef557ca144131c19cf27a4b92a4be874e9ba09690a4c52a2c2518c3ed7c734dbe888e6f25c1dce7a6e494d57bb70b09c66cebb174c24d10fa3e3b86495909fd1e5c94bc26c518265982bef0dd9e2d86717b33440cd3a9ecbadb6d7ec8304cc2e30459329e1fc46cc230ca49f15db9aea4124f1f6c3ed0609843094af283add3c88d326098e4fb13c424fffc5c930fb682444066e42f8ca162414bd6764b612e41872f4c62e1b405ab245b762fcc76268a67bf796198bdb45f8613afe3a81ecf56124901e2217d1684ec5bd73ed8fcef5806780793a4bc4a76bcdbb8bc201a37928709d00ee6b0aff77bb2223550d7c1b0196332342f1b403a982dcc7f4e594bbe2e3938076349fa4e89e19e1fb3837230d7971c95837e1de360f0a482d041ac2465c3c124a527a9f49c341584928d1a6f307792f44dc5995cd51c037483418f29a57298c90949aa65886d305b3ca9233f55de6ba0e7410d1fd0280107870818816c30aa992c39467e7efa6b0d66d39ef4a8c5a806b34793b55cb2c8fe5b300d064fdafddf4af2d1602a9db486e797eeec9c3318deabeae7e33efea56630088f23f65bff83924f1a60194cdadc73d6de3edd514206939ce3def3d7640ca69cf3c8d28ffd95223198fc3b89bde25fa353270c0659420a06a3b598b0a1bfc42f98dd84aa28fb9f7126e805c3c45ec3c552b06daf14b4546fb6dc74494bd205f3dcbbe9d91dd11d6711b151c3a3900b66ad68d5237fadde1d8d238305885b3076f05379f1839ed3e1c7a1164c999697fb3e6300b39055d66856cc5bbaac7026a79cdf4b59c482c9d34e094ab89c2b18be6409bae72dade2bf080216a040042e98809a26d00a7cb977785d6d886c85d1258daead94c42a2c77a544f9091da9603cf9ea94ec789eaf94e0140c4add892daae45c4b270d11d0484f402998c410b95ee27a2b58ea83cd460d8f432460140c3aacadb3cefbc14603a1602aebf58a71293f07133ec1d8714d977f3d22726494ed009d60fc142cdef67e12bd0f0ac22698ca2413cd32edb4ad432f036482410997bd8ff9f62f7a1f888c849c11912598367dafa42474b76cab1aa804a3f89f244c8c8b93ea43a3c68c1935108660124cea5795df7f1dd038211e6a238804e3a86579f224aaaeab814064bc40051c1cf908448688d9cb4812101a770c63a95c6277d06fd501a146e2c103746424e5658c243744560535f0a8c0196359ed12844a7e128262182fdd9e891a9d2742ec32561084238651de2c98ba4f0dc33c6362c9134bc7eca84e1826614de5557ad4b5985f304c96f2626aa2c5c1c1c131850386490e7d27c99b17edabe417c6fc53dbf9a2993b8dbb3ab116eff255d3f13b7e9e9ccdbb5e1876adde4b12345e183cfdbbdededc85712d49e275094a5d1894d29f4d10a57d827a2f1706d5d034b58f7f4a3e8b0b732c3ff1e27b720b83e7ac7897bee6399dd8c2942eeff5d80515cb502d38d178f9acbaa07a5bb93c7a4f0be3e7f50adb4fa5fe4274b859983edb28397d9770b230f889a6ae73f0b8658a85d1cf47ffc84fbb9d5f5898ce4407b7bbec15461bebca15a6f30da9a2f7d30ac42e2cca8ce5504b277a95b4b2fbb7362b0cb282ca13254fee746815d65636bb9b4ba3f19d756fe1ec842c13cfa20a838c097f429c1c97479c0ae3792a49e7d36e264e438559dcae4b924c7a8a4259123f5318ecb7cc47c7afc4b74b61ce65ee152f67cb49795214b5bb3aee2bd7a76dad658892b398d5a33075da553979432913af1385295dd8863eb19d47edd9830b85c153570afaea2bea754440e1ba5b8a5a49ad4f982fe9bf699fd21f4dda13065b3dbd2b4149272849d7097357b8a09fa4ead239bf5e384e18d46bc7b84ba10f3619c7ecdce881b94d98d2b7a5aa9cdb7388f35d5034613aadfde741492f13c6aaffa417ec04214a4988e1e038210f72983098a5d56c0f51f273b62f61f4b10f4afea44a091a42219c258c9f4227259c9c4feeb815e12a61f8b060a9c3dc892fdaad0a6ae06183a384a9848659c951547616651c25dc24cc2f4ac57512164a9c931cc249c26c52c5933f6ab288ae46b848942b5cca974ebcc141c2a04baf07a5fd5dcdef7b8441eb99de7aadf4f88f239284d59ff8a88db811e5fa12b9ee107b7b59cd3ce11a77a58212de3946982ddde52aed27d494d8224c9e54f80a72564ded451179892faac43bf3126152feaf972daf890afa21c2f425871357f62fa9b97708c355db7fceb69e214cdb77ba5784acfcd10049f60a61ec38d144b124e57ac9470873e6b789bf3519fbfe06612e3be526990ef5d3f69f208c26331fd5e402618e3ab25f8210f5b59d0e0e10a9a552b1512bfb0aed4e69f13c07d3ae87bc0e745043c62d10ee0f6ec8b9bff6d12b91c1f9c1fce147e4ca8c2cf1751f148bb714626e25df16d54a506a3c4e759658f1f8603c133de588aab894f3116e0fa63ad94ecb98d827057170e8800648c88888c78dd343fa29492d0b7342270f57b2bf92d5925f94c6e121edb157d24299279d34031b77874cc35a5ebbb3dae325b19e46bff4ea23221e36446670eee0ec603ab7ec7152086509ae0ee6ea4f82b613db5309277430aa7b8e65793ee751627330b7c94a9e2773474c2707f3f625f18adac5c1249ed2d5c1c1e849e9e93149c91b4c49861026ba17dd3de706838652fa96a349137f5d1bcef8545bb939ab4b66d244ad6851d7e4d860165929cec7d21a8c76764950693f4689abc1a4be453c76c54e17940663ef2549c9374283a99327c9c4efcae549ce603a25bdc74fd946891e3318e647c7d22e6530ec08919e7193c1d41a97b28dc1fc39aec991264a3ea518ccd7e361e6bc8292c63018cdb4fcdb6779972e81c1204dbb2f1844ebe44ef67bc1b027ac855c3bbfbb0b26a5d484e90b71c11cd482103b275b309d64668210134f44470b06b9962a3c56b260d0f9d35b5d8d05a3e737256cc65730fa5bba7072ba24955ac124447a6ead4fd258f2ab60ec92fa79a194b426f6543048b91735da340583b070c19485f2517a2918f6635ce7fb5130eaa8d86f224ab47728983ba885dc18f917fd09269d24bd52b932490e39c1e8490aed67254d304965951e6b4cae799860104ab4a52429f7a02f4b30a7a6aaf6f77c4a3089b85df83709a696d71a1d91f2be1e124c49990996963c865174cc536951725bd08d61b494214edbb675febc18c613f6c4b3b29c180635af1794cacfdaf961182f9a7af219cf345d18c633f5f3e8a76098ff656c642bc030a8feeb906f3a55aafcc214dae49c4bb6a074dabe30a8d0e14e16a1f2a8b917c68bd6a2836b658d14f1c2a4ca42dd785dca268a7661146149ed6249b2a514e9c26c99a7848b622ecce19df359958f38e1c2b4fde14d56ad3a93bb85592dd693203d4e3c6d618e596257b9a96ecd502d4cf5be7ae2b9cc2749b4302729c5ca836cd7d02c4ce13c78ee139dbd735998449ed83f268a85295b49f23142b030456dcf35695ea94a5e6110aad54a1631492eed0a63bf9eacdeba15464be993480baf92c40a838725f511b29d6f5e85b184d01f5a7c76b316d8ba80d905eeea22f0a103101a17280e5e0ea34001021084e402fe22228f6a84d858000046446eb8800001781d9c1b3aa0716420400022218f6e788d101b09880100020000000040000b00000000a080f71a342e20d2ef35683480012b0262e32c20008900808848c24100019c91e31c0508c019395e2384e300001800041210f21e0c0040001e708020d8e005828cf800c477812023323e448e87470252173666a05c24af3123152071615cf53c13363d086dd25b00e1cc4804485b1c206b812023211f22323c3c1290b430c5afe8a973bdb3307d2749127a61f2a7bd6561aa3e0b2ab7eccd95508d19337e06cff8199bb1c8aa64f9bb1cb38166fc8c9df1335013168906c88c54807c050d90198900e90a933039efe53fce2e5e6346fa1839337203d90a0419f11a336e7878242059617259339959b26398f083fbecc69987bf1040641c10f436448a8dd00041111a202332406e241e212dc8558cbcc88c9f61805485d798910890a94840383352011215083222e3c60c0f0f04e4294c996b623fc8c714e67eef20634adaf81db314064f5be2fdb32895a693a41885716c8ac2943a7b10f2f4e85bf364288c9e2dc8bbd7b7fe17144611622354e446e315e4274cd2579f9f245e8ff484493095bf53fc2a41cb101b40f84e9892bc8e4f52d65f7a13274cbda6b43de8b44f3a64426ec2e8a62b3c090fa6e2b7d4c4952aa558a92e7d651bcd36794866c258f964dc73ce0942942270c1041213e664b2c7eedb4a55335ec264f52727597c63d52a6909e3765ae94f59a98451fdc4932ecdfd9f7a29611c4fd13357ee244c954f98d60f3296ed9384e1d35c0a3e62dedeb48c44f2f5ae2297d6522be4b28dd07ba71adad9848439bfcfc4550ba33dc224687a16154a6bfae97070a4238c955f6ed1ac64bb20fe60f3b05163cf04d908f3a8df761125c60873caa8ee9ce453d7cf2fc2f4ab57823a71f74b5a146192f784caa23d904c8449ae54ce9ba1eb732d8c08539e7ac9a3a64b16cfa143983aa7e792f25677bc3b6908e30533adabb2562a540883ca1bdba5122184f94bdb9b0e2ad7c120cc7f5549befdb5fc1c09c270f2e93022fe48e3200361d6bea43b7a98db5c8e0e006170cb5b51a2a585ca655243860e3c465ea4d81f8c6392e82728a52ecc637e309ca04a92f298c925057d1fccf94f2a4916bdd926a7201f4c3a66265f540e22eb0f6a84e8600fe6dd3693c3789204bbfd834d0fa62ccb97041f59e447b4069907a3c55392924eec9c04b58c0fc183c984efb2587ba972971b7807b358e9854e2e27fd441a20891d4c1e4caf6ea7e8880f505a1d4c9220ed9467e9d7ce061d0c7736d25b3cb6ce723f0793a404d9498e92fb602b72304949dd79cebb566a486226838c8371b6f333c7a46839367e4424657088c68d942a483898f20815d3c193f20daaa8979c87a757fecfbd56da04991d0e0e37184bca33f7bf62b6e19465477f967daacc68241b4cd963625e0304366c9c909155410d3c6e906b3065c4c2dd67cc254ba5de56f24dd492d4e239483598d693f89877d134985d3cefa9ac9dd4c8190da60c75722c693c4b797f06c37a9c1394ecde0cc63329995c921cce949c530673573ce9697d2383612ffcd8889534f623442e30432167390663f52561f721db90111b2906d3e7effc24491eb530988338c94b54f23269a604832673c962ddad5adec5ed5a56944eda7112e4174c3254099e37cdf482c13bb848f5f4c92e98db243769bba28dff13a8a0061e2e482e98542c754bc7452fc73fd86cac6dc1a467ba3d94ec95fd93c8d91101c1d4827188c0059d055350c2633cde050ba6a454a509f369547f2eaf60b090a3f27cc765f52aad6036314ae6b3831ac82a982ca6c71331f53962ccc54052a1b94aa55ea72d6259ede5144ca17aefe24af61c264f2998af3ca5d352b28a783ca360ce3115fcb2bb75be100aa69caff760a97c82c9478967f1b2934e30951244454d5213d7441f6c2320890648c88708da07b209869393eeb3133da88f950e24138ceb2979c96df15c82179eabe7243d359e0f360fa3412ac1e8a59f6295ee83d13c904930894b7fba6e3a5b094a9e4622c1bc2545ff1e773fd8f818a61b517aa55f9d52461f6c42e8c17b84241e232033decc18e6fb64d9d1c35a9f30fee0699462a0426c8ab4301a0f6880bc203d80c43056ffc9e1243fdb123e1f6cc330d8a7cf262bba73e3000ac3a43fa4cbfb97efbafec1e6f13e001939323ea42e18d6ac59b433710fb54ad1732895a4e4e2df0829d6c030f8a8784e2a5b481715d1b8210210fcc2e47174cae7259ed2657c726478388d2f6f230830f819274404692f03847d61d269eb63ae27a47fb017869357c4ecaeafa8517961be8ba6d2a9b57117a6feb292e2093a8a757461182594b01667920bd3769f24fb4fb034b9425c187c2feba756d589ad6ec1c69bab5a0adfa51dd75ef25e50137eb0d53822201c1c323e3928e0e07819203666d4401c1c2c40c1a22d0ce22bf7c2dce42446570bc3d5c591abf849848ed1c29cfec4ec5de66fba6916e6184b4a897f0e29a69785399824a8fc21be047142b1306a7ce509164d4ed21f58184c5578b4e4c19368e32b4c9dd4ce6d4cec0a53692dc93636bcdeeeda56186dacc489134dada88f082b4c925ab14f4ae9e0a75d5c853989e96c71848926070668daa80a9368e6a2ae63d2dabfa9309992b257befc537e212acc16a746cfe675d78d33c0536061561d6fb39d366aa77da0298cfd5be779f6a4507e82a530b577f04b625ddf4ea79014e620c65198c3a5d47759944461300bf5ef24e93db92ca130697a0963a269e75853c9fdb48f50e169003ff103f44410b013e911a7656aefe43349c809c3b48285db8ba5ec953a4775f1e0260cdb167af5834c13c6ad784947e4a5f5e83013869d5382dcd88c983086502a7e5749f2323b9730ffecd58e7f32a5e7ab21a3aca025ccf296e3f2f9c70e0faa0496bf4af5c196dc10c112e880064872e3053b05a484d1eca4a9f211da4eca380993b0f8d1c46e64953c3ed0c899718792304d89956a374c3ed8426c00e13d641c1f7c62244c76af5fb515e4724148ce1ed1382322344a900609b369114ff3a21f7e1f3e42b170e9dd6d2e887a59c54fb2ffef9ca90374844168d7b21cd637c274c19364d7297950254698e452be2df33f96b4651186932d5b8a3028d3237af47789308baa8b735751829039228c95e4bcfc5582fcf2d0210cbab492fca8723f416c08a3aa0725cb4e8cebfd0a6174cfa1795a374218439ab60f2dd5208ecfda56595171d30ad71022ae65dcca4f4198468bd82dd11d1716d141c8f1b00b84c1646a494a673f893c68dc08f1a8e108085332d1d3abb5ac05ff60fc2d495e4ada154ae68a7e30e9132e9fe8174fa50941807d30f6de9d9ede356444d33c66dca071c807f3788f2511d332a2f4b80783b0b21cc6b41bf22325b002eac1246fa851f2a8fc1425f6c126e2a1c6031a373e46540598878951a8642c858342d27030168c44025130105236cb0100e312080020601c8ac502c2509ecab9aa071400037044263e3c242220180705428138200a06c3602028100604018140301004851e12b5466aec6dd202d642c7d1043a3303790457422b52b1483765c50ec603e390758149ef1a47ccd51a256ad7a2e30d23725ebd5c390a40dda5cabbb9717f88fa83d8b896bc0928eac4fa97dd102efab30dfb58fa3bded1b9d10e8546d76a118e1c7db2e8731f9f7d1d4d1ccaa4110e4e65f18d10bd829455e32ffae1b30f91a700bc1ca8a529d8343d6a8c7151890a723baa76852b405a0c5136421b3b0652fa88f6dfc192d60c7b43bffdebbab681df7f89f73aea123ce3b6576a6073f0fc6250e4f2cb84ccc00f8aced3ac643766987c360ba84482a1b0f5c1aa89f0bf35e10272e09a962168652212740c44ce96238fbd12658eda8941beef21a08cd2a481450f73a0818bc4417dd0255067cf95fa01007124cea80f88831e7652235a0a50afb1d7e731cf341e044ae71b71316280a05e5c349e3fa2d75c7f7a3f2d8db63885800b15fbad77d2f0671046049a64c10145aa098011d16299e65000abd35f9542496231ea457845d2a067cad9e46a030ccbbbb8ed374887fe5c083bd8fc8e6cc4ebccc5cb17862b9da243a72582a8118a599d8b36ba711a87d89898b4002a655995ca2be404f2a23bdf430e88093b2a0690be06977f38b56e30d0fab96c7ea6cc82c62cff16440f2171544c2d025c61f1925ad00fb72917faa9302493f43b14ee2a6f4e310a7bbcef9458daa0e7863a7c43752e0fe720907224f53870c716e0a3225ffb68efb1cd035717e80f26283493f19f6d3b39b8414abe59f047bc24ef9d6d088edc2de906c7843cdd04319a535bfd982e4459fd26ca2b9b1228e28e41497eeedbaf08a387f5a87a5e51bfa44cb2b9465972c196215d675b0f2e6260dcd11756f37f755e2c86b95d9a01d8102b782d16da29d22b8ae7b4e6f8883ad1f322e26fa57c80bcae8b747476d411b7adac62c233c2619ed943a4b876ea232ad1861471462392ef62494bf68e048d312d54568079b57794281c91f827e3c0a18f157c5b0e1c65d24a58a827e17c1a5378c930ffd84f3238cd837c11b905fcd88eb61f864b6079f339f45faddd987b29f6f8072eaaec4800ddb11689b7ecd78fb5204973113050a121e0fac233b40a106490989d6fff2515f0da8486682c5b705cf471df6279ec4965f206ca8896e32b9c34d589a659f18aa264672d106e0e19b5d3b1c66f6390c4519a8cd6d17f805b2f8677b6a7a8bb647356f9bf3effacac6914789ef01a39d5d775d634ec2c8921437e56501c775ea6f98e4ef902b0adf5ee88f9aa49c5f7010e6b16e6efebe134896b0c5d3c43afdfc09ae7f0588987216b055276cabb102522c103b7badfc9c42befd4e2deef1f42548e55c7de9985927e5a47be46ce1ddece4bd331a29b5a9d035deab7216ddc26c9261fe9920ea204740d41889a950cc5561d63b0070a4f8848e66172d69fb4f4a9ef5ad9a29ac9fb3ac05e14932e6ecae241ff586980da5d5d4a733f2f2a57d089ef538953137aa0883dbbd042f48f1de869728dcb57129f478199153eb1a7fd9760b4d585c6c808a537107d605516310bec2e7388d483190439d129e099ad6b04d5548a42ce1982aba0223972b107efb78e2568732db94ee9bee544b10f31adb6b1f9d8867460ac1bb5c450e40a5985c0c316ff80aa34138a361bfb291b466255a3210c72f296335e37c5073a314e7775f146eaf05ee224dc4393f28a7e8ca114448643bc0083899467574d3303e26854d5aa238a4786530804fb26961674882cf1a3f164063260b83eaa32e434b781cfd15f4a93c3bf8358b63dee87d00e9828574a92a5d539b45c37f24bd7a8c5d683da32b2f45e6859b6bca23221cb529d871ad042bda4cadf28ea4ba5bf07521e0cb0089868bbac1be6d46010ff54b331c81164211d7baa4711a898b9b49a27257f7e4017886c659e9aa19e4f9bc9bdda25c66d0fecde1dd224c7b9eab77faf1fc35dddf7aac41fb3b8102a30b9442c76cb10dd6b614bf1e3e692041d902e9b4e035a1d82665a484b778bd1bbebfb73897f9e8e0f23423eacc2d750314760364849fc49bfe1496c7f5671daec006a8f4b8305a75c00b937b20c78b4a94605cb0ae9324c8529847636b603f7ad512d5e02c681621a52f767082127b737acebb3b5b11fd93d8039027f1a47afda0ee071645304ce4bd77e458ee384159e6b5b587efef9df2066adc1e2932155486c8ab88e3a39f5616307c6ecaa44b573ebc61c2081d89d8428e997d17d016ef43d30b788b71182ab1e471588c1cced97288a4640029a6f51a74a070490db2c59403c751c0aafbe6102aa00dc361b6bdec27aee50f94a7be8695ece5ec5dcb98a80a8e18d4dd63c68bfd2bc9e14d42ff2ceefa39cef1e11084f0f1a99495a9d4c9f412748abb5e2fd1e0f45c9bd0608118b0a4460278b3723903530dd94d965419d70ee3a751e3e04a9e15008f8e51223fd921cd8a55f2ca5702231a0f64920d6b8679e79eb2c9ae29045d1bb37d4905abbafa59cbd9c5b197790ba6fce4a497be58a6a2f7a1cf12591b29226dfb22d392429adb9abc66b59f03ee64fd6d081af2197e63f971f24841804c03b129090293e14dc23bdf2f50bcd29f3e25e29e31f0b9ea46ef955319c7d788e140e97cca1ad466a0785adee2a000653f7b5b09b13463c8368dcefebbb4e761c2941bf36b6dc215e57731fd5c6633297dd722db240eacc4c98632c89e6a23d3e925db23fa3c0d4b176cd1cbc307bd7b22f6081302011e1eed53f45c63cd3d845e220701a9600d2670f78b802e9fc65c602827741b57fa7653382338de1e8f7d485d20a0bcc46f2472c9fa2bebf602c1e04ccc41210268a32699e42e3b906e50bcfba8837ca9235d17958ce868c2615f02ef8e811ae58cd7ddb196f550b521519713a543e5f8a62024e558258d37082aa6938d7e701d01b83d938945a8c84b4d1cd167b00ba7dd9edfe07c50e653f2844167133d42fd970dfacb889930514cc0581bc948ef89fcf0bd66b7968021791b468518ab65870c72a1ba2ce420d3bb897c5a55d275278d0fa7f67e638e742e83e872b2302b4b5f7bbe0a70d290cac1ac56c52d24fe589f3e1d3afcb49127ddae7004546bff062c62ca2dd8885450a3219cfd50fd84e4da6fe0fac56bcf6f9c6bcf64278b465a209adfed5c614a057ededcb7bff2c0088965aa85afcfc4d04273366da26af227c9971d84092bebdbffd14a074121d1ba617910216d1bf8edb6371c10b601340450413381867dc080cc733a50fffefebfafb7f80b51b00fdb7cdd170e0f13237e0679bac452723101dc83234b5bb1196f5165dc2aabc208994c186295fbf700b7d18e255965977a86f0eef1a2fca416265cdc114473f318f992231e0982fc26a1f35c3f8f7f5a43736b3dbddb3d1056673b6da7f83a3b609a38a86d8abb86a566bdda4413ffadaa1d6551aa6a0317812f3fc1c7769a3b8417da10e8a08cad44af99a7a8b45329702d65d234dc41b0d419d5e12c5abbb88b95e9f8161b439610ea18a474f62c40284c81380bcdf8c038a89ae78c34098f036c5047fa69d3dd1b878c53dfe695b5f8864d32d390a5d7ac2f38cb4d087e5984056ea6afcfa9ee0234576a10a784af94d92fdc6f56543da896a18e9d798f36a8506ea22f48f3eb5c22052a76de41527395f18b92e0b41ce1e4f41026061715a73fd0ec0eed3ffc31e1fc66525340bae443a40628a57f349ed51beda8ae2359c5f76249fe3f9f8429858fa18208315a9a298333d2380014232466f68e3d545191ccb7ab62e2a2984d5bc7ea28cf47c366a6218b6e0a8f67ec25ed86fb8b580b2cc49d5d9fa7647a0ad14fdb25e430260a5c28218c6e48745e3c70a16aacb1e4f074503a254cceb27b9cc0fe3cc191a1c72d1771522c51ec0a18d9cd7b6d9991028474346be9c31ccc2cf988bd23c0ee7c4cc1f4f6ab499061df36abf8824cd42b292014f9be97db9a9331eeef10ee40cc8561cfcbe8e4d364cf0cd954ff8d7ef613bb4be68fc195bc2ef2477dc962cc25551bb74b22d0477100727b0964eb70bbc1ac0baae647db86ddc9b4ab7cc4d0e12f150e764f73ab808b767bf438e3a5d42f9fa298dc7abaa9639303301689d189b64235f0eb415965d1ab3d466d6c6d1b07d4baf6075793f83dc77e41e28cfb80885827f8a506d32265c5918d036c3b23798a08f4b8268671e8435a73d7d9b09eaf040dba1459bb5d3375286baa334784e1864f848bcaeaca8607d2d18571c48938125c65eb8dfec2fd33904df9d443641eea5c250f71caddca2882c2824851a965b2751e525cef9efdddc11c54158d7dcf74f83f29fd681d17852d9d57f3948befa84fbec052cfb8109234973ca65d8d9e6bcef4f9854e5803b3aba0f2e8abad37568cc5464bde14ca3c5e76839ae278c151449cf0bb104080756b907292fe9ca1f72a631ad64e02e3066e31aa73d1b795b1c0ac4939a7314e94416dad496d5fb159fe49f9a1e5eb428c573b8aa35b47385f21441503d741872fd1730b2cbe066093f3d511c0dbe264f381361cb6f93e62da6e55c6445320de65a10040c8cfba98f908752c813f049a27450466a0517d21ce714b064cff6992cb05c330ad441f9b7862a904439ac5ecbc22b8e72375c6fe8d7c955391cdb338bff23904a681f7176fc53f05aaa07bf301b23ee5bbae34a9354369c25aafb7a39a7767509f09f5a364eb3ef5918d91ec179b9b594392f67a8fa435de527bc133ad6b489b2cc523252e7101ef287191f1f1b9854e4d2b16034c533418f616e6c3a96d426c1e246a90e70b8edb2bcd8e6e21fba0dbf250ba4d465dc788d6b750c00cbfba7e2477bf29fc19015786a729b9e3058c35d4a87b0236f7c74341f05766b4a6c51b7f990ce6736332bb7fa53b9e2882e5ee4ed3cdd0379cc9711b47fb751849d1f75f8bc1b060da4c1fadce78177c2f5c4daa6ff1c6d23eb9d9aa9b6e71369f96e243fb31702478756c77a7bebb81d6d48d478dcc6f9b44f43f9b8f29626f7d994b2fbfdb7c55d4f231dda56dcb0a04bfae52a949e6222815543d7c8a555e8241b5bb7173f9dadb2bf16c6d359273bda357b1ad603dc39140a0c02de290e0ab967f7d379854037bb712fee010c1b6e6b09d8e791fa9f9cb3bc5e60486070ab8986d4b3c480dde0e139601c63c3db6e80381975661f74cf32d8af7b38a2c15aa16710675ffa66547743fedadcdad5306fd14dd7813f249c0ecd44af802d327629e3f81be6b1e6010e004cf56a4faf4880a5e88267aa3a6143b4023ba0ed8f2d6bae6b3be370c09e5b89816926aa207db24e6c87cc073a506722c5a2d12d164a4fc0b025df68d9a52341d4142cf498c1505cbd386eea446a0dc7d8882e70b3adaac404fb45050d1eafc23d4576b21188fec0d7a55ccfc5c0b11104abb3bef167a6e10db4168d7bcd61d62c2df5106c33f57ce2cd51f9c26a0010e084986ec05be05b01c67d8e24fa75201be42b8ad29d07e5531891819b74cb2e78aa619632a0569c3b08210530dca661705a0b594a0f7b21cf172b7ae737aa68d87521b74265216d15274714d8592723b617e08ac5b7080ff010818583fa4619a6327d301130e484633028ec95fdb9c1e4906da575af185d373bdb36a85b4544b350418102531123f04398b8476dea4aa34d8c916dc5766f20dc60760d6fa2cd0aa41b9241ce69291ae44cb7ff4c2570c5b68a30001c1dd0fa442f211b1033453a6a1d3dde5b2298cc981ef9156de0659c54ab5fe378236de6c201e347b168210166de6508e669563220711a4702d104c0553efbb62bed3c724155ee6b7833365d579124d058ef11269c5f0f9eeee6ee19ed91bd1bd456d8ee55ef52f6673fc3bdc53f5d15d61e617c144c35d3752333791a4327592d30f0bb0da70f402e6e2790a3fbf842fdaf8da4b15611049a6b1f4a10f5446041f13edc03c0ffdb413a5ffde64d99e149fd68d044bc15b2c549f6ca4ebb5a80e1a4f9123eee4d989aaf079c113557af7f72a2af066bb5f58bb64e75792814005e2040221f6560e26ba7f67e8f86d98916a0442bc6a16464423454616b05976c11325b58510740ab5b4e222030173489cd49ebc29db438166d9a4dc29c8dd41a9c909833e22a67e56fdd3ea909ec3134f92edd68f6fa546e32e9803d93be02884671a1bea34854efde78ec6ecbc2487b5e2e6e48ab7731f0463cebb7bb9f6705b7651e911ab10027e22a19c9dfa324bf79f58f9550a965bda60f8fa030a6b25d7066f22df78ab82c01273d5ef08059ac93da83b522fc007601385e234f9207022f1bf4d96ca922b3d0fbf056b0aa5917e05a89962c79944fa505135c0339cb9c0a628096b65abb3fe79304a144fb142a8e0eddbbd63220c0f5420e1245927826e4124f7c2df0c75c9d6bc512680fb4bb91bdb0ef343ca5ff242152ab8f9c9d92af3b9913036e290d36d60ed53254d15ab979b5a2069914a0bc11fae5477c133419a3f144dd01704a3e7394026ec52d8f0dac21633949149988a4313cf04aa59ab45a70e03cbd269034fa25297cd3f4cf0185e94cf02cf8def2d91642c560bb061377f9ac56471eb2e908e0717acf103b1987701ba470dcba4c3147be0a26cc1915c99eefc75186ff1252abec10e15a52c67179ec0a4ba586ea31b0642303260191e6e2d1d19deda9d37e4cacde5ca9526068caaf02b6233613aa1d21b1473d9ce2e5d95ce5fae69681356248550d7eb45ce136b8028cbe1df541cad7953c25b197ed3adf32d3f9dd3442552f7767fc5c25f445c1a7ea7461007a2e3c30d76123307ac379982cd193ec6cb1d2a109ec509f0588a1b70a4fb0def95285a750f55987fd7460a90a46fc30c4dce33ff4b000f0e63bb1298c4681830dcdaf23c8a67db30e028f07db816a5ea9606b94f235838a5aed97c9fd322b9d37e887aa7596c4d79b6c5ef6715f28c44bbc4abfa191fed1d3e4527f0e0ca7865daba8858234f57140e3cb1e754f5c78370c587dff4c6e5d8f2418142ab553573f18979b05f95c489377cfa275a50118d20c6a33bcb519bb26b5e2c495b9a3a6749b6b4f921db042f860729ba77af44107b6a1d676c4a7c99b86565d489f1b302186761f4bbb386773373ad5839b53dc23655a6bb4d20cdce7d4dd7c0ab8e90211b3cc0cf6a2308ebfc83fc0c1e834d6dde20581862b576c5179b4ecb5f6a04a3f784bb73543a44b59408ebdc6a1ac8031710cf5c31171493c14ac4c3f9abb283cadb04a5adafc9bedd363e5fbeba67692caa69abe260c0d791cfa6d37e1055381dec62006db84d9f47421ef5e7268e5f2a85b3cded468531775db7d6c33763add8c7f2b89682527c30070e44b4d54102b476757c037d26d21613222918291bd762089bfe886d8172676723bd8c176f67a69663d59d06d2ca0babc54bcabf570a740b848f8987c959eed4a236bbc73ab7ca7138cd3962e80565749aff806cebe8bb2168c44b8d2d3c1c2c7fe26078956a5e887925ec48289f394a19ad789321abd6205972db2e6144671c9ad757a31db7fd97091db7e875762db7c01b601e4862242eea8ba4d980107701b795616499789361055ea7da88d4b1ff4c0455740b9cb2290e2d6c69268f7596bd5889b86f0b92823b0e43c4955236ef6f26c6dcd4adb9917de652216aa62b551a55af86dab96f4263fed1278db316990c2ffc914b2fe6f424254cfde29d37699582021d6708ff2ab76f21ed6a39b13054b6b3fa60e9ed2b681b050dd4b0af3efaa0ec45a912e549b4b682d289b1df507464d66ff2ba8f2e45e9d3bb6ec63c10fbd82dd9a6e49c945233587e82da87eaaee4490390263344fa1660982dc1959f12574ffde37845983cca07922fc0f75319ab217e664ebdc79b194e02ac958ab816519587e1acca4fecb0bd963aba110cc0a754e3b1413448113ce9cc9dec2b2c9880ab7fb3fc4098a8d445926786c6669651713894f8ba69b0698d636d372b785030a76773c2ba0a11dadacb9cc33f18f76e9134898c88705e007f12d45c3f696de4481a13c4486fcd568b47437371ceaeb5c3bbb211b4f9aaef4c1e3a0f96683ef45aec90f51bf23e2dc2dfb9ed2d2cb7fb80c7520b9dca4032174de30e58bc7f9d9eaf40e869664e87d170c6953c16d12b4758d79324d47c9c34f97aad2aae7eb61c798f9af34c2b96625e0536c5b0931fdceedec387b929fb164a0d5715f16f3d2de3bdd5f7d282223f65dc46e093632d9e08cedb10ed5d3554c263aecdea0540a43a5d83c29314bad4497d3e7f1c1edde729ec51d853d29c15977801fd36064ec15ac8fbbd59c7114f4dde54835c9ecedef3255a250cbbbbcc45151c92a692a04141c321052343bd200faea101ee8c72d9e605146904c45061c7a3acf1d34627048a871c6deba7dd1a8e9801162eb2b0540608481cb0e7bee262d2b89f93e462ce078802405eb63f2695d4edc9d3cd5c02a22b6c7bd95d37407b29e2a113da40679f1304aaa5ca92aec08acdc985f77f232a0b0f9de32c0fed3bb13b859e65913248b6bc2b71c186f5ac48375fd05bf7f45ceece8ca65b4ce327e75264d1d12486b1b6e709726058d8a0068a1107ac1e01a1b6cba60b1ca0054543059e9b5da1ba58686329ec3245355cf99e2d04e27d07f2446e8fab54a0ee41cd453004aae1b66c5d69e7a2a8a9f87dd546f7321d027a2c7aa358d0405fec0eb110622567ce455278607f05c1b032c04f3f1220deb220e2fd5f5552769bcde517a988ce1354fc0425935c8d220ea3c853bef1109994bebc66f2c26c29614bd832f0d890382d35814224e0c173d2e83b37cc3d8c8779e6a272c324ff5297d09e2f234b3f3c256219690a43d4c2d585a94460249e5c0c09d9448e457cdd9921bcef82f63ff8c0c8ca4b410345011c01f28db20a6d39865840299fa90757222b5f0da0331ec443241d43b115a342ef2a6c5c3395ee0b243d2bb52c3b9a82fda19439d382d48894b31eb3a24fa1cd85f42983a9c3dc992a52230a8d91e0e4badd33f5fe7471d01ba4b7ae89b7ee66e220b4288403e51640a34a984a3367834bb2ac8db6bce2341b144808087bf0cc3de51b9b0abb71f75d34f1b124c4a6b5b1c95897b20d828842804c2141cda623d4a2ca134e219d7dfd0f2754e25900a3dd8a30f1fdc87e27f58958cd7af7d8446a7fb3d96ade5e1bcb3f015de47e3d24db8508579347befe9324125afada274e2981d0358d5248a18122e124b615985a6c0eaeb99689270c2626c2868649c5ba7f6f17a0c7438e648f89d0cc57e7463b43a9262f489b0e650b9fa04306300b20632a90e1b6f4e3d69b3a4417cead386a6ad8bac776f04447afe5c4d6868fdaa95000ad5e1ba60af4460e710147baf586fb7a8816adfc98bd6d43932cd16b56e1d04d9d5d8bb032f57e89cca12d3cbf259ce0a8b4e00f8341494a94aeae4915a8f8bf7a202a3ca1291fc211451a6326810a12e25b1e4f9b2b41b44eac41bb016ac6c0d8c19cb8558052632c3892187a008f60b18d9471c356e8082187c6c16af703249cb6ada558ae264398b686694036bfd2e64d93506574dfac010bda8652fa7792b4a1032fa3b77e7db3e9a9a9d665b50ec1491e483b6b93959870cfb8b9a3e2c5d724ab616f69b970a425ac3aba6fe3677558506eae0262bc934d8a1ebde696c56230e5b55ba283e9c44c117a9e745f38e26cf98c39a435e1254db2813ed291394a19e866139934a3623652fc44808d60891689b3d2a35be87ad5ebd891bdf0c03d1bdf495c5604e5461b57736aa4b802a43d9b406ca00adcd9f337f13b8c64a7e58700aece3d08afe38918b9e9bacbcc02e67a94d16abba965ec0c53090747b6e93864c03897375fbb6ed9cce93bf84511dbab07a8cbd75abfdcadd7b0e099b19bf371dfd0a", + "0x3a65787472696e7369635f696e646578": "0x00000000", + "0x3a6772616e6470615f617574686f726974696573": "0x010888dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee0100000000000000d17c2d7823ebf260fd138f2d7e27d114c0145d968b5ff5006125f2414fadae690100000000000000", + "0x3db7a24cfdc9de785974746c14a99df94e7b9012096b41c4eb3aaf947f6ea429": "0x0300", + "0x3f1467a096bcd71a5b6a0c8155e20810308ce9615de0775a82f8a94dc3d285a1": "0x01", + "0x3f1467a096bcd71a5b6a0c8155e208103f2edf3bdf381debe331ab7446addfdc": "0x000064a7b3b6e00d0000000000000000", + "0x3f1467a096bcd71a5b6a0c8155e208104e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x426e15054d267946093858132eb537f105fe52c2045750c3c492ccdcf62e2b9c": "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d", + "0x426e15054d267946093858132eb537f14e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x426e15054d267946093858132eb537f195999521c6c89cd80b677e53ce20f98c": "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d", + "0x426e15054d267946093858132eb537f1a47a9ff5cd5bf4d848a80a0b1a947dc3": "0x00000000000000000000000000000000", + "0x426e15054d267946093858132eb537f1ba7fb8745735dc3be2a2c61a72c39e78": "0x181cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc208eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a4890b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27de659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e", + "0x426e15054d267946093858132eb537f1d0b4a3f7631f0c0e761898fe198211de": "0xe7030000", + "0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429": "0x0900", + "0x4a83351006488ef6369cb758091f878c4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x4ff3897794d496d78686afcfe760a1144e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x5c0d1176a568c1f92944340dbfed9e9c4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x5c0d1176a568c1f92944340dbfed9e9c530ebca703c85910e7164cb7d1c9e47b": "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d", + "0x5e8a19e3cd1b7c148b33880c479c02814e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x5f27b51b5ec208ee9cb25b55d8728243308ce9615de0775a82f8a94dc3d285a1": "0x01", + "0x5f27b51b5ec208ee9cb25b55d87282434e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca0b6a45321efae92aea15e0740ec7afe7": "0x00000000", + "0x5f3e4907f716ac89b6347d15ececedca138e71612491192d68deab7e6f563fe1": "0x02000000", + "0x5f3e4907f716ac89b6347d15ececedca28dccb559b95c40168a1b2696581b5a7": "0x00000000000000000000000000000000", + "0x5f3e4907f716ac89b6347d15ececedca308ce9615de0775a82f8a94dc3d285a1": "0x06", + "0x5f3e4907f716ac89b6347d15ececedca3ed14b45ed20d054f05e37e2542cfe700e5be00fbc2e15b5fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0x8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48", + "0x5f3e4907f716ac89b6347d15ececedca3ed14b45ed20d054f05e37e2542cfe70e535263148daaf49be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d", + "0x5f3e4907f716ac89b6347d15ececedca422adb579f1dbf4f3886c5cfa3bb8cc44f9aea1afa791265fae359272badc1cf8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48": "0xfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e13000064a7b3b6e00d13000064a7b3b6e00d0000", + "0x5f3e4907f716ac89b6347d15ececedca422adb579f1dbf4f3886c5cfa3bb8cc4de1e86a9a8c739864cf3cc5ec2bea59fd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d": "0xbe5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f13000064a7b3b6e00d13000064a7b3b6e00d0000", + "0x5f3e4907f716ac89b6347d15ececedca42982b9d6c7acc99faa9094c912372c2b4def25cfda6ef3a000000000e5be00fbc2e15b5fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0x13d4fe63a7b3b6e00d13d4fe63a7b3b6e00d00", + "0x5f3e4907f716ac89b6347d15ececedca42982b9d6c7acc99faa9094c912372c2b4def25cfda6ef3a00000000e535263148daaf49be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0x13d4fe63a7b3b6e00d13d4fe63a7b3b6e00d00", + "0x5f3e4907f716ac89b6347d15ececedca487df464e44a534ba6b0cbb32407b587": "0x0000000000", + "0x5f3e4907f716ac89b6347d15ececedca4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca5579297f4dfb9609e7e4c2ebab9ce40a": "0x00", + "0x5f3e4907f716ac89b6347d15ececedca666fdcbb473985b3ac933d13f4acff8d": "0x00000000000000000000000000000000", + "0x5f3e4907f716ac89b6347d15ececedca682db92dde20a10d96d00ff0e9e221c0b4def25cfda6ef3a000000000e5be00fbc2e15b5fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca682db92dde20a10d96d00ff0e9e221c0b4def25cfda6ef3a00000000e535263148daaf49be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca6ddc7809c6da9bb6093ee22e0fda4ba8": "0x02000000", + "0x5f3e4907f716ac89b6347d15ececedca88dcde934c658227ee1dfafcd6e169030e5be00fbc2e15b5fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca88dcde934c658227ee1dfafcd6e16903e535263148daaf49be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca8bde0a0ea8864605e3b68ed9cb2da01bb4def25cfda6ef3a000000000e5be00fbc2e15b5fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0x13d4fe63a7b3b6e00d13d4fe63a7b3b6e00d00", + "0x5f3e4907f716ac89b6347d15ececedca8bde0a0ea8864605e3b68ed9cb2da01bb4def25cfda6ef3a00000000e535263148daaf49be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0x13d4fe63a7b3b6e00d13d4fe63a7b3b6e00d00", + "0x5f3e4907f716ac89b6347d15ececedca9220e172bed316605f73f1ff7b4ade980e5be00fbc2e15b5fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0x00", + "0x5f3e4907f716ac89b6347d15ececedca9220e172bed316605f73f1ff7b4ade98e535263148daaf49be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0x00", + "0x5f3e4907f716ac89b6347d15ececedcaa141c4fe67c2d11f4a10c6aca7a79a04b4def25cfda6ef3a00000000": "0xa8fdc74e676dc11b0000000000000000", + "0x5f3e4907f716ac89b6347d15ececedcaad811cd65a470ddc5f1d628ff0550982b4def25cfda6ef3a00000000": "0x00000000", + "0x5f3e4907f716ac89b6347d15ececedcab49a2738eeb30896aacb8b3fb46471bd": "0x02000000", + "0x5f3e4907f716ac89b6347d15ececedcac0d39ff577af2cc6b67ac3641fa9c4e7": "0x01000000", + "0x5f3e4907f716ac89b6347d15ececedcac29a0310e1bb45d20cace77ccb62c97d": "0x00e1f505", + "0x5f3e4907f716ac89b6347d15ececedcaea07de2b8f010516dca3f7ef52f7ac5a": "0x040000000000000000", + "0x5f3e4907f716ac89b6347d15ececedcaed441ceb81326c56263efbb60c95c2e4": "0x00000000000000000000000000000000", + "0x5f3e4907f716ac89b6347d15ececedcaf7dad0317324aecae8744b87fc95f2f3": "0x00", + "0x5f9cc45b7a00c5899361e1c6099678dc4e7b9012096b41c4eb3aaf947f6ea429": "0x0400", + "0x5f9cc45b7a00c5899361e1c6099678dc8a2d09463effcc78a22d75b9cb87dffc": "0x0000000000000000", + "0x5f9cc45b7a00c5899361e1c6099678dcd47cb8f5328af743ddfb361e7180e7fcbb1bdbcacd6ac9340000000000000000": "0x00000000", + "0x6441fb391296410bd2f14381bb7494334e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x6786c4cec8d628b6598d7a70ace7acd44e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x682a59d51ab9e48a8c8cc418ff9708d24e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x6c63e84bfc5a0d62149aaab70897685c4ba24bcd9ac206424105f255ae95a355": "0xb104000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x6c63e84bfc5a0d62149aaab70897685c4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x7474449cca95dc5d0c00e71735a6d17d4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0x74dd702da46f77d7acf77f5a48d4af7d4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x74dd702da46f77d7acf77f5a48d4af7d62556a85fcb7c61b2c6c750924846b150e5be00fbc2e15b5fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0xfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e01be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f00b304f91831830500622780fd38770500", + "0x74dd702da46f77d7acf77f5a48d4af7d62556a85fcb7c61b2c6c750924846b15e535263148daaf49be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0xbe5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f0001fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860eb304f91831830500622780fd38770500", + "0x74dd702da46f77d7acf77f5a48d4af7d7a6dc62e324093ba1331bf49fdb2f24a": "0x02000000", + "0x74dd702da46f77d7acf77f5a48d4af7de5c03730c8f59f00941607850b6633d81fad1867486365c5b304f91831830500": "0x01be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f01fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e", + "0x7a6d38deaa01cb6e76ee69889f1696272be9a4e88368a2188d2b9100a9f3cd43": "0x00407a10f35a00000000000000000000", + "0x7a6d38deaa01cb6e76ee69889f16962730256ea2c545a3e5e3744665ffb2ed28": "0x00020000", + "0x7a6d38deaa01cb6e76ee69889f1696273f0d64e1907361c689834a9c1cb0fbe0": "0x20000000", + "0x7a6d38deaa01cb6e76ee69889f16962749d67997de33812a1cc37310f765b82e": "0x0080c6a47e8d03000000000000000000", + "0x7a6d38deaa01cb6e76ee69889f1696274e7b9012096b41c4eb3aaf947f6ea429": "0x0300", + "0x7a6d38deaa01cb6e76ee69889f169627ba93302f3b868c50785e6ade45c6a1d8": "0x10000000", + "0x7cda3cfa86b349fdafce4979b197118f4e7b9012096b41c4eb3aaf947f6ea429": "0x0400", + "0x7cda3cfa86b349fdafce4979b197118f71cd3068e6118bfb392b798317f63a8910c174c55fd2c633e659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e": "0x04e659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e000064a7b3b6e00d000000000000000000000000000000000000000000000000", + "0x7cda3cfa86b349fdafce4979b197118f71cd3068e6118bfb392b798317f63a893e73123ebcdee9161cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c": "0x041cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c000064a7b3b6e00d000000000000000000000000000000000000000000000000", + "0x7cda3cfa86b349fdafce4979b197118f71cd3068e6118bfb392b798317f63a894f58b588ac077bd5306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc20": "0x04306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc20000064a7b3b6e00d000000000000000000000000000000000000000000000000", + "0x7cda3cfa86b349fdafce4979b197118f71cd3068e6118bfb392b798317f63a89518366b5b1bc7c99d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d": "0x04d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d000064a7b3b6e00d000000000000000000000000000000000000000000000000", + "0x7cda3cfa86b349fdafce4979b197118f71cd3068e6118bfb392b798317f63a89a647e755c30521d38eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48": "0x048eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48000064a7b3b6e00d000000000000000000000000000000000000000000000000", + "0x7cda3cfa86b349fdafce4979b197118f71cd3068e6118bfb392b798317f63a89dd4e3f25f5378a6d90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22": "0x0490b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22000064a7b3b6e00d000000000000000000000000000000000000000000000000", + "0x7cda3cfa86b349fdafce4979b197118fba7fb8745735dc3be2a2c61a72c39e78": "0x181cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c000064a7b3b6e00d000000000000000000000000000000000000000000000000306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc20000064a7b3b6e00d0000000000000000000000000000000000000000000000008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48000064a7b3b6e00d00000000000000000000000000000000000000000000000090b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22000064a7b3b6e00d000000000000000000000000000000000000000000000000d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d000064a7b3b6e00d000000000000000000000000000000000000000000000000e659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e000064a7b3b6e00d000000000000000000000000000000000000000000000000", + "0x89d139e01a5eb2256f222e5fc5dbe6b34e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x913b40454eb582a66ab74c86f6137db94e7b9012096b41c4eb3aaf947f6ea429": "0x0400", + "0xa0eb495036d368196a2b6c51d9d788814e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xa2ce73642c549ae79c14f0a671cf45f94e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xa37f719efab16103103a0c8c2c784ce14e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xa42f90c8b47838c3a5332d85ee9aa5c34e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xa8c65209d47ee80f56b0011e8fd91f504e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xaebd463ed9925c488c112434d61debc04e7b9012096b41c4eb3aaf947f6ea429": "0x0400", + "0xaebd463ed9925c488c112434d61debc0ba7fb8745735dc3be2a2c61a72c39e78": "0x18d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a4890b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc20e659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e1cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c", + "0xbd2a529379475088d3e29a918cd478724e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xc2261276cc9d1f8598ea4b6a74b15c2f218f26c73add634897550b4003b26bc632a5935f6edc617ae178fef9eb1e211fbe5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0x047374616b696e6720000064a7b3b6e00d000000000000000002", + "0xc2261276cc9d1f8598ea4b6a74b15c2f218f26c73add634897550b4003b26bc66f2e33376834a63c86a195bcf685aebbfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0x047374616b696e6720000064a7b3b6e00d000000000000000002", + "0xc2261276cc9d1f8598ea4b6a74b15c2f308ce9615de0775a82f8a94dc3d285a1": "0x01", + "0xc2261276cc9d1f8598ea4b6a74b15c2f4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xc2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80": "0x0040fa7f398074858a02000000000000", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb30e5be00fbc2e15b5fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0xd17c2d7823ebf260fd138f2d7e27d114c0145d968b5ff5006125f2414fadae698eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a488eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a488eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3e535263148daaf49be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0x88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0eed43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27dd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27dd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d", + "0xcec5070d609dd3497f72bde07fc96ba04e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19500e3a507571a62417696d6f6e808eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48": "0xfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19505905fe216cc5924c6772616e80d17c2d7823ebf260fd138f2d7e27d114c0145d968b5ff5006125f2414fadae69": "0xfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195066b8d48da86b869b6261626580d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d": "0xbe5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19509d4a4cfe1c2ef0b961756469808eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48": "0xfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950c9b0c13125732d276175646980d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d": "0xbe5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950d62c40514b41f31962616265808eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48": "0xfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950ed43a85541921049696d6f6e80d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d": "0xbe5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950f5537bdb2a1f626b6772616e8088dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee": "0xbe5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f", + "0xcec5070d609dd3497f72bde07fc96ba088dcde934c658227ee1dfafcd6e16903": "0x08be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25ffe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e", + "0xcec5070d609dd3497f72bde07fc96ba0e0cdd062e6eaf24295ad4ccfc41d4609": "0x08be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0eed43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27dd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27dd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27dfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860ed17c2d7823ebf260fd138f2d7e27d114c0145d968b5ff5006125f2414fadae698eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a488eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a488eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48", + "0xd57bce545fb382c34570e5dfbf338f5e4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xd5c41b52a371aa36c9254ce34324f2a54e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xd5e1a2fa16732ce6906189438c0a82c64e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xd8f314b7f4e6b095f0f8ee4656a448254e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xed25f63942de25ac5253ba64b5eb64d14e7b9012096b41c4eb3aaf947f6ea429": "0x0400", + "0xed25f63942de25ac5253ba64b5eb64d1ba7fb8745735dc3be2a2c61a72c39e78": "0x18d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a4890b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc20e659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e1cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c", + "0xede8e4fdc3c8b556f0ce2f77fc2575e34e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xedfb05b766f199ce00df85317e33050e4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xf0c365c3cf59d671eb72da0e7a4113c44e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xf2794c22e353e9a839f12faab03a911b4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xf2794c22e353e9a839f12faab03a911b7f17cdfbfa73331856cca0acddd7842e": "0x00000000", + "0xf2794c22e353e9a839f12faab03a911bbdcb0c5143a8617ed38ae3810dd45bc6": "0x00000000", + "0xf2794c22e353e9a839f12faab03a911be2f6cb0456905c189bcb0458f9440f13": "0x00000000", + "0xf5a4963e4efb097983d7a693b0c1ee454e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xfbc9f53700f75f681f234e70fb7241eb4e7b9012096b41c4eb3aaf947f6ea429": "0x0000" + }, + "childrenDefault": {} + } + } +} \ No newline at end of file diff --git a/zombienet/0001-basic-warp-sync/generate-warp-sync-database.toml b/zombienet/0001-basic-warp-sync/generate-warp-sync-database.toml new file mode 100644 index 0000000000..ca78ee290c --- /dev/null +++ b/zombienet/0001-basic-warp-sync/generate-warp-sync-database.toml @@ -0,0 +1,17 @@ +# this file is not intended to be executed in CI stage +[relaychain] +default_image = "docker.io/parity/substrate:latest" +default_command = "substrate" + +# refer to ./README.md for more details on how to create snapshot and spec +chain = "gen-db" +chain_spec_path = "chain-spec.json" + + + [[relaychain.nodes]] + name = "alice" + validator = true + + [[relaychain.nodes]] + name = "bob" + validator = true diff --git a/zombienet/0001-basic-warp-sync/test-warp-sync.toml b/zombienet/0001-basic-warp-sync/test-warp-sync.toml new file mode 100644 index 0000000000..ae2810b6ec --- /dev/null +++ b/zombienet/0001-basic-warp-sync/test-warp-sync.toml @@ -0,0 +1,30 @@ +[settings] +enable_tracing = false + +[relaychain] +default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" +default_command = "substrate" + +chain = "gen-db" +chain_spec_path = "zombienet/0001-basic-warp-sync/chain-spec.json" + + [[relaychain.nodes]] + name = "alice" + validator = false + db_snapshot="https://storage.googleapis.com/zombienet-db-snaps/substrate/0001-basic-warp-sync/chains-0bb3f0be2ce41b5615b224215bcc8363aa0416a6.tgz" + + [[relaychain.nodes]] + name = "bob" + validator = false + db_snapshot="https://storage.googleapis.com/zombienet-db-snaps/substrate/0001-basic-warp-sync/chains-0bb3f0be2ce41b5615b224215bcc8363aa0416a6.tgz" + + #we need at least 3 nodes for warp sync + [[relaychain.nodes]] + name = "charlie" + validator = false + db_snapshot="https://storage.googleapis.com/zombienet-db-snaps/substrate/0001-basic-warp-sync/chains-0bb3f0be2ce41b5615b224215bcc8363aa0416a6.tgz" + + [[relaychain.nodes]] + name = "dave" + validator = false + args = ["--sync warp"] diff --git a/zombienet/0001-basic-warp-sync/test-warp-sync.zndsl b/zombienet/0001-basic-warp-sync/test-warp-sync.zndsl new file mode 100644 index 0000000000..1ccacb2e6d --- /dev/null +++ b/zombienet/0001-basic-warp-sync/test-warp-sync.zndsl @@ -0,0 +1,35 @@ +Description: Warp sync +Network: ./test-warp-sync.toml +Creds: config + +alice: is up +bob: is up +charlie: is up +dave: is up + +alice: reports node_roles is 1 +bob: reports node_roles is 1 +charlie: reports node_roles is 1 +dave: reports node_roles is 1 + +alice: reports peers count is at least 3 within 60 seconds +bob: reports peers count is at least 3 within 60 seconds +charlie: reports peers count is at least 3 within 60 seconds +dave: reports peers count is at least 3 within 60 seconds + + +# db snapshot has 12133 blocks +dave: reports block height is at least 1 within 60 seconds +dave: reports block height is at least 12132 within 60 seconds +dave: reports block height is at least 12133 within 60 seconds + +alice: reports block height is at least 12133 within 60 seconds +bob: reports block height is at least 12133 within 60 seconds +charlie: reports block height is at least 12133 within 60 seconds + +dave: log line matches "Warp sync is complete" within 60 seconds +# workaround for: https://github.com/paritytech/zombienet/issues/580 +dave: count of log lines containing "Block history download is complete" is 1 within 10 seconds + +dave: count of log lines containing "error" is 0 within 10 seconds +dave: count of log lines containing "verification failed" is 0 within 10 seconds From ea3ca3f757ff9d9559665719a77da81f4cf0f0ce Mon Sep 17 00:00:00 2001 From: Anthony Alaribe Date: Thu, 8 Dec 2022 14:47:13 +0200 Subject: [PATCH 011/127] Move LockableCurrency trait to fungibles::Lockable and deprecate LockableCurrency (#12798) * WIP move LockableCurrency to fungibles * rename Lockable and LockIdentifier to funginbles::* * fix imports further * change Lockable from fungible to fungibles * reintroduce LockableCurrency but marked as deprecated * fix imports * fix imports * cargo fmt * add allow deprecated warnings * remove unused benchmark import * fix some of the docs * fix failing doctest check * reexport LockIdentifier and LockableCurrency from support/traits * reexport LockIdentifier and LockableCurrency from support/traits * allow using deprecated re-export * replace LockableCurrency and LockIdentifier with a module alias * Update frame/support/src/traits/tokens/fungibles/lockable.rs * Update frame/staking/src/pallet/mod.rs Co-authored-by: Squirrel * Update frame/support/src/traits.rs Co-authored-by: Squirrel * REVERT removing fungibles::Lockable import Co-authored-by: parity-processbot <> Co-authored-by: Squirrel --- bin/node/runtime/src/lib.rs | 8 +-- frame/balances/README.md | 8 +-- frame/balances/src/lib.rs | 26 ++++---- frame/balances/src/tests.rs | 6 +- frame/contracts/src/tests.rs | 2 +- frame/conviction-voting/src/benchmarking.rs | 2 +- frame/conviction-voting/src/lib.rs | 6 +- frame/democracy/src/lib.rs | 10 +-- frame/elections-phragmen/src/lib.rs | 10 +-- frame/executive/src/lib.rs | 9 +-- frame/referenda/src/lib.rs | 7 +- frame/staking/src/pallet/impls.rs | 5 +- frame/staking/src/pallet/mod.rs | 8 +-- frame/support/src/traits.rs | 1 + frame/support/src/traits/tokens/currency.rs | 5 +- .../src/traits/tokens/currency/lockable.rs | 48 +------------- frame/support/src/traits/tokens/fungible.rs | 1 + frame/support/src/traits/tokens/fungibles.rs | 2 + .../src/traits/tokens/fungibles/lockable.rs | 65 +++++++++++++++++++ frame/vesting/src/lib.rs | 11 ++-- 20 files changed, 135 insertions(+), 105 deletions(-) create mode 100644 frame/support/src/traits/tokens/fungibles/lockable.rs diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 8c4e70c37d..44d8e28706 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -32,9 +32,9 @@ use frame_support::{ pallet_prelude::Get, parameter_types, traits::{ - fungible::ItemOf, AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, - Currency, EitherOfDiverse, EqualPrivilegeOnly, Everything, Imbalance, InstanceFilter, - KeyOwnerProofSystem, LockIdentifier, Nothing, OnUnbalanced, U128CurrencyToVote, + fungible::ItemOf, fungibles, AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, + ConstU32, Currency, EitherOfDiverse, EqualPrivilegeOnly, Everything, Imbalance, + InstanceFilter, KeyOwnerProofSystem, Nothing, OnUnbalanced, U128CurrencyToVote, WithdrawReasons, }, weights::{ @@ -1003,7 +1003,7 @@ parameter_types! { pub const DesiredRunnersUp: u32 = 7; pub const MaxVoters: u32 = 10 * 1000; pub const MaxCandidates: u32 = 1000; - pub const ElectionsPhragmenPalletId: LockIdentifier = *b"phrelect"; + pub const ElectionsPhragmenPalletId: fungibles::LockIdentifier = *b"phrelect"; } // Make sure that there are no more than `MaxMembers` members elected via elections-phragmen. diff --git a/frame/balances/README.md b/frame/balances/README.md index 93e424a89c..d32fffbf0e 100644 --- a/frame/balances/README.md +++ b/frame/balances/README.md @@ -57,7 +57,7 @@ that you need, then you can avoid coupling with the Balances module. fungible assets system. - [`ReservableCurrency`](https://docs.rs/frame-support/latest/frame_support/traits/trait.ReservableCurrency.html): Functions for dealing with assets that can be reserved from an account. -- [`LockableCurrency`](https://docs.rs/frame-support/latest/frame_support/traits/trait.LockableCurrency.html): Functions for +- [`Lockable`](https://docs.rs/frame-support/latest/frame_support/traits/fungibles/trait.Lockable.html): Functions for dealing with accounts that allow liquidity restrictions. - [`Imbalance`](https://docs.rs/frame-support/latest/frame_support/traits/trait.Imbalance.html): Functions for handling imbalances between total issuance in the system and account balances. Must be used when a function @@ -88,13 +88,13 @@ pub type NegativeImbalanceOf = <::Currency as Currency<; + type Currency: fungibles::Lockable; } fn update_ledger( diff --git a/frame/balances/src/lib.rs b/frame/balances/src/lib.rs index 381a0ffcee..d74de37e99 100644 --- a/frame/balances/src/lib.rs +++ b/frame/balances/src/lib.rs @@ -79,7 +79,7 @@ //! - [`ReservableCurrency`](frame_support::traits::ReservableCurrency): //! - [`NamedReservableCurrency`](frame_support::traits::NamedReservableCurrency): //! Functions for dealing with assets that can be reserved from an account. -//! - [`LockableCurrency`](frame_support::traits::LockableCurrency): Functions for +//! - [`Lockable`](frame_support::traits::fungibles::Lockable): Functions for //! dealing with accounts that allow liquidity restrictions. //! - [`Imbalance`](frame_support::traits::Imbalance): Functions for handling //! imbalances between total issuance in the system and account balances. Must be used when a @@ -113,13 +113,13 @@ //! # fn main() {} //! ``` //! -//! The Staking pallet uses the `LockableCurrency` trait to lock a stash account's funds: +//! The Staking pallet uses the `fungibles::Lockable` trait to lock a stash account's funds: //! //! ``` -//! use frame_support::traits::{WithdrawReasons, LockableCurrency}; +//! use frame_support::traits::{WithdrawReasons, fungibles, fungibles::Lockable}; //! use sp_runtime::traits::Bounded; //! pub trait Config: frame_system::Config { -//! type Currency: LockableCurrency; +//! type Currency: fungibles::Lockable; //! } //! # struct StakingLedger { //! # stash: ::AccountId, @@ -171,11 +171,13 @@ use frame_support::{ ensure, pallet_prelude::DispatchResult, traits::{ - tokens::{fungible, BalanceStatus as Status, DepositConsequence, WithdrawConsequence}, + tokens::{ + fungible, fungibles, BalanceStatus as Status, DepositConsequence, WithdrawConsequence, + }, Currency, DefensiveSaturating, ExistenceRequirement, ExistenceRequirement::{AllowDeath, KeepAlive}, - Get, Imbalance, LockIdentifier, LockableCurrency, NamedReservableCurrency, OnUnbalanced, - ReservableCurrency, SignedImbalance, StoredMap, TryDrop, WithdrawReasons, + Get, Imbalance, NamedReservableCurrency, OnUnbalanced, ReservableCurrency, SignedImbalance, + StoredMap, TryDrop, WithdrawReasons, }, WeakBoundedVec, }; @@ -662,7 +664,7 @@ impl BitOr for Reasons { #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)] pub struct BalanceLock { /// An identifier for this lock. Only one lock may be in existence for each identifier. - pub id: LockIdentifier, + pub id: fungibles::LockIdentifier, /// The amount which the free balance may not drop below when this lock is in effect. pub amount: Balance, /// If true, then the lock remains in effect even for payment of transaction fees. @@ -2131,7 +2133,7 @@ where } } -impl, I: 'static> LockableCurrency for Pallet +impl, I: 'static> fungibles::Lockable for Pallet where T::Balance: MaybeSerializeDeserialize + Debug, { @@ -2142,7 +2144,7 @@ where // Set a lock on the balance of `who`. // Is a no-op if lock amount is zero or `reasons` `is_none()`. fn set_lock( - id: LockIdentifier, + id: fungibles::LockIdentifier, who: &T::AccountId, amount: T::Balance, reasons: WithdrawReasons, @@ -2164,7 +2166,7 @@ where // Extend a lock on the balance of `who`. // Is a no-op if lock amount is zero or `reasons` `is_none()`. fn extend_lock( - id: LockIdentifier, + id: fungibles::LockIdentifier, who: &T::AccountId, amount: T::Balance, reasons: WithdrawReasons, @@ -2193,7 +2195,7 @@ where Self::update_locks(who, &locks[..]); } - fn remove_lock(id: LockIdentifier, who: &T::AccountId) { + fn remove_lock(id: fungibles::LockIdentifier, who: &T::AccountId) { let mut locks = Self::locks(who); locks.retain(|l| l.id != id); Self::update_locks(who, &locks[..]); diff --git a/frame/balances/src/tests.rs b/frame/balances/src/tests.rs index 83944caf9f..44a71b9325 100644 --- a/frame/balances/src/tests.rs +++ b/frame/balances/src/tests.rs @@ -28,15 +28,15 @@ macro_rules! decl_tests { use frame_support::{ assert_noop, assert_storage_noop, assert_ok, assert_err, traits::{ - LockableCurrency, LockIdentifier, WithdrawReasons, + fungibles, fungibles::Lockable, WithdrawReasons, Currency, ReservableCurrency, ExistenceRequirement::AllowDeath } }; use pallet_transaction_payment::{ChargeTransactionPayment, Multiplier}; use frame_system::RawOrigin; - const ID_1: LockIdentifier = *b"1 "; - const ID_2: LockIdentifier = *b"2 "; + const ID_1: fungibles::LockIdentifier = *b"1 "; + const ID_2: fungibles::LockIdentifier = *b"2 "; pub const CALL: &<$test as frame_system::Config>::RuntimeCall = &RuntimeCall::Balances(pallet_balances::Call::transfer { dest: 0, value: 0 }); diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index e5395c73d2..f4c8889ef0 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -37,7 +37,7 @@ use frame_support::{ parameter_types, storage::child, traits::{ - BalanceStatus, ConstU32, ConstU64, Contains, Currency, Get, LockableCurrency, OnIdle, + fungibles::Lockable, BalanceStatus, ConstU32, ConstU64, Contains, Currency, Get, OnIdle, OnInitialize, ReservableCurrency, WithdrawReasons, }, weights::{constants::WEIGHT_PER_SECOND, Weight}, diff --git a/frame/conviction-voting/src/benchmarking.rs b/frame/conviction-voting/src/benchmarking.rs index 117bb7fe22..4bebc6a97c 100644 --- a/frame/conviction-voting/src/benchmarking.rs +++ b/frame/conviction-voting/src/benchmarking.rs @@ -23,7 +23,7 @@ use assert_matches::assert_matches; use frame_benchmarking::{account, benchmarks_instance_pallet, whitelist_account}; use frame_support::{ dispatch::RawOrigin, - traits::{fungible, Currency, Get}, + traits::{Currency, Get}, }; use sp_runtime::traits::Bounded; use sp_std::collections::btree_map::BTreeMap; diff --git a/frame/conviction-voting/src/lib.rs b/frame/conviction-voting/src/lib.rs index 3ecc6e56be..992b532fb9 100644 --- a/frame/conviction-voting/src/lib.rs +++ b/frame/conviction-voting/src/lib.rs @@ -31,7 +31,7 @@ use frame_support::{ dispatch::{DispatchError, DispatchResult}, ensure, traits::{ - fungible, Currency, Get, LockIdentifier, LockableCurrency, PollStatus, Polling, + fungible, fungibles, fungibles::Lockable, Currency, Get, PollStatus, Polling, ReservableCurrency, WithdrawReasons, }, }; @@ -60,7 +60,7 @@ mod tests; #[cfg(feature = "runtime-benchmarks")] pub mod benchmarking; -const CONVICTION_VOTING_ID: LockIdentifier = *b"pyconvot"; +const CONVICTION_VOTING_ID: fungibles::LockIdentifier = *b"pyconvot"; type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; type BalanceOf = @@ -104,7 +104,7 @@ pub mod pallet { type WeightInfo: WeightInfo; /// Currency type with which voting happens. type Currency: ReservableCurrency - + LockableCurrency + + fungibles::Lockable + fungible::Inspect; /// The implementation of the logic which conducts polls. diff --git a/frame/democracy/src/lib.rs b/frame/democracy/src/lib.rs index cf954d4800..096122cb1c 100644 --- a/frame/democracy/src/lib.rs +++ b/frame/democracy/src/lib.rs @@ -157,9 +157,11 @@ use frame_support::{ ensure, traits::{ defensive_prelude::*, + fungibles, + fungibles::Lockable, schedule::{v3::Named as ScheduleNamed, DispatchTime}, - Bounded, Currency, Get, LockIdentifier, LockableCurrency, OnUnbalanced, QueryPreimage, - ReservableCurrency, StorePreimage, WithdrawReasons, + Bounded, Currency, Get, OnUnbalanced, QueryPreimage, ReservableCurrency, StorePreimage, + WithdrawReasons, }, weights::Weight, }; @@ -189,7 +191,7 @@ pub mod benchmarking; pub mod migrations; -const DEMOCRACY_ID: LockIdentifier = *b"democrac"; +const DEMOCRACY_ID: fungibles::LockIdentifier = *b"democrac"; /// A proposal index. pub type PropIndex = u32; @@ -234,7 +236,7 @@ pub mod pallet { /// Currency type for this pallet. type Currency: ReservableCurrency - + LockableCurrency; + + fungibles::Lockable; /// The period between a proposal being approved and enacted. /// diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 165a8fcab4..13190237ea 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -101,8 +101,8 @@ use codec::{Decode, Encode}; use frame_support::{ traits::{ - defensive_prelude::*, ChangeMembers, Contains, ContainsLengthBound, Currency, - CurrencyToVote, Get, InitializeMembers, LockIdentifier, LockableCurrency, OnUnbalanced, + defensive_prelude::*, fungibles, fungibles::Lockable, ChangeMembers, Contains, + ContainsLengthBound, Currency, CurrencyToVote, Get, InitializeMembers, OnUnbalanced, ReservableCurrency, SortedMembers, WithdrawReasons, }, weights::Weight, @@ -199,10 +199,10 @@ pub mod pallet { /// Identifier for the elections-phragmen pallet's lock #[pallet::constant] - type PalletId: Get; + type PalletId: Get; /// The currency that people are electing with. - type Currency: LockableCurrency + type Currency: fungibles::Lockable + ReservableCurrency; /// What to do when the members change. @@ -1274,7 +1274,7 @@ mod tests { } parameter_types! { - pub const ElectionsPhragmenPalletId: LockIdentifier = *b"phrelect"; + pub const ElectionsPhragmenPalletId: fungibles::LockIdentifier = *b"phrelect"; pub const PhragmenMaxVoters: u32 = 1000; pub const PhragmenMaxCandidates: u32 = 100; } diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index 5a4ef92b1c..2d307a1a02 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -620,10 +620,7 @@ mod tests { use frame_support::{ assert_err, parameter_types, - traits::{ - ConstU32, ConstU64, ConstU8, Currency, LockIdentifier, LockableCurrency, - WithdrawReasons, - }, + traits::{fungibles, ConstU32, ConstU64, ConstU8, Currency, WithdrawReasons}, weights::{ConstantMultiplier, IdentityFee, RuntimeDbWeight, Weight, WeightToFee}, }; use frame_system::{Call as SystemCall, ChainContext, LastRuntimeUpgradeInfo}; @@ -1185,11 +1182,11 @@ mod tests { #[test] fn can_pay_for_tx_fee_on_full_lock() { - let id: LockIdentifier = *b"0 "; + let id: fungibles::LockIdentifier = *b"0 "; let execute_with_lock = |lock: WithdrawReasons| { let mut t = new_test_ext(1); t.execute_with(|| { - as LockableCurrency>::set_lock( + as fungibles::Lockable>::set_lock( id, &1, 110, lock, ); let xt = TestXt::new( diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index 2bb01baa0c..551628fee9 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -1,5 +1,3 @@ -// This file is part of Substrate. - // Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 @@ -68,11 +66,12 @@ use codec::{Codec, Encode}; use frame_support::{ ensure, traits::{ + fungibles, schedule::{ v3::{Anon as ScheduleAnon, Named as ScheduleNamed}, DispatchTime, }, - Currency, LockIdentifier, OnUnbalanced, OriginTrait, PollStatus, Polling, QueryPreimage, + Currency, OnUnbalanced, OriginTrait, PollStatus, Polling, QueryPreimage, ReservableCurrency, StorePreimage, VoteTally, }, BoundedVec, @@ -133,7 +132,7 @@ macro_rules! impl_tracksinfo_get { }; } -const ASSEMBLY_ID: LockIdentifier = *b"assembly"; +const ASSEMBLY_ID: fungibles::LockIdentifier = *b"assembly"; #[frame_support::pallet] pub mod pallet { diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index c22a2bd2d1..34e12fbcf6 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -25,8 +25,9 @@ use frame_support::{ dispatch::WithPostDispatchInfo, pallet_prelude::*, traits::{ - Currency, CurrencyToVote, Defensive, DefensiveResult, EstimateNextNewSession, Get, - Imbalance, LockableCurrency, OnUnbalanced, TryCollect, UnixTime, WithdrawReasons, + fungibles::Lockable, Currency, CurrencyToVote, Defensive, DefensiveResult, + EstimateNextNewSession, Get, Imbalance, OnUnbalanced, TryCollect, UnixTime, + WithdrawReasons, }, weights::Weight, }; diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 8fddba2150..fd0c494fa6 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -24,8 +24,8 @@ use frame_support::{ dispatch::Codec, pallet_prelude::*, traits::{ - Currency, CurrencyToVote, Defensive, DefensiveResult, DefensiveSaturating, EnsureOrigin, - EstimateNextNewSession, Get, LockIdentifier, LockableCurrency, OnUnbalanced, TryCollect, + fungibles, fungibles::Lockable, Currency, CurrencyToVote, Defensive, DefensiveResult, + DefensiveSaturating, EnsureOrigin, EstimateNextNewSession, Get, OnUnbalanced, TryCollect, UnixTime, }, weights::Weight, @@ -50,7 +50,7 @@ use crate::{ ValidatorPrefs, }; -const STAKING_ID: LockIdentifier = *b"staking "; +const STAKING_ID: fungibles::LockIdentifier = *b"staking "; #[frame_support::pallet] pub mod pallet { @@ -78,7 +78,7 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { /// The staking balance. - type Currency: LockableCurrency< + type Currency: fungibles::Lockable< Self::AccountId, Moment = Self::BlockNumber, Balance = Self::CurrencyBalance, diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index e5ba98fe0c..3a831d9c27 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -20,6 +20,7 @@ //! NOTE: If you're looking for `parameter_types`, it has moved in to the top-level module. pub mod tokens; +#[allow(deprecated)] pub use tokens::{ currency::{ ActiveIssuanceOf, Currency, LockIdentifier, LockableCurrency, NamedReservableCurrency, diff --git a/frame/support/src/traits/tokens/currency.rs b/frame/support/src/traits/tokens/currency.rs index 48247b6021..29603198e9 100644 --- a/frame/support/src/traits/tokens/currency.rs +++ b/frame/support/src/traits/tokens/currency.rs @@ -32,7 +32,10 @@ use sp_std::fmt::Debug; mod reservable; pub use reservable::{NamedReservableCurrency, ReservableCurrency}; mod lockable; -pub use lockable::{LockIdentifier, LockableCurrency, VestingSchedule}; + +#[deprecated(note = "Deprecated in favour of using fungibles::Lockable trait directly")] +pub use super::fungibles::{LockIdentifier, Lockable as LockableCurrency}; +pub use lockable::VestingSchedule; /// Abstraction over a fungible assets system. pub trait Currency { diff --git a/frame/support/src/traits/tokens/currency/lockable.rs b/frame/support/src/traits/tokens/currency/lockable.rs index a10edd6e3e..5b7cad3b5c 100644 --- a/frame/support/src/traits/tokens/currency/lockable.rs +++ b/frame/support/src/traits/tokens/currency/lockable.rs @@ -17,52 +17,8 @@ //! The lockable currency trait and some associated types. -use super::{super::misc::WithdrawReasons, Currency}; -use crate::{dispatch::DispatchResult, traits::misc::Get}; - -/// An identifier for a lock. Used for disambiguating different locks so that -/// they can be individually replaced or removed. -pub type LockIdentifier = [u8; 8]; - -/// A currency whose accounts can have liquidity restrictions. -pub trait LockableCurrency: Currency { - /// The quantity used to denote time; usually just a `BlockNumber`. - type Moment; - - /// The maximum number of locks a user should have on their account. - type MaxLocks: Get; - - /// Create a new balance lock on account `who`. - /// - /// If the new lock is valid (i.e. not already expired), it will push the struct to - /// the `Locks` vec in storage. Note that you can lock more funds than a user has. - /// - /// If the lock `id` already exists, this will update it. - fn set_lock( - id: LockIdentifier, - who: &AccountId, - amount: Self::Balance, - reasons: WithdrawReasons, - ); - - /// Changes a balance lock (selected by `id`) so that it becomes less liquid in all - /// parameters or creates a new one if it does not exist. - /// - /// Calling `extend_lock` on an existing lock `id` differs from `set_lock` in that it - /// applies the most severe constraints of the two, while `set_lock` replaces the lock - /// with the new parameters. As in, `extend_lock` will set: - /// - maximum `amount` - /// - bitwise mask of all `reasons` - fn extend_lock( - id: LockIdentifier, - who: &AccountId, - amount: Self::Balance, - reasons: WithdrawReasons, - ); - - /// Remove an existing lock. - fn remove_lock(id: LockIdentifier, who: &AccountId); -} +use super::Currency; +use crate::dispatch::DispatchResult; /// A vesting schedule over a currency. This allows a particular currency to have vesting limits /// applied to it. diff --git a/frame/support/src/traits/tokens/fungible.rs b/frame/support/src/traits/tokens/fungible.rs index 05e109b870..d11959fd7c 100644 --- a/frame/support/src/traits/tokens/fungible.rs +++ b/frame/support/src/traits/tokens/fungible.rs @@ -29,6 +29,7 @@ use sp_runtime::traits::Saturating; mod balanced; mod imbalance; + pub use balanced::{Balanced, Unbalanced}; pub use imbalance::{CreditOf, DebtOf, HandleImbalanceDrop, Imbalance}; diff --git a/frame/support/src/traits/tokens/fungibles.rs b/frame/support/src/traits/tokens/fungibles.rs index 0743e3031c..6cc6d70de1 100644 --- a/frame/support/src/traits/tokens/fungibles.rs +++ b/frame/support/src/traits/tokens/fungibles.rs @@ -33,7 +33,9 @@ pub mod metadata; pub use balanced::{Balanced, Unbalanced}; mod imbalance; pub use imbalance::{CreditOf, DebtOf, HandleImbalanceDrop, Imbalance}; +mod lockable; pub mod roles; +pub use lockable::{LockIdentifier, Lockable}; /// Trait for providing balance-inspection access to a set of named fungible assets. pub trait Inspect { diff --git a/frame/support/src/traits/tokens/fungibles/lockable.rs b/frame/support/src/traits/tokens/fungibles/lockable.rs new file mode 100644 index 0000000000..185b40eae9 --- /dev/null +++ b/frame/support/src/traits/tokens/fungibles/lockable.rs @@ -0,0 +1,65 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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. + +//! The Lockable trait and some associated types. + +use super::{super::misc::WithdrawReasons, currency::Currency}; +use crate::traits::misc::Get; + +/// An identifier for a lock. Used for disambiguating different locks so that +/// they can be individually replaced or removed. +pub type LockIdentifier = [u8; 8]; + +/// A currency whose accounts can have liquidity restrictions. +pub trait Lockable: Currency { + /// The quantity used to denote time; usually just a `BlockNumber`. + type Moment; + + /// The maximum number of locks a user should have on their account. + type MaxLocks: Get; + + /// Create a new balance lock on account `who`. + /// + /// If the new lock is valid (i.e. not already expired), it will push the struct to + /// the `Locks` vec in storage. Note that you can lock more funds than a user has. + /// + /// If the lock `id` already exists, this will update it. + fn set_lock( + id: LockIdentifier, + who: &AccountId, + amount: Self::Balance, + reasons: WithdrawReasons, + ); + + /// Changes a balance lock (selected by `id`) so that it becomes less liquid in all + /// parameters or creates a new one if it does not exist. + /// + /// Calling `extend_lock` on an existing lock `id` differs from `set_lock` in that it + /// applies the most severe constraints of the two, while `set_lock` replaces the lock + /// with the new parameters. As in, `extend_lock` will set: + /// - maximum `amount` + /// - bitwise mask of all `reasons` + fn extend_lock( + id: LockIdentifier, + who: &AccountId, + amount: Self::Balance, + reasons: WithdrawReasons, + ); + + /// Remove an existing lock. + fn remove_lock(id: LockIdentifier, who: &AccountId); +} diff --git a/frame/vesting/src/lib.rs b/frame/vesting/src/lib.rs index a92f94baf6..226f539a74 100644 --- a/frame/vesting/src/lib.rs +++ b/frame/vesting/src/lib.rs @@ -62,7 +62,7 @@ use frame_support::{ ensure, storage::bounded_vec::BoundedVec, traits::{ - Currency, ExistenceRequirement, Get, LockIdentifier, LockableCurrency, VestingSchedule, + fungibles, fungibles::Lockable, Currency, ExistenceRequirement, Get, VestingSchedule, WithdrawReasons, }, weights::Weight, @@ -83,11 +83,12 @@ pub use weights::WeightInfo; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; -type MaxLocksOf = - <::Currency as LockableCurrency<::AccountId>>::MaxLocks; +type MaxLocksOf = <::Currency as fungibles::Lockable< + ::AccountId, +>>::MaxLocks; type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; -const VESTING_ID: LockIdentifier = *b"vesting "; +const VESTING_ID: fungibles::LockIdentifier = *b"vesting "; // A value placed in storage that represents the current version of the Vesting storage. // This value is used by `on_runtime_upgrade` to determine whether we run storage migration logic. @@ -159,7 +160,7 @@ pub mod pallet { type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// The currency trait. - type Currency: LockableCurrency; + type Currency: fungibles::Lockable; /// Convert the block number into a balance. type BlockNumberToBalance: Convert>; From fae9e366eabd70000895cfd5f52cd1cc2afaacd6 Mon Sep 17 00:00:00 2001 From: dharjeezy Date: Thu, 8 Dec 2022 15:09:17 +0100 Subject: [PATCH 012/127] [pallet-assets] add asset_exists(id: AssetId) function (#12782) * check if an asset exists via loose coupling * Update frame/support/src/traits/tokens/fungibles.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> Co-authored-by: parity-processbot <> --- frame/assets/src/impl_fungibles.rs | 4 ++++ frame/support/src/traits/tokens/fungibles.rs | 3 +++ 2 files changed, 7 insertions(+) diff --git a/frame/assets/src/impl_fungibles.rs b/frame/assets/src/impl_fungibles.rs index 6e0a9ac08e..b10b8c6b10 100644 --- a/frame/assets/src/impl_fungibles.rs +++ b/frame/assets/src/impl_fungibles.rs @@ -59,6 +59,10 @@ impl, I: 'static> fungibles::Inspect<::AccountId ) -> WithdrawConsequence { Pallet::::can_decrease(asset, who, amount, false) } + + fn asset_exists(asset: Self::AssetId) -> bool { + Asset::::contains_key(asset) + } } impl, I: 'static> fungibles::InspectMetadata<::AccountId> diff --git a/frame/support/src/traits/tokens/fungibles.rs b/frame/support/src/traits/tokens/fungibles.rs index 6cc6d70de1..045ecd0513 100644 --- a/frame/support/src/traits/tokens/fungibles.rs +++ b/frame/support/src/traits/tokens/fungibles.rs @@ -83,6 +83,9 @@ pub trait Inspect { who: &AccountId, amount: Self::Balance, ) -> WithdrawConsequence; + + /// Returns `true` if an `asset` exists. + fn asset_exists(asset: Self::AssetId) -> bool; } /// Trait for reading metadata from a fungible asset. From d0540a79967cb06cd7598a4965c7c06afc788b0c Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Fri, 9 Dec 2022 01:18:51 +0900 Subject: [PATCH 013/127] Introduce sensible weight constants (#12868) * Introduce sensible weight constants * cargo fmt * Remove unused import * Add missing import * ".git/.scripts/bench-bot.sh" pallet dev pallet_lottery Co-authored-by: command-bot <> --- bin/node-template/runtime/src/lib.rs | 6 +- bin/node/runtime/src/lib.rs | 7 ++- docs/Upgrading-2.0-to-3.0.md | 4 +- frame/babe/src/default_weights.rs | 21 ++++--- frame/contracts/src/tests.rs | 4 +- frame/democracy/src/tests.rs | 4 +- .../election-provider-multi-phase/src/mock.rs | 2 +- frame/fast-unstake/src/mock.rs | 4 +- frame/grandpa/src/default_weights.rs | 20 ++++--- frame/lottery/src/weights.rs | 58 +++++++++---------- .../src/default_weights.rs | 4 +- frame/offences/benchmarking/src/mock.rs | 4 +- frame/offences/src/mock.rs | 6 +- frame/staking/src/mock.rs | 2 +- frame/staking/src/tests.rs | 13 +++-- frame/support/src/weights/block_weights.rs | 9 +-- .../support/src/weights/extrinsic_weights.rs | 9 +-- frame/support/src/weights/paritydb_weights.rs | 12 ++-- frame/support/src/weights/rocksdb_weights.rs | 12 ++-- frame/system/src/limits.rs | 2 +- primitives/weights/src/lib.rs | 11 ++-- .../frame/benchmarking-cli/src/block/bench.rs | 4 +- .../benchmarking-cli/src/overhead/README.md | 6 +- .../benchmarking-cli/src/overhead/weights.hbs | 13 +++-- .../benchmarking-cli/src/storage/README.md | 4 +- .../benchmarking-cli/src/storage/weights.hbs | 12 ++-- 26 files changed, 140 insertions(+), 113 deletions(-) diff --git a/bin/node-template/runtime/src/lib.rs b/bin/node-template/runtime/src/lib.rs index 1d0e18d31b..938282c662 100644 --- a/bin/node-template/runtime/src/lib.rs +++ b/bin/node-template/runtime/src/lib.rs @@ -32,7 +32,9 @@ pub use frame_support::{ ConstU128, ConstU32, ConstU64, ConstU8, KeyOwnerProofSystem, Randomness, StorageInfo, }, weights::{ - constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND}, + constants::{ + BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_REF_TIME_PER_SECOND, + }, IdentityFee, Weight, }, StorageValue, @@ -141,7 +143,7 @@ parameter_types! { /// We allow for 2 seconds of compute with a 6 second average block time. pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::with_sensible_defaults( - (2u64 * WEIGHT_PER_SECOND).set_proof_size(u64::MAX), + Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX), NORMAL_DISPATCH_RATIO, ); pub BlockLength: frame_system::limits::BlockLength = frame_system::limits::BlockLength diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 44d8e28706..a754fac1da 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -38,7 +38,9 @@ use frame_support::{ WithdrawReasons, }, weights::{ - constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND}, + constants::{ + BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_REF_TIME_PER_SECOND, + }, ConstantMultiplier, IdentityFee, Weight, }, PalletId, RuntimeDebug, @@ -173,7 +175,8 @@ const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(10); /// by Operational extrinsics. const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); /// We allow for 2 seconds of compute with a 6 second average block time, with maximum proof size. -const MAXIMUM_BLOCK_WEIGHT: Weight = WEIGHT_PER_SECOND.saturating_mul(2).set_proof_size(u64::MAX); +const MAXIMUM_BLOCK_WEIGHT: Weight = + Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2), u64::MAX); parameter_types! { pub const BlockHashCount: BlockNumber = 2400; diff --git a/docs/Upgrading-2.0-to-3.0.md b/docs/Upgrading-2.0-to-3.0.md index 7540e0d5b5..906018db9a 100644 --- a/docs/Upgrading-2.0-to-3.0.md +++ b/docs/Upgrading-2.0-to-3.0.md @@ -100,12 +100,12 @@ And update the overall definition for weights on frame and a few related types a +/// by Operational extrinsics. +const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); +/// We allow for 2 seconds of compute with a 6 second average block time. -+const MAXIMUM_BLOCK_WEIGHT: Weight = 2u64 * WEIGHT_PER_SECOND; ++const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX); + parameter_types! { pub const BlockHashCount: BlockNumber = 2400; - /// We allow for 2 seconds of compute with a 6 second average block time. -- pub const MaximumBlockWeight: Weight = 2u64 * WEIGHT_PER_SECOND; +- pub const MaximumBlockWeight: Weight = Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX); - pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75); - /// Assume 10% of weight for average on_initialize calls. - pub MaximumExtrinsicWeight: Weight = diff --git a/frame/babe/src/default_weights.rs b/frame/babe/src/default_weights.rs index d3e0c9d044..f864fd18d8 100644 --- a/frame/babe/src/default_weights.rs +++ b/frame/babe/src/default_weights.rs @@ -19,7 +19,7 @@ //! This file was not auto-generated. use frame_support::weights::{ - constants::{RocksDbWeight as DbWeight, WEIGHT_PER_MICROS, WEIGHT_PER_NANOS}, + constants::{RocksDbWeight as DbWeight, WEIGHT_REF_TIME_PER_MICROS, WEIGHT_REF_TIME_PER_NANOS}, Weight, }; @@ -38,17 +38,20 @@ impl crate::WeightInfo for () { const MAX_NOMINATORS: u64 = 200; // checking membership proof - let ref_time_weight = (35u64 * WEIGHT_PER_MICROS) - .saturating_add((175u64 * WEIGHT_PER_NANOS).saturating_mul(validator_count)) + Weight::from_ref_time(35u64 * WEIGHT_REF_TIME_PER_MICROS) + .saturating_add( + Weight::from_ref_time(175u64 * WEIGHT_REF_TIME_PER_NANOS) + .saturating_mul(validator_count), + ) .saturating_add(DbWeight::get().reads(5)) // check equivocation proof - .saturating_add(110u64 * WEIGHT_PER_MICROS) + .saturating_add(Weight::from_ref_time(110u64 * WEIGHT_REF_TIME_PER_MICROS)) // report offence - .saturating_add(110u64 * WEIGHT_PER_MICROS) - .saturating_add(25u64 * WEIGHT_PER_MICROS * MAX_NOMINATORS) + .saturating_add(Weight::from_ref_time(110u64 * WEIGHT_REF_TIME_PER_MICROS)) + .saturating_add(Weight::from_ref_time( + 25u64 * WEIGHT_REF_TIME_PER_MICROS * MAX_NOMINATORS, + )) .saturating_add(DbWeight::get().reads(14 + 3 * MAX_NOMINATORS)) - .saturating_add(DbWeight::get().writes(10 + 3 * MAX_NOMINATORS)); - - ref_time_weight + .saturating_add(DbWeight::get().writes(10 + 3 * MAX_NOMINATORS)) } } diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index f4c8889ef0..f4cba0c85b 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -40,7 +40,7 @@ use frame_support::{ fungibles::Lockable, BalanceStatus, ConstU32, ConstU64, Contains, Currency, Get, OnIdle, OnInitialize, ReservableCurrency, WithdrawReasons, }, - weights::{constants::WEIGHT_PER_SECOND, Weight}, + weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}, }; use frame_system::{self as system, EventRecord, Phase}; use pretty_assertions::{assert_eq, assert_ne}; @@ -285,7 +285,7 @@ impl RegisteredChainExtension for TempStorageExtension { parameter_types! { pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::simple_max( - (2u64 * WEIGHT_PER_SECOND).set_proof_size(u64::MAX), + Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX), ); pub static ExistentialDeposit: u64 = 1; } diff --git a/frame/democracy/src/tests.rs b/frame/democracy/src/tests.rs index eceb1a3400..41b2790350 100644 --- a/frame/democracy/src/tests.rs +++ b/frame/democracy/src/tests.rs @@ -77,7 +77,9 @@ impl Contains for BaseFilter { parameter_types! { pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(frame_support::weights::constants::WEIGHT_PER_SECOND.set_proof_size(u64::MAX)); + frame_system::limits::BlockWeights::simple_max( + Weight::from_parts(frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND, u64::MAX), + ); } impl frame_system::Config for Test { type BaseCallFilter = BaseFilter; diff --git a/frame/election-provider-multi-phase/src/mock.rs b/frame/election-provider-multi-phase/src/mock.rs index 8ab7e5bbf7..347a4f1918 100644 --- a/frame/election-provider-multi-phase/src/mock.rs +++ b/frame/election-provider-multi-phase/src/mock.rs @@ -239,7 +239,7 @@ parameter_types! { pub const ExistentialDeposit: u64 = 1; pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights ::with_sensible_defaults( - Weight::from_parts(2u64 * constants::WEIGHT_PER_SECOND.ref_time(), u64::MAX), + Weight::from_parts(2u64 * constants::WEIGHT_REF_TIME_PER_SECOND, u64::MAX), NORMAL_DISPATCH_RATIO, ); } diff --git a/frame/fast-unstake/src/mock.rs b/frame/fast-unstake/src/mock.rs index d66f4ba566..b67dcf581e 100644 --- a/frame/fast-unstake/src/mock.rs +++ b/frame/fast-unstake/src/mock.rs @@ -21,7 +21,7 @@ use frame_support::{ pallet_prelude::*, parameter_types, traits::{ConstU64, Currency}, - weights::constants::WEIGHT_PER_SECOND, + weights::constants::WEIGHT_REF_TIME_PER_SECOND, }; use sp_runtime::traits::{Convert, IdentityLookup}; @@ -37,7 +37,7 @@ pub type T = Runtime; parameter_types! { pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::simple_max( - (2u64 * WEIGHT_PER_SECOND).set_proof_size(u64::MAX), + Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX), ); } diff --git a/frame/grandpa/src/default_weights.rs b/frame/grandpa/src/default_weights.rs index 4ca94dd576..ba343cabac 100644 --- a/frame/grandpa/src/default_weights.rs +++ b/frame/grandpa/src/default_weights.rs @@ -19,7 +19,7 @@ //! This file was not auto-generated. use frame_support::weights::{ - constants::{RocksDbWeight as DbWeight, WEIGHT_PER_MICROS, WEIGHT_PER_NANOS}, + constants::{RocksDbWeight as DbWeight, WEIGHT_REF_TIME_PER_MICROS, WEIGHT_REF_TIME_PER_NANOS}, Weight, }; @@ -34,14 +34,19 @@ impl crate::WeightInfo for () { const MAX_NOMINATORS: u64 = 200; // checking membership proof - (35u64 * WEIGHT_PER_MICROS) - .saturating_add((175u64 * WEIGHT_PER_NANOS).saturating_mul(validator_count)) + Weight::from_ref_time(35u64 * WEIGHT_REF_TIME_PER_MICROS) + .saturating_add( + Weight::from_ref_time(175u64 * WEIGHT_REF_TIME_PER_NANOS) + .saturating_mul(validator_count), + ) .saturating_add(DbWeight::get().reads(5)) // check equivocation proof - .saturating_add(95u64 * WEIGHT_PER_MICROS) + .saturating_add(Weight::from_ref_time(95u64 * WEIGHT_REF_TIME_PER_MICROS)) // report offence - .saturating_add(110u64 * WEIGHT_PER_MICROS) - .saturating_add(25u64 * WEIGHT_PER_MICROS * MAX_NOMINATORS) + .saturating_add(Weight::from_ref_time(110u64 * WEIGHT_REF_TIME_PER_MICROS)) + .saturating_add(Weight::from_ref_time( + 25u64 * WEIGHT_REF_TIME_PER_MICROS * MAX_NOMINATORS, + )) .saturating_add(DbWeight::get().reads(14 + 3 * MAX_NOMINATORS)) .saturating_add(DbWeight::get().writes(10 + 3 * MAX_NOMINATORS)) // fetching set id -> session index mappings @@ -49,6 +54,7 @@ impl crate::WeightInfo for () { } fn note_stalled() -> Weight { - (3u64 * WEIGHT_PER_MICROS).saturating_add(DbWeight::get().writes(1)) + Weight::from_ref_time(3u64 * WEIGHT_REF_TIME_PER_MICROS) + .saturating_add(DbWeight::get().writes(1)) } } diff --git a/frame/lottery/src/weights.rs b/frame/lottery/src/weights.rs index c0936ae6c8..e9ee528cc4 100644 --- a/frame/lottery/src/weights.rs +++ b/frame/lottery/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_lottery //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-18, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-12-08, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 @@ -67,33 +67,33 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) // Storage: Lottery Tickets (r:0 w:1) fn buy_ticket() -> Weight { - // Minimum execution time: 53_735 nanoseconds. - Weight::from_ref_time(54_235_000) + // Minimum execution time: 52_479 nanoseconds. + Weight::from_ref_time(53_225_000) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) } // Storage: Lottery CallIndices (r:0 w:1) /// The range of component `n` is `[0, 10]`. fn set_calls(n: u32, ) -> Weight { - // Minimum execution time: 15_065 nanoseconds. - Weight::from_ref_time(16_467_398) - // Standard Error: 5_392 - .saturating_add(Weight::from_ref_time(294_914).saturating_mul(n.into())) + // Minimum execution time: 14_433 nanoseconds. + Weight::from_ref_time(15_660_780) + // Standard Error: 5_894 + .saturating_add(Weight::from_ref_time(290_482).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Lottery Lottery (r:1 w:1) // Storage: Lottery LotteryIndex (r:1 w:1) // Storage: System Account (r:1 w:1) fn start_lottery() -> Weight { - // Minimum execution time: 45_990 nanoseconds. - Weight::from_ref_time(46_789_000) + // Minimum execution time: 43_683 nanoseconds. + Weight::from_ref_time(44_580_000) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } // Storage: Lottery Lottery (r:1 w:1) fn stop_repeat() -> Weight { - // Minimum execution time: 10_783 nanoseconds. - Weight::from_ref_time(11_180_000) + // Minimum execution time: 10_514 nanoseconds. + Weight::from_ref_time(10_821_000) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -103,8 +103,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Lottery TicketsCount (r:1 w:1) // Storage: Lottery Tickets (r:1 w:0) fn on_initialize_end() -> Weight { - // Minimum execution time: 62_088 nanoseconds. - Weight::from_ref_time(63_670_000) + // Minimum execution time: 60_254 nanoseconds. + Weight::from_ref_time(61_924_000) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -115,8 +115,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Lottery Tickets (r:1 w:0) // Storage: Lottery LotteryIndex (r:1 w:1) fn on_initialize_repeat() -> Weight { - // Minimum execution time: 64_953 nanoseconds. - Weight::from_ref_time(65_465_000) + // Minimum execution time: 61_552 nanoseconds. + Weight::from_ref_time(62_152_000) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(5)) } @@ -132,33 +132,33 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) // Storage: Lottery Tickets (r:0 w:1) fn buy_ticket() -> Weight { - // Minimum execution time: 53_735 nanoseconds. - Weight::from_ref_time(54_235_000) + // Minimum execution time: 52_479 nanoseconds. + Weight::from_ref_time(53_225_000) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().writes(4)) } // Storage: Lottery CallIndices (r:0 w:1) /// The range of component `n` is `[0, 10]`. fn set_calls(n: u32, ) -> Weight { - // Minimum execution time: 15_065 nanoseconds. - Weight::from_ref_time(16_467_398) - // Standard Error: 5_392 - .saturating_add(Weight::from_ref_time(294_914).saturating_mul(n.into())) + // Minimum execution time: 14_433 nanoseconds. + Weight::from_ref_time(15_660_780) + // Standard Error: 5_894 + .saturating_add(Weight::from_ref_time(290_482).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Lottery Lottery (r:1 w:1) // Storage: Lottery LotteryIndex (r:1 w:1) // Storage: System Account (r:1 w:1) fn start_lottery() -> Weight { - // Minimum execution time: 45_990 nanoseconds. - Weight::from_ref_time(46_789_000) + // Minimum execution time: 43_683 nanoseconds. + Weight::from_ref_time(44_580_000) .saturating_add(RocksDbWeight::get().reads(3)) .saturating_add(RocksDbWeight::get().writes(3)) } // Storage: Lottery Lottery (r:1 w:1) fn stop_repeat() -> Weight { - // Minimum execution time: 10_783 nanoseconds. - Weight::from_ref_time(11_180_000) + // Minimum execution time: 10_514 nanoseconds. + Weight::from_ref_time(10_821_000) .saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().writes(1)) } @@ -168,8 +168,8 @@ impl WeightInfo for () { // Storage: Lottery TicketsCount (r:1 w:1) // Storage: Lottery Tickets (r:1 w:0) fn on_initialize_end() -> Weight { - // Minimum execution time: 62_088 nanoseconds. - Weight::from_ref_time(63_670_000) + // Minimum execution time: 60_254 nanoseconds. + Weight::from_ref_time(61_924_000) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().writes(4)) } @@ -180,8 +180,8 @@ impl WeightInfo for () { // Storage: Lottery Tickets (r:1 w:0) // Storage: Lottery LotteryIndex (r:1 w:1) fn on_initialize_repeat() -> Weight { - // Minimum execution time: 64_953 nanoseconds. - Weight::from_ref_time(65_465_000) + // Minimum execution time: 61_552 nanoseconds. + Weight::from_ref_time(62_152_000) .saturating_add(RocksDbWeight::get().reads(7)) .saturating_add(RocksDbWeight::get().writes(5)) } diff --git a/frame/merkle-mountain-range/src/default_weights.rs b/frame/merkle-mountain-range/src/default_weights.rs index e513e2197f..e4f9750fbc 100644 --- a/frame/merkle-mountain-range/src/default_weights.rs +++ b/frame/merkle-mountain-range/src/default_weights.rs @@ -19,7 +19,7 @@ //! This file was not auto-generated. use frame_support::weights::{ - constants::{RocksDbWeight as DbWeight, WEIGHT_PER_NANOS}, + constants::{RocksDbWeight as DbWeight, WEIGHT_REF_TIME_PER_NANOS}, Weight, }; @@ -28,7 +28,7 @@ impl crate::WeightInfo for () { // Reading the parent hash. let leaf_weight = DbWeight::get().reads(1); // Blake2 hash cost. - let hash_weight = 2u64 * WEIGHT_PER_NANOS; + let hash_weight = Weight::from_ref_time(2u64 * WEIGHT_REF_TIME_PER_NANOS); // No-op hook. let hook_weight = Weight::zero(); diff --git a/frame/offences/benchmarking/src/mock.rs b/frame/offences/benchmarking/src/mock.rs index e022d81c5b..de3a4eca63 100644 --- a/frame/offences/benchmarking/src/mock.rs +++ b/frame/offences/benchmarking/src/mock.rs @@ -24,7 +24,7 @@ use frame_election_provider_support::{onchain, SequentialPhragmen}; use frame_support::{ parameter_types, traits::{ConstU32, ConstU64}, - weights::constants::WEIGHT_PER_SECOND, + weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}, }; use frame_system as system; use pallet_session::historical as pallet_session_historical; @@ -41,7 +41,7 @@ type Balance = u64; parameter_types! { pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::simple_max( - 2u64 * WEIGHT_PER_SECOND + Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX) ); } diff --git a/frame/offences/src/mock.rs b/frame/offences/src/mock.rs index 31dac8d51d..8e4256ec3d 100644 --- a/frame/offences/src/mock.rs +++ b/frame/offences/src/mock.rs @@ -26,7 +26,7 @@ use frame_support::{ parameter_types, traits::{ConstU32, ConstU64}, weights::{ - constants::{RocksDbWeight, WEIGHT_PER_SECOND}, + constants::{RocksDbWeight, WEIGHT_REF_TIME_PER_SECOND}, Weight, }, }; @@ -85,7 +85,9 @@ frame_support::construct_runtime!( parameter_types! { pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(2u64 * WEIGHT_PER_SECOND); + frame_system::limits::BlockWeights::simple_max( + Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX), + ); } impl frame_system::Config for Runtime { type BaseCallFilter = frame_support::traits::Everything; diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index 16e4e5ddd7..d3affda052 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -115,7 +115,7 @@ impl FindAuthor for Author11 { parameter_types! { pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::simple_max( - frame_support::weights::constants::WEIGHT_PER_SECOND * 2 + Weight::from_parts(frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND * 2, u64::MAX), ); pub static SessionsPerEra: SessionIndex = 3; pub static ExistentialDeposit: Balance = 1; diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 6609b90876..78429122d0 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4365,9 +4365,10 @@ mod election_data_provider { #[test] fn targets_2sec_block() { let mut validators = 1000; - while ::WeightInfo::get_npos_targets(validators) - .all_lt(2u64 * frame_support::weights::constants::WEIGHT_PER_SECOND) - { + while ::WeightInfo::get_npos_targets(validators).all_lt(Weight::from_parts( + 2u64 * frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND, + u64::MAX, + )) { validators += 1; } @@ -4384,8 +4385,10 @@ mod election_data_provider { let mut nominators = 1000; while ::WeightInfo::get_npos_voters(validators, nominators, slashing_spans) - .all_lt(2u64 * frame_support::weights::constants::WEIGHT_PER_SECOND) - { + .all_lt(Weight::from_parts( + 2u64 * frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND, + u64::MAX, + )) { nominators += 1; } diff --git a/frame/support/src/weights/block_weights.rs b/frame/support/src/weights/block_weights.rs index 5c8e1f1c86..b68c1fb508 100644 --- a/frame/support/src/weights/block_weights.rs +++ b/frame/support/src/weights/block_weights.rs @@ -37,7 +37,7 @@ // --repeat=100 use sp_core::parameter_types; -use sp_weights::{constants::WEIGHT_PER_NANOS, Weight}; +use sp_weights::{constants::WEIGHT_REF_TIME_PER_NANOS, Weight}; parameter_types! { /// Time to execute an empty block. @@ -53,7 +53,8 @@ parameter_types! { /// 99th: 390_723 /// 95th: 365_799 /// 75th: 361_582 - pub const BlockExecutionWeight: Weight = WEIGHT_PER_NANOS.saturating_mul(358_523); + pub const BlockExecutionWeight: Weight = + Weight::from_ref_time(WEIGHT_REF_TIME_PER_NANOS.saturating_mul(358_523)); } #[cfg(test)] @@ -69,12 +70,12 @@ mod test_weights { // At least 100 µs. assert!( - w.ref_time() >= 100u64 * constants::WEIGHT_PER_MICROS.ref_time(), + w.ref_time() >= 100u64 * constants::WEIGHT_REF_TIME_PER_MICROS, "Weight should be at least 100 µs." ); // At most 50 ms. assert!( - w.ref_time() <= 50u64 * constants::WEIGHT_PER_MILLIS.ref_time(), + w.ref_time() <= 50u64 * constants::WEIGHT_REF_TIME_PER_MILLIS, "Weight should be at most 50 ms." ); } diff --git a/frame/support/src/weights/extrinsic_weights.rs b/frame/support/src/weights/extrinsic_weights.rs index 1db2281dfe..ced1fb9162 100644 --- a/frame/support/src/weights/extrinsic_weights.rs +++ b/frame/support/src/weights/extrinsic_weights.rs @@ -37,7 +37,7 @@ // --repeat=100 use sp_core::parameter_types; -use sp_weights::{constants::WEIGHT_PER_NANOS, Weight}; +use sp_weights::{constants::WEIGHT_REF_TIME_PER_NANOS, Weight}; parameter_types! { /// Time to execute a NO-OP extrinsic, for example `System::remark`. @@ -53,7 +53,8 @@ parameter_types! { /// 99th: 99_202 /// 95th: 99_163 /// 75th: 99_030 - pub const ExtrinsicBaseWeight: Weight = WEIGHT_PER_NANOS.saturating_mul(98_974); + pub const ExtrinsicBaseWeight: Weight = + Weight::from_ref_time(WEIGHT_REF_TIME_PER_NANOS.saturating_mul(98_974)); } #[cfg(test)] @@ -69,12 +70,12 @@ mod test_weights { // At least 10 µs. assert!( - w.ref_time() >= 10u64 * constants::WEIGHT_PER_MICROS.ref_time(), + w.ref_time() >= 10u64 * constants::WEIGHT_REF_TIME_PER_MICROS, "Weight should be at least 10 µs." ); // At most 1 ms. assert!( - w.ref_time() <= constants::WEIGHT_PER_MILLIS.ref_time(), + w.ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, "Weight should be at most 1 ms." ); } diff --git a/frame/support/src/weights/paritydb_weights.rs b/frame/support/src/weights/paritydb_weights.rs index 344e6cf0dd..6fd1112ee2 100644 --- a/frame/support/src/weights/paritydb_weights.rs +++ b/frame/support/src/weights/paritydb_weights.rs @@ -24,8 +24,8 @@ pub mod constants { /// ParityDB can be enabled with a feature flag, but is still experimental. These weights /// are available for brave runtime engineers who may want to try this out as default. pub const ParityDbWeight: RuntimeDbWeight = RuntimeDbWeight { - read: 8_000 * constants::WEIGHT_PER_NANOS.ref_time(), - write: 50_000 * constants::WEIGHT_PER_NANOS.ref_time(), + read: 8_000 * constants::WEIGHT_REF_TIME_PER_NANOS, + write: 50_000 * constants::WEIGHT_REF_TIME_PER_NANOS, }; } @@ -41,20 +41,20 @@ pub mod constants { fn sane() { // At least 1 µs. assert!( - W::get().reads(1).ref_time() >= constants::WEIGHT_PER_MICROS.ref_time(), + W::get().reads(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, "Read weight should be at least 1 µs." ); assert!( - W::get().writes(1).ref_time() >= constants::WEIGHT_PER_MICROS.ref_time(), + W::get().writes(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, "Write weight should be at least 1 µs." ); // At most 1 ms. assert!( - W::get().reads(1).ref_time() <= constants::WEIGHT_PER_MILLIS.ref_time(), + W::get().reads(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, "Read weight should be at most 1 ms." ); assert!( - W::get().writes(1).ref_time() <= constants::WEIGHT_PER_MILLIS.ref_time(), + W::get().writes(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, "Write weight should be at most 1 ms." ); } diff --git a/frame/support/src/weights/rocksdb_weights.rs b/frame/support/src/weights/rocksdb_weights.rs index 4dec2d8c87..b18b387de9 100644 --- a/frame/support/src/weights/rocksdb_weights.rs +++ b/frame/support/src/weights/rocksdb_weights.rs @@ -24,8 +24,8 @@ pub mod constants { /// By default, Substrate uses RocksDB, so this will be the weight used throughout /// the runtime. pub const RocksDbWeight: RuntimeDbWeight = RuntimeDbWeight { - read: 25_000 * constants::WEIGHT_PER_NANOS.ref_time(), - write: 100_000 * constants::WEIGHT_PER_NANOS.ref_time(), + read: 25_000 * constants::WEIGHT_REF_TIME_PER_NANOS, + write: 100_000 * constants::WEIGHT_REF_TIME_PER_NANOS, }; } @@ -41,20 +41,20 @@ pub mod constants { fn sane() { // At least 1 µs. assert!( - W::get().reads(1).ref_time() >= constants::WEIGHT_PER_MICROS.ref_time(), + W::get().reads(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, "Read weight should be at least 1 µs." ); assert!( - W::get().writes(1).ref_time() >= constants::WEIGHT_PER_MICROS.ref_time(), + W::get().writes(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, "Write weight should be at least 1 µs." ); // At most 1 ms. assert!( - W::get().reads(1).ref_time() <= constants::WEIGHT_PER_MILLIS.ref_time(), + W::get().reads(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, "Read weight should be at most 1 ms." ); assert!( - W::get().writes(1).ref_time() <= constants::WEIGHT_PER_MILLIS.ref_time(), + W::get().writes(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, "Write weight should be at most 1 ms." ); } diff --git a/frame/system/src/limits.rs b/frame/system/src/limits.rs index eb95b699eb..54d27c5b9e 100644 --- a/frame/system/src/limits.rs +++ b/frame/system/src/limits.rs @@ -208,7 +208,7 @@ pub struct BlockWeights { impl Default for BlockWeights { fn default() -> Self { Self::with_sensible_defaults( - Weight::from_parts(constants::WEIGHT_PER_SECOND.ref_time(), u64::MAX), + Weight::from_parts(constants::WEIGHT_REF_TIME_PER_SECOND, u64::MAX), DEFAULT_NORMAL_RATIO, ) } diff --git a/primitives/weights/src/lib.rs b/primitives/weights/src/lib.rs index af9e730fbf..928080d139 100644 --- a/primitives/weights/src/lib.rs +++ b/primitives/weights/src/lib.rs @@ -47,12 +47,13 @@ pub use weight_meter::*; pub use weight_v2::*; pub mod constants { - use super::Weight; + pub const WEIGHT_REF_TIME_PER_SECOND: u64 = 1_000_000_000_000; + pub const WEIGHT_REF_TIME_PER_MILLIS: u64 = 1_000_000_000; + pub const WEIGHT_REF_TIME_PER_MICROS: u64 = 1_000_000; + pub const WEIGHT_REF_TIME_PER_NANOS: u64 = 1_000; - pub const WEIGHT_PER_SECOND: Weight = Weight::from_ref_time(1_000_000_000_000); - pub const WEIGHT_PER_MILLIS: Weight = Weight::from_ref_time(1_000_000_000); - pub const WEIGHT_PER_MICROS: Weight = Weight::from_ref_time(1_000_000); - pub const WEIGHT_PER_NANOS: Weight = Weight::from_ref_time(1_000); + pub const WEIGHT_PROOF_SIZE_PER_MB: u64 = 1024 * 1024; + pub const WEIGHT_PROOF_SIZE_PER_KB: u64 = 1024; } /// The old weight type. diff --git a/utils/frame/benchmarking-cli/src/block/bench.rs b/utils/frame/benchmarking-cli/src/block/bench.rs index 5a67b11f49..578158d8a2 100644 --- a/utils/frame/benchmarking-cli/src/block/bench.rs +++ b/utils/frame/benchmarking-cli/src/block/bench.rs @@ -18,7 +18,7 @@ //! Contains the core benchmarking logic. use codec::DecodeAll; -use frame_support::weights::constants::WEIGHT_PER_NANOS; +use frame_support::weights::constants::WEIGHT_REF_TIME_PER_NANOS; use frame_system::ConsumedWeight; use sc_block_builder::{BlockBuilderApi, BlockBuilderProvider}; use sc_cli::{Error, Result}; @@ -148,7 +148,7 @@ where let weight = ConsumedWeight::decode_all(&mut raw_weight)?; // Should be divisible, but still use floats in case we ever change that. - Ok((weight.total().ref_time() as f64 / WEIGHT_PER_NANOS.ref_time() as f64).floor() + Ok((weight.total().ref_time() as f64 / WEIGHT_REF_TIME_PER_NANOS as f64).floor() as NanoSeconds) } diff --git a/utils/frame/benchmarking-cli/src/overhead/README.md b/utils/frame/benchmarking-cli/src/overhead/README.md index b21d051e9d..1584c2affe 100644 --- a/utils/frame/benchmarking-cli/src/overhead/README.md +++ b/utils/frame/benchmarking-cli/src/overhead/README.md @@ -30,7 +30,8 @@ The file will contain the concrete weight value and various statistics about the /// 99th: 3_631_863 /// 95th: 3_595_674 /// 75th: 3_526_435 -pub const BlockExecutionWeight: Weight = WEIGHT_PER_NANOS.saturating_mul(3_532_484); +pub const BlockExecutionWeight: Weight = + Weight::from_ref_time(WEIGHT_REF_TIME_PER_NANOS.saturating_mul(3_532_484)); ``` In this example it takes 3.5 ms to execute an empty block. That means that it always takes at least 3.5 ms to execute *any* block. @@ -59,7 +60,8 @@ The relevant section in the output file looks like this: /// 99th: 68_758 /// 95th: 67_843 /// 75th: 67_749 -pub const ExtrinsicBaseWeight: Weight = WEIGHT_PER_NANOS.saturating_mul(67_745); +pub const ExtrinsicBaseWeight: Weight = + Weight::from_ref_time(WEIGHT_REF_TIME_PER_NANOS.saturating_mul(67_745)); ``` In this example it takes 67.7 µs to execute a NO-OP extrinsic. That means that it always takes at least 67.7 µs to execute *any* extrinsic. diff --git a/utils/frame/benchmarking-cli/src/overhead/weights.hbs b/utils/frame/benchmarking-cli/src/overhead/weights.hbs index 8d1a369372..c54393d200 100644 --- a/utils/frame/benchmarking-cli/src/overhead/weights.hbs +++ b/utils/frame/benchmarking-cli/src/overhead/weights.hbs @@ -14,7 +14,7 @@ {{/each}} use sp_core::parameter_types; -use sp_weights::{constants::WEIGHT_PER_NANOS, Weight}; +use sp_weights::{constants::WEIGHT_REF_TIME_PER_NANOS, Weight}; parameter_types! { {{#if (eq short_name "block")}} @@ -34,7 +34,8 @@ parameter_types! { /// 99th: {{underscore stats.p99}} /// 95th: {{underscore stats.p95}} /// 75th: {{underscore stats.p75}} - pub const {{long_name}}Weight: Weight = WEIGHT_PER_NANOS.saturating_mul({{underscore weight}}); + pub const {{long_name}}Weight: Weight = + Weight::from_ref_time(WEIGHT_REF_TIME_PER_NANOS.saturating_mul({{underscore weight}})); } #[cfg(test)] @@ -51,23 +52,23 @@ mod test_weights { {{#if (eq short_name "block")}} // At least 100 µs. assert!( - w.ref_time() >= 100u64 * constants::WEIGHT_PER_MICROS.ref_time(), + w.ref_time() >= 100u64 * constants::WEIGHT_REF_TIME_PER_MICROS, "Weight should be at least 100 µs." ); // At most 50 ms. assert!( - w.ref_time() <= 50u64 * constants::WEIGHT_PER_MILLIS.ref_time(), + w.ref_time() <= 50u64 * constants::WEIGHT_REF_TIME_PER_MILLIS, "Weight should be at most 50 ms." ); {{else}} // At least 10 µs. assert!( - w.ref_time() >= 10u64 * constants::WEIGHT_PER_MICROS.ref_time(), + w.ref_time() >= 10u64 * constants::WEIGHT_REF_TIME_PER_MICROS, "Weight should be at least 10 µs." ); // At most 1 ms. assert!( - w.ref_time() <= constants::WEIGHT_PER_MILLIS.ref_time(), + w.ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, "Weight should be at most 1 ms." ); {{/if}} diff --git a/utils/frame/benchmarking-cli/src/storage/README.md b/utils/frame/benchmarking-cli/src/storage/README.md index ecaf4edada..f61b7ba1bd 100644 --- a/utils/frame/benchmarking-cli/src/storage/README.md +++ b/utils/frame/benchmarking-cli/src/storage/README.md @@ -69,7 +69,7 @@ The interesting part in the generated weight file tells us the weight constants /// 99th: 18_270 /// 95th: 16_190 /// 75th: 14_819 -read: 14_262 * constants::WEIGHT_PER_NANOS, +read: 14_262 * constants::WEIGHT_REF_TIME_PER_NANOS, /// Time to write one storage item. /// Calculated by multiplying the *Average* of all values with `1.1` and adding `0`. @@ -84,7 +84,7 @@ read: 14_262 * constants::WEIGHT_PER_NANOS, /// 99th: 135_839 /// 95th: 106_129 /// 75th: 79_239 -write: 71_347 * constants::WEIGHT_PER_NANOS, +write: 71_347 * constants::WEIGHT_REF_TIME_PER_NANOS, ``` ## Arguments diff --git a/utils/frame/benchmarking-cli/src/storage/weights.hbs b/utils/frame/benchmarking-cli/src/storage/weights.hbs index 82e581cf99..135b18b193 100644 --- a/utils/frame/benchmarking-cli/src/storage/weights.hbs +++ b/utils/frame/benchmarking-cli/src/storage/weights.hbs @@ -43,7 +43,7 @@ pub mod constants { /// 99th: {{underscore read.0.p99}} /// 95th: {{underscore read.0.p95}} /// 75th: {{underscore read.0.p75}} - read: {{underscore read_weight}} * constants::WEIGHT_PER_NANOS, + read: {{underscore read_weight}} * constants::WEIGHT_REF_TIME_PER_NANOS, /// Time to write one storage item. /// Calculated by multiplying the *{{params.weight_params.weight_metric}}* of all values with `{{params.weight_params.weight_mul}}` and adding `{{params.weight_params.weight_add}}`. @@ -58,7 +58,7 @@ pub mod constants { /// 99th: {{underscore write.0.p99}} /// 95th: {{underscore write.0.p95}} /// 75th: {{underscore write.0.p75}} - write: {{underscore write_weight}} * constants::WEIGHT_PER_NANOS, + write: {{underscore write_weight}} * constants::WEIGHT_REF_TIME_PER_NANOS, }; } @@ -74,20 +74,20 @@ pub mod constants { fn bound() { // At least 1 µs. assert!( - W::get().reads(1).ref_time() >= constants::WEIGHT_PER_MICROS.ref_time(), + W::get().reads(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, "Read weight should be at least 1 µs." ); assert!( - W::get().writes(1).ref_time() >= constants::WEIGHT_PER_MICROS.ref_time(), + W::get().writes(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, "Write weight should be at least 1 µs." ); // At most 1 ms. assert!( - W::get().reads(1).ref_time() <= constants::WEIGHT_PER_MILLIS.ref_time(), + W::get().reads(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, "Read weight should be at most 1 ms." ); assert!( - W::get().writes(1).ref_time() <= constants::WEIGHT_PER_MILLIS.ref_time(), + W::get().writes(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, "Write weight should be at most 1 ms." ); } From 02a9deaa007e358a2aa537ce624497c3ef3cf2ca Mon Sep 17 00:00:00 2001 From: Vlad Date: Thu, 8 Dec 2022 17:58:52 +0000 Subject: [PATCH 014/127] Checkout to the branch HEAD explicitly in `build-linux-substrate` (#12876) --- scripts/ci/gitlab/pipeline/build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/ci/gitlab/pipeline/build.yml b/scripts/ci/gitlab/pipeline/build.yml index 5bbc3fb8f7..2f8cff7b3f 100644 --- a/scripts/ci/gitlab/pipeline/build.yml +++ b/scripts/ci/gitlab/pipeline/build.yml @@ -64,6 +64,9 @@ build-linux-substrate: before_script: - mkdir -p ./artifacts/substrate/ - !reference [.rusty-cachier, before_script] + # tldr: we need to checkout the branch HEAD explicitly because of our dynamic versioning approach while building the substrate binary + # see https://github.com/paritytech/ci_cd/issues/682#issuecomment-1340953589 + - git checkout -B "$CI_COMMIT_REF_NAME" "$CI_COMMIT_SHA" script: - rusty-cachier snapshot create - WASM_BUILD_NO_COLOR=1 time cargo build --locked --release --verbose From 9a0644ca46410613d332d7a2754c502d89146e2f Mon Sep 17 00:00:00 2001 From: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Date: Thu, 8 Dec 2022 22:15:14 +0200 Subject: [PATCH 015/127] cli: Improve pruning documentation (#12819) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * cli: Improve pruning documentation Signed-off-by: Alexandru Vasile * cli: Keep `finalized` notation and remove `canonical` one * cli: Fix cargo doc * cli: `PruningModeClap` IR enum Signed-off-by: Alexandru Vasile * cli: Convert PruningModeClap into pruning modes Signed-off-by: Alexandru Vasile * cli: Use `PruningModeClap` Signed-off-by: Alexandru Vasile * cli: Rename to `DatabasePruningMode` Signed-off-by: Alexandru Vasile * cli: Implement `FromStr` instead of `clap::ValueEnum` Signed-off-by: Alexandru Vasile * Update client/cli/src/params/pruning_params.rs Co-authored-by: Bastian Köcher * Fix clippy Signed-off-by: Alexandru Vasile * cli: Add option documentation back Signed-off-by: Alexandru Vasile * Apply suggestions from code review Signed-off-by: Alexandru Vasile Co-authored-by: Bastian Köcher --- client/cli/src/params/pruning_params.rs | 114 ++++++++++++++++-------- 1 file changed, 75 insertions(+), 39 deletions(-) diff --git a/client/cli/src/params/pruning_params.rs b/client/cli/src/params/pruning_params.rs index 2da1de9197..7e50f53d71 100644 --- a/client/cli/src/params/pruning_params.rs +++ b/client/cli/src/params/pruning_params.rs @@ -23,57 +23,93 @@ use sc_service::{BlocksPruning, PruningMode}; /// Parameters to define the pruning mode #[derive(Debug, Clone, PartialEq, Args)] pub struct PruningParams { - /// Specify the state pruning mode, a number of blocks to keep or 'archive'. + /// Specify the state pruning mode. /// - /// Default is to keep only the last 256 blocks, - /// otherwise, the state can be kept for all of the blocks (i.e 'archive'), - /// or for all of the canonical blocks (i.e 'archive-canonical'). - #[arg(alias = "pruning", long, value_name = "PRUNING_MODE")] - pub state_pruning: Option, - /// Specify the blocks pruning mode, a number of blocks to keep or 'archive'. + /// This mode specifies when the block's state (ie, storage) + /// should be pruned (ie, removed) from the database. /// - /// Default is to keep all finalized blocks. - /// otherwise, all blocks can be kept (i.e 'archive'), - /// or for all canonical blocks (i.e 'archive-canonical'), - /// or for the last N blocks (i.e a number). + /// Possible values: + /// 'archive' Keep the state of all blocks. + /// 'archive-canonical' Keep only the state of finalized blocks. + /// number Keep the state of the last number of finalized blocks. + #[arg(alias = "pruning", long, value_name = "PRUNING_MODE", default_value = "256")] + pub state_pruning: DatabasePruningMode, + /// Specify the blocks pruning mode. /// - /// NOTE: only finalized blocks are subject for removal! - #[arg(alias = "keep-blocks", long, value_name = "COUNT")] - pub blocks_pruning: Option, + /// This mode specifies when the block's body (including justifications) + /// should be pruned (ie, removed) from the database. + /// + /// Possible values: + /// 'archive' Keep all blocks. + /// 'archive-canonical' Keep only finalized blocks. + /// number Keep the last `number` of finalized blocks. + #[arg( + alias = "keep-blocks", + long, + value_name = "PRUNING_MODE", + default_value = "archive-canonical" + )] + pub blocks_pruning: DatabasePruningMode, } impl PruningParams { /// Get the pruning value from the parameters pub fn state_pruning(&self) -> error::Result> { - self.state_pruning - .as_ref() - .map(|s| match s.as_str() { - "archive" => Ok(PruningMode::ArchiveAll), - "archive-canonical" => Ok(PruningMode::ArchiveCanonical), - bc => bc - .parse() - .map_err(|_| { - error::Error::Input("Invalid state pruning mode specified".to_string()) - }) - .map(PruningMode::blocks_pruning), - }) - .transpose() + Ok(Some(self.state_pruning.into())) } /// Get the block pruning value from the parameters pub fn blocks_pruning(&self) -> error::Result { - match self.blocks_pruning.as_ref() { - Some(bp) => match bp.as_str() { - "archive" => Ok(BlocksPruning::KeepAll), - "archive-canonical" => Ok(BlocksPruning::KeepFinalized), - bc => bc - .parse() - .map_err(|_| { - error::Error::Input("Invalid blocks pruning mode specified".to_string()) - }) - .map(BlocksPruning::Some), - }, - None => Ok(BlocksPruning::KeepFinalized), + Ok(self.blocks_pruning.into()) + } +} + +/// Specifies the pruning mode of the database. +/// +/// This specifies when the block's data (either state via `--state-pruning` +/// or body via `--blocks-pruning`) should be pruned (ie, removed) from +/// the database. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum DatabasePruningMode { + /// Keep the data of all blocks. + Archive, + /// Keep only the data of finalized blocks. + ArchiveCanonical, + /// Keep the data of the last number of finalized blocks. + Custom(u32), +} + +impl std::str::FromStr for DatabasePruningMode { + type Err = String; + + fn from_str(input: &str) -> Result { + match input { + "archive" => Ok(Self::Archive), + "archive-canonical" => Ok(Self::ArchiveCanonical), + bc => bc + .parse() + .map_err(|_| "Invalid pruning mode specified".to_string()) + .map(Self::Custom), + } + } +} + +impl Into for DatabasePruningMode { + fn into(self) -> PruningMode { + match self { + DatabasePruningMode::Archive => PruningMode::ArchiveAll, + DatabasePruningMode::ArchiveCanonical => PruningMode::ArchiveCanonical, + DatabasePruningMode::Custom(n) => PruningMode::blocks_pruning(n), + } + } +} + +impl Into for DatabasePruningMode { + fn into(self) -> BlocksPruning { + match self { + DatabasePruningMode::Archive => BlocksPruning::KeepAll, + DatabasePruningMode::ArchiveCanonical => BlocksPruning::KeepFinalized, + DatabasePruningMode::Custom(n) => BlocksPruning::Some(n), } } } From e6bbc53af99458c87b6fafe02314b4959b8da2b0 Mon Sep 17 00:00:00 2001 From: Anthony Alaribe Date: Fri, 9 Dec 2022 11:40:59 +0200 Subject: [PATCH 016/127] Revert "Move LockableCurrency trait to fungibles::Lockable and deprecate LockableCurrency (#12798)" (#12882) This reverts commit ea3ca3f757ff9d9559665719a77da81f4cf0f0ce. --- bin/node/runtime/src/lib.rs | 8 +-- frame/balances/README.md | 8 +-- frame/balances/src/lib.rs | 26 ++++---- frame/balances/src/tests.rs | 6 +- frame/contracts/src/tests.rs | 2 +- frame/conviction-voting/src/benchmarking.rs | 2 +- frame/conviction-voting/src/lib.rs | 6 +- frame/democracy/src/lib.rs | 10 ++- frame/elections-phragmen/src/lib.rs | 10 +-- frame/executive/src/lib.rs | 9 ++- frame/referenda/src/lib.rs | 7 +- frame/staking/src/pallet/impls.rs | 5 +- frame/staking/src/pallet/mod.rs | 8 +-- frame/support/src/traits.rs | 1 - frame/support/src/traits/tokens/currency.rs | 5 +- .../src/traits/tokens/currency/lockable.rs | 48 +++++++++++++- frame/support/src/traits/tokens/fungible.rs | 1 - frame/support/src/traits/tokens/fungibles.rs | 2 - .../src/traits/tokens/fungibles/lockable.rs | 65 ------------------- frame/vesting/src/lib.rs | 11 ++-- 20 files changed, 105 insertions(+), 135 deletions(-) delete mode 100644 frame/support/src/traits/tokens/fungibles/lockable.rs diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index a754fac1da..1bb4dd6f91 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -32,9 +32,9 @@ use frame_support::{ pallet_prelude::Get, parameter_types, traits::{ - fungible::ItemOf, fungibles, AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, - ConstU32, Currency, EitherOfDiverse, EqualPrivilegeOnly, Everything, Imbalance, - InstanceFilter, KeyOwnerProofSystem, Nothing, OnUnbalanced, U128CurrencyToVote, + fungible::ItemOf, AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, + Currency, EitherOfDiverse, EqualPrivilegeOnly, Everything, Imbalance, InstanceFilter, + KeyOwnerProofSystem, LockIdentifier, Nothing, OnUnbalanced, U128CurrencyToVote, WithdrawReasons, }, weights::{ @@ -1006,7 +1006,7 @@ parameter_types! { pub const DesiredRunnersUp: u32 = 7; pub const MaxVoters: u32 = 10 * 1000; pub const MaxCandidates: u32 = 1000; - pub const ElectionsPhragmenPalletId: fungibles::LockIdentifier = *b"phrelect"; + pub const ElectionsPhragmenPalletId: LockIdentifier = *b"phrelect"; } // Make sure that there are no more than `MaxMembers` members elected via elections-phragmen. diff --git a/frame/balances/README.md b/frame/balances/README.md index d32fffbf0e..93e424a89c 100644 --- a/frame/balances/README.md +++ b/frame/balances/README.md @@ -57,7 +57,7 @@ that you need, then you can avoid coupling with the Balances module. fungible assets system. - [`ReservableCurrency`](https://docs.rs/frame-support/latest/frame_support/traits/trait.ReservableCurrency.html): Functions for dealing with assets that can be reserved from an account. -- [`Lockable`](https://docs.rs/frame-support/latest/frame_support/traits/fungibles/trait.Lockable.html): Functions for +- [`LockableCurrency`](https://docs.rs/frame-support/latest/frame_support/traits/trait.LockableCurrency.html): Functions for dealing with accounts that allow liquidity restrictions. - [`Imbalance`](https://docs.rs/frame-support/latest/frame_support/traits/trait.Imbalance.html): Functions for handling imbalances between total issuance in the system and account balances. Must be used when a function @@ -88,13 +88,13 @@ pub type NegativeImbalanceOf = <::Currency as Currency<; + type Currency: LockableCurrency; } fn update_ledger( diff --git a/frame/balances/src/lib.rs b/frame/balances/src/lib.rs index d74de37e99..381a0ffcee 100644 --- a/frame/balances/src/lib.rs +++ b/frame/balances/src/lib.rs @@ -79,7 +79,7 @@ //! - [`ReservableCurrency`](frame_support::traits::ReservableCurrency): //! - [`NamedReservableCurrency`](frame_support::traits::NamedReservableCurrency): //! Functions for dealing with assets that can be reserved from an account. -//! - [`Lockable`](frame_support::traits::fungibles::Lockable): Functions for +//! - [`LockableCurrency`](frame_support::traits::LockableCurrency): Functions for //! dealing with accounts that allow liquidity restrictions. //! - [`Imbalance`](frame_support::traits::Imbalance): Functions for handling //! imbalances between total issuance in the system and account balances. Must be used when a @@ -113,13 +113,13 @@ //! # fn main() {} //! ``` //! -//! The Staking pallet uses the `fungibles::Lockable` trait to lock a stash account's funds: +//! The Staking pallet uses the `LockableCurrency` trait to lock a stash account's funds: //! //! ``` -//! use frame_support::traits::{WithdrawReasons, fungibles, fungibles::Lockable}; +//! use frame_support::traits::{WithdrawReasons, LockableCurrency}; //! use sp_runtime::traits::Bounded; //! pub trait Config: frame_system::Config { -//! type Currency: fungibles::Lockable; +//! type Currency: LockableCurrency; //! } //! # struct StakingLedger { //! # stash: ::AccountId, @@ -171,13 +171,11 @@ use frame_support::{ ensure, pallet_prelude::DispatchResult, traits::{ - tokens::{ - fungible, fungibles, BalanceStatus as Status, DepositConsequence, WithdrawConsequence, - }, + tokens::{fungible, BalanceStatus as Status, DepositConsequence, WithdrawConsequence}, Currency, DefensiveSaturating, ExistenceRequirement, ExistenceRequirement::{AllowDeath, KeepAlive}, - Get, Imbalance, NamedReservableCurrency, OnUnbalanced, ReservableCurrency, SignedImbalance, - StoredMap, TryDrop, WithdrawReasons, + Get, Imbalance, LockIdentifier, LockableCurrency, NamedReservableCurrency, OnUnbalanced, + ReservableCurrency, SignedImbalance, StoredMap, TryDrop, WithdrawReasons, }, WeakBoundedVec, }; @@ -664,7 +662,7 @@ impl BitOr for Reasons { #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)] pub struct BalanceLock { /// An identifier for this lock. Only one lock may be in existence for each identifier. - pub id: fungibles::LockIdentifier, + pub id: LockIdentifier, /// The amount which the free balance may not drop below when this lock is in effect. pub amount: Balance, /// If true, then the lock remains in effect even for payment of transaction fees. @@ -2133,7 +2131,7 @@ where } } -impl, I: 'static> fungibles::Lockable for Pallet +impl, I: 'static> LockableCurrency for Pallet where T::Balance: MaybeSerializeDeserialize + Debug, { @@ -2144,7 +2142,7 @@ where // Set a lock on the balance of `who`. // Is a no-op if lock amount is zero or `reasons` `is_none()`. fn set_lock( - id: fungibles::LockIdentifier, + id: LockIdentifier, who: &T::AccountId, amount: T::Balance, reasons: WithdrawReasons, @@ -2166,7 +2164,7 @@ where // Extend a lock on the balance of `who`. // Is a no-op if lock amount is zero or `reasons` `is_none()`. fn extend_lock( - id: fungibles::LockIdentifier, + id: LockIdentifier, who: &T::AccountId, amount: T::Balance, reasons: WithdrawReasons, @@ -2195,7 +2193,7 @@ where Self::update_locks(who, &locks[..]); } - fn remove_lock(id: fungibles::LockIdentifier, who: &T::AccountId) { + fn remove_lock(id: LockIdentifier, who: &T::AccountId) { let mut locks = Self::locks(who); locks.retain(|l| l.id != id); Self::update_locks(who, &locks[..]); diff --git a/frame/balances/src/tests.rs b/frame/balances/src/tests.rs index 44a71b9325..83944caf9f 100644 --- a/frame/balances/src/tests.rs +++ b/frame/balances/src/tests.rs @@ -28,15 +28,15 @@ macro_rules! decl_tests { use frame_support::{ assert_noop, assert_storage_noop, assert_ok, assert_err, traits::{ - fungibles, fungibles::Lockable, WithdrawReasons, + LockableCurrency, LockIdentifier, WithdrawReasons, Currency, ReservableCurrency, ExistenceRequirement::AllowDeath } }; use pallet_transaction_payment::{ChargeTransactionPayment, Multiplier}; use frame_system::RawOrigin; - const ID_1: fungibles::LockIdentifier = *b"1 "; - const ID_2: fungibles::LockIdentifier = *b"2 "; + const ID_1: LockIdentifier = *b"1 "; + const ID_2: LockIdentifier = *b"2 "; pub const CALL: &<$test as frame_system::Config>::RuntimeCall = &RuntimeCall::Balances(pallet_balances::Call::transfer { dest: 0, value: 0 }); diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index f4cba0c85b..a467800dfe 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -37,7 +37,7 @@ use frame_support::{ parameter_types, storage::child, traits::{ - fungibles::Lockable, BalanceStatus, ConstU32, ConstU64, Contains, Currency, Get, OnIdle, + BalanceStatus, ConstU32, ConstU64, Contains, Currency, Get, LockableCurrency, OnIdle, OnInitialize, ReservableCurrency, WithdrawReasons, }, weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}, diff --git a/frame/conviction-voting/src/benchmarking.rs b/frame/conviction-voting/src/benchmarking.rs index 4bebc6a97c..117bb7fe22 100644 --- a/frame/conviction-voting/src/benchmarking.rs +++ b/frame/conviction-voting/src/benchmarking.rs @@ -23,7 +23,7 @@ use assert_matches::assert_matches; use frame_benchmarking::{account, benchmarks_instance_pallet, whitelist_account}; use frame_support::{ dispatch::RawOrigin, - traits::{Currency, Get}, + traits::{fungible, Currency, Get}, }; use sp_runtime::traits::Bounded; use sp_std::collections::btree_map::BTreeMap; diff --git a/frame/conviction-voting/src/lib.rs b/frame/conviction-voting/src/lib.rs index 992b532fb9..3ecc6e56be 100644 --- a/frame/conviction-voting/src/lib.rs +++ b/frame/conviction-voting/src/lib.rs @@ -31,7 +31,7 @@ use frame_support::{ dispatch::{DispatchError, DispatchResult}, ensure, traits::{ - fungible, fungibles, fungibles::Lockable, Currency, Get, PollStatus, Polling, + fungible, Currency, Get, LockIdentifier, LockableCurrency, PollStatus, Polling, ReservableCurrency, WithdrawReasons, }, }; @@ -60,7 +60,7 @@ mod tests; #[cfg(feature = "runtime-benchmarks")] pub mod benchmarking; -const CONVICTION_VOTING_ID: fungibles::LockIdentifier = *b"pyconvot"; +const CONVICTION_VOTING_ID: LockIdentifier = *b"pyconvot"; type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; type BalanceOf = @@ -104,7 +104,7 @@ pub mod pallet { type WeightInfo: WeightInfo; /// Currency type with which voting happens. type Currency: ReservableCurrency - + fungibles::Lockable + + LockableCurrency + fungible::Inspect; /// The implementation of the logic which conducts polls. diff --git a/frame/democracy/src/lib.rs b/frame/democracy/src/lib.rs index 096122cb1c..cf954d4800 100644 --- a/frame/democracy/src/lib.rs +++ b/frame/democracy/src/lib.rs @@ -157,11 +157,9 @@ use frame_support::{ ensure, traits::{ defensive_prelude::*, - fungibles, - fungibles::Lockable, schedule::{v3::Named as ScheduleNamed, DispatchTime}, - Bounded, Currency, Get, OnUnbalanced, QueryPreimage, ReservableCurrency, StorePreimage, - WithdrawReasons, + Bounded, Currency, Get, LockIdentifier, LockableCurrency, OnUnbalanced, QueryPreimage, + ReservableCurrency, StorePreimage, WithdrawReasons, }, weights::Weight, }; @@ -191,7 +189,7 @@ pub mod benchmarking; pub mod migrations; -const DEMOCRACY_ID: fungibles::LockIdentifier = *b"democrac"; +const DEMOCRACY_ID: LockIdentifier = *b"democrac"; /// A proposal index. pub type PropIndex = u32; @@ -236,7 +234,7 @@ pub mod pallet { /// Currency type for this pallet. type Currency: ReservableCurrency - + fungibles::Lockable; + + LockableCurrency; /// The period between a proposal being approved and enacted. /// diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 13190237ea..165a8fcab4 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -101,8 +101,8 @@ use codec::{Decode, Encode}; use frame_support::{ traits::{ - defensive_prelude::*, fungibles, fungibles::Lockable, ChangeMembers, Contains, - ContainsLengthBound, Currency, CurrencyToVote, Get, InitializeMembers, OnUnbalanced, + defensive_prelude::*, ChangeMembers, Contains, ContainsLengthBound, Currency, + CurrencyToVote, Get, InitializeMembers, LockIdentifier, LockableCurrency, OnUnbalanced, ReservableCurrency, SortedMembers, WithdrawReasons, }, weights::Weight, @@ -199,10 +199,10 @@ pub mod pallet { /// Identifier for the elections-phragmen pallet's lock #[pallet::constant] - type PalletId: Get; + type PalletId: Get; /// The currency that people are electing with. - type Currency: fungibles::Lockable + type Currency: LockableCurrency + ReservableCurrency; /// What to do when the members change. @@ -1274,7 +1274,7 @@ mod tests { } parameter_types! { - pub const ElectionsPhragmenPalletId: fungibles::LockIdentifier = *b"phrelect"; + pub const ElectionsPhragmenPalletId: LockIdentifier = *b"phrelect"; pub const PhragmenMaxVoters: u32 = 1000; pub const PhragmenMaxCandidates: u32 = 100; } diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index 2d307a1a02..5a4ef92b1c 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -620,7 +620,10 @@ mod tests { use frame_support::{ assert_err, parameter_types, - traits::{fungibles, ConstU32, ConstU64, ConstU8, Currency, WithdrawReasons}, + traits::{ + ConstU32, ConstU64, ConstU8, Currency, LockIdentifier, LockableCurrency, + WithdrawReasons, + }, weights::{ConstantMultiplier, IdentityFee, RuntimeDbWeight, Weight, WeightToFee}, }; use frame_system::{Call as SystemCall, ChainContext, LastRuntimeUpgradeInfo}; @@ -1182,11 +1185,11 @@ mod tests { #[test] fn can_pay_for_tx_fee_on_full_lock() { - let id: fungibles::LockIdentifier = *b"0 "; + let id: LockIdentifier = *b"0 "; let execute_with_lock = |lock: WithdrawReasons| { let mut t = new_test_ext(1); t.execute_with(|| { - as fungibles::Lockable>::set_lock( + as LockableCurrency>::set_lock( id, &1, 110, lock, ); let xt = TestXt::new( diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index 551628fee9..2bb01baa0c 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -1,3 +1,5 @@ +// This file is part of Substrate. + // Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 @@ -66,12 +68,11 @@ use codec::{Codec, Encode}; use frame_support::{ ensure, traits::{ - fungibles, schedule::{ v3::{Anon as ScheduleAnon, Named as ScheduleNamed}, DispatchTime, }, - Currency, OnUnbalanced, OriginTrait, PollStatus, Polling, QueryPreimage, + Currency, LockIdentifier, OnUnbalanced, OriginTrait, PollStatus, Polling, QueryPreimage, ReservableCurrency, StorePreimage, VoteTally, }, BoundedVec, @@ -132,7 +133,7 @@ macro_rules! impl_tracksinfo_get { }; } -const ASSEMBLY_ID: fungibles::LockIdentifier = *b"assembly"; +const ASSEMBLY_ID: LockIdentifier = *b"assembly"; #[frame_support::pallet] pub mod pallet { diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 34e12fbcf6..c22a2bd2d1 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -25,9 +25,8 @@ use frame_support::{ dispatch::WithPostDispatchInfo, pallet_prelude::*, traits::{ - fungibles::Lockable, Currency, CurrencyToVote, Defensive, DefensiveResult, - EstimateNextNewSession, Get, Imbalance, OnUnbalanced, TryCollect, UnixTime, - WithdrawReasons, + Currency, CurrencyToVote, Defensive, DefensiveResult, EstimateNextNewSession, Get, + Imbalance, LockableCurrency, OnUnbalanced, TryCollect, UnixTime, WithdrawReasons, }, weights::Weight, }; diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index fd0c494fa6..8fddba2150 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -24,8 +24,8 @@ use frame_support::{ dispatch::Codec, pallet_prelude::*, traits::{ - fungibles, fungibles::Lockable, Currency, CurrencyToVote, Defensive, DefensiveResult, - DefensiveSaturating, EnsureOrigin, EstimateNextNewSession, Get, OnUnbalanced, TryCollect, + Currency, CurrencyToVote, Defensive, DefensiveResult, DefensiveSaturating, EnsureOrigin, + EstimateNextNewSession, Get, LockIdentifier, LockableCurrency, OnUnbalanced, TryCollect, UnixTime, }, weights::Weight, @@ -50,7 +50,7 @@ use crate::{ ValidatorPrefs, }; -const STAKING_ID: fungibles::LockIdentifier = *b"staking "; +const STAKING_ID: LockIdentifier = *b"staking "; #[frame_support::pallet] pub mod pallet { @@ -78,7 +78,7 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { /// The staking balance. - type Currency: fungibles::Lockable< + type Currency: LockableCurrency< Self::AccountId, Moment = Self::BlockNumber, Balance = Self::CurrencyBalance, diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index 3a831d9c27..e5ba98fe0c 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -20,7 +20,6 @@ //! NOTE: If you're looking for `parameter_types`, it has moved in to the top-level module. pub mod tokens; -#[allow(deprecated)] pub use tokens::{ currency::{ ActiveIssuanceOf, Currency, LockIdentifier, LockableCurrency, NamedReservableCurrency, diff --git a/frame/support/src/traits/tokens/currency.rs b/frame/support/src/traits/tokens/currency.rs index 29603198e9..48247b6021 100644 --- a/frame/support/src/traits/tokens/currency.rs +++ b/frame/support/src/traits/tokens/currency.rs @@ -32,10 +32,7 @@ use sp_std::fmt::Debug; mod reservable; pub use reservable::{NamedReservableCurrency, ReservableCurrency}; mod lockable; - -#[deprecated(note = "Deprecated in favour of using fungibles::Lockable trait directly")] -pub use super::fungibles::{LockIdentifier, Lockable as LockableCurrency}; -pub use lockable::VestingSchedule; +pub use lockable::{LockIdentifier, LockableCurrency, VestingSchedule}; /// Abstraction over a fungible assets system. pub trait Currency { diff --git a/frame/support/src/traits/tokens/currency/lockable.rs b/frame/support/src/traits/tokens/currency/lockable.rs index 5b7cad3b5c..a10edd6e3e 100644 --- a/frame/support/src/traits/tokens/currency/lockable.rs +++ b/frame/support/src/traits/tokens/currency/lockable.rs @@ -17,8 +17,52 @@ //! The lockable currency trait and some associated types. -use super::Currency; -use crate::dispatch::DispatchResult; +use super::{super::misc::WithdrawReasons, Currency}; +use crate::{dispatch::DispatchResult, traits::misc::Get}; + +/// An identifier for a lock. Used for disambiguating different locks so that +/// they can be individually replaced or removed. +pub type LockIdentifier = [u8; 8]; + +/// A currency whose accounts can have liquidity restrictions. +pub trait LockableCurrency: Currency { + /// The quantity used to denote time; usually just a `BlockNumber`. + type Moment; + + /// The maximum number of locks a user should have on their account. + type MaxLocks: Get; + + /// Create a new balance lock on account `who`. + /// + /// If the new lock is valid (i.e. not already expired), it will push the struct to + /// the `Locks` vec in storage. Note that you can lock more funds than a user has. + /// + /// If the lock `id` already exists, this will update it. + fn set_lock( + id: LockIdentifier, + who: &AccountId, + amount: Self::Balance, + reasons: WithdrawReasons, + ); + + /// Changes a balance lock (selected by `id`) so that it becomes less liquid in all + /// parameters or creates a new one if it does not exist. + /// + /// Calling `extend_lock` on an existing lock `id` differs from `set_lock` in that it + /// applies the most severe constraints of the two, while `set_lock` replaces the lock + /// with the new parameters. As in, `extend_lock` will set: + /// - maximum `amount` + /// - bitwise mask of all `reasons` + fn extend_lock( + id: LockIdentifier, + who: &AccountId, + amount: Self::Balance, + reasons: WithdrawReasons, + ); + + /// Remove an existing lock. + fn remove_lock(id: LockIdentifier, who: &AccountId); +} /// A vesting schedule over a currency. This allows a particular currency to have vesting limits /// applied to it. diff --git a/frame/support/src/traits/tokens/fungible.rs b/frame/support/src/traits/tokens/fungible.rs index d11959fd7c..05e109b870 100644 --- a/frame/support/src/traits/tokens/fungible.rs +++ b/frame/support/src/traits/tokens/fungible.rs @@ -29,7 +29,6 @@ use sp_runtime::traits::Saturating; mod balanced; mod imbalance; - pub use balanced::{Balanced, Unbalanced}; pub use imbalance::{CreditOf, DebtOf, HandleImbalanceDrop, Imbalance}; diff --git a/frame/support/src/traits/tokens/fungibles.rs b/frame/support/src/traits/tokens/fungibles.rs index 045ecd0513..a29cb974fe 100644 --- a/frame/support/src/traits/tokens/fungibles.rs +++ b/frame/support/src/traits/tokens/fungibles.rs @@ -33,9 +33,7 @@ pub mod metadata; pub use balanced::{Balanced, Unbalanced}; mod imbalance; pub use imbalance::{CreditOf, DebtOf, HandleImbalanceDrop, Imbalance}; -mod lockable; pub mod roles; -pub use lockable::{LockIdentifier, Lockable}; /// Trait for providing balance-inspection access to a set of named fungible assets. pub trait Inspect { diff --git a/frame/support/src/traits/tokens/fungibles/lockable.rs b/frame/support/src/traits/tokens/fungibles/lockable.rs deleted file mode 100644 index 185b40eae9..0000000000 --- a/frame/support/src/traits/tokens/fungibles/lockable.rs +++ /dev/null @@ -1,65 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2019-2022 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. - -//! The Lockable trait and some associated types. - -use super::{super::misc::WithdrawReasons, currency::Currency}; -use crate::traits::misc::Get; - -/// An identifier for a lock. Used for disambiguating different locks so that -/// they can be individually replaced or removed. -pub type LockIdentifier = [u8; 8]; - -/// A currency whose accounts can have liquidity restrictions. -pub trait Lockable: Currency { - /// The quantity used to denote time; usually just a `BlockNumber`. - type Moment; - - /// The maximum number of locks a user should have on their account. - type MaxLocks: Get; - - /// Create a new balance lock on account `who`. - /// - /// If the new lock is valid (i.e. not already expired), it will push the struct to - /// the `Locks` vec in storage. Note that you can lock more funds than a user has. - /// - /// If the lock `id` already exists, this will update it. - fn set_lock( - id: LockIdentifier, - who: &AccountId, - amount: Self::Balance, - reasons: WithdrawReasons, - ); - - /// Changes a balance lock (selected by `id`) so that it becomes less liquid in all - /// parameters or creates a new one if it does not exist. - /// - /// Calling `extend_lock` on an existing lock `id` differs from `set_lock` in that it - /// applies the most severe constraints of the two, while `set_lock` replaces the lock - /// with the new parameters. As in, `extend_lock` will set: - /// - maximum `amount` - /// - bitwise mask of all `reasons` - fn extend_lock( - id: LockIdentifier, - who: &AccountId, - amount: Self::Balance, - reasons: WithdrawReasons, - ); - - /// Remove an existing lock. - fn remove_lock(id: LockIdentifier, who: &AccountId); -} diff --git a/frame/vesting/src/lib.rs b/frame/vesting/src/lib.rs index 226f539a74..a92f94baf6 100644 --- a/frame/vesting/src/lib.rs +++ b/frame/vesting/src/lib.rs @@ -62,7 +62,7 @@ use frame_support::{ ensure, storage::bounded_vec::BoundedVec, traits::{ - fungibles, fungibles::Lockable, Currency, ExistenceRequirement, Get, VestingSchedule, + Currency, ExistenceRequirement, Get, LockIdentifier, LockableCurrency, VestingSchedule, WithdrawReasons, }, weights::Weight, @@ -83,12 +83,11 @@ pub use weights::WeightInfo; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; -type MaxLocksOf = <::Currency as fungibles::Lockable< - ::AccountId, ->>::MaxLocks; +type MaxLocksOf = + <::Currency as LockableCurrency<::AccountId>>::MaxLocks; type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; -const VESTING_ID: fungibles::LockIdentifier = *b"vesting "; +const VESTING_ID: LockIdentifier = *b"vesting "; // A value placed in storage that represents the current version of the Vesting storage. // This value is used by `on_runtime_upgrade` to determine whether we run storage migration logic. @@ -160,7 +159,7 @@ pub mod pallet { type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// The currency trait. - type Currency: fungibles::Lockable; + type Currency: LockableCurrency; /// Convert the block number into a balance. type BlockNumberToBalance: Convert>; From 90ab4fafa0982ad71cecf451e5719a53346fcd32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 9 Dec 2022 11:31:57 +0100 Subject: [PATCH 017/127] Don't indefinitely block on shutting down Tokio (#12885) * Don't indefinitely on shutting down Tokio Now we wait in maximum 60 seconds before we shutdown the node. Tasks are may be leaked and leading to some data corruption. * Drink less :thinking_face: --- client/cli/src/runner.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/client/cli/src/runner.rs b/client/cli/src/runner.rs index f6edd84447..c976c31970 100644 --- a/client/cli/src/runner.rs +++ b/client/cli/src/runner.rs @@ -22,7 +22,7 @@ use futures::{future, future::FutureExt, pin_mut, select, Future}; use log::info; use sc_service::{Configuration, Error as ServiceError, TaskManager}; use sc_utils::metrics::{TOKIO_THREADS_ALIVE, TOKIO_THREADS_TOTAL}; -use std::marker::PhantomData; +use std::{marker::PhantomData, time::Duration}; #[cfg(target_family = "unix")] async fn main(func: F) -> std::result::Result<(), E> @@ -147,7 +147,11 @@ impl Runner { self.print_node_infos(); let mut task_manager = self.tokio_runtime.block_on(initialize(self.config))?; let res = self.tokio_runtime.block_on(main(task_manager.future().fuse())); - Ok(res?) + + // Give all futures 60 seconds to shutdown, before tokio "leaks" them. + self.tokio_runtime.shutdown_timeout(Duration::from_secs(60)); + + res.map_err(Into::into) } /// A helper function that runs a command with the configuration of this node. From 47bd959c1ee12e2083feb38bf118fa906ff6fea5 Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Fri, 9 Dec 2022 10:38:24 +0000 Subject: [PATCH 018/127] General Message Queue Pallet (#12485) * The message queue * Make fully generic * Refactor * Docs * Refactor * Use iter not slice * Per-origin queues * Multi-queue processing * Introduce MaxReady * Remove MaxReady in favour of ready ring * Cleanups * ReadyRing and tests * Stale page reaping * from_components -> from_parts Signed-off-by: Oliver Tale-Yazdi * Move WeightCounter to sp_weights Signed-off-by: Oliver Tale-Yazdi * Add MockedWeightInfo Signed-off-by: Oliver Tale-Yazdi * Deploy to kitchensink Signed-off-by: Oliver Tale-Yazdi * Use WeightCounter Signed-off-by: Oliver Tale-Yazdi * Small fixes and logging Signed-off-by: Oliver Tale-Yazdi * Add service_page Signed-off-by: Oliver Tale-Yazdi * Typo Signed-off-by: Oliver Tale-Yazdi * Move service_page below service_queue Signed-off-by: Oliver Tale-Yazdi * Add service_message Signed-off-by: Oliver Tale-Yazdi * Use correct weight function Signed-off-by: Oliver Tale-Yazdi * Overweight execution * Refactor * Missing file * Fix WeightCounter usage in scheduler Signed-off-by: Oliver Tale-Yazdi * Fix peek_index Take into account that decoding from a mutable slice modifies it. Signed-off-by: Oliver Tale-Yazdi * Add tests and bench service_page_item Signed-off-by: Oliver Tale-Yazdi * Add debug_info Signed-off-by: Oliver Tale-Yazdi * Add no-progress check to service_queues Signed-off-by: Oliver Tale-Yazdi * Add more benches Signed-off-by: Oliver Tale-Yazdi * Bound from_message and try_append_message Signed-off-by: Oliver Tale-Yazdi * Add PageReaped event Signed-off-by: Oliver Tale-Yazdi * Rename BookStateOf and BookStateFor Signed-off-by: Oliver Tale-Yazdi * Update tests and remove logging Signed-off-by: Oliver Tale-Yazdi * Remove redundant per-message origins; add footprint() and sweep_queue() * Move testing stuff to mock.rs Signed-off-by: Oliver Tale-Yazdi * Add integration test Signed-off-by: Oliver Tale-Yazdi * Fix no-progress check Signed-off-by: Oliver Tale-Yazdi * Fix debug_info Signed-off-by: Oliver Tale-Yazdi * Fixup merge and tests Signed-off-by: Oliver Tale-Yazdi * Fix footprint tracking * Introduce * Formatting * OverweightEnqueued event, auto-servicing config item * Update tests and benchmarks Signed-off-by: Oliver Tale-Yazdi * Clippy Signed-off-by: Oliver Tale-Yazdi * Add tests Signed-off-by: Oliver Tale-Yazdi * Provide change handler * Add missing BookStateFor::insert and call QueueChangeHandler Signed-off-by: Oliver Tale-Yazdi * Docs Signed-off-by: Oliver Tale-Yazdi * Update benchmarks and weights Signed-off-by: Oliver Tale-Yazdi * More tests... Signed-off-by: Oliver Tale-Yazdi * Use weight metering functions Signed-off-by: Oliver Tale-Yazdi * weightInfo::process_message_payload is gone Signed-off-by: Oliver Tale-Yazdi * Add defensive_saturating_accrue Signed-off-by: Oliver Tale-Yazdi * Rename WeightCounter to WeightMeter Ctr+Shift+H should do the trick. Signed-off-by: Oliver Tale-Yazdi * Test on_initialize Signed-off-by: Oliver Tale-Yazdi * Add module docs Signed-off-by: Oliver Tale-Yazdi * Remove origin from MaxMessageLen The message origin is not encoded into the heap and does therefore not influence the max message length anymore. Signed-off-by: Oliver Tale-Yazdi * Add BoundedVec::as_slice Signed-off-by: Oliver Tale-Yazdi * Test Page::{from_message, try_append_message} Signed-off-by: Oliver Tale-Yazdi * Fixup docs Signed-off-by: Oliver Tale-Yazdi * Docs * Do nothing in sweep_queue if the queue does not exist ... otherwise it inserts default values into the storage. Signed-off-by: Oliver Tale-Yazdi * Test ring (un)knitting Signed-off-by: Oliver Tale-Yazdi * Upgrade stress-test Change the test to not assume that all queued messages will be processed in the next block but split it over multiple. Signed-off-by: Oliver Tale-Yazdi * More tests... Signed-off-by: Oliver Tale-Yazdi * Beauty fixes Signed-off-by: Oliver Tale-Yazdi * clippy Signed-off-by: Oliver Tale-Yazdi * Rename BoundedVec::as_slice to as_bounded_slice Conflicts with deref().as_slice() otherwise. Signed-off-by: Oliver Tale-Yazdi * Fix imports Signed-off-by: Oliver Tale-Yazdi * Remove ReadyRing struct Was used for testing only. Instead use 'fn assert_ring' which also check the service head and backlinks. Signed-off-by: Oliver Tale-Yazdi * Beauty fixes Signed-off-by: Oliver Tale-Yazdi * Fix stale page watermark Signed-off-by: Oliver Tale-Yazdi * Cleanup Signed-off-by: Oliver Tale-Yazdi * Fix test feature and clippy Signed-off-by: Oliver Tale-Yazdi * QueueChanged handler is called correctly Signed-off-by: Oliver Tale-Yazdi * Update benches Signed-off-by: Oliver Tale-Yazdi * Abstract testing functions Signed-off-by: Oliver Tale-Yazdi * More tests Signed-off-by: Oliver Tale-Yazdi * Cleanup Signed-off-by: Oliver Tale-Yazdi * Clippy Signed-off-by: Oliver Tale-Yazdi * fmt Signed-off-by: Oliver Tale-Yazdi * Simplify tests Signed-off-by: Oliver Tale-Yazdi * Make stuff compile Signed-off-by: Oliver Tale-Yazdi * Extend overweight execution benchmark Signed-off-by: Oliver Tale-Yazdi * Remove TODOs Signed-off-by: Oliver Tale-Yazdi * Test service queue with faulty MessageProcessor Signed-off-by: Oliver Tale-Yazdi * fmt Signed-off-by: Oliver Tale-Yazdi * Update pallet ui tests to 1.65 Signed-off-by: Oliver Tale-Yazdi * More docs Signed-off-by: Oliver Tale-Yazdi * Review doc fixes Co-authored-by: Robert Klotzner Signed-off-by: Oliver Tale-Yazdi * Add weight_limit to extrinsic weight of execute_overweight * Correctly return unused weight * Return actual weight consumed in do_execute_overweight * Review fixes Signed-off-by: Oliver Tale-Yazdi * Set version 7.0.0-dev Signed-off-by: Oliver Tale-Yazdi * Make it compile Signed-off-by: Oliver Tale-Yazdi * Switch message_size to u64 Signed-off-by: Oliver Tale-Yazdi * Switch message_count to u64 Signed-off-by: Oliver Tale-Yazdi * Fix benchmarks Signed-off-by: Oliver Tale-Yazdi * Make CI green Signed-off-by: Oliver Tale-Yazdi * Docs * Update tests Signed-off-by: Oliver Tale-Yazdi * ".git/.scripts/bench-bot.sh" pallet dev pallet_message_queue * Dont mention README.md in the Cargo.toml Signed-off-by: Oliver Tale-Yazdi * Remove reference to readme Signed-off-by: Oliver Tale-Yazdi Co-authored-by: Oliver Tale-Yazdi Co-authored-by: parity-processbot <> Co-authored-by: Robert Klotzner Co-authored-by: Keith Yeung --- Cargo.lock | 40 +- Cargo.toml | 1 + bin/node/runtime/Cargo.toml | 4 + bin/node/runtime/src/lib.rs | 21 + frame/message-queue/Cargo.toml | 53 + frame/message-queue/src/benchmarking.rs | 204 +++ frame/message-queue/src/integration_test.rs | 224 +++ frame/message-queue/src/lib.rs | 1308 +++++++++++++++++ frame/message-queue/src/mock.rs | 312 ++++ frame/message-queue/src/mock_helpers.rs | 185 +++ frame/message-queue/src/tests.rs | 1092 ++++++++++++++ frame/message-queue/src/weights.rs | 216 +++ frame/scheduler/Cargo.toml | 2 + frame/scheduler/src/lib.rs | 3 +- frame/support/src/traits.rs | 6 + frame/support/src/traits/messages.rs | 202 +++ ...age_ensure_span_are_ok_on_wrong_gen.stderr | 6 +- ...re_span_are_ok_on_wrong_gen_unnamed.stderr | 6 +- primitives/core/src/bounded/bounded_vec.rs | 7 + primitives/weights/src/weight_meter.rs | 6 + 20 files changed, 3883 insertions(+), 15 deletions(-) create mode 100644 frame/message-queue/Cargo.toml create mode 100644 frame/message-queue/src/benchmarking.rs create mode 100644 frame/message-queue/src/integration_test.rs create mode 100644 frame/message-queue/src/lib.rs create mode 100644 frame/message-queue/src/mock.rs create mode 100644 frame/message-queue/src/mock_helpers.rs create mode 100644 frame/message-queue/src/tests.rs create mode 100644 frame/message-queue/src/weights.rs create mode 100644 frame/support/src/traits/messages.rs diff --git a/Cargo.lock b/Cargo.lock index 73effefc48..41c641cf05 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3117,6 +3117,7 @@ dependencies = [ "pallet-indices", "pallet-lottery", "pallet-membership", + "pallet-message-queue", "pallet-mmr", "pallet-multisig", "pallet-nis", @@ -5322,6 +5323,28 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-message-queue" +version = "7.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "rand 0.8.5", + "rand_distr", + "scale-info", + "serde", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "sp-tracing", + "sp-weights", +] + [[package]] name = "pallet-mmr" version = "4.0.0-dev" @@ -5709,6 +5732,7 @@ dependencies = [ "sp-io", "sp-runtime", "sp-std", + "sp-weights", "substrate-test-utils", ] @@ -8397,9 +8421,9 @@ dependencies = [ [[package]] name = "scale-info" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8980cafbe98a7ee7a9cc16b32ebce542c77883f512d83fbf2ddc8f6a85ea74c9" +checksum = "333af15b02563b8182cd863f925bd31ef8fa86a0e095d30c091956057d436153" dependencies = [ "bitvec", "cfg-if", @@ -8411,9 +8435,9 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4260c630e8a8a33429d1688eff2f163f24c65a4e1b1578ef6b565061336e4b6f" +checksum = "53f56acbd0743d29ffa08f911ab5397def774ad01bab3786804cf6ee057fb5e1" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -8570,9 +8594,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.136" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" dependencies = [ "serde_derive", ] @@ -8589,9 +8613,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.136" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 12f2ced0d1..eb78d5e104 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -121,6 +121,7 @@ members = [ "frame/offences/benchmarking", "frame/preimage", "frame/proxy", + "frame/message-queue", "frame/nomination-pools", "frame/nomination-pools/fuzzer", "frame/nomination-pools/benchmarking", diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 02a2ae292d..477545c9ac 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -75,6 +75,7 @@ pallet-indices = { version = "4.0.0-dev", default-features = false, path = "../. pallet-identity = { version = "4.0.0-dev", default-features = false, path = "../../../frame/identity" } pallet-lottery = { version = "4.0.0-dev", default-features = false, path = "../../../frame/lottery" } pallet-membership = { version = "4.0.0-dev", default-features = false, path = "../../../frame/membership" } +pallet-message-queue = { version = "7.0.0-dev", default-features = false, path = "../../../frame/message-queue" } pallet-mmr = { version = "4.0.0-dev", default-features = false, path = "../../../frame/merkle-mountain-range" } pallet-multisig = { version = "4.0.0-dev", default-features = false, path = "../../../frame/multisig" } pallet-nomination-pools = { version = "1.0.0", default-features = false, path = "../../../frame/nomination-pools"} @@ -150,6 +151,7 @@ std = [ "sp-inherents/std", "pallet-lottery/std", "pallet-membership/std", + "pallet-message-queue/std", "pallet-mmr/std", "pallet-multisig/std", "pallet-nomination-pools/std", @@ -229,6 +231,7 @@ runtime-benchmarks = [ "pallet-indices/runtime-benchmarks", "pallet-lottery/runtime-benchmarks", "pallet-membership/runtime-benchmarks", + "pallet-message-queue/runtime-benchmarks", "pallet-mmr/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", "pallet-nomination-pools-benchmarking/runtime-benchmarks", @@ -282,6 +285,7 @@ try-runtime = [ "pallet-identity/try-runtime", "pallet-lottery/try-runtime", "pallet-membership/try-runtime", + "pallet-message-queue/try-runtime", "pallet-mmr/try-runtime", "pallet-multisig/try-runtime", "pallet-nomination-pools/try-runtime", diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 1bb4dd6f91..7cd42be73a 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1135,6 +1135,25 @@ impl pallet_bounties::Config for Runtime { type ChildBountyManager = ChildBounties; } +parameter_types! { + /// Allocate at most 20% of each block for message processing. + /// + /// Is set to 20% since the scheduler can already consume a maximum of 80%. + pub MessageQueueServiceWeight: Option = Some(Perbill::from_percent(20) * RuntimeBlockWeights::get().max_block); +} + +impl pallet_message_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + /// NOTE: Always set this to `NoopMessageProcessor` for benchmarking. + type MessageProcessor = pallet_message_queue::mock_helpers::NoopMessageProcessor; + type Size = u32; + type QueueChangeHandler = (); + type HeapSize = ConstU32<{ 64 * 1024 }>; + type MaxStale = ConstU32<128>; + type ServiceWeight = MessageQueueServiceWeight; +} + parameter_types! { pub const ChildBountyValueMinimum: Balance = 1 * DOLLARS; } @@ -1699,6 +1718,7 @@ construct_runtime!( RankedPolls: pallet_referenda::, RankedCollective: pallet_ranked_collective, FastUnstake: pallet_fast_unstake, + MessageQueue: pallet_message_queue, } ); @@ -1793,6 +1813,7 @@ mod benches { [pallet_indices, Indices] [pallet_lottery, Lottery] [pallet_membership, TechnicalMembership] + [pallet_message_queue, MessageQueue] [pallet_mmr, Mmr] [pallet_multisig, Multisig] [pallet_nomination_pools, NominationPoolsBench::] diff --git a/frame/message-queue/Cargo.toml b/frame/message-queue/Cargo.toml new file mode 100644 index 0000000000..47d114902f --- /dev/null +++ b/frame/message-queue/Cargo.toml @@ -0,0 +1,53 @@ +[package] +authors = ["Parity Technologies "] +edition = "2021" +name = "pallet-message-queue" +version = "7.0.0-dev" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME pallet to queue and process messages" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.2", default-features = false, features = ["derive"] } +serde = { version = "1.0.137", optional = true, features = ["derive"] } +log = { version = "0.4.17", default-features = false } + +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-arithmetic = { version = "6.0.0", default-features = false, path = "../../primitives/arithmetic" } +sp-weights = { version = "4.0.0", default-features = false, path = "../../primitives/weights" } + +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } + +[dev-dependencies] +sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } +rand = "0.8.5" +rand_distr = "0.4.3" + +[features] +default = ["std"] +std = [ + "codec/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "sp-arithmetic/std", + "sp-weights/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", +] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/message-queue/src/benchmarking.rs b/frame/message-queue/src/benchmarking.rs new file mode 100644 index 0000000000..c0ff20431d --- /dev/null +++ b/frame/message-queue/src/benchmarking.rs @@ -0,0 +1,204 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Benchmarking for the message queue pallet. + +#![cfg(feature = "runtime-benchmarks")] +#![allow(unused_assignments)] // Needed for `ready_ring_knit`. + +use super::{mock_helpers::*, Pallet as MessageQueue, *}; + +use frame_benchmarking::{benchmarks, whitelisted_caller}; +use frame_support::traits::Get; +use frame_system::RawOrigin; +use sp_std::prelude::*; + +benchmarks! { + where_clause { + where + // NOTE: We need to generate multiple origins, therefore Origin is `From`. The + // `PartialEq` is for asserting the outcome of the ring (un)knitting and *could* be + // removed if really necessary. + <::MessageProcessor as ProcessMessage>::Origin: From + PartialEq, + ::Size: From, + } + + // Worst case path of `ready_ring_knit`. + ready_ring_knit { + let mid: MessageOriginOf:: = 1.into(); + build_ring::(&[0.into(), mid.clone(), 2.into()]); + unknit::(&mid); + assert_ring::(&[0.into(), 2.into()]); + let mut neighbours = None; + }: { + neighbours = MessageQueue::::ready_ring_knit(&mid).ok(); + } verify { + // The neighbours needs to be modified manually. + BookStateFor::::mutate(&mid, |b| { b.ready_neighbours = neighbours }); + assert_ring::(&[0.into(), 2.into(), mid]); + } + + // Worst case path of `ready_ring_unknit`. + ready_ring_unknit { + build_ring::(&[0.into(), 1.into(), 2.into()]); + assert_ring::(&[0.into(), 1.into(), 2.into()]); + let o: MessageOriginOf:: = 0.into(); + let neighbours = BookStateFor::::get(&o).ready_neighbours.unwrap(); + }: { + MessageQueue::::ready_ring_unknit(&o, neighbours); + } verify { + assert_ring::(&[1.into(), 2.into()]); + } + + // `service_queues` without any queue processing. + service_queue_base { + }: { + MessageQueue::::service_queue(0.into(), &mut WeightMeter::max_limit(), Weight::MAX) + } + + // `service_page` without any message processing but with page completion. + service_page_base_completion { + let origin: MessageOriginOf = 0.into(); + let page = PageOf::::default(); + Pages::::insert(&origin, 0, &page); + let mut book_state = single_page_book::(); + let mut meter = WeightMeter::max_limit(); + let limit = Weight::MAX; + }: { + MessageQueue::::service_page(&origin, &mut book_state, &mut meter, limit) + } + + // `service_page` without any message processing and without page completion. + service_page_base_no_completion { + let origin: MessageOriginOf = 0.into(); + let mut page = PageOf::::default(); + // Mock the storage such that `is_complete` returns `false` but `peek_first` returns `None`. + page.first = 1.into(); + page.remaining = 1.into(); + Pages::::insert(&origin, 0, &page); + let mut book_state = single_page_book::(); + let mut meter = WeightMeter::max_limit(); + let limit = Weight::MAX; + }: { + MessageQueue::::service_page(&origin, &mut book_state, &mut meter, limit) + } + + // Processing a single message from a page. + service_page_item { + let msg = vec![1u8; MaxMessageLenOf::::get() as usize]; + let mut page = page::(&msg.clone()); + let mut book = book_for::(&page); + assert!(page.peek_first().is_some(), "There is one message"); + let mut weight = WeightMeter::max_limit(); + }: { + let status = MessageQueue::::service_page_item(&0u32.into(), 0, &mut book, &mut page, &mut weight, Weight::MAX); + assert_eq!(status, ItemExecutionStatus::Executed(true)); + } verify { + // Check that it was processed. + assert_last_event::(Event::Processed { + hash: T::Hashing::hash(&msg), origin: 0.into(), + weight_used: 1.into_weight(), success: true + }.into()); + let (_, processed, _) = page.peek_index(0).unwrap(); + assert!(processed); + assert_eq!(book.message_count, 0); + } + + // Worst case for calling `bump_service_head`. + bump_service_head { + setup_bump_service_head::(0.into(), 10.into()); + let mut weight = WeightMeter::max_limit(); + }: { + MessageQueue::::bump_service_head(&mut weight); + } verify { + assert_eq!(ServiceHead::::get().unwrap(), 10u32.into()); + assert_eq!(weight.consumed, T::WeightInfo::bump_service_head()); + } + + reap_page { + // Mock the storage to get a *cullable* but not *reapable* page. + let origin: MessageOriginOf = 0.into(); + let mut book = single_page_book::(); + let (page, msgs) = full_page::(); + + for p in 0 .. T::MaxStale::get() * T::MaxStale::get() { + if p == 0 { + Pages::::insert(&origin, p, &page); + } + book.end += 1; + book.count += 1; + book.message_count += msgs as u64; + book.size += page.remaining_size.into() as u64; + } + book.begin = book.end - T::MaxStale::get(); + BookStateFor::::insert(&origin, &book); + assert!(Pages::::contains_key(&origin, 0)); + + }: _(RawOrigin::Signed(whitelisted_caller()), 0u32.into(), 0) + verify { + assert_last_event::(Event::PageReaped{ origin: 0.into(), index: 0 }.into()); + assert!(!Pages::::contains_key(&origin, 0)); + } + + // Worst case for `execute_overweight` where the page is removed as completed. + // + // The worst case occurs when executing the last message in a page of which all are skipped since it is using `peek_index` which has linear complexities. + execute_overweight_page_removed { + let origin: MessageOriginOf = 0.into(); + let (mut page, msgs) = full_page::(); + // Skip all messages. + for _ in 1..msgs { + page.skip_first(true); + } + page.skip_first(false); + let book = book_for::(&page); + Pages::::insert(&origin, 0, &page); + BookStateFor::::insert(&origin, &book); + }: { + MessageQueue::::execute_overweight(RawOrigin::Signed(whitelisted_caller()).into(), 0u32.into(), 0u32, ((msgs - 1) as u32).into(), Weight::MAX).unwrap() + } + verify { + assert_last_event::(Event::Processed { + hash: T::Hashing::hash(&((msgs - 1) as u32).encode()), origin: 0.into(), + weight_used: Weight::from_parts(1, 1), success: true + }.into()); + assert!(!Pages::::contains_key(&origin, 0), "Page must be removed"); + } + + // Worst case for `execute_overweight` where the page is updated. + execute_overweight_page_updated { + let origin: MessageOriginOf = 0.into(); + let (mut page, msgs) = full_page::(); + // Skip all messages. + for _ in 0..msgs { + page.skip_first(false); + } + let book = book_for::(&page); + Pages::::insert(&origin, 0, &page); + BookStateFor::::insert(&origin, &book); + }: { + MessageQueue::::execute_overweight(RawOrigin::Signed(whitelisted_caller()).into(), 0u32.into(), 0u32, ((msgs - 1) as u32).into(), Weight::MAX).unwrap() + } + verify { + assert_last_event::(Event::Processed { + hash: T::Hashing::hash(&((msgs - 1) as u32).encode()), origin: 0.into(), + weight_used: Weight::from_parts(1, 1), success: true + }.into()); + assert!(Pages::::contains_key(&origin, 0), "Page must be updated"); + } + + impl_benchmark_test_suite!(MessageQueue, crate::mock::new_test_ext::(), crate::integration_test::Test); +} diff --git a/frame/message-queue/src/integration_test.rs b/frame/message-queue/src/integration_test.rs new file mode 100644 index 0000000000..a9b6ee9bd2 --- /dev/null +++ b/frame/message-queue/src/integration_test.rs @@ -0,0 +1,224 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Stress tests pallet-message-queue. Defines its own runtime config to use larger constants for +//! `HeapSize` and `MaxStale`. + +#![cfg(test)] + +use crate::{ + mock::{ + new_test_ext, CountingMessageProcessor, IntoWeight, MockedWeightInfo, NumMessagesProcessed, + }, + *, +}; + +use crate as pallet_message_queue; +use frame_support::{ + parameter_types, + traits::{ConstU32, ConstU64}, +}; +use rand::{rngs::StdRng, Rng, SeedableRng}; +use rand_distr::Pareto; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, +}; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + MessageQueue: pallet_message_queue::{Pallet, Call, Storage, Event}, + } +); + +parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); +} +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type RuntimeCall = RuntimeCall; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +parameter_types! { + pub const HeapSize: u32 = 32 * 1024; + pub const MaxStale: u32 = 32; + pub static ServiceWeight: Option = Some(Weight::from_parts(100, 100)); +} + +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = MockedWeightInfo; + type MessageProcessor = CountingMessageProcessor; + type Size = u32; + type QueueChangeHandler = (); + type HeapSize = HeapSize; + type MaxStale = MaxStale; + type ServiceWeight = ServiceWeight; +} + +/// Simulates heavy usage by enqueueing and processing large amounts of messages. +/// +/// Best to run with `-r`, `RUST_LOG=info` and `RUSTFLAGS='-Cdebug-assertions=y'`. +/// +/// # Example output +/// +/// ```pre +/// Enqueued 1189 messages across 176 queues. Payload 46.97 KiB +/// Processing 772 of 1189 messages +/// Enqueued 9270 messages across 1559 queues. Payload 131.85 KiB +/// Processing 6262 of 9687 messages +/// Enqueued 5025 messages across 1225 queues. Payload 100.23 KiB +/// Processing 1739 of 8450 messages +/// Enqueued 42061 messages across 6357 queues. Payload 536.29 KiB +/// Processing 11675 of 48772 messages +/// Enqueued 20253 messages across 2420 queues. Payload 288.34 KiB +/// Processing 28711 of 57350 messages +/// Processing all remaining 28639 messages +/// ``` +#[test] +#[ignore] // Only run in the CI. +fn stress_test_enqueue_and_service() { + let blocks = 20; + let max_queues = 10_000; + let max_messages_per_queue = 10_000; + let max_msg_len = MaxMessageLenOf::::get(); + let mut rng = StdRng::seed_from_u64(42); + + new_test_ext::().execute_with(|| { + let mut msgs_remaining = 0; + for _ in 0..blocks { + // Start by enqueuing a large number of messages. + let (enqueued, _) = + enqueue_messages(max_queues, max_messages_per_queue, max_msg_len, &mut rng); + msgs_remaining += enqueued; + + // Pick a fraction of all messages currently in queue and process them. + let processed = rng.gen_range(1..=msgs_remaining); + log::info!("Processing {} of all messages {}", processed, msgs_remaining); + process_messages(processed); // This also advances the block. + msgs_remaining -= processed; + } + log::info!("Processing all remaining {} messages", msgs_remaining); + process_messages(msgs_remaining); + post_conditions(); + }); +} + +/// Enqueue a random number of random messages into a random number of queues. +fn enqueue_messages( + max_queues: u32, + max_per_queue: u32, + max_msg_len: u32, + rng: &mut StdRng, +) -> (u32, usize) { + let num_queues = rng.gen_range(1..max_queues); + let mut num_messages = 0; + let mut total_msg_len = 0; + for origin in 0..num_queues { + let num_messages_per_queue = + (rng.sample(Pareto::new(1.0, 1.1).unwrap()) as u32).min(max_per_queue); + + for m in 0..num_messages_per_queue { + let mut message = format!("{}:{}", &origin, &m).into_bytes(); + let msg_len = (rng.sample(Pareto::new(1.0, 1.0).unwrap()) as u32) + .clamp(message.len() as u32, max_msg_len); + message.resize(msg_len as usize, 0); + MessageQueue::enqueue_message( + BoundedSlice::defensive_truncate_from(&message), + origin.into(), + ); + total_msg_len += msg_len; + } + num_messages += num_messages_per_queue; + } + log::info!( + "Enqueued {} messages across {} queues. Payload {:.2} KiB", + num_messages, + num_queues, + total_msg_len as f64 / 1024.0 + ); + (num_messages, total_msg_len as usize) +} + +/// Process the number of messages. +fn process_messages(num_msgs: u32) { + let weight = (num_msgs as u64).into_weight(); + ServiceWeight::set(Some(weight)); + let consumed = next_block(); + + assert_eq!(consumed, weight, "\n{}", MessageQueue::debug_info()); + assert_eq!(NumMessagesProcessed::take(), num_msgs as usize); +} + +/// Returns the weight consumed by `MessageQueue::on_initialize()`. +fn next_block() -> Weight { + MessageQueue::on_finalize(System::block_number()); + System::on_finalize(System::block_number()); + System::set_block_number(System::block_number() + 1); + System::on_initialize(System::block_number()); + MessageQueue::on_initialize(System::block_number()) +} + +/// Assert that the pallet is in the expected post state. +fn post_conditions() { + // All queues are empty. + for (_, book) in BookStateFor::::iter() { + assert!(book.end >= book.begin); + assert_eq!(book.count, 0); + assert_eq!(book.size, 0); + assert_eq!(book.message_count, 0); + assert!(book.ready_neighbours.is_none()); + } + // No pages remain. + assert_eq!(Pages::::iter().count(), 0); + // Service head is gone. + assert!(ServiceHead::::get().is_none()); + // This still works fine. + assert_eq!(MessageQueue::service_queues(Weight::MAX), Weight::zero(), "Nothing left"); + next_block(); +} diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs new file mode 100644 index 0000000000..9b976c4824 --- /dev/null +++ b/frame/message-queue/src/lib.rs @@ -0,0 +1,1308 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! # Generalized Message Queue Pallet +//! +//! Provides generalized message queuing and processing capabilities on a per-queue basis for +//! arbitrary use-cases. +//! +//! # Design Goals +//! +//! 1. Minimal assumptions about `Message`s and `MessageOrigin`s. Both should be MEL bounded blobs. +//! This ensures the generality and reusability of the pallet. +//! 2. Well known and tightly limited pre-dispatch PoV weights, especially for message execution. +//! This is paramount for the success of the pallet since message execution is done in +//! `on_initialize` which must _never_ under-estimate its PoV weight. It also needs a frugal PoV +//! footprint since PoV is scarce and this is (possibly) done in every block. This must also hold +//! in the presence of unpredictable message size distributions. +//! 3. Usable as XCMP, DMP and UMP message/dispatch queue - possibly through adapter types. +//! +//! # Design +//! +//! The pallet has means to enqueue, store and process messages. This is implemented by having +//! *queues* which store enqueued messages and can be *served* to process said messages. A queue is +//! identified by its origin in the `BookStateFor`. Each message has an origin which defines into +//! which queue it will be stored. Messages are stored by being appended to the last [`Page`] of a +//! book. Each book keeps track of its pages by indexing `Pages`. The `ReadyRing` contains all +//! queues which hold at least one unprocessed message and are thereby *ready* to be serviced. The +//! `ServiceHead` indicates which *ready* queue is the next to be serviced. +//! The pallet implements [`frame_support::traits::EnqueueMessage`], +//! [`frame_support::traits::ServiceQueues`] and has [`frame_support::traits::ProcessMessage`] and +//! [`OnQueueChanged`] hooks to communicate with the outside world. +//! +//! NOTE: The storage items are not linked since they are not public. +//! +//! **Message Execution** +//! +//! Executing a message is offloaded to the [`Config::MessageProcessor`] which contains the actual +//! logic of how to handle the message since they are blobs. A message can be temporarily or +//! permanently overweight. The pallet will perpetually try to execute a temporarily overweight +//! message. A permanently overweight message is skipped and must be executed manually. +//! +//! **Pagination** +//! +//! Queues are stored in a *paged* manner by splitting their messages into [`Page`]s. This results +//! in a lot of complexity when implementing the pallet but is completely necessary to archive the +//! second #[Design Goal](design-goals). The problem comes from the fact a message can *possibly* be +//! quite large, lets say 64KiB. This then results in a *MEL* of at least 64KiB which results in a +//! PoV of at least 64KiB. Now we have the assumption that most messages are much shorter than their +//! maximum allowed length. This would result in most messages having a pre-dispatch PoV size which +//! is much larger than their post-dispatch PoV size, possibly by a factor of thousand. Disregarding +//! this observation would cripple the processing power of the pallet since it cannot straighten out +//! this discrepancy at runtime. Conceptually, the implementation is packing as many messages into a +//! single bounded vec, as actually fit into the bounds. This reduces the wasted PoV. +//! +//! **Page Data Layout** +//! +//! A Page contains a heap which holds all its messages. The heap is built by concatenating +//! `(ItemHeader, Message)` pairs. The [`ItemHeader`] contains the length of the message which is +//! needed for retrieving it. This layout allows for constant access time of the next message and +//! linear access time for any message in the page. The header must remain minimal to reduce its PoV +//! impact. +//! +//! **Weight Metering** +//! +//! The pallet utilizes the [`sp_weights::WeightMeter`] to manually track its consumption to always +//! stay within the required limit. This implies that the message processor hook can calculate the +//! weight of a message without executing it. This restricts the possible use-cases but is necessary +//! since the pallet runs in `on_initialize` which has a hard weight limit. The weight meter is used +//! in a way that `can_accrue` and `check_accrue` are always used to check the remaining weight of +//! an operation before committing to it. The process of exiting due to insufficient weight is +//! termed "bailing". +//! +//! # Scenario: Message enqueuing +//! +//! A message `m` is enqueued for origin `o` into queue `Q[o]` through +//! [`frame_support::traits::EnqueueMessage::enqueue_message`]`(m, o)`. +//! +//! First the queue is either loaded if it exists or otherwise created with empty default values. +//! The message is then inserted to the queue by appended it into its last `Page` or by creating a +//! new `Page` just for `m` if it does not fit in there. The number of messages in the `Book` is +//! incremented. +//! +//! `Q[o]` is now *ready* which will eventually result in `m` being processed. +//! +//! # Scenario: Message processing +//! +//! The pallet runs each block in `on_initialize` or when being manually called through +//! [`frame_support::traits::ServiceQueues::service_queues`]. +//! +//! First it tries to "rotate" the `ReadyRing` by one through advancing the `ServiceHead` to the +//! next *ready* queue. It then starts to service this queue by servicing as many pages of it as +//! possible. Servicing a page means to execute as many message of it as possible. Each executed +//! message is marked as *processed* if the [`Config::MessageProcessor`] return Ok. An event +//! [`Event::Processed`] is emitted afterwards. It is possible that the weight limit of the pallet +//! will never allow a specific message to be executed. In this case it remains as unprocessed and +//! is skipped. This process stops if either there are no more messages in the queue or the +//! remaining weight became insufficient to service this queue. If there is enough weight it tries +//! to advance to the next *ready* queue and service it. This continues until there are no more +//! queues on which it can make progress or not enough weight to check that. +//! +//! # Scenario: Overweight execution +//! +//! A permanently over-weight message which was skipped by the message processing will never be +//! executed automatically through `on_initialize` nor by calling +//! [`frame_support::traits::ServiceQueues::service_queues`]. +//! +//! Manual intervention in the form of +//! [`frame_support::traits::ServiceQueues::execute_overweight`] is necessary. Overweight messages +//! emit an [`Event::OverweightEnqueued`] event which can be used to extract the arguments for +//! manual execution. This only works on permanently overweight messages. There is no guarantee that +//! this will work since the message could be part of a stale page and be reaped before execution +//! commences. +//! +//! # Terminology +//! +//! - `Message`: A blob of data into which the pallet has no introspection, defined as +//! [`BoundedSlice>`]. The message length is limited by [`MaxMessageLenOf`] +//! which is calculated from [`Config::HeapSize`] and [`ItemHeader::max_encoded_len()`]. +//! - `MessageOrigin`: A generic *origin* of a message, defined as [`MessageOriginOf`]. The +//! requirements for it are kept minimal to remain as generic as possible. The type is defined in +//! [`frame_support::traits::ProcessMessage::Origin`]. +//! - `Page`: An array of `Message`s, see [`Page`]. Can never be empty. +//! - `Book`: A list of `Page`s, see [`BookState`]. Can be empty. +//! - `Queue`: A `Book` together with an `MessageOrigin` which can be part of the `ReadyRing`. Can +//! be empty. +//! - `ReadyRing`: A double-linked list which contains all *ready* `Queue`s. It chains together the +//! queues via their `ready_neighbours` fields. A `Queue` is *ready* if it contains at least one +//! `Message` which can be processed. Can be empty. +//! - `ServiceHead`: A pointer into the `ReadyRing` to the next `Queue` to be serviced. +//! - (`un`)`processed`: A message is marked as *processed* after it was executed by the pallet. A +//! message which was either: not yet executed or could not be executed remains as `unprocessed` +//! which is the default state for a message after being enqueued. +//! - `knitting`/`unknitting`: The means of adding or removing a `Queue` from the `ReadyRing`. +//! - `MEL`: The Max Encoded Length of a type, see [`codec::MaxEncodedLen`]. +//! +//! # Properties +//! +//! **Liveness - Enqueueing** +//! +//! It is always possible to enqueue any message for any `MessageOrigin`. +//! +//! **Liveness - Processing** +//! +//! `on_initialize` always respects its finite weight-limit. +//! +//! **Progress - Enqueueing** +//! +//! An enqueued message immediately becomes *unprocessed* and thereby eligible for execution. +//! +//! **Progress - Processing** +//! +//! The pallet will execute at least one unprocessed message per block, if there is any. Ensuring +//! this property needs careful consideration of the concrete weights, since it is possible that the +//! weight limit of `on_initialize` never allows for the execution of even one message; trivially if +//! the limit is set to zero. `integrity_test` can be used to ensure that this property holds. +//! +//! **Fairness - Enqueuing** +//! +//! Enqueueing a message for a specific `MessageOrigin` does not influence the ability to enqueue a +//! message for the same of any other `MessageOrigin`; guaranteed by **Liveness - Enqueueing**. +//! +//! **Fairness - Processing** +//! +//! The average amount of weight available for message processing is the same for each queue if the +//! number of queues is constant. Creating a new queue must therefore be, possibly economically, +//! expensive. Currently this is archived by having one queue per para-chain/thread, which keeps the +//! number of queues within `O(n)` and should be "good enough". + +#![cfg_attr(not(feature = "std"), no_std)] + +mod benchmarking; +mod integration_test; +mod mock; +pub mod mock_helpers; +mod tests; +pub mod weights; + +use codec::{Codec, Decode, Encode, MaxEncodedLen}; +use frame_support::{ + defensive, + pallet_prelude::*, + traits::{ + DefensiveTruncateFrom, EnqueueMessage, ExecuteOverweightError, Footprint, ProcessMessage, + ProcessMessageError, ServiceQueues, + }, + BoundedSlice, CloneNoBound, DefaultNoBound, +}; +use frame_system::pallet_prelude::*; +pub use pallet::*; +use scale_info::TypeInfo; +use sp_arithmetic::traits::{BaseArithmetic, Unsigned}; +use sp_runtime::{ + traits::{Hash, One, Zero}, + SaturatedConversion, Saturating, +}; +use sp_std::{fmt::Debug, ops::Deref, prelude::*, vec}; +use sp_weights::WeightMeter; +pub use weights::WeightInfo; + +/// Type for identifying a page. +type PageIndex = u32; + +/// Data encoded and prefixed to the encoded `MessageItem`. +#[derive(Encode, Decode, PartialEq, MaxEncodedLen, Debug)] +pub struct ItemHeader { + /// The length of this item, not including the size of this header. The next item of the page + /// follows immediately after the payload of this item. + payload_len: Size, + /// Whether this item has been processed. + is_processed: bool, +} + +/// A page of messages. Pages always contain at least one item. +#[derive( + CloneNoBound, Encode, Decode, RuntimeDebugNoBound, DefaultNoBound, TypeInfo, MaxEncodedLen, +)] +#[scale_info(skip_type_params(HeapSize))] +#[codec(mel_bound(Size: MaxEncodedLen))] +pub struct Page + Debug + Clone + Default, HeapSize: Get> { + /// Messages remaining to be processed; this includes overweight messages which have been + /// skipped. + remaining: Size, + /// The size of all remaining messages to be processed. + /// + /// Includes overweight messages outside of the `first` to `last` window. + remaining_size: Size, + /// The number of items before the `first` item in this page. + first_index: Size, + /// The heap-offset of the header of the first message item in this page which is ready for + /// processing. + first: Size, + /// The heap-offset of the header of the last message item in this page. + last: Size, + /// The heap. If `self.offset == self.heap.len()` then the page is empty and should be deleted. + heap: BoundedVec>, +} + +impl< + Size: BaseArithmetic + Unsigned + Copy + Into + Codec + MaxEncodedLen + Debug + Default, + HeapSize: Get, + > Page +{ + /// Create a [`Page`] from one unprocessed message. + fn from_message(message: BoundedSlice>) -> Self { + let payload_len = message.len(); + let data_len = ItemHeader::::max_encoded_len().saturating_add(payload_len); + let payload_len = payload_len.saturated_into(); + let header = ItemHeader:: { payload_len, is_processed: false }; + + let mut heap = Vec::with_capacity(data_len); + header.using_encoded(|h| heap.extend_from_slice(h)); + heap.extend_from_slice(message.deref()); + + Page { + remaining: One::one(), + remaining_size: payload_len, + first_index: Zero::zero(), + first: Zero::zero(), + last: Zero::zero(), + heap: BoundedVec::defensive_truncate_from(heap), + } + } + + /// Try to append one message to a page. + fn try_append_message( + &mut self, + message: BoundedSlice>, + ) -> Result<(), ()> { + let pos = self.heap.len(); + let payload_len = message.len(); + let data_len = ItemHeader::::max_encoded_len().saturating_add(payload_len); + let payload_len = payload_len.saturated_into(); + let header = ItemHeader:: { payload_len, is_processed: false }; + let heap_size: u32 = HeapSize::get().into(); + if (heap_size as usize).saturating_sub(self.heap.len()) < data_len { + // Can't fit. + return Err(()) + } + + let mut heap = sp_std::mem::take(&mut self.heap).into_inner(); + header.using_encoded(|h| heap.extend_from_slice(h)); + heap.extend_from_slice(message.deref()); + self.heap = BoundedVec::defensive_truncate_from(heap); + self.last = pos.saturated_into(); + self.remaining.saturating_inc(); + self.remaining_size.saturating_accrue(payload_len); + Ok(()) + } + + /// Returns the first message in the page without removing it. + /// + /// SAFETY: Does not panic even on corrupted storage. + fn peek_first(&self) -> Option>> { + if self.first > self.last { + return None + } + let f = (self.first.into() as usize).min(self.heap.len()); + let mut item_slice = &self.heap[f..]; + if let Ok(h) = ItemHeader::::decode(&mut item_slice) { + let payload_len = h.payload_len.into() as usize; + if payload_len <= item_slice.len() { + // impossible to truncate since is sliced up from `self.heap: BoundedVec` + return Some(BoundedSlice::defensive_truncate_from(&item_slice[..payload_len])) + } + } + defensive!("message-queue: heap corruption"); + None + } + + /// Point `first` at the next message, marking the first as processed if `is_processed` is true. + fn skip_first(&mut self, is_processed: bool) { + let f = (self.first.into() as usize).min(self.heap.len()); + if let Ok(mut h) = ItemHeader::decode(&mut &self.heap[f..]) { + if is_processed && !h.is_processed { + h.is_processed = true; + h.using_encoded(|d| self.heap[f..f + d.len()].copy_from_slice(d)); + self.remaining.saturating_dec(); + self.remaining_size.saturating_reduce(h.payload_len); + } + self.first + .saturating_accrue(ItemHeader::::max_encoded_len().saturated_into()); + self.first.saturating_accrue(h.payload_len); + self.first_index.saturating_inc(); + } + } + + /// Return the message with index `index` in the form of `(position, processed, message)`. + fn peek_index(&self, index: usize) -> Option<(usize, bool, &[u8])> { + let mut pos = 0; + let mut item_slice = &self.heap[..]; + let header_len: usize = ItemHeader::::max_encoded_len().saturated_into(); + for _ in 0..index { + let h = ItemHeader::::decode(&mut item_slice).ok()?; + let item_len = h.payload_len.into() as usize; + if item_slice.len() < item_len { + return None + } + item_slice = &item_slice[item_len..]; + pos.saturating_accrue(header_len.saturating_add(item_len)); + } + let h = ItemHeader::::decode(&mut item_slice).ok()?; + if item_slice.len() < h.payload_len.into() as usize { + return None + } + item_slice = &item_slice[..h.payload_len.into() as usize]; + Some((pos, h.is_processed, item_slice)) + } + + /// Set the `is_processed` flag for the item at `pos` to be `true` if not already and decrement + /// the `remaining` counter of the page. + /// + /// Does nothing if no [`ItemHeader`] could be decoded at the given position. + fn note_processed_at_pos(&mut self, pos: usize) { + if let Ok(mut h) = ItemHeader::::decode(&mut &self.heap[pos..]) { + if !h.is_processed { + h.is_processed = true; + h.using_encoded(|d| self.heap[pos..pos + d.len()].copy_from_slice(d)); + self.remaining.saturating_dec(); + self.remaining_size.saturating_reduce(h.payload_len); + } + } + } + + /// Returns whether the page is *complete* which means that no messages remain. + fn is_complete(&self) -> bool { + self.remaining.is_zero() + } +} + +/// A single link in the double-linked Ready Ring list. +#[derive(Clone, Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebug, PartialEq)] +pub struct Neighbours { + /// The previous queue. + prev: MessageOrigin, + /// The next queue. + next: MessageOrigin, +} + +/// The state of a queue as represented by a book of its pages. +/// +/// Each queue has exactly one book which holds all of its pages. All pages of a book combined +/// contain all of the messages of its queue; hence the name *Book*. +/// Books can be chained together in a double-linked fashion through their `ready_neighbours` field. +#[derive(Clone, Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebug)] +pub struct BookState { + /// The first page with some items to be processed in it. If this is `>= end`, then there are + /// no pages with items to be processing in them. + begin: PageIndex, + /// One more than the last page with some items to be processed in it. + end: PageIndex, + /// The number of pages stored at present. + /// + /// This might be larger than `end-begin`, because we keep pages with unprocessed overweight + /// messages outside of the end/begin window. + count: PageIndex, + /// If this book has any ready pages, then this will be `Some` with the previous and next + /// neighbours. This wraps around. + ready_neighbours: Option>, + /// The number of unprocessed messages stored at present. + message_count: u64, + /// The total size of all unprocessed messages stored at present. + size: u64, +} + +impl Default for BookState { + fn default() -> Self { + Self { begin: 0, end: 0, count: 0, ready_neighbours: None, message_count: 0, size: 0 } + } +} + +/// Handler code for when the items in a queue change. +pub trait OnQueueChanged { + /// Note that the queue `id` now has `item_count` items in it, taking up `items_size` bytes. + fn on_queue_changed(id: Id, items_count: u64, items_size: u64); +} + +impl OnQueueChanged for () { + fn on_queue_changed(_: Id, _: u64, _: u64) {} +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + /// The module configuration trait. + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + + /// Processor for a message. + /// + /// Must be set to [`mock_helpers::NoopMessageProcessor`] for benchmarking. + /// Other message processors that consumes exactly (1, 1) weight for any give message will + /// work as well. Otherwise the benchmarking will also measure the weight of the message + /// processor, which is not desired. + type MessageProcessor: ProcessMessage; + + /// Page/heap size type. + type Size: BaseArithmetic + + Unsigned + + Copy + + Into + + Member + + Encode + + Decode + + MaxEncodedLen + + TypeInfo + + Default; + + /// Code to be called when a message queue changes - either with items introduced or + /// removed. + type QueueChangeHandler: OnQueueChanged<::Origin>; + + /// The size of the page; this implies the maximum message size which can be sent. + /// + /// A good value depends on the expected message sizes, their weights, the weight that is + /// available for processing them and the maximal needed message size. The maximal message + /// size is slightly lower than this as defined by [`MaxMessageLenOf`]. + #[pallet::constant] + type HeapSize: Get; + + /// The maximum number of stale pages (i.e. of overweight messages) allowed before culling + /// can happen. Once there are more stale pages than this, then historical pages may be + /// dropped, even if they contain unprocessed overweight messages. + #[pallet::constant] + type MaxStale: Get; + + /// The amount of weight (if any) which should be provided to the message queue for + /// servicing enqueued items. + /// + /// This may be legitimately `None` in the case that you will call + /// `ServiceQueues::service_queues` manually. + #[pallet::constant] + type ServiceWeight: Get>; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Message discarded due to an inability to decode the item. Usually caused by state + /// corruption. + Discarded { hash: T::Hash }, + /// Message discarded due to an error in the `MessageProcessor` (usually a format error). + ProcessingFailed { hash: T::Hash, origin: MessageOriginOf, error: ProcessMessageError }, + /// Message is processed. + Processed { hash: T::Hash, origin: MessageOriginOf, weight_used: Weight, success: bool }, + /// Message placed in overweight queue. + OverweightEnqueued { + hash: T::Hash, + origin: MessageOriginOf, + page_index: PageIndex, + message_index: T::Size, + }, + /// This page was reaped. + PageReaped { origin: MessageOriginOf, index: PageIndex }, + } + + #[pallet::error] + pub enum Error { + /// Page is not reapable because it has items remaining to be processed and is not old + /// enough. + NotReapable, + /// Page to be reaped does not exist. + NoPage, + /// The referenced message could not be found. + NoMessage, + /// The message was already processed and cannot be processed again. + AlreadyProcessed, + /// The message is queued for future execution. + Queued, + /// There is temporarily not enough weight to continue servicing messages. + InsufficientWeight, + } + + /// The index of the first and last (non-empty) pages. + #[pallet::storage] + pub(super) type BookStateFor = + StorageMap<_, Twox64Concat, MessageOriginOf, BookState>, ValueQuery>; + + /// The origin at which we should begin servicing. + #[pallet::storage] + pub(super) type ServiceHead = StorageValue<_, MessageOriginOf, OptionQuery>; + + /// The map of page indices to pages. + #[pallet::storage] + pub(super) type Pages = StorageDoubleMap< + _, + Twox64Concat, + MessageOriginOf, + Twox64Concat, + PageIndex, + Page, + OptionQuery, + >; + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(_n: BlockNumberFor) -> Weight { + if let Some(weight_limit) = T::ServiceWeight::get() { + Self::service_queues(weight_limit) + } else { + Weight::zero() + } + } + + /// Check all assumptions about [`crate::Config`]. + fn integrity_test() { + assert!(!MaxMessageLenOf::::get().is_zero(), "HeapSize too low"); + } + } + + #[pallet::call] + impl Pallet { + /// Remove a page which has no more messages remaining to be processed or is stale. + #[pallet::weight(T::WeightInfo::reap_page())] + pub fn reap_page( + origin: OriginFor, + message_origin: MessageOriginOf, + page_index: PageIndex, + ) -> DispatchResult { + let _ = ensure_signed(origin)?; + Self::do_reap_page(&message_origin, page_index) + } + + /// Execute an overweight message. + /// + /// - `origin`: Must be `Signed`. + /// - `message_origin`: The origin from which the message to be executed arrived. + /// - `page`: The page in the queue in which the message to be executed is sitting. + /// - `index`: The index into the queue of the message to be executed. + /// - `weight_limit`: The maximum amount of weight allowed to be consumed in the execution + /// of the message. + /// + /// Benchmark complexity considerations: O(index + weight_limit). + #[pallet::weight( + T::WeightInfo::execute_overweight_page_updated().max( + T::WeightInfo::execute_overweight_page_removed()).saturating_add(*weight_limit) + )] + pub fn execute_overweight( + origin: OriginFor, + message_origin: MessageOriginOf, + page: PageIndex, + index: T::Size, + weight_limit: Weight, + ) -> DispatchResultWithPostInfo { + let _ = ensure_signed(origin)?; + let actual_weight = + Self::do_execute_overweight(message_origin, page, index, weight_limit)?; + Ok(Some(actual_weight).into()) + } + } +} + +/// The status of a page after trying to execute its next message. +#[derive(PartialEq, Debug)] +enum PageExecutionStatus { + /// The execution bailed because there was not enough weight remaining. + Bailed, + /// No more messages could be loaded. This does _not_ imply `page.is_complete()`. + /// + /// The reasons for this status are: + /// - The end of the page is reached but there could still be skipped messages. + /// - The storage is corrupted. + NoMore, +} + +/// The status after trying to execute the next item of a [`Page`]. +#[derive(PartialEq, Debug)] +enum ItemExecutionStatus { + /// The execution bailed because there was not enough weight remaining. + Bailed, + /// The item was not found. + NoItem, + /// Whether the execution of an item resulted in it being processed. + /// + /// One reason for `false` would be permanently overweight. + Executed(bool), +} + +/// The status of an attempt to process a message. +#[derive(PartialEq)] +enum MessageExecutionStatus { + /// There is not enough weight remaining at present. + InsufficientWeight, + /// There will never be enough weight. + Overweight, + /// The message was processed successfully. + Processed, + /// The message was processed and resulted in a permanent error. + Unprocessable, +} + +impl Pallet { + /// Knit `origin` into the ready ring right at the end. + /// + /// Return the two ready ring neighbours of `origin`. + fn ready_ring_knit(origin: &MessageOriginOf) -> Result>, ()> { + if let Some(head) = ServiceHead::::get() { + let mut head_book_state = BookStateFor::::get(&head); + let mut head_neighbours = head_book_state.ready_neighbours.take().ok_or(())?; + let tail = head_neighbours.prev; + head_neighbours.prev = origin.clone(); + head_book_state.ready_neighbours = Some(head_neighbours); + BookStateFor::::insert(&head, head_book_state); + + let mut tail_book_state = BookStateFor::::get(&tail); + let mut tail_neighbours = tail_book_state.ready_neighbours.take().ok_or(())?; + tail_neighbours.next = origin.clone(); + tail_book_state.ready_neighbours = Some(tail_neighbours); + BookStateFor::::insert(&tail, tail_book_state); + + Ok(Neighbours { next: head, prev: tail }) + } else { + ServiceHead::::put(origin); + Ok(Neighbours { next: origin.clone(), prev: origin.clone() }) + } + } + + fn ready_ring_unknit(origin: &MessageOriginOf, neighbours: Neighbours>) { + if origin == &neighbours.next { + debug_assert!( + origin == &neighbours.prev, + "unknitting from single item ring; outgoing must be only item" + ); + // Service queue empty. + ServiceHead::::kill(); + } else { + BookStateFor::::mutate(&neighbours.next, |book_state| { + if let Some(ref mut n) = book_state.ready_neighbours { + n.prev = neighbours.prev.clone() + } + }); + BookStateFor::::mutate(&neighbours.prev, |book_state| { + if let Some(ref mut n) = book_state.ready_neighbours { + n.next = neighbours.next.clone() + } + }); + if let Some(head) = ServiceHead::::get() { + if &head == origin { + ServiceHead::::put(neighbours.next); + } + } else { + defensive!("`ServiceHead` must be some if there was a ready queue"); + } + } + } + + /// Tries to bump the current `ServiceHead` to the next ready queue. + /// + /// Returns the current head if it got be bumped and `None` otherwise. + fn bump_service_head(weight: &mut WeightMeter) -> Option> { + if !weight.check_accrue(T::WeightInfo::bump_service_head()) { + return None + } + + if let Some(head) = ServiceHead::::get() { + let mut head_book_state = BookStateFor::::get(&head); + if let Some(head_neighbours) = head_book_state.ready_neighbours.take() { + ServiceHead::::put(&head_neighbours.next); + Some(head) + } else { + None + } + } else { + None + } + } + + fn do_enqueue_message( + origin: &MessageOriginOf, + message: BoundedSlice>, + ) { + let mut book_state = BookStateFor::::get(origin); + book_state.message_count.saturating_inc(); + book_state + .size + // This should be payload size, but here the payload *is* the message. + .saturating_accrue(message.len() as u64); + + if book_state.end > book_state.begin { + debug_assert!(book_state.ready_neighbours.is_some(), "Must be in ready ring if ready"); + // Already have a page in progress - attempt to append. + let last = book_state.end - 1; + let mut page = match Pages::::get(origin, last) { + Some(p) => p, + None => { + defensive!("Corruption: referenced page doesn't exist."); + return + }, + }; + if page.try_append_message::(message).is_ok() { + Pages::::insert(origin, last, &page); + BookStateFor::::insert(origin, book_state); + return + } + } else { + debug_assert!( + book_state.ready_neighbours.is_none(), + "Must not be in ready ring if not ready" + ); + // insert into ready queue. + match Self::ready_ring_knit(origin) { + Ok(neighbours) => book_state.ready_neighbours = Some(neighbours), + Err(()) => { + defensive!("Ring state invalid when knitting"); + }, + } + } + // No room on the page or no page - link in a new page. + book_state.end.saturating_inc(); + book_state.count.saturating_inc(); + let page = Page::from_message::(message); + Pages::::insert(origin, book_state.end - 1, page); + // NOTE: `T::QueueChangeHandler` is called by the caller. + BookStateFor::::insert(origin, book_state); + } + + /// Try to execute a single message that was marked as overweight. + /// + /// The `weight_limit` is the weight that can be consumed to execute the message. The base + /// weight of the function it self must be measured by the caller. + pub fn do_execute_overweight( + origin: MessageOriginOf, + page_index: PageIndex, + index: T::Size, + weight_limit: Weight, + ) -> Result> { + let mut book_state = BookStateFor::::get(&origin); + let mut page = Pages::::get(&origin, page_index).ok_or(Error::::NoPage)?; + let (pos, is_processed, payload) = + page.peek_index(index.into() as usize).ok_or(Error::::NoMessage)?; + let payload_len = payload.len() as u64; + ensure!( + page_index < book_state.begin || + (page_index == book_state.begin && pos < page.first.into() as usize), + Error::::Queued + ); + ensure!(!is_processed, Error::::AlreadyProcessed); + use MessageExecutionStatus::*; + let mut weight_counter = WeightMeter::from_limit(weight_limit); + match Self::process_message_payload( + origin.clone(), + page_index, + index, + payload, + &mut weight_counter, + Weight::MAX, + // ^^^ We never recognise it as permanently overweight, since that would result in an + // additional overweight event being deposited. + ) { + Overweight | InsufficientWeight => Err(Error::::InsufficientWeight), + Unprocessable | Processed => { + page.note_processed_at_pos(pos); + book_state.message_count.saturating_dec(); + book_state.size.saturating_reduce(payload_len); + let page_weight = if page.remaining.is_zero() { + debug_assert!( + page.remaining_size.is_zero(), + "no messages remaining; no space taken; qed" + ); + Pages::::remove(&origin, page_index); + debug_assert!(book_state.count >= 1, "page exists, so book must have pages"); + book_state.count.saturating_dec(); + T::WeightInfo::execute_overweight_page_removed() + // no need to consider .first or ready ring since processing an overweight page + // would not alter that state. + } else { + Pages::::insert(&origin, page_index, page); + T::WeightInfo::execute_overweight_page_updated() + }; + BookStateFor::::insert(&origin, &book_state); + T::QueueChangeHandler::on_queue_changed( + origin, + book_state.message_count, + book_state.size, + ); + Ok(weight_counter.consumed.saturating_add(page_weight)) + }, + } + } + + /// Remove a stale page or one which has no more messages remaining to be processed. + fn do_reap_page(origin: &MessageOriginOf, page_index: PageIndex) -> DispatchResult { + let mut book_state = BookStateFor::::get(origin); + // definitely not reapable if the page's index is no less than the `begin`ning of ready + // pages. + ensure!(page_index < book_state.begin, Error::::NotReapable); + + let page = Pages::::get(origin, page_index).ok_or(Error::::NoPage)?; + + // definitely reapable if the page has no messages in it. + let reapable = page.remaining.is_zero(); + + // also reapable if the page index has dropped below our watermark. + let cullable = || { + let total_pages = book_state.count; + let ready_pages = book_state.end.saturating_sub(book_state.begin).min(total_pages); + + // The number of stale pages - i.e. pages which contain unprocessed overweight messages. + // We would prefer to keep these around but will restrict how far into history they can + // extend if we notice that there's too many of them. + // + // We don't know *where* in history these pages are so we use a dynamic formula which + // reduces the historical time horizon as the stale pages pile up and increases it as + // they reduce. + let stale_pages = total_pages - ready_pages; + + // The maximum number of stale pages (i.e. of overweight messages) allowed before + // culling can happen at all. Once there are more stale pages than this, then historical + // pages may be dropped, even if they contain unprocessed overweight messages. + let max_stale = T::MaxStale::get(); + + // The amount beyond the maximum which are being used. If it's not beyond the maximum + // then we exit now since no culling is needed. + let overflow = match stale_pages.checked_sub(max_stale + 1) { + Some(x) => x + 1, + None => return false, + }; + + // The special formula which tells us how deep into index-history we will pages. As + // the overflow is greater (and thus the need to drop items from storage is more urgent) + // this is reduced, allowing a greater range of pages to be culled. + // With a minimum `overflow` (`1`), this returns `max_stale ** 2`, indicating we only + // cull beyond that number of indices deep into history. + // At this overflow increases, our depth reduces down to a limit of `max_stale`. We + // never want to reduce below this since this will certainly allow enough pages to be + // culled in order to bring `overflow` back to zero. + let backlog = (max_stale * max_stale / overflow).max(max_stale); + + let watermark = book_state.begin.saturating_sub(backlog); + page_index < watermark + }; + ensure!(reapable || cullable(), Error::::NotReapable); + + Pages::::remove(origin, page_index); + debug_assert!(book_state.count > 0, "reaping a page implies there are pages"); + book_state.count.saturating_dec(); + book_state.message_count.saturating_reduce(page.remaining.into() as u64); + book_state.size.saturating_reduce(page.remaining_size.into() as u64); + BookStateFor::::insert(origin, &book_state); + T::QueueChangeHandler::on_queue_changed( + origin.clone(), + book_state.message_count, + book_state.size, + ); + Self::deposit_event(Event::PageReaped { origin: origin.clone(), index: page_index }); + + Ok(()) + } + + /// Execute any messages remaining to be processed in the queue of `origin`, using up to + /// `weight_limit` to do so. Any messages which would take more than `overweight_limit` to + /// execute are deemed overweight and ignored. + fn service_queue( + origin: MessageOriginOf, + weight: &mut WeightMeter, + overweight_limit: Weight, + ) -> (bool, Option>) { + if !weight.check_accrue( + T::WeightInfo::service_queue_base().saturating_add(T::WeightInfo::ready_ring_unknit()), + ) { + return (false, None) + } + + let mut book_state = BookStateFor::::get(&origin); + let mut total_processed = 0; + + while book_state.end > book_state.begin { + let (processed, status) = + Self::service_page(&origin, &mut book_state, weight, overweight_limit); + total_processed.saturating_accrue(processed); + match status { + // Store the page progress and do not go to the next one. + PageExecutionStatus::Bailed => break, + // Go to the next page if this one is at the end. + PageExecutionStatus::NoMore => (), + }; + book_state.begin.saturating_inc(); + } + let next_ready = book_state.ready_neighbours.as_ref().map(|x| x.next.clone()); + if book_state.begin >= book_state.end && total_processed > 0 { + // No longer ready - unknit. + if let Some(neighbours) = book_state.ready_neighbours.take() { + Self::ready_ring_unknit(&origin, neighbours); + } else { + defensive!("Freshly processed queue must have been ready"); + } + } + BookStateFor::::insert(&origin, &book_state); + if total_processed > 0 { + T::QueueChangeHandler::on_queue_changed( + origin, + book_state.message_count, + book_state.size, + ); + } + (total_processed > 0, next_ready) + } + + /// Service as many messages of a page as possible. + /// + /// Returns how many messages were processed and the page's status. + fn service_page( + origin: &MessageOriginOf, + book_state: &mut BookStateOf, + weight: &mut WeightMeter, + overweight_limit: Weight, + ) -> (u32, PageExecutionStatus) { + use PageExecutionStatus::*; + if !weight.check_accrue( + T::WeightInfo::service_page_base_completion() + .max(T::WeightInfo::service_page_base_no_completion()), + ) { + return (0, Bailed) + } + + let page_index = book_state.begin; + let mut page = match Pages::::get(origin, page_index) { + Some(p) => p, + None => { + defensive!("message-queue: referenced page not found"); + return (0, NoMore) + }, + }; + + let mut total_processed = 0; + + // Execute as many messages as possible. + let status = loop { + use ItemExecutionStatus::*; + match Self::service_page_item( + origin, + page_index, + book_state, + &mut page, + weight, + overweight_limit, + ) { + Bailed => break PageExecutionStatus::Bailed, + NoItem => break PageExecutionStatus::NoMore, + // Keep going as long as we make progress... + Executed(true) => total_processed.saturating_inc(), + Executed(false) => (), + } + }; + + if page.is_complete() { + debug_assert!(status != Bailed, "we never bail if a page became complete"); + Pages::::remove(origin, page_index); + debug_assert!(book_state.count > 0, "completing a page implies there are pages"); + book_state.count.saturating_dec(); + } else { + Pages::::insert(origin, page_index, page); + } + (total_processed, status) + } + + /// Execute the next message of a page. + pub(crate) fn service_page_item( + origin: &MessageOriginOf, + page_index: PageIndex, + book_state: &mut BookStateOf, + page: &mut PageOf, + weight: &mut WeightMeter, + overweight_limit: Weight, + ) -> ItemExecutionStatus { + // This ugly pre-checking is needed for the invariant + // "we never bail if a page became complete". + if page.is_complete() { + return ItemExecutionStatus::NoItem + } + if !weight.check_accrue(T::WeightInfo::service_page_item()) { + return ItemExecutionStatus::Bailed + } + + let payload = &match page.peek_first() { + Some(m) => m, + None => return ItemExecutionStatus::NoItem, + }[..]; + + use MessageExecutionStatus::*; + let is_processed = match Self::process_message_payload( + origin.clone(), + page_index, + page.first_index, + payload.deref(), + weight, + overweight_limit, + ) { + InsufficientWeight => return ItemExecutionStatus::Bailed, + Processed | Unprocessable => true, + Overweight => false, + }; + + if is_processed { + book_state.message_count.saturating_dec(); + book_state.size.saturating_reduce(payload.len() as u64); + } + page.skip_first(is_processed); + ItemExecutionStatus::Executed(is_processed) + } + + /// Print the pages in each queue and the messages in each page. + /// + /// Processed messages are prefixed with a `*` and the current `begin`ning page with a `>`. + /// + /// # Example output + /// + /// ```text + /// queue Here: + /// page 0: [] + /// > page 1: [] + /// page 2: ["\0weight=4", "\0c", ] + /// page 3: ["\0bigbig 1", ] + /// page 4: ["\0bigbig 2", ] + /// page 5: ["\0bigbig 3", ] + /// ``` + #[cfg(feature = "std")] + pub fn debug_info() -> String { + let mut info = String::new(); + for (origin, book_state) in BookStateFor::::iter() { + let mut queue = format!("queue {:?}:\n", &origin); + let mut pages = Pages::::iter_prefix(&origin).collect::>(); + pages.sort_by(|(a, _), (b, _)| a.cmp(b)); + for (page_index, mut page) in pages.into_iter() { + let page_info = if book_state.begin == page_index { ">" } else { " " }; + let mut page_info = format!( + "{} page {} ({:?} first, {:?} last, {:?} remain): [ ", + page_info, page_index, page.first, page.last, page.remaining + ); + for i in 0..u32::MAX { + if let Some((_, processed, message)) = + page.peek_index(i.try_into().expect("std-only code")) + { + let msg = String::from_utf8_lossy(message.deref()); + if processed { + page_info.push('*'); + } + page_info.push_str(&format!("{:?}, ", msg)); + page.skip_first(true); + } else { + break + } + } + page_info.push_str("]\n"); + queue.push_str(&page_info); + } + info.push_str(&queue); + } + info + } + + /// Process a single message. + /// + /// The base weight of this function needs to be accounted for by the caller. `weight` is the + /// remaining weight to process the message. `overweight_limit` is the maximum weight that a + /// message can ever consume. Messages above this limit are marked as permanently overweight. + fn process_message_payload( + origin: MessageOriginOf, + page_index: PageIndex, + message_index: T::Size, + message: &[u8], + weight: &mut WeightMeter, + overweight_limit: Weight, + ) -> MessageExecutionStatus { + let hash = T::Hashing::hash(message); + use ProcessMessageError::Overweight; + match T::MessageProcessor::process_message(message, origin.clone(), weight.remaining()) { + Err(Overweight(w)) if w.any_gt(overweight_limit) => { + // Permanently overweight. + Self::deposit_event(Event::::OverweightEnqueued { + hash, + origin, + page_index, + message_index, + }); + MessageExecutionStatus::Overweight + }, + Err(Overweight(_)) => { + // Temporarily overweight - save progress and stop processing this + // queue. + MessageExecutionStatus::InsufficientWeight + }, + Err(error) => { + // Permanent error - drop + Self::deposit_event(Event::::ProcessingFailed { hash, origin, error }); + MessageExecutionStatus::Unprocessable + }, + Ok((success, weight_used)) => { + // Success + weight.defensive_saturating_accrue(weight_used); + let event = Event::::Processed { hash, origin, weight_used, success }; + Self::deposit_event(event); + MessageExecutionStatus::Processed + }, + } + } +} + +/// Provides a [`sp_core::Get`] to access the `MEL` of a [`codec::MaxEncodedLen`] type. +pub struct MaxEncodedLenOf(sp_std::marker::PhantomData); +impl Get for MaxEncodedLenOf { + fn get() -> u32 { + T::max_encoded_len() as u32 + } +} + +/// Calculates the maximum message length and exposed it through the [`codec::MaxEncodedLen`] trait. +pub struct MaxMessageLen( + sp_std::marker::PhantomData<(Origin, Size, HeapSize)>, +); +impl, HeapSize: Get> Get + for MaxMessageLen +{ + fn get() -> u32 { + (HeapSize::get().into()).saturating_sub(ItemHeader::::max_encoded_len() as u32) + } +} + +/// The maximal message length. +pub type MaxMessageLenOf = + MaxMessageLen, ::Size, ::HeapSize>; +/// The maximal encoded origin length. +pub type MaxOriginLenOf = MaxEncodedLenOf>; +/// The `MessageOrigin` of this pallet. +pub type MessageOriginOf = <::MessageProcessor as ProcessMessage>::Origin; +/// The maximal heap size of a page. +pub type HeapSizeU32Of = IntoU32<::HeapSize, ::Size>; +/// The [`Page`] of this pallet. +pub type PageOf = Page<::Size, ::HeapSize>; +/// The [`BookState`] of this pallet. +pub type BookStateOf = BookState>; + +/// Converts a [`sp_core::Get`] with returns a type that can be cast into an `u32` into a `Get` +/// which returns an `u32`. +pub struct IntoU32(sp_std::marker::PhantomData<(T, O)>); +impl, O: Into> Get for IntoU32 { + fn get() -> u32 { + T::get().into() + } +} + +impl ServiceQueues for Pallet { + type OverweightMessageAddress = (MessageOriginOf, PageIndex, T::Size); + + fn service_queues(weight_limit: Weight) -> Weight { + // The maximum weight that processing a single message may take. + let overweight_limit = weight_limit; + let mut weight = WeightMeter::from_limit(weight_limit); + + let mut next = match Self::bump_service_head(&mut weight) { + Some(h) => h, + None => return weight.consumed, + }; + // The last queue that did not make any progress. + // The loop aborts as soon as it arrives at this queue again without making any progress + // on other queues in between. + let mut last_no_progress = None; + + loop { + let (progressed, n) = Self::service_queue(next.clone(), &mut weight, overweight_limit); + next = match n { + Some(n) => + if !progressed { + if last_no_progress == Some(n.clone()) { + break + } + if last_no_progress.is_none() { + last_no_progress = Some(next.clone()) + } + n + } else { + last_no_progress = None; + n + }, + None => break, + } + } + weight.consumed + } + + /// Execute a single overweight message. + /// + /// The weight limit must be enough for `execute_overweight` and the message execution itself. + fn execute_overweight( + weight_limit: Weight, + (message_origin, page, index): Self::OverweightMessageAddress, + ) -> Result { + let mut weight = WeightMeter::from_limit(weight_limit); + if !weight.check_accrue( + T::WeightInfo::execute_overweight_page_removed() + .max(T::WeightInfo::execute_overweight_page_updated()), + ) { + return Err(ExecuteOverweightError::InsufficientWeight) + } + + Pallet::::do_execute_overweight(message_origin, page, index, weight.remaining()).map_err( + |e| match e { + Error::::InsufficientWeight => ExecuteOverweightError::InsufficientWeight, + _ => ExecuteOverweightError::NotFound, + }, + ) + } +} + +impl EnqueueMessage> for Pallet { + type MaxMessageLen = + MaxMessageLen<::Origin, T::Size, T::HeapSize>; + + fn enqueue_message( + message: BoundedSlice, + origin: ::Origin, + ) { + Self::do_enqueue_message(&origin, message); + let book_state = BookStateFor::::get(&origin); + T::QueueChangeHandler::on_queue_changed(origin, book_state.message_count, book_state.size); + } + + fn enqueue_messages<'a>( + messages: impl Iterator>, + origin: ::Origin, + ) { + for message in messages { + Self::do_enqueue_message(&origin, message); + } + let book_state = BookStateFor::::get(&origin); + T::QueueChangeHandler::on_queue_changed(origin, book_state.message_count, book_state.size); + } + + fn sweep_queue(origin: MessageOriginOf) { + if !BookStateFor::::contains_key(&origin) { + return + } + let mut book_state = BookStateFor::::get(&origin); + book_state.begin = book_state.end; + if let Some(neighbours) = book_state.ready_neighbours.take() { + Self::ready_ring_unknit(&origin, neighbours); + } + BookStateFor::::insert(&origin, &book_state); + } + + fn footprint(origin: MessageOriginOf) -> Footprint { + let book_state = BookStateFor::::get(&origin); + Footprint { count: book_state.message_count, size: book_state.size } + } +} diff --git a/frame/message-queue/src/mock.rs b/frame/message-queue/src/mock.rs new file mode 100644 index 0000000000..bb9942443e --- /dev/null +++ b/frame/message-queue/src/mock.rs @@ -0,0 +1,312 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +#![cfg(test)] + +pub use super::mock_helpers::*; +use super::*; + +use crate as pallet_message_queue; +use frame_support::{ + parameter_types, + traits::{ConstU32, ConstU64}, +}; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, +}; +use sp_std::collections::btree_map::BTreeMap; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + MessageQueue: pallet_message_queue::{Pallet, Call, Storage, Event}, + } +); +parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); +} +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type RuntimeCall = RuntimeCall; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} +parameter_types! { + pub const HeapSize: u32 = 24; + pub const MaxStale: u32 = 2; + pub const ServiceWeight: Option = Some(Weight::from_parts(10, 10)); +} +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = MockedWeightInfo; + type MessageProcessor = RecordingMessageProcessor; + type Size = u32; + type QueueChangeHandler = RecordingQueueChangeHandler; + type HeapSize = HeapSize; + type MaxStale = MaxStale; + type ServiceWeight = ServiceWeight; +} + +/// Mocked `WeightInfo` impl with allows to set the weight per call. +pub struct MockedWeightInfo; + +parameter_types! { + /// Storage for `MockedWeightInfo`, do not use directly. + pub static WeightForCall: BTreeMap = Default::default(); +} + +/// Set the return value for a function from the `WeightInfo` trait. +impl MockedWeightInfo { + /// Set the weight of a specific weight function. + pub fn set_weight(call_name: &str, weight: Weight) { + let mut calls = WeightForCall::get(); + calls.insert(call_name.into(), weight); + WeightForCall::set(calls); + } +} + +impl crate::weights::WeightInfo for MockedWeightInfo { + fn reap_page() -> Weight { + WeightForCall::get().get("reap_page").copied().unwrap_or_default() + } + fn execute_overweight_page_updated() -> Weight { + WeightForCall::get() + .get("execute_overweight_page_updated") + .copied() + .unwrap_or_default() + } + fn execute_overweight_page_removed() -> Weight { + WeightForCall::get() + .get("execute_overweight_page_removed") + .copied() + .unwrap_or_default() + } + fn service_page_base_completion() -> Weight { + WeightForCall::get() + .get("service_page_base_completion") + .copied() + .unwrap_or_default() + } + fn service_page_base_no_completion() -> Weight { + WeightForCall::get() + .get("service_page_base_no_completion") + .copied() + .unwrap_or_default() + } + fn service_queue_base() -> Weight { + WeightForCall::get().get("service_queue_base").copied().unwrap_or_default() + } + fn bump_service_head() -> Weight { + WeightForCall::get().get("bump_service_head").copied().unwrap_or_default() + } + fn service_page_item() -> Weight { + WeightForCall::get().get("service_page_item").copied().unwrap_or_default() + } + fn ready_ring_knit() -> Weight { + WeightForCall::get().get("ready_ring_knit").copied().unwrap_or_default() + } + fn ready_ring_unknit() -> Weight { + WeightForCall::get().get("ready_ring_unknit").copied().unwrap_or_default() + } +} + +parameter_types! { + pub static MessagesProcessed: Vec<(Vec, MessageOrigin)> = vec![]; +} + +/// A message processor which records all processed messages into [`MessagesProcessed`]. +pub struct RecordingMessageProcessor; +impl ProcessMessage for RecordingMessageProcessor { + /// The transport from where a message originates. + type Origin = MessageOrigin; + + /// Process the given message, using no more than `weight_limit` in weight to do so. + /// + /// Consumes exactly `n` weight of all components if it starts `weight=n` and `1` otherwise. + /// Errors if given the `weight_limit` is insufficient to process the message or if the message + /// is `badformat`, `corrupt` or `unsupported` with the respective error. + fn process_message( + message: &[u8], + origin: Self::Origin, + weight_limit: Weight, + ) -> Result<(bool, Weight), ProcessMessageError> { + processing_message(message)?; + + let weight = if message.starts_with(&b"weight="[..]) { + let mut w: u64 = 0; + for &c in &message[7..] { + if (b'0'..=b'9').contains(&c) { + w = w * 10 + (c - b'0') as u64; + } else { + break + } + } + w + } else { + 1 + }; + let weight = Weight::from_parts(weight, weight); + + if weight.all_lte(weight_limit) { + let mut m = MessagesProcessed::get(); + m.push((message.to_vec(), origin)); + MessagesProcessed::set(m); + Ok((true, weight)) + } else { + Err(ProcessMessageError::Overweight(weight)) + } + } +} + +/// Processed a mocked message. Messages that end with `badformat`, `corrupt` or `unsupported` will +/// fail with the respective error. +fn processing_message(msg: &[u8]) -> Result<(), ProcessMessageError> { + let msg = String::from_utf8_lossy(msg); + if msg.ends_with("badformat") { + Err(ProcessMessageError::BadFormat) + } else if msg.ends_with("corrupt") { + Err(ProcessMessageError::Corrupt) + } else if msg.ends_with("unsupported") { + Err(ProcessMessageError::Unsupported) + } else { + Ok(()) + } +} + +parameter_types! { + pub static NumMessagesProcessed: usize = 0; + pub static NumMessagesErrored: usize = 0; +} + +/// Similar to [`RecordingMessageProcessor`] but only counts the number of messages processed and +/// does always consume one weight per message. +/// +/// The [`RecordingMessageProcessor`] is a bit too slow for the integration tests. +pub struct CountingMessageProcessor; +impl ProcessMessage for CountingMessageProcessor { + type Origin = MessageOrigin; + + fn process_message( + message: &[u8], + _origin: Self::Origin, + weight_limit: Weight, + ) -> Result<(bool, Weight), ProcessMessageError> { + if let Err(e) = processing_message(message) { + NumMessagesErrored::set(NumMessagesErrored::get() + 1); + return Err(e) + } + let weight = Weight::from_parts(1, 1); + + if weight.all_lte(weight_limit) { + NumMessagesProcessed::set(NumMessagesProcessed::get() + 1); + Ok((true, weight)) + } else { + Err(ProcessMessageError::Overweight(weight)) + } + } +} + +parameter_types! { + /// Storage for `RecordingQueueChangeHandler`, do not use directly. + pub static QueueChanges: Vec<(MessageOrigin, u64, u64)> = vec![]; +} + +/// Records all queue changes into [`QueueChanges`]. +pub struct RecordingQueueChangeHandler; +impl OnQueueChanged for RecordingQueueChangeHandler { + fn on_queue_changed(id: MessageOrigin, items_count: u64, items_size: u64) { + QueueChanges::mutate(|cs| cs.push((id, items_count, items_size))); + } +} + +/// Create new test externalities. +/// +/// Is generic since it is used by the unit test, integration tests and benchmarks. +pub fn new_test_ext() -> sp_io::TestExternalities +where + ::BlockNumber: From, +{ + sp_tracing::try_init_simple(); + WeightForCall::take(); + QueueChanges::take(); + NumMessagesErrored::take(); + let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| frame_system::Pallet::::set_block_number(1.into())); + ext +} + +/// Set the weight of a specific weight function. +pub fn set_weight(name: &str, w: Weight) { + MockedWeightInfo::set_weight::(name, w); +} + +/// Assert that exactly these pages are present. Assumes `Here` origin. +pub fn assert_pages(indices: &[u32]) { + assert_eq!(Pages::::iter().count(), indices.len()); + for i in indices { + assert!(Pages::::contains_key(MessageOrigin::Here, i)); + } +} + +/// Build a ring with three queues: `Here`, `There` and `Everywhere(0)`. +pub fn build_triple_ring() { + use MessageOrigin::*; + build_ring::(&[Here, There, Everywhere(0)]) +} + +/// Shim to get rid of the annoying `::` everywhere. +pub fn assert_ring(queues: &[MessageOrigin]) { + super::mock_helpers::assert_ring::(queues); +} + +pub fn knit(queue: &MessageOrigin) { + super::mock_helpers::knit::(queue); +} + +pub fn unknit(queue: &MessageOrigin) { + super::mock_helpers::unknit::(queue); +} diff --git a/frame/message-queue/src/mock_helpers.rs b/frame/message-queue/src/mock_helpers.rs new file mode 100644 index 0000000000..39d961d8fc --- /dev/null +++ b/frame/message-queue/src/mock_helpers.rs @@ -0,0 +1,185 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Std setup helpers for testing and benchmarking. +//! +//! Cannot be put into mock.rs since benchmarks require no-std and mock.rs is std. + +use crate::*; +use frame_support::traits::Defensive; + +/// Converts `Self` into a `Weight` by using `Self` for all components. +pub trait IntoWeight { + fn into_weight(self) -> Weight; +} + +impl IntoWeight for u64 { + fn into_weight(self) -> Weight { + Weight::from_parts(self, self) + } +} + +/// Mocked message origin for testing. +#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, TypeInfo, Debug)] +pub enum MessageOrigin { + Here, + There, + Everywhere(u32), +} + +impl From for MessageOrigin { + fn from(i: u32) -> Self { + Self::Everywhere(i) + } +} + +/// Processes any message and consumes (1, 1) weight per message. +pub struct NoopMessageProcessor; +impl ProcessMessage for NoopMessageProcessor { + type Origin = MessageOrigin; + + fn process_message( + _message: &[u8], + _origin: Self::Origin, + weight_limit: Weight, + ) -> Result<(bool, Weight), ProcessMessageError> { + let weight = Weight::from_parts(1, 1); + + if weight.all_lte(weight_limit) { + Ok((true, weight)) + } else { + Err(ProcessMessageError::Overweight(weight)) + } + } +} + +/// Create a message from the given data. +pub fn msg>(x: &'static str) -> BoundedSlice { + BoundedSlice::defensive_truncate_from(x.as_bytes()) +} + +pub fn vmsg(x: &'static str) -> Vec { + x.as_bytes().to_vec() +} + +/// Create a page from a single message. +pub fn page(msg: &[u8]) -> PageOf { + PageOf::::from_message::(msg.try_into().unwrap()) +} + +pub fn single_page_book() -> BookStateOf { + BookState { begin: 0, end: 1, count: 1, ready_neighbours: None, message_count: 0, size: 0 } +} + +pub fn empty_book() -> BookStateOf { + BookState { begin: 0, end: 1, count: 1, ready_neighbours: None, message_count: 0, size: 0 } +} + +/// Returns a full page of messages with their index as payload and the number of messages. +pub fn full_page() -> (PageOf, usize) { + let mut msgs = 0; + let mut page = PageOf::::default(); + for i in 0..u32::MAX { + let r = i.using_encoded(|d| page.try_append_message::(d.try_into().unwrap())); + if r.is_err() { + break + } else { + msgs += 1; + } + } + assert!(msgs > 0, "page must hold at least one message"); + (page, msgs) +} + +/// Returns a page filled with empty messages and the number of messages. +pub fn book_for(page: &PageOf) -> BookStateOf { + BookState { + count: 1, + begin: 0, + end: 1, + ready_neighbours: None, + message_count: page.remaining.into() as u64, + size: page.remaining_size.into() as u64, + } +} + +/// Assert the last event that was emitted. +#[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] +pub fn assert_last_event(generic_event: ::RuntimeEvent) { + assert!( + !frame_system::Pallet::::block_number().is_zero(), + "The genesis block has n o events" + ); + frame_system::Pallet::::assert_last_event(generic_event.into()); +} + +/// Provide a setup for `bump_service_head`. +pub fn setup_bump_service_head( + current: <::MessageProcessor as ProcessMessage>::Origin, + next: <::MessageProcessor as ProcessMessage>::Origin, +) { + let mut book = single_page_book::(); + book.ready_neighbours = Some(Neighbours::> { prev: next.clone(), next }); + ServiceHead::::put(¤t); + BookStateFor::::insert(¤t, &book); +} + +/// Knit a queue into the ready-ring and write it back to storage. +pub fn knit(o: &<::MessageProcessor as ProcessMessage>::Origin) { + let mut b = BookStateFor::::get(o); + b.ready_neighbours = crate::Pallet::::ready_ring_knit(o).ok().defensive(); + BookStateFor::::insert(o, b); +} + +/// Unknit a queue into the ready-ring and write it back to storage. +pub fn unknit(o: &<::MessageProcessor as ProcessMessage>::Origin) { + let mut b = BookStateFor::::get(o); + crate::Pallet::::ready_ring_unknit(o, b.ready_neighbours.unwrap()); + b.ready_neighbours = None; + BookStateFor::::insert(o, b); +} + +/// Build a ring with three queues: `Here`, `There` and `Everywhere(0)`. +pub fn build_ring( + queues: &[<::MessageProcessor as ProcessMessage>::Origin], +) { + for queue in queues { + BookStateFor::::insert(queue, empty_book::()); + } + for queue in queues { + knit::(queue); + } + assert_ring::(queues); +} + +/// Check that the Ready Ring consists of `queues` in that exact order. +/// +/// Also check that all backlinks are valid and that the first element is the service head. +pub fn assert_ring( + queues: &[<::MessageProcessor as ProcessMessage>::Origin], +) { + for (i, origin) in queues.iter().enumerate() { + let book = BookStateFor::::get(origin); + assert_eq!( + book.ready_neighbours, + Some(Neighbours { + prev: queues[(i + queues.len() - 1) % queues.len()].clone(), + next: queues[(i + 1) % queues.len()].clone(), + }) + ); + } + assert_eq!(ServiceHead::::get(), queues.first().cloned()); +} diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs new file mode 100644 index 0000000000..103fb690dd --- /dev/null +++ b/frame/message-queue/src/tests.rs @@ -0,0 +1,1092 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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. + +//! Tests for Message Queue Pallet. + +#![cfg(test)] + +use crate::{mock::*, *}; + +use frame_support::{assert_noop, assert_ok, assert_storage_noop, StorageNoopGuard}; +use rand::{rngs::StdRng, Rng, SeedableRng}; + +#[test] +fn mocked_weight_works() { + new_test_ext::().execute_with(|| { + assert!(::WeightInfo::service_queue_base().is_zero()); + }); + new_test_ext::().execute_with(|| { + set_weight("service_queue_base", Weight::MAX); + assert_eq!(::WeightInfo::service_queue_base(), Weight::MAX); + }); + // The externalities reset it. + new_test_ext::().execute_with(|| { + assert!(::WeightInfo::service_queue_base().is_zero()); + }); +} + +#[test] +fn enqueue_within_one_page_works() { + new_test_ext::().execute_with(|| { + use MessageOrigin::*; + MessageQueue::enqueue_message(msg("a"), Here); + MessageQueue::enqueue_message(msg("b"), Here); + MessageQueue::enqueue_message(msg("c"), Here); + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(b"a".to_vec(), Here), (b"b".to_vec(), Here)]); + + assert_eq!(MessageQueue::service_queues(2.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(b"c".to_vec(), Here)]); + + assert_eq!(MessageQueue::service_queues(2.into_weight()), 0.into_weight()); + assert!(MessagesProcessed::get().is_empty()); + + MessageQueue::enqueue_messages([msg("a"), msg("b"), msg("c")].into_iter(), There); + + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); + assert_eq!( + MessagesProcessed::take(), + vec![(b"a".to_vec(), There), (b"b".to_vec(), There),] + ); + + MessageQueue::enqueue_message(msg("d"), Everywhere(1)); + + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); + assert_eq!(MessageQueue::service_queues(2.into_weight()), 0.into_weight()); + assert_eq!( + MessagesProcessed::take(), + vec![(b"c".to_vec(), There), (b"d".to_vec(), Everywhere(1))] + ); + }); +} + +#[test] +fn queue_priority_retains() { + new_test_ext::().execute_with(|| { + use MessageOrigin::*; + assert_ring(&[]); + MessageQueue::enqueue_message(msg("a"), Everywhere(1)); + assert_ring(&[Everywhere(1)]); + MessageQueue::enqueue_message(msg("b"), Everywhere(2)); + assert_ring(&[Everywhere(1), Everywhere(2)]); + MessageQueue::enqueue_message(msg("c"), Everywhere(3)); + assert_ring(&[Everywhere(1), Everywhere(2), Everywhere(3)]); + MessageQueue::enqueue_message(msg("d"), Everywhere(2)); + assert_ring(&[Everywhere(1), Everywhere(2), Everywhere(3)]); + // service head is 1, it will process a, leaving service head at 2. it also processes b but + // doees not empty queue 2, so service head will end at 2. + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); + assert_eq!( + MessagesProcessed::take(), + vec![(vmsg("a"), Everywhere(1)), (vmsg("b"), Everywhere(2)),] + ); + assert_ring(&[Everywhere(2), Everywhere(3)]); + // service head is 2, so will process d first, then c. + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); + assert_eq!( + MessagesProcessed::get(), + vec![(vmsg("d"), Everywhere(2)), (vmsg("c"), Everywhere(3)),] + ); + assert_ring(&[]); + }); +} + +#[test] +fn queue_priority_reset_once_serviced() { + new_test_ext::().execute_with(|| { + use MessageOrigin::*; + MessageQueue::enqueue_message(msg("a"), Everywhere(1)); + MessageQueue::enqueue_message(msg("b"), Everywhere(2)); + MessageQueue::enqueue_message(msg("c"), Everywhere(3)); + // service head is 1, it will process a, leaving service head at 2. it also processes b and + // empties queue 2, so service head will end at 3. + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); + MessageQueue::enqueue_message(msg("d"), Everywhere(2)); + // service head is 3, so will process c first, then d. + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); + + assert_eq!( + MessagesProcessed::get(), + vec![ + (vmsg("a"), Everywhere(1)), + (vmsg("b"), Everywhere(2)), + (vmsg("c"), Everywhere(3)), + (vmsg("d"), Everywhere(2)), + ] + ); + }); +} + +#[test] +fn service_queues_basic_works() { + use MessageOrigin::*; + new_test_ext::().execute_with(|| { + MessageQueue::enqueue_messages(vec![msg("a"), msg("ab"), msg("abc")].into_iter(), Here); + MessageQueue::enqueue_messages(vec![msg("x"), msg("xy"), msg("xyz")].into_iter(), There); + assert_eq!(QueueChanges::take(), vec![(Here, 3, 6), (There, 3, 6)]); + + // Service one message from `Here`. + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("a"), Here)]); + assert_eq!(QueueChanges::take(), vec![(Here, 2, 5)]); + + // Service one message from `There`. + ServiceHead::::set(There.into()); + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("x"), There)]); + assert_eq!(QueueChanges::take(), vec![(There, 2, 5)]); + + // Service the remaining from `Here`. + ServiceHead::::set(Here.into()); + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("ab"), Here), (vmsg("abc"), Here)]); + assert_eq!(QueueChanges::take(), vec![(Here, 0, 0)]); + + // Service all remaining messages. + assert_eq!(MessageQueue::service_queues(Weight::MAX), 2.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("xy"), There), (vmsg("xyz"), There)]); + assert_eq!(QueueChanges::take(), vec![(There, 0, 0)]); + }); +} + +#[test] +fn service_queues_failing_messages_works() { + use MessageOrigin::*; + new_test_ext::().execute_with(|| { + set_weight("service_page_item", 1.into_weight()); + MessageQueue::enqueue_message(msg("badformat"), Here); + MessageQueue::enqueue_message(msg("corrupt"), Here); + MessageQueue::enqueue_message(msg("unsupported"), Here); + // Starts with three pages. + assert_pages(&[0, 1, 2]); + + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_last_event::( + Event::ProcessingFailed { + hash: ::Hashing::hash(b"badformat"), + origin: MessageOrigin::Here, + error: ProcessMessageError::BadFormat, + } + .into(), + ); + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_last_event::( + Event::ProcessingFailed { + hash: ::Hashing::hash(b"corrupt"), + origin: MessageOrigin::Here, + error: ProcessMessageError::Corrupt, + } + .into(), + ); + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_last_event::( + Event::ProcessingFailed { + hash: ::Hashing::hash(b"unsupported"), + origin: MessageOrigin::Here, + error: ProcessMessageError::Unsupported, + } + .into(), + ); + // All pages removed. + assert_pages(&[]); + }); +} + +#[test] +fn reap_page_permanent_overweight_works() { + use MessageOrigin::*; + new_test_ext::().execute_with(|| { + // Create 10 pages more than the stale limit. + let n = (MaxStale::get() + 10) as usize; + for _ in 0..n { + MessageQueue::enqueue_message(msg("weight=2"), Here); + } + assert_eq!(Pages::::iter().count(), n); + assert_eq!(QueueChanges::take().len(), n); + // Mark all pages as stale since their message is permanently overweight. + MessageQueue::service_queues(1.into_weight()); + + // Check that we can reap everything below the watermark. + let max_stale = MaxStale::get(); + for i in 0..n as u32 { + let b = BookStateFor::::get(Here); + let stale_pages = n as u32 - i; + let overflow = stale_pages.saturating_sub(max_stale + 1) + 1; + let backlog = (max_stale * max_stale / overflow).max(max_stale); + let watermark = b.begin.saturating_sub(backlog); + + if i >= watermark { + break + } + assert_ok!(MessageQueue::do_reap_page(&Here, i)); + assert_eq!(QueueChanges::take(), vec![(Here, b.message_count - 1, b.size - 8)]); + } + + // Cannot reap any more pages. + for (o, i, _) in Pages::::iter() { + assert_noop!(MessageQueue::do_reap_page(&o, i), Error::::NotReapable); + assert!(QueueChanges::take().is_empty()); + } + }); +} + +#[test] +fn reaping_overweight_fails_properly() { + use MessageOrigin::*; + assert_eq!(MaxStale::get(), 2, "The stale limit is two"); + + new_test_ext::().execute_with(|| { + // page 0 + MessageQueue::enqueue_message(msg("weight=4"), Here); + MessageQueue::enqueue_message(msg("a"), Here); + // page 1 + MessageQueue::enqueue_message(msg("weight=4"), Here); + MessageQueue::enqueue_message(msg("b"), Here); + // page 2 + MessageQueue::enqueue_message(msg("weight=4"), Here); + MessageQueue::enqueue_message(msg("c"), Here); + // page 3 + MessageQueue::enqueue_message(msg("bigbig 1"), Here); + // page 4 + MessageQueue::enqueue_message(msg("bigbig 2"), Here); + // page 5 + MessageQueue::enqueue_message(msg("bigbig 3"), Here); + // Double-check that exactly these pages exist. + assert_pages(&[0, 1, 2, 3, 4, 5]); + + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("a"), Here), (vmsg("b"), Here)]); + // 2 stale now. + + // Nothing reapable yet, because we haven't hit the stale limit. + for (o, i, _) in Pages::::iter() { + assert_noop!(MessageQueue::do_reap_page(&o, i), Error::::NotReapable); + } + assert_pages(&[0, 1, 2, 3, 4, 5]); + + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("c"), Here)]); + // 3 stale now: can take something 4 pages in history. + + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("bigbig 1"), Here)]); + + // Nothing reapable yet, because we haven't hit the stale limit. + for (o, i, _) in Pages::::iter() { + assert_noop!(MessageQueue::do_reap_page(&o, i), Error::::NotReapable); + } + assert_pages(&[0, 1, 2, 4, 5]); + + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("bigbig 2"), Here)]); + assert_pages(&[0, 1, 2, 5]); + + // First is now reapable as it is too far behind the first ready page (5). + assert_ok!(MessageQueue::do_reap_page(&Here, 0)); + // Others not reapable yet, because we haven't hit the stale limit. + for (o, i, _) in Pages::::iter() { + assert_noop!(MessageQueue::do_reap_page(&o, i), Error::::NotReapable); + } + assert_pages(&[1, 2, 5]); + + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("bigbig 3"), Here)]); + + assert_noop!(MessageQueue::do_reap_page(&Here, 0), Error::::NoPage); + assert_noop!(MessageQueue::do_reap_page(&Here, 3), Error::::NoPage); + assert_noop!(MessageQueue::do_reap_page(&Here, 4), Error::::NoPage); + // Still not reapable, since the number of stale pages is only 2. + for (o, i, _) in Pages::::iter() { + assert_noop!(MessageQueue::do_reap_page(&o, i), Error::::NotReapable); + } + }); +} + +#[test] +fn service_queue_bails() { + // Not enough weight for `service_queue_base`. + new_test_ext::().execute_with(|| { + set_weight("service_queue_base", 2.into_weight()); + let mut meter = WeightMeter::from_limit(1.into_weight()); + + assert_storage_noop!(MessageQueue::service_queue(0u32.into(), &mut meter, Weight::MAX)); + assert!(meter.consumed.is_zero()); + }); + // Not enough weight for `ready_ring_unknit`. + new_test_ext::().execute_with(|| { + set_weight("ready_ring_unknit", 2.into_weight()); + let mut meter = WeightMeter::from_limit(1.into_weight()); + + assert_storage_noop!(MessageQueue::service_queue(0u32.into(), &mut meter, Weight::MAX)); + assert!(meter.consumed.is_zero()); + }); + // Not enough weight for `service_queue_base` and `ready_ring_unknit`. + new_test_ext::().execute_with(|| { + set_weight("service_queue_base", 2.into_weight()); + set_weight("ready_ring_unknit", 2.into_weight()); + + let mut meter = WeightMeter::from_limit(3.into_weight()); + assert_storage_noop!(MessageQueue::service_queue(0.into(), &mut meter, Weight::MAX)); + assert!(meter.consumed.is_zero()); + }); +} + +#[test] +fn service_page_works() { + use super::integration_test::Test; // Run with larger page size. + use MessageOrigin::*; + use PageExecutionStatus::*; + new_test_ext::().execute_with(|| { + set_weight("service_page_base_completion", 2.into_weight()); + set_weight("service_page_item", 3.into_weight()); + + let (page, mut msgs) = full_page::(); + assert!(msgs >= 10, "pre-condition: need at least 10 msgs per page"); + let mut book = book_for::(&page); + Pages::::insert(Here, 0, page); + + // Call it a few times each with a random weight limit. + let mut rng = rand::rngs::StdRng::seed_from_u64(42); + while msgs > 0 { + let process = rng.gen_range(0..=msgs); + msgs -= process; + + // Enough weight to process `process` messages. + let mut meter = WeightMeter::from_limit(((2 + (3 + 1) * process) as u64).into_weight()); + System::reset_events(); + let (processed, status) = + crate::Pallet::::service_page(&Here, &mut book, &mut meter, Weight::MAX); + assert_eq!(processed as usize, process); + assert_eq!(NumMessagesProcessed::take(), process); + assert_eq!(System::events().len(), process); + if msgs == 0 { + assert_eq!(status, NoMore); + } else { + assert_eq!(status, Bailed); + } + } + assert!(!Pages::::contains_key(Here, 0), "The page got removed"); + }); +} + +// `service_page` does nothing when called with an insufficient weight limit. +#[test] +fn service_page_bails() { + // Not enough weight for `service_page_base_completion`. + new_test_ext::().execute_with(|| { + set_weight("service_page_base_completion", 2.into_weight()); + let mut meter = WeightMeter::from_limit(1.into_weight()); + + let (page, _) = full_page::(); + let mut book = book_for::(&page); + Pages::::insert(MessageOrigin::Here, 0, page); + + assert_storage_noop!(MessageQueue::service_page( + &MessageOrigin::Here, + &mut book, + &mut meter, + Weight::MAX + )); + assert!(meter.consumed.is_zero()); + }); + // Not enough weight for `service_page_base_no_completion`. + new_test_ext::().execute_with(|| { + set_weight("service_page_base_no_completion", 2.into_weight()); + let mut meter = WeightMeter::from_limit(1.into_weight()); + + let (page, _) = full_page::(); + let mut book = book_for::(&page); + Pages::::insert(MessageOrigin::Here, 0, page); + + assert_storage_noop!(MessageQueue::service_page( + &MessageOrigin::Here, + &mut book, + &mut meter, + Weight::MAX + )); + assert!(meter.consumed.is_zero()); + }); +} + +#[test] +fn service_page_item_bails() { + new_test_ext::().execute_with(|| { + let _guard = StorageNoopGuard::default(); + let (mut page, _) = full_page::(); + let mut weight = WeightMeter::from_limit(10.into_weight()); + let overweight_limit = 10.into_weight(); + set_weight("service_page_item", 11.into_weight()); + + assert_eq!( + MessageQueue::service_page_item( + &MessageOrigin::Here, + 0, + &mut book_for::(&page), + &mut page, + &mut weight, + overweight_limit, + ), + ItemExecutionStatus::Bailed + ); + }); +} + +#[test] +fn bump_service_head_works() { + use MessageOrigin::*; + new_test_ext::().execute_with(|| { + // Create a ready ring with three queues. + BookStateFor::::insert(Here, empty_book::()); + knit(&Here); + BookStateFor::::insert(There, empty_book::()); + knit(&There); + BookStateFor::::insert(Everywhere(0), empty_book::()); + knit(&Everywhere(0)); + + // Bump 99 times. + for i in 0..99 { + let current = MessageQueue::bump_service_head(&mut WeightMeter::max_limit()).unwrap(); + assert_eq!(current, [Here, There, Everywhere(0)][i % 3]); + } + + // The ready ring is intact and the service head is still `Here`. + assert_ring(&[Here, There, Everywhere(0)]); + }); +} + +/// `bump_service_head` does nothing when called with an insufficient weight limit. +#[test] +fn bump_service_head_bails() { + new_test_ext::().execute_with(|| { + set_weight("bump_service_head", 2.into_weight()); + setup_bump_service_head::(0.into(), 10.into()); + + let _guard = StorageNoopGuard::default(); + let mut meter = WeightMeter::from_limit(1.into_weight()); + assert!(MessageQueue::bump_service_head(&mut meter).is_none()); + assert_eq!(meter.consumed, 0.into_weight()); + }); +} + +#[test] +fn bump_service_head_trivial_works() { + new_test_ext::().execute_with(|| { + set_weight("bump_service_head", 2.into_weight()); + let mut meter = WeightMeter::max_limit(); + + assert_eq!(MessageQueue::bump_service_head(&mut meter), None, "Cannot bump"); + assert_eq!(meter.consumed, 2.into_weight()); + + setup_bump_service_head::(0.into(), 1.into()); + + assert_eq!(MessageQueue::bump_service_head(&mut meter), Some(0.into())); + assert_eq!(ServiceHead::::get().unwrap(), 1.into(), "Bumped the head"); + assert_eq!(meter.consumed, 4.into_weight()); + + assert_eq!(MessageQueue::bump_service_head(&mut meter), None, "Cannot bump"); + assert_eq!(meter.consumed, 6.into_weight()); + }); +} + +#[test] +fn bump_service_head_no_head_noops() { + use MessageOrigin::*; + new_test_ext::().execute_with(|| { + // Create a ready ring with three queues. + BookStateFor::::insert(Here, empty_book::()); + knit(&Here); + BookStateFor::::insert(There, empty_book::()); + knit(&There); + BookStateFor::::insert(Everywhere(0), empty_book::()); + knit(&Everywhere(0)); + + // But remove the service head. + ServiceHead::::kill(); + + // Nothing happens. + assert_storage_noop!(MessageQueue::bump_service_head(&mut WeightMeter::max_limit())); + }); +} + +#[test] +fn service_page_item_consumes_correct_weight() { + new_test_ext::().execute_with(|| { + let mut page = page::(b"weight=3"); + let mut weight = WeightMeter::from_limit(10.into_weight()); + let overweight_limit = 0.into_weight(); + set_weight("service_page_item", 2.into_weight()); + + assert_eq!( + MessageQueue::service_page_item( + &MessageOrigin::Here, + 0, + &mut book_for::(&page), + &mut page, + &mut weight, + overweight_limit + ), + ItemExecutionStatus::Executed(true) + ); + assert_eq!(weight.consumed, 5.into_weight()); + }); +} + +/// `service_page_item` skips a permanently `Overweight` message and marks it as `unprocessed`. +#[test] +fn service_page_item_skips_perm_overweight_message() { + new_test_ext::().execute_with(|| { + let mut page = page::(b"TooMuch"); + let mut weight = WeightMeter::from_limit(2.into_weight()); + let overweight_limit = 0.into_weight(); + set_weight("service_page_item", 2.into_weight()); + + assert_eq!( + crate::Pallet::::service_page_item( + &MessageOrigin::Here, + 0, + &mut book_for::(&page), + &mut page, + &mut weight, + overweight_limit + ), + ItemExecutionStatus::Executed(false) + ); + assert_eq!(weight.consumed, 2.into_weight()); + assert_last_event::( + Event::OverweightEnqueued { + hash: ::Hashing::hash(b"TooMuch"), + origin: MessageOrigin::Here, + message_index: 0, + page_index: 0, + } + .into(), + ); + + // Check that the message was skipped. + let (pos, processed, payload) = page.peek_index(0).unwrap(); + assert_eq!(pos, 0); + assert!(!processed); + assert_eq!(payload, b"TooMuch".encode()); + }); +} + +#[test] +fn peek_index_works() { + use super::integration_test::Test; // Run with larger page size. + new_test_ext::().execute_with(|| { + // Fill a page with messages. + let (mut page, msgs) = full_page::(); + let msg_enc_len = ItemHeader::<::Size>::max_encoded_len() + 4; + + for i in 0..msgs { + // Skip all even messages. + page.skip_first(i % 2 == 0); + // Peek each message and check that it is correct. + let (pos, processed, payload) = page.peek_index(i).unwrap(); + assert_eq!(pos, msg_enc_len * i); + assert_eq!(processed, i % 2 == 0); + // `full_page` uses the index as payload. + assert_eq!(payload, (i as u32).encode()); + } + }); +} + +#[test] +fn peek_first_and_skip_first_works() { + use super::integration_test::Test; // Run with larger page size. + new_test_ext::().execute_with(|| { + // Fill a page with messages. + let (mut page, msgs) = full_page::(); + + for i in 0..msgs { + let msg = page.peek_first().unwrap(); + // `full_page` uses the index as payload. + assert_eq!(msg.deref(), (i as u32).encode()); + page.skip_first(i % 2 == 0); // True of False should not matter here. + } + assert!(page.peek_first().is_none(), "Page must be at the end"); + + // Check that all messages were correctly marked as (un)processed. + for i in 0..msgs { + let (_, processed, _) = page.peek_index(i).unwrap(); + assert_eq!(processed, i % 2 == 0); + } + }); +} + +#[test] +fn note_processed_at_pos_works() { + use super::integration_test::Test; // Run with larger page size. + new_test_ext::().execute_with(|| { + let (mut page, msgs) = full_page::(); + + for i in 0..msgs { + let (pos, processed, _) = page.peek_index(i).unwrap(); + assert!(!processed); + assert_eq!(page.remaining as usize, msgs - i); + + page.note_processed_at_pos(pos); + + let (_, processed, _) = page.peek_index(i).unwrap(); + assert!(processed); + assert_eq!(page.remaining as usize, msgs - i - 1); + } + // `skip_first` still works fine. + for _ in 0..msgs { + page.peek_first().unwrap(); + page.skip_first(false); + } + assert!(page.peek_first().is_none()); + }); +} + +#[test] +fn note_processed_at_pos_idempotent() { + let (mut page, _) = full_page::(); + page.note_processed_at_pos(0); + + let original = page.clone(); + page.note_processed_at_pos(0); + assert_eq!(page.heap, original.heap); +} + +#[test] +fn is_complete_works() { + use super::integration_test::Test; // Run with larger page size. + new_test_ext::().execute_with(|| { + let (mut page, msgs) = full_page::(); + assert!(msgs > 3, "Boring"); + let msg_enc_len = ItemHeader::<::Size>::max_encoded_len() + 4; + + assert!(!page.is_complete()); + for i in 0..msgs { + if i % 2 == 0 { + page.skip_first(false); + } else { + page.note_processed_at_pos(msg_enc_len * i); + } + } + // Not complete since `skip_first` was called with `false`. + assert!(!page.is_complete()); + for i in 0..msgs { + if i % 2 == 0 { + assert!(!page.is_complete()); + let (pos, _, _) = page.peek_index(i).unwrap(); + page.note_processed_at_pos(pos); + } + } + assert!(page.is_complete()); + assert_eq!(page.remaining_size, 0); + // Each message is marked as processed. + for i in 0..msgs { + let (_, processed, _) = page.peek_index(i).unwrap(); + assert!(processed); + } + }); +} + +#[test] +fn page_from_message_basic_works() { + assert!(MaxMessageLenOf::::get() > 0, "pre-condition unmet"); + let mut msg: BoundedVec> = Default::default(); + msg.bounded_resize(MaxMessageLenOf::::get() as usize, 123); + + let page = PageOf::::from_message::(msg.as_bounded_slice()); + assert_eq!(page.remaining, 1); + assert_eq!(page.remaining_size as usize, msg.len()); + assert!(page.first_index == 0 && page.first == 0 && page.last == 0); + + // Verify the content of the heap. + let mut heap = Vec::::new(); + let header = + ItemHeader::<::Size> { payload_len: msg.len() as u32, is_processed: false }; + heap.extend(header.encode()); + heap.extend(msg.deref()); + assert_eq!(page.heap, heap); +} + +#[test] +fn page_try_append_message_basic_works() { + use super::integration_test::Test; // Run with larger page size. + + let mut page = PageOf::::default(); + let mut msgs = 0; + // Append as many 4-byte message as possible. + for i in 0..u32::MAX { + let r = i.using_encoded(|i| page.try_append_message::(i.try_into().unwrap())); + if r.is_err() { + break + } else { + msgs += 1; + } + } + let expected_msgs = (::HeapSize::get()) / + (ItemHeader::<::Size>::max_encoded_len() as u32 + 4); + assert_eq!(expected_msgs, msgs, "Wrong number of messages"); + assert_eq!(page.remaining, msgs); + assert_eq!(page.remaining_size, msgs * 4); + + // Verify that the heap content is correct. + let mut heap = Vec::::new(); + for i in 0..msgs { + let header = ItemHeader::<::Size> { payload_len: 4, is_processed: false }; + heap.extend(header.encode()); + heap.extend(i.encode()); + } + assert_eq!(page.heap, heap); +} + +#[test] +fn page_try_append_message_max_msg_len_works_works() { + use super::integration_test::Test; // Run with larger page size. + + // We start off with an empty page. + let mut page = PageOf::::default(); + // … and append a message with maximum possible length. + let msg = vec![123u8; MaxMessageLenOf::::get() as usize]; + // … which works. + page.try_append_message::(BoundedSlice::defensive_truncate_from(&msg)) + .unwrap(); + // Now we cannot append *anything* since the heap is full. + page.try_append_message::(BoundedSlice::defensive_truncate_from(&[])) + .unwrap_err(); + assert_eq!(page.heap.len(), ::HeapSize::get() as usize); +} + +#[test] +fn page_try_append_message_with_remaining_size_works_works() { + use super::integration_test::Test; // Run with larger page size. + let header_size = ItemHeader::<::Size>::max_encoded_len(); + + // We start off with an empty page. + let mut page = PageOf::::default(); + let mut remaining = ::HeapSize::get() as usize; + let mut msgs = Vec::new(); + let mut rng = StdRng::seed_from_u64(42); + // Now we keep appending messages with different lengths. + while remaining >= header_size { + let take = rng.gen_range(0..=(remaining - header_size)); + let msg = vec![123u8; take]; + page.try_append_message::(BoundedSlice::defensive_truncate_from(&msg)) + .unwrap(); + remaining -= take + header_size; + msgs.push(msg); + } + // Cannot even fit a single header in there now. + assert!(remaining < header_size); + assert_eq!(::HeapSize::get() as usize - page.heap.len(), remaining); + assert_eq!(page.remaining as usize, msgs.len()); + assert_eq!( + page.remaining_size as usize, + msgs.iter().fold(0, |mut a, m| { + a += m.len(); + a + }) + ); + // Verify the heap content. + let mut heap = Vec::new(); + for msg in msgs.into_iter() { + let header = ItemHeader::<::Size> { + payload_len: msg.len() as u32, + is_processed: false, + }; + heap.extend(header.encode()); + heap.extend(msg); + } + assert_eq!(page.heap, heap); +} + +// `Page::from_message` does not panic when called with the maximum message and origin lengths. +#[test] +fn page_from_message_max_len_works() { + let max_msg_len: usize = MaxMessageLenOf::::get() as usize; + + let page = PageOf::::from_message::(vec![1; max_msg_len][..].try_into().unwrap()); + + assert_eq!(page.remaining, 1); +} + +#[test] +fn sweep_queue_works() { + use MessageOrigin::*; + new_test_ext::().execute_with(|| { + build_triple_ring(); + + let book = BookStateFor::::get(Here); + assert!(book.begin != book.end); + // Removing the service head works + assert_eq!(ServiceHead::::get(), Some(Here)); + MessageQueue::sweep_queue(Here); + assert_ring(&[There, Everywhere(0)]); + // The book still exits, but has updated begin and end. + let book = BookStateFor::::get(Here); + assert_eq!(book.begin, book.end); + + // Removing something that is not the service head works. + assert!(ServiceHead::::get() != Some(Everywhere(0))); + MessageQueue::sweep_queue(Everywhere(0)); + assert_ring(&[There]); + // The book still exits, but has updated begin and end. + let book = BookStateFor::::get(Everywhere(0)); + assert_eq!(book.begin, book.end); + + MessageQueue::sweep_queue(There); + // The book still exits, but has updated begin and end. + let book = BookStateFor::::get(There); + assert_eq!(book.begin, book.end); + assert_ring(&[]); + + // Sweeping a queue never calls OnQueueChanged. + assert!(QueueChanges::take().is_empty()); + }) +} + +/// Test that `sweep_queue` also works if the ReadyRing wraps around. +#[test] +fn sweep_queue_wraps_works() { + use MessageOrigin::*; + new_test_ext::().execute_with(|| { + BookStateFor::::insert(Here, empty_book::()); + knit(&Here); + + MessageQueue::sweep_queue(Here); + let book = BookStateFor::::get(Here); + assert!(book.ready_neighbours.is_none()); + }); +} + +#[test] +fn sweep_queue_invalid_noops() { + use MessageOrigin::*; + new_test_ext::().execute_with(|| { + assert_storage_noop!(MessageQueue::sweep_queue(Here)); + }); +} + +#[test] +fn footprint_works() { + new_test_ext::().execute_with(|| { + let origin = MessageOrigin::Here; + let (page, msgs) = full_page::(); + let book = book_for::(&page); + BookStateFor::::insert(origin, book); + + let info = MessageQueue::footprint(origin); + assert_eq!(info.count as usize, msgs); + assert_eq!(info.size, page.remaining_size as u64); + + // Sweeping a queue never calls OnQueueChanged. + assert!(QueueChanges::take().is_empty()); + }) +} + +/// The footprint of an invalid queue is the default footprint. +#[test] +fn footprint_invalid_works() { + new_test_ext::().execute_with(|| { + let origin = MessageOrigin::Here; + assert_eq!(MessageQueue::footprint(origin), Default::default()); + }) +} + +/// The footprint of a swept queue is still correct. +#[test] +fn footprint_on_swept_works() { + use MessageOrigin::*; + new_test_ext::().execute_with(|| { + let mut book = empty_book::(); + book.message_count = 3; + book.size = 10; + BookStateFor::::insert(Here, &book); + knit(&Here); + + MessageQueue::sweep_queue(Here); + let fp = MessageQueue::footprint(Here); + assert_eq!(fp.count, 3); + assert_eq!(fp.size, 10); + }) +} + +#[test] +fn execute_overweight_works() { + new_test_ext::().execute_with(|| { + set_weight("bump_service_head", 1.into_weight()); + set_weight("service_queue_base", 1.into_weight()); + set_weight("service_page_base_completion", 1.into_weight()); + + // Enqueue a message + let origin = MessageOrigin::Here; + MessageQueue::enqueue_message(msg("weight=6"), origin); + // Load the current book + let book = BookStateFor::::get(origin); + assert_eq!(book.message_count, 1); + assert!(Pages::::contains_key(origin, 0)); + + // Mark the message as permanently overweight. + assert_eq!(MessageQueue::service_queues(4.into_weight()), 4.into_weight()); + assert_eq!(QueueChanges::take(), vec![(origin, 1, 8)]); + assert_last_event::( + Event::OverweightEnqueued { + hash: ::Hashing::hash(b"weight=6"), + origin: MessageOrigin::Here, + message_index: 0, + page_index: 0, + } + .into(), + ); + + // Now try to execute it with too few weight. + let consumed = + ::execute_overweight(5.into_weight(), (origin, 0, 0)); + assert_eq!(consumed, Err(ExecuteOverweightError::InsufficientWeight)); + + // Execute it with enough weight. + assert_eq!(Pages::::iter().count(), 1); + assert!(QueueChanges::take().is_empty()); + let consumed = + ::execute_overweight(7.into_weight(), (origin, 0, 0)) + .unwrap(); + assert_eq!(consumed, 6.into_weight()); + assert_eq!(QueueChanges::take(), vec![(origin, 0, 0)]); + // There is no message left in the book. + let book = BookStateFor::::get(origin); + assert_eq!(book.message_count, 0); + // And no more pages. + assert_eq!(Pages::::iter().count(), 0); + + // Doing it again with enough weight will error. + let consumed = + ::execute_overweight(70.into_weight(), (origin, 0, 0)); + assert_eq!(consumed, Err(ExecuteOverweightError::NotFound)); + assert!(QueueChanges::take().is_empty()); + assert!(!Pages::::contains_key(origin, 0), "Page is gone"); + }); +} + +/// Checks that (un)knitting the ready ring works with just one queue. +/// +/// This case is interesting since it wraps and a lot of `mutate` now operate on the same object. +#[test] +fn ready_ring_knit_basic_works() { + use MessageOrigin::*; + + new_test_ext::().execute_with(|| { + BookStateFor::::insert(Here, empty_book::()); + + for i in 0..10 { + if i % 2 == 0 { + knit(&Here); + assert_ring(&[Here]); + } else { + unknit(&Here); + assert_ring(&[]); + } + } + assert_ring(&[]); + }); +} + +#[test] +fn ready_ring_knit_and_unknit_works() { + use MessageOrigin::*; + + new_test_ext::().execute_with(|| { + // Place three queues into the storage. + BookStateFor::::insert(Here, empty_book::()); + BookStateFor::::insert(There, empty_book::()); + BookStateFor::::insert(Everywhere(0), empty_book::()); + + // Knit them into the ready ring. + assert_ring(&[]); + knit(&Here); + assert_ring(&[Here]); + knit(&There); + assert_ring(&[Here, There]); + knit(&Everywhere(0)); + assert_ring(&[Here, There, Everywhere(0)]); + + // Now unknit… + unknit(&Here); + assert_ring(&[There, Everywhere(0)]); + unknit(&There); + assert_ring(&[Everywhere(0)]); + unknit(&Everywhere(0)); + assert_ring(&[]); + }); +} + +#[test] +fn enqueue_message_works() { + use MessageOrigin::*; + let max_msg_per_page = ::HeapSize::get() as u64 / + (ItemHeader::<::Size>::max_encoded_len() as u64 + 1); + + new_test_ext::().execute_with(|| { + // Enqueue messages which should fill three pages. + let n = max_msg_per_page * 3; + for i in 1..=n { + MessageQueue::enqueue_message(msg("a"), Here); + assert_eq!(QueueChanges::take(), vec![(Here, i, i)], "OnQueueChanged not called"); + } + assert_eq!(Pages::::iter().count(), 3); + + // Enqueue one more onto page 4. + MessageQueue::enqueue_message(msg("abc"), Here); + assert_eq!(QueueChanges::take(), vec![(Here, n + 1, n + 3)]); + assert_eq!(Pages::::iter().count(), 4); + + // Check the state. + assert_eq!(BookStateFor::::iter().count(), 1); + let book = BookStateFor::::get(Here); + assert_eq!(book.message_count, n + 1); + assert_eq!(book.size, n + 3); + assert_eq!((book.begin, book.end), (0, 4)); + assert_eq!(book.count as usize, Pages::::iter().count()); + }); +} + +#[test] +fn enqueue_messages_works() { + use MessageOrigin::*; + let max_msg_per_page = ::HeapSize::get() as u64 / + (ItemHeader::<::Size>::max_encoded_len() as u64 + 1); + + new_test_ext::().execute_with(|| { + // Enqueue messages which should fill three pages. + let n = max_msg_per_page * 3; + let msgs = vec![msg("a"); n as usize]; + + // Now queue all messages at once. + MessageQueue::enqueue_messages(msgs.into_iter(), Here); + // The changed handler should only be called once. + assert_eq!(QueueChanges::take(), vec![(Here, n, n)], "OnQueueChanged not called"); + assert_eq!(Pages::::iter().count(), 3); + + // Enqueue one more onto page 4. + MessageQueue::enqueue_message(msg("abc"), Here); + assert_eq!(QueueChanges::take(), vec![(Here, n + 1, n + 3)]); + assert_eq!(Pages::::iter().count(), 4); + + // Check the state. + assert_eq!(BookStateFor::::iter().count(), 1); + let book = BookStateFor::::get(Here); + assert_eq!(book.message_count, n + 1); + assert_eq!(book.size, n + 3); + assert_eq!((book.begin, book.end), (0, 4)); + assert_eq!(book.count as usize, Pages::::iter().count()); + }); +} diff --git a/frame/message-queue/src/weights.rs b/frame/message-queue/src/weights.rs new file mode 100644 index 0000000000..cd9268ffde --- /dev/null +++ b/frame/message-queue/src/weights.rs @@ -0,0 +1,216 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Autogenerated weights for pallet_message_queue +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-12-08, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// /home/benchbot/cargo_target_dir/production/substrate +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/substrate/.git/.artifacts/bench.json +// --pallet=pallet_message_queue +// --chain=dev +// --header=./HEADER-APACHE2 +// --output=./frame/message-queue/src/weights.rs +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_message_queue. +pub trait WeightInfo { + fn ready_ring_knit() -> Weight; + fn ready_ring_unknit() -> Weight; + fn service_queue_base() -> Weight; + fn service_page_base_completion() -> Weight; + fn service_page_base_no_completion() -> Weight; + fn service_page_item() -> Weight; + fn bump_service_head() -> Weight; + fn reap_page() -> Weight; + fn execute_overweight_page_removed() -> Weight; + fn execute_overweight_page_updated() -> Weight; +} + +/// Weights for pallet_message_queue using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: MessageQueue ServiceHead (r:1 w:0) + // Storage: MessageQueue BookStateFor (r:2 w:2) + fn ready_ring_knit() -> Weight { + // Minimum execution time: 12_330 nanoseconds. + Weight::from_ref_time(12_711_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: MessageQueue BookStateFor (r:2 w:2) + // Storage: MessageQueue ServiceHead (r:1 w:1) + fn ready_ring_unknit() -> Weight { + // Minimum execution time: 12_322 nanoseconds. + Weight::from_ref_time(12_560_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } + // Storage: MessageQueue BookStateFor (r:1 w:1) + fn service_queue_base() -> Weight { + // Minimum execution time: 4_652 nanoseconds. + Weight::from_ref_time(4_848_000) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: MessageQueue Pages (r:1 w:1) + fn service_page_base_completion() -> Weight { + // Minimum execution time: 7_115 nanoseconds. + Weight::from_ref_time(7_407_000) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: MessageQueue Pages (r:1 w:1) + fn service_page_base_no_completion() -> Weight { + // Minimum execution time: 6_974 nanoseconds. + Weight::from_ref_time(7_200_000) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + fn service_page_item() -> Weight { + // Minimum execution time: 79_657 nanoseconds. + Weight::from_ref_time(80_050_000) + } + // Storage: MessageQueue ServiceHead (r:1 w:1) + // Storage: MessageQueue BookStateFor (r:1 w:0) + fn bump_service_head() -> Weight { + // Minimum execution time: 7_598 nanoseconds. + Weight::from_ref_time(8_118_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: MessageQueue BookStateFor (r:1 w:1) + // Storage: MessageQueue Pages (r:1 w:1) + fn reap_page() -> Weight { + // Minimum execution time: 60_562 nanoseconds. + Weight::from_ref_time(61_430_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: MessageQueue BookStateFor (r:1 w:1) + // Storage: MessageQueue Pages (r:1 w:1) + fn execute_overweight_page_removed() -> Weight { + // Minimum execution time: 74_582 nanoseconds. + Weight::from_ref_time(75_445_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: MessageQueue BookStateFor (r:1 w:1) + // Storage: MessageQueue Pages (r:1 w:1) + fn execute_overweight_page_updated() -> Weight { + // Minimum execution time: 87_526 nanoseconds. + Weight::from_ref_time(88_055_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: MessageQueue ServiceHead (r:1 w:0) + // Storage: MessageQueue BookStateFor (r:2 w:2) + fn ready_ring_knit() -> Weight { + // Minimum execution time: 12_330 nanoseconds. + Weight::from_ref_time(12_711_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(2)) + } + // Storage: MessageQueue BookStateFor (r:2 w:2) + // Storage: MessageQueue ServiceHead (r:1 w:1) + fn ready_ring_unknit() -> Weight { + // Minimum execution time: 12_322 nanoseconds. + Weight::from_ref_time(12_560_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(3)) + } + // Storage: MessageQueue BookStateFor (r:1 w:1) + fn service_queue_base() -> Weight { + // Minimum execution time: 4_652 nanoseconds. + Weight::from_ref_time(4_848_000) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: MessageQueue Pages (r:1 w:1) + fn service_page_base_completion() -> Weight { + // Minimum execution time: 7_115 nanoseconds. + Weight::from_ref_time(7_407_000) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: MessageQueue Pages (r:1 w:1) + fn service_page_base_no_completion() -> Weight { + // Minimum execution time: 6_974 nanoseconds. + Weight::from_ref_time(7_200_000) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + fn service_page_item() -> Weight { + // Minimum execution time: 79_657 nanoseconds. + Weight::from_ref_time(80_050_000) + } + // Storage: MessageQueue ServiceHead (r:1 w:1) + // Storage: MessageQueue BookStateFor (r:1 w:0) + fn bump_service_head() -> Weight { + // Minimum execution time: 7_598 nanoseconds. + Weight::from_ref_time(8_118_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: MessageQueue BookStateFor (r:1 w:1) + // Storage: MessageQueue Pages (r:1 w:1) + fn reap_page() -> Weight { + // Minimum execution time: 60_562 nanoseconds. + Weight::from_ref_time(61_430_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) + } + // Storage: MessageQueue BookStateFor (r:1 w:1) + // Storage: MessageQueue Pages (r:1 w:1) + fn execute_overweight_page_removed() -> Weight { + // Minimum execution time: 74_582 nanoseconds. + Weight::from_ref_time(75_445_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) + } + // Storage: MessageQueue BookStateFor (r:1 w:1) + // Storage: MessageQueue Pages (r:1 w:1) + fn execute_overweight_page_updated() -> Weight { + // Minimum execution time: 87_526 nanoseconds. + Weight::from_ref_time(88_055_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) + } +} diff --git a/frame/scheduler/Cargo.toml b/frame/scheduler/Cargo.toml index 86ca63c753..25ac602681 100644 --- a/frame/scheduler/Cargo.toml +++ b/frame/scheduler/Cargo.toml @@ -19,6 +19,7 @@ frame-system = { version = "4.0.0-dev", default-features = false, path = "../sys sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-weights = { version = "4.0.0", default-features = false, path = "../../primitives/weights" } [dev-dependencies] pallet-preimage = { version = "4.0.0-dev", path = "../preimage" } @@ -42,5 +43,6 @@ std = [ "sp-io/std", "sp-runtime/std", "sp-std/std", + "sp-weights/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index 78533540be..2e0d0c6be1 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -73,7 +73,6 @@ use frame_support::{ weights::{Weight, WeightMeter}, }; use frame_system::{self as system}; -pub use pallet::*; use scale_info::TypeInfo; use sp_io::hashing::blake2_256; use sp_runtime::{ @@ -81,6 +80,8 @@ use sp_runtime::{ BoundedVec, RuntimeDebug, }; use sp_std::{borrow::Borrow, cmp::Ordering, marker::PhantomData, prelude::*}; + +pub use pallet::*; pub use weights::WeightInfo; /// Just a simple index for naming period tasks. diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index e5ba98fe0c..63c86c1f68 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -112,6 +112,12 @@ pub use voting::{ mod preimages; pub use preimages::{Bounded, BoundedInline, FetchResult, Hash, QueryPreimage, StorePreimage}; +mod messages; +pub use messages::{ + EnqueueMessage, ExecuteOverweightError, Footprint, ProcessMessage, ProcessMessageError, + ServiceQueues, +}; + #[cfg(feature = "try-runtime")] mod try_runtime; #[cfg(feature = "try-runtime")] diff --git a/frame/support/src/traits/messages.rs b/frame/support/src/traits/messages.rs new file mode 100644 index 0000000000..9b86c421ad --- /dev/null +++ b/frame/support/src/traits/messages.rs @@ -0,0 +1,202 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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. + +//! Traits for managing message queuing and handling. + +use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_core::{ConstU32, Get, TypedGet}; +use sp_runtime::{traits::Convert, BoundedSlice, RuntimeDebug}; +use sp_std::{fmt::Debug, marker::PhantomData, prelude::*}; +use sp_weights::Weight; + +/// Errors that can happen when attempting to process a message with +/// [`ProcessMessage::process_message()`]. +#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, TypeInfo, RuntimeDebug)] +pub enum ProcessMessageError { + /// The message data format is unknown (e.g. unrecognised header) + BadFormat, + /// The message data is bad (e.g. decoding returns an error). + Corrupt, + /// The message format is unsupported (e.g. old XCM version). + Unsupported, + /// Message processing was not attempted because it was not certain that the weight limit + /// would be respected. The parameter gives the maximum weight which the message could take + /// to process. + Overweight(Weight), +} + +/// Can process messages from a specific origin. +pub trait ProcessMessage { + /// The transport from where a message originates. + type Origin: FullCodec + MaxEncodedLen + Clone + Eq + PartialEq + TypeInfo + Debug; + + /// Process the given message, using no more than `weight_limit` in weight to do so. + fn process_message( + message: &[u8], + origin: Self::Origin, + weight_limit: Weight, + ) -> Result<(bool, Weight), ProcessMessageError>; +} + +/// Errors that can happen when attempting to execute an overweight message with +/// [`ServiceQueues::execute_overweight()`]. +#[derive(Eq, PartialEq, RuntimeDebug)] +pub enum ExecuteOverweightError { + /// The referenced message was not found. + NotFound, + /// The available weight was insufficient to execute the message. + InsufficientWeight, +} + +/// Can service queues and execute overweight messages. +pub trait ServiceQueues { + /// Addresses a specific overweight message. + type OverweightMessageAddress; + + /// Service all message queues in some fair manner. + /// + /// - `weight_limit`: The maximum amount of dynamic weight that this call can use. + /// + /// Returns the dynamic weight used by this call; is never greater than `weight_limit`. + fn service_queues(weight_limit: Weight) -> Weight; + + /// Executes a message that could not be executed by [`Self::service_queues()`] because it was + /// temporarily overweight. + fn execute_overweight( + _weight_limit: Weight, + _address: Self::OverweightMessageAddress, + ) -> Result { + Err(ExecuteOverweightError::NotFound) + } +} + +/// The resource footprint of a queue. +#[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug)] +pub struct Footprint { + pub count: u64, + pub size: u64, +} + +/// Can enqueue messages for multiple origins. +pub trait EnqueueMessage { + /// The maximal length any enqueued message may have. + type MaxMessageLen: Get; + + /// Enqueue a single `message` from a specific `origin`. + fn enqueue_message(message: BoundedSlice, origin: Origin); + + /// Enqueue multiple `messages` from a specific `origin`. + fn enqueue_messages<'a>( + messages: impl Iterator>, + origin: Origin, + ); + + /// Any remaining unprocessed messages should happen only lazily, not proactively. + fn sweep_queue(origin: Origin); + + /// Return the state footprint of the given queue. + fn footprint(origin: Origin) -> Footprint; +} + +impl EnqueueMessage for () { + type MaxMessageLen = ConstU32<0>; + fn enqueue_message(_: BoundedSlice, _: Origin) {} + fn enqueue_messages<'a>( + _: impl Iterator>, + _: Origin, + ) { + } + fn sweep_queue(_: Origin) {} + fn footprint(_: Origin) -> Footprint { + Footprint::default() + } +} + +/// Transform the origin of an [`EnqueueMessage`] via `C::convert`. +pub struct TransformOrigin(PhantomData<(E, O, N, C)>); +impl, O: MaxEncodedLen, N: MaxEncodedLen, C: Convert> EnqueueMessage + for TransformOrigin +{ + type MaxMessageLen = E::MaxMessageLen; + + fn enqueue_message(message: BoundedSlice, origin: N) { + E::enqueue_message(message, C::convert(origin)); + } + + fn enqueue_messages<'a>( + messages: impl Iterator>, + origin: N, + ) { + E::enqueue_messages(messages, C::convert(origin)); + } + + fn sweep_queue(origin: N) { + E::sweep_queue(C::convert(origin)); + } + + fn footprint(origin: N) -> Footprint { + E::footprint(C::convert(origin)) + } +} + +/// Handles incoming messages for a single origin. +pub trait HandleMessage { + /// The maximal length any enqueued message may have. + type MaxMessageLen: Get; + + /// Enqueue a single `message` with an implied origin. + fn handle_message(message: BoundedSlice); + + /// Enqueue multiple `messages` from an implied origin. + fn handle_messages<'a>( + messages: impl Iterator>, + ); + + /// Any remaining unprocessed messages should happen only lazily, not proactively. + fn sweep_queue(); + + /// Return the state footprint of the queue. + fn footprint() -> Footprint; +} + +/// Adapter type to transform an [`EnqueueMessage`] with an origin into a [`HandleMessage`] impl. +pub struct EnqueueWithOrigin(PhantomData<(E, O)>); +impl, O: TypedGet> HandleMessage for EnqueueWithOrigin +where + O::Type: MaxEncodedLen, +{ + type MaxMessageLen = E::MaxMessageLen; + + fn handle_message(message: BoundedSlice) { + E::enqueue_message(message, O::get()); + } + + fn handle_messages<'a>( + messages: impl Iterator>, + ) { + E::enqueue_messages(messages, O::get()); + } + + fn sweep_queue() { + E::sweep_queue(O::get()); + } + + fn footprint() -> Footprint { + E::footprint(O::get()) + } +} diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr index 42ef5a34e4..999d8585c2 100644 --- a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr @@ -28,7 +28,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 278 others + and 279 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` @@ -69,7 +69,7 @@ error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied (A, B, C, D) (A, B, C, D, E) (A, B, C, D, E, F) - and 161 others + and 162 others = note: required for `Bar` to implement `StaticTypeInfo` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` @@ -103,7 +103,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 278 others + and 279 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr index 461d63ebb0..e2870ffb9e 100644 --- a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr @@ -28,7 +28,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 278 others + and 279 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` @@ -69,7 +69,7 @@ error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied (A, B, C, D) (A, B, C, D, E) (A, B, C, D, E, F) - and 161 others + and 162 others = note: required for `Bar` to implement `StaticTypeInfo` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` @@ -103,7 +103,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 278 others + and 279 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` diff --git a/primitives/core/src/bounded/bounded_vec.rs b/primitives/core/src/bounded/bounded_vec.rs index 2f39f3340c..6e1e1c7cfd 100644 --- a/primitives/core/src/bounded/bounded_vec.rs +++ b/primitives/core/src/bounded/bounded_vec.rs @@ -675,6 +675,13 @@ impl> BoundedVec { } } +impl BoundedVec { + /// Return a [`BoundedSlice`] with the content and bound of [`Self`]. + pub fn as_bounded_slice(&self) -> BoundedSlice { + BoundedSlice(&self.0[..], PhantomData::default()) + } +} + impl Default for BoundedVec { fn default() -> Self { // the bound cannot be below 0, which is satisfied by an empty vector diff --git a/primitives/weights/src/weight_meter.rs b/primitives/weights/src/weight_meter.rs index d03e72968b..17c5da1502 100644 --- a/primitives/weights/src/weight_meter.rs +++ b/primitives/weights/src/weight_meter.rs @@ -71,6 +71,12 @@ impl WeightMeter { time.max(pov) } + /// Consume some weight and defensively fail if it is over the limit. Saturate in any case. + pub fn defensive_saturating_accrue(&mut self, w: Weight) { + self.consumed.saturating_accrue(w); + debug_assert!(self.consumed.all_lte(self.limit), "Weight counter overflow"); + } + /// Consume the given weight after checking that it can be consumed. Otherwise do nothing. pub fn check_accrue(&mut self, w: Weight) -> bool { self.consumed.checked_add(&w).map_or(false, |test| { From f0b6e79a8d21856743acc5370b5d5c3271048cb9 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Fri, 9 Dec 2022 13:47:04 +0100 Subject: [PATCH 019/127] zombienet timings adjusted (#12890) * zombinet tests: add some timeout to allow net spin-up Sometimes tests are failing at first try, as the pods were not up yet. Adding timeout should allow the network to spin up properly. * initial timeout increased to 30s --- zombienet/0000-block-building/block-building.zndsl | 4 ++-- zombienet/0001-basic-warp-sync/test-warp-sync.zndsl | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/zombienet/0000-block-building/block-building.zndsl b/zombienet/0000-block-building/block-building.zndsl index c53e50915c..86a5477348 100644 --- a/zombienet/0000-block-building/block-building.zndsl +++ b/zombienet/0000-block-building/block-building.zndsl @@ -2,8 +2,8 @@ Description: Block building Network: ./block-building.toml Creds: config -alice: is up -bob: is up +alice: is up within 30 seconds +bob: is up within 30 seconds alice: reports node_roles is 4 bob: reports node_roles is 4 diff --git a/zombienet/0001-basic-warp-sync/test-warp-sync.zndsl b/zombienet/0001-basic-warp-sync/test-warp-sync.zndsl index 1ccacb2e6d..8ceb61c8b0 100644 --- a/zombienet/0001-basic-warp-sync/test-warp-sync.zndsl +++ b/zombienet/0001-basic-warp-sync/test-warp-sync.zndsl @@ -2,10 +2,10 @@ Description: Warp sync Network: ./test-warp-sync.toml Creds: config -alice: is up -bob: is up -charlie: is up -dave: is up +alice: is up within 30 seconds +bob: is up within 30 seconds +charlie: is up within 30 seconds +dave: is up within 30 seconds alice: reports node_roles is 1 bob: reports node_roles is 1 From 9931220910f9fb65227fe4571842f800d61c7b95 Mon Sep 17 00:00:00 2001 From: Aaro Altonen <48052676+altonen@users.noreply.github.com> Date: Fri, 9 Dec 2022 21:50:57 +0200 Subject: [PATCH 020/127] Move import queue out of `sc-network` (#12764) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Move import queue out of `sc-network` Add supplementary asynchronous API for the import queue which means it can be run as an independent task and communicated with through the `ImportQueueService`. This commit removes removes block and justification imports from `sc-network` and provides `ChainSync` with a handle to import queue so it can import blocks and justifications. Polling of the import queue is moved complete out of `sc-network` and `sc_consensus::Link` is implemented for `ChainSyncInterfaceHandled` so the import queue can still influence the syncing process. * Fix tests * Apply review comments * Apply suggestions from code review Co-authored-by: Bastian Köcher * Update client/network/sync/src/lib.rs Co-authored-by: Bastian Köcher Co-authored-by: Bastian Köcher --- Cargo.lock | 2 + client/consensus/common/Cargo.toml | 1 + client/consensus/common/src/import_queue.rs | 23 +- .../common/src/import_queue/basic_queue.rs | 69 ++- .../common/src/import_queue/buffered_link.rs | 32 +- .../consensus/common/src/import_queue/mock.rs | 46 ++ client/network/common/src/sync.rs | 28 +- client/network/src/behaviour.rs | 31 +- client/network/src/config.rs | 7 - client/network/src/lib.rs | 9 +- client/network/src/protocol.rs | 107 +--- client/network/src/service.rs | 88 +--- client/network/src/service/metrics.rs | 10 - .../network/src/service/tests/chain_sync.rs | 106 ++-- client/network/src/service/tests/mod.rs | 40 +- client/network/sync/Cargo.toml | 1 + client/network/sync/src/lib.rs | 483 ++++++++++++------ client/network/sync/src/mock.rs | 14 +- client/network/sync/src/service/chain_sync.rs | 53 ++ client/network/sync/src/service/mock.rs | 33 +- client/network/sync/src/tests.rs | 3 + client/network/test/src/lib.rs | 12 +- client/service/src/builder.rs | 6 +- client/service/src/chain_ops/import_blocks.rs | 2 +- 24 files changed, 716 insertions(+), 490 deletions(-) create mode 100644 client/consensus/common/src/import_queue/mock.rs diff --git a/Cargo.lock b/Cargo.lock index 41c641cf05..8225e55714 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7328,6 +7328,7 @@ dependencies = [ "futures-timer", "libp2p", "log", + "mockall", "parking_lot 0.12.1", "sc-client-api", "sc-utils", @@ -7929,6 +7930,7 @@ dependencies = [ "sp-runtime", "sp-test-primitives", "sp-tracing", + "substrate-prometheus-endpoint", "substrate-test-runtime-client", "thiserror", "tokio", diff --git a/client/consensus/common/Cargo.toml b/client/consensus/common/Cargo.toml index 971ee71ab8..b61c6a4334 100644 --- a/client/consensus/common/Cargo.toml +++ b/client/consensus/common/Cargo.toml @@ -18,6 +18,7 @@ futures = { version = "0.3.21", features = ["thread-pool"] } futures-timer = "3.0.1" libp2p = { version = "0.49.0", default-features = false } log = "0.4.17" +mockall = "0.11.2" parking_lot = "0.12.1" serde = { version = "1.0", features = ["derive"] } thiserror = "1.0.30" diff --git a/client/consensus/common/src/import_queue.rs b/client/consensus/common/src/import_queue.rs index 3741fa9966..d49b240ef3 100644 --- a/client/consensus/common/src/import_queue.rs +++ b/client/consensus/common/src/import_queue.rs @@ -53,6 +53,7 @@ pub type DefaultImportQueue = mod basic_queue; pub mod buffered_link; +pub mod mock; /// Shared block import struct used by the queue. pub type BoxBlockImport = @@ -105,10 +106,10 @@ pub trait Verifier: Send + Sync { /// Blocks import queue API. /// /// The `import_*` methods can be called in order to send elements for the import queue to verify. -/// Afterwards, call `poll_actions` to determine how to respond to these elements. -pub trait ImportQueue: Send { +pub trait ImportQueueService: Send { /// Import bunch of blocks. fn import_blocks(&mut self, origin: BlockOrigin, blocks: Vec>); + /// Import block justifications. fn import_justifications( &mut self, @@ -117,12 +118,26 @@ pub trait ImportQueue: Send { number: NumberFor, justifications: Justifications, ); - /// Polls for actions to perform on the network. - /// +} + +#[async_trait::async_trait] +pub trait ImportQueue: Send { + /// Get a copy of the handle to [`ImportQueueService`]. + fn service(&self) -> Box>; + + /// Get a reference to the handle to [`ImportQueueService`]. + fn service_ref(&mut self) -> &mut dyn ImportQueueService; + /// This method should behave in a way similar to `Future::poll`. It can register the current /// task and notify later when more actions are ready to be polled. To continue the comparison, /// it is as if this method always returned `Poll::Pending`. fn poll_actions(&mut self, cx: &mut futures::task::Context, link: &mut dyn Link); + + /// Start asynchronous runner for import queue. + /// + /// Takes an object implementing [`Link`] which allows the import queue to + /// influece the synchronization process. + async fn run(self, link: Box>); } /// Hooks that the verification queue can use to influence the synchronization diff --git a/client/consensus/common/src/import_queue/basic_queue.rs b/client/consensus/common/src/import_queue/basic_queue.rs index 0e607159b7..20e8d262ca 100644 --- a/client/consensus/common/src/import_queue/basic_queue.rs +++ b/client/consensus/common/src/import_queue/basic_queue.rs @@ -34,7 +34,8 @@ use crate::{ import_queue::{ buffered_link::{self, BufferedLinkReceiver, BufferedLinkSender}, import_single_block_metered, BlockImportError, BlockImportStatus, BoxBlockImport, - BoxJustificationImport, ImportQueue, IncomingBlock, Link, RuntimeOrigin, Verifier, + BoxJustificationImport, ImportQueue, ImportQueueService, IncomingBlock, Link, + RuntimeOrigin, Verifier, }, metrics::Metrics, }; @@ -42,10 +43,8 @@ use crate::{ /// Interface to a basic block import queue that is importing blocks sequentially in a separate /// task, with plugable verification. pub struct BasicQueue { - /// Channel to send justification import messages to the background task. - justification_sender: TracingUnboundedSender>, - /// Channel to send block import messages to the background task. - block_import_sender: TracingUnboundedSender>, + /// Handle for sending justification and block import messages to the background task. + handle: BasicQueueHandle, /// Results coming from the worker task. result_port: BufferedLinkReceiver, _phantom: PhantomData, @@ -54,8 +53,7 @@ pub struct BasicQueue { impl Drop for BasicQueue { fn drop(&mut self) { // Flush the queue and close the receiver to terminate the future. - self.justification_sender.close_channel(); - self.block_import_sender.close_channel(); + self.handle.close(); self.result_port.close(); } } @@ -95,11 +93,37 @@ impl BasicQueue { future.boxed(), ); - Self { justification_sender, block_import_sender, result_port, _phantom: PhantomData } + Self { + handle: BasicQueueHandle::new(justification_sender, block_import_sender), + result_port, + _phantom: PhantomData, + } } } -impl ImportQueue for BasicQueue { +#[derive(Clone)] +struct BasicQueueHandle { + /// Channel to send justification import messages to the background task. + justification_sender: TracingUnboundedSender>, + /// Channel to send block import messages to the background task. + block_import_sender: TracingUnboundedSender>, +} + +impl BasicQueueHandle { + pub fn new( + justification_sender: TracingUnboundedSender>, + block_import_sender: TracingUnboundedSender>, + ) -> Self { + Self { justification_sender, block_import_sender } + } + + pub fn close(&mut self) { + self.justification_sender.close_channel(); + self.block_import_sender.close_channel(); + } +} + +impl ImportQueueService for BasicQueueHandle { fn import_blocks(&mut self, origin: BlockOrigin, blocks: Vec>) { if blocks.is_empty() { return @@ -138,12 +162,39 @@ impl ImportQueue for BasicQueue } } } +} + +#[async_trait::async_trait] +impl ImportQueue for BasicQueue { + /// Get handle to [`ImportQueueService`]. + fn service(&self) -> Box> { + Box::new(self.handle.clone()) + } + /// Get a reference to the handle to [`ImportQueueService`]. + fn service_ref(&mut self) -> &mut dyn ImportQueueService { + &mut self.handle + } + + /// Poll actions from network. fn poll_actions(&mut self, cx: &mut Context, link: &mut dyn Link) { if self.result_port.poll_actions(cx, link).is_err() { log::error!(target: "sync", "poll_actions: Background import task is no longer alive"); } } + + /// Start asynchronous runner for import queue. + /// + /// Takes an object implementing [`Link`] which allows the import queue to + /// influece the synchronization process. + async fn run(mut self, mut link: Box>) { + loop { + if let Err(_) = self.result_port.next_action(&mut *link).await { + log::error!(target: "sync", "poll_actions: Background import task is no longer alive"); + return + } + } + } } /// Messages destinated to the background worker. diff --git a/client/consensus/common/src/import_queue/buffered_link.rs b/client/consensus/common/src/import_queue/buffered_link.rs index 5d418dddf0..e6d3b212fd 100644 --- a/client/consensus/common/src/import_queue/buffered_link.rs +++ b/client/consensus/common/src/import_queue/buffered_link.rs @@ -80,7 +80,7 @@ impl Clone for BufferedLinkSender { } /// Internal buffered message. -enum BlockImportWorkerMsg { +pub enum BlockImportWorkerMsg { BlocksProcessed(usize, usize, Vec<(BlockImportResult, B::Hash)>), JustificationImported(RuntimeOrigin, B::Hash, NumberFor, bool), RequestJustification(B::Hash, NumberFor), @@ -122,6 +122,18 @@ pub struct BufferedLinkReceiver { } impl BufferedLinkReceiver { + /// Send action for the synchronization to perform. + pub fn send_actions(&mut self, msg: BlockImportWorkerMsg, link: &mut dyn Link) { + match msg { + BlockImportWorkerMsg::BlocksProcessed(imported, count, results) => + link.blocks_processed(imported, count, results), + BlockImportWorkerMsg::JustificationImported(who, hash, number, success) => + link.justification_imported(who, &hash, number, success), + BlockImportWorkerMsg::RequestJustification(hash, number) => + link.request_justification(&hash, number), + } + } + /// Polls for the buffered link actions. Any enqueued action will be propagated to the link /// passed as parameter. /// @@ -138,15 +150,17 @@ impl BufferedLinkReceiver { Poll::Pending => break Ok(()), }; - match msg { - BlockImportWorkerMsg::BlocksProcessed(imported, count, results) => - link.blocks_processed(imported, count, results), - BlockImportWorkerMsg::JustificationImported(who, hash, number, success) => - link.justification_imported(who, &hash, number, success), - BlockImportWorkerMsg::RequestJustification(hash, number) => - link.request_justification(&hash, number), - } + self.send_actions(msg, &mut *link); + } + } + + /// Poll next element from import queue and send the corresponding action command over the link. + pub async fn next_action(&mut self, link: &mut dyn Link) -> Result<(), ()> { + if let Some(msg) = self.rx.next().await { + self.send_actions(msg, link); + return Ok(()) } + Err(()) } /// Close the channel. diff --git a/client/consensus/common/src/import_queue/mock.rs b/client/consensus/common/src/import_queue/mock.rs new file mode 100644 index 0000000000..67deee9514 --- /dev/null +++ b/client/consensus/common/src/import_queue/mock.rs @@ -0,0 +1,46 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 . + +use super::*; + +mockall::mock! { + pub ImportQueueHandle {} + + impl ImportQueueService for ImportQueueHandle { + fn import_blocks(&mut self, origin: BlockOrigin, blocks: Vec>); + fn import_justifications( + &mut self, + who: RuntimeOrigin, + hash: B::Hash, + number: NumberFor, + justifications: Justifications, + ); + } +} + +mockall::mock! { + pub ImportQueue {} + + #[async_trait::async_trait] + impl ImportQueue for ImportQueue { + fn service(&self) -> Box>; + fn service_ref(&mut self) -> &mut dyn ImportQueueService; + fn poll_actions<'a>(&mut self, cx: &mut futures::task::Context<'a>, link: &mut dyn Link); + async fn run(self, link: Box>); + } +} diff --git a/client/network/common/src/sync.rs b/client/network/common/src/sync.rs index bed9935698..5e8219c550 100644 --- a/client/network/common/src/sync.rs +++ b/client/network/common/src/sync.rs @@ -24,9 +24,7 @@ pub mod warp; use libp2p::PeerId; use message::{BlockAnnounce, BlockData, BlockRequest, BlockResponse}; -use sc_consensus::{ - import_queue::RuntimeOrigin, BlockImportError, BlockImportStatus, IncomingBlock, -}; +use sc_consensus::{import_queue::RuntimeOrigin, IncomingBlock}; use sp_consensus::BlockOrigin; use sp_runtime::{ traits::{Block as BlockT, NumberFor}, @@ -317,6 +315,12 @@ pub trait ChainSync: Send { response: BlockResponse, ) -> Result, BadPeer>; + /// Procss received block data. + fn process_block_response_data( + &mut self, + blocks_to_import: Result, BadPeer>, + ); + /// Handle a response from the remote to a justification request that we made. /// /// `request` must be the original request that triggered `response`. @@ -326,17 +330,6 @@ pub trait ChainSync: Send { response: BlockResponse, ) -> Result, BadPeer>; - /// A batch of blocks have been processed, with or without errors. - /// - /// Call this when a batch of blocks have been processed by the import - /// queue, with or without errors. - fn on_blocks_processed( - &mut self, - imported: usize, - count: usize, - results: Vec<(Result>, BlockImportError>, Block::Hash)>, - ) -> Box), BadPeer>>>; - /// Call this when a justification has been processed by the import queue, /// with or without errors. fn on_justification_import( @@ -378,7 +371,7 @@ pub trait ChainSync: Send { /// Call when a peer has disconnected. /// Canceled obsolete block request may result in some blocks being ready for /// import, so this functions checks for such blocks and returns them. - fn peer_disconnected(&mut self, who: &PeerId) -> Option>; + fn peer_disconnected(&mut self, who: &PeerId); /// Return some key metrics. fn metrics(&self) -> Metrics; @@ -395,7 +388,10 @@ pub trait ChainSync: Send { /// Internally calls [`ChainSync::poll_block_announce_validation()`] and /// this function should be polled until it returns [`Poll::Pending`] to /// consume all pending events. - fn poll(&mut self, cx: &mut std::task::Context) -> Poll>; + fn poll( + &mut self, + cx: &mut std::task::Context, + ) -> Poll>; /// Send block request to peer fn send_block_request(&mut self, who: PeerId, request: BlockRequest); diff --git a/client/network/src/behaviour.rs b/client/network/src/behaviour.rs index 48d6127f64..3a977edbca 100644 --- a/client/network/src/behaviour.rs +++ b/client/network/src/behaviour.rs @@ -32,7 +32,6 @@ use libp2p::{ NetworkBehaviour, }; -use sc_consensus::import_queue::{IncomingBlock, RuntimeOrigin}; use sc_network_common::{ protocol::{ event::DhtEvent, @@ -43,18 +42,14 @@ use sc_network_common::{ }; use sc_peerset::{PeersetHandle, ReputationChange}; use sp_blockchain::HeaderBackend; -use sp_consensus::BlockOrigin; -use sp_runtime::{ - traits::{Block as BlockT, NumberFor}, - Justifications, -}; +use sp_runtime::traits::Block as BlockT; use std::{collections::HashSet, time::Duration}; pub use crate::request_responses::{InboundFailure, OutboundFailure, RequestId, ResponseFailure}; /// General behaviour of the network. Combines all protocols together. #[derive(NetworkBehaviour)] -#[behaviour(out_event = "BehaviourOut")] +#[behaviour(out_event = "BehaviourOut")] pub struct Behaviour where B: BlockT, @@ -72,10 +67,7 @@ where } /// Event generated by `Behaviour`. -pub enum BehaviourOut { - BlockImport(BlockOrigin, Vec>), - JustificationImport(RuntimeOrigin, B::Hash, NumberFor, Justifications), - +pub enum BehaviourOut { /// Started a random iterative Kademlia discovery query. RandomKademliaStarted, @@ -107,10 +99,7 @@ pub enum BehaviourOut { }, /// A request protocol handler issued reputation changes for the given peer. - ReputationChanges { - peer: PeerId, - changes: Vec, - }, + ReputationChanges { peer: PeerId, changes: Vec }, /// Opened a substream with the given node with the given notifications protocol. /// @@ -306,13 +295,9 @@ fn reported_roles_to_observed_role(roles: Roles) -> ObservedRole { } } -impl From> for BehaviourOut { +impl From> for BehaviourOut { fn from(event: CustomMessageOutcome) -> Self { match event { - CustomMessageOutcome::BlockImport(origin, blocks) => - BehaviourOut::BlockImport(origin, blocks), - CustomMessageOutcome::JustificationImport(origin, hash, nb, justification) => - BehaviourOut::JustificationImport(origin, hash, nb, justification), CustomMessageOutcome::NotificationStreamOpened { remote, protocol, @@ -344,7 +329,7 @@ impl From> for BehaviourOut { } } -impl From for BehaviourOut { +impl From for BehaviourOut { fn from(event: request_responses::Event) -> Self { match event { request_responses::Event::InboundRequest { peer, protocol, result } => @@ -357,14 +342,14 @@ impl From for BehaviourOut { } } -impl From for BehaviourOut { +impl From for BehaviourOut { fn from(event: peer_info::PeerInfoEvent) -> Self { let peer_info::PeerInfoEvent::Identified { peer_id, info } = event; BehaviourOut::PeerIdentify { peer_id, info } } } -impl From for BehaviourOut { +impl From for BehaviourOut { fn from(event: DiscoveryOut) -> Self { match event { DiscoveryOut::UnroutablePeer(_peer_id) => { diff --git a/client/network/src/config.rs b/client/network/src/config.rs index b10612dd17..52993e2519 100644 --- a/client/network/src/config.rs +++ b/client/network/src/config.rs @@ -40,7 +40,6 @@ use libp2p::{ multiaddr, Multiaddr, }; use prometheus_endpoint::Registry; -use sc_consensus::ImportQueue; use sc_network_common::{ config::{MultiaddrWithPeerId, NonDefaultSetConfig, SetConfig, TransportConfig}, sync::ChainSync, @@ -82,12 +81,6 @@ where /// name on the wire. pub fork_id: Option, - /// Import queue to use. - /// - /// The import queue is the component that verifies that blocks received from other nodes are - /// valid. - pub import_queue: Box>, - /// Instance of chain sync implementation. pub chain_sync: Box>, diff --git a/client/network/src/lib.rs b/client/network/src/lib.rs index f3faa44ee6..f185458e0d 100644 --- a/client/network/src/lib.rs +++ b/client/network/src/lib.rs @@ -258,6 +258,7 @@ pub mod network_state; #[doc(inline)] pub use libp2p::{multiaddr, Multiaddr, PeerId}; pub use protocol::PeerInfo; +use sc_consensus::{JustificationSyncLink, Link}; pub use sc_network_common::{ protocol::{ event::{DhtEvent, Event}, @@ -297,11 +298,15 @@ const MAX_CONNECTIONS_ESTABLISHED_INCOMING: u32 = 10_000; /// Abstraction over syncing-related services pub trait ChainSyncInterface: - NetworkSyncForkRequest> + Send + Sync + NetworkSyncForkRequest> + JustificationSyncLink + Link + Send + Sync { } impl ChainSyncInterface for T where - T: NetworkSyncForkRequest> + Send + Sync + T: NetworkSyncForkRequest> + + JustificationSyncLink + + Link + + Send + + Sync { } diff --git a/client/network/src/protocol.rs b/client/network/src/protocol.rs index 8c1dd39b49..10eb31b595 100644 --- a/client/network/src/protocol.rs +++ b/client/network/src/protocol.rs @@ -29,32 +29,26 @@ use libp2p::{ }, Multiaddr, PeerId, }; -use log::{debug, error, info, log, trace, warn, Level}; +use log::{debug, error, log, trace, warn, Level}; use lru::LruCache; use message::{generic::Message as GenericMessage, Message}; use notifications::{Notifications, NotificationsOut}; use prometheus_endpoint::{register, Gauge, GaugeVec, Opts, PrometheusError, Registry, U64}; use sc_client_api::HeaderBackend; -use sc_consensus::import_queue::{ - BlockImportError, BlockImportStatus, IncomingBlock, RuntimeOrigin, -}; use sc_network_common::{ config::NonReservedPeerMode, error, protocol::{role::Roles, ProtocolName}, sync::{ message::{BlockAnnounce, BlockAnnouncesHandshake, BlockData, BlockResponse, BlockState}, - BadPeer, ChainSync, ImportResult, OnBlockData, PollBlockAnnounceValidation, PollResult, - SyncStatus, + BadPeer, ChainSync, PollBlockAnnounceValidation, SyncStatus, }, utils::{interval, LruHashSet}, }; use sp_arithmetic::traits::SaturatedConversion; -use sp_consensus::BlockOrigin; use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, CheckedSub, Header as HeaderT, NumberFor, Zero}, - Justifications, }; use std::{ collections::{HashMap, HashSet, VecDeque}, @@ -481,12 +475,7 @@ where } if let Some(_peer_data) = self.peers.remove(&peer) { - if let Some(OnBlockData::Import(origin, blocks)) = - self.chain_sync.peer_disconnected(&peer) - { - self.pending_messages - .push_back(CustomMessageOutcome::BlockImport(origin, blocks)); - } + self.chain_sync.peer_disconnected(&peer); self.default_peers_set_no_slot_connected_peers.remove(&peer); Ok(()) } else { @@ -785,25 +774,13 @@ where }], }, ); + self.chain_sync.process_block_response_data(blocks_to_import); if is_best { self.pending_messages.push_back(CustomMessageOutcome::PeerNewBest(who, number)); } - match blocks_to_import { - Ok(OnBlockData::Import(origin, blocks)) => - CustomMessageOutcome::BlockImport(origin, blocks), - Ok(OnBlockData::Request(peer, req)) => { - self.chain_sync.send_block_request(peer, req); - CustomMessageOutcome::None - }, - Ok(OnBlockData::Continue) => CustomMessageOutcome::None, - Err(BadPeer(id, repu)) => { - self.behaviour.disconnect_peer(&id, HARDCODED_PEERSETS_SYNC); - self.peerset_handle.report_peer(id, repu); - CustomMessageOutcome::None - }, - } + CustomMessageOutcome::None } /// Call this when a block has been finalized. The sync layer may have some additional @@ -812,58 +789,6 @@ where self.chain_sync.on_block_finalized(&hash, *header.number()) } - /// Request a justification for the given block. - /// - /// Uses `protocol` to queue a new justification request and tries to dispatch all pending - /// requests. - pub fn request_justification(&mut self, hash: &B::Hash, number: NumberFor) { - self.chain_sync.request_justification(hash, number) - } - - /// Clear all pending justification requests. - pub fn clear_justification_requests(&mut self) { - self.chain_sync.clear_justification_requests(); - } - - /// A batch of blocks have been processed, with or without errors. - /// Call this when a batch of blocks have been processed by the importqueue, with or without - /// errors. - pub fn on_blocks_processed( - &mut self, - imported: usize, - count: usize, - results: Vec<(Result>, BlockImportError>, B::Hash)>, - ) { - let results = self.chain_sync.on_blocks_processed(imported, count, results); - for result in results { - match result { - Ok((id, req)) => self.chain_sync.send_block_request(id, req), - Err(BadPeer(id, repu)) => { - self.behaviour.disconnect_peer(&id, HARDCODED_PEERSETS_SYNC); - self.peerset_handle.report_peer(id, repu) - }, - } - } - } - - /// Call this when a justification has been processed by the import queue, with or without - /// errors. - pub fn justification_import_result( - &mut self, - who: PeerId, - hash: B::Hash, - number: NumberFor, - success: bool, - ) { - self.chain_sync.on_justification_import(hash, number, success); - if !success { - info!("💔 Invalid justification provided by {} for #{}", who, hash); - self.behaviour.disconnect_peer(&who, HARDCODED_PEERSETS_SYNC); - self.peerset_handle - .report_peer(who, sc_peerset::ReputationChange::new_fatal("Invalid justification")); - } - } - /// Set whether the syncing peers set is in reserved-only mode. pub fn set_reserved_only(&self, reserved_only: bool) { self.peerset_handle.set_reserved_only(HARDCODED_PEERSETS_SYNC, reserved_only); @@ -997,8 +922,6 @@ where #[derive(Debug)] #[must_use] pub enum CustomMessageOutcome { - BlockImport(BlockOrigin, Vec>), - JustificationImport(RuntimeOrigin, B::Hash, NumberFor, Justifications), /// Notification protocols have been opened with a remote. NotificationStreamOpened { remote: PeerId, @@ -1106,23 +1029,9 @@ where // Process any received requests received from `NetworkService` and // check if there is any block announcement validation finished. while let Poll::Ready(result) = self.chain_sync.poll(cx) { - match result { - PollResult::Import(import) => self.pending_messages.push_back(match import { - ImportResult::BlockImport(origin, blocks) => - CustomMessageOutcome::BlockImport(origin, blocks), - ImportResult::JustificationImport(origin, hash, number, justifications) => - CustomMessageOutcome::JustificationImport( - origin, - hash, - number, - justifications, - ), - }), - PollResult::Announce(announce) => - match self.process_block_announce_validation_result(announce) { - CustomMessageOutcome::None => {}, - outcome => self.pending_messages.push_back(outcome), - }, + match self.process_block_announce_validation_result(result) { + CustomMessageOutcome::None => {}, + outcome => self.pending_messages.push_back(outcome), } } diff --git a/client/network/src/service.rs b/client/network/src/service.rs index d35594a07e..08e498299a 100644 --- a/client/network/src/service.rs +++ b/client/network/src/service.rs @@ -54,7 +54,6 @@ use libp2p::{ use log::{debug, error, info, trace, warn}; use metrics::{Histogram, HistogramVec, MetricSources, Metrics}; use parking_lot::Mutex; -use sc_consensus::{BlockImportError, BlockImportStatus, ImportQueue, Link}; use sc_network_common::{ config::{MultiaddrWithPeerId, TransportConfig}, error::Error, @@ -450,7 +449,6 @@ where is_major_syncing, network_service: swarm, service, - import_queue: params.import_queue, from_service, event_streams: out_events::OutChannels::new(params.metrics_registry.as_ref())?, peers_notifications_sinks, @@ -748,13 +746,11 @@ impl sc_consensus::JustificationSyncLink for NetworkSe /// On success, the justification will be passed to the import queue that was part at /// initialization as part of the configuration. fn request_justification(&self, hash: &B::Hash, number: NumberFor) { - let _ = self - .to_worker - .unbounded_send(ServiceToWorkerMsg::RequestJustification(*hash, number)); + let _ = self.chain_sync_service.request_justification(hash, number); } fn clear_justification_requests(&self) { - let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::ClearJustificationRequests); + let _ = self.chain_sync_service.clear_justification_requests(); } } @@ -1208,8 +1204,6 @@ impl<'a> NotificationSenderReadyT for NotificationSenderReady<'a> { /// /// Each entry corresponds to a method of `NetworkService`. enum ServiceToWorkerMsg { - RequestJustification(B::Hash, NumberFor), - ClearJustificationRequests, AnnounceBlock(B::Hash, Option>), GetValue(KademliaKey), PutValue(KademliaKey, Vec), @@ -1261,8 +1255,6 @@ where service: Arc>, /// The *actual* network. network_service: Swarm>, - /// The import queue that was passed at initialization. - import_queue: Box>, /// Messages from the [`NetworkService`] that must be processed. from_service: TracingUnboundedReceiver>, /// Senders for events that happen on the network. @@ -1290,10 +1282,6 @@ where fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context) -> Poll { let this = &mut *self; - // Poll the import queue for actions to perform. - this.import_queue - .poll_actions(cx, &mut NetworkLink { protocol: &mut this.network_service }); - // At the time of writing of this comment, due to a high volume of messages, the network // worker sometimes takes a long time to process the loop below. When that happens, the // rest of the polling is frozen. In order to avoid negative side-effects caused by this @@ -1322,16 +1310,6 @@ where .behaviour_mut() .user_protocol_mut() .announce_block(hash, data), - ServiceToWorkerMsg::RequestJustification(hash, number) => this - .network_service - .behaviour_mut() - .user_protocol_mut() - .request_justification(&hash, number), - ServiceToWorkerMsg::ClearJustificationRequests => this - .network_service - .behaviour_mut() - .user_protocol_mut() - .clear_justification_requests(), ServiceToWorkerMsg::GetValue(key) => this.network_service.behaviour_mut().get_value(key), ServiceToWorkerMsg::PutValue(key, value) => @@ -1435,23 +1413,6 @@ where match poll_value { Poll::Pending => break, - Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::BlockImport(origin, blocks))) => { - if let Some(metrics) = this.metrics.as_ref() { - metrics.import_queue_blocks_submitted.inc(); - } - this.import_queue.import_blocks(origin, blocks); - }, - Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::JustificationImport( - origin, - hash, - nb, - justifications, - ))) => { - if let Some(metrics) = this.metrics.as_ref() { - metrics.import_queue_justifications_submitted.inc(); - } - this.import_queue.import_justifications(origin, hash, nb, justifications); - }, Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::InboundRequest { protocol, result, @@ -1952,51 +1913,6 @@ where { } -// Implementation of `import_queue::Link` trait using the available local variables. -struct NetworkLink<'a, B, Client> -where - B: BlockT, - Client: HeaderBackend + 'static, -{ - protocol: &'a mut Swarm>, -} - -impl<'a, B, Client> Link for NetworkLink<'a, B, Client> -where - B: BlockT, - Client: HeaderBackend + 'static, -{ - fn blocks_processed( - &mut self, - imported: usize, - count: usize, - results: Vec<(Result>, BlockImportError>, B::Hash)>, - ) { - self.protocol - .behaviour_mut() - .user_protocol_mut() - .on_blocks_processed(imported, count, results) - } - fn justification_imported( - &mut self, - who: PeerId, - hash: &B::Hash, - number: NumberFor, - success: bool, - ) { - self.protocol - .behaviour_mut() - .user_protocol_mut() - .justification_import_result(who, *hash, number, success); - } - fn request_justification(&mut self, hash: &B::Hash, number: NumberFor) { - self.protocol - .behaviour_mut() - .user_protocol_mut() - .request_justification(hash, number) - } -} - fn ensure_addresses_consistent_with_transport<'a>( addresses: impl Iterator, transport: &TransportConfig, diff --git a/client/network/src/service/metrics.rs b/client/network/src/service/metrics.rs index db1b6f7f65..a099bba716 100644 --- a/client/network/src/service/metrics.rs +++ b/client/network/src/service/metrics.rs @@ -53,8 +53,6 @@ pub struct Metrics { pub connections_opened_total: CounterVec, pub distinct_peers_connections_closed_total: Counter, pub distinct_peers_connections_opened_total: Counter, - pub import_queue_blocks_submitted: Counter, - pub import_queue_justifications_submitted: Counter, pub incoming_connections_errors_total: CounterVec, pub incoming_connections_total: Counter, pub issued_light_requests: Counter, @@ -103,14 +101,6 @@ impl Metrics { "substrate_sub_libp2p_distinct_peers_connections_opened_total", "Total number of connections opened with distinct peers" )?, registry)?, - import_queue_blocks_submitted: prometheus::register(Counter::new( - "substrate_import_queue_blocks_submitted", - "Number of blocks submitted to the import queue.", - )?, registry)?, - import_queue_justifications_submitted: prometheus::register(Counter::new( - "substrate_import_queue_justifications_submitted", - "Number of justifications submitted to the import queue.", - )?, registry)?, incoming_connections_errors_total: prometheus::register(CounterVec::new( Opts::new( "substrate_sub_libp2p_incoming_connections_handshake_errors_total", diff --git a/client/network/src/service/tests/chain_sync.rs b/client/network/src/service/tests/chain_sync.rs index bd4967f259..0f47b64c35 100644 --- a/client/network/src/service/tests/chain_sync.rs +++ b/client/network/src/service/tests/chain_sync.rs @@ -86,27 +86,26 @@ async fn normal_network_poll_no_peers() { #[tokio::test] async fn request_justification() { - // build `ChainSyncInterface` provider and set no expecations for it (i.e., it cannot be - // called) - let chain_sync_service = - Box::new(MockChainSyncInterface::::new()); - - // build `ChainSync` and verify that call to `request_justification()` is made - let mut chain_sync = - Box::new(MockChainSync::::new()); - let hash = H256::random(); let number = 1337u64; - chain_sync - .expect_request_justification() + // build `ChainSyncInterface` provider and and expect + // `JustificationSyncLink::request_justification() to be called once + let mut chain_sync_service = + Box::new(MockChainSyncInterface::::new()); + + chain_sync_service + .expect_justification_sync_link_request_justification() .withf(move |in_hash, in_number| &hash == in_hash && &number == in_number) .once() .returning(|_, _| ()); + // build `ChainSync` and set default expecations for it + let mut chain_sync = MockChainSync::::new(); + set_default_expecations_no_peers(&mut chain_sync); let mut network = TestNetworkBuilder::new(Handle::current()) - .with_chain_sync((chain_sync, chain_sync_service)) + .with_chain_sync((Box::new(chain_sync), chain_sync_service)) .build(); // send "request justifiction" message and poll the network @@ -121,17 +120,20 @@ async fn request_justification() { #[tokio::test] async fn clear_justification_requests() { - // build `ChainSyncInterface` provider and set no expecations for it (i.e., it cannot be - // called) - let chain_sync_service = + // build `ChainSyncInterface` provider and expect + // `JustificationSyncLink::clear_justification_requests()` to be called + let mut chain_sync_service = Box::new(MockChainSyncInterface::::new()); - // build `ChainSync` and verify that call to `clear_justification_requests()` is made + chain_sync_service + .expect_justification_sync_link_clear_justification_requests() + .once() + .returning(|| ()); + + // build `ChainSync` and set default expecations for it let mut chain_sync = Box::new(MockChainSync::::new()); - chain_sync.expect_clear_justification_requests().once().returning(|| ()); - set_default_expecations_no_peers(&mut chain_sync); let mut network = TestNetworkBuilder::new(Handle::current()) .with_chain_sync((chain_sync, chain_sync_service)) @@ -235,19 +237,13 @@ async fn on_block_finalized() { // and verify that connection to the peer is closed #[tokio::test] async fn invalid_justification_imported() { - struct DummyImportQueue( - Arc< - RwLock< - Option<( - PeerId, - substrate_test_runtime_client::runtime::Hash, - sp_runtime::traits::NumberFor, - )>, - >, - >, - ); + struct DummyImportQueueHandle; - impl sc_consensus::ImportQueue for DummyImportQueue { + impl + sc_consensus::import_queue::ImportQueueService< + substrate_test_runtime_client::runtime::Block, + > for DummyImportQueueHandle + { fn import_blocks( &mut self, _origin: sp_consensus::BlockOrigin, @@ -265,7 +261,23 @@ async fn invalid_justification_imported() { _justifications: sp_runtime::Justifications, ) { } + } + struct DummyImportQueue( + Arc< + RwLock< + Option<( + PeerId, + substrate_test_runtime_client::runtime::Hash, + sp_runtime::traits::NumberFor, + )>, + >, + >, + DummyImportQueueHandle, + ); + + #[async_trait::async_trait] + impl sc_consensus::ImportQueue for DummyImportQueue { fn poll_actions( &mut self, _cx: &mut futures::task::Context, @@ -275,13 +287,40 @@ async fn invalid_justification_imported() { link.justification_imported(peer, &hash, number, false); } } + + fn service( + &self, + ) -> Box< + dyn sc_consensus::import_queue::ImportQueueService< + substrate_test_runtime_client::runtime::Block, + >, + > { + Box::new(DummyImportQueueHandle {}) + } + + fn service_ref( + &mut self, + ) -> &mut dyn sc_consensus::import_queue::ImportQueueService< + substrate_test_runtime_client::runtime::Block, + > { + &mut self.1 + } + + async fn run( + self, + _link: Box>, + ) { + } } let justification_info = Arc::new(RwLock::new(None)); let listen_addr = config::build_multiaddr![Memory(rand::random::())]; let (service1, mut event_stream1) = TestNetworkBuilder::new(Handle::current()) - .with_import_queue(Box::new(DummyImportQueue(justification_info.clone()))) + .with_import_queue(Box::new(DummyImportQueue( + justification_info.clone(), + DummyImportQueueHandle {}, + ))) .with_listen_addresses(vec![listen_addr.clone()]) .build() .start_network(); @@ -331,6 +370,7 @@ async fn disconnect_peer_using_chain_sync_handle() { let client = Arc::new(TestClientBuilder::with_default_backend().build_with_longest_chain().0); let listen_addr = config::build_multiaddr![Memory(rand::random::())]; + let import_queue = Box::new(sc_consensus::import_queue::mock::MockImportQueueHandle::new()); let (chain_sync_network_provider, chain_sync_network_handle) = sc_network_sync::service::network::NetworkServiceProvider::new(); let handle_clone = chain_sync_network_handle.clone(); @@ -344,7 +384,9 @@ async fn disconnect_peer_using_chain_sync_handle() { Box::new(sp_consensus::block_validation::DefaultBlockAnnounceValidator), 1u32, None, + None, chain_sync_network_handle.clone(), + import_queue, ProtocolName::from("block-request"), ProtocolName::from("state-request"), None, @@ -353,7 +395,7 @@ async fn disconnect_peer_using_chain_sync_handle() { let (node1, mut event_stream1) = TestNetworkBuilder::new(Handle::current()) .with_listen_addresses(vec![listen_addr.clone()]) - .with_chain_sync((Box::new(chain_sync), chain_sync_service)) + .with_chain_sync((Box::new(chain_sync), Box::new(chain_sync_service))) .with_chain_sync_network((chain_sync_network_provider, chain_sync_network_handle)) .with_client(client.clone()) .build() diff --git a/client/network/src/service/tests/mod.rs b/client/network/src/service/tests/mod.rs index f8635e39e9..fa1486a791 100644 --- a/client/network/src/service/tests/mod.rs +++ b/client/network/src/service/tests/mod.rs @@ -21,7 +21,7 @@ use crate::{config, ChainSyncInterface, NetworkService, NetworkWorker}; use futures::prelude::*; use libp2p::Multiaddr; use sc_client_api::{BlockBackend, HeaderBackend}; -use sc_consensus::ImportQueue; +use sc_consensus::{ImportQueue, Link}; use sc_network_common::{ config::{ NonDefaultSetConfig, NonReservedPeerMode, NotificationHandshake, ProtocolId, SetConfig, @@ -93,6 +93,7 @@ impl TestNetwork { struct TestNetworkBuilder { import_queue: Option>>, + link: Option>>, client: Option>, listen_addresses: Vec, set_config: Option, @@ -106,6 +107,7 @@ impl TestNetworkBuilder { pub fn new(rt_handle: Handle) -> Self { Self { import_queue: None, + link: None, client: None, listen_addresses: Vec::new(), set_config: None, @@ -212,13 +214,14 @@ impl TestNetworkBuilder { } } - let import_queue = self.import_queue.unwrap_or(Box::new(sc_consensus::BasicQueue::new( - PassThroughVerifier(false), - Box::new(client.clone()), - None, - &sp_core::testing::TaskExecutor::new(), - None, - ))); + let mut import_queue = + self.import_queue.unwrap_or(Box::new(sc_consensus::BasicQueue::new( + PassThroughVerifier(false), + Box::new(client.clone()), + None, + &sp_core::testing::TaskExecutor::new(), + None, + ))); let protocol_id = ProtocolId::from("test-protocol-name"); let fork_id = Some(String::from("test-fork-id")); @@ -289,15 +292,23 @@ impl TestNetworkBuilder { Box::new(sp_consensus::block_validation::DefaultBlockAnnounceValidator), network_config.max_parallel_downloads, None, + None, chain_sync_network_handle, + import_queue.service(), block_request_protocol_config.name.clone(), state_request_protocol_config.name.clone(), None, ) .unwrap(); - (Box::new(chain_sync), chain_sync_service) + if let None = self.link { + self.link = Some(Box::new(chain_sync_service.clone())); + } + (Box::new(chain_sync), Box::new(chain_sync_service)) }); + let mut link = self + .link + .unwrap_or(Box::new(sc_network_sync::service::mock::MockChainSyncInterface::new())); let handle = self.rt_handle.clone(); let executor = move |f| { @@ -316,7 +327,6 @@ impl TestNetworkBuilder { chain: client.clone(), protocol_id, fork_id, - import_queue, chain_sync, chain_sync_service, metrics_registry: None, @@ -333,6 +343,16 @@ impl TestNetworkBuilder { self.rt_handle.spawn(async move { let _ = chain_sync_network_provider.run(service).await; }); + self.rt_handle.spawn(async move { + loop { + futures::future::poll_fn(|cx| { + import_queue.poll_actions(cx, &mut *link); + std::task::Poll::Ready(()) + }) + .await; + tokio::time::sleep(std::time::Duration::from_millis(250)).await; + } + }); TestNetwork::new(worker, self.rt_handle) } diff --git a/client/network/sync/Cargo.toml b/client/network/sync/Cargo.toml index 086ab3c30c..e29d804716 100644 --- a/client/network/sync/Cargo.toml +++ b/client/network/sync/Cargo.toml @@ -28,6 +28,7 @@ prost = "0.11" smallvec = "1.8.0" thiserror = "1.0" fork-tree = { version = "3.0.0", path = "../../../utils/fork-tree" } +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../../utils/prometheus" } sc-client-api = { version = "4.0.0-dev", path = "../../api" } sc-consensus = { version = "0.10.0-dev", path = "../../consensus/common" } sc-network-common = { version = "0.10.0-dev", path = "../common" } diff --git a/client/network/sync/src/lib.rs b/client/network/sync/src/lib.rs index 697445334a..75eda91219 100644 --- a/client/network/sync/src/lib.rs +++ b/client/network/sync/src/lib.rs @@ -54,9 +54,12 @@ use futures::{ }; use libp2p::{request_response::OutboundFailure, PeerId}; use log::{debug, error, info, trace, warn}; +use prometheus_endpoint::{register, Counter, PrometheusError, Registry, U64}; use prost::Message; use sc_client_api::{BlockBackend, ProofProvider}; -use sc_consensus::{BlockImportError, BlockImportStatus, IncomingBlock}; +use sc_consensus::{ + import_queue::ImportQueueService, BlockImportError, BlockImportStatus, IncomingBlock, +}; use sc_network_common::{ config::{ NonDefaultSetConfig, NonReservedPeerMode, NotificationHandshake, ProtocolId, SetConfig, @@ -71,8 +74,8 @@ use sc_network_common::{ warp::{EncodedProof, WarpProofRequest, WarpSyncPhase, WarpSyncProgress, WarpSyncProvider}, BadPeer, ChainSync as ChainSyncT, ImportResult, Metrics, OnBlockData, OnBlockJustification, OnStateData, OpaqueBlockRequest, OpaqueBlockResponse, OpaqueStateRequest, - OpaqueStateResponse, PeerInfo, PeerRequest, PollBlockAnnounceValidation, PollResult, - SyncMode, SyncState, SyncStatus, + OpaqueStateResponse, PeerInfo, PeerRequest, PollBlockAnnounceValidation, SyncMode, + SyncState, SyncStatus, }, }; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver}; @@ -233,6 +236,32 @@ impl Default for AllowedRequests { } } +struct SyncingMetrics { + pub import_queue_blocks_submitted: Counter, + pub import_queue_justifications_submitted: Counter, +} + +impl SyncingMetrics { + fn register(registry: &Registry) -> Result { + Ok(Self { + import_queue_blocks_submitted: register( + Counter::new( + "substrate_sync_import_queue_blocks_submitted", + "Number of blocks submitted to the import queue.", + )?, + registry, + )?, + import_queue_justifications_submitted: register( + Counter::new( + "substrate_sync_import_queue_justifications_submitted", + "Number of justifications submitted to the import queue.", + )?, + registry, + )?, + }) + } +} + struct GapSync { blocks: BlockCollection, best_queued_number: NumberFor, @@ -311,6 +340,10 @@ pub struct ChainSync { warp_sync_protocol_name: Option, /// Pending responses pending_responses: FuturesUnordered>, + /// Handle to import queue. + import_queue: Box>, + /// Metrics. + metrics: Option, } /// All the data we have about a Peer that we are trying to sync with @@ -961,6 +994,19 @@ where Ok(self.validate_and_queue_blocks(new_blocks, gap)) } + fn process_block_response_data(&mut self, blocks_to_import: Result, BadPeer>) { + match blocks_to_import { + Ok(OnBlockData::Import(origin, blocks)) => self.import_blocks(origin, blocks), + Ok(OnBlockData::Request(peer, req)) => self.send_block_request(peer, req), + Ok(OnBlockData::Continue) => {}, + Err(BadPeer(id, repu)) => { + self.network_service + .disconnect_peer(id, self.block_announce_protocol_name.clone()); + self.network_service.report_peer(id, repu); + }, + } + } + fn on_block_justification( &mut self, who: PeerId, @@ -1016,156 +1062,6 @@ where Ok(OnBlockJustification::Nothing) } - fn on_blocks_processed( - &mut self, - imported: usize, - count: usize, - results: Vec<(Result>, BlockImportError>, B::Hash)>, - ) -> Box), BadPeer>>> { - trace!(target: "sync", "Imported {} of {}", imported, count); - - let mut output = Vec::new(); - - let mut has_error = false; - for (_, hash) in &results { - self.queue_blocks.remove(hash); - self.blocks.clear_queued(hash); - if let Some(gap_sync) = &mut self.gap_sync { - gap_sync.blocks.clear_queued(hash); - } - } - for (result, hash) in results { - if has_error { - break - } - - if result.is_err() { - has_error = true; - } - - match result { - Ok(BlockImportStatus::ImportedKnown(number, who)) => - if let Some(peer) = who { - self.update_peer_common_number(&peer, number); - }, - Ok(BlockImportStatus::ImportedUnknown(number, aux, who)) => { - if aux.clear_justification_requests { - trace!( - target: "sync", - "Block imported clears all pending justification requests {}: {:?}", - number, - hash, - ); - self.clear_justification_requests(); - } - - if aux.needs_justification { - trace!( - target: "sync", - "Block imported but requires justification {}: {:?}", - number, - hash, - ); - self.request_justification(&hash, number); - } - - if aux.bad_justification { - if let Some(ref peer) = who { - warn!("💔 Sent block with bad justification to import"); - output.push(Err(BadPeer(*peer, rep::BAD_JUSTIFICATION))); - } - } - - if let Some(peer) = who { - self.update_peer_common_number(&peer, number); - } - let state_sync_complete = - self.state_sync.as_ref().map_or(false, |s| s.target() == hash); - if state_sync_complete { - info!( - target: "sync", - "State sync is complete ({} MiB), restarting block sync.", - self.state_sync.as_ref().map_or(0, |s| s.progress().size / (1024 * 1024)), - ); - self.state_sync = None; - self.mode = SyncMode::Full; - output.extend(self.restart()); - } - let warp_sync_complete = self - .warp_sync - .as_ref() - .map_or(false, |s| s.target_block_hash() == Some(hash)); - if warp_sync_complete { - info!( - target: "sync", - "Warp sync is complete ({} MiB), restarting block sync.", - self.warp_sync.as_ref().map_or(0, |s| s.progress().total_bytes / (1024 * 1024)), - ); - self.warp_sync = None; - self.mode = SyncMode::Full; - output.extend(self.restart()); - } - let gap_sync_complete = - self.gap_sync.as_ref().map_or(false, |s| s.target == number); - if gap_sync_complete { - info!( - target: "sync", - "Block history download is complete." - ); - self.gap_sync = None; - } - }, - Err(BlockImportError::IncompleteHeader(who)) => - if let Some(peer) = who { - warn!( - target: "sync", - "💔 Peer sent block with incomplete header to import", - ); - output.push(Err(BadPeer(peer, rep::INCOMPLETE_HEADER))); - output.extend(self.restart()); - }, - Err(BlockImportError::VerificationFailed(who, e)) => - if let Some(peer) = who { - warn!( - target: "sync", - "💔 Verification failed for block {:?} received from peer: {}, {:?}", - hash, - peer, - e, - ); - output.push(Err(BadPeer(peer, rep::VERIFICATION_FAIL))); - output.extend(self.restart()); - }, - Err(BlockImportError::BadBlock(who)) => - if let Some(peer) = who { - warn!( - target: "sync", - "💔 Block {:?} received from peer {} has been blacklisted", - hash, - peer, - ); - output.push(Err(BadPeer(peer, rep::BAD_BLOCK))); - }, - Err(BlockImportError::MissingState) => { - // This may happen if the chain we were requesting upon has been discarded - // in the meantime because other chain has been finalized. - // Don't mark it as bad as it still may be synced if explicitly requested. - trace!(target: "sync", "Obsolete block {:?}", hash); - }, - e @ Err(BlockImportError::UnknownParent) | e @ Err(BlockImportError::Other(_)) => { - warn!(target: "sync", "💔 Error importing block {:?}: {}", hash, e.unwrap_err()); - self.state_sync = None; - self.warp_sync = None; - output.extend(self.restart()); - }, - Err(BlockImportError::Cancelled) => {}, - }; - } - - self.allowed_requests.set_all(); - Box::new(output.into_iter()) - } - fn on_justification_import(&mut self, hash: B::Hash, number: NumberFor, success: bool) { let finalization_result = if success { Ok((hash, number)) } else { Err(()) }; self.extra_justifications @@ -1331,7 +1227,7 @@ where } } - fn peer_disconnected(&mut self, who: &PeerId) -> Option> { + fn peer_disconnected(&mut self, who: &PeerId) { self.blocks.clear_peer_download(who); if let Some(gap_sync) = &mut self.gap_sync { gap_sync.blocks.clear_peer_download(who) @@ -1343,8 +1239,13 @@ where target.peers.remove(who); !target.peers.is_empty() }); + let blocks = self.ready_blocks(); - (!blocks.is_empty()).then(|| self.validate_and_queue_blocks(blocks, false)) + if let Some(OnBlockData::Import(origin, blocks)) = + (!blocks.is_empty()).then(|| self.validate_and_queue_blocks(blocks, false)) + { + self.import_blocks(origin, blocks); + } } fn metrics(&self) -> Metrics { @@ -1421,22 +1322,56 @@ where .map_err(|error: codec::Error| error.to_string()) } - fn poll(&mut self, cx: &mut std::task::Context) -> Poll> { + fn poll( + &mut self, + cx: &mut std::task::Context, + ) -> Poll> { while let Poll::Ready(Some(event)) = self.service_rx.poll_next_unpin(cx) { match event { ToServiceCommand::SetSyncForkRequest(peers, hash, number) => { self.set_sync_fork_request(peers, &hash, number); }, + ToServiceCommand::RequestJustification(hash, number) => + self.request_justification(&hash, number), + ToServiceCommand::ClearJustificationRequests => self.clear_justification_requests(), + ToServiceCommand::BlocksProcessed(imported, count, results) => { + for result in self.on_blocks_processed(imported, count, results) { + match result { + Ok((id, req)) => self.send_block_request(id, req), + Err(BadPeer(id, repu)) => { + self.network_service + .disconnect_peer(id, self.block_announce_protocol_name.clone()); + self.network_service.report_peer(id, repu) + }, + } + } + }, + ToServiceCommand::JustificationImported(peer, hash, number, success) => { + self.on_justification_import(hash, number, success); + if !success { + info!(target: "sync", "💔 Invalid justification provided by {} for #{}", peer, hash); + self.network_service + .disconnect_peer(peer, self.block_announce_protocol_name.clone()); + self.network_service.report_peer( + peer, + sc_peerset::ReputationChange::new_fatal("Invalid justification"), + ); + } + }, } } self.process_outbound_requests(); - if let Poll::Ready(result) = self.poll_pending_responses(cx) { - return Poll::Ready(PollResult::Import(result)) + while let Poll::Ready(result) = self.poll_pending_responses(cx) { + match result { + ImportResult::BlockImport(origin, blocks) => self.import_blocks(origin, blocks), + ImportResult::JustificationImport(who, hash, number, justifications) => + self.import_justifications(who, hash, number, justifications), + } } if let Poll::Ready(announce) = self.poll_block_announce_validation(cx) { - return Poll::Ready(PollResult::Announce(announce)) + return Poll::Ready(announce) } Poll::Pending @@ -1494,11 +1429,13 @@ where block_announce_validator: Box + Send>, max_parallel_downloads: u32, warp_sync_provider: Option>>, + metrics_registry: Option<&Registry>, network_service: service::network::NetworkServiceHandle, + import_queue: Box>, block_request_protocol_name: ProtocolName, state_request_protocol_name: ProtocolName, warp_sync_protocol_name: Option, - ) -> Result<(Self, Box>, NonDefaultSetConfig), ClientError> { + ) -> Result<(Self, ChainSyncInterfaceHandle, NonDefaultSetConfig), ClientError> { let (tx, service_rx) = tracing_unbounded("mpsc_chain_sync"); let block_announce_config = Self::get_block_announce_proto_config( protocol_id, @@ -1544,10 +1481,22 @@ where .clone() .into(), pending_responses: Default::default(), + import_queue, + metrics: if let Some(r) = &metrics_registry { + match SyncingMetrics::register(r) { + Ok(metrics) => Some(metrics), + Err(err) => { + error!(target: "sync", "Failed to register metrics for ChainSync: {err:?}"); + None + }, + } + } else { + None + }, }; sync.reset_sync_start_point()?; - Ok((sync, Box::new(ChainSyncInterfaceHandle::new(tx)), block_announce_config)) + Ok((sync, ChainSyncInterfaceHandle::new(tx), block_announce_config)) } /// Returns the median seen block number. @@ -2173,8 +2122,10 @@ where if request.fields == BlockAttributes::JUSTIFICATION { match self.on_block_justification(peer_id, block_response) { Ok(OnBlockJustification::Nothing) => None, - Ok(OnBlockJustification::Import { peer, hash, number, justifications }) => - Some(ImportResult::JustificationImport(peer, hash, number, justifications)), + Ok(OnBlockJustification::Import { peer, hash, number, justifications }) => { + self.import_justifications(peer, hash, number, justifications); + None + }, Err(BadPeer(id, repu)) => { self.network_service .disconnect_peer(id, self.block_announce_protocol_name.clone()); @@ -2184,8 +2135,10 @@ where } } else { match self.on_block_data(&peer_id, Some(request), block_response) { - Ok(OnBlockData::Import(origin, blocks)) => - Some(ImportResult::BlockImport(origin, blocks)), + Ok(OnBlockData::Import(origin, blocks)) => { + self.import_blocks(origin, blocks); + None + }, Ok(OnBlockData::Request(peer, req)) => { self.send_block_request(peer, req); None @@ -2712,6 +2665,182 @@ where }, } } + + fn import_blocks(&mut self, origin: BlockOrigin, blocks: Vec>) { + if let Some(metrics) = &self.metrics { + metrics.import_queue_blocks_submitted.inc(); + } + + self.import_queue.import_blocks(origin, blocks); + } + + fn import_justifications( + &mut self, + peer: PeerId, + hash: B::Hash, + number: NumberFor, + justifications: Justifications, + ) { + if let Some(metrics) = &self.metrics { + metrics.import_queue_justifications_submitted.inc(); + } + + self.import_queue.import_justifications(peer, hash, number, justifications); + } + + /// A batch of blocks have been processed, with or without errors. + /// + /// Call this when a batch of blocks have been processed by the import + /// queue, with or without errors. + fn on_blocks_processed( + &mut self, + imported: usize, + count: usize, + results: Vec<(Result>, BlockImportError>, B::Hash)>, + ) -> Box), BadPeer>>> { + trace!(target: "sync", "Imported {} of {}", imported, count); + + let mut output = Vec::new(); + + let mut has_error = false; + for (_, hash) in &results { + self.queue_blocks.remove(hash); + self.blocks.clear_queued(hash); + if let Some(gap_sync) = &mut self.gap_sync { + gap_sync.blocks.clear_queued(hash); + } + } + for (result, hash) in results { + if has_error { + break + } + + if result.is_err() { + has_error = true; + } + + match result { + Ok(BlockImportStatus::ImportedKnown(number, who)) => + if let Some(peer) = who { + self.update_peer_common_number(&peer, number); + }, + Ok(BlockImportStatus::ImportedUnknown(number, aux, who)) => { + if aux.clear_justification_requests { + trace!( + target: "sync", + "Block imported clears all pending justification requests {}: {:?}", + number, + hash, + ); + self.clear_justification_requests(); + } + + if aux.needs_justification { + trace!( + target: "sync", + "Block imported but requires justification {}: {:?}", + number, + hash, + ); + self.request_justification(&hash, number); + } + + if aux.bad_justification { + if let Some(ref peer) = who { + warn!("💔 Sent block with bad justification to import"); + output.push(Err(BadPeer(*peer, rep::BAD_JUSTIFICATION))); + } + } + + if let Some(peer) = who { + self.update_peer_common_number(&peer, number); + } + let state_sync_complete = + self.state_sync.as_ref().map_or(false, |s| s.target() == hash); + if state_sync_complete { + info!( + target: "sync", + "State sync is complete ({} MiB), restarting block sync.", + self.state_sync.as_ref().map_or(0, |s| s.progress().size / (1024 * 1024)), + ); + self.state_sync = None; + self.mode = SyncMode::Full; + output.extend(self.restart()); + } + let warp_sync_complete = self + .warp_sync + .as_ref() + .map_or(false, |s| s.target_block_hash() == Some(hash)); + if warp_sync_complete { + info!( + target: "sync", + "Warp sync is complete ({} MiB), restarting block sync.", + self.warp_sync.as_ref().map_or(0, |s| s.progress().total_bytes / (1024 * 1024)), + ); + self.warp_sync = None; + self.mode = SyncMode::Full; + output.extend(self.restart()); + } + let gap_sync_complete = + self.gap_sync.as_ref().map_or(false, |s| s.target == number); + if gap_sync_complete { + info!( + target: "sync", + "Block history download is complete." + ); + self.gap_sync = None; + } + }, + Err(BlockImportError::IncompleteHeader(who)) => + if let Some(peer) = who { + warn!( + target: "sync", + "💔 Peer sent block with incomplete header to import", + ); + output.push(Err(BadPeer(peer, rep::INCOMPLETE_HEADER))); + output.extend(self.restart()); + }, + Err(BlockImportError::VerificationFailed(who, e)) => + if let Some(peer) = who { + warn!( + target: "sync", + "💔 Verification failed for block {:?} received from peer: {}, {:?}", + hash, + peer, + e, + ); + output.push(Err(BadPeer(peer, rep::VERIFICATION_FAIL))); + output.extend(self.restart()); + }, + Err(BlockImportError::BadBlock(who)) => + if let Some(peer) = who { + warn!( + target: "sync", + "💔 Block {:?} received from peer {} has been blacklisted", + hash, + peer, + ); + output.push(Err(BadPeer(peer, rep::BAD_BLOCK))); + }, + Err(BlockImportError::MissingState) => { + // This may happen if the chain we were requesting upon has been discarded + // in the meantime because other chain has been finalized. + // Don't mark it as bad as it still may be synced if explicitly requested. + trace!(target: "sync", "Obsolete block {:?}", hash); + }, + e @ Err(BlockImportError::UnknownParent) | e @ Err(BlockImportError::Other(_)) => { + warn!(target: "sync", "💔 Error importing block {:?}: {}", hash, e.unwrap_err()); + self.state_sync = None; + self.warp_sync = None; + output.extend(self.restart()); + }, + Err(BlockImportError::Cancelled) => {}, + }; + } + + self.allowed_requests.set_all(); + Box::new(output.into_iter()) + } } // This is purely during a backwards compatible transitionary period and should be removed @@ -3089,6 +3218,7 @@ mod test { let block_announce_validator = Box::new(DefaultBlockAnnounceValidator); let peer_id = PeerId::random(); + let import_queue = Box::new(sc_consensus::import_queue::mock::MockImportQueueHandle::new()); let (_chain_sync_network_provider, chain_sync_network_handle) = NetworkServiceProvider::new(); let (mut sync, _, _) = ChainSync::new( @@ -3100,7 +3230,9 @@ mod test { block_announce_validator, 1, None, + None, chain_sync_network_handle, + import_queue, ProtocolName::from("block-request"), ProtocolName::from("state-request"), None, @@ -3151,6 +3283,7 @@ mod test { #[test] fn restart_doesnt_affect_peers_downloading_finality_data() { let mut client = Arc::new(TestClientBuilder::new().build()); + let import_queue = Box::new(sc_consensus::import_queue::mock::MockImportQueueHandle::new()); let (_chain_sync_network_provider, chain_sync_network_handle) = NetworkServiceProvider::new(); @@ -3163,7 +3296,9 @@ mod test { Box::new(DefaultBlockAnnounceValidator), 1, None, + None, chain_sync_network_handle, + import_queue, ProtocolName::from("block-request"), ProtocolName::from("state-request"), None, @@ -3330,6 +3465,7 @@ mod test { sp_tracing::try_init_simple(); let mut client = Arc::new(TestClientBuilder::new().build()); + let import_queue = Box::new(sc_consensus::import_queue::mock::MockImportQueueHandle::new()); let (_chain_sync_network_provider, chain_sync_network_handle) = NetworkServiceProvider::new(); @@ -3342,7 +3478,9 @@ mod test { Box::new(DefaultBlockAnnounceValidator), 5, None, + None, chain_sync_network_handle, + import_queue, ProtocolName::from("block-request"), ProtocolName::from("state-request"), None, @@ -3453,6 +3591,7 @@ mod test { }; let mut client = Arc::new(TestClientBuilder::new().build()); + let import_queue = Box::new(sc_consensus::import_queue::mock::MockImportQueueHandle::new()); let (_chain_sync_network_provider, chain_sync_network_handle) = NetworkServiceProvider::new(); let info = client.info(); @@ -3466,7 +3605,9 @@ mod test { Box::new(DefaultBlockAnnounceValidator), 5, None, + None, chain_sync_network_handle, + import_queue, ProtocolName::from("block-request"), ProtocolName::from("state-request"), None, @@ -3584,6 +3725,7 @@ mod test { fn can_sync_huge_fork() { sp_tracing::try_init_simple(); + let import_queue = Box::new(sc_consensus::import_queue::mock::MockImportQueueHandle::new()); let (_chain_sync_network_provider, chain_sync_network_handle) = NetworkServiceProvider::new(); let mut client = Arc::new(TestClientBuilder::new().build()); @@ -3619,7 +3761,9 @@ mod test { Box::new(DefaultBlockAnnounceValidator), 5, None, + None, chain_sync_network_handle, + import_queue, ProtocolName::from("block-request"), ProtocolName::from("state-request"), None, @@ -3722,6 +3866,7 @@ mod test { fn syncs_fork_without_duplicate_requests() { sp_tracing::try_init_simple(); + let import_queue = Box::new(sc_consensus::import_queue::mock::MockImportQueueHandle::new()); let (_chain_sync_network_provider, chain_sync_network_handle) = NetworkServiceProvider::new(); let mut client = Arc::new(TestClientBuilder::new().build()); @@ -3757,7 +3902,9 @@ mod test { Box::new(DefaultBlockAnnounceValidator), 5, None, + None, chain_sync_network_handle, + import_queue, ProtocolName::from("block-request"), ProtocolName::from("state-request"), None, @@ -3881,6 +4028,7 @@ mod test { #[test] fn removes_target_fork_on_disconnect() { sp_tracing::try_init_simple(); + let import_queue = Box::new(sc_consensus::import_queue::mock::MockImportQueueHandle::new()); let (_chain_sync_network_provider, chain_sync_network_handle) = NetworkServiceProvider::new(); let mut client = Arc::new(TestClientBuilder::new().build()); @@ -3895,7 +4043,9 @@ mod test { Box::new(DefaultBlockAnnounceValidator), 1, None, + None, chain_sync_network_handle, + import_queue, ProtocolName::from("block-request"), ProtocolName::from("state-request"), None, @@ -3921,6 +4071,7 @@ mod test { #[test] fn can_import_response_with_missing_blocks() { sp_tracing::try_init_simple(); + let import_queue = Box::new(sc_consensus::import_queue::mock::MockImportQueueHandle::new()); let (_chain_sync_network_provider, chain_sync_network_handle) = NetworkServiceProvider::new(); let mut client2 = Arc::new(TestClientBuilder::new().build()); @@ -3937,7 +4088,9 @@ mod test { Box::new(DefaultBlockAnnounceValidator), 1, None, + None, chain_sync_network_handle, + import_queue, ProtocolName::from("block-request"), ProtocolName::from("state-request"), None, diff --git a/client/network/sync/src/mock.rs b/client/network/sync/src/mock.rs index 48d72c425b..b59ea7e4fe 100644 --- a/client/network/sync/src/mock.rs +++ b/client/network/sync/src/mock.rs @@ -21,11 +21,10 @@ use futures::task::Poll; use libp2p::PeerId; -use sc_consensus::{BlockImportError, BlockImportStatus}; use sc_network_common::sync::{ message::{BlockAnnounce, BlockData, BlockRequest, BlockResponse}, BadPeer, ChainSync as ChainSyncT, Metrics, OnBlockData, OnBlockJustification, - OpaqueBlockResponse, PeerInfo, PollBlockAnnounceValidation, PollResult, SyncStatus, + OpaqueBlockResponse, PeerInfo, PollBlockAnnounceValidation, SyncStatus, }; use sp_runtime::traits::{Block as BlockT, NumberFor}; @@ -60,17 +59,12 @@ mockall::mock! { request: Option>, response: BlockResponse, ) -> Result, BadPeer>; + fn process_block_response_data(&mut self, blocks_to_import: Result, BadPeer>); fn on_block_justification( &mut self, who: PeerId, response: BlockResponse, ) -> Result, BadPeer>; - fn on_blocks_processed( - &mut self, - imported: usize, - count: usize, - results: Vec<(Result>, BlockImportError>, Block::Hash)>, - ) -> Box), BadPeer>>>; fn on_justification_import( &mut self, hash: Block::Hash, @@ -89,7 +83,7 @@ mockall::mock! { &mut self, cx: &mut std::task::Context<'a>, ) -> Poll>; - fn peer_disconnected(&mut self, who: &PeerId) -> Option>; + fn peer_disconnected(&mut self, who: &PeerId); fn metrics(&self) -> Metrics; fn block_response_into_blocks( &self, @@ -99,7 +93,7 @@ mockall::mock! { fn poll<'a>( &mut self, cx: &mut std::task::Context<'a>, - ) -> Poll>; + ) -> Poll>; fn send_block_request( &mut self, who: PeerId, diff --git a/client/network/sync/src/service/chain_sync.rs b/client/network/sync/src/service/chain_sync.rs index cf07c65ee3..50ded5b643 100644 --- a/client/network/sync/src/service/chain_sync.rs +++ b/client/network/sync/src/service/chain_sync.rs @@ -17,6 +17,7 @@ // along with this program. If not, see . use libp2p::PeerId; +use sc_consensus::{BlockImportError, BlockImportStatus, JustificationSyncLink, Link}; use sc_network_common::service::NetworkSyncForkRequest; use sc_utils::mpsc::TracingUnboundedSender; use sp_runtime::traits::{Block as BlockT, NumberFor}; @@ -25,9 +26,18 @@ use sp_runtime::traits::{Block as BlockT, NumberFor}; #[derive(Debug)] pub enum ToServiceCommand { SetSyncForkRequest(Vec, B::Hash, NumberFor), + RequestJustification(B::Hash, NumberFor), + ClearJustificationRequests, + BlocksProcessed( + usize, + usize, + Vec<(Result>, BlockImportError>, B::Hash)>, + ), + JustificationImported(PeerId, B::Hash, NumberFor, bool), } /// Handle for communicating with `ChainSync` asynchronously +#[derive(Clone)] pub struct ChainSyncInterfaceHandle { tx: TracingUnboundedSender>, } @@ -56,3 +66,46 @@ impl NetworkSyncForkRequest> .unbounded_send(ToServiceCommand::SetSyncForkRequest(peers, hash, number)); } } + +impl JustificationSyncLink for ChainSyncInterfaceHandle { + /// Request a justification for the given block from the network. + /// + /// On success, the justification will be passed to the import queue that was part at + /// initialization as part of the configuration. + fn request_justification(&self, hash: &B::Hash, number: NumberFor) { + let _ = self.tx.unbounded_send(ToServiceCommand::RequestJustification(*hash, number)); + } + + fn clear_justification_requests(&self) { + let _ = self.tx.unbounded_send(ToServiceCommand::ClearJustificationRequests); + } +} + +impl Link for ChainSyncInterfaceHandle { + fn blocks_processed( + &mut self, + imported: usize, + count: usize, + results: Vec<(Result>, BlockImportError>, B::Hash)>, + ) { + let _ = self + .tx + .unbounded_send(ToServiceCommand::BlocksProcessed(imported, count, results)); + } + + fn justification_imported( + &mut self, + who: PeerId, + hash: &B::Hash, + number: NumberFor, + success: bool, + ) { + let _ = self + .tx + .unbounded_send(ToServiceCommand::JustificationImported(who, *hash, number, success)); + } + + fn request_justification(&mut self, hash: &B::Hash, number: NumberFor) { + let _ = self.tx.unbounded_send(ToServiceCommand::RequestJustification(*hash, number)); + } +} diff --git a/client/network/sync/src/service/mock.rs b/client/network/sync/src/service/mock.rs index c8a29e1fba..d8aad2fa7b 100644 --- a/client/network/sync/src/service/mock.rs +++ b/client/network/sync/src/service/mock.rs @@ -18,6 +18,7 @@ use futures::channel::oneshot; use libp2p::{Multiaddr, PeerId}; +use sc_consensus::{BlockImportError, BlockImportStatus}; use sc_network_common::{ config::MultiaddrWithPeerId, protocol::ProtocolName, @@ -29,13 +30,43 @@ use sp_runtime::traits::{Block as BlockT, NumberFor}; use std::collections::HashSet; mockall::mock! { - pub ChainSyncInterface {} + pub ChainSyncInterface { + pub fn justification_sync_link_request_justification(&self, hash: &B::Hash, number: NumberFor); + pub fn justification_sync_link_clear_justification_requests(&self); + } impl NetworkSyncForkRequest> for ChainSyncInterface { fn set_sync_fork_request(&self, peers: Vec, hash: B::Hash, number: NumberFor); } + + impl sc_consensus::Link for ChainSyncInterface { + fn blocks_processed( + &mut self, + imported: usize, + count: usize, + results: Vec<(Result>, BlockImportError>, B::Hash)>, + ); + fn justification_imported( + &mut self, + who: PeerId, + hash: &B::Hash, + number: NumberFor, + success: bool, + ); + fn request_justification(&mut self, hash: &B::Hash, number: NumberFor); + } +} + +impl sc_consensus::JustificationSyncLink for MockChainSyncInterface { + fn request_justification(&self, hash: &B::Hash, number: NumberFor) { + self.justification_sync_link_request_justification(hash, number); + } + + fn clear_justification_requests(&self) { + self.justification_sync_link_clear_justification_requests(); + } } mockall::mock! { diff --git a/client/network/sync/src/tests.rs b/client/network/sync/src/tests.rs index a03e657f03..61de08443a 100644 --- a/client/network/sync/src/tests.rs +++ b/client/network/sync/src/tests.rs @@ -37,6 +37,7 @@ use substrate_test_runtime_client::{TestClientBuilder, TestClientBuilderExt as _ // poll `ChainSync` and verify that a new sync fork request has been registered #[tokio::test] async fn delegate_to_chainsync() { + let import_queue = Box::new(sc_consensus::import_queue::mock::MockImportQueueHandle::new()); let (_chain_sync_network_provider, chain_sync_network_handle) = NetworkServiceProvider::new(); let (mut chain_sync, chain_sync_service, _) = ChainSync::new( sc_network_common::sync::SyncMode::Full, @@ -47,7 +48,9 @@ async fn delegate_to_chainsync() { Box::new(DefaultBlockAnnounceValidator), 1u32, None, + None, chain_sync_network_handle, + import_queue, ProtocolName::from("block-request"), ProtocolName::from("state-request"), None, diff --git a/client/network/test/src/lib.rs b/client/network/test/src/lib.rs index d3642e69cb..173ca81653 100644 --- a/client/network/test/src/lib.rs +++ b/client/network/test/src/lib.rs @@ -43,8 +43,8 @@ use sc_client_api::{ }; use sc_consensus::{ BasicQueue, BlockCheckParams, BlockImport, BlockImportParams, BoxJustificationImport, - ForkChoiceStrategy, ImportResult, JustificationImport, JustificationSyncLink, LongestChain, - Verifier, + ForkChoiceStrategy, ImportQueue, ImportResult, JustificationImport, JustificationSyncLink, + LongestChain, Verifier, }; use sc_network::{ config::{NetworkConfiguration, RequestResponseConfig, Role, SyncMode}, @@ -896,7 +896,9 @@ where block_announce_validator, network_config.max_parallel_downloads, Some(warp_sync), + None, chain_sync_network_handle, + import_queue.service(), block_request_protocol_config.name.clone(), state_request_protocol_config.name.clone(), Some(warp_protocol_config.name.clone()), @@ -915,9 +917,8 @@ where chain: client.clone(), protocol_id, fork_id, - import_queue, chain_sync: Box::new(chain_sync), - chain_sync_service, + chain_sync_service: Box::new(chain_sync_service.clone()), metrics_registry: None, block_announce_config, request_response_protocol_configs: [ @@ -936,6 +937,9 @@ where self.rt_handle().spawn(async move { chain_sync_network_provider.run(service).await; }); + self.rt_handle().spawn(async move { + import_queue.run(Box::new(chain_sync_service)).await; + }); self.mut_peers(move |peers| { for peer in peers.iter_mut() { diff --git a/client/service/src/builder.rs b/client/service/src/builder.rs index dd89ce6dff..7153672030 100644 --- a/client/service/src/builder.rs +++ b/client/service/src/builder.rs @@ -853,7 +853,9 @@ where block_announce_validator, config.network.max_parallel_downloads, warp_sync_provider, + config.prometheus_config.as_ref().map(|config| config.registry.clone()).as_ref(), chain_sync_network_handle, + import_queue.service(), block_request_protocol_config.name.clone(), state_request_protocol_config.name.clone(), warp_sync_protocol_config.as_ref().map(|config| config.name.clone()), @@ -877,9 +879,8 @@ where chain: client.clone(), protocol_id: protocol_id.clone(), fork_id: config.chain_spec.fork_id().map(ToOwned::to_owned), - import_queue: Box::new(import_queue), chain_sync: Box::new(chain_sync), - chain_sync_service, + chain_sync_service: Box::new(chain_sync_service.clone()), metrics_registry: config.prometheus_config.as_ref().map(|config| config.registry.clone()), block_announce_config, request_response_protocol_configs: request_response_protocol_configs @@ -925,6 +926,7 @@ where Some("networking"), chain_sync_network_provider.run(network.clone()), ); + spawn_handle.spawn("import-queue", None, import_queue.run(Box::new(chain_sync_service))); let (system_rpc_tx, system_rpc_rx) = tracing_unbounded("mpsc_system_rpc"); diff --git a/client/service/src/chain_ops/import_blocks.rs b/client/service/src/chain_ops/import_blocks.rs index c0612124dd..ca09c1658d 100644 --- a/client/service/src/chain_ops/import_blocks.rs +++ b/client/service/src/chain_ops/import_blocks.rs @@ -157,7 +157,7 @@ fn import_block_to_queue( let (header, extrinsics) = signed_block.block.deconstruct(); let hash = header.hash(); // import queue handles verification and importing it into the client. - queue.import_blocks( + queue.service_ref().import_blocks( BlockOrigin::File, vec![IncomingBlock:: { hash, From 15cfd9c5dbbfa1f3ed49623eb55b67354d1645e9 Mon Sep 17 00:00:00 2001 From: tgmichel Date: Fri, 9 Dec 2022 20:55:56 +0100 Subject: [PATCH 021/127] Trace response payload in default `jsonrpsee` middleware (#12886) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Trace result in default `jsonrpsee` middleware * `rpc_metrics::extra` Co-authored-by: Bastian Köcher Co-authored-by: Bastian Köcher --- client/rpc-servers/src/middleware.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/rpc-servers/src/middleware.rs b/client/rpc-servers/src/middleware.rs index 0d77442323..9e0d422b23 100644 --- a/client/rpc-servers/src/middleware.rs +++ b/client/rpc-servers/src/middleware.rs @@ -204,8 +204,9 @@ impl RpcMiddleware { } /// Called once the JSON-RPC request is finished and response is sent to the output buffer. - fn on_response(&self, _result: &str, started_at: std::time::Instant) { + fn on_response(&self, result: &str, started_at: std::time::Instant) { log::trace!(target: "rpc_metrics", "[{}] on_response started_at={:?}", self.transport_label, started_at); + log::trace!(target: "rpc_metrics::extra", "[{}] result={:?}", self.transport_label, result); self.metrics.requests_finished.with_label_values(&[self.transport_label]).inc(); } } From 33e6029e4c75bab850aac1d10925e2b327e9c4b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Sat, 10 Dec 2022 14:28:36 +0100 Subject: [PATCH 022/127] Ensure that we inform all tasks to stop before starting the 60 seconds shutdown (#12897) * Ensure that we inform all tasks to stop before starting the 60 seconds shutdown The change of waiting in maximum 60 seconds for the node to shutdown actually introduced a bug. We were actually waiting always 60 seconds as we didn't informed our tasks to shutdown. The solution to this problem is to drop the task manager as this will then inform all tasks to end. It also adds tests to ensure that the behaviors work as expected. (This should already have been done in the first pr! :() * ".git/.scripts/fmt.sh" 1 Co-authored-by: command-bot <> --- Cargo.lock | 1 + client/cli/Cargo.toml | 1 + client/cli/src/runner.rs | 211 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 213 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 8225e55714..110f6fcdc9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7227,6 +7227,7 @@ dependencies = [ "clap 4.0.11", "fdlimit", "futures", + "futures-timer", "libp2p", "log", "names", diff --git a/client/cli/Cargo.toml b/client/cli/Cargo.toml index 2f079a0c7c..fd84ff4d45 100644 --- a/client/cli/Cargo.toml +++ b/client/cli/Cargo.toml @@ -49,6 +49,7 @@ sp-version = { version = "5.0.0", path = "../../primitives/version" } [dev-dependencies] tempfile = "3.1.0" +futures-timer = "3.0.1" [features] default = ["rocksdb"] diff --git a/client/cli/src/runner.rs b/client/cli/src/runner.rs index c976c31970..d4191feddf 100644 --- a/client/cli/src/runner.rs +++ b/client/cli/src/runner.rs @@ -145,8 +145,14 @@ impl Runner { E: std::error::Error + Send + Sync + 'static + From, { self.print_node_infos(); + let mut task_manager = self.tokio_runtime.block_on(initialize(self.config))?; let res = self.tokio_runtime.block_on(main(task_manager.future().fuse())); + // We need to drop the task manager here to inform all tasks that they should shut down. + // + // This is important to be done before we instruct the tokio runtime to shutdown. Otherwise + // the tokio runtime will wait the full 60 seconds for all tasks to stop. + drop(task_manager); // Give all futures 60 seconds to shutdown, before tokio "leaks" them. self.tokio_runtime.shutdown_timeout(Duration::from_secs(60)); @@ -208,3 +214,208 @@ pub fn print_node_infos(config: &Configuration) { ); info!("⛓ Native runtime: {}", C::native_runtime_version(&config.chain_spec)); } + +#[cfg(test)] +mod tests { + use std::{ + path::PathBuf, + sync::atomic::{AtomicU64, Ordering}, + }; + + use sc_network::config::NetworkConfiguration; + use sc_service::{Arc, ChainType, GenericChainSpec, NoExtension}; + use sp_runtime::create_runtime_str; + use sp_version::create_apis_vec; + + use super::*; + + struct Cli; + + impl SubstrateCli for Cli { + fn author() -> String { + "test".into() + } + + fn impl_name() -> String { + "yep".into() + } + + fn impl_version() -> String { + "version".into() + } + + fn description() -> String { + "desc".into() + } + + fn support_url() -> String { + "no.pe".into() + } + + fn copyright_start_year() -> i32 { + 2042 + } + + fn load_spec( + &self, + _: &str, + ) -> std::result::Result, String> { + Err("nope".into()) + } + + fn native_runtime_version( + _: &Box, + ) -> &'static sp_version::RuntimeVersion { + const VERSION: sp_version::RuntimeVersion = sp_version::RuntimeVersion { + spec_name: create_runtime_str!("spec"), + impl_name: create_runtime_str!("name"), + authoring_version: 0, + spec_version: 0, + impl_version: 0, + apis: create_apis_vec!([]), + transaction_version: 2, + state_version: 0, + }; + + &VERSION + } + } + + fn create_runner() -> Runner { + let runtime = build_runtime().unwrap(); + + let runner = Runner::new( + Configuration { + impl_name: "spec".into(), + impl_version: "3".into(), + role: sc_service::Role::Authority, + tokio_handle: runtime.handle().clone(), + transaction_pool: Default::default(), + network: NetworkConfiguration::new_memory(), + keystore: sc_service::config::KeystoreConfig::InMemory, + keystore_remote: None, + database: sc_client_db::DatabaseSource::ParityDb { path: PathBuf::from("db") }, + trie_cache_maximum_size: None, + state_pruning: None, + blocks_pruning: sc_client_db::BlocksPruning::KeepAll, + chain_spec: Box::new(GenericChainSpec::from_genesis( + "test", + "test_id", + ChainType::Development, + || unimplemented!("Not required in tests"), + Vec::new(), + None, + None, + None, + None, + NoExtension::None, + )), + wasm_method: Default::default(), + wasm_runtime_overrides: None, + execution_strategies: Default::default(), + rpc_http: None, + rpc_ws: None, + rpc_ipc: None, + rpc_ws_max_connections: None, + rpc_cors: None, + rpc_methods: Default::default(), + rpc_max_payload: None, + rpc_max_request_size: None, + rpc_max_response_size: None, + rpc_id_provider: None, + rpc_max_subs_per_conn: None, + ws_max_out_buffer_capacity: None, + prometheus_config: None, + telemetry_endpoints: None, + default_heap_pages: None, + offchain_worker: Default::default(), + force_authoring: false, + disable_grandpa: false, + dev_key_seed: None, + tracing_targets: None, + tracing_receiver: Default::default(), + max_runtime_instances: 8, + announce_block: true, + base_path: None, + informant_output_format: Default::default(), + runtime_cache_size: 2, + }, + runtime, + ) + .unwrap(); + + runner + } + + #[test] + fn ensure_run_until_exit_informs_tasks_to_end() { + let runner = create_runner(); + + let counter = Arc::new(AtomicU64::new(0)); + let counter2 = counter.clone(); + + runner + .run_node_until_exit(move |cfg| async move { + let task_manager = TaskManager::new(cfg.tokio_handle.clone(), None).unwrap(); + let (sender, receiver) = futures::channel::oneshot::channel(); + + // We need to use `spawn_blocking` here so that we get a dedicated thread for our + // future. This is important for this test, as otherwise tokio can just "drop" the + // future. + task_manager.spawn_handle().spawn_blocking("test", None, async move { + let _ = sender.send(()); + loop { + counter2.fetch_add(1, Ordering::Relaxed); + futures_timer::Delay::new(Duration::from_millis(50)).await; + } + }); + + task_manager.spawn_essential_handle().spawn_blocking("test2", None, async { + // Let's stop this essential task directly when our other task started. + // It will signal that the task manager should end. + let _ = receiver.await; + }); + + Ok::<_, sc_service::Error>(task_manager) + }) + .unwrap_err(); + + let count = counter.load(Ordering::Relaxed); + + // Ensure that our counting task was running for less than 30 seconds. + // It should be directly killed, but for CI and whatever we are being a little bit more + // "relaxed". + assert!((count as u128) < (Duration::from_secs(30).as_millis() / 50)); + } + + /// This test ensures that `run_node_until_exit` aborts waiting for "stuck" tasks after 60 + /// seconds, aka doesn't wait until they are finished (which may never happen). + #[test] + fn ensure_run_until_exit_is_not_blocking_indefinitely() { + let runner = create_runner(); + + runner + .run_node_until_exit(move |cfg| async move { + let task_manager = TaskManager::new(cfg.tokio_handle.clone(), None).unwrap(); + let (sender, receiver) = futures::channel::oneshot::channel(); + + // We need to use `spawn_blocking` here so that we get a dedicated thread for our + // future. This future is more blocking code that will never end. + task_manager.spawn_handle().spawn_blocking("test", None, async move { + let _ = sender.send(()); + loop { + std::thread::sleep(Duration::from_secs(30)); + } + }); + + task_manager.spawn_essential_handle().spawn_blocking("test2", None, async { + // Let's stop this essential task directly when our other task started. + // It will signal that the task manager should end. + let _ = receiver.await; + }); + + Ok::<_, sc_service::Error>(task_manager) + }) + .unwrap_err(); + } +} From 2f0d59d766ff79ca2b552216a411c8e2b1762da4 Mon Sep 17 00:00:00 2001 From: Ankan <10196091+Ank4n@users.noreply.github.com> Date: Sat, 10 Dec 2022 23:52:23 +0100 Subject: [PATCH 023/127] Safe desired targets call (#12826) * checked call for desired targets * fix compile * fmt * fix tests * cleaner with and_then --- frame/election-provider-multi-phase/src/lib.rs | 10 +++++----- .../election-provider-multi-phase/src/signed.rs | 10 ++++++---- frame/election-provider-support/src/lib.rs | 16 +++++++--------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 2d49cd79db..cd70514fd3 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -1409,12 +1409,12 @@ impl Pallet { return Err(ElectionError::DataProvider("Snapshot too big for submission.")) } - let mut desired_targets = - T::DataProvider::desired_targets().map_err(ElectionError::DataProvider)?; + let mut desired_targets = as ElectionProviderBase>::desired_targets_checked() + .map_err(|e| ElectionError::DataProvider(e))?; - // If `desired_targets` > `targets.len()`, cap `desired_targets` to that - // level and emit a warning - let max_desired_targets: u32 = (targets.len() as u32).min(T::MaxWinners::get()); + // If `desired_targets` > `targets.len()`, cap `desired_targets` to that level and emit a + // warning + let max_desired_targets: u32 = targets.len() as u32; if desired_targets > max_desired_targets { log!( warn, diff --git a/frame/election-provider-multi-phase/src/signed.rs b/frame/election-provider-multi-phase/src/signed.rs index 9d629ad77f..12d39e83b6 100644 --- a/frame/election-provider-multi-phase/src/signed.rs +++ b/frame/election-provider-multi-phase/src/signed.rs @@ -594,10 +594,12 @@ mod tests { DesiredTargets::set(4); MaxWinners::set(3); - let (_, _, actual_desired_targets) = MultiPhase::create_snapshot_external().unwrap(); - - // snapshot is created with min of desired_targets and MaxWinners - assert_eq!(actual_desired_targets, 3); + // snapshot not created because data provider returned an unexpected number of + // desired_targets + assert_noop!( + MultiPhase::create_snapshot_external(), + ElectionError::DataProvider("desired_targets must not be greater than MaxWinners."), + ); }) } diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index 38924a18e2..8b26148844 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -386,15 +386,13 @@ pub trait ElectionProviderBase { /// checked call to `Self::DataProvider::desired_targets()` ensuring the value never exceeds /// [`Self::MaxWinners`]. fn desired_targets_checked() -> data_provider::Result { - match Self::DataProvider::desired_targets() { - Ok(desired_targets) => - if desired_targets <= Self::MaxWinners::get() { - Ok(desired_targets) - } else { - Err("desired_targets should not be greater than MaxWinners") - }, - Err(e) => Err(e), - } + Self::DataProvider::desired_targets().and_then(|desired_targets| { + if desired_targets <= Self::MaxWinners::get() { + Ok(desired_targets) + } else { + Err("desired_targets must not be greater than MaxWinners.") + } + }) } } From 0ba5206f670d7029dcab20d4a6df05e796fe6716 Mon Sep 17 00:00:00 2001 From: Luke Schoen Date: Mon, 12 Dec 2022 09:10:18 +1100 Subject: [PATCH 024/127] Fix typo (#12900) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 02f8a7591a..7d8c7e5755 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Then try out one of the [tutorials](https://docs.substrate.io/tutorials/). ## Community & Support -Join the highly active and supportive community on the [Susbstrate Stack Exchange](https://substrate.stackexchange.com/) to ask questions about use and problems you run into using this software. +Join the highly active and supportive community on the [Substrate Stack Exchange](https://substrate.stackexchange.com/) to ask questions about use and problems you run into using this software. Please do report bugs and [issues here](https://github.com/paritytech/substrate/issues) for anything you suspect requires action in the source. ## Contributions & Code of Conduct From 9772209ee962fe36c3502afb638e1bd835c23d9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Sun, 11 Dec 2022 23:18:59 +0100 Subject: [PATCH 025/127] ValidateUnsigned: Improve docs. (#12870) * ValidateUnsigned: Improve docs. * Review comments --- primitives/runtime/src/traits.rs | 33 ++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/primitives/runtime/src/traits.rs b/primitives/runtime/src/traits.rs index 6af711cba8..c69f8616b4 100644 --- a/primitives/runtime/src/traits.rs +++ b/primitives/runtime/src/traits.rs @@ -1335,12 +1335,13 @@ pub trait GetNodeBlockType { type NodeBlock: self::Block; } -/// Something that can validate unsigned extrinsics for the transaction pool. +/// Provide validation for unsigned extrinsics. /// -/// Note that any checks done here are only used for determining the validity of -/// the transaction for the transaction pool. -/// During block execution phase one need to perform the same checks anyway, -/// since this function is not being called. +/// This trait provides two functions [`pre_dispatch`](Self::pre_dispatch) and +/// [`validate_unsigned`](Self::validate_unsigned). The [`pre_dispatch`](Self::pre_dispatch) +/// function is called right before dispatching the call wrapped by an unsigned extrinsic. The +/// [`validate_unsigned`](Self::validate_unsigned) function is mainly being used in the context of +/// the transaction pool to check the validity of the call wrapped by an unsigned extrinsic. pub trait ValidateUnsigned { /// The call to validate type Call; @@ -1348,13 +1349,15 @@ pub trait ValidateUnsigned { /// Validate the call right before dispatch. /// /// This method should be used to prevent transactions already in the pool - /// (i.e. passing `validate_unsigned`) from being included in blocks - /// in case we know they now became invalid. + /// (i.e. passing [`validate_unsigned`](Self::validate_unsigned)) from being included in blocks + /// in case they became invalid since being added to the pool. /// - /// By default it's a good idea to call `validate_unsigned` from within - /// this function again to make sure we never include an invalid transaction. + /// By default it's a good idea to call [`validate_unsigned`](Self::validate_unsigned) from + /// within this function again to make sure we never include an invalid transaction. Otherwise + /// the implementation of the call or this method will need to provide proper validation to + /// ensure that the transaction is valid. /// - /// Changes made to storage WILL be persisted if the call returns `Ok`. + /// Changes made to storage *WILL* be persisted if the call returns `Ok`. fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> { Self::validate_unsigned(TransactionSource::InBlock, call) .map(|_| ()) @@ -1363,8 +1366,14 @@ pub trait ValidateUnsigned { /// Return the validity of the call /// - /// This doesn't execute any side-effects; it merely checks - /// whether the transaction would panic if it were included or not. + /// This method has no side-effects. It merely checks whether the call would be rejected + /// by the runtime in an unsigned extrinsic. + /// + /// The validity checks should be as lightweight as possible because every node will execute + /// this code before the unsigned extrinsic enters the transaction pool and also periodically + /// afterwards to ensure the validity. To prevent dos-ing a network with unsigned + /// extrinsics, these validity checks should include some checks around uniqueness, for example, + /// like checking that the unsigned extrinsic was send by an authority in the active set. /// /// Changes made to storage should be discarded by caller. fn validate_unsigned(source: TransactionSource, call: &Self::Call) -> TransactionValidity; From 06090ab35255833d892016dfc74fdc9d2d2fe06f Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 12 Dec 2022 11:32:55 +0100 Subject: [PATCH 026/127] rpc server with HTTP/WS on the same socket (#12663) * jsonrpsee v0.16 add backwards compatibility run old http server on http only * cargo fmt * update jsonrpsee 0.16.1 * less verbose cors log * fix nit in log: WS -> HTTP * revert needless changes in Cargo.lock * remove unused features in tower * fix nits; add client-core feature * jsonrpsee v0.16.2 --- Cargo.lock | 165 ++++++++++-------- bin/node-template/node/Cargo.toml | 2 +- bin/node/cli/Cargo.toml | 2 +- bin/node/rpc/Cargo.toml | 2 +- client/beefy/rpc/Cargo.toml | 2 +- client/beefy/rpc/src/lib.rs | 2 +- client/consensus/babe/rpc/Cargo.toml | 2 +- client/consensus/manual-seal/Cargo.toml | 2 +- client/finality-grandpa/rpc/Cargo.toml | 2 +- client/finality-grandpa/rpc/src/lib.rs | 2 +- client/merkle-mountain-range/rpc/Cargo.toml | 2 +- client/rpc-api/Cargo.toml | 2 +- client/rpc-servers/Cargo.toml | 5 +- client/rpc-servers/src/lib.rs | 142 ++++++++------- client/rpc-servers/src/middleware.rs | 130 +++++--------- client/rpc-spec-v2/Cargo.toml | 2 +- client/rpc-spec-v2/src/chain_spec/tests.rs | 2 +- client/rpc/Cargo.toml | 2 +- client/rpc/src/author/tests.rs | 2 +- client/rpc/src/chain/tests.rs | 2 +- client/rpc/src/state/mod.rs | 3 +- client/rpc/src/state/tests.rs | 2 +- client/rpc/src/system/tests.rs | 2 +- client/service/Cargo.toml | 2 +- client/service/src/lib.rs | 50 ++---- client/sync-state-rpc/Cargo.toml | 2 +- frame/transaction-payment/rpc/Cargo.toml | 2 +- utils/frame/remote-externalities/src/lib.rs | 120 ++++++++----- utils/frame/rpc/client/Cargo.toml | 2 +- utils/frame/rpc/client/src/lib.rs | 5 +- .../rpc/state-trie-migration-rpc/Cargo.toml | 2 +- utils/frame/rpc/support/Cargo.toml | 4 +- utils/frame/rpc/system/Cargo.toml | 2 +- 33 files changed, 355 insertions(+), 317 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 110f6fcdc9..2f2b4aa7cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -288,7 +288,7 @@ dependencies = [ "futures-sink", "futures-util", "memchr", - "pin-project-lite 0.2.6", + "pin-project-lite 0.2.9", ] [[package]] @@ -2293,7 +2293,7 @@ dependencies = [ "futures-io", "memchr", "parking", - "pin-project-lite 0.2.6", + "pin-project-lite 0.2.9", "waker-fn", ] @@ -2350,7 +2350,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project-lite 0.2.6", + "pin-project-lite 0.2.9", "pin-utils", "slab", ] @@ -2667,15 +2667,21 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.2" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60daa14be0e0786db0f03a9e57cb404c9d756eed2b6c62b9ea98ec5743ec75a9" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", "http", - "pin-project-lite 0.2.6", + "pin-project-lite 0.2.9", ] +[[package]] +name = "http-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + [[package]] name = "httparse" version = "1.8.0" @@ -2710,7 +2716,7 @@ dependencies = [ "httparse", "httpdate", "itoa 1.0.4", - "pin-project-lite 0.2.6", + "pin-project-lite 0.2.9", "socket2", "tokio", "tower-service", @@ -2907,24 +2913,23 @@ dependencies = [ [[package]] name = "jsonrpsee" -version = "0.15.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bd0d559d5e679b1ab2f869b486a11182923863b1b3ee8b421763cdd707b783a" +checksum = "7d291e3a5818a2384645fd9756362e6d89cf0541b0b916fa7702ea4a9833608e" dependencies = [ "jsonrpsee-core", - "jsonrpsee-http-server", "jsonrpsee-proc-macros", + "jsonrpsee-server", "jsonrpsee-types", "jsonrpsee-ws-client", - "jsonrpsee-ws-server", "tracing", ] [[package]] name = "jsonrpsee-client-transport" -version = "0.15.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8752740ecd374bcbf8b69f3e80b0327942df76f793f8d4e60d3355650c31fb74" +checksum = "965de52763f2004bc91ac5bcec504192440f0b568a5d621c59d9dbd6f886c3fb" dependencies = [ "futures-util", "http", @@ -2943,9 +2948,9 @@ dependencies = [ [[package]] name = "jsonrpsee-core" -version = "0.15.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3dc3e9cf2ba50b7b1d7d76a667619f82846caa39e8e8daa8a4962d74acaddca" +checksum = "a4e70b4439a751a5de7dd5ed55eacff78ebf4ffe0fc009cb1ebb11417f5b536b" dependencies = [ "anyhow", "arrayvec 0.7.2", @@ -2956,10 +2961,8 @@ dependencies = [ "futures-timer", "futures-util", "globset", - "http", "hyper", "jsonrpsee-types", - "lazy_static", "parking_lot 0.12.1", "rand 0.8.5", "rustc-hash", @@ -2969,45 +2972,48 @@ dependencies = [ "thiserror", "tokio", "tracing", - "tracing-futures", - "unicase", ] [[package]] -name = "jsonrpsee-http-server" -version = "0.15.1" +name = "jsonrpsee-proc-macros" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baa6da1e4199c10d7b1d0a6e5e8bd8e55f351163b6f4b3cbb044672a69bd4c1c" +dependencies = [ + "heck", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "jsonrpsee-server" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03802f0373a38c2420c70b5144742d800b509e2937edc4afb116434f07120117" +checksum = "1fb69dad85df79527c019659a992498d03f8495390496da2f07e6c24c2b356fc" dependencies = [ "futures-channel", "futures-util", + "http", "hyper", "jsonrpsee-core", "jsonrpsee-types", "serde", "serde_json", + "soketto", "tokio", + "tokio-stream", + "tokio-util", + "tower", "tracing", - "tracing-futures", -] - -[[package]] -name = "jsonrpsee-proc-macros" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd67957d4280217247588ac86614ead007b301ca2fa9f19c19f880a536f029e3" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", ] [[package]] name = "jsonrpsee-types" -version = "0.15.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e290bba767401b646812f608c099b922d8142603c9e73a50fb192d3ac86f4a0d" +checksum = "5bd522fe1ce3702fd94812965d7bb7a3364b1c9aba743944c5a00529aae80f8c" dependencies = [ "anyhow", "beef", @@ -3019,9 +3025,9 @@ dependencies = [ [[package]] name = "jsonrpsee-ws-client" -version = "0.15.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ee5feddd5188e62ac08fcf0e56478138e581509d4730f3f7be9b57dd402a4ff" +checksum = "0b83daeecfc6517cfe210df24e570fb06213533dfb990318fae781f4c7119dd9" dependencies = [ "http", "jsonrpsee-client-transport", @@ -3029,26 +3035,6 @@ dependencies = [ "jsonrpsee-types", ] -[[package]] -name = "jsonrpsee-ws-server" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d488ba74fb369e5ab68926feb75a483458b88e768d44319f37e4ecad283c7325" -dependencies = [ - "futures-channel", - "futures-util", - "http", - "jsonrpsee-core", - "jsonrpsee-types", - "serde_json", - "soketto", - "tokio", - "tokio-stream", - "tokio-util", - "tracing", - "tracing-futures", -] - [[package]] name = "k256" version = "0.11.5" @@ -6334,9 +6320,9 @@ checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" [[package]] name = "pin-project-lite" -version = "0.2.6" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pin-utils" @@ -8110,11 +8096,14 @@ name = "sc-rpc-server" version = "4.0.0-dev" dependencies = [ "futures", + "http", "jsonrpsee", "log", "serde_json", "substrate-prometheus-endpoint", "tokio", + "tower", + "tower-http", ] [[package]] @@ -8822,6 +8811,7 @@ dependencies = [ "bytes", "flate2", "futures", + "http", "httparse", "log", "rand 0.8.5", @@ -10297,7 +10287,7 @@ dependencies = [ "mio", "num_cpus", "parking_lot 0.12.1", - "pin-project-lite 0.2.6", + "pin-project-lite 0.2.9", "signal-hook-registry", "socket2", "tokio-macros", @@ -10333,7 +10323,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b2f3f698253f03119ac0102beaa64f67a67e08074d03a22d18784104543727f" dependencies = [ "futures-core", - "pin-project-lite 0.2.6", + "pin-project-lite 0.2.9", "tokio", ] @@ -10360,7 +10350,7 @@ dependencies = [ "futures-core", "futures-io", "futures-sink", - "pin-project-lite 0.2.6", + "pin-project-lite 0.2.9", "tokio", "tracing", ] @@ -10374,6 +10364,41 @@ dependencies = [ "serde", ] +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite 0.2.9", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + [[package]] name = "tower-service" version = "0.3.1" @@ -10387,7 +10412,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" dependencies = [ "cfg-if", - "pin-project-lite 0.2.6", + "log", + "pin-project-lite 0.2.9", "tracing-attributes", "tracing-core", ] @@ -10657,15 +10683,6 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "unicase" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" -dependencies = [ - "version_check", -] - [[package]] name = "unicode-bidi" version = "0.3.4" diff --git a/bin/node-template/node/Cargo.toml b/bin/node-template/node/Cargo.toml index d609edc884..2ea841093d 100644 --- a/bin/node-template/node/Cargo.toml +++ b/bin/node-template/node/Cargo.toml @@ -43,7 +43,7 @@ frame-system = { version = "4.0.0-dev", path = "../../../frame/system" } pallet-transaction-payment = { version = "4.0.0-dev", default-features = false, path = "../../../frame/transaction-payment" } # These dependencies are used for the node template's RPCs -jsonrpsee = { version = "0.15.1", features = ["server"] } +jsonrpsee = { version = "0.16.2", features = ["server"] } sc-rpc = { version = "4.0.0-dev", path = "../../../client/rpc" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sc-rpc-api = { version = "0.10.0-dev", path = "../../../client/rpc-api" } diff --git a/bin/node/cli/Cargo.toml b/bin/node/cli/Cargo.toml index 2ca37e7feb..4ee4bcd033 100644 --- a/bin/node/cli/Cargo.toml +++ b/bin/node/cli/Cargo.toml @@ -39,7 +39,7 @@ array-bytes = "4.1" clap = { version = "4.0.9", features = ["derive"], optional = true } codec = { package = "parity-scale-codec", version = "3.0.0" } serde = { version = "1.0.136", features = ["derive"] } -jsonrpsee = { version = "0.15.1", features = ["server"] } +jsonrpsee = { version = "0.16.2", features = ["server"] } futures = "0.3.21" log = "0.4.17" rand = "0.8" diff --git a/bin/node/rpc/Cargo.toml b/bin/node/rpc/Cargo.toml index 9d28104136..f34922a287 100644 --- a/bin/node/rpc/Cargo.toml +++ b/bin/node/rpc/Cargo.toml @@ -13,7 +13,7 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { version = "0.15.1", features = ["server"] } +jsonrpsee = { version = "0.16.2", features = ["server"] } node-primitives = { version = "2.0.0", path = "../primitives" } pallet-transaction-payment-rpc = { version = "4.0.0-dev", path = "../../../frame/transaction-payment/rpc/" } mmr-rpc = { version = "4.0.0-dev", path = "../../../client/merkle-mountain-range/rpc/" } diff --git a/client/beefy/rpc/Cargo.toml b/client/beefy/rpc/Cargo.toml index d272258245..f5b5770153 100644 --- a/client/beefy/rpc/Cargo.toml +++ b/client/beefy/rpc/Cargo.toml @@ -11,7 +11,7 @@ homepage = "https://substrate.io" [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } futures = "0.3.21" -jsonrpsee = { version = "0.15.1", features = ["server", "macros"] } +jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } log = "0.4" parking_lot = "0.12.1" serde = { version = "1.0.136", features = ["derive"] } diff --git a/client/beefy/rpc/src/lib.rs b/client/beefy/rpc/src/lib.rs index d29ed433c3..59a133b862 100644 --- a/client/beefy/rpc/src/lib.rs +++ b/client/beefy/rpc/src/lib.rs @@ -172,7 +172,7 @@ mod tests { }; use beefy_primitives::{known_payloads, Payload, SignedCommitment}; use codec::{Decode, Encode}; - use jsonrpsee::{types::EmptyParams, RpcModule}; + use jsonrpsee::{types::EmptyServerParams as EmptyParams, RpcModule}; use sp_runtime::traits::{BlakeTwo256, Hash}; use substrate_test_runtime_client::runtime::Block; diff --git a/client/consensus/babe/rpc/Cargo.toml b/client/consensus/babe/rpc/Cargo.toml index d0a65a3fc3..4f5aaf8549 100644 --- a/client/consensus/babe/rpc/Cargo.toml +++ b/client/consensus/babe/rpc/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { version = "0.15.1", features = ["server", "macros"] } +jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } futures = "0.3.21" serde = { version = "1.0.136", features = ["derive"] } thiserror = "1.0" diff --git a/client/consensus/manual-seal/Cargo.toml b/client/consensus/manual-seal/Cargo.toml index cf151424c2..fb89445a97 100644 --- a/client/consensus/manual-seal/Cargo.toml +++ b/client/consensus/manual-seal/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { version = "0.15.1", features = ["server", "macros"] } +jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } assert_matches = "1.3.0" async-trait = "0.1.57" codec = { package = "parity-scale-codec", version = "3.0.0" } diff --git a/client/finality-grandpa/rpc/Cargo.toml b/client/finality-grandpa/rpc/Cargo.toml index 7be77c122b..252c5e3871 100644 --- a/client/finality-grandpa/rpc/Cargo.toml +++ b/client/finality-grandpa/rpc/Cargo.toml @@ -12,7 +12,7 @@ homepage = "https://substrate.io" [dependencies] finality-grandpa = { version = "0.16.0", features = ["derive-codec"] } futures = "0.3.16" -jsonrpsee = { version = "0.15.1", features = ["server", "macros"] } +jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } log = "0.4.8" parity-scale-codec = { version = "3.0.0", features = ["derive"] } serde = { version = "1.0.105", features = ["derive"] } diff --git a/client/finality-grandpa/rpc/src/lib.rs b/client/finality-grandpa/rpc/src/lib.rs index 85df72de77..dfdad666ba 100644 --- a/client/finality-grandpa/rpc/src/lib.rs +++ b/client/finality-grandpa/rpc/src/lib.rs @@ -138,7 +138,7 @@ mod tests { use std::{collections::HashSet, convert::TryInto, sync::Arc}; use jsonrpsee::{ - types::{EmptyParams, SubscriptionId}, + types::{EmptyServerParams as EmptyParams, SubscriptionId}, RpcModule, }; use parity_scale_codec::{Decode, Encode}; diff --git a/client/merkle-mountain-range/rpc/Cargo.toml b/client/merkle-mountain-range/rpc/Cargo.toml index ca14544000..dcc5e49c52 100644 --- a/client/merkle-mountain-range/rpc/Cargo.toml +++ b/client/merkle-mountain-range/rpc/Cargo.toml @@ -13,7 +13,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } -jsonrpsee = { version = "0.15.1", features = ["server", "macros"] } +jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } serde = { version = "1.0.136", features = ["derive"] } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } diff --git a/client/rpc-api/Cargo.toml b/client/rpc-api/Cargo.toml index cb82a3b267..c46488db2d 100644 --- a/client/rpc-api/Cargo.toml +++ b/client/rpc-api/Cargo.toml @@ -28,4 +28,4 @@ sp-rpc = { version = "6.0.0", path = "../../primitives/rpc" } sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } sp-version = { version = "5.0.0", path = "../../primitives/version" } -jsonrpsee = { version = "0.15.1", features = ["server", "macros"] } +jsonrpsee = { version = "0.16.2", features = ["server", "client-core", "macros"] } diff --git a/client/rpc-servers/Cargo.toml b/client/rpc-servers/Cargo.toml index a3e64c367a..b494749ffd 100644 --- a/client/rpc-servers/Cargo.toml +++ b/client/rpc-servers/Cargo.toml @@ -14,8 +14,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] futures = "0.3.21" -jsonrpsee = { version = "0.15.1", features = ["server"] } +jsonrpsee = { version = "0.16.2", features = ["server"] } log = "0.4.17" serde_json = "1.0.85" tokio = { version = "1.22.0", features = ["parking_lot"] } prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } +tower-http = { version = "0.3.4", features = ["cors"] } +tower = "0.4.13" +http = "0.2.8" diff --git a/client/rpc-servers/src/lib.rs b/client/rpc-servers/src/lib.rs index 7eb825e169..1fa2ba81d8 100644 --- a/client/rpc-servers/src/lib.rs +++ b/client/rpc-servers/src/lib.rs @@ -21,17 +21,21 @@ #![warn(missing_docs)] use jsonrpsee::{ - http_server::{AccessControlBuilder, HttpServerBuilder, HttpServerHandle}, - ws_server::{WsServerBuilder, WsServerHandle}, + server::{ + middleware::proxy_get_request::ProxyGetRequestLayer, AllowHosts, ServerBuilder, + ServerHandle, + }, RpcModule, }; use std::{error::Error as StdError, net::SocketAddr}; -pub use crate::middleware::{RpcMetrics, RpcMiddleware}; +pub use crate::middleware::RpcMetrics; +use http::header::HeaderValue; pub use jsonrpsee::core::{ id_providers::{RandomIntegerIdProvider, RandomStringIdProvider}, traits::IdProvider, }; +use tower_http::cors::{AllowOrigin, CorsLayer}; const MEGABYTE: usize = 1024 * 1024; @@ -46,12 +50,11 @@ const WS_MAX_SUBS_PER_CONN: usize = 1024; pub mod middleware; -/// Type alias for http server -pub type HttpServer = HttpServerHandle; -/// Type alias for ws server -pub type WsServer = WsServerHandle; +/// Type alias JSON-RPC server +pub type Server = ServerHandle; -/// WebSocket specific settings on the server. +/// Server config. +#[derive(Debug, Clone)] pub struct WsConfig { /// Maximum connections. pub max_connections: Option, @@ -67,8 +70,8 @@ impl WsConfig { // Deconstructs the config to get the finalized inner values. // // `Payload size` or `max subs per connection` bigger than u32::MAX will be truncated. - fn deconstruct(self) -> (u32, u32, u64, u32) { - let max_conns = self.max_connections.unwrap_or(WS_MAX_CONNECTIONS) as u64; + fn deconstruct(self) -> (u32, u32, u32, u32) { + let max_conns = self.max_connections.unwrap_or(WS_MAX_CONNECTIONS) as u32; let max_payload_in_mb = payload_size_or_default(self.max_payload_in_mb) as u32; let max_payload_out_mb = payload_size_or_default(self.max_payload_out_mb) as u32; let max_subs_per_conn = self.max_subs_per_conn.unwrap_or(WS_MAX_SUBS_PER_CONN) as u32; @@ -86,31 +89,27 @@ pub async fn start_http( metrics: Option, rpc_api: RpcModule, rt: tokio::runtime::Handle, -) -> Result> { - let max_payload_in = payload_size_or_default(max_payload_in_mb); - let max_payload_out = payload_size_or_default(max_payload_out_mb); +) -> Result> { + let max_payload_in = payload_size_or_default(max_payload_in_mb) as u32; + let max_payload_out = payload_size_or_default(max_payload_out_mb) as u32; + let host_filter = hosts_filter(cors.is_some(), &addrs); - let mut acl = AccessControlBuilder::new(); + let middleware = tower::ServiceBuilder::new() + // Proxy `GET /health` requests to internal `system_health` method. + .layer(ProxyGetRequestLayer::new("/health", "system_health")?) + .layer(try_into_cors(cors)?); - if let Some(cors) = cors { - // Whitelist listening address. - // NOTE: set_allowed_hosts will whitelist both ports but only one will used. - acl = acl.set_allowed_hosts(format_allowed_hosts(&addrs[..]))?; - acl = acl.set_allowed_origins(cors)?; - }; - - let builder = HttpServerBuilder::new() - .max_request_body_size(max_payload_in as u32) - .max_response_body_size(max_payload_out as u32) - .set_access_control(acl.build()) - .health_api("/health", "system_health")? - .custom_tokio_runtime(rt); + let builder = ServerBuilder::new() + .max_request_body_size(max_payload_in) + .max_response_body_size(max_payload_out) + .set_host_filtering(host_filter) + .set_middleware(middleware) + .custom_tokio_runtime(rt) + .http_only(); let rpc_api = build_rpc_api(rpc_api); let (handle, addr) = if let Some(metrics) = metrics { - let middleware = RpcMiddleware::new(metrics, "http".into()); - let builder = builder.set_middleware(middleware); - let server = builder.build(&addrs[..]).await?; + let server = builder.set_logger(metrics).build(&addrs[..]).await?; let addr = server.local_addr(); (server.start(rpc_api)?, addr) } else { @@ -120,16 +119,16 @@ pub async fn start_http( }; log::info!( - "Running JSON-RPC HTTP server: addr={}, allowed origins={:?}", + "Running JSON-RPC HTTP server: addr={}, allowed origins={}", addr.map_or_else(|_| "unknown".to_string(), |a| a.to_string()), - cors + format_cors(cors) ); Ok(handle) } -/// Start WS server listening on given address. -pub async fn start_ws( +/// Start a JSON-RPC server listening on given address that supports both HTTP and WS. +pub async fn start( addrs: [SocketAddr; 2], cors: Option<&Vec>, ws_config: WsConfig, @@ -137,27 +136,26 @@ pub async fn start_ws( rpc_api: RpcModule, rt: tokio::runtime::Handle, id_provider: Option>, -) -> Result> { +) -> Result> { let (max_payload_in, max_payload_out, max_connections, max_subs_per_conn) = ws_config.deconstruct(); - let mut acl = AccessControlBuilder::new(); + let host_filter = hosts_filter(cors.is_some(), &addrs); - if let Some(cors) = cors { - // Whitelist listening address. - // NOTE: set_allowed_hosts will whitelist both ports but only one will used. - acl = acl.set_allowed_hosts(format_allowed_hosts(&addrs[..]))?; - acl = acl.set_allowed_origins(cors)?; - }; + let middleware = tower::ServiceBuilder::new() + // Proxy `GET /health` requests to internal `system_health` method. + .layer(ProxyGetRequestLayer::new("/health", "system_health")?) + .layer(try_into_cors(cors)?); - let mut builder = WsServerBuilder::new() + let mut builder = ServerBuilder::new() .max_request_body_size(max_payload_in) .max_response_body_size(max_payload_out) .max_connections(max_connections) .max_subscriptions_per_connection(max_subs_per_conn) .ping_interval(std::time::Duration::from_secs(30)) - .custom_tokio_runtime(rt) - .set_access_control(acl.build()); + .set_host_filtering(host_filter) + .set_middleware(middleware) + .custom_tokio_runtime(rt); if let Some(provider) = id_provider { builder = builder.set_id_provider(provider); @@ -167,9 +165,7 @@ pub async fn start_ws( let rpc_api = build_rpc_api(rpc_api); let (handle, addr) = if let Some(metrics) = metrics { - let middleware = RpcMiddleware::new(metrics, "ws".into()); - let builder = builder.set_middleware(middleware); - let server = builder.build(&addrs[..]).await?; + let server = builder.set_logger(metrics).build(&addrs[..]).await?; let addr = server.local_addr(); (server.start(rpc_api)?, addr) } else { @@ -179,23 +175,14 @@ pub async fn start_ws( }; log::info!( - "Running JSON-RPC WS server: addr={}, allowed origins={:?}", + "Running JSON-RPC WS server: addr={}, allowed origins={}", addr.map_or_else(|_| "unknown".to_string(), |a| a.to_string()), - cors + format_cors(cors) ); Ok(handle) } -fn format_allowed_hosts(addrs: &[SocketAddr]) -> Vec { - let mut hosts = Vec::with_capacity(addrs.len() * 2); - for addr in addrs { - hosts.push(format!("localhost:{}", addr.port())); - hosts.push(format!("127.0.0.1:{}", addr.port())); - } - hosts -} - fn build_rpc_api(mut rpc_api: RpcModule) -> RpcModule { let mut available_methods = rpc_api.method_names().collect::>(); available_methods.sort(); @@ -214,3 +201,40 @@ fn build_rpc_api(mut rpc_api: RpcModule) -> RpcModu fn payload_size_or_default(size_mb: Option) -> usize { size_mb.map_or(RPC_MAX_PAYLOAD_DEFAULT, |mb| mb.saturating_mul(MEGABYTE)) } + +fn hosts_filter(enabled: bool, addrs: &[SocketAddr]) -> AllowHosts { + if enabled { + // NOTE The listening addresses are whitelisted by default. + let mut hosts = Vec::with_capacity(addrs.len() * 2); + for addr in addrs { + hosts.push(format!("localhost:{}", addr.port()).into()); + hosts.push(format!("127.0.0.1:{}", addr.port()).into()); + } + AllowHosts::Only(hosts) + } else { + AllowHosts::Any + } +} + +fn try_into_cors( + maybe_cors: Option<&Vec>, +) -> Result> { + if let Some(cors) = maybe_cors { + let mut list = Vec::new(); + for origin in cors { + list.push(HeaderValue::from_str(origin)?); + } + Ok(CorsLayer::new().allow_origin(AllowOrigin::list(list))) + } else { + // allow all cors + Ok(CorsLayer::permissive()) + } +} + +fn format_cors(maybe_cors: Option<&Vec>) -> String { + if let Some(cors) = maybe_cors { + format!("{:?}", cors) + } else { + format!("{:?}", ["*"]) + } +} diff --git a/client/rpc-servers/src/middleware.rs b/client/rpc-servers/src/middleware.rs index 9e0d422b23..1c25ac1dfd 100644 --- a/client/rpc-servers/src/middleware.rs +++ b/client/rpc-servers/src/middleware.rs @@ -16,9 +16,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! RPC middlware to collect prometheus metrics on RPC calls. +//! RPC middleware to collect prometheus metrics on RPC calls. -use jsonrpsee::core::middleware::{Headers, HttpMiddleware, MethodKind, Params, WsMiddleware}; +use jsonrpsee::server::logger::{HttpRequest, Logger, MethodKind, Params, TransportProtocol}; use prometheus_endpoint::{ register, Counter, CounterVec, HistogramOpts, HistogramVec, Opts, PrometheusError, Registry, U64, @@ -54,9 +54,9 @@ pub struct RpcMetrics { calls_started: CounterVec, /// Number of calls completed. calls_finished: CounterVec, - /// Number of Websocket sessions opened (Websocket only). + /// Number of Websocket sessions opened. ws_sessions_opened: Option>, - /// Number of Websocket sessions closed (Websocket only). + /// Number of Websocket sessions closed. ws_sessions_closed: Option>, } @@ -139,62 +139,61 @@ impl RpcMetrics { } } -#[derive(Clone)] -/// Middleware for RPC calls -pub struct RpcMiddleware { - metrics: RpcMetrics, - transport_label: &'static str, -} +impl Logger for RpcMetrics { + type Instant = std::time::Instant; -impl RpcMiddleware { - /// Create a new [`RpcMiddleware`] with the provided [`RpcMetrics`]. - pub fn new(metrics: RpcMetrics, transport_label: &'static str) -> Self { - Self { metrics, transport_label } + fn on_connect( + &self, + _remote_addr: SocketAddr, + _request: &HttpRequest, + transport: TransportProtocol, + ) { + if let TransportProtocol::WebSocket = transport { + self.ws_sessions_opened.as_ref().map(|counter| counter.inc()); + } } - /// Called when a new JSON-RPC request comes to the server. - fn on_request(&self) -> std::time::Instant { + fn on_request(&self, transport: TransportProtocol) -> Self::Instant { + let transport_label = transport_label_str(transport); let now = std::time::Instant::now(); - self.metrics.requests_started.with_label_values(&[self.transport_label]).inc(); + self.requests_started.with_label_values(&[transport_label]).inc(); now } - /// Called on each JSON-RPC method call, batch requests will trigger `on_call` multiple times. - fn on_call(&self, name: &str, params: Params, kind: MethodKind) { + fn on_call(&self, name: &str, params: Params, kind: MethodKind, transport: TransportProtocol) { + let transport_label = transport_label_str(transport); log::trace!( target: "rpc_metrics", "[{}] on_call name={} params={:?} kind={}", - self.transport_label, + transport_label, name, params, kind, ); - self.metrics - .calls_started - .with_label_values(&[self.transport_label, name]) - .inc(); + self.calls_started.with_label_values(&[transport_label, name]).inc(); } - /// Called on each JSON-RPC method completion, batch requests will trigger `on_result` multiple - /// times. - fn on_result(&self, name: &str, success: bool, started_at: std::time::Instant) { + fn on_result( + &self, + name: &str, + success: bool, + started_at: Self::Instant, + transport: TransportProtocol, + ) { + let transport_label = transport_label_str(transport); let micros = started_at.elapsed().as_micros(); log::debug!( target: "rpc_metrics", "[{}] {} call took {} μs", - self.transport_label, + transport_label, name, micros, ); - self.metrics - .calls_time - .with_label_values(&[self.transport_label, name]) - .observe(micros as _); + self.calls_time.with_label_values(&[transport_label, name]).observe(micros as _); - self.metrics - .calls_finished + self.calls_finished .with_label_values(&[ - self.transport_label, + transport_label, name, // the label "is_error", so `success` should be regarded as false // and vice-versa to be registrered correctly. @@ -203,58 +202,23 @@ impl RpcMiddleware { .inc(); } - /// Called once the JSON-RPC request is finished and response is sent to the output buffer. - fn on_response(&self, result: &str, started_at: std::time::Instant) { - log::trace!(target: "rpc_metrics", "[{}] on_response started_at={:?}", self.transport_label, started_at); - log::trace!(target: "rpc_metrics::extra", "[{}] result={:?}", self.transport_label, result); - self.metrics.requests_finished.with_label_values(&[self.transport_label]).inc(); - } -} - -impl WsMiddleware for RpcMiddleware { - type Instant = std::time::Instant; - - fn on_connect(&self, _remote_addr: SocketAddr, _headers: &Headers) { - self.metrics.ws_sessions_opened.as_ref().map(|counter| counter.inc()); - } - - fn on_request(&self) -> Self::Instant { - self.on_request() - } - - fn on_call(&self, name: &str, params: Params, kind: MethodKind) { - self.on_call(name, params, kind) + fn on_response(&self, result: &str, started_at: Self::Instant, transport: TransportProtocol) { + let transport_label = transport_label_str(transport); + log::trace!(target: "rpc_metrics", "[{}] on_response started_at={:?}", transport_label, started_at); + log::trace!(target: "rpc_metrics::extra", "[{}] result={:?}", transport_label, result); + self.requests_finished.with_label_values(&[transport_label]).inc(); } - fn on_result(&self, name: &str, success: bool, started_at: Self::Instant) { - self.on_result(name, success, started_at) - } - - fn on_response(&self, _result: &str, started_at: Self::Instant) { - self.on_response(_result, started_at) - } - - fn on_disconnect(&self, _remote_addr: SocketAddr) { - self.metrics.ws_sessions_closed.as_ref().map(|counter| counter.inc()); + fn on_disconnect(&self, _remote_addr: SocketAddr, transport: TransportProtocol) { + if let TransportProtocol::WebSocket = transport { + self.ws_sessions_closed.as_ref().map(|counter| counter.inc()); + } } } -impl HttpMiddleware for RpcMiddleware { - type Instant = std::time::Instant; - - fn on_request(&self, _remote_addr: SocketAddr, _headers: &Headers) -> Self::Instant { - self.on_request() - } - - fn on_call(&self, name: &str, params: Params, kind: MethodKind) { - self.on_call(name, params, kind) - } - - fn on_result(&self, name: &str, success: bool, started_at: Self::Instant) { - self.on_result(name, success, started_at) - } - - fn on_response(&self, _result: &str, started_at: Self::Instant) { - self.on_response(_result, started_at) +fn transport_label_str(t: TransportProtocol) -> &'static str { + match t { + TransportProtocol::Http => "http", + TransportProtocol::WebSocket => "ws", } } diff --git a/client/rpc-spec-v2/Cargo.toml b/client/rpc-spec-v2/Cargo.toml index a0ae303837..930aeb4bd8 100644 --- a/client/rpc-spec-v2/Cargo.toml +++ b/client/rpc-spec-v2/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { version = "0.15.1", features = ["server", "macros"] } +jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } # Internal chain structures for "chain_spec". sc-chain-spec = { version = "4.0.0-dev", path = "../chain-spec" } # Pool for submitting extrinsics required by "transaction" diff --git a/client/rpc-spec-v2/src/chain_spec/tests.rs b/client/rpc-spec-v2/src/chain_spec/tests.rs index 6c078b2974..6f662ba422 100644 --- a/client/rpc-spec-v2/src/chain_spec/tests.rs +++ b/client/rpc-spec-v2/src/chain_spec/tests.rs @@ -17,7 +17,7 @@ // along with this program. If not, see . use super::*; -use jsonrpsee::{types::EmptyParams, RpcModule}; +use jsonrpsee::{types::EmptyServerParams as EmptyParams, RpcModule}; use sc_chain_spec::Properties; const CHAIN_NAME: &'static str = "TEST_CHAIN_NAME"; diff --git a/client/rpc/Cargo.toml b/client/rpc/Cargo.toml index d690e2c7b4..a241807cc2 100644 --- a/client/rpc/Cargo.toml +++ b/client/rpc/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.21" hash-db = { version = "0.15.2", default-features = false } -jsonrpsee = { version = "0.15.1", features = ["server"] } +jsonrpsee = { version = "0.16.2", features = ["server"] } lazy_static = { version = "1.4.0", optional = true } log = "0.4.17" parking_lot = "0.12.1" diff --git a/client/rpc/src/author/tests.rs b/client/rpc/src/author/tests.rs index f969812e5b..573d01630d 100644 --- a/client/rpc/src/author/tests.rs +++ b/client/rpc/src/author/tests.rs @@ -23,7 +23,7 @@ use assert_matches::assert_matches; use codec::Encode; use jsonrpsee::{ core::Error as RpcError, - types::{error::CallError, EmptyParams}, + types::{error::CallError, EmptyServerParams as EmptyParams}, RpcModule, }; use sc_transaction_pool::{BasicPool, FullChainApi}; diff --git a/client/rpc/src/chain/tests.rs b/client/rpc/src/chain/tests.rs index 1e6dbd5aca..224d021f94 100644 --- a/client/rpc/src/chain/tests.rs +++ b/client/rpc/src/chain/tests.rs @@ -19,7 +19,7 @@ use super::*; use crate::testing::{test_executor, timeout_secs}; use assert_matches::assert_matches; -use jsonrpsee::types::EmptyParams; +use jsonrpsee::types::EmptyServerParams as EmptyParams; use sc_block_builder::BlockBuilderProvider; use sp_consensus::BlockOrigin; use sp_rpc::list::ListOrValue; diff --git a/client/rpc/src/state/mod.rs b/client/rpc/src/state/mod.rs index 7213e4360a..fd802e5a80 100644 --- a/client/rpc/src/state/mod.rs +++ b/client/rpc/src/state/mod.rs @@ -28,9 +28,8 @@ use std::sync::Arc; use crate::SubscriptionTaskExecutor; use jsonrpsee::{ - core::{Error as JsonRpseeError, RpcResult}, + core::{server::rpc_module::SubscriptionSink, Error as JsonRpseeError, RpcResult}, types::SubscriptionResult, - ws_server::SubscriptionSink, }; use sc_rpc_api::{state::ReadProof, DenyUnsafe}; diff --git a/client/rpc/src/state/tests.rs b/client/rpc/src/state/tests.rs index 53dd8ebf50..3ef59e5ca9 100644 --- a/client/rpc/src/state/tests.rs +++ b/client/rpc/src/state/tests.rs @@ -23,7 +23,7 @@ use assert_matches::assert_matches; use futures::executor; use jsonrpsee::{ core::Error as RpcError, - types::{error::CallError as RpcCallError, EmptyParams, ErrorObject}, + types::{error::CallError as RpcCallError, EmptyServerParams as EmptyParams, ErrorObject}, }; use sc_block_builder::BlockBuilderProvider; use sc_rpc_api::DenyUnsafe; diff --git a/client/rpc/src/system/tests.rs b/client/rpc/src/system/tests.rs index 2f91648008..00ab9c4686 100644 --- a/client/rpc/src/system/tests.rs +++ b/client/rpc/src/system/tests.rs @@ -21,7 +21,7 @@ use assert_matches::assert_matches; use futures::prelude::*; use jsonrpsee::{ core::Error as RpcError, - types::{error::CallError, EmptyParams}, + types::{error::CallError, EmptyServerParams as EmptyParams}, RpcModule, }; use sc_network::{self, config::Role, PeerId}; diff --git a/client/service/Cargo.toml b/client/service/Cargo.toml index 87949ef12d..4d1d267d45 100644 --- a/client/service/Cargo.toml +++ b/client/service/Cargo.toml @@ -22,7 +22,7 @@ test-helpers = [] runtime-benchmarks = ["sc-client-db/runtime-benchmarks"] [dependencies] -jsonrpsee = { version = "0.15.1", features = ["server"] } +jsonrpsee = { version = "0.16.2", features = ["server"] } thiserror = "1.0.30" futures = "0.3.21" rand = "0.7.3" diff --git a/client/service/src/lib.rs b/client/service/src/lib.rs index 091b4bbe9f..f0e3f72510 100644 --- a/client/service/src/lib.rs +++ b/client/service/src/lib.rs @@ -43,7 +43,6 @@ use log::{debug, error, warn}; use sc_client_api::{blockchain::HeaderBackend, BlockBackend, BlockchainEvents, ProofProvider}; use sc_network::PeerId; use sc_network_common::{config::MultiaddrWithPeerId, service::NetworkBlock}; -use sc_rpc_server::WsConfig; use sc_utils::mpsc::TracingUnboundedReceiver; use sp_blockchain::HeaderMetadata; use sp_consensus::SyncOracle; @@ -294,20 +293,9 @@ async fn build_network_future< // Wrapper for HTTP and WS servers that makes sure they are properly shut down. mod waiting { - pub struct HttpServer(pub Option); + pub struct Server(pub Option); - impl Drop for HttpServer { - 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(); - } - } - } - - pub struct WsServer(pub Option); - - impl Drop for WsServer { + 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. @@ -326,9 +314,6 @@ fn start_rpc_servers( where R: Fn(sc_rpc::DenyUnsafe) -> Result, Error>, { - let (max_request_size, ws_max_response_size, http_max_response_size) = - legacy_cli_parsing(config); - fn deny_unsafe(addr: SocketAddr, methods: &RpcMethods) -> sc_rpc::DenyUnsafe { let is_exposed_addr = !addr.ip().is_loopback(); match (is_exposed_addr, methods) { @@ -337,6 +322,9 @@ where } } + let (max_request_size, ws_max_response_size, http_max_response_size) = + legacy_cli_parsing(config); + let random_port = |mut addr: SocketAddr| { addr.set_port(0); addr @@ -346,6 +334,7 @@ where .rpc_ws .unwrap_or_else(|| "127.0.0.1:9944".parse().expect("valid sockaddr; qed")); let ws_addr2 = random_port(ws_addr); + let http_addr = config .rpc_http .unwrap_or_else(|| "127.0.0.1:9933".parse().expect("valid sockaddr; qed")); @@ -353,29 +342,29 @@ where let metrics = sc_rpc_server::RpcMetrics::new(config.prometheus_registry())?; + let server_config = sc_rpc_server::WsConfig { + max_connections: config.rpc_ws_max_connections, + max_payload_in_mb: max_request_size, + max_payload_out_mb: ws_max_response_size, + max_subs_per_conn: config.rpc_max_subs_per_conn, + }; + let http_fut = sc_rpc_server::start_http( [http_addr, http_addr2], config.rpc_cors.as_ref(), max_request_size, http_max_response_size, metrics.clone(), - gen_rpc_module(deny_unsafe(ws_addr, &config.rpc_methods))?, + gen_rpc_module(deny_unsafe(http_addr, &config.rpc_methods))?, config.tokio_handle.clone(), ); - let ws_config = WsConfig { - max_connections: config.rpc_ws_max_connections, - max_payload_in_mb: max_request_size, - max_payload_out_mb: ws_max_response_size, - max_subs_per_conn: config.rpc_max_subs_per_conn, - }; - - let ws_fut = sc_rpc_server::start_ws( + let ws_fut = sc_rpc_server::start( [ws_addr, ws_addr2], config.rpc_cors.as_ref(), - ws_config, - metrics, - gen_rpc_module(deny_unsafe(http_addr, &config.rpc_methods))?, + server_config.clone(), + metrics.clone(), + gen_rpc_module(deny_unsafe(ws_addr, &config.rpc_methods))?, config.tokio_handle.clone(), rpc_id_provider, ); @@ -383,8 +372,7 @@ where match tokio::task::block_in_place(|| { config.tokio_handle.block_on(futures::future::try_join(http_fut, ws_fut)) }) { - Ok((http, ws)) => - Ok(Box::new((waiting::HttpServer(Some(http)), waiting::WsServer(Some(ws))))), + Ok((http, ws)) => Ok(Box::new((waiting::Server(Some(http)), waiting::Server(Some(ws))))), Err(e) => Err(Error::Application(e)), } } diff --git a/client/sync-state-rpc/Cargo.toml b/client/sync-state-rpc/Cargo.toml index 9730eb56e9..a72b4106ba 100644 --- a/client/sync-state-rpc/Cargo.toml +++ b/client/sync-state-rpc/Cargo.toml @@ -13,7 +13,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } -jsonrpsee = { version = "0.15.1", features = ["server", "macros"] } +jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0.85" thiserror = "1.0.30" diff --git a/frame/transaction-payment/rpc/Cargo.toml b/frame/transaction-payment/rpc/Cargo.toml index 06dcaca937..b77143201f 100644 --- a/frame/transaction-payment/rpc/Cargo.toml +++ b/frame/transaction-payment/rpc/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } -jsonrpsee = { version = "0.15.1", features = ["server", "macros"] } +jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } pallet-transaction-payment-rpc-runtime-api = { version = "4.0.0-dev", path = "./runtime-api" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } diff --git a/utils/frame/remote-externalities/src/lib.rs b/utils/frame/remote-externalities/src/lib.rs index 86cfc767bf..4f95331c03 100644 --- a/utils/frame/remote-externalities/src/lib.rs +++ b/utils/frame/remote-externalities/src/lib.rs @@ -39,7 +39,9 @@ use std::{ path::{Path, PathBuf}, sync::Arc, }; -use substrate_rpc_client::{rpc_params, ws_client, ChainApi, ClientT, StateApi, WsClient}; +use substrate_rpc_client::{ + rpc_params, ws_client, BatchRequestBuilder, ChainApi, ClientT, StateApi, WsClient, +}; type KeyValue = (StorageKey, StorageData); type TopKeyValues = Vec; @@ -334,33 +336,50 @@ where log::debug!(target: LOG_TARGET, "Querying a total of {} keys", keys.len()); let mut key_values: Vec = vec![]; + let mut batch_success = true; + let client = self.as_online().rpc_client(); for chunk_keys in keys.chunks(BATCH_SIZE) { - let batch = chunk_keys - .iter() - .cloned() - .map(|key| ("state_getStorage", rpc_params![key, at])) - .collect::>(); - - let values = client.batch_request::>(batch).await.map_err(|e| { - log::error!( - target: LOG_TARGET, - "failed to execute batch: {:?}. Error: {:?}", - chunk_keys.iter().map(HexDisplay::from).collect::>(), - e - ); - "batch failed." - })?; + let mut batch = BatchRequestBuilder::new(); - assert_eq!(chunk_keys.len(), values.len()); + for key in chunk_keys.iter() { + batch + .insert("state_getStorage", rpc_params![key, at]) + .map_err(|_| "Invalid batch params")?; + } + + let batch_response = + client.batch_request::>(batch).await.map_err(|e| { + log::error!( + target: LOG_TARGET, + "failed to execute batch: {:?}. Error: {:?}", + chunk_keys.iter().map(HexDisplay::from).collect::>(), + e + ); + "batch failed." + })?; + + assert_eq!(chunk_keys.len(), batch_response.len()); + + for (key, maybe_value) in chunk_keys.into_iter().zip(batch_response) { + match maybe_value { + Ok(Some(v)) => { + key_values.push((key.clone(), v)); + }, + Ok(None) => { + log::warn!( + target: LOG_TARGET, + "key {:?} had none corresponding value.", + &key + ); + key_values.push((key.clone(), StorageData(vec![]))); + }, + Err(e) => { + log::error!(target: LOG_TARGET, "key {:?} failed: {:?}", &key, e); + batch_success = false; + }, + }; - for (idx, key) in chunk_keys.iter().enumerate() { - let maybe_value = values[idx].clone(); - let value = maybe_value.unwrap_or_else(|| { - log::warn!(target: LOG_TARGET, "key {:?} had none corresponding value.", &key); - StorageData(vec![]) - }); - key_values.push((key.clone(), value)); if key_values.len() % (10 * BATCH_SIZE) == 0 { let ratio: f64 = key_values.len() as f64 / keys_count as f64; log::debug!( @@ -374,7 +393,11 @@ where } } - Ok(key_values) + if batch_success { + Ok(key_values) + } else { + Err("batch failed.") + } } /// Get the values corresponding to `child_keys` at the given `prefixed_top_key`. @@ -385,12 +408,14 @@ where at: B::Hash, ) -> Result, &'static str> { let mut child_kv_inner = vec![]; + let mut batch_success = true; + for batch_child_key in child_keys.chunks(BATCH_SIZE) { - let batch_request = batch_child_key - .iter() - .cloned() - .map(|key| { - ( + let mut batch_request = BatchRequestBuilder::new(); + + for key in batch_child_key { + batch_request + .insert( "childstate_getStorage", rpc_params![ PrefixedStorageKey::new(prefixed_top_key.as_ref().to_vec()), @@ -398,8 +423,8 @@ where at ], ) - }) - .collect::>(); + .map_err(|_| "Invalid batch params")?; + } let batch_response = self .as_online() @@ -418,17 +443,32 @@ where assert_eq!(batch_child_key.len(), batch_response.len()); - for (idx, key) in batch_child_key.iter().enumerate() { - let maybe_value = batch_response[idx].clone(); - let value = maybe_value.unwrap_or_else(|| { - log::warn!(target: LOG_TARGET, "key {:?} had none corresponding value.", &key); - StorageData(vec![]) - }); - child_kv_inner.push((key.clone(), value)); + for (key, maybe_value) in batch_child_key.iter().zip(batch_response) { + match maybe_value { + Ok(Some(v)) => { + child_kv_inner.push((key.clone(), v)); + }, + Ok(None) => { + log::warn!( + target: LOG_TARGET, + "key {:?} had none corresponding value.", + &key + ); + child_kv_inner.push((key.clone(), StorageData(vec![]))); + }, + Err(e) => { + log::error!(target: LOG_TARGET, "key {:?} failed: {:?}", &key, e); + batch_success = false; + }, + }; } } - Ok(child_kv_inner) + if batch_success { + Ok(child_kv_inner) + } else { + Err("batch failed.") + } } pub(crate) async fn rpc_child_get_keys( diff --git a/utils/frame/rpc/client/Cargo.toml b/utils/frame/rpc/client/Cargo.toml index bbe8879818..ee9982971c 100644 --- a/utils/frame/rpc/client/Cargo.toml +++ b/utils/frame/rpc/client/Cargo.toml @@ -12,7 +12,7 @@ description = "Shared JSON-RPC client" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { version = "0.15.1", features = ["ws-client"] } +jsonrpsee = { version = "0.16.2", features = ["ws-client"] } sc-rpc-api = { version = "0.10.0-dev", path = "../../../../client/rpc-api" } async-trait = "0.1.57" serde = "1" diff --git a/utils/frame/rpc/client/src/lib.rs b/utils/frame/rpc/client/src/lib.rs index 254cc193c0..a211fc6c69 100644 --- a/utils/frame/rpc/client/src/lib.rs +++ b/utils/frame/rpc/client/src/lib.rs @@ -43,7 +43,10 @@ use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; use std::collections::VecDeque; pub use jsonrpsee::{ - core::client::{ClientT, Subscription, SubscriptionClientT}, + core::{ + client::{ClientT, Subscription, SubscriptionClientT}, + params::BatchRequestBuilder, + }, rpc_params, ws_client::{WsClient, WsClientBuilder}, }; diff --git a/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml b/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml index 4886563a99..3a1b4b8b6c 100644 --- a/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml +++ b/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml @@ -25,7 +25,7 @@ sp-state-machine = { path = "../../../../primitives/state-machine" } sp-trie = { path = "../../../../primitives/trie" } trie-db = "0.24.0" -jsonrpsee = { version = "0.15.1", features = ["server", "macros"] } +jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } # Substrate Dependencies sc-client-api = { version = "4.0.0-dev", path = "../../../../client/api" } diff --git a/utils/frame/rpc/support/Cargo.toml b/utils/frame/rpc/support/Cargo.toml index 119acbd937..d098877e73 100644 --- a/utils/frame/rpc/support/Cargo.toml +++ b/utils/frame/rpc/support/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.21" -jsonrpsee = { version = "0.15.1", features = ["jsonrpsee-types"] } +jsonrpsee = { version = "0.16.2", features = ["jsonrpsee-types"] } serde = "1" frame-support = { version = "4.0.0-dev", path = "../../../../frame/support" } sc-rpc-api = { version = "0.10.0-dev", path = "../../../../client/rpc-api" } @@ -25,7 +25,7 @@ sp-storage = { version = "7.0.0", path = "../../../../primitives/storage" } [dev-dependencies] scale-info = "2.1.1" -jsonrpsee = { version = "0.15.1", features = ["ws-client", "jsonrpsee-types"] } +jsonrpsee = { version = "0.16.2", features = ["ws-client", "jsonrpsee-types"] } tokio = "1.22.0" sp-core = { version = "7.0.0", path = "../../../../primitives/core" } sp-runtime = { version = "7.0.0", path = "../../../../primitives/runtime" } diff --git a/utils/frame/rpc/system/Cargo.toml b/utils/frame/rpc/system/Cargo.toml index 56b8a79f8c..92cf6882a1 100644 --- a/utils/frame/rpc/system/Cargo.toml +++ b/utils/frame/rpc/system/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] serde_json = "1" codec = { package = "parity-scale-codec", version = "3.0.0" } -jsonrpsee = { version = "0.15.1", features = ["server"] } +jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } futures = "0.3.21" log = "0.4.17" frame-system-rpc-runtime-api = { version = "4.0.0-dev", path = "../../../../frame/system/rpc/runtime-api" } From e5d5d88d0d0e79042393c8bc85e5e9ebe6a24000 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Mon, 12 Dec 2022 11:48:43 +0100 Subject: [PATCH 027/127] `pallet-message-queue`: Fix license (#12895) * Fix license Signed-off-by: Oliver Tale-Yazdi * Add mock doc Signed-off-by: Oliver Tale-Yazdi Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/benchmarking.rs | 31 +++++++++++---------- frame/message-queue/src/integration_test.rs | 31 +++++++++++---------- frame/message-queue/src/lib.rs | 31 +++++++++++---------- frame/message-queue/src/mock.rs | 27 ++++++++++-------- frame/message-queue/src/mock_helpers.rs | 31 +++++++++++---------- 5 files changed, 79 insertions(+), 72 deletions(-) diff --git a/frame/message-queue/src/benchmarking.rs b/frame/message-queue/src/benchmarking.rs index c0ff20431d..9cd6b75e4d 100644 --- a/frame/message-queue/src/benchmarking.rs +++ b/frame/message-queue/src/benchmarking.rs @@ -1,18 +1,19 @@ -// Copyright 2022 Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Polkadot is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . +// This file is part of Substrate. + +// Copyright (C) 2022 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. //! Benchmarking for the message queue pallet. diff --git a/frame/message-queue/src/integration_test.rs b/frame/message-queue/src/integration_test.rs index a9b6ee9bd2..f4b1b7a125 100644 --- a/frame/message-queue/src/integration_test.rs +++ b/frame/message-queue/src/integration_test.rs @@ -1,18 +1,19 @@ -// Copyright 2022 Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Polkadot is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . +// This file is part of Substrate. + +// Copyright (C) 2022 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. //! Stress tests pallet-message-queue. Defines its own runtime config to use larger constants for //! `HeapSize` and `MaxStale`. diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 9b976c4824..6945ff1b1e 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -1,18 +1,19 @@ -// Copyright 2022 Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Polkadot is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . +// This file is part of Substrate. + +// Copyright (C) 2022 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. //! # Generalized Message Queue Pallet //! diff --git a/frame/message-queue/src/mock.rs b/frame/message-queue/src/mock.rs index bb9942443e..7159840d1c 100644 --- a/frame/message-queue/src/mock.rs +++ b/frame/message-queue/src/mock.rs @@ -1,18 +1,21 @@ -// Copyright 2022 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 -// 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. +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 -// 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. +// 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. -// You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . +//! Test helpers and runtime setup for the message queue pallet. #![cfg(test)] diff --git a/frame/message-queue/src/mock_helpers.rs b/frame/message-queue/src/mock_helpers.rs index 39d961d8fc..f12cf4cc41 100644 --- a/frame/message-queue/src/mock_helpers.rs +++ b/frame/message-queue/src/mock_helpers.rs @@ -1,18 +1,19 @@ -// Copyright 2022 Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Polkadot is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . +// This file is part of Substrate. + +// Copyright (C) 2022 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. //! Std setup helpers for testing and benchmarking. //! From b3d9f3c57e7f799d49442e3456614431f2d0e51a Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Mon, 12 Dec 2022 15:32:41 +0100 Subject: [PATCH 028/127] Use explicit call indices (#12891) * frame-system: explicit call index Signed-off-by: Oliver Tale-Yazdi * Use explicit call indices Signed-off-by: Oliver Tale-Yazdi * pallet-template: explicit call index Signed-off-by: Oliver Tale-Yazdi * DNM: Temporarily require call_index Signed-off-by: Oliver Tale-Yazdi * Revert "DNM: Temporarily require call_index" This reverts commit c4934e312e12af72ca05a8029d7da753a9c99346. Signed-off-by: Oliver Tale-Yazdi --- bin/node-template/pallets/template/src/lib.rs | 2 ++ frame/alliance/src/lib.rs | 18 ++++++++++++ frame/assets/src/lib.rs | 28 +++++++++++++++++++ frame/atomic-swap/src/lib.rs | 3 ++ frame/authorship/src/lib.rs | 1 + frame/babe/src/lib.rs | 3 ++ frame/bags-list/src/lib.rs | 2 ++ frame/balances/src/lib.rs | 6 ++++ frame/benchmarking/src/tests.rs | 3 ++ frame/benchmarking/src/tests_instance.rs | 2 ++ frame/bounties/src/lib.rs | 9 ++++++ frame/child-bounties/src/lib.rs | 7 +++++ frame/collective/src/lib.rs | 7 +++++ frame/collective/src/tests.rs | 1 + frame/contracts/src/lib.rs | 9 ++++++ frame/conviction-voting/src/lib.rs | 6 ++++ frame/democracy/src/lib.rs | 18 ++++++++++++ .../election-provider-multi-phase/src/lib.rs | 5 ++++ frame/elections-phragmen/src/lib.rs | 6 ++++ frame/examples/basic/src/lib.rs | 2 ++ frame/examples/offchain-worker/src/lib.rs | 3 ++ frame/executive/src/lib.rs | 7 +++++ frame/fast-unstake/src/lib.rs | 3 ++ frame/grandpa/src/lib.rs | 3 ++ frame/identity/src/lib.rs | 15 ++++++++++ frame/im-online/src/lib.rs | 1 + frame/indices/src/lib.rs | 5 ++++ frame/lottery/src/lib.rs | 4 +++ frame/membership/src/lib.rs | 7 +++++ frame/message-queue/src/lib.rs | 2 ++ frame/multisig/src/lib.rs | 4 +++ frame/nicks/src/lib.rs | 4 +++ frame/nis/src/lib.rs | 4 +++ frame/node-authorization/src/lib.rs | 9 ++++++ frame/nomination-pools/src/lib.rs | 14 ++++++++++ frame/preimage/src/lib.rs | 4 +++ frame/proxy/src/lib.rs | 10 +++++++ frame/ranked-collective/src/lib.rs | 6 ++++ frame/recovery/src/lib.rs | 9 ++++++ frame/referenda/src/lib.rs | 8 ++++++ frame/remark/src/lib.rs | 1 + frame/root-offences/src/lib.rs | 1 + frame/root-testing/src/lib.rs | 1 + frame/scheduler/src/lib.rs | 6 ++++ frame/scheduler/src/mock.rs | 2 ++ frame/scored-pool/src/lib.rs | 5 ++++ frame/session/src/lib.rs | 2 ++ frame/society/src/lib.rs | 12 ++++++++ frame/staking/src/pallet/mod.rs | 25 +++++++++++++++++ frame/state-trie-migration/src/lib.rs | 6 ++++ frame/sudo/src/lib.rs | 4 +++ frame/sudo/src/mock.rs | 2 ++ frame/support/test/tests/pallet.rs | 3 ++ .../test/tests/pallet_compatibility.rs | 1 + .../tests/pallet_compatibility_instance.rs | 1 + frame/support/test/tests/pallet_instance.rs | 2 ++ frame/support/test/tests/storage_layers.rs | 1 + frame/system/src/lib.rs | 8 ++++++ frame/timestamp/src/lib.rs | 1 + frame/tips/src/lib.rs | 6 ++++ frame/transaction-storage/src/lib.rs | 3 ++ frame/treasury/src/lib.rs | 5 ++++ frame/uniques/src/lib.rs | 26 +++++++++++++++++ frame/utility/src/lib.rs | 6 ++++ frame/utility/src/tests.rs | 4 +++ frame/vesting/src/lib.rs | 5 ++++ frame/whitelist/src/lib.rs | 4 +++ 67 files changed, 403 insertions(+) diff --git a/bin/node-template/pallets/template/src/lib.rs b/bin/node-template/pallets/template/src/lib.rs index 28d36ac2c6..4630e344ad 100644 --- a/bin/node-template/pallets/template/src/lib.rs +++ b/bin/node-template/pallets/template/src/lib.rs @@ -64,6 +64,7 @@ pub mod pallet { impl Pallet { /// An example dispatchable that takes a singles value as a parameter, writes the value to /// storage and emits an event. This function must be dispatched by a signed extrinsic. + #[pallet::call_index(0)] #[pallet::weight(10_000 + T::DbWeight::get().writes(1).ref_time())] pub fn do_something(origin: OriginFor, something: u32) -> DispatchResult { // Check that the extrinsic was signed and get the signer. @@ -81,6 +82,7 @@ pub mod pallet { } /// An example dispatchable that may throw a custom error. + #[pallet::call_index(1)] #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1).ref_time())] pub fn cause_error(origin: OriginFor) -> DispatchResult { let _who = ensure_signed(origin)?; diff --git a/frame/alliance/src/lib.rs b/frame/alliance/src/lib.rs index 7e03da9ac1..790c3c384e 100644 --- a/frame/alliance/src/lib.rs +++ b/frame/alliance/src/lib.rs @@ -503,6 +503,7 @@ pub mod pallet { /// Add a new proposal to be voted on. /// /// Must be called by a Fellow. + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::propose_proposed( *length_bound, // B T::MaxFellows::get(), // M @@ -524,6 +525,7 @@ pub mod pallet { /// Add an aye or nay vote for the sender to the given proposal. /// /// Must be called by a Fellow. + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::vote(T::MaxFellows::get()))] pub fn vote( origin: OriginFor, @@ -541,6 +543,7 @@ pub mod pallet { /// Close a vote that is either approved, disapproved, or whose voting period has ended. /// /// Must be called by a Fellow. + #[pallet::call_index(2)] #[pallet::weight({ let b = *length_bound; let m = T::MaxFellows::get(); @@ -573,6 +576,7 @@ pub mod pallet { /// The Alliance must be empty, and the call must provide some founding members. /// /// Must be called by the Root origin. + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::init_members( fellows.len() as u32, allies.len() as u32, @@ -623,6 +627,7 @@ pub mod pallet { /// Disband the Alliance, remove all active members and unreserve deposits. /// /// Witness data must be set. + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::disband( witness.fellow_members, witness.ally_members, @@ -673,6 +678,7 @@ pub mod pallet { } /// Set a new IPFS CID to the alliance rule. + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::set_rule())] pub fn set_rule(origin: OriginFor, rule: Cid) -> DispatchResult { T::AdminOrigin::ensure_origin(origin)?; @@ -684,6 +690,7 @@ pub mod pallet { } /// Make an announcement of a new IPFS CID about alliance issues. + #[pallet::call_index(6)] #[pallet::weight(T::WeightInfo::announce())] pub fn announce(origin: OriginFor, announcement: Cid) -> DispatchResult { T::AnnouncementOrigin::ensure_origin(origin)?; @@ -699,6 +706,7 @@ pub mod pallet { } /// Remove an announcement. + #[pallet::call_index(7)] #[pallet::weight(T::WeightInfo::remove_announcement())] pub fn remove_announcement(origin: OriginFor, announcement: Cid) -> DispatchResult { T::AnnouncementOrigin::ensure_origin(origin)?; @@ -716,6 +724,7 @@ pub mod pallet { } /// Submit oneself for candidacy. A fixed deposit is reserved. + #[pallet::call_index(8)] #[pallet::weight(T::WeightInfo::join_alliance())] pub fn join_alliance(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; @@ -752,6 +761,7 @@ pub mod pallet { /// A Fellow can nominate someone to join the alliance as an Ally. There is no deposit /// required from the nominator or nominee. + #[pallet::call_index(9)] #[pallet::weight(T::WeightInfo::nominate_ally())] pub fn nominate_ally(origin: OriginFor, who: AccountIdLookupOf) -> DispatchResult { let nominator = ensure_signed(origin)?; @@ -776,6 +786,7 @@ pub mod pallet { } /// Elevate an Ally to Fellow. + #[pallet::call_index(10)] #[pallet::weight(T::WeightInfo::elevate_ally())] pub fn elevate_ally(origin: OriginFor, ally: AccountIdLookupOf) -> DispatchResult { T::MembershipManager::ensure_origin(origin)?; @@ -792,6 +803,7 @@ pub mod pallet { /// As a member, give a retirement notice and start a retirement period required to pass in /// order to retire. + #[pallet::call_index(11)] #[pallet::weight(T::WeightInfo::give_retirement_notice())] pub fn give_retirement_notice(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; @@ -814,6 +826,7 @@ pub mod pallet { /// /// This can only be done once you have called `give_retirement_notice` and the /// `RetirementPeriod` has passed. + #[pallet::call_index(12)] #[pallet::weight(T::WeightInfo::retire())] pub fn retire(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; @@ -836,6 +849,7 @@ pub mod pallet { } /// Kick a member from the Alliance and slash its deposit. + #[pallet::call_index(13)] #[pallet::weight(T::WeightInfo::kick_member())] pub fn kick_member(origin: OriginFor, who: AccountIdLookupOf) -> DispatchResult { T::MembershipManager::ensure_origin(origin)?; @@ -853,6 +867,7 @@ pub mod pallet { } /// Add accounts or websites to the list of unscrupulous items. + #[pallet::call_index(14)] #[pallet::weight(T::WeightInfo::add_unscrupulous_items(items.len() as u32, T::MaxWebsiteUrlLength::get()))] pub fn add_unscrupulous_items( origin: OriginFor, @@ -882,6 +897,7 @@ pub mod pallet { } /// Deem some items no longer unscrupulous. + #[pallet::call_index(15)] #[pallet::weight(>::WeightInfo::remove_unscrupulous_items( items.len() as u32, T::MaxWebsiteUrlLength::get() ))] @@ -907,6 +923,7 @@ pub mod pallet { /// Close a vote that is either approved, disapproved, or whose voting period has ended. /// /// Must be called by a Fellow. + #[pallet::call_index(16)] #[pallet::weight({ let b = *length_bound; let m = T::MaxFellows::get(); @@ -934,6 +951,7 @@ pub mod pallet { /// Abdicate one's position as a voting member and just be an Ally. May be used by Fellows /// who do not want to leave the Alliance but do not have the capacity to participate /// operationally for some time. + #[pallet::call_index(17)] #[pallet::weight(T::WeightInfo::abdicate_fellow_status())] pub fn abdicate_fellow_status(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; diff --git a/frame/assets/src/lib.rs b/frame/assets/src/lib.rs index 2902477d0f..629a0243cf 100644 --- a/frame/assets/src/lib.rs +++ b/frame/assets/src/lib.rs @@ -563,6 +563,7 @@ pub mod pallet { /// Emits `Created` event when successful. /// /// Weight: `O(1)` + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::create())] pub fn create( origin: OriginFor, @@ -620,6 +621,7 @@ pub mod pallet { /// Emits `ForceCreated` event when successful. /// /// Weight: `O(1)` + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::force_create())] pub fn force_create( origin: OriginFor, @@ -645,6 +647,7 @@ pub mod pallet { /// asset. /// /// The asset class must be frozen before calling `start_destroy`. + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::start_destroy())] pub fn start_destroy(origin: OriginFor, id: T::AssetIdParameter) -> DispatchResult { let maybe_check_owner = match T::ForceOrigin::try_origin(origin) { @@ -667,6 +670,7 @@ pub mod pallet { /// asset. /// /// Each call emits the `Event::DestroyedAccounts` event. + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::destroy_accounts(T::RemoveItemsLimit::get()))] pub fn destroy_accounts( origin: OriginFor, @@ -690,6 +694,7 @@ pub mod pallet { /// asset. /// /// Each call emits the `Event::DestroyedApprovals` event. + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::destroy_approvals(T::RemoveItemsLimit::get()))] pub fn destroy_approvals( origin: OriginFor, @@ -711,6 +716,7 @@ pub mod pallet { /// asset. /// /// Each successful call emits the `Event::Destroyed` event. + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::finish_destroy())] pub fn finish_destroy(origin: OriginFor, id: T::AssetIdParameter) -> DispatchResult { let _ = ensure_signed(origin)?; @@ -730,6 +736,7 @@ pub mod pallet { /// /// Weight: `O(1)` /// Modes: Pre-existing balance of `beneficiary`; Account pre-existence of `beneficiary`. + #[pallet::call_index(6)] #[pallet::weight(T::WeightInfo::mint())] pub fn mint( origin: OriginFor, @@ -759,6 +766,7 @@ pub mod pallet { /// /// Weight: `O(1)` /// Modes: Post-existence of `who`; Pre & post Zombie-status of `who`. + #[pallet::call_index(7)] #[pallet::weight(T::WeightInfo::burn())] pub fn burn( origin: OriginFor, @@ -793,6 +801,7 @@ pub mod pallet { /// Weight: `O(1)` /// Modes: Pre-existence of `target`; Post-existence of sender; Account pre-existence of /// `target`. + #[pallet::call_index(8)] #[pallet::weight(T::WeightInfo::transfer())] pub fn transfer( origin: OriginFor, @@ -826,6 +835,7 @@ pub mod pallet { /// Weight: `O(1)` /// Modes: Pre-existence of `target`; Post-existence of sender; Account pre-existence of /// `target`. + #[pallet::call_index(9)] #[pallet::weight(T::WeightInfo::transfer_keep_alive())] pub fn transfer_keep_alive( origin: OriginFor, @@ -860,6 +870,7 @@ pub mod pallet { /// Weight: `O(1)` /// Modes: Pre-existence of `dest`; Post-existence of `source`; Account pre-existence of /// `dest`. + #[pallet::call_index(10)] #[pallet::weight(T::WeightInfo::force_transfer())] pub fn force_transfer( origin: OriginFor, @@ -887,6 +898,7 @@ pub mod pallet { /// Emits `Frozen`. /// /// Weight: `O(1)` + #[pallet::call_index(11)] #[pallet::weight(T::WeightInfo::freeze())] pub fn freeze( origin: OriginFor, @@ -923,6 +935,7 @@ pub mod pallet { /// Emits `Thawed`. /// /// Weight: `O(1)` + #[pallet::call_index(12)] #[pallet::weight(T::WeightInfo::thaw())] pub fn thaw( origin: OriginFor, @@ -958,6 +971,7 @@ pub mod pallet { /// Emits `Frozen`. /// /// Weight: `O(1)` + #[pallet::call_index(13)] #[pallet::weight(T::WeightInfo::freeze_asset())] pub fn freeze_asset(origin: OriginFor, id: T::AssetIdParameter) -> DispatchResult { let origin = ensure_signed(origin)?; @@ -984,6 +998,7 @@ pub mod pallet { /// Emits `Thawed`. /// /// Weight: `O(1)` + #[pallet::call_index(14)] #[pallet::weight(T::WeightInfo::thaw_asset())] pub fn thaw_asset(origin: OriginFor, id: T::AssetIdParameter) -> DispatchResult { let origin = ensure_signed(origin)?; @@ -1011,6 +1026,7 @@ pub mod pallet { /// Emits `OwnerChanged`. /// /// Weight: `O(1)` + #[pallet::call_index(15)] #[pallet::weight(T::WeightInfo::transfer_ownership())] pub fn transfer_ownership( origin: OriginFor, @@ -1054,6 +1070,7 @@ pub mod pallet { /// Emits `TeamChanged`. /// /// Weight: `O(1)` + #[pallet::call_index(16)] #[pallet::weight(T::WeightInfo::set_team())] pub fn set_team( origin: OriginFor, @@ -1098,6 +1115,7 @@ pub mod pallet { /// Emits `MetadataSet`. /// /// Weight: `O(1)` + #[pallet::call_index(17)] #[pallet::weight(T::WeightInfo::set_metadata(name.len() as u32, symbol.len() as u32))] pub fn set_metadata( origin: OriginFor, @@ -1122,6 +1140,7 @@ pub mod pallet { /// Emits `MetadataCleared`. /// /// Weight: `O(1)` + #[pallet::call_index(18)] #[pallet::weight(T::WeightInfo::clear_metadata())] pub fn clear_metadata(origin: OriginFor, id: T::AssetIdParameter) -> DispatchResult { let origin = ensure_signed(origin)?; @@ -1153,6 +1172,7 @@ pub mod pallet { /// Emits `MetadataSet`. /// /// Weight: `O(N + S)` where N and S are the length of the name and symbol respectively. + #[pallet::call_index(19)] #[pallet::weight(T::WeightInfo::force_set_metadata(name.len() as u32, symbol.len() as u32))] pub fn force_set_metadata( origin: OriginFor, @@ -1204,6 +1224,7 @@ pub mod pallet { /// Emits `MetadataCleared`. /// /// Weight: `O(1)` + #[pallet::call_index(20)] #[pallet::weight(T::WeightInfo::force_clear_metadata())] pub fn force_clear_metadata( origin: OriginFor, @@ -1243,6 +1264,7 @@ pub mod pallet { /// Emits `AssetStatusChanged` with the identity of the asset. /// /// Weight: `O(1)` + #[pallet::call_index(21)] #[pallet::weight(T::WeightInfo::force_asset_status())] pub fn force_asset_status( origin: OriginFor, @@ -1299,6 +1321,7 @@ pub mod pallet { /// Emits `ApprovedTransfer` on success. /// /// Weight: `O(1)` + #[pallet::call_index(22)] #[pallet::weight(T::WeightInfo::approve_transfer())] pub fn approve_transfer( origin: OriginFor, @@ -1325,6 +1348,7 @@ pub mod pallet { /// Emits `ApprovalCancelled` on success. /// /// Weight: `O(1)` + #[pallet::call_index(23)] #[pallet::weight(T::WeightInfo::cancel_approval())] pub fn cancel_approval( origin: OriginFor, @@ -1361,6 +1385,7 @@ pub mod pallet { /// Emits `ApprovalCancelled` on success. /// /// Weight: `O(1)` + #[pallet::call_index(24)] #[pallet::weight(T::WeightInfo::force_cancel_approval())] pub fn force_cancel_approval( origin: OriginFor, @@ -1410,6 +1435,7 @@ pub mod pallet { /// Emits `TransferredApproved` on success. /// /// Weight: `O(1)` + #[pallet::call_index(25)] #[pallet::weight(T::WeightInfo::transfer_approved())] pub fn transfer_approved( origin: OriginFor, @@ -1434,6 +1460,7 @@ pub mod pallet { /// - `id`: The identifier of the asset for the account to be created. /// /// Emits `Touched` event when successful. + #[pallet::call_index(26)] #[pallet::weight(T::WeightInfo::mint())] pub fn touch(origin: OriginFor, id: T::AssetIdParameter) -> DispatchResult { let id: T::AssetId = id.into(); @@ -1448,6 +1475,7 @@ pub mod pallet { /// - `allow_burn`: If `true` then assets may be destroyed in order to complete the refund. /// /// Emits `Refunded` event when successful. + #[pallet::call_index(27)] #[pallet::weight(T::WeightInfo::mint())] pub fn refund( origin: OriginFor, diff --git a/frame/atomic-swap/src/lib.rs b/frame/atomic-swap/src/lib.rs index 9c60564971..66628e8e6f 100644 --- a/frame/atomic-swap/src/lib.rs +++ b/frame/atomic-swap/src/lib.rs @@ -243,6 +243,7 @@ pub mod pallet { /// - `duration`: Locked duration of the atomic swap. For safety reasons, it is recommended /// that the revealer uses a shorter duration than the counterparty, to prevent the /// situation where the revealer reveals the proof too late around the end block. + #[pallet::call_index(0)] #[pallet::weight(T::DbWeight::get().reads_writes(1, 1).ref_time().saturating_add(40_000_000))] pub fn create_swap( origin: OriginFor, @@ -278,6 +279,7 @@ pub mod pallet { /// - `proof`: Revealed proof of the claim. /// - `action`: Action defined in the swap, it must match the entry in blockchain. Otherwise /// the operation fails. This is used for weight calculation. + #[pallet::call_index(1)] #[pallet::weight( T::DbWeight::get().reads_writes(1, 1) .saturating_add(action.weight()) @@ -318,6 +320,7 @@ pub mod pallet { /// /// - `target`: Target of the original atomic swap. /// - `hashed_proof`: Hashed proof of the original atomic swap. + #[pallet::call_index(2)] #[pallet::weight(T::DbWeight::get().reads_writes(1, 1).ref_time().saturating_add(40_000_000))] pub fn cancel_swap( origin: OriginFor, diff --git a/frame/authorship/src/lib.rs b/frame/authorship/src/lib.rs index c08e773abe..a40f32d36c 100644 --- a/frame/authorship/src/lib.rs +++ b/frame/authorship/src/lib.rs @@ -237,6 +237,7 @@ pub mod pallet { #[pallet::call] impl Pallet { /// Provide a set of uncles. + #[pallet::call_index(0)] #[pallet::weight((0, DispatchClass::Mandatory))] pub fn set_uncles(origin: OriginFor, new_uncles: Vec) -> DispatchResult { ensure_none(origin)?; diff --git a/frame/babe/src/lib.rs b/frame/babe/src/lib.rs index eadaa03633..1a9b320008 100644 --- a/frame/babe/src/lib.rs +++ b/frame/babe/src/lib.rs @@ -403,6 +403,7 @@ pub mod pallet { /// the equivocation proof and validate the given key ownership proof /// against the extracted offender. If both are valid, the offence will /// be reported. + #[pallet::call_index(0)] #[pallet::weight(::WeightInfo::report_equivocation( key_owner_proof.validator_count(), ))] @@ -424,6 +425,7 @@ pub mod pallet { /// block authors will call it (validated in `ValidateUnsigned`), as such /// if the block author is defined it will be defined as the equivocation /// reporter. + #[pallet::call_index(1)] #[pallet::weight(::WeightInfo::report_equivocation( key_owner_proof.validator_count(), ))] @@ -445,6 +447,7 @@ pub mod pallet { /// the next call to `enact_epoch_change`. The config will be activated one epoch after. /// Multiple calls to this method will replace any existing planned config change that had /// not been enacted yet. + #[pallet::call_index(2)] #[pallet::weight(::WeightInfo::plan_config_change())] pub fn plan_config_change( origin: OriginFor, diff --git a/frame/bags-list/src/lib.rs b/frame/bags-list/src/lib.rs index 2b48fbf0ca..1ffdf29345 100644 --- a/frame/bags-list/src/lib.rs +++ b/frame/bags-list/src/lib.rs @@ -224,6 +224,7 @@ pub mod pallet { /// `ScoreProvider`. /// /// If `dislocated` does not exists, it returns an error. + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::rebag_non_terminal().max(T::WeightInfo::rebag_terminal()))] pub fn rebag(origin: OriginFor, dislocated: AccountIdLookupOf) -> DispatchResult { ensure_signed(origin)?; @@ -242,6 +243,7 @@ pub mod pallet { /// Only works if /// - both nodes are within the same bag, /// - and `origin` has a greater `Score` than `lighter`. + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::put_in_front_of())] pub fn put_in_front_of( origin: OriginFor, diff --git a/frame/balances/src/lib.rs b/frame/balances/src/lib.rs index 381a0ffcee..57f76b1ff6 100644 --- a/frame/balances/src/lib.rs +++ b/frame/balances/src/lib.rs @@ -282,6 +282,7 @@ pub mod pallet { /// --------------------------------- /// - Origin account is already in memory, so no DB operations for them. /// # + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::transfer())] pub fn transfer( origin: OriginFor, @@ -307,6 +308,7 @@ pub mod pallet { /// it will reset the account nonce (`frame_system::AccountNonce`). /// /// The dispatch origin for this call is `root`. + #[pallet::call_index(1)] #[pallet::weight( T::WeightInfo::set_balance_creating() // Creates a new account. .max(T::WeightInfo::set_balance_killing()) // Kills an existing account. @@ -360,6 +362,7 @@ pub mod pallet { /// - Same as transfer, but additional read and write because the source account is not /// assumed to be in the overlay. /// # + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::force_transfer())] pub fn force_transfer( origin: OriginFor, @@ -385,6 +388,7 @@ pub mod pallet { /// 99% of the time you want [`transfer`] instead. /// /// [`transfer`]: struct.Pallet.html#method.transfer + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::transfer_keep_alive())] pub fn transfer_keep_alive( origin: OriginFor, @@ -414,6 +418,7 @@ pub mod pallet { /// keep the sender account alive (true). # /// - O(1). Just like transfer, but reading the user's transferable balance first. /// # + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::transfer_all())] pub fn transfer_all( origin: OriginFor, @@ -432,6 +437,7 @@ pub mod pallet { /// Unreserve some balance from a user by force. /// /// Can only be called by ROOT. + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::force_unreserve())] pub fn force_unreserve( origin: OriginFor, diff --git a/frame/benchmarking/src/tests.rs b/frame/benchmarking/src/tests.rs index 88a7d6d028..1499f9c182 100644 --- a/frame/benchmarking/src/tests.rs +++ b/frame/benchmarking/src/tests.rs @@ -51,6 +51,7 @@ mod pallet_test { #[pallet::call] impl Pallet { + #[pallet::call_index(0)] #[pallet::weight(0)] pub fn set_value(origin: OriginFor, n: u32) -> DispatchResult { let _sender = ensure_signed(origin)?; @@ -58,12 +59,14 @@ mod pallet_test { Ok(()) } + #[pallet::call_index(1)] #[pallet::weight(0)] pub fn dummy(origin: OriginFor, _n: u32) -> DispatchResult { let _sender = ensure_none(origin)?; Ok(()) } + #[pallet::call_index(2)] #[pallet::weight(0)] pub fn always_error(_origin: OriginFor) -> DispatchResult { return Err("I always fail".into()) diff --git a/frame/benchmarking/src/tests_instance.rs b/frame/benchmarking/src/tests_instance.rs index 7e1cd48840..ecc0a78a19 100644 --- a/frame/benchmarking/src/tests_instance.rs +++ b/frame/benchmarking/src/tests_instance.rs @@ -61,6 +61,7 @@ mod pallet_test { where ::OtherEvent: Into<>::RuntimeEvent>, { + #[pallet::call_index(0)] #[pallet::weight(0)] pub fn set_value(origin: OriginFor, n: u32) -> DispatchResult { let _sender = ensure_signed(origin)?; @@ -68,6 +69,7 @@ mod pallet_test { Ok(()) } + #[pallet::call_index(1)] #[pallet::weight(0)] pub fn dummy(origin: OriginFor, _n: u32) -> DispatchResult { let _sender = ensure_none(origin)?; diff --git a/frame/bounties/src/lib.rs b/frame/bounties/src/lib.rs index 2e350dd1e2..eb92c774f8 100644 --- a/frame/bounties/src/lib.rs +++ b/frame/bounties/src/lib.rs @@ -333,6 +333,7 @@ pub mod pallet { /// - `fee`: The curator fee. /// - `value`: The total payment amount of this bounty, curator fee included. /// - `description`: The description of this bounty. + #[pallet::call_index(0)] #[pallet::weight(>::WeightInfo::propose_bounty(description.len() as u32))] pub fn propose_bounty( origin: OriginFor, @@ -352,6 +353,7 @@ pub mod pallet { /// # /// - O(1). /// # + #[pallet::call_index(1)] #[pallet::weight(>::WeightInfo::approve_bounty())] pub fn approve_bounty( origin: OriginFor, @@ -383,6 +385,7 @@ pub mod pallet { /// # /// - O(1). /// # + #[pallet::call_index(2)] #[pallet::weight(>::WeightInfo::propose_curator())] pub fn propose_curator( origin: OriginFor, @@ -432,6 +435,7 @@ pub mod pallet { /// # /// - O(1). /// # + #[pallet::call_index(3)] #[pallet::weight(>::WeightInfo::unassign_curator())] pub fn unassign_curator( origin: OriginFor, @@ -517,6 +521,7 @@ pub mod pallet { /// # /// - O(1). /// # + #[pallet::call_index(4)] #[pallet::weight(>::WeightInfo::accept_curator())] pub fn accept_curator( origin: OriginFor, @@ -559,6 +564,7 @@ pub mod pallet { /// # /// - O(1). /// # + #[pallet::call_index(5)] #[pallet::weight(>::WeightInfo::award_bounty())] pub fn award_bounty( origin: OriginFor, @@ -606,6 +612,7 @@ pub mod pallet { /// # /// - O(1). /// # + #[pallet::call_index(6)] #[pallet::weight(>::WeightInfo::claim_bounty())] pub fn claim_bounty( origin: OriginFor, @@ -669,6 +676,7 @@ pub mod pallet { /// # /// - O(1). /// # + #[pallet::call_index(7)] #[pallet::weight(>::WeightInfo::close_bounty_proposed() .max(>::WeightInfo::close_bounty_active()))] pub fn close_bounty( @@ -760,6 +768,7 @@ pub mod pallet { /// # /// - O(1). /// # + #[pallet::call_index(8)] #[pallet::weight(>::WeightInfo::extend_bounty_expiry())] pub fn extend_bounty_expiry( origin: OriginFor, diff --git a/frame/child-bounties/src/lib.rs b/frame/child-bounties/src/lib.rs index 2dfe0660ad..9eb784eacc 100644 --- a/frame/child-bounties/src/lib.rs +++ b/frame/child-bounties/src/lib.rs @@ -237,6 +237,7 @@ pub mod pallet { /// - `parent_bounty_id`: Index of parent bounty for which child-bounty is being added. /// - `value`: Value for executing the proposal. /// - `description`: Text description for the child-bounty. + #[pallet::call_index(0)] #[pallet::weight(::WeightInfo::add_child_bounty(description.len() as u32))] pub fn add_child_bounty( origin: OriginFor, @@ -311,6 +312,7 @@ pub mod pallet { /// - `child_bounty_id`: Index of child bounty. /// - `curator`: Address of child-bounty curator. /// - `fee`: payment fee to child-bounty curator for execution. + #[pallet::call_index(1)] #[pallet::weight(::WeightInfo::propose_curator())] pub fn propose_curator( origin: OriginFor, @@ -380,6 +382,7 @@ pub mod pallet { /// /// - `parent_bounty_id`: Index of parent bounty. /// - `child_bounty_id`: Index of child bounty. + #[pallet::call_index(2)] #[pallet::weight(::WeightInfo::accept_curator())] pub fn accept_curator( origin: OriginFor, @@ -456,6 +459,7 @@ pub mod pallet { /// /// - `parent_bounty_id`: Index of parent bounty. /// - `child_bounty_id`: Index of child bounty. + #[pallet::call_index(3)] #[pallet::weight(::WeightInfo::unassign_curator())] pub fn unassign_curator( origin: OriginFor, @@ -570,6 +574,7 @@ pub mod pallet { /// - `parent_bounty_id`: Index of parent bounty. /// - `child_bounty_id`: Index of child bounty. /// - `beneficiary`: Beneficiary account. + #[pallet::call_index(4)] #[pallet::weight(::WeightInfo::award_child_bounty())] pub fn award_child_bounty( origin: OriginFor, @@ -636,6 +641,7 @@ pub mod pallet { /// /// - `parent_bounty_id`: Index of parent bounty. /// - `child_bounty_id`: Index of child bounty. + #[pallet::call_index(5)] #[pallet::weight(::WeightInfo::claim_child_bounty())] pub fn claim_child_bounty( origin: OriginFor, @@ -745,6 +751,7 @@ pub mod pallet { /// /// - `parent_bounty_id`: Index of parent bounty. /// - `child_bounty_id`: Index of child bounty. + #[pallet::call_index(6)] #[pallet::weight(::WeightInfo::close_child_bounty_added() .max(::WeightInfo::close_child_bounty_active()))] pub fn close_child_bounty( diff --git a/frame/collective/src/lib.rs b/frame/collective/src/lib.rs index 06d5b1fab7..c522b71891 100644 --- a/frame/collective/src/lib.rs +++ b/frame/collective/src/lib.rs @@ -372,6 +372,7 @@ pub mod pallet { /// - `P` storage mutations (codec `O(M)`) for updating the votes for each proposal /// - 1 storage write (codec `O(1)`) for deleting the old `prime` and setting the new one /// # + #[pallet::call_index(0)] #[pallet::weight(( T::WeightInfo::set_members( *old_count, // M @@ -429,6 +430,7 @@ pub mod pallet { /// - DB: 1 read (codec `O(M)`) + DB access of `proposal` /// - 1 event /// # + #[pallet::call_index(1)] #[pallet::weight(( T::WeightInfo::execute( *length_bound, // B @@ -492,6 +494,7 @@ pub mod pallet { /// - 1 storage write `Voting` (codec `O(M)`) /// - 1 event /// # + #[pallet::call_index(2)] #[pallet::weight(( if *threshold < 2 { T::WeightInfo::propose_execute( @@ -557,6 +560,7 @@ pub mod pallet { /// - 1 storage mutation `Voting` (codec `O(M)`) /// - 1 event /// # + #[pallet::call_index(3)] #[pallet::weight((T::WeightInfo::vote(T::MaxMembers::get()), DispatchClass::Operational))] pub fn vote( origin: OriginFor, @@ -610,6 +614,7 @@ pub mod pallet { /// - any mutations done while executing `proposal` (`P1`) /// - up to 3 events /// # + #[pallet::call_index(4)] #[pallet::weight(( { let b = *length_bound; @@ -653,6 +658,7 @@ pub mod pallet { /// * Reads: Proposals /// * Writes: Voting, Proposals, ProposalOf /// # + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::disapprove_proposal(T::MaxProposals::get()))] pub fn disapprove_proposal( origin: OriginFor, @@ -695,6 +701,7 @@ pub mod pallet { /// - any mutations done while executing `proposal` (`P1`) /// - up to 3 events /// # + #[pallet::call_index(6)] #[pallet::weight(( { let b = *length_bound; diff --git a/frame/collective/src/tests.rs b/frame/collective/src/tests.rs index 3d1540a8c3..648b6f88ec 100644 --- a/frame/collective/src/tests.rs +++ b/frame/collective/src/tests.rs @@ -69,6 +69,7 @@ mod mock_democracy { #[pallet::call] impl Pallet { + #[pallet::call_index(0)] #[pallet::weight(0)] pub fn external_propose_majority(origin: OriginFor) -> DispatchResult { T::ExternalMajorityOrigin::ensure_origin(origin)?; diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index 4bbb311313..06d817785c 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -385,6 +385,7 @@ pub mod pallet { as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, { /// Deprecated version if [`Self::call`] for use in an in-storage `Call`. + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::call().saturating_add(>::compat_weight(*gas_limit)))] #[allow(deprecated)] #[deprecated(note = "1D weight is used in this extrinsic, please migrate to `call`")] @@ -407,6 +408,7 @@ pub mod pallet { } /// Deprecated version if [`Self::instantiate_with_code`] for use in an in-storage `Call`. + #[pallet::call_index(1)] #[pallet::weight( T::WeightInfo::instantiate_with_code(code.len() as u32, salt.len() as u32) .saturating_add(>::compat_weight(*gas_limit)) @@ -436,6 +438,7 @@ pub mod pallet { } /// Deprecated version if [`Self::instantiate`] for use in an in-storage `Call`. + #[pallet::call_index(2)] #[pallet::weight( T::WeightInfo::instantiate(salt.len() as u32).saturating_add(>::compat_weight(*gas_limit)) )] @@ -481,6 +484,7 @@ pub mod pallet { /// To avoid this situation a constructor could employ access control so that it can /// only be instantiated by permissioned entities. The same is true when uploading /// through [`Self::instantiate_with_code`]. + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::upload_code(code.len() as u32))] pub fn upload_code( origin: OriginFor, @@ -497,6 +501,7 @@ pub mod pallet { /// /// A code can only be removed by its original uploader (its owner) and only if it is /// not used by any contract. + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::remove_code())] pub fn remove_code( origin: OriginFor, @@ -518,6 +523,7 @@ pub mod pallet { /// This does **not** change the address of the contract in question. This means /// that the contract address is no longer derived from its code hash after calling /// this dispatchable. + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::set_code())] pub fn set_code( origin: OriginFor, @@ -563,6 +569,7 @@ pub mod pallet { /// * If the account is a regular account, any value will be transferred. /// * If no account exists and the call value is not less than `existential_deposit`, /// a regular account will be created and any value will be transferred. + #[pallet::call_index(6)] #[pallet::weight(T::WeightInfo::call().saturating_add(*gas_limit))] pub fn call( origin: OriginFor, @@ -619,6 +626,7 @@ pub mod pallet { /// - The smart-contract account is created at the computed address. /// - The `value` is transferred to the new account. /// - The `deploy` function is executed in the context of the newly-created account. + #[pallet::call_index(7)] #[pallet::weight( T::WeightInfo::instantiate_with_code(code.len() as u32, salt.len() as u32) .saturating_add(*gas_limit) @@ -661,6 +669,7 @@ pub mod pallet { /// This function is identical to [`Self::instantiate_with_code`] but without the /// code deployment step. Instead, the `code_hash` of an on-chain deployed wasm binary /// must be supplied. + #[pallet::call_index(8)] #[pallet::weight( T::WeightInfo::instantiate(salt.len() as u32).saturating_add(*gas_limit) )] diff --git a/frame/conviction-voting/src/lib.rs b/frame/conviction-voting/src/lib.rs index 3ecc6e56be..141e9690fa 100644 --- a/frame/conviction-voting/src/lib.rs +++ b/frame/conviction-voting/src/lib.rs @@ -208,6 +208,7 @@ pub mod pallet { /// - `vote`: The vote configuration. /// /// Weight: `O(R)` where R is the number of polls the voter has voted on. + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::vote_new().max(T::WeightInfo::vote_existing()))] pub fn vote( origin: OriginFor, @@ -243,6 +244,7 @@ pub mod pallet { /// voted on. Weight is initially charged as if maximum votes, but is refunded later. // NOTE: weight must cover an incorrect voting of origin with max votes, this is ensure // because a valid delegation cover decoding a direct voting with max votes. + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::delegate(T::MaxVotes::get()))] pub fn delegate( origin: OriginFor, @@ -274,6 +276,7 @@ pub mod pallet { /// voted on. Weight is initially charged as if maximum votes, but is refunded later. // NOTE: weight must cover an incorrect voting of origin with max votes, this is ensure // because a valid delegation cover decoding a direct voting with max votes. + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::undelegate(T::MaxVotes::get().into()))] pub fn undelegate( origin: OriginFor, @@ -293,6 +296,7 @@ pub mod pallet { /// - `target`: The account to remove the lock on. /// /// Weight: `O(R)` with R number of vote of target. + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::unlock())] pub fn unlock( origin: OriginFor, @@ -334,6 +338,7 @@ pub mod pallet { /// /// Weight: `O(R + log R)` where R is the number of polls that `target` has voted on. /// Weight is calculated for the maximum number of vote. + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::remove_vote())] pub fn remove_vote( origin: OriginFor, @@ -360,6 +365,7 @@ pub mod pallet { /// /// Weight: `O(R + log R)` where R is the number of polls that `target` has voted on. /// Weight is calculated for the maximum number of vote. + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::remove_other_vote())] pub fn remove_other_vote( origin: OriginFor, diff --git a/frame/democracy/src/lib.rs b/frame/democracy/src/lib.rs index cf954d4800..2c65e5d94b 100644 --- a/frame/democracy/src/lib.rs +++ b/frame/democracy/src/lib.rs @@ -549,6 +549,7 @@ pub mod pallet { /// - `value`: The amount of deposit (must be at least `MinimumDeposit`). /// /// Emits `Proposed`. + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::propose())] pub fn propose( origin: OriginFor, @@ -591,6 +592,7 @@ pub mod pallet { /// must have funds to cover the deposit, equal to the original deposit. /// /// - `proposal`: The index of the proposal to second. + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::second())] pub fn second( origin: OriginFor, @@ -616,6 +618,7 @@ pub mod pallet { /// /// - `ref_index`: The index of the referendum to vote for. /// - `vote`: The vote configuration. + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::vote_new().max(T::WeightInfo::vote_existing()))] pub fn vote( origin: OriginFor, @@ -634,6 +637,7 @@ pub mod pallet { /// -`ref_index`: The index of the referendum to cancel. /// /// Weight: `O(1)`. + #[pallet::call_index(3)] #[pallet::weight((T::WeightInfo::emergency_cancel(), DispatchClass::Operational))] pub fn emergency_cancel( origin: OriginFor, @@ -656,6 +660,7 @@ pub mod pallet { /// The dispatch origin of this call must be `ExternalOrigin`. /// /// - `proposal_hash`: The preimage hash of the proposal. + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::external_propose())] pub fn external_propose( origin: OriginFor, @@ -684,6 +689,7 @@ pub mod pallet { /// pre-scheduled `external_propose` call. /// /// Weight: `O(1)` + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::external_propose_majority())] pub fn external_propose_majority( origin: OriginFor, @@ -705,6 +711,7 @@ pub mod pallet { /// pre-scheduled `external_propose` call. /// /// Weight: `O(1)` + #[pallet::call_index(6)] #[pallet::weight(T::WeightInfo::external_propose_default())] pub fn external_propose_default( origin: OriginFor, @@ -731,6 +738,7 @@ pub mod pallet { /// Emits `Started`. /// /// Weight: `O(1)` + #[pallet::call_index(7)] #[pallet::weight(T::WeightInfo::fast_track())] pub fn fast_track( origin: OriginFor, @@ -783,6 +791,7 @@ pub mod pallet { /// Emits `Vetoed`. /// /// Weight: `O(V + log(V))` where V is number of `existing vetoers` + #[pallet::call_index(8)] #[pallet::weight(T::WeightInfo::veto_external())] pub fn veto_external(origin: OriginFor, proposal_hash: H256) -> DispatchResult { let who = T::VetoOrigin::ensure_origin(origin)?; @@ -817,6 +826,7 @@ pub mod pallet { /// - `ref_index`: The index of the referendum to cancel. /// /// # Weight: `O(1)`. + #[pallet::call_index(9)] #[pallet::weight(T::WeightInfo::cancel_referendum())] pub fn cancel_referendum( origin: OriginFor, @@ -849,6 +859,7 @@ pub mod pallet { /// voted on. Weight is charged as if maximum votes. // NOTE: weight must cover an incorrect voting of origin with max votes, this is ensure // because a valid delegation cover decoding a direct voting with max votes. + #[pallet::call_index(10)] #[pallet::weight(T::WeightInfo::delegate(T::MaxVotes::get()))] pub fn delegate( origin: OriginFor, @@ -877,6 +888,7 @@ pub mod pallet { /// voted on. Weight is charged as if maximum votes. // NOTE: weight must cover an incorrect voting of origin with max votes, this is ensure // because a valid delegation cover decoding a direct voting with max votes. + #[pallet::call_index(11)] #[pallet::weight(T::WeightInfo::undelegate(T::MaxVotes::get()))] pub fn undelegate(origin: OriginFor) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; @@ -889,6 +901,7 @@ pub mod pallet { /// The dispatch origin of this call must be _Root_. /// /// Weight: `O(1)`. + #[pallet::call_index(12)] #[pallet::weight(T::WeightInfo::clear_public_proposals())] pub fn clear_public_proposals(origin: OriginFor) -> DispatchResult { ensure_root(origin)?; @@ -903,6 +916,7 @@ pub mod pallet { /// - `target`: The account to remove the lock on. /// /// Weight: `O(R)` with R number of vote of target. + #[pallet::call_index(13)] #[pallet::weight(T::WeightInfo::unlock_set(T::MaxVotes::get()).max(T::WeightInfo::unlock_remove(T::MaxVotes::get())))] pub fn unlock(origin: OriginFor, target: AccountIdLookupOf) -> DispatchResult { ensure_signed(origin)?; @@ -938,6 +952,7 @@ pub mod pallet { /// /// Weight: `O(R + log R)` where R is the number of referenda that `target` has voted on. /// Weight is calculated for the maximum number of vote. + #[pallet::call_index(14)] #[pallet::weight(T::WeightInfo::remove_vote(T::MaxVotes::get()))] pub fn remove_vote(origin: OriginFor, index: ReferendumIndex) -> DispatchResult { let who = ensure_signed(origin)?; @@ -959,6 +974,7 @@ pub mod pallet { /// /// Weight: `O(R + log R)` where R is the number of referenda that `target` has voted on. /// Weight is calculated for the maximum number of vote. + #[pallet::call_index(15)] #[pallet::weight(T::WeightInfo::remove_other_vote(T::MaxVotes::get()))] pub fn remove_other_vote( origin: OriginFor, @@ -987,6 +1003,7 @@ pub mod pallet { /// /// Weight: `O(p)` (though as this is an high-privilege dispatch, we assume it has a /// reasonable value). + #[pallet::call_index(16)] #[pallet::weight((T::WeightInfo::blacklist(), DispatchClass::Operational))] pub fn blacklist( origin: OriginFor, @@ -1036,6 +1053,7 @@ pub mod pallet { /// - `prop_index`: The index of the proposal to cancel. /// /// Weight: `O(p)` where `p = PublicProps::::decode_len()` + #[pallet::call_index(17)] #[pallet::weight(T::WeightInfo::cancel_proposal())] pub fn cancel_proposal( origin: OriginFor, diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index cd70514fd3..4704eaffa0 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -892,6 +892,7 @@ pub mod pallet { /// putting their authoring reward at risk. /// /// No deposit or reward is associated with this submission. + #[pallet::call_index(0)] #[pallet::weight(( T::WeightInfo::submit_unsigned( witness.voters, @@ -941,6 +942,7 @@ pub mod pallet { /// Dispatch origin must be aligned with `T::ForceOrigin`. /// /// This check can be turned off by setting the value to `None`. + #[pallet::call_index(1)] #[pallet::weight(T::DbWeight::get().writes(1))] pub fn set_minimum_untrusted_score( origin: OriginFor, @@ -959,6 +961,7 @@ pub mod pallet { /// The solution is not checked for any feasibility and is assumed to be trustworthy, as any /// feasibility check itself can in principle cause the election process to fail (due to /// memory/weight constrains). + #[pallet::call_index(2)] #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))] pub fn set_emergency_election_result( origin: OriginFor, @@ -996,6 +999,7 @@ pub mod pallet { /// /// A deposit is reserved and recorded for the solution. Based on the outcome, the solution /// might be rewarded, slashed, or get all or a part of the deposit back. + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::submit())] pub fn submit( origin: OriginFor, @@ -1065,6 +1069,7 @@ pub mod pallet { /// /// This can only be called when [`Phase::Emergency`] is enabled, as an alternative to /// calling [`Call::set_emergency_election_result`]. + #[pallet::call_index(4)] #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))] pub fn governance_fallback( origin: OriginFor, diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 165a8fcab4..1cfdc25fd9 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -309,6 +309,7 @@ pub mod pallet { /// # /// We assume the maximum weight among all 3 cases: vote_equal, vote_more and vote_less. /// # + #[pallet::call_index(0)] #[pallet::weight( T::WeightInfo::vote_more(votes.len() as u32) .max(T::WeightInfo::vote_less(votes.len() as u32)) @@ -371,6 +372,7 @@ pub mod pallet { /// This removes the lock and returns the deposit. /// /// The dispatch origin of this call must be signed and be a voter. + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::remove_voter())] pub fn remove_voter(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; @@ -394,6 +396,7 @@ pub mod pallet { /// # /// The number of current candidates must be provided as witness data. /// # + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::submit_candidacy(*candidate_count))] pub fn submit_candidacy( origin: OriginFor, @@ -438,6 +441,7 @@ pub mod pallet { /// # /// The type of renouncing must be provided as witness data. /// # + #[pallet::call_index(3)] #[pallet::weight(match *renouncing { Renouncing::Candidate(count) => T::WeightInfo::renounce_candidacy_candidate(count), Renouncing::Member => T::WeightInfo::renounce_candidacy_members(), @@ -500,6 +504,7 @@ pub mod pallet { /// If we have a replacement, we use a small weight. Else, since this is a root call and /// will go into phragmen, we assume full block for now. /// # + #[pallet::call_index(4)] #[pallet::weight(if *rerun_election { T::WeightInfo::remove_member_without_replacement() } else { @@ -535,6 +540,7 @@ pub mod pallet { /// # /// The total number of voters and those that are defunct must be provided as witness data. /// # + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::clean_defunct_voters(*_num_voters, *_num_defunct))] pub fn clean_defunct_voters( origin: OriginFor, diff --git a/frame/examples/basic/src/lib.rs b/frame/examples/basic/src/lib.rs index 256529421c..d5045157da 100644 --- a/frame/examples/basic/src/lib.rs +++ b/frame/examples/basic/src/lib.rs @@ -497,6 +497,7 @@ pub mod pallet { // // The weight for this extrinsic we rely on the auto-generated `WeightInfo` from the // benchmark toolchain. + #[pallet::call_index(0)] #[pallet::weight( ::WeightInfo::accumulate_dummy() )] @@ -541,6 +542,7 @@ pub mod pallet { // // The weight for this extrinsic we use our own weight object `WeightForSetDummy` to // determine its weight + #[pallet::call_index(1)] #[pallet::weight(WeightForSetDummy::(>::from(100u32)))] pub fn set_dummy( origin: OriginFor, diff --git a/frame/examples/offchain-worker/src/lib.rs b/frame/examples/offchain-worker/src/lib.rs index fdf8b61a01..46ff7725e3 100644 --- a/frame/examples/offchain-worker/src/lib.rs +++ b/frame/examples/offchain-worker/src/lib.rs @@ -229,6 +229,7 @@ pub mod pallet { /// working and receives (and provides) meaningful data. /// This example is not focused on correctness of the oracle itself, but rather its /// purpose is to showcase offchain worker capabilities. + #[pallet::call_index(0)] #[pallet::weight(0)] pub fn submit_price(origin: OriginFor, price: u32) -> DispatchResultWithPostInfo { // Retrieve sender of the transaction. @@ -254,6 +255,7 @@ pub mod pallet { /// /// This example is not focused on correctness of the oracle itself, but rather its /// purpose is to showcase offchain worker capabilities. + #[pallet::call_index(1)] #[pallet::weight(0)] pub fn submit_price_unsigned( origin: OriginFor, @@ -270,6 +272,7 @@ pub mod pallet { Ok(().into()) } + #[pallet::call_index(2)] #[pallet::weight(0)] pub fn submit_price_unsigned_with_signed_payload( origin: OriginFor, diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index 5a4ef92b1c..6f59ac72eb 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -674,6 +674,7 @@ mod tests { #[pallet::call] impl Pallet { + #[pallet::call_index(0)] #[pallet::weight(100)] pub fn some_function(origin: OriginFor) -> DispatchResult { // NOTE: does not make any different. @@ -681,36 +682,42 @@ mod tests { Ok(()) } + #[pallet::call_index(1)] #[pallet::weight((200, DispatchClass::Operational))] pub fn some_root_operation(origin: OriginFor) -> DispatchResult { frame_system::ensure_root(origin)?; Ok(()) } + #[pallet::call_index(2)] #[pallet::weight(0)] pub fn some_unsigned_message(origin: OriginFor) -> DispatchResult { frame_system::ensure_none(origin)?; Ok(()) } + #[pallet::call_index(3)] #[pallet::weight(0)] pub fn allowed_unsigned(origin: OriginFor) -> DispatchResult { frame_system::ensure_root(origin)?; Ok(()) } + #[pallet::call_index(4)] #[pallet::weight(0)] pub fn unallowed_unsigned(origin: OriginFor) -> DispatchResult { frame_system::ensure_root(origin)?; Ok(()) } + #[pallet::call_index(5)] #[pallet::weight((0, DispatchClass::Mandatory))] pub fn inherent_call(origin: OriginFor) -> DispatchResult { frame_system::ensure_none(origin)?; Ok(()) } + #[pallet::call_index(6)] #[pallet::weight(0)] pub fn calculate_storage_root(_origin: OriginFor) -> DispatchResult { let root = sp_io::storage::root(sp_runtime::StateVersion::V1); diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index 618afa63c2..7f226826cb 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -228,6 +228,7 @@ pub mod pallet { /// If the check fails, the stash remains chilled and waiting for being unbonded as in with /// the normal staking system, but they lose part of their unbonding chunks due to consuming /// the chain's resources. + #[pallet::call_index(0)] #[pallet::weight(::WeightInfo::register_fast_unstake())] pub fn register_fast_unstake(origin: OriginFor) -> DispatchResult { let ctrl = ensure_signed(origin)?; @@ -257,6 +258,7 @@ pub mod pallet { /// Note that the associated stash is still fully unbonded and chilled as a consequence of /// calling `register_fast_unstake`. This should probably be followed by a call to /// `Staking::rebond`. + #[pallet::call_index(1)] #[pallet::weight(::WeightInfo::deregister())] pub fn deregister(origin: OriginFor) -> DispatchResult { let ctrl = ensure_signed(origin)?; @@ -282,6 +284,7 @@ pub mod pallet { /// Control the operation of this pallet. /// /// Dispatch origin must be signed by the [`Config::ControlOrigin`]. + #[pallet::call_index(2)] #[pallet::weight(::WeightInfo::control())] pub fn control(origin: OriginFor, unchecked_eras_to_check: EraIndex) -> DispatchResult { let _ = T::ControlOrigin::ensure_origin(origin)?; diff --git a/frame/grandpa/src/lib.rs b/frame/grandpa/src/lib.rs index fe5b986185..c6b7fd2516 100644 --- a/frame/grandpa/src/lib.rs +++ b/frame/grandpa/src/lib.rs @@ -193,6 +193,7 @@ pub mod pallet { /// equivocation proof and validate the given key ownership proof /// against the extracted offender. If both are valid, the offence /// will be reported. + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::report_equivocation(key_owner_proof.validator_count()))] pub fn report_equivocation( origin: OriginFor, @@ -213,6 +214,7 @@ pub mod pallet { /// block authors will call it (validated in `ValidateUnsigned`), as such /// if the block author is defined it will be defined as the equivocation /// reporter. + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::report_equivocation(key_owner_proof.validator_count()))] pub fn report_equivocation_unsigned( origin: OriginFor, @@ -240,6 +242,7 @@ pub mod pallet { /// block of all validators of the new authority set. /// /// Only callable by root. + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::note_stalled())] pub fn note_stalled( origin: OriginFor, diff --git a/frame/identity/src/lib.rs b/frame/identity/src/lib.rs index 95f5a84d8a..8eab2c6741 100644 --- a/frame/identity/src/lib.rs +++ b/frame/identity/src/lib.rs @@ -284,6 +284,7 @@ pub mod pallet { /// - One storage mutation (codec `O(R)`). /// - One event. /// # + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::add_registrar(T::MaxRegistrars::get()))] pub fn add_registrar( origin: OriginFor, @@ -329,6 +330,7 @@ pub mod pallet { /// - One storage mutation (codec-read `O(X' + R)`, codec-write `O(X + R)`). /// - One event. /// # + #[pallet::call_index(1)] #[pallet::weight( T::WeightInfo::set_identity( T::MaxRegistrars::get(), // R T::MaxAdditionalFields::get(), // X @@ -404,6 +406,7 @@ pub mod pallet { // N storage items for N sub accounts. Right now the weight on this function // is a large overestimate due to the fact that it could potentially write // to 2 x T::MaxSubAccounts::get(). + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::set_subs_old(T::MaxSubAccounts::get()) // P: Assume max sub accounts removed. .saturating_add(T::WeightInfo::set_subs_new(subs.len() as u32)) // S: Assume all subs are new. )] @@ -475,6 +478,7 @@ pub mod pallet { /// - `2` storage reads and `S + 2` storage deletions. /// - One event. /// # + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::clear_identity( T::MaxRegistrars::get(), // R T::MaxSubAccounts::get(), // S @@ -526,6 +530,7 @@ pub mod pallet { /// - Storage: 1 read `O(R)`, 1 mutate `O(X + R)`. /// - One event. /// # + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::request_judgement( T::MaxRegistrars::get(), // R T::MaxAdditionalFields::get(), // X @@ -588,6 +593,7 @@ pub mod pallet { /// - One storage mutation `O(R + X)`. /// - One event /// # + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::cancel_request( T::MaxRegistrars::get(), // R T::MaxAdditionalFields::get(), // X @@ -636,6 +642,7 @@ pub mod pallet { /// - One storage mutation `O(R)`. /// - Benchmark: 7.315 + R * 0.329 µs (min squares analysis) /// # + #[pallet::call_index(6)] #[pallet::weight(T::WeightInfo::set_fee(T::MaxRegistrars::get()))] // R pub fn set_fee( origin: OriginFor, @@ -674,6 +681,7 @@ pub mod pallet { /// - One storage mutation `O(R)`. /// - Benchmark: 8.823 + R * 0.32 µs (min squares analysis) /// # + #[pallet::call_index(7)] #[pallet::weight(T::WeightInfo::set_account_id(T::MaxRegistrars::get()))] // R pub fn set_account_id( origin: OriginFor, @@ -713,6 +721,7 @@ pub mod pallet { /// - One storage mutation `O(R)`. /// - Benchmark: 7.464 + R * 0.325 µs (min squares analysis) /// # + #[pallet::call_index(8)] #[pallet::weight(T::WeightInfo::set_fields(T::MaxRegistrars::get()))] // R pub fn set_fields( origin: OriginFor, @@ -761,6 +770,7 @@ pub mod pallet { /// - Storage: 1 read `O(R)`, 1 mutate `O(R + X)`. /// - One event. /// # + #[pallet::call_index(9)] #[pallet::weight(T::WeightInfo::provide_judgement( T::MaxRegistrars::get(), // R T::MaxAdditionalFields::get(), // X @@ -834,6 +844,7 @@ pub mod pallet { /// - `S + 2` storage mutations. /// - One event. /// # + #[pallet::call_index(10)] #[pallet::weight(T::WeightInfo::kill_identity( T::MaxRegistrars::get(), // R T::MaxSubAccounts::get(), // S @@ -874,6 +885,7 @@ pub mod pallet { /// /// The dispatch origin for this call must be _Signed_ and the sender must have a registered /// sub identity of `sub`. + #[pallet::call_index(11)] #[pallet::weight(T::WeightInfo::add_sub(T::MaxSubAccounts::get()))] pub fn add_sub( origin: OriginFor, @@ -909,6 +921,7 @@ pub mod pallet { /// /// The dispatch origin for this call must be _Signed_ and the sender must have a registered /// sub identity of `sub`. + #[pallet::call_index(12)] #[pallet::weight(T::WeightInfo::rename_sub(T::MaxSubAccounts::get()))] pub fn rename_sub( origin: OriginFor, @@ -930,6 +943,7 @@ pub mod pallet { /// /// The dispatch origin for this call must be _Signed_ and the sender must have a registered /// sub identity of `sub`. + #[pallet::call_index(13)] #[pallet::weight(T::WeightInfo::remove_sub(T::MaxSubAccounts::get()))] pub fn remove_sub(origin: OriginFor, sub: AccountIdLookupOf) -> DispatchResult { let sender = ensure_signed(origin)?; @@ -959,6 +973,7 @@ pub mod pallet { /// /// NOTE: This should not normally be used, but is provided in the case that the non- /// controller of an account is maliciously registered as a sub-account. + #[pallet::call_index(14)] #[pallet::weight(T::WeightInfo::quit_sub(T::MaxSubAccounts::get()))] pub fn quit_sub(origin: OriginFor) -> DispatchResult { let sender = ensure_signed(origin)?; diff --git a/frame/im-online/src/lib.rs b/frame/im-online/src/lib.rs index 342522ff29..f23e610a48 100644 --- a/frame/im-online/src/lib.rs +++ b/frame/im-online/src/lib.rs @@ -474,6 +474,7 @@ pub mod pallet { /// # // NOTE: the weight includes the cost of validate_unsigned as it is part of the cost to // import block with such an extrinsic. + #[pallet::call_index(0)] #[pallet::weight(::WeightInfo::validate_unsigned_and_then_heartbeat( heartbeat.validators_len as u32, heartbeat.network_state.external_addresses.len() as u32, diff --git a/frame/indices/src/lib.rs b/frame/indices/src/lib.rs index 41893254c3..95d3cf4b2e 100644 --- a/frame/indices/src/lib.rs +++ b/frame/indices/src/lib.rs @@ -98,6 +98,7 @@ pub mod pallet { /// ------------------- /// - DB Weight: 1 Read/Write (Accounts) /// # + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::claim())] pub fn claim(origin: OriginFor, index: T::AccountIndex) -> DispatchResult { let who = ensure_signed(origin)?; @@ -131,6 +132,7 @@ pub mod pallet { /// - Reads: Indices Accounts, System Account (recipient) /// - Writes: Indices Accounts, System Account (recipient) /// # + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::transfer())] pub fn transfer( origin: OriginFor, @@ -171,6 +173,7 @@ pub mod pallet { /// ------------------- /// - DB Weight: 1 Read/Write (Accounts) /// # + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::free())] pub fn free(origin: OriginFor, index: T::AccountIndex) -> DispatchResult { let who = ensure_signed(origin)?; @@ -207,6 +210,7 @@ pub mod pallet { /// - Reads: Indices Accounts, System Account (original owner) /// - Writes: Indices Accounts, System Account (original owner) /// # + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::force_transfer())] pub fn force_transfer( origin: OriginFor, @@ -245,6 +249,7 @@ pub mod pallet { /// ------------------- /// - DB Weight: 1 Read/Write (Accounts) /// # + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::freeze())] pub fn freeze(origin: OriginFor, index: T::AccountIndex) -> DispatchResult { let who = ensure_signed(origin)?; diff --git a/frame/lottery/src/lib.rs b/frame/lottery/src/lib.rs index c501a30ef5..3255062e3f 100644 --- a/frame/lottery/src/lib.rs +++ b/frame/lottery/src/lib.rs @@ -296,6 +296,7 @@ pub mod pallet { /// should listen for the `TicketBought` event. /// /// This extrinsic must be called by a signed origin. + #[pallet::call_index(0)] #[pallet::weight( T::WeightInfo::buy_ticket() .saturating_add(call.get_dispatch_info().weight) @@ -317,6 +318,7 @@ pub mod pallet { /// provided by this pallet, which uses storage to determine the valid calls. /// /// This extrinsic must be called by the Manager origin. + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::set_calls(calls.len() as u32))] pub fn set_calls( origin: OriginFor, @@ -344,6 +346,7 @@ pub mod pallet { /// * `length`: How long the lottery should run for starting at the current block. /// * `delay`: How long after the lottery end we should wait before picking a winner. /// * `repeat`: If the lottery should repeat when completed. + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::start_lottery())] pub fn start_lottery( origin: OriginFor, @@ -376,6 +379,7 @@ pub mod pallet { /// The lottery will continue to run to completion. /// /// This extrinsic must be called by the `ManagerOrigin`. + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::stop_repeat())] pub fn stop_repeat(origin: OriginFor) -> DispatchResult { T::ManagerOrigin::ensure_origin(origin)?; diff --git a/frame/membership/src/lib.rs b/frame/membership/src/lib.rs index 4191bbcc5d..77b53aa72d 100644 --- a/frame/membership/src/lib.rs +++ b/frame/membership/src/lib.rs @@ -166,6 +166,7 @@ pub mod pallet { /// Add a member `who` to the set. /// /// May only be called from `T::AddOrigin`. + #[pallet::call_index(0)] #[pallet::weight(50_000_000)] pub fn add_member(origin: OriginFor, who: AccountIdLookupOf) -> DispatchResult { T::AddOrigin::ensure_origin(origin)?; @@ -188,6 +189,7 @@ pub mod pallet { /// Remove a member `who` from the set. /// /// May only be called from `T::RemoveOrigin`. + #[pallet::call_index(1)] #[pallet::weight(50_000_000)] pub fn remove_member(origin: OriginFor, who: AccountIdLookupOf) -> DispatchResult { T::RemoveOrigin::ensure_origin(origin)?; @@ -211,6 +213,7 @@ pub mod pallet { /// May only be called from `T::SwapOrigin`. /// /// Prime membership is *not* passed from `remove` to `add`, if extant. + #[pallet::call_index(2)] #[pallet::weight(50_000_000)] pub fn swap_member( origin: OriginFor, @@ -244,6 +247,7 @@ pub mod pallet { /// pass `members` pre-sorted. /// /// May only be called from `T::ResetOrigin`. + #[pallet::call_index(3)] #[pallet::weight(50_000_000)] pub fn reset_members(origin: OriginFor, members: Vec) -> DispatchResult { T::ResetOrigin::ensure_origin(origin)?; @@ -266,6 +270,7 @@ pub mod pallet { /// May only be called from `Signed` origin of a current member. /// /// Prime membership is passed from the origin account to `new`, if extant. + #[pallet::call_index(4)] #[pallet::weight(50_000_000)] pub fn change_key(origin: OriginFor, new: AccountIdLookupOf) -> DispatchResult { let remove = ensure_signed(origin)?; @@ -300,6 +305,7 @@ pub mod pallet { /// Set the prime member. Must be a current member. /// /// May only be called from `T::PrimeOrigin`. + #[pallet::call_index(5)] #[pallet::weight(50_000_000)] pub fn set_prime(origin: OriginFor, who: AccountIdLookupOf) -> DispatchResult { T::PrimeOrigin::ensure_origin(origin)?; @@ -313,6 +319,7 @@ pub mod pallet { /// Remove the prime member if it exists. /// /// May only be called from `T::PrimeOrigin`. + #[pallet::call_index(6)] #[pallet::weight(50_000_000)] pub fn clear_prime(origin: OriginFor) -> DispatchResult { T::PrimeOrigin::ensure_origin(origin)?; diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 6945ff1b1e..8d9faebe05 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -575,6 +575,7 @@ pub mod pallet { #[pallet::call] impl Pallet { /// Remove a page which has no more messages remaining to be processed or is stale. + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::reap_page())] pub fn reap_page( origin: OriginFor, @@ -595,6 +596,7 @@ pub mod pallet { /// of the message. /// /// Benchmark complexity considerations: O(index + weight_limit). + #[pallet::call_index(1)] #[pallet::weight( T::WeightInfo::execute_overweight_page_updated().max( T::WeightInfo::execute_overweight_page_removed()).saturating_add(*weight_limit) diff --git a/frame/multisig/src/lib.rs b/frame/multisig/src/lib.rs index ae4efb7633..076a289e06 100644 --- a/frame/multisig/src/lib.rs +++ b/frame/multisig/src/lib.rs @@ -272,6 +272,7 @@ pub mod pallet { /// - DB Weight: None /// - Plus Call Weight /// # + #[pallet::call_index(0)] #[pallet::weight({ let dispatch_info = call.get_dispatch_info(); ( @@ -365,6 +366,7 @@ pub mod pallet { /// - Writes: Multisig Storage, [Caller Account] /// - Plus Call Weight /// # + #[pallet::call_index(1)] #[pallet::weight({ let s = other_signatories.len() as u32; let z = call.using_encoded(|d| d.len()) as u32; @@ -428,6 +430,7 @@ pub mod pallet { /// - Read: Multisig Storage, [Caller Account] /// - Write: Multisig Storage, [Caller Account] /// # + #[pallet::call_index(2)] #[pallet::weight({ let s = other_signatories.len() as u32; @@ -480,6 +483,7 @@ pub mod pallet { /// - Read: Multisig Storage, [Caller Account], Refund Account /// - Write: Multisig Storage, [Caller Account], Refund Account /// # + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::cancel_as_multi(other_signatories.len() as u32))] pub fn cancel_as_multi( origin: OriginFor, diff --git a/frame/nicks/src/lib.rs b/frame/nicks/src/lib.rs index b3238630d3..79daeb9bdb 100644 --- a/frame/nicks/src/lib.rs +++ b/frame/nicks/src/lib.rs @@ -135,6 +135,7 @@ pub mod pallet { /// - One storage read/write. /// - One event. /// # + #[pallet::call_index(0)] #[pallet::weight(50_000_000)] pub fn set_name(origin: OriginFor, name: Vec) -> DispatchResult { let sender = ensure_signed(origin)?; @@ -167,6 +168,7 @@ pub mod pallet { /// - One storage read/write. /// - One event. /// # + #[pallet::call_index(1)] #[pallet::weight(70_000_000)] pub fn clear_name(origin: OriginFor) -> DispatchResult { let sender = ensure_signed(origin)?; @@ -193,6 +195,7 @@ pub mod pallet { /// - One storage read/write. /// - One event. /// # + #[pallet::call_index(2)] #[pallet::weight(70_000_000)] pub fn kill_name(origin: OriginFor, target: AccountIdLookupOf) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; @@ -220,6 +223,7 @@ pub mod pallet { /// - One storage read/write. /// - One event. /// # + #[pallet::call_index(3)] #[pallet::weight(70_000_000)] pub fn force_name( origin: OriginFor, diff --git a/frame/nis/src/lib.rs b/frame/nis/src/lib.rs index 97f727c241..dff64625a3 100644 --- a/frame/nis/src/lib.rs +++ b/frame/nis/src/lib.rs @@ -520,6 +520,7 @@ pub mod pallet { /// /// Complexities: /// - `Queues[duration].len()` (just take max). + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::place_bid_max())] pub fn place_bid( origin: OriginFor, @@ -581,6 +582,7 @@ pub mod pallet { /// /// - `amount`: The amount of the previous bid. /// - `duration`: The duration of the previous bid. + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::retract_bid(T::MaxQueueLen::get()))] pub fn retract_bid( origin: OriginFor, @@ -615,6 +617,7 @@ pub mod pallet { /// Ensure we have sufficient funding for all potential payouts. /// /// - `origin`: Must be accepted by `FundOrigin`. + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::fund_deficit())] pub fn fund_deficit(origin: OriginFor) -> DispatchResult { T::FundOrigin::ensure_origin(origin)?; @@ -636,6 +639,7 @@ pub mod pallet { /// - `index`: The index of the receipt. /// - `portion`: If `Some`, then only the given portion of the receipt should be thawed. If /// `None`, then all of it should be. + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::thaw())] pub fn thaw( origin: OriginFor, diff --git a/frame/node-authorization/src/lib.rs b/frame/node-authorization/src/lib.rs index bd1b14d10b..543ba24500 100644 --- a/frame/node-authorization/src/lib.rs +++ b/frame/node-authorization/src/lib.rs @@ -210,6 +210,7 @@ pub mod pallet { /// May only be called from `T::AddOrigin`. /// /// - `node`: identifier of the node. + #[pallet::call_index(0)] #[pallet::weight((T::WeightInfo::add_well_known_node(), DispatchClass::Operational))] pub fn add_well_known_node( origin: OriginFor, @@ -239,6 +240,7 @@ pub mod pallet { /// May only be called from `T::RemoveOrigin`. /// /// - `node`: identifier of the node. + #[pallet::call_index(1)] #[pallet::weight((T::WeightInfo::remove_well_known_node(), DispatchClass::Operational))] pub fn remove_well_known_node(origin: OriginFor, node: PeerId) -> DispatchResult { T::RemoveOrigin::ensure_origin(origin)?; @@ -264,6 +266,7 @@ pub mod pallet { /// /// - `remove`: the node which will be moved out from the list. /// - `add`: the node which will be put in the list. + #[pallet::call_index(2)] #[pallet::weight((T::WeightInfo::swap_well_known_node(), DispatchClass::Operational))] pub fn swap_well_known_node( origin: OriginFor, @@ -300,6 +303,7 @@ pub mod pallet { /// May only be called from `T::ResetOrigin`. /// /// - `nodes`: the new nodes for the allow list. + #[pallet::call_index(3)] #[pallet::weight((T::WeightInfo::reset_well_known_nodes(), DispatchClass::Operational))] pub fn reset_well_known_nodes( origin: OriginFor, @@ -318,6 +322,7 @@ pub mod pallet { /// PeerId, so claim it right away! /// /// - `node`: identifier of the node. + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::claim_node())] pub fn claim_node(origin: OriginFor, node: PeerId) -> DispatchResult { let sender = ensure_signed(origin)?; @@ -335,6 +340,7 @@ pub mod pallet { /// needs to reach consensus among the network participants. /// /// - `node`: identifier of the node. + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::remove_claim())] pub fn remove_claim(origin: OriginFor, node: PeerId) -> DispatchResult { let sender = ensure_signed(origin)?; @@ -355,6 +361,7 @@ pub mod pallet { /// /// - `node`: identifier of the node. /// - `owner`: new owner of the node. + #[pallet::call_index(6)] #[pallet::weight(T::WeightInfo::transfer_node())] pub fn transfer_node( origin: OriginFor, @@ -378,6 +385,7 @@ pub mod pallet { /// /// - `node`: identifier of the node. /// - `connections`: additonal nodes from which the connections are allowed. + #[pallet::call_index(7)] #[pallet::weight(T::WeightInfo::add_connections())] pub fn add_connections( origin: OriginFor, @@ -412,6 +420,7 @@ pub mod pallet { /// /// - `node`: identifier of the node. /// - `connections`: additonal nodes from which the connections are not allowed anymore. + #[pallet::call_index(8)] #[pallet::weight(T::WeightInfo::remove_connections())] pub fn remove_connections( origin: OriginFor, diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 9ca9539b3d..fd533ee376 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1506,6 +1506,7 @@ pub mod pallet { /// * This call will *not* dust the member account, so the member must have at least /// `existential deposit + amount` in their account. /// * Only a pool with [`PoolState::Open`] can be joined + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::join())] pub fn join( origin: OriginFor, @@ -1563,6 +1564,7 @@ pub mod pallet { // NOTE: this transaction is implemented with the sole purpose of readability and // correctness, not optimization. We read/write several storage items multiple times instead // of just once, in the spirit reusing code. + #[pallet::call_index(1)] #[pallet::weight( T::WeightInfo::bond_extra_transfer() .max(T::WeightInfo::bond_extra_reward()) @@ -1605,6 +1607,7 @@ pub mod pallet { /// /// The member will earn rewards pro rata based on the members stake vs the sum of the /// members in the pools stake. Rewards do not "expire". + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::claim_payout())] pub fn claim_payout(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; @@ -1644,6 +1647,7 @@ pub mod pallet { /// [`Call::pool_withdraw_unbonded`] can be called to try and minimize unlocking chunks. If /// there are too many unlocking chunks, the result of this call will likely be the /// `NoMoreChunks` error from the staking system. + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::unbond())] pub fn unbond( origin: OriginFor, @@ -1719,6 +1723,7 @@ pub mod pallet { /// can be cleared by withdrawing. In the case there are too many unlocking chunks, the user /// would probably see an error like `NoMoreChunks` emitted from the staking system when /// they attempt to unbond. + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::pool_withdraw_unbonded(*num_slashing_spans))] pub fn pool_withdraw_unbonded( origin: OriginFor, @@ -1753,6 +1758,7 @@ pub mod pallet { /// # Note /// /// If the target is the depositor, the pool will be destroyed. + #[pallet::call_index(5)] #[pallet::weight( T::WeightInfo::withdraw_unbonded_kill(*num_slashing_spans) )] @@ -1874,6 +1880,7 @@ pub mod pallet { /// /// In addition to `amount`, the caller will transfer the existential deposit; so the caller /// needs at have at least `amount + existential_deposit` transferrable. + #[pallet::call_index(6)] #[pallet::weight(T::WeightInfo::create())] pub fn create( origin: OriginFor, @@ -1898,6 +1905,7 @@ pub mod pallet { /// /// same as `create` with the inclusion of /// * `pool_id` - `A valid PoolId. + #[pallet::call_index(7)] #[pallet::weight(T::WeightInfo::create())] pub fn create_with_pool_id( origin: OriginFor, @@ -1922,6 +1930,7 @@ pub mod pallet { /// /// This directly forward the call to the staking pallet, on behalf of the pool bonded /// account. + #[pallet::call_index(8)] #[pallet::weight(T::WeightInfo::nominate(validators.len() as u32))] pub fn nominate( origin: OriginFor, @@ -1944,6 +1953,7 @@ pub mod pallet { /// 1. signed by the state toggler, or the root role of the pool, /// 2. if the pool conditions to be open are NOT met (as described by `ok_to_be_open`), and /// then the state of the pool can be permissionlessly changed to `Destroying`. + #[pallet::call_index(9)] #[pallet::weight(T::WeightInfo::set_state())] pub fn set_state( origin: OriginFor, @@ -1972,6 +1982,7 @@ pub mod pallet { /// /// The dispatch origin of this call must be signed by the state toggler, or the root role /// of the pool. + #[pallet::call_index(10)] #[pallet::weight(T::WeightInfo::set_metadata(metadata.len() as u32))] pub fn set_metadata( origin: OriginFor, @@ -2003,6 +2014,7 @@ pub mod pallet { /// * `max_pools` - Set [`MaxPools`]. /// * `max_members` - Set [`MaxPoolMembers`]. /// * `max_members_per_pool` - Set [`MaxPoolMembersPerPool`]. + #[pallet::call_index(11)] #[pallet::weight(T::WeightInfo::set_configs())] pub fn set_configs( origin: OriginFor, @@ -2039,6 +2051,7 @@ pub mod pallet { /// /// It emits an event, notifying UIs of the role change. This event is quite relevant to /// most pool members and they should be informed of changes to pool roles. + #[pallet::call_index(12)] #[pallet::weight(T::WeightInfo::update_roles())] pub fn update_roles( origin: OriginFor, @@ -2091,6 +2104,7 @@ pub mod pallet { /// /// This directly forward the call to the staking pallet, on behalf of the pool bonded /// account. + #[pallet::call_index(13)] #[pallet::weight(T::WeightInfo::chill())] pub fn chill(origin: OriginFor, pool_id: PoolId) -> DispatchResult { let who = ensure_signed(origin)?; diff --git a/frame/preimage/src/lib.rs b/frame/preimage/src/lib.rs index 6549832c11..bf7d602057 100644 --- a/frame/preimage/src/lib.rs +++ b/frame/preimage/src/lib.rs @@ -153,6 +153,7 @@ pub mod pallet { /// /// If the preimage was previously requested, no fees or deposits are taken for providing /// the preimage. Otherwise, a deposit is taken proportional to the size of the preimage. + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::note_preimage(bytes.len() as u32))] pub fn note_preimage(origin: OriginFor, bytes: Vec) -> DispatchResultWithPostInfo { // We accept a signed origin which will pay a deposit, or a root origin where a deposit @@ -172,6 +173,7 @@ pub mod pallet { /// /// - `hash`: The hash of the preimage to be removed from the store. /// - `len`: The length of the preimage of `hash`. + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::unnote_preimage())] pub fn unnote_preimage(origin: OriginFor, hash: T::Hash) -> DispatchResult { let maybe_sender = Self::ensure_signed_or_manager(origin)?; @@ -182,6 +184,7 @@ pub mod pallet { /// /// If the preimage requests has already been provided on-chain, we unreserve any deposit /// a user may have paid, and take the control of the preimage out of their hands. + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::request_preimage())] pub fn request_preimage(origin: OriginFor, hash: T::Hash) -> DispatchResult { T::ManagerOrigin::ensure_origin(origin)?; @@ -192,6 +195,7 @@ pub mod pallet { /// Clear a previously made request for a preimage. /// /// NOTE: THIS MUST NOT BE CALLED ON `hash` MORE TIMES THAN `request_preimage`. + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::unrequest_preimage())] pub fn unrequest_preimage(origin: OriginFor, hash: T::Hash) -> DispatchResult { T::ManagerOrigin::ensure_origin(origin)?; diff --git a/frame/proxy/src/lib.rs b/frame/proxy/src/lib.rs index 5c07a2b012..d98534d16a 100644 --- a/frame/proxy/src/lib.rs +++ b/frame/proxy/src/lib.rs @@ -191,6 +191,7 @@ pub mod pallet { /// - `real`: The account that the proxy will make a call on behalf of. /// - `force_proxy_type`: Specify the exact proxy type to be used and checked for this call. /// - `call`: The call to be made by the `real` account. + #[pallet::call_index(0)] #[pallet::weight({ let di = call.get_dispatch_info(); (T::WeightInfo::proxy(T::MaxProxies::get()) @@ -224,6 +225,7 @@ pub mod pallet { /// - `proxy_type`: The permissions allowed for this proxy account. /// - `delay`: The announcement period required of the initial proxy. Will generally be /// zero. + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::add_proxy(T::MaxProxies::get()))] pub fn add_proxy( origin: OriginFor, @@ -243,6 +245,7 @@ pub mod pallet { /// Parameters: /// - `proxy`: The account that the `caller` would like to remove as a proxy. /// - `proxy_type`: The permissions currently enabled for the removed proxy account. + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::remove_proxy(T::MaxProxies::get()))] pub fn remove_proxy( origin: OriginFor, @@ -261,6 +264,7 @@ pub mod pallet { /// /// WARNING: This may be called on accounts created by `pure`, however if done, then /// the unreserved fees will be inaccessible. **All access to this account will be lost.** + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::remove_proxies(T::MaxProxies::get()))] pub fn remove_proxies(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; @@ -288,6 +292,7 @@ pub mod pallet { /// same sender, with the same parameters. /// /// Fails if there are insufficient funds to pay for deposit. + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::create_pure(T::MaxProxies::get()))] pub fn create_pure( origin: OriginFor, @@ -335,6 +340,7 @@ pub mod pallet { /// /// Fails with `NoPermission` in case the caller is not a previously created pure /// account whose `pure` call has corresponding parameters. + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::kill_pure(T::MaxProxies::get()))] pub fn kill_pure( origin: OriginFor, @@ -372,6 +378,7 @@ pub mod pallet { /// Parameters: /// - `real`: The account that the proxy will make a call on behalf of. /// - `call_hash`: The hash of the call to be made by the `real` account. + #[pallet::call_index(6)] #[pallet::weight(T::WeightInfo::announce(T::MaxPending::get(), T::MaxProxies::get()))] pub fn announce( origin: OriginFor, @@ -421,6 +428,7 @@ pub mod pallet { /// Parameters: /// - `real`: The account that the proxy will make a call on behalf of. /// - `call_hash`: The hash of the call to be made by the `real` account. + #[pallet::call_index(7)] #[pallet::weight(T::WeightInfo::remove_announcement( T::MaxPending::get(), T::MaxProxies::get() @@ -447,6 +455,7 @@ pub mod pallet { /// Parameters: /// - `delegate`: The account that previously announced the call. /// - `call_hash`: The hash of the call to be made. + #[pallet::call_index(8)] #[pallet::weight(T::WeightInfo::reject_announcement( T::MaxPending::get(), T::MaxProxies::get() @@ -476,6 +485,7 @@ pub mod pallet { /// - `real`: The account that the proxy will make a call on behalf of. /// - `force_proxy_type`: Specify the exact proxy type to be used and checked for this call. /// - `call`: The call to be made by the `real` account. + #[pallet::call_index(9)] #[pallet::weight({ let di = call.get_dispatch_info(); (T::WeightInfo::proxy_announced(T::MaxPending::get(), T::MaxProxies::get()) diff --git a/frame/ranked-collective/src/lib.rs b/frame/ranked-collective/src/lib.rs index 33aed27049..b057a57508 100644 --- a/frame/ranked-collective/src/lib.rs +++ b/frame/ranked-collective/src/lib.rs @@ -470,6 +470,7 @@ pub mod pallet { /// - `rank`: The rank to give the new member. /// /// Weight: `O(1)` + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::add_member())] pub fn add_member(origin: OriginFor, who: AccountIdLookupOf) -> DispatchResult { let _ = T::PromoteOrigin::ensure_origin(origin)?; @@ -483,6 +484,7 @@ pub mod pallet { /// - `who`: Account of existing member. /// /// Weight: `O(1)` + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::promote_member(0))] pub fn promote_member(origin: OriginFor, who: AccountIdLookupOf) -> DispatchResult { let max_rank = T::PromoteOrigin::ensure_origin(origin)?; @@ -497,6 +499,7 @@ pub mod pallet { /// - `who`: Account of existing member of rank greater than zero. /// /// Weight: `O(1)`, less if the member's index is highest in its rank. + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::demote_member(0))] pub fn demote_member(origin: OriginFor, who: AccountIdLookupOf) -> DispatchResult { let max_rank = T::DemoteOrigin::ensure_origin(origin)?; @@ -528,6 +531,7 @@ pub mod pallet { /// - `min_rank`: The rank of the member or greater. /// /// Weight: `O(min_rank)`. + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::remove_member(*min_rank as u32))] pub fn remove_member( origin: OriginFor, @@ -562,6 +566,7 @@ pub mod pallet { /// fee. /// /// Weight: `O(1)`, less if there was no previous vote on the poll by the member. + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::vote())] pub fn vote( origin: OriginFor, @@ -618,6 +623,7 @@ pub mod pallet { /// Transaction fees are waived if the operation is successful. /// /// Weight `O(max)` (less if there are fewer items to remove than `max`). + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::cleanup_poll(*max))] pub fn cleanup_poll( origin: OriginFor, diff --git a/frame/recovery/src/lib.rs b/frame/recovery/src/lib.rs index 18d3d48dc0..9c57ca79d2 100644 --- a/frame/recovery/src/lib.rs +++ b/frame/recovery/src/lib.rs @@ -374,6 +374,7 @@ pub mod pallet { /// Parameters: /// - `account`: The recovered account you want to make a call on-behalf-of. /// - `call`: The call you want to make with the recovered account. + #[pallet::call_index(0)] #[pallet::weight({ let dispatch_info = call.get_dispatch_info(); ( @@ -403,6 +404,7 @@ pub mod pallet { /// Parameters: /// - `lost`: The "lost account" to be recovered. /// - `rescuer`: The "rescuer account" which can call as the lost account. + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::set_recovered())] pub fn set_recovered( origin: OriginFor, @@ -437,6 +439,7 @@ pub mod pallet { /// friends. /// - `delay_period`: The number of blocks after a recovery attempt is initialized that /// needs to pass before the account can be recovered. + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::create_recovery(friends.len() as u32))] pub fn create_recovery( origin: OriginFor, @@ -488,6 +491,7 @@ pub mod pallet { /// Parameters: /// - `account`: The lost account that you want to recover. This account needs to be /// recoverable (i.e. have a recovery configuration). + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::initiate_recovery())] pub fn initiate_recovery( origin: OriginFor, @@ -532,6 +536,7 @@ pub mod pallet { /// /// The combination of these two parameters must point to an active recovery /// process. + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::vouch_recovery(T::MaxFriends::get()))] pub fn vouch_recovery( origin: OriginFor, @@ -575,6 +580,7 @@ pub mod pallet { /// Parameters: /// - `account`: The lost account that you want to claim has been successfully recovered by /// you. + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::claim_recovery(T::MaxFriends::get()))] pub fn claim_recovery( origin: OriginFor, @@ -622,6 +628,7 @@ pub mod pallet { /// /// Parameters: /// - `rescuer`: The account trying to rescue this recoverable account. + #[pallet::call_index(6)] #[pallet::weight(T::WeightInfo::close_recovery(T::MaxFriends::get()))] pub fn close_recovery( origin: OriginFor, @@ -659,6 +666,7 @@ pub mod pallet { /// /// The dispatch origin for this call must be _Signed_ and must be a /// recoverable account (i.e. has a recovery configuration). + #[pallet::call_index(7)] #[pallet::weight(T::WeightInfo::remove_recovery(T::MaxFriends::get()))] pub fn remove_recovery(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; @@ -681,6 +689,7 @@ pub mod pallet { /// /// Parameters: /// - `account`: The recovered account you are able to call on-behalf-of. + #[pallet::call_index(8)] #[pallet::weight(T::WeightInfo::cancel_recovered())] pub fn cancel_recovered( origin: OriginFor, diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index 2bb01baa0c..0b846faf88 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -397,6 +397,7 @@ pub mod pallet { /// - `enactment_moment`: The moment that the proposal should be enacted. /// /// Emits `Submitted`. + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::submit())] pub fn submit( origin: OriginFor, @@ -444,6 +445,7 @@ pub mod pallet { /// posted. /// /// Emits `DecisionDepositPlaced`. + #[pallet::call_index(1)] #[pallet::weight(ServiceBranch::max_weight_of_deposit::())] pub fn place_decision_deposit( origin: OriginFor, @@ -471,6 +473,7 @@ pub mod pallet { /// refunded. /// /// Emits `DecisionDepositRefunded`. + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::refund_decision_deposit())] pub fn refund_decision_deposit( origin: OriginFor, @@ -500,6 +503,7 @@ pub mod pallet { /// - `index`: The index of the referendum to be cancelled. /// /// Emits `Cancelled`. + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::cancel())] pub fn cancel(origin: OriginFor, index: ReferendumIndex) -> DispatchResult { T::CancelOrigin::ensure_origin(origin)?; @@ -524,6 +528,7 @@ pub mod pallet { /// - `index`: The index of the referendum to be cancelled. /// /// Emits `Killed` and `DepositSlashed`. + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::kill())] pub fn kill(origin: OriginFor, index: ReferendumIndex) -> DispatchResult { T::KillOrigin::ensure_origin(origin)?; @@ -544,6 +549,7 @@ pub mod pallet { /// /// - `origin`: must be `Root`. /// - `index`: the referendum to be advanced. + #[pallet::call_index(5)] #[pallet::weight(ServiceBranch::max_weight_of_nudge::())] pub fn nudge_referendum( origin: OriginFor, @@ -570,6 +576,7 @@ pub mod pallet { /// `DecidingCount` is not yet updated. This means that we should either: /// - begin deciding another referendum (and leave `DecidingCount` alone); or /// - decrement `DecidingCount`. + #[pallet::call_index(6)] #[pallet::weight(OneFewerDecidingBranch::max_weight::())] pub fn one_fewer_deciding( origin: OriginFor, @@ -603,6 +610,7 @@ pub mod pallet { /// refunded. /// /// Emits `SubmissionDepositRefunded`. + #[pallet::call_index(7)] #[pallet::weight(T::WeightInfo::refund_submission_deposit())] pub fn refund_submission_deposit( origin: OriginFor, diff --git a/frame/remark/src/lib.rs b/frame/remark/src/lib.rs index b61c79f7f2..80fe393c20 100644 --- a/frame/remark/src/lib.rs +++ b/frame/remark/src/lib.rs @@ -62,6 +62,7 @@ pub mod pallet { #[pallet::call] impl Pallet { /// Index and store data off chain. + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::store(remark.len() as u32))] pub fn store(origin: OriginFor, remark: Vec) -> DispatchResultWithPostInfo { ensure!(!remark.is_empty(), Error::::Empty); diff --git a/frame/root-offences/src/lib.rs b/frame/root-offences/src/lib.rs index 298fe0078a..ed039f46be 100644 --- a/frame/root-offences/src/lib.rs +++ b/frame/root-offences/src/lib.rs @@ -81,6 +81,7 @@ pub mod pallet { #[pallet::call] impl Pallet { /// Allows the `root`, for example sudo to create an offence. + #[pallet::call_index(0)] #[pallet::weight(T::DbWeight::get().reads(2))] pub fn create_offence( origin: OriginFor, diff --git a/frame/root-testing/src/lib.rs b/frame/root-testing/src/lib.rs index 25d66cfac2..da67904967 100644 --- a/frame/root-testing/src/lib.rs +++ b/frame/root-testing/src/lib.rs @@ -45,6 +45,7 @@ pub mod pallet { #[pallet::call] impl Pallet { /// A dispatch that will fill the block weight up to the given ratio. + #[pallet::call_index(0)] #[pallet::weight(*_ratio * T::BlockWeights::get().max_block)] pub fn fill_block(origin: OriginFor, _ratio: Perbill) -> DispatchResult { ensure_root(origin)?; diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index 2e0d0c6be1..d6a66c5e2c 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -297,6 +297,7 @@ pub mod pallet { #[pallet::call] impl Pallet { /// Anonymously schedule a task. + #[pallet::call_index(0)] #[pallet::weight(::WeightInfo::schedule(T::MaxScheduledPerBlock::get()))] pub fn schedule( origin: OriginFor, @@ -318,6 +319,7 @@ pub mod pallet { } /// Cancel an anonymously scheduled task. + #[pallet::call_index(1)] #[pallet::weight(::WeightInfo::cancel(T::MaxScheduledPerBlock::get()))] pub fn cancel(origin: OriginFor, when: T::BlockNumber, index: u32) -> DispatchResult { T::ScheduleOrigin::ensure_origin(origin.clone())?; @@ -327,6 +329,7 @@ pub mod pallet { } /// Schedule a named task. + #[pallet::call_index(2)] #[pallet::weight(::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get()))] pub fn schedule_named( origin: OriginFor, @@ -350,6 +353,7 @@ pub mod pallet { } /// Cancel a named scheduled task. + #[pallet::call_index(3)] #[pallet::weight(::WeightInfo::cancel_named(T::MaxScheduledPerBlock::get()))] pub fn cancel_named(origin: OriginFor, id: TaskName) -> DispatchResult { T::ScheduleOrigin::ensure_origin(origin.clone())?; @@ -363,6 +367,7 @@ pub mod pallet { /// # /// Same as [`schedule`]. /// # + #[pallet::call_index(4)] #[pallet::weight(::WeightInfo::schedule(T::MaxScheduledPerBlock::get()))] pub fn schedule_after( origin: OriginFor, @@ -388,6 +393,7 @@ pub mod pallet { /// # /// Same as [`schedule_named`](Self::schedule_named). /// # + #[pallet::call_index(5)] #[pallet::weight(::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get()))] pub fn schedule_named_after( origin: OriginFor, diff --git a/frame/scheduler/src/mock.rs b/frame/scheduler/src/mock.rs index 61efdfb67b..0aaac56667 100644 --- a/frame/scheduler/src/mock.rs +++ b/frame/scheduler/src/mock.rs @@ -72,6 +72,7 @@ pub mod logger { where ::RuntimeOrigin: OriginTrait, { + #[pallet::call_index(0)] #[pallet::weight(*weight)] pub fn log(origin: OriginFor, i: u32, weight: Weight) -> DispatchResult { Self::deposit_event(Event::Logged(i, weight)); @@ -81,6 +82,7 @@ pub mod logger { Ok(()) } + #[pallet::call_index(1)] #[pallet::weight(*weight)] pub fn log_without_filter(origin: OriginFor, i: u32, weight: Weight) -> DispatchResult { Self::deposit_event(Event::Logged(i, weight)); diff --git a/frame/scored-pool/src/lib.rs b/frame/scored-pool/src/lib.rs index a015c1c568..5db9c6506d 100644 --- a/frame/scored-pool/src/lib.rs +++ b/frame/scored-pool/src/lib.rs @@ -311,6 +311,7 @@ pub mod pallet { /// /// The `index` parameter of this function must be set to /// the index of the transactor in the `Pool`. + #[pallet::call_index(0)] #[pallet::weight(0)] pub fn submit_candidacy(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; @@ -340,6 +341,7 @@ pub mod pallet { /// /// The `index` parameter of this function must be set to /// the index of the transactor in the `Pool`. + #[pallet::call_index(1)] #[pallet::weight(0)] pub fn withdraw_candidacy(origin: OriginFor, index: u32) -> DispatchResult { let who = ensure_signed(origin)?; @@ -358,6 +360,7 @@ pub mod pallet { /// /// The `index` parameter of this function must be set to /// the index of `dest` in the `Pool`. + #[pallet::call_index(2)] #[pallet::weight(0)] pub fn kick( origin: OriginFor, @@ -382,6 +385,7 @@ pub mod pallet { /// /// The `index` parameter of this function must be set to /// the index of the `dest` in the `Pool`. + #[pallet::call_index(3)] #[pallet::weight(0)] pub fn score( origin: OriginFor, @@ -421,6 +425,7 @@ pub mod pallet { /// (this happens each `Period`). /// /// May only be called from root. + #[pallet::call_index(4)] #[pallet::weight(0)] pub fn change_member_count(origin: OriginFor, count: u32) -> DispatchResult { ensure_root(origin)?; diff --git a/frame/session/src/lib.rs b/frame/session/src/lib.rs index 7b97a20860..4e2caf5e08 100644 --- a/frame/session/src/lib.rs +++ b/frame/session/src/lib.rs @@ -595,6 +595,7 @@ pub mod pallet { /// - DbReads per key id: `KeyOwner` /// - DbWrites per key id: `KeyOwner` /// # + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::set_keys())] pub fn set_keys(origin: OriginFor, keys: T::Keys, proof: Vec) -> DispatchResult { let who = ensure_signed(origin)?; @@ -620,6 +621,7 @@ pub mod pallet { /// - DbWrites: `NextKeys`, `origin account` /// - DbWrites per key id: `KeyOwner` /// # + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::purge_keys())] pub fn purge_keys(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index 73a09490ea..0edf00ff80 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -711,6 +711,7 @@ pub mod pallet { /// /// Total Complexity: O(M + B + C + logM + logB + X) /// # + #[pallet::call_index(0)] #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn bid(origin: OriginFor, value: BalanceOf) -> DispatchResult { let who = ensure_signed(origin)?; @@ -750,6 +751,7 @@ pub mod pallet { /// /// Total Complexity: O(B + X) /// # + #[pallet::call_index(1)] #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn unbid(origin: OriginFor, pos: u32) -> DispatchResult { let who = ensure_signed(origin)?; @@ -822,6 +824,7 @@ pub mod pallet { /// /// Total Complexity: O(M + B + C + logM + logB + X) /// # + #[pallet::call_index(2)] #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn vouch( origin: OriginFor, @@ -873,6 +876,7 @@ pub mod pallet { /// /// Total Complexity: O(B) /// # + #[pallet::call_index(3)] #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn unvouch(origin: OriginFor, pos: u32) -> DispatchResult { let voucher = ensure_signed(origin)?; @@ -914,6 +918,7 @@ pub mod pallet { /// /// Total Complexity: O(M + logM + C) /// # + #[pallet::call_index(4)] #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn vote( origin: OriginFor, @@ -950,6 +955,7 @@ pub mod pallet { /// /// Total Complexity: O(M + logM) /// # + #[pallet::call_index(5)] #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn defender_vote(origin: OriginFor, approve: bool) -> DispatchResult { let voter = ensure_signed(origin)?; @@ -984,6 +990,7 @@ pub mod pallet { /// /// Total Complexity: O(M + logM + P + X) /// # + #[pallet::call_index(6)] #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn payout(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; @@ -1026,6 +1033,7 @@ pub mod pallet { /// /// Total Complexity: O(1) /// # + #[pallet::call_index(7)] #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn found( origin: OriginFor, @@ -1060,6 +1068,7 @@ pub mod pallet { /// /// Total Complexity: O(1) /// # + #[pallet::call_index(8)] #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn unfound(origin: OriginFor) -> DispatchResult { let founder = ensure_signed(origin)?; @@ -1105,6 +1114,7 @@ pub mod pallet { /// /// Total Complexity: O(M + logM + B) /// # + #[pallet::call_index(9)] #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn judge_suspended_member( origin: OriginFor, @@ -1182,6 +1192,7 @@ pub mod pallet { /// /// Total Complexity: O(M + logM + B + X) /// # + #[pallet::call_index(10)] #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn judge_suspended_candidate( origin: OriginFor, @@ -1255,6 +1266,7 @@ pub mod pallet { /// /// Total Complexity: O(1) /// # + #[pallet::call_index(11)] #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn set_max_members(origin: OriginFor, max: u32) -> DispatchResult { ensure_root(origin)?; diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 8fddba2150..9dc39dd4a2 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -831,6 +831,7 @@ pub mod pallet { /// unless the `origin` falls below _existential deposit_ and gets removed as dust. /// ------------------ /// # + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::bond())] pub fn bond( origin: OriginFor, @@ -900,6 +901,7 @@ pub mod pallet { /// - Independent of the arguments. Insignificant complexity. /// - O(1). /// # + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::bond_extra())] pub fn bond_extra( origin: OriginFor, @@ -953,6 +955,7 @@ pub mod pallet { /// Emits `Unbonded`. /// /// See also [`Call::withdraw_unbonded`]. + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::unbond())] pub fn unbond( origin: OriginFor, @@ -1032,6 +1035,7 @@ pub mod pallet { /// Complexity O(S) where S is the number of slashing spans to remove /// NOTE: Weight annotation is the kill scenario, we refund otherwise. /// # + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::withdraw_unbonded_kill(*num_slashing_spans))] pub fn withdraw_unbonded( origin: OriginFor, @@ -1079,6 +1083,7 @@ pub mod pallet { /// Effects will be felt at the beginning of the next era. /// /// The dispatch origin for this call must be _Signed_ by the controller, not the stash. + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::validate())] pub fn validate(origin: OriginFor, prefs: ValidatorPrefs) -> DispatchResult { let controller = ensure_signed(origin)?; @@ -1122,6 +1127,7 @@ pub mod pallet { /// which is capped at CompactAssignments::LIMIT (T::MaxNominations). /// - Both the reads and writes follow a similar pattern. /// # + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::nominate(targets.len() as u32))] pub fn nominate( origin: OriginFor, @@ -1190,6 +1196,7 @@ pub mod pallet { /// - Contains one read. /// - Writes are limited to the `origin` account key. /// # + #[pallet::call_index(6)] #[pallet::weight(T::WeightInfo::chill())] pub fn chill(origin: OriginFor) -> DispatchResult { let controller = ensure_signed(origin)?; @@ -1214,6 +1221,7 @@ pub mod pallet { /// - Read: Ledger /// - Write: Payee /// # + #[pallet::call_index(7)] #[pallet::weight(T::WeightInfo::set_payee())] pub fn set_payee( origin: OriginFor, @@ -1242,6 +1250,7 @@ pub mod pallet { /// - Read: Bonded, Ledger New Controller, Ledger Old Controller /// - Write: Bonded, Ledger New Controller, Ledger Old Controller /// # + #[pallet::call_index(8)] #[pallet::weight(T::WeightInfo::set_controller())] pub fn set_controller( origin: OriginFor, @@ -1270,6 +1279,7 @@ pub mod pallet { /// Weight: O(1) /// Write: Validator Count /// # + #[pallet::call_index(9)] #[pallet::weight(T::WeightInfo::set_validator_count())] pub fn set_validator_count( origin: OriginFor, @@ -1294,6 +1304,7 @@ pub mod pallet { /// # /// Same as [`Self::set_validator_count`]. /// # + #[pallet::call_index(10)] #[pallet::weight(T::WeightInfo::set_validator_count())] pub fn increase_validator_count( origin: OriginFor, @@ -1319,6 +1330,7 @@ pub mod pallet { /// # /// Same as [`Self::set_validator_count`]. /// # + #[pallet::call_index(11)] #[pallet::weight(T::WeightInfo::set_validator_count())] pub fn scale_validator_count(origin: OriginFor, factor: Percent) -> DispatchResult { ensure_root(origin)?; @@ -1349,6 +1361,7 @@ pub mod pallet { /// - Weight: O(1) /// - Write: ForceEra /// # + #[pallet::call_index(12)] #[pallet::weight(T::WeightInfo::force_no_eras())] pub fn force_no_eras(origin: OriginFor) -> DispatchResult { ensure_root(origin)?; @@ -1372,6 +1385,7 @@ pub mod pallet { /// - Weight: O(1) /// - Write ForceEra /// # + #[pallet::call_index(13)] #[pallet::weight(T::WeightInfo::force_new_era())] pub fn force_new_era(origin: OriginFor) -> DispatchResult { ensure_root(origin)?; @@ -1382,6 +1396,7 @@ pub mod pallet { /// Set the validators who cannot be slashed (if any). /// /// The dispatch origin must be Root. + #[pallet::call_index(14)] #[pallet::weight(T::WeightInfo::set_invulnerables(invulnerables.len() as u32))] pub fn set_invulnerables( origin: OriginFor, @@ -1395,6 +1410,7 @@ pub mod pallet { /// Force a current staker to become completely unstaked, immediately. /// /// The dispatch origin must be Root. + #[pallet::call_index(15)] #[pallet::weight(T::WeightInfo::force_unstake(*num_slashing_spans))] pub fn force_unstake( origin: OriginFor, @@ -1420,6 +1436,7 @@ pub mod pallet { /// The election process starts multiple blocks before the end of the era. /// If this is called just before a new era is triggered, the election process may not /// have enough blocks to get a result. + #[pallet::call_index(16)] #[pallet::weight(T::WeightInfo::force_new_era_always())] pub fn force_new_era_always(origin: OriginFor) -> DispatchResult { ensure_root(origin)?; @@ -1432,6 +1449,7 @@ pub mod pallet { /// Can be called by the `T::SlashCancelOrigin`. /// /// Parameters: era and indices of the slashes for that era to kill. + #[pallet::call_index(17)] #[pallet::weight(T::WeightInfo::cancel_deferred_slash(slash_indices.len() as u32))] pub fn cancel_deferred_slash( origin: OriginFor, @@ -1477,6 +1495,7 @@ pub mod pallet { /// NOTE: weights are assuming that payouts are made to alive stash account (Staked). /// Paying even a dead controller is cheaper weight-wise. We don't do any refunds here. /// # + #[pallet::call_index(18)] #[pallet::weight(T::WeightInfo::payout_stakers_alive_staked( T::MaxNominatorRewardedPerValidator::get() ))] @@ -1498,6 +1517,7 @@ pub mod pallet { /// - Bounded by `MaxUnlockingChunks`. /// - Storage changes: Can't increase storage, only decrease it. /// # + #[pallet::call_index(19)] #[pallet::weight(T::WeightInfo::rebond(T::MaxUnlockingChunks::get() as u32))] pub fn rebond( origin: OriginFor, @@ -1542,6 +1562,7 @@ pub mod pallet { /// It can be called by anyone, as long as `stash` meets the above requirements. /// /// Refunds the transaction fees upon successful execution. + #[pallet::call_index(20)] #[pallet::weight(T::WeightInfo::reap_stash(*num_slashing_spans))] pub fn reap_stash( origin: OriginFor, @@ -1574,6 +1595,7 @@ pub mod pallet { /// /// Note: Making this call only makes sense if you first set the validator preferences to /// block any further nominations. + #[pallet::call_index(21)] #[pallet::weight(T::WeightInfo::kick(who.len() as u32))] pub fn kick(origin: OriginFor, who: Vec>) -> DispatchResult { let controller = ensure_signed(origin)?; @@ -1621,6 +1643,7 @@ pub mod pallet { /// to kick people under the new limits, `chill_other` should be called. // We assume the worst case for this call is either: all items are set or all items are // removed. + #[pallet::call_index(22)] #[pallet::weight( T::WeightInfo::set_staking_configs_all_set() .max(T::WeightInfo::set_staking_configs_all_remove()) @@ -1681,6 +1704,7 @@ pub mod pallet { /// /// This can be helpful if bond requirements are updated, and we need to remove old users /// who do not satisfy these requirements. + #[pallet::call_index(23)] #[pallet::weight(T::WeightInfo::chill_other())] pub fn chill_other(origin: OriginFor, controller: T::AccountId) -> DispatchResult { // Anyone can call this function. @@ -1743,6 +1767,7 @@ pub mod pallet { /// Force a validator to have at least the minimum commission. This will not affect a /// validator who already has a commission greater than or equal to the minimum. Any account /// can call this. + #[pallet::call_index(24)] #[pallet::weight(T::WeightInfo::force_apply_min_commission())] pub fn force_apply_min_commission( origin: OriginFor, diff --git a/frame/state-trie-migration/src/lib.rs b/frame/state-trie-migration/src/lib.rs index aab92e678e..823ea08a0b 100644 --- a/frame/state-trie-migration/src/lib.rs +++ b/frame/state-trie-migration/src/lib.rs @@ -546,6 +546,7 @@ pub mod pallet { /// Control the automatic migration. /// /// The dispatch origin of this call must be [`Config::ControlOrigin`]. + #[pallet::call_index(0)] #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))] pub fn control_auto_migration( origin: OriginFor, @@ -577,6 +578,7 @@ pub mod pallet { /// Based on the documentation of [`MigrationTask::migrate_until_exhaustion`], the /// recommended way of doing this is to pass a `limit` that only bounds `count`, as the /// `size` limit can always be overwritten. + #[pallet::call_index(1)] #[pallet::weight( // the migration process Pallet::::dynamic_weight(limits.item, * real_size_upper) @@ -648,6 +650,7 @@ pub mod pallet { /// /// This does not affect the global migration process tracker ([`MigrationProcess`]), and /// should only be used in case any keys are leftover due to a bug. + #[pallet::call_index(2)] #[pallet::weight( T::WeightInfo::migrate_custom_top_success() .max(T::WeightInfo::migrate_custom_top_fail()) @@ -704,6 +707,7 @@ pub mod pallet { /// /// This does not affect the global migration process tracker ([`MigrationProcess`]), and /// should only be used in case any keys are leftover due to a bug. + #[pallet::call_index(3)] #[pallet::weight( T::WeightInfo::migrate_custom_child_success() .max(T::WeightInfo::migrate_custom_child_fail()) @@ -764,6 +768,7 @@ pub mod pallet { } /// Set the maximum limit of the signed migration. + #[pallet::call_index(4)] #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))] pub fn set_signed_max_limits( origin: OriginFor, @@ -783,6 +788,7 @@ pub mod pallet { /// /// In case you mess things up, you can also, in principle, use this to reset the migration /// process. + #[pallet::call_index(5)] #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))] pub fn force_set_progress( origin: OriginFor, diff --git a/frame/sudo/src/lib.rs b/frame/sudo/src/lib.rs index c18ced8911..0867f24b16 100644 --- a/frame/sudo/src/lib.rs +++ b/frame/sudo/src/lib.rs @@ -148,6 +148,7 @@ pub mod pallet { /// - One DB write (event). /// - Weight of derivative `call` execution + 10,000. /// # + #[pallet::call_index(0)] #[pallet::weight({ let dispatch_info = call.get_dispatch_info(); (dispatch_info.weight, dispatch_info.class) @@ -176,6 +177,7 @@ pub mod pallet { /// - O(1). /// - The weight of this call is defined by the caller. /// # + #[pallet::call_index(1)] #[pallet::weight((*_weight, call.get_dispatch_info().class))] pub fn sudo_unchecked_weight( origin: OriginFor, @@ -202,6 +204,7 @@ pub mod pallet { /// - Limited storage reads. /// - One DB change. /// # + #[pallet::call_index(2)] #[pallet::weight(0)] pub fn set_key( origin: OriginFor, @@ -229,6 +232,7 @@ pub mod pallet { /// - One DB write (event). /// - Weight of derivative `call` execution + 10,000. /// # + #[pallet::call_index(3)] #[pallet::weight({ let dispatch_info = call.get_dispatch_info(); ( diff --git a/frame/sudo/src/mock.rs b/frame/sudo/src/mock.rs index db2ad4d563..639e81ceaa 100644 --- a/frame/sudo/src/mock.rs +++ b/frame/sudo/src/mock.rs @@ -49,6 +49,7 @@ pub mod logger { #[pallet::call] impl Pallet { + #[pallet::call_index(0)] #[pallet::weight(*weight)] pub fn privileged_i32_log( origin: OriginFor, @@ -62,6 +63,7 @@ pub mod logger { Ok(().into()) } + #[pallet::call_index(1)] #[pallet::weight(*weight)] pub fn non_privileged_log( origin: OriginFor, diff --git a/frame/support/test/tests/pallet.rs b/frame/support/test/tests/pallet.rs index 0fd32dad22..c0376d5aa4 100644 --- a/frame/support/test/tests/pallet.rs +++ b/frame/support/test/tests/pallet.rs @@ -192,6 +192,7 @@ pub mod pallet { T::AccountId: From + From + SomeAssociation1, { /// Doc comment put in metadata + #[pallet::call_index(0)] #[pallet::weight(Weight::from_ref_time(*_foo as u64))] pub fn foo( origin: OriginFor, @@ -206,6 +207,7 @@ pub mod pallet { } /// Doc comment put in metadata + #[pallet::call_index(1)] #[pallet::weight(1)] pub fn foo_storage_layer( _origin: OriginFor, @@ -220,6 +222,7 @@ pub mod pallet { } // Test for DispatchResult return type + #[pallet::call_index(2)] #[pallet::weight(1)] pub fn foo_no_post_info(_origin: OriginFor) -> DispatchResult { Ok(()) diff --git a/frame/support/test/tests/pallet_compatibility.rs b/frame/support/test/tests/pallet_compatibility.rs index 398137d644..300fb9a40c 100644 --- a/frame/support/test/tests/pallet_compatibility.rs +++ b/frame/support/test/tests/pallet_compatibility.rs @@ -141,6 +141,7 @@ pub mod pallet { #[pallet::call] impl Pallet { + #[pallet::call_index(0)] #[pallet::weight(>::into(new_value.clone()))] pub fn set_dummy( origin: OriginFor, diff --git a/frame/support/test/tests/pallet_compatibility_instance.rs b/frame/support/test/tests/pallet_compatibility_instance.rs index e8b5fe9fa3..79370d911b 100644 --- a/frame/support/test/tests/pallet_compatibility_instance.rs +++ b/frame/support/test/tests/pallet_compatibility_instance.rs @@ -127,6 +127,7 @@ pub mod pallet { #[pallet::call] impl, I: 'static> Pallet { + #[pallet::call_index(0)] #[pallet::weight(>::into(new_value.clone()))] pub fn set_dummy( origin: OriginFor, diff --git a/frame/support/test/tests/pallet_instance.rs b/frame/support/test/tests/pallet_instance.rs index 7e05e2ecf7..d8ad13ceda 100644 --- a/frame/support/test/tests/pallet_instance.rs +++ b/frame/support/test/tests/pallet_instance.rs @@ -82,6 +82,7 @@ pub mod pallet { #[pallet::call] impl, I: 'static> Pallet { /// Doc comment put in metadata + #[pallet::call_index(0)] #[pallet::weight(Weight::from_ref_time(*_foo as u64))] pub fn foo( origin: OriginFor, @@ -93,6 +94,7 @@ pub mod pallet { } /// Doc comment put in metadata + #[pallet::call_index(1)] #[pallet::weight(1)] pub fn foo_storage_layer( origin: OriginFor, diff --git a/frame/support/test/tests/storage_layers.rs b/frame/support/test/tests/storage_layers.rs index 6fbbb8ac67..cff81c0bea 100644 --- a/frame/support/test/tests/storage_layers.rs +++ b/frame/support/test/tests/storage_layers.rs @@ -46,6 +46,7 @@ pub mod pallet { #[pallet::call] impl Pallet { + #[pallet::call_index(0)] #[pallet::weight(1)] pub fn set_value(_origin: OriginFor, value: u32) -> DispatchResult { Value::::put(value); diff --git a/frame/system/src/lib.rs b/frame/system/src/lib.rs index f7e3849bee..b41083538a 100644 --- a/frame/system/src/lib.rs +++ b/frame/system/src/lib.rs @@ -372,6 +372,7 @@ pub mod pallet { /// # /// - `O(1)` /// # + #[pallet::call_index(0)] #[pallet::weight(T::SystemWeightInfo::remark(_remark.len() as u32))] pub fn remark(origin: OriginFor, _remark: Vec) -> DispatchResultWithPostInfo { ensure_signed_or_root(origin)?; @@ -379,6 +380,7 @@ pub mod pallet { } /// Set the number of pages in the WebAssembly environment's heap. + #[pallet::call_index(1)] #[pallet::weight((T::SystemWeightInfo::set_heap_pages(), DispatchClass::Operational))] pub fn set_heap_pages(origin: OriginFor, pages: u64) -> DispatchResultWithPostInfo { ensure_root(origin)?; @@ -399,6 +401,7 @@ pub mod pallet { /// The weight of this function is dependent on the runtime, but generally this is very /// expensive. We will treat this as a full block. /// # + #[pallet::call_index(2)] #[pallet::weight((T::BlockWeights::get().max_block, DispatchClass::Operational))] pub fn set_code(origin: OriginFor, code: Vec) -> DispatchResultWithPostInfo { ensure_root(origin)?; @@ -416,6 +419,7 @@ pub mod pallet { /// - 1 event. /// The weight of this function is dependent on the runtime. We will treat this as a full /// block. # + #[pallet::call_index(3)] #[pallet::weight((T::BlockWeights::get().max_block, DispatchClass::Operational))] pub fn set_code_without_checks( origin: OriginFor, @@ -427,6 +431,7 @@ pub mod pallet { } /// Set some items of storage. + #[pallet::call_index(4)] #[pallet::weight(( T::SystemWeightInfo::set_storage(items.len() as u32), DispatchClass::Operational, @@ -443,6 +448,7 @@ pub mod pallet { } /// Kill some items from storage. + #[pallet::call_index(5)] #[pallet::weight(( T::SystemWeightInfo::kill_storage(keys.len() as u32), DispatchClass::Operational, @@ -459,6 +465,7 @@ pub mod pallet { /// /// **NOTE:** We rely on the Root origin to provide us the number of subkeys under /// the prefix we are removing to accurately calculate the weight of this function. + #[pallet::call_index(6)] #[pallet::weight(( T::SystemWeightInfo::kill_prefix(_subkeys.saturating_add(1)), DispatchClass::Operational, @@ -474,6 +481,7 @@ pub mod pallet { } /// Make some on-chain remark and emit event. + #[pallet::call_index(7)] #[pallet::weight(T::SystemWeightInfo::remark_with_event(remark.len() as u32))] pub fn remark_with_event( origin: OriginFor, diff --git a/frame/timestamp/src/lib.rs b/frame/timestamp/src/lib.rs index 6a7f849d13..e859474c2c 100644 --- a/frame/timestamp/src/lib.rs +++ b/frame/timestamp/src/lib.rs @@ -198,6 +198,7 @@ pub mod pallet { /// `on_finalize`) /// - 1 event handler `on_timestamp_set`. Must be `O(1)`. /// # + #[pallet::call_index(0)] #[pallet::weight(( T::WeightInfo::set(), DispatchClass::Mandatory diff --git a/frame/tips/src/lib.rs b/frame/tips/src/lib.rs index 9313a26e52..dd9ebc9813 100644 --- a/frame/tips/src/lib.rs +++ b/frame/tips/src/lib.rs @@ -235,6 +235,7 @@ pub mod pallet { /// - DbReads: `Reasons`, `Tips` /// - DbWrites: `Reasons`, `Tips` /// # + #[pallet::call_index(0)] #[pallet::weight(>::WeightInfo::report_awesome(reason.len() as u32))] pub fn report_awesome( origin: OriginFor, @@ -292,6 +293,7 @@ pub mod pallet { /// - DbReads: `Tips`, `origin account` /// - DbWrites: `Reasons`, `Tips`, `origin account` /// # + #[pallet::call_index(1)] #[pallet::weight(>::WeightInfo::retract_tip())] pub fn retract_tip(origin: OriginFor, hash: T::Hash) -> DispatchResult { let who = ensure_signed(origin)?; @@ -330,6 +332,7 @@ pub mod pallet { /// - DbReads: `Tippers`, `Reasons` /// - DbWrites: `Reasons`, `Tips` /// # + #[pallet::call_index(2)] #[pallet::weight(>::WeightInfo::tip_new(reason.len() as u32, T::Tippers::max_len() as u32))] pub fn tip_new( origin: OriginFor, @@ -384,6 +387,7 @@ pub mod pallet { /// - DbReads: `Tippers`, `Tips` /// - DbWrites: `Tips` /// # + #[pallet::call_index(3)] #[pallet::weight(>::WeightInfo::tip(T::Tippers::max_len() as u32))] pub fn tip( origin: OriginFor, @@ -417,6 +421,7 @@ pub mod pallet { /// - DbReads: `Tips`, `Tippers`, `tip finder` /// - DbWrites: `Reasons`, `Tips`, `Tippers`, `tip finder` /// # + #[pallet::call_index(4)] #[pallet::weight(>::WeightInfo::close_tip(T::Tippers::max_len() as u32))] pub fn close_tip(origin: OriginFor, hash: T::Hash) -> DispatchResult { ensure_signed(origin)?; @@ -443,6 +448,7 @@ pub mod pallet { /// `T` is charged as upper bound given by `ContainsLengthBound`. /// The actual cost depends on the implementation of `T::Tippers`. /// # + #[pallet::call_index(5)] #[pallet::weight(>::WeightInfo::slash_tip(T::Tippers::max_len() as u32))] pub fn slash_tip(origin: OriginFor, hash: T::Hash) -> DispatchResult { T::RejectOrigin::ensure_origin(origin)?; diff --git a/frame/transaction-storage/src/lib.rs b/frame/transaction-storage/src/lib.rs index 07144c5617..cda7610efd 100644 --- a/frame/transaction-storage/src/lib.rs +++ b/frame/transaction-storage/src/lib.rs @@ -188,6 +188,7 @@ pub mod pallet { /// - n*log(n) of data size, as all data is pushed to an in-memory trie. /// Additionally contains a DB write. /// # + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::store(data.len() as u32))] pub fn store(origin: OriginFor, data: Vec) -> DispatchResult { ensure!(data.len() > 0, Error::::EmptyTransaction); @@ -236,6 +237,7 @@ pub mod pallet { /// # /// - Constant. /// # + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::renew())] pub fn renew( origin: OriginFor, @@ -281,6 +283,7 @@ pub mod pallet { /// There's a DB read for each transaction. /// Here we assume a maximum of 100 probed transactions. /// # + #[pallet::call_index(2)] #[pallet::weight((T::WeightInfo::check_proof_max(), DispatchClass::Mandatory))] pub fn check_proof( origin: OriginFor, diff --git a/frame/treasury/src/lib.rs b/frame/treasury/src/lib.rs index 4aa00c3485..0ffc53d8b7 100644 --- a/frame/treasury/src/lib.rs +++ b/frame/treasury/src/lib.rs @@ -350,6 +350,7 @@ pub mod pallet { /// - DbReads: `ProposalCount`, `origin account` /// - DbWrites: `ProposalCount`, `Proposals`, `origin account` /// # + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::propose_spend())] pub fn propose_spend( origin: OriginFor, @@ -380,6 +381,7 @@ pub mod pallet { /// - DbReads: `Proposals`, `rejected proposer account` /// - DbWrites: `Proposals`, `rejected proposer account` /// # + #[pallet::call_index(1)] #[pallet::weight((T::WeightInfo::reject_proposal(), DispatchClass::Operational))] pub fn reject_proposal( origin: OriginFor, @@ -410,6 +412,7 @@ pub mod pallet { /// - DbReads: `Proposals`, `Approvals` /// - DbWrite: `Approvals` /// # + #[pallet::call_index(2)] #[pallet::weight((T::WeightInfo::approve_proposal(T::MaxApprovals::get()), DispatchClass::Operational))] pub fn approve_proposal( origin: OriginFor, @@ -431,6 +434,7 @@ pub mod pallet { /// /// NOTE: For record-keeping purposes, the proposer is deemed to be equivalent to the /// beneficiary. + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::spend())] pub fn spend( origin: OriginFor, @@ -472,6 +476,7 @@ pub mod pallet { /// - `ProposalNotApproved`: The `proposal_id` supplied was not found in the approval queue, /// i.e., the proposal has not been approved. This could also mean the proposal does not /// exist altogether, thus there is no way it would have been approved in the first place. + #[pallet::call_index(4)] #[pallet::weight((T::WeightInfo::remove_approval(), DispatchClass::Operational))] pub fn remove_approval( origin: OriginFor, diff --git a/frame/uniques/src/lib.rs b/frame/uniques/src/lib.rs index 185d8fc0c8..8157817d41 100644 --- a/frame/uniques/src/lib.rs +++ b/frame/uniques/src/lib.rs @@ -449,6 +449,7 @@ pub mod pallet { /// Emits `Created` event when successful. /// /// Weight: `O(1)` + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::create())] pub fn create( origin: OriginFor, @@ -485,6 +486,7 @@ pub mod pallet { /// Emits `ForceCreated` event when successful. /// /// Weight: `O(1)` + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::force_create())] pub fn force_create( origin: OriginFor, @@ -520,6 +522,7 @@ pub mod pallet { /// - `n = witness.items` /// - `m = witness.item_metadatas` /// - `a = witness.attributes` + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::destroy( witness.items, witness.item_metadatas, @@ -555,6 +558,7 @@ pub mod pallet { /// Emits `Issued` event when successful. /// /// Weight: `O(1)` + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::mint())] pub fn mint( origin: OriginFor, @@ -584,6 +588,7 @@ pub mod pallet { /// /// Weight: `O(1)` /// Modes: `check_owner.is_some()`. + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::burn())] pub fn burn( origin: OriginFor, @@ -622,6 +627,7 @@ pub mod pallet { /// Emits `Transferred`. /// /// Weight: `O(1)` + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::transfer())] pub fn transfer( origin: OriginFor, @@ -658,6 +664,7 @@ pub mod pallet { /// is not permitted to call it. /// /// Weight: `O(items.len())` + #[pallet::call_index(6)] #[pallet::weight(T::WeightInfo::redeposit(items.len() as u32))] pub fn redeposit( origin: OriginFor, @@ -718,6 +725,7 @@ pub mod pallet { /// Emits `Frozen`. /// /// Weight: `O(1)` + #[pallet::call_index(7)] #[pallet::weight(T::WeightInfo::freeze())] pub fn freeze( origin: OriginFor, @@ -749,6 +757,7 @@ pub mod pallet { /// Emits `Thawed`. /// /// Weight: `O(1)` + #[pallet::call_index(8)] #[pallet::weight(T::WeightInfo::thaw())] pub fn thaw( origin: OriginFor, @@ -779,6 +788,7 @@ pub mod pallet { /// Emits `CollectionFrozen`. /// /// Weight: `O(1)` + #[pallet::call_index(9)] #[pallet::weight(T::WeightInfo::freeze_collection())] pub fn freeze_collection( origin: OriginFor, @@ -806,6 +816,7 @@ pub mod pallet { /// Emits `CollectionThawed`. /// /// Weight: `O(1)` + #[pallet::call_index(10)] #[pallet::weight(T::WeightInfo::thaw_collection())] pub fn thaw_collection( origin: OriginFor, @@ -835,6 +846,7 @@ pub mod pallet { /// Emits `OwnerChanged`. /// /// Weight: `O(1)` + #[pallet::call_index(11)] #[pallet::weight(T::WeightInfo::transfer_ownership())] pub fn transfer_ownership( origin: OriginFor, @@ -883,6 +895,7 @@ pub mod pallet { /// Emits `TeamChanged`. /// /// Weight: `O(1)` + #[pallet::call_index(12)] #[pallet::weight(T::WeightInfo::set_team())] pub fn set_team( origin: OriginFor, @@ -923,6 +936,7 @@ pub mod pallet { /// Emits `ApprovedTransfer` on success. /// /// Weight: `O(1)` + #[pallet::call_index(13)] #[pallet::weight(T::WeightInfo::approve_transfer())] pub fn approve_transfer( origin: OriginFor, @@ -976,6 +990,7 @@ pub mod pallet { /// Emits `ApprovalCancelled` on success. /// /// Weight: `O(1)` + #[pallet::call_index(14)] #[pallet::weight(T::WeightInfo::cancel_approval())] pub fn cancel_approval( origin: OriginFor, @@ -1028,6 +1043,7 @@ pub mod pallet { /// Emits `ItemStatusChanged` with the identity of the item. /// /// Weight: `O(1)` + #[pallet::call_index(15)] #[pallet::weight(T::WeightInfo::force_item_status())] pub fn force_item_status( origin: OriginFor, @@ -1077,6 +1093,7 @@ pub mod pallet { /// Emits `AttributeSet`. /// /// Weight: `O(1)` + #[pallet::call_index(16)] #[pallet::weight(T::WeightInfo::set_attribute())] pub fn set_attribute( origin: OriginFor, @@ -1139,6 +1156,7 @@ pub mod pallet { /// Emits `AttributeCleared`. /// /// Weight: `O(1)` + #[pallet::call_index(17)] #[pallet::weight(T::WeightInfo::clear_attribute())] pub fn clear_attribute( origin: OriginFor, @@ -1188,6 +1206,7 @@ pub mod pallet { /// Emits `MetadataSet`. /// /// Weight: `O(1)` + #[pallet::call_index(18)] #[pallet::weight(T::WeightInfo::set_metadata())] pub fn set_metadata( origin: OriginFor, @@ -1250,6 +1269,7 @@ pub mod pallet { /// Emits `MetadataCleared`. /// /// Weight: `O(1)` + #[pallet::call_index(19)] #[pallet::weight(T::WeightInfo::clear_metadata())] pub fn clear_metadata( origin: OriginFor, @@ -1299,6 +1319,7 @@ pub mod pallet { /// Emits `CollectionMetadataSet`. /// /// Weight: `O(1)` + #[pallet::call_index(20)] #[pallet::weight(T::WeightInfo::set_collection_metadata())] pub fn set_collection_metadata( origin: OriginFor, @@ -1356,6 +1377,7 @@ pub mod pallet { /// Emits `CollectionMetadataCleared`. /// /// Weight: `O(1)` + #[pallet::call_index(21)] #[pallet::weight(T::WeightInfo::clear_collection_metadata())] pub fn clear_collection_metadata( origin: OriginFor, @@ -1392,6 +1414,7 @@ pub mod pallet { /// ownership transferal. /// /// Emits `OwnershipAcceptanceChanged`. + #[pallet::call_index(22)] #[pallet::weight(T::WeightInfo::set_accept_ownership())] pub fn set_accept_ownership( origin: OriginFor, @@ -1428,6 +1451,7 @@ pub mod pallet { /// - `max_supply`: The maximum amount of items a collection could have. /// /// Emits `CollectionMaxSupplySet` event when successful. + #[pallet::call_index(23)] #[pallet::weight(T::WeightInfo::set_collection_max_supply())] pub fn set_collection_max_supply( origin: OriginFor, @@ -1467,6 +1491,7 @@ pub mod pallet { /// /// Emits `ItemPriceSet` on success if the price is not `None`. /// Emits `ItemPriceRemoved` on success if the price is `None`. + #[pallet::call_index(24)] #[pallet::weight(T::WeightInfo::set_price())] pub fn set_price( origin: OriginFor, @@ -1489,6 +1514,7 @@ pub mod pallet { /// - `bid_price`: The price the sender is willing to pay. /// /// Emits `ItemBought` on success. + #[pallet::call_index(25)] #[pallet::weight(T::WeightInfo::buy_item())] #[transactional] pub fn buy_item( diff --git a/frame/utility/src/lib.rs b/frame/utility/src/lib.rs index 00cb18e1b2..2d60ae1567 100644 --- a/frame/utility/src/lib.rs +++ b/frame/utility/src/lib.rs @@ -181,6 +181,7 @@ pub mod pallet { /// `BatchInterrupted` event is deposited, along with the number of successful calls made /// and the error of the failed call. If all were successful, then the `BatchCompleted` /// 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() @@ -254,6 +255,7 @@ pub mod pallet { /// NOTE: Prior to version *12, this was called `as_limited_sub`. /// /// The dispatch origin for this call must be _Signed_. + #[pallet::call_index(1)] #[pallet::weight({ let dispatch_info = call.get_dispatch_info(); ( @@ -302,6 +304,7 @@ pub mod pallet { /// # /// - Complexity: 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() @@ -377,6 +380,7 @@ pub mod pallet { /// - One DB write (event). /// - Weight of derivative `call` execution + T::WeightInfo::dispatch_as(). /// # + #[pallet::call_index(3)] #[pallet::weight({ let dispatch_info = call.get_dispatch_info(); ( @@ -414,6 +418,7 @@ pub mod pallet { /// # /// - Complexity: 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() @@ -481,6 +486,7 @@ pub mod pallet { /// Root origin to specify the weight of the call. /// /// The dispatch origin for this call must be _Root_. + #[pallet::call_index(5)] #[pallet::weight((*_weight, call.get_dispatch_info().class))] pub fn with_weight( origin: OriginFor, diff --git a/frame/utility/src/tests.rs b/frame/utility/src/tests.rs index d48ce139d8..f9d6a16c1a 100644 --- a/frame/utility/src/tests.rs +++ b/frame/utility/src/tests.rs @@ -53,11 +53,13 @@ pub mod example { #[pallet::call] impl Pallet { + #[pallet::call_index(0)] #[pallet::weight(*_weight)] pub fn noop(_origin: OriginFor, _weight: Weight) -> DispatchResult { Ok(()) } + #[pallet::call_index(1)] #[pallet::weight(*_start_weight)] pub fn foobar( origin: OriginFor, @@ -78,6 +80,7 @@ pub mod example { } } + #[pallet::call_index(2)] #[pallet::weight(0)] pub fn big_variant(_origin: OriginFor, _arg: [u8; 400]) -> DispatchResult { Ok(()) @@ -105,6 +108,7 @@ mod mock_democracy { #[pallet::call] impl Pallet { + #[pallet::call_index(3)] #[pallet::weight(0)] pub fn external_propose_majority(origin: OriginFor) -> DispatchResult { T::ExternalMajorityOrigin::ensure_origin(origin)?; diff --git a/frame/vesting/src/lib.rs b/frame/vesting/src/lib.rs index a92f94baf6..3439608af3 100644 --- a/frame/vesting/src/lib.rs +++ b/frame/vesting/src/lib.rs @@ -303,6 +303,7 @@ pub mod pallet { /// - Reads: Vesting Storage, Balances Locks, [Sender Account] /// - Writes: Vesting Storage, Balances Locks, [Sender Account] /// # + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::vest_locked(MaxLocksOf::::get(), T::MAX_VESTING_SCHEDULES) .max(T::WeightInfo::vest_unlocked(MaxLocksOf::::get(), T::MAX_VESTING_SCHEDULES)) )] @@ -326,6 +327,7 @@ pub mod pallet { /// - Reads: Vesting Storage, Balances Locks, Target Account /// - Writes: Vesting Storage, Balances Locks, Target Account /// # + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::vest_other_locked(MaxLocksOf::::get(), T::MAX_VESTING_SCHEDULES) .max(T::WeightInfo::vest_other_unlocked(MaxLocksOf::::get(), T::MAX_VESTING_SCHEDULES)) )] @@ -352,6 +354,7 @@ pub mod pallet { /// - Reads: Vesting Storage, Balances Locks, Target Account, [Sender Account] /// - Writes: Vesting Storage, Balances Locks, Target Account, [Sender Account] /// # + #[pallet::call_index(2)] #[pallet::weight( T::WeightInfo::vested_transfer(MaxLocksOf::::get(), T::MAX_VESTING_SCHEDULES) )] @@ -383,6 +386,7 @@ pub mod pallet { /// - Reads: Vesting Storage, Balances Locks, Target Account, Source Account /// - Writes: Vesting Storage, Balances Locks, Target Account, Source Account /// # + #[pallet::call_index(3)] #[pallet::weight( T::WeightInfo::force_vested_transfer(MaxLocksOf::::get(), T::MAX_VESTING_SCHEDULES) )] @@ -417,6 +421,7 @@ pub mod pallet { /// /// - `schedule1_index`: index of the first schedule to merge. /// - `schedule2_index`: index of the second schedule to merge. + #[pallet::call_index(4)] #[pallet::weight( T::WeightInfo::not_unlocking_merge_schedules(MaxLocksOf::::get(), T::MAX_VESTING_SCHEDULES) .max(T::WeightInfo::unlocking_merge_schedules(MaxLocksOf::::get(), T::MAX_VESTING_SCHEDULES)) diff --git a/frame/whitelist/src/lib.rs b/frame/whitelist/src/lib.rs index 1b2dc94156..8a5666331c 100644 --- a/frame/whitelist/src/lib.rs +++ b/frame/whitelist/src/lib.rs @@ -119,6 +119,7 @@ pub mod pallet { #[pallet::call] impl Pallet { + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::whitelist_call())] pub fn whitelist_call(origin: OriginFor, call_hash: PreimageHash) -> DispatchResult { T::WhitelistOrigin::ensure_origin(origin)?; @@ -136,6 +137,7 @@ pub mod pallet { Ok(()) } + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::remove_whitelisted_call())] pub fn remove_whitelisted_call( origin: OriginFor, @@ -152,6 +154,7 @@ pub mod pallet { Ok(()) } + #[pallet::call_index(2)] #[pallet::weight( T::WeightInfo::dispatch_whitelisted_call(*call_encoded_len) .saturating_add(*call_weight_witness) @@ -190,6 +193,7 @@ pub mod pallet { Ok(actual_weight.into()) } + #[pallet::call_index(3)] #[pallet::weight({ let call_weight = call.get_dispatch_info().weight; let call_len = call.encoded_size() as u32; From f3c95e63ab92154a102aca61b6e5a206c7e3b32c Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Mon, 12 Dec 2022 16:22:47 +0100 Subject: [PATCH 029/127] Pin canonincalized block (#12902) --- client/state-db/src/noncanonical.rs | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/client/state-db/src/noncanonical.rs b/client/state-db/src/noncanonical.rs index df09a9c017..7cb3017966 100644 --- a/client/state-db/src/noncanonical.rs +++ b/client/state-db/src/noncanonical.rs @@ -38,6 +38,7 @@ pub struct NonCanonicalOverlay { // would be deleted but kept around because block is pinned, ref counted. pinned: HashMap, pinned_insertions: HashMap, u32)>, + last_canon_pinned: Option, } #[cfg_attr(test, derive(PartialEq, Debug))] @@ -225,6 +226,7 @@ impl NonCanonicalOverlay { pinned: Default::default(), pinned_insertions: Default::default(), values, + last_canon_pinned: None, }) } @@ -367,6 +369,16 @@ impl NonCanonicalOverlay { .position(|overlay| overlay.hash == *hash) .ok_or(StateDbError::InvalidBlock)?; + // No failures are possible beyond this point. + + // Unpin previously canonicalized block + if let Some(prev_hash) = self.last_canon_pinned.take() { + self.unpin(&prev_hash); + } + // Force pin canonicalized block so that it is no discarded immediately + self.pin(hash); + self.last_canon_pinned = Some(hash.clone()); + let mut discarded_journals = Vec::new(); let mut discarded_blocks = Vec::new(); for (i, overlay) in level.blocks.into_iter().enumerate() { @@ -680,6 +692,7 @@ mod tests { db.commit(&overlay.insert(&h2, 11, &h1, make_changeset(&[5], &[3])).unwrap()); let mut commit = CommitSet::default(); overlay.canonicalize(&h1, &mut commit).unwrap(); + overlay.unpin(&h1); db.commit(&commit); assert_eq!(overlay.levels.len(), 1); @@ -707,15 +720,16 @@ mod tests { let mut commit = CommitSet::default(); overlay.canonicalize(&h1, &mut commit).unwrap(); db.commit(&commit); - assert!(!contains(&overlay, 5)); + assert!(contains(&overlay, 5)); assert!(contains(&overlay, 7)); assert_eq!(overlay.levels.len(), 1); - assert_eq!(overlay.parents.len(), 1); + assert_eq!(overlay.parents.len(), 2); let mut commit = CommitSet::default(); overlay.canonicalize(&h2, &mut commit).unwrap(); + assert!(!contains(&overlay, 5)); db.commit(&commit); assert_eq!(overlay.levels.len(), 0); - assert_eq!(overlay.parents.len(), 0); + assert_eq!(overlay.parents.len(), 1); assert!(db.data_eq(&make_db(&[1, 4, 6, 7, 8]))); } @@ -732,6 +746,8 @@ mod tests { let mut commit = CommitSet::default(); overlay.canonicalize(&h_1, &mut commit).unwrap(); db.commit(&commit); + // explicitly unpin last block + overlay.unpin(&h_1); assert!(!contains(&overlay, 1)); } @@ -818,6 +834,8 @@ mod tests { // canonicalize 1. 2 and all its children should be discarded let mut commit = CommitSet::default(); overlay.canonicalize(&h_1, &mut commit).unwrap(); + // explicitly unpin last block + overlay.unpin(&h_1); db.commit(&commit); assert_eq!(overlay.levels.len(), 2); assert_eq!(overlay.parents.len(), 6); @@ -838,6 +856,7 @@ mod tests { // canonicalize 1_2. 1_1 and all its children should be discarded let mut commit = CommitSet::default(); overlay.canonicalize(&h_1_2, &mut commit).unwrap(); + overlay.unpin(&h_1_2); db.commit(&commit); assert_eq!(overlay.levels.len(), 1); assert_eq!(overlay.parents.len(), 3); @@ -854,6 +873,7 @@ mod tests { // canonicalize 1_2_2 let mut commit = CommitSet::default(); overlay.canonicalize(&h_1_2_2, &mut commit).unwrap(); + overlay.unpin(&h_1_2_2); db.commit(&commit); assert_eq!(overlay.levels.len(), 0); assert_eq!(overlay.parents.len(), 0); @@ -964,6 +984,7 @@ mod tests { assert!(contains(&overlay, 1)); overlay.unpin(&h_21); assert!(!contains(&overlay, 1)); + overlay.unpin(&h_12); assert!(overlay.pinned.is_empty()); } @@ -998,6 +1019,7 @@ mod tests { let mut commit = CommitSet::default(); overlay.canonicalize(&h21, &mut commit).unwrap(); // h11 should stay in the DB + overlay.unpin(&h21); db.commit(&commit); assert!(!contains(&overlay, 21)); } From d4837cb5ede0c4a17a1aca8a72a48fbac11e9ef7 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Mon, 12 Dec 2022 17:05:13 +0000 Subject: [PATCH 030/127] Remove implicit approval chilling upon slash. (#12420) * don't read slashing spans when taking election snapshot * update cargo.toml * bring back remote test * fix merge stuff * fix npos-voters function sig * remove as much redundant diff as you can * Update frame/staking/src/pallet/mod.rs Co-authored-by: Andronik * fix * Update frame/staking/src/pallet/impls.rs * update lock * fix all tests * review comments * fmt * fix offence bench * clippy * ".git/.scripts/bench-bot.sh" pallet dev pallet_staking Co-authored-by: Andronik Co-authored-by: Ankan Co-authored-by: command-bot <> --- .../election-provider-multi-phase/src/lib.rs | 3 + frame/offences/benchmarking/src/lib.rs | 14 +- frame/staking/src/benchmarking.rs | 11 +- frame/staking/src/pallet/impls.rs | 37 +- frame/staking/src/pallet/mod.rs | 7 +- frame/staking/src/slashing.rs | 10 +- frame/staking/src/tests.rs | 330 +++++---- frame/staking/src/weights.rs | 631 +++++++++--------- utils/frame/remote-externalities/src/lib.rs | 29 +- 9 files changed, 526 insertions(+), 546 deletions(-) diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 4704eaffa0..6c4a55800f 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -2293,6 +2293,8 @@ mod tests { assert_eq!(MultiPhase::elect().unwrap_err(), ElectionError::Fallback("NoFallback.")); // phase is now emergency. assert_eq!(MultiPhase::current_phase(), Phase::Emergency); + // snapshot is still there until election finalizes. + assert!(MultiPhase::snapshot().is_some()); assert_eq!( multi_phase_events(), @@ -2318,6 +2320,7 @@ mod tests { // phase is now emergency. assert_eq!(MultiPhase::current_phase(), Phase::Emergency); assert!(MultiPhase::queued_solution().is_none()); + assert!(MultiPhase::snapshot().is_some()); // no single account can trigger this assert_noop!( diff --git a/frame/offences/benchmarking/src/lib.rs b/frame/offences/benchmarking/src/lib.rs index 555ec42882..e5ec2952f8 100644 --- a/frame/offences/benchmarking/src/lib.rs +++ b/frame/offences/benchmarking/src/lib.rs @@ -308,17 +308,20 @@ benchmarks! { let slash_amount = slash_fraction * bond_amount; let reward_amount = slash_amount.saturating_mul(1 + n) / 2; let reward = reward_amount / r; + let slash_report = |id| core::iter::once( + ::RuntimeEvent::from(StakingEvent::::SlashReported{ validator: id, fraction: slash_fraction, slash_era: 0}) + ); let slash = |id| core::iter::once( - ::RuntimeEvent::from(StakingEvent::::Slashed{staker: id, amount: BalanceOf::::from(slash_amount)}) + ::RuntimeEvent::from(StakingEvent::::Slashed{ staker: id, amount: BalanceOf::::from(slash_amount) }) ); let balance_slash = |id| core::iter::once( - ::RuntimeEvent::from(pallet_balances::Event::::Slashed{who: id, amount: slash_amount.into()}) + ::RuntimeEvent::from(pallet_balances::Event::::Slashed{ who: id, amount: slash_amount.into() }) ); let chill = |id| core::iter::once( - ::RuntimeEvent::from(StakingEvent::::Chilled{stash: id}) + ::RuntimeEvent::from(StakingEvent::::Chilled{ stash: id }) ); let balance_deposit = |id, amount: u32| - ::RuntimeEvent::from(pallet_balances::Event::::Deposit{who: id, amount: amount.into()}); + ::RuntimeEvent::from(pallet_balances::Event::::Deposit{ who: id, amount: amount.into() }); let mut first = true; let slash_events = raw_offenders.into_iter() .flat_map(|offender| { @@ -328,6 +331,7 @@ benchmarks! { }); let mut events = chill(offender.stash.clone()).map(Into::into) + .chain(slash_report(offender.stash.clone()).map(Into::into)) .chain(balance_slash(offender.stash.clone()).map(Into::into)) .chain(slash(offender.stash).map(Into::into)) .chain(nom_slashes) @@ -407,6 +411,7 @@ benchmarks! { System::::event_count(), 0 + 1 // offence + 3 // reporter (reward + endowment) + + 1 // offenders reported + 2 // offenders slashed + 1 // offenders chilled + 2 * n // nominators slashed @@ -443,6 +448,7 @@ benchmarks! { System::::event_count(), 0 + 1 // offence + 3 // reporter (reward + endowment) + + 1 // offenders reported + 2 // offenders slashed + 1 // offenders chilled + 2 * n // nominators slashed diff --git a/frame/staking/src/benchmarking.rs b/frame/staking/src/benchmarking.rs index dcb861e2ce..8409b5413f 100644 --- a/frame/staking/src/benchmarking.rs +++ b/frame/staking/src/benchmarking.rs @@ -792,12 +792,10 @@ benchmarks! { } get_npos_voters { - // number of validator intention. + // number of validator intention. we will iterate all of them. let v in (MaxValidators::::get() / 2) .. MaxValidators::::get(); - // number of nominator intention. + // number of nominator intention. we will iterate all of them. let n in (MaxNominators::::get() / 2) .. MaxNominators::::get(); - // total number of slashing spans. Assigned to validators randomly. - let s in 1 .. 20; let validators = create_validators_with_nominators_for_era::( v, n, T::MaxNominations::get() as usize, false, None @@ -806,9 +804,8 @@ benchmarks! { .map(|v| T::Lookup::lookup(v).unwrap()) .collect::>(); - (0..s).for_each(|index| { - add_slashing_spans::(&validators[index as usize], 10); - }); + assert_eq!(Validators::::count(), v); + assert_eq!(Nominators::::count(), n); let num_voters = (v + n) as usize; }: { diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index c22a2bd2d1..6729a2ca32 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -40,7 +40,7 @@ use sp_staking::{ offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}, EraIndex, SessionIndex, Stake, StakingInterface, }; -use sp_std::{collections::btree_map::BTreeMap, prelude::*}; +use sp_std::prelude::*; use crate::{ log, slashing, weights::WeightInfo, ActiveEraInfo, BalanceOf, EraPayout, Exposure, ExposureOf, @@ -351,6 +351,7 @@ impl Pallet { } } + /// Start a new era. It does: /// /// * Increment `active_era.index`, /// * reset `active_era.start`, @@ -704,11 +705,6 @@ impl Pallet { /// `maybe_max_len` can imposes a cap on the number of voters returned; /// /// This function is self-weighing as [`DispatchClass::Mandatory`]. - /// - /// ### Slashing - /// - /// All votes that have been submitted before the last non-zero slash of the corresponding - /// target are *auto-chilled*, but still count towards the limit imposed by `maybe_max_len`. pub fn get_npos_voters(maybe_max_len: Option) -> Vec> { let max_allowed_len = { let all_voter_count = T::VoterList::count() as usize; @@ -719,7 +715,6 @@ impl Pallet { // cache a few things. let weight_of = Self::weight_of_fn(); - let slashing_spans = >::iter().collect::>(); let mut voters_seen = 0u32; let mut validators_taken = 0u32; @@ -737,18 +732,12 @@ impl Pallet { None => break, }; - if let Some(Nominations { submitted_in, mut targets, suppressed: _ }) = - >::get(&voter) - { - // if this voter is a nominator: - targets.retain(|stash| { - slashing_spans - .get(stash) - .map_or(true, |spans| submitted_in >= spans.last_nonzero_slash()) - }); - if !targets.len().is_zero() { + if let Some(Nominations { targets, .. }) = >::get(&voter) { + if !targets.is_empty() { all_voters.push((voter.clone(), weight_of(&voter), targets)); nominators_taken.saturating_inc(); + } else { + // Technically should never happen, but not much we can do about it. } } else if Validators::::contains_key(&voter) { // if this voter is a validator: @@ -771,18 +760,14 @@ impl Pallet { warn, "DEFENSIVE: invalid item in `VoterList`: {:?}, this nominator probably has too many nominations now", voter - ) + ); } } // all_voters should have not re-allocated. debug_assert!(all_voters.capacity() == max_allowed_len); - Self::register_weight(T::WeightInfo::get_npos_voters( - validators_taken, - nominators_taken, - slashing_spans.len() as u32, - )); + Self::register_weight(T::WeightInfo::get_npos_voters(validators_taken, nominators_taken)); log!( info, @@ -1285,6 +1270,12 @@ where disable_strategy, }); + Self::deposit_event(Event::::SlashReported { + validator: stash.clone(), + fraction: *slash_fraction, + slash_era, + }); + if let Some(mut unapplied) = unapplied { let nominators_len = unapplied.others.len() as u64; let reporters_len = details.reporters.len() as u64; diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 9dc39dd4a2..fda455ca3c 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -517,7 +517,7 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn slashing_spans)] #[pallet::unbounded] - pub(crate) type SlashingSpans = + pub type SlashingSpans = StorageMap<_, Twox64Concat, T::AccountId, slashing::SlashingSpans>; /// Records information about the maximum slash of a stash within a slashing span, @@ -671,8 +671,11 @@ pub mod pallet { EraPaid { era_index: EraIndex, validator_payout: BalanceOf, remainder: BalanceOf }, /// The nominator has been rewarded by this amount. Rewarded { stash: T::AccountId, amount: BalanceOf }, - /// One staker (and potentially its nominators) has been slashed by the given amount. + /// A staker (validator or nominator) has been slashed by the given amount. Slashed { staker: T::AccountId, amount: BalanceOf }, + /// A slash for the given validator, for the given percentage of their stake, at the given + /// era as been reported. + SlashReported { validator: T::AccountId, fraction: Perbill, slash_era: EraIndex }, /// An old slashing report from a prior era was discarded because it could /// not be processed. OldSlashingReportDiscarded { session_index: SessionIndex }, diff --git a/frame/staking/src/slashing.rs b/frame/staking/src/slashing.rs index a1900136d6..aeea0a1a58 100644 --- a/frame/staking/src/slashing.rs +++ b/frame/staking/src/slashing.rs @@ -239,9 +239,9 @@ pub(crate) fn compute_slash( return None } - let (prior_slash_p, _era_slash) = + let prior_slash_p = as Store>::ValidatorSlashInEra::get(¶ms.slash_era, params.stash) - .unwrap_or((Perbill::zero(), Zero::zero())); + .map_or(Zero::zero(), |(prior_slash_proportion, _)| prior_slash_proportion); // compare slash proportions rather than slash values to avoid issues due to rounding // error. @@ -390,9 +390,7 @@ fn slash_nominators( let mut era_slash = as Store>::NominatorSlashInEra::get(¶ms.slash_era, stash) .unwrap_or_else(Zero::zero); - era_slash += own_slash_difference; - as Store>::NominatorSlashInEra::insert(¶ms.slash_era, stash, &era_slash); era_slash @@ -411,12 +409,10 @@ fn slash_nominators( let target_span = spans.compare_and_update_span_slash(params.slash_era, era_slash); if target_span == Some(spans.span_index()) { - // End the span, but don't chill the nominator. its nomination - // on this validator will be ignored in the future. + // end the span, but don't chill the nominator. spans.end_span(params.now); } } - nominators_slashed.push((stash.clone(), nom_slashed)); } diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 78429122d0..3e0a62f53d 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -2845,6 +2845,8 @@ fn deferred_slashes_are_deferred() { assert_eq!(Balances::free_balance(101), 2000); let nominated_value = exposure.others.iter().find(|o| o.who == 101).unwrap().value; + System::reset_events(); + on_offence_now( &[OffenceDetails { offender: (11, Staking::eras_stakers(active_era(), 11)), @@ -2853,6 +2855,9 @@ fn deferred_slashes_are_deferred() { &[Perbill::from_percent(10)], ); + // nominations are not removed regardless of the deferring. + assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); + assert_eq!(Balances::free_balance(11), 1000); assert_eq!(Balances::free_balance(101), 2000); @@ -2866,8 +2871,6 @@ fn deferred_slashes_are_deferred() { assert_eq!(Balances::free_balance(11), 1000); assert_eq!(Balances::free_balance(101), 2000); - System::reset_events(); - // at the start of era 4, slashes from era 1 are processed, // after being deferred for at least 2 full eras. mock::start_active_era(4); @@ -2875,15 +2878,16 @@ fn deferred_slashes_are_deferred() { assert_eq!(Balances::free_balance(11), 900); assert_eq!(Balances::free_balance(101), 2000 - (nominated_value / 10)); - assert_eq!( - staking_events_since_last_call(), - vec![ - Event::StakersElected, - Event::EraPaid { era_index: 3, validator_payout: 11075, remainder: 33225 }, + assert!(matches!( + staking_events_since_last_call().as_slice(), + &[ + Event::Chilled { stash: 11 }, + Event::SlashReported { validator: 11, slash_era: 1, .. }, + .., Event::Slashed { staker: 11, amount: 100 }, Event::Slashed { staker: 101, amount: 12 } ] - ); + )); }) } @@ -2896,25 +2900,29 @@ fn retroactive_deferred_slashes_two_eras_before() { let exposure_11_at_era1 = Staking::eras_stakers(active_era(), 11); mock::start_active_era(3); + + assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); + + System::reset_events(); on_offence_in_era( &[OffenceDetails { offender: (11, exposure_11_at_era1), reporters: vec![] }], &[Perbill::from_percent(10)], 1, // should be deferred for two full eras, and applied at the beginning of era 4. DisableStrategy::Never, ); - System::reset_events(); mock::start_active_era(4); - assert_eq!( - staking_events_since_last_call(), - vec![ - Event::StakersElected, - Event::EraPaid { era_index: 3, validator_payout: 7100, remainder: 21300 }, + assert!(matches!( + staking_events_since_last_call().as_slice(), + &[ + Event::Chilled { stash: 11 }, + Event::SlashReported { validator: 11, slash_era: 1, .. }, + .., Event::Slashed { staker: 11, amount: 100 }, - Event::Slashed { staker: 101, amount: 12 }, + Event::Slashed { staker: 101, amount: 12 } ] - ); + )); }) } @@ -2932,35 +2940,29 @@ fn retroactive_deferred_slashes_one_before() { assert_ok!(Staking::unbond(RuntimeOrigin::signed(10), 100)); mock::start_active_era(3); + System::reset_events(); on_offence_in_era( &[OffenceDetails { offender: (11, exposure_11_at_era1), reporters: vec![] }], &[Perbill::from_percent(10)], 2, // should be deferred for two full eras, and applied at the beginning of era 5. DisableStrategy::Never, ); - System::reset_events(); mock::start_active_era(4); - assert_eq!( - staking_events_since_last_call(), - vec![ - Event::StakersElected, - Event::EraPaid { era_index: 3, validator_payout: 11075, remainder: 33225 } - ] - ); assert_eq!(Staking::ledger(10).unwrap().total, 1000); // slash happens after the next line. + mock::start_active_era(5); - assert_eq!( - staking_events_since_last_call(), - vec![ - Event::StakersElected, - Event::EraPaid { era_index: 4, validator_payout: 11075, remainder: 33225 }, + assert!(matches!( + staking_events_since_last_call().as_slice(), + &[ + Event::SlashReported { validator: 11, slash_era: 2, .. }, + .., Event::Slashed { staker: 11, amount: 100 }, Event::Slashed { staker: 101, amount: 12 } ] - ); + )); // their ledger has already been slashed. assert_eq!(Staking::ledger(10).unwrap().total, 900); @@ -3068,6 +3070,7 @@ fn remove_deferred() { mock::start_active_era(2); // reported later, but deferred to start of era 4 as well. + System::reset_events(); on_offence_in_era( &[OffenceDetails { offender: (11, exposure.clone()), reporters: vec![] }], &[Perbill::from_percent(15)], @@ -3094,19 +3097,18 @@ fn remove_deferred() { // at the start of era 4, slashes from era 1 are processed, // after being deferred for at least 2 full eras. - System::reset_events(); mock::start_active_era(4); - // the first slash for 10% was cancelled, but the 15% one - assert_eq!( - staking_events_since_last_call(), - vec![ - Event::StakersElected, - Event::EraPaid { era_index: 3, validator_payout: 11075, remainder: 33225 }, + // the first slash for 10% was cancelled, but the 15% one not. + assert!(matches!( + staking_events_since_last_call().as_slice(), + &[ + Event::SlashReported { validator: 11, slash_era: 1, .. }, + .., Event::Slashed { staker: 11, amount: 50 }, Event::Slashed { staker: 101, amount: 7 } ] - ); + )); let slash_10 = Perbill::from_percent(10); let slash_15 = Perbill::from_percent(15); @@ -3196,6 +3198,9 @@ fn slash_kicks_validators_not_nominators_and_disables_nominator_for_kicked_valid assert_eq!(Balances::free_balance(11), 1000); assert_eq!(Balances::free_balance(101), 2000); + // 100 has approval for 11 as of now + assert!(Staking::nominators(101).unwrap().targets.contains(&11)); + // 11 and 21 both have the support of 100 let exposure_11 = Staking::eras_stakers(active_era(), &11); let exposure_21 = Staking::eras_stakers(active_era(), &21); @@ -3208,23 +3213,29 @@ fn slash_kicks_validators_not_nominators_and_disables_nominator_for_kicked_valid &[Perbill::from_percent(10)], ); + assert_eq!( + staking_events_since_last_call(), + vec![ + Event::StakersElected, + Event::EraPaid { era_index: 0, validator_payout: 11075, remainder: 33225 }, + Event::Chilled { stash: 11 }, + Event::SlashReported { + validator: 11, + fraction: Perbill::from_percent(10), + slash_era: 1 + }, + Event::Slashed { staker: 11, amount: 100 }, + Event::Slashed { staker: 101, amount: 12 }, + ] + ); + // post-slash balance let nominator_slash_amount_11 = 125 / 10; assert_eq!(Balances::free_balance(11), 900); assert_eq!(Balances::free_balance(101), 2000 - nominator_slash_amount_11); - // This is the best way to check that the validator was chilled; `get` will - // return default value. - for (stash, _) in ::Validators::iter() { - assert!(stash != 11); - } - - let nominations = ::Nominators::get(&101).unwrap(); - - // and make sure that the vote will be ignored even if the validator - // re-registers. - let last_slash = ::SlashingSpans::get(&11).unwrap().last_nonzero_slash(); - assert!(nominations.submitted_in < last_slash); + // check that validator was chilled. + assert!(::Validators::iter().all(|(stash, _)| stash != 11)); // actually re-bond the slashed validator assert_ok!(Staking::validate(RuntimeOrigin::signed(10), Default::default())); @@ -3233,11 +3244,12 @@ fn slash_kicks_validators_not_nominators_and_disables_nominator_for_kicked_valid let exposure_11 = Staking::eras_stakers(active_era(), &11); let exposure_21 = Staking::eras_stakers(active_era(), &21); - // 10 is re-elected, but without the support of 100 - assert_eq!(exposure_11.total, 900); - - // 20 is re-elected, with the (almost) entire support of 100 - assert_eq!(exposure_21.total, 1000 + 500 - nominator_slash_amount_11); + // 11's own expo is reduced. sum of support from 11 is less (448), which is 500 + // 900 + 146 + assert!(matches!(exposure_11, Exposure { own: 900, total: 1046, .. })); + // 1000 + 342 + assert!(matches!(exposure_21, Exposure { own: 1000, total: 1342, .. })); + assert_eq!(500 - 146 - 342, nominator_slash_amount_11); }); } @@ -3256,12 +3268,40 @@ fn non_slashable_offence_doesnt_disable_validator() { &[Perbill::zero()], ); + // it does NOT affect the nominator. + assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); + // offence that slashes 25% of the bond on_offence_now( &[OffenceDetails { offender: (21, exposure_21.clone()), reporters: vec![] }], &[Perbill::from_percent(25)], ); + // it DOES NOT affect the nominator. + assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); + + assert_eq!( + staking_events_since_last_call(), + vec![ + Event::StakersElected, + Event::EraPaid { era_index: 0, validator_payout: 11075, remainder: 33225 }, + Event::Chilled { stash: 11 }, + Event::SlashReported { + validator: 11, + fraction: Perbill::from_percent(0), + slash_era: 1 + }, + Event::Chilled { stash: 21 }, + Event::SlashReported { + validator: 21, + fraction: Perbill::from_percent(25), + slash_era: 1 + }, + Event::Slashed { staker: 21, amount: 250 }, + Event::Slashed { staker: 101, amount: 94 } + ] + ); + // the offence for validator 10 wasn't slashable so it wasn't disabled assert!(!is_disabled(10)); // whereas validator 20 gets disabled @@ -3288,6 +3328,9 @@ fn slashing_independent_of_disabling_validator() { DisableStrategy::Always, ); + // nomination remains untouched. + assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); + // offence that slashes 25% of the bond, BUT not disabling on_offence_in_era( &[OffenceDetails { offender: (21, exposure_21.clone()), reporters: vec![] }], @@ -3296,6 +3339,31 @@ fn slashing_independent_of_disabling_validator() { DisableStrategy::Never, ); + // nomination remains untouched. + assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); + + assert_eq!( + staking_events_since_last_call(), + vec![ + Event::StakersElected, + Event::EraPaid { era_index: 0, validator_payout: 11075, remainder: 33225 }, + Event::Chilled { stash: 11 }, + Event::SlashReported { + validator: 11, + fraction: Perbill::from_percent(0), + slash_era: 1 + }, + Event::Chilled { stash: 21 }, + Event::SlashReported { + validator: 21, + fraction: Perbill::from_percent(25), + slash_era: 1 + }, + Event::Slashed { staker: 21, amount: 250 }, + Event::Slashed { staker: 101, amount: 94 } + ] + ); + // the offence for validator 10 was explicitly disabled assert!(is_disabled(10)); // whereas validator 20 is explicitly not disabled @@ -3370,6 +3438,9 @@ fn disabled_validators_are_kept_disabled_for_whole_era() { &[Perbill::from_percent(25)], ); + // nominations are not updated. + assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); + // validator 10 should not be disabled since the offence wasn't slashable assert!(!is_disabled(10)); // validator 20 gets disabled since it got slashed @@ -3387,6 +3458,9 @@ fn disabled_validators_are_kept_disabled_for_whole_era() { &[Perbill::from_percent(25)], ); + // nominations are not updated. + assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); + advance_session(); // and both are disabled in the last session of the era @@ -3503,18 +3577,10 @@ fn zero_slash_keeps_nominators() { assert_eq!(Balances::free_balance(11), 1000); assert_eq!(Balances::free_balance(101), 2000); - // This is the best way to check that the validator was chilled; `get` will - // return default value. - for (stash, _) in ::Validators::iter() { - assert!(stash != 11); - } - - let nominations = ::Nominators::get(&101).unwrap(); - - // and make sure that the vote will not be ignored, because the slash was - // zero. - let last_slash = ::SlashingSpans::get(&11).unwrap().last_nonzero_slash(); - assert!(nominations.submitted_in >= last_slash); + // 11 is still removed.. + assert!(::Validators::iter().all(|(stash, _)| stash != 11)); + // but their nominations are kept. + assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); }); } @@ -4380,15 +4446,14 @@ mod election_data_provider { // we assume a network only wants up to 1000 validators in most cases, thus having 2000 // candidates is as high as it gets. let validators = 2000; - // we assume the worse case: each validator also has a slashing span. - let slashing_spans = validators; let mut nominators = 1000; - while ::WeightInfo::get_npos_voters(validators, nominators, slashing_spans) - .all_lt(Weight::from_parts( + while ::WeightInfo::get_npos_voters(validators, nominators).all_lt( + Weight::from_parts( 2u64 * frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND, u64::MAX, - )) { + ), + ) { nominators += 1; } @@ -4410,49 +4475,6 @@ mod election_data_provider { }) } - #[test] - fn voters_exclude_slashed() { - ExtBuilder::default().build_and_execute(|| { - assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); - assert_eq!( - ::electing_voters(None) - .unwrap() - .iter() - .find(|x| x.0 == 101) - .unwrap() - .2, - vec![11, 21] - ); - - start_active_era(1); - add_slash(&11); - - // 11 is gone. - start_active_era(2); - assert_eq!( - ::electing_voters(None) - .unwrap() - .iter() - .find(|x| x.0 == 101) - .unwrap() - .2, - vec![21] - ); - - // resubmit and it is back - assert_ok!(Staking::nominate(RuntimeOrigin::signed(100), vec![11, 21])); - assert_eq!( - ::electing_voters(None) - .unwrap() - .iter() - .find(|x| x.0 == 101) - .unwrap() - .2, - vec![11, 21] - ); - }) - } - #[test] fn respects_snapshot_len_limits() { ExtBuilder::default() @@ -4489,10 +4511,26 @@ mod election_data_provider { fn only_iterates_max_2_times_max_allowed_len() { ExtBuilder::default() .nominate(false) - // the other nominators only nominate 21 - .add_staker(61, 60, 2_000, StakerStatus::::Nominator(vec![21])) - .add_staker(71, 70, 2_000, StakerStatus::::Nominator(vec![21])) - .add_staker(81, 80, 2_000, StakerStatus::::Nominator(vec![21])) + // the best way to invalidate a bunch of nominators is to have them nominate a lot of + // ppl, but then lower the MaxNomination limit. + .add_staker( + 61, + 60, + 2_000, + StakerStatus::::Nominator(vec![21, 22, 23, 24, 25]), + ) + .add_staker( + 71, + 70, + 2_000, + StakerStatus::::Nominator(vec![21, 22, 23, 24, 25]), + ) + .add_staker( + 81, + 80, + 2_000, + StakerStatus::::Nominator(vec![21, 22, 23, 24, 25]), + ) .build_and_execute(|| { // all voters ordered by stake, assert_eq!( @@ -4500,10 +4538,7 @@ mod election_data_provider { vec![61, 71, 81, 11, 21, 31] ); - run_to_block(25); - - // slash 21, the only validator nominated by our first 3 nominators - add_slash(&21); + MaxNominations::set(2); // we want 2 voters now, and in maximum we allow 4 iterations. This is what happens: // 61 is pruned; @@ -4523,55 +4558,6 @@ mod election_data_provider { }); } - // Even if some of the higher staked nominators are slashed, we still get up to max len voters - // by adding more lower staked nominators. In other words, we assert that we keep on adding - // valid nominators until we reach max len voters; which is opposed to simply stopping after we - // have iterated max len voters, but not adding all of them to voters due to some nominators not - // having valid targets. - #[test] - fn get_max_len_voters_even_if_some_nominators_are_slashed() { - ExtBuilder::default() - .nominate(false) - .add_staker(61, 60, 20, StakerStatus::::Nominator(vec![21])) - .add_staker(71, 70, 10, StakerStatus::::Nominator(vec![11, 21])) - .add_staker(81, 80, 10, StakerStatus::::Nominator(vec![11, 21])) - .build_and_execute(|| { - // given our voters ordered by stake, - assert_eq!( - ::VoterList::iter().collect::>(), - vec![11, 21, 31, 61, 71, 81] - ); - - // we take 4 voters - assert_eq!( - Staking::electing_voters(Some(4)) - .unwrap() - .iter() - .map(|(stash, _, _)| stash) - .copied() - .collect::>(), - vec![11, 21, 31, 61], - ); - - // roll to session 5 - run_to_block(25); - - // slash 21, the only validator nominated by 61. - add_slash(&21); - - // we take 4 voters; 71 and 81 are replacing the ejected ones. - assert_eq!( - Staking::electing_voters(Some(4)) - .unwrap() - .iter() - .map(|(stash, _, _)| stash) - .copied() - .collect::>(), - vec![11, 31, 71, 81], - ); - }); - } - #[test] fn estimate_next_election_works() { ExtBuilder::default().session_per_era(5).period(5).build_and_execute(|| { diff --git a/frame/staking/src/weights.rs b/frame/staking/src/weights.rs index 56374ffbc4..21fc3d6f07 100644 --- a/frame/staking/src/weights.rs +++ b/frame/staking/src/weights.rs @@ -18,24 +18,25 @@ //! Autogenerated weights for pallet_staking //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2022-12-12, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// ./target/production/substrate +// /home/benchbot/cargo_target_dir/production/substrate // benchmark // pallet -// --chain=dev // --steps=50 // --repeat=20 -// --pallet=pallet_staking // --extrinsic=* // --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 -// --output=./frame/staking/src/weights.rs +// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/substrate/.git/.artifacts/bench.json +// --pallet=pallet_staking +// --chain=dev // --header=./HEADER-APACHE2 +// --output=./frame/staking/src/weights.rs // --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] @@ -70,7 +71,7 @@ pub trait WeightInfo { fn rebond(l: u32, ) -> Weight; fn reap_stash(s: u32, ) -> Weight; fn new_era(v: u32, n: u32, ) -> Weight; - fn get_npos_voters(v: u32, n: u32, s: u32, ) -> Weight; + fn get_npos_voters(v: u32, n: u32, ) -> Weight; fn get_npos_targets(v: u32, ) -> Weight; fn set_staking_configs_all_set() -> Weight; fn set_staking_configs_all_remove() -> Weight; @@ -87,10 +88,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn bond() -> Weight { - // Minimum execution time: 53_097 nanoseconds. - Weight::from_ref_time(53_708_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Minimum execution time: 56_034 nanoseconds. + Weight::from_ref_time(56_646_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(4)) } // Storage: Staking Bonded (r:1 w:0) // Storage: Staking Ledger (r:1 w:1) @@ -98,10 +99,10 @@ impl WeightInfo for SubstrateWeight { // Storage: VoterList ListNodes (r:3 w:3) // Storage: VoterList ListBags (r:2 w:2) fn bond_extra() -> Weight { - // Minimum execution time: 92_199 nanoseconds. - Weight::from_ref_time(93_541_000 as u64) - .saturating_add(T::DbWeight::get().reads(8 as u64)) - .saturating_add(T::DbWeight::get().writes(7 as u64)) + // Minimum execution time: 94_354 nanoseconds. + Weight::from_ref_time(95_318_000) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(7)) } // Storage: Staking Ledger (r:1 w:1) // Storage: Staking Nominators (r:1 w:0) @@ -113,10 +114,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking Bonded (r:1 w:0) // Storage: VoterList ListBags (r:2 w:2) fn unbond() -> Weight { - // Minimum execution time: 98_227 nanoseconds. - Weight::from_ref_time(99_070_000 as u64) - .saturating_add(T::DbWeight::get().reads(12 as u64)) - .saturating_add(T::DbWeight::get().writes(8 as u64)) + // Minimum execution time: 99_960 nanoseconds. + Weight::from_ref_time(101_022_000) + .saturating_add(T::DbWeight::get().reads(12)) + .saturating_add(T::DbWeight::get().writes(8)) } // Storage: Staking Ledger (r:1 w:1) // Storage: Staking CurrentEra (r:1 w:0) @@ -124,12 +125,12 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_update(s: u32, ) -> Weight { - // Minimum execution time: 45_058 nanoseconds. - Weight::from_ref_time(46_592_713 as u64) - // Standard Error: 413 - .saturating_add(Weight::from_ref_time(63_036 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 45_819 nanoseconds. + Weight::from_ref_time(48_073_614) + // Standard Error: 1_410 + .saturating_add(Weight::from_ref_time(62_881).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(3)) } // Storage: Staking Ledger (r:1 w:1) // Storage: Staking CurrentEra (r:1 w:0) @@ -146,10 +147,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking Payee (r:0 w:1) /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_kill(_s: u32, ) -> Weight { - // Minimum execution time: 86_087 nanoseconds. - Weight::from_ref_time(87_627_894 as u64) - .saturating_add(T::DbWeight::get().reads(13 as u64)) - .saturating_add(T::DbWeight::get().writes(11 as u64)) + // Minimum execution time: 86_035 nanoseconds. + Weight::from_ref_time(89_561_735) + .saturating_add(T::DbWeight::get().reads(13)) + .saturating_add(T::DbWeight::get().writes(11)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking MinValidatorBond (r:1 w:0) @@ -163,22 +164,22 @@ impl WeightInfo for SubstrateWeight { // Storage: VoterList CounterForListNodes (r:1 w:1) // Storage: Staking CounterForValidators (r:1 w:1) fn validate() -> Weight { - // Minimum execution time: 67_690 nanoseconds. - Weight::from_ref_time(68_348_000 as u64) - .saturating_add(T::DbWeight::get().reads(11 as u64)) - .saturating_add(T::DbWeight::get().writes(5 as u64)) + // Minimum execution time: 68_748 nanoseconds. + Weight::from_ref_time(69_285_000) + .saturating_add(T::DbWeight::get().reads(11)) + .saturating_add(T::DbWeight::get().writes(5)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking Nominators (r:1 w:1) /// The range of component `k` is `[1, 128]`. fn kick(k: u32, ) -> Weight { - // Minimum execution time: 43_512 nanoseconds. - Weight::from_ref_time(47_300_477 as u64) - // Standard Error: 11_609 - .saturating_add(Weight::from_ref_time(6_770_405 as u64).saturating_mul(k as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(k as u64))) - .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(k as u64))) + // Minimum execution time: 41_641 nanoseconds. + Weight::from_ref_time(48_919_231) + // Standard Error: 11_548 + .saturating_add(Weight::from_ref_time(6_901_201).saturating_mul(k.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking MinNominatorBond (r:1 w:0) @@ -193,13 +194,13 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking CounterForNominators (r:1 w:1) /// The range of component `n` is `[1, 16]`. fn nominate(n: u32, ) -> Weight { - // Minimum execution time: 74_296 nanoseconds. - Weight::from_ref_time(73_201_782 as u64) - // Standard Error: 5_007 - .saturating_add(Weight::from_ref_time(2_810_370 as u64).saturating_mul(n as u64)) - .saturating_add(T::DbWeight::get().reads(12 as u64)) - .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(n as u64))) - .saturating_add(T::DbWeight::get().writes(6 as u64)) + // Minimum execution time: 75_097 nanoseconds. + Weight::from_ref_time(74_052_497) + // Standard Error: 6_784 + .saturating_add(Weight::from_ref_time(2_842_146).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(12)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(6)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking Validators (r:1 w:0) @@ -209,59 +210,59 @@ impl WeightInfo for SubstrateWeight { // Storage: VoterList ListBags (r:1 w:1) // Storage: VoterList CounterForListNodes (r:1 w:1) fn chill() -> Weight { - // Minimum execution time: 66_605 nanoseconds. - Weight::from_ref_time(67_279_000 as u64) - .saturating_add(T::DbWeight::get().reads(8 as u64)) - .saturating_add(T::DbWeight::get().writes(6 as u64)) + // Minimum execution time: 67_307 nanoseconds. + Weight::from_ref_time(67_838_000) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(6)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking Payee (r:0 w:1) fn set_payee() -> Weight { - // Minimum execution time: 18_897 nanoseconds. - Weight::from_ref_time(19_357_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 18_831 nanoseconds. + Weight::from_ref_time(19_047_000) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Staking Bonded (r:1 w:1) // Storage: Staking Ledger (r:2 w:2) fn set_controller() -> Weight { - // Minimum execution time: 26_509 nanoseconds. - Weight::from_ref_time(26_961_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 27_534 nanoseconds. + Weight::from_ref_time(27_806_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) } // Storage: Staking ValidatorCount (r:0 w:1) fn set_validator_count() -> Weight { - // Minimum execution time: 5_025 nanoseconds. - Weight::from_ref_time(5_240_000 as u64) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 5_211 nanoseconds. + Weight::from_ref_time(5_372_000) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Staking ForceEra (r:0 w:1) fn force_no_eras() -> Weight { - // Minimum execution time: 5_107 nanoseconds. - Weight::from_ref_time(5_320_000 as u64) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 5_382 nanoseconds. + Weight::from_ref_time(5_654_000) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Staking ForceEra (r:0 w:1) fn force_new_era() -> Weight { - // Minimum execution time: 5_094 nanoseconds. - Weight::from_ref_time(5_377_000 as u64) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 5_618 nanoseconds. + Weight::from_ref_time(5_714_000) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Staking ForceEra (r:0 w:1) fn force_new_era_always() -> Weight { - // Minimum execution time: 5_219 nanoseconds. - Weight::from_ref_time(5_434_000 as u64) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 5_589 nanoseconds. + Weight::from_ref_time(5_776_000) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Staking Invulnerables (r:0 w:1) /// The range of component `v` is `[0, 1000]`. fn set_invulnerables(v: u32, ) -> Weight { - // Minimum execution time: 5_122 nanoseconds. - Weight::from_ref_time(5_977_533 as u64) - // Standard Error: 34 - .saturating_add(Weight::from_ref_time(10_205 as u64).saturating_mul(v as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 5_541 nanoseconds. + Weight::from_ref_time(6_479_253) + // Standard Error: 49 + .saturating_add(Weight::from_ref_time(10_125).saturating_mul(v.into())) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Staking Bonded (r:1 w:1) // Storage: Staking SlashingSpans (r:1 w:0) @@ -278,23 +279,23 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking SpanSlash (r:0 w:2) /// The range of component `s` is `[0, 100]`. fn force_unstake(s: u32, ) -> Weight { - // Minimum execution time: 80_216 nanoseconds. - Weight::from_ref_time(86_090_609 as u64) - // Standard Error: 2_006 - .saturating_add(Weight::from_ref_time(1_039_308 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(11 as u64)) - .saturating_add(T::DbWeight::get().writes(12 as u64)) - .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(s as u64))) + // Minimum execution time: 81_041 nanoseconds. + Weight::from_ref_time(88_526_481) + // Standard Error: 11_494 + .saturating_add(Weight::from_ref_time(1_095_933).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(11)) + .saturating_add(T::DbWeight::get().writes(12)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) } // Storage: Staking UnappliedSlashes (r:1 w:1) /// The range of component `s` is `[1, 1000]`. fn cancel_deferred_slash(s: u32, ) -> Weight { - // Minimum execution time: 92_034 nanoseconds. - Weight::from_ref_time(896_585_370 as u64) - // Standard Error: 58_231 - .saturating_add(Weight::from_ref_time(4_908_277 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 92_308 nanoseconds. + Weight::from_ref_time(900_351_007) + // Standard Error: 59_145 + .saturating_add(Weight::from_ref_time(4_944_988).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Staking CurrentEra (r:1 w:0) // Storage: Staking ErasValidatorReward (r:1 w:0) @@ -307,14 +308,14 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) /// The range of component `n` is `[0, 256]`. fn payout_stakers_dead_controller(n: u32, ) -> Weight { - // Minimum execution time: 127_936 nanoseconds. - Weight::from_ref_time(184_556_084 as u64) - // Standard Error: 26_981 - .saturating_add(Weight::from_ref_time(21_786_304 as u64).saturating_mul(n as u64)) - .saturating_add(T::DbWeight::get().reads(9 as u64)) - .saturating_add(T::DbWeight::get().reads((3 as u64).saturating_mul(n as u64))) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(n as u64))) + // Minimum execution time: 131_855 nanoseconds. + Weight::from_ref_time(197_412_779) + // Standard Error: 21_283 + .saturating_add(Weight::from_ref_time(22_093_758).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(2)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) } // Storage: Staking CurrentEra (r:1 w:0) // Storage: Staking ErasValidatorReward (r:1 w:0) @@ -328,14 +329,14 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) /// The range of component `n` is `[0, 256]`. fn payout_stakers_alive_staked(n: u32, ) -> Weight { - // Minimum execution time: 157_778 nanoseconds. - Weight::from_ref_time(223_306_359 as u64) - // Standard Error: 27_216 - .saturating_add(Weight::from_ref_time(30_612_663 as u64).saturating_mul(n as u64)) - .saturating_add(T::DbWeight::get().reads(10 as u64)) - .saturating_add(T::DbWeight::get().reads((5 as u64).saturating_mul(n as u64))) - .saturating_add(T::DbWeight::get().writes(3 as u64)) - .saturating_add(T::DbWeight::get().writes((3 as u64).saturating_mul(n as u64))) + // Minimum execution time: 163_118 nanoseconds. + Weight::from_ref_time(229_356_697) + // Standard Error: 30_740 + .saturating_add(Weight::from_ref_time(31_575_360).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(10)) + .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(3)) + .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(n.into()))) } // Storage: Staking Ledger (r:1 w:1) // Storage: Balances Locks (r:1 w:1) @@ -345,12 +346,12 @@ impl WeightInfo for SubstrateWeight { // Storage: VoterList ListBags (r:2 w:2) /// The range of component `l` is `[1, 32]`. fn rebond(l: u32, ) -> Weight { - // Minimum execution time: 92_880 nanoseconds. - Weight::from_ref_time(94_434_663 as u64) - // Standard Error: 1_734 - .saturating_add(Weight::from_ref_time(34_453 as u64).saturating_mul(l as u64)) - .saturating_add(T::DbWeight::get().reads(9 as u64)) - .saturating_add(T::DbWeight::get().writes(8 as u64)) + // Minimum execution time: 94_048 nanoseconds. + Weight::from_ref_time(95_784_236) + // Standard Error: 2_313 + .saturating_add(Weight::from_ref_time(52_798).saturating_mul(l.into())) + .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().writes(8)) } // Storage: System Account (r:1 w:1) // Storage: Staking Bonded (r:1 w:1) @@ -367,16 +368,15 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking SpanSlash (r:0 w:1) /// The range of component `s` is `[1, 100]`. fn reap_stash(s: u32, ) -> Weight { - // Minimum execution time: 92_334 nanoseconds. - Weight::from_ref_time(95_207_614 as u64) - // Standard Error: 1_822 - .saturating_add(Weight::from_ref_time(1_036_787 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(12 as u64)) - .saturating_add(T::DbWeight::get().writes(12 as u64)) - .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(s as u64))) + // Minimum execution time: 93_342 nanoseconds. + Weight::from_ref_time(95_756_184) + // Standard Error: 2_067 + .saturating_add(Weight::from_ref_time(1_090_785).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(12)) + .saturating_add(T::DbWeight::get().writes(12)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) } // Storage: VoterList CounterForListNodes (r:1 w:0) - // Storage: Staking SlashingSpans (r:1 w:0) // Storage: VoterList ListBags (r:200 w:0) // Storage: VoterList ListNodes (r:101 w:0) // Storage: Staking Nominators (r:101 w:0) @@ -395,20 +395,19 @@ impl WeightInfo for SubstrateWeight { /// The range of component `v` is `[1, 10]`. /// The range of component `n` is `[0, 100]`. fn new_era(v: u32, n: u32, ) -> Weight { - // Minimum execution time: 535_169 nanoseconds. - Weight::from_ref_time(548_667_000 as u64) - // Standard Error: 1_759_252 - .saturating_add(Weight::from_ref_time(58_283_319 as u64).saturating_mul(v as u64)) - // Standard Error: 175_299 - .saturating_add(Weight::from_ref_time(13_578_512 as u64).saturating_mul(n as u64)) - .saturating_add(T::DbWeight::get().reads(207 as u64)) - .saturating_add(T::DbWeight::get().reads((5 as u64).saturating_mul(v as u64))) - .saturating_add(T::DbWeight::get().reads((4 as u64).saturating_mul(n as u64))) - .saturating_add(T::DbWeight::get().writes(3 as u64)) - .saturating_add(T::DbWeight::get().writes((3 as u64).saturating_mul(v as u64))) + // Minimum execution time: 506_874 nanoseconds. + Weight::from_ref_time(507_798_000) + // Standard Error: 1_802_261 + .saturating_add(Weight::from_ref_time(59_874_736).saturating_mul(v.into())) + // Standard Error: 179_585 + .saturating_add(Weight::from_ref_time(13_668_574).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(206)) + .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into()))) + .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(3)) + .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(v.into()))) } // Storage: VoterList CounterForListNodes (r:1 w:0) - // Storage: Staking SlashingSpans (r:21 w:0) // Storage: VoterList ListBags (r:200 w:0) // Storage: VoterList ListNodes (r:1500 w:0) // Storage: Staking Nominators (r:1500 w:0) @@ -417,29 +416,27 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking Ledger (r:1500 w:0) /// The range of component `v` is `[500, 1000]`. /// The range of component `n` is `[500, 1000]`. - /// The range of component `s` is `[1, 20]`. - fn get_npos_voters(v: u32, n: u32, s: u32, ) -> Weight { - // Minimum execution time: 25_323_129 nanoseconds. - Weight::from_ref_time(25_471_672_000 as u64) - // Standard Error: 266_391 - .saturating_add(Weight::from_ref_time(6_665_504 as u64).saturating_mul(v as u64)) - // Standard Error: 266_391 - .saturating_add(Weight::from_ref_time(6_956_606 as u64).saturating_mul(n as u64)) - .saturating_add(T::DbWeight::get().reads(202 as u64)) - .saturating_add(T::DbWeight::get().reads((5 as u64).saturating_mul(v as u64))) - .saturating_add(T::DbWeight::get().reads((4 as u64).saturating_mul(n as u64))) - .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(s as u64))) + fn get_npos_voters(v: u32, n: u32, ) -> Weight { + // Minimum execution time: 24_634_585 nanoseconds. + Weight::from_ref_time(24_718_377_000) + // Standard Error: 324_839 + .saturating_add(Weight::from_ref_time(3_654_508).saturating_mul(v.into())) + // Standard Error: 324_839 + .saturating_add(Weight::from_ref_time(2_927_535).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(201)) + .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into()))) + .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into()))) } // Storage: Staking CounterForValidators (r:1 w:0) // Storage: Staking Validators (r:501 w:0) /// The range of component `v` is `[500, 1000]`. fn get_npos_targets(v: u32, ) -> Weight { - // Minimum execution time: 4_905_036 nanoseconds. - Weight::from_ref_time(78_163_554 as u64) - // Standard Error: 23_723 - .saturating_add(Weight::from_ref_time(9_784_870 as u64).saturating_mul(v as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(v as u64))) + // Minimum execution time: 4_805_490 nanoseconds. + Weight::from_ref_time(118_475_494) + // Standard Error: 26_332 + .saturating_add(Weight::from_ref_time(9_635_188).saturating_mul(v.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(v.into()))) } // Storage: Staking MinCommission (r:0 w:1) // Storage: Staking MinValidatorBond (r:0 w:1) @@ -448,9 +445,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking MaxNominatorsCount (r:0 w:1) // Storage: Staking MinNominatorBond (r:0 w:1) fn set_staking_configs_all_set() -> Weight { - // Minimum execution time: 10_096 nanoseconds. - Weight::from_ref_time(10_538_000 as u64) - .saturating_add(T::DbWeight::get().writes(6 as u64)) + // Minimum execution time: 10_816 nanoseconds. + Weight::from_ref_time(11_242_000) + .saturating_add(T::DbWeight::get().writes(6)) } // Storage: Staking MinCommission (r:0 w:1) // Storage: Staking MinValidatorBond (r:0 w:1) @@ -459,9 +456,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking MaxNominatorsCount (r:0 w:1) // Storage: Staking MinNominatorBond (r:0 w:1) fn set_staking_configs_all_remove() -> Weight { - // Minimum execution time: 9_045 nanoseconds. - Weight::from_ref_time(9_379_000 as u64) - .saturating_add(T::DbWeight::get().writes(6 as u64)) + // Minimum execution time: 9_581 nanoseconds. + Weight::from_ref_time(10_383_000) + .saturating_add(T::DbWeight::get().writes(6)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking Nominators (r:1 w:1) @@ -474,18 +471,18 @@ impl WeightInfo for SubstrateWeight { // Storage: VoterList ListBags (r:1 w:1) // Storage: VoterList CounterForListNodes (r:1 w:1) fn chill_other() -> Weight { - // Minimum execution time: 81_457 nanoseconds. - Weight::from_ref_time(82_410_000 as u64) - .saturating_add(T::DbWeight::get().reads(11 as u64)) - .saturating_add(T::DbWeight::get().writes(6 as u64)) + // Minimum execution time: 83_669 nanoseconds. + Weight::from_ref_time(84_772_000) + .saturating_add(T::DbWeight::get().reads(11)) + .saturating_add(T::DbWeight::get().writes(6)) } // Storage: Staking MinCommission (r:1 w:0) // Storage: Staking Validators (r:1 w:1) fn force_apply_min_commission() -> Weight { - // Minimum execution time: 19_684 nanoseconds. - Weight::from_ref_time(20_059_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 20_553 nanoseconds. + Weight::from_ref_time(20_933_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) } } @@ -497,10 +494,10 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn bond() -> Weight { - // Minimum execution time: 53_097 nanoseconds. - Weight::from_ref_time(53_708_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // Minimum execution time: 56_034 nanoseconds. + Weight::from_ref_time(56_646_000) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().writes(4)) } // Storage: Staking Bonded (r:1 w:0) // Storage: Staking Ledger (r:1 w:1) @@ -508,10 +505,10 @@ impl WeightInfo for () { // Storage: VoterList ListNodes (r:3 w:3) // Storage: VoterList ListBags (r:2 w:2) fn bond_extra() -> Weight { - // Minimum execution time: 92_199 nanoseconds. - Weight::from_ref_time(93_541_000 as u64) - .saturating_add(RocksDbWeight::get().reads(8 as u64)) - .saturating_add(RocksDbWeight::get().writes(7 as u64)) + // Minimum execution time: 94_354 nanoseconds. + Weight::from_ref_time(95_318_000) + .saturating_add(RocksDbWeight::get().reads(8)) + .saturating_add(RocksDbWeight::get().writes(7)) } // Storage: Staking Ledger (r:1 w:1) // Storage: Staking Nominators (r:1 w:0) @@ -523,10 +520,10 @@ impl WeightInfo for () { // Storage: Staking Bonded (r:1 w:0) // Storage: VoterList ListBags (r:2 w:2) fn unbond() -> Weight { - // Minimum execution time: 98_227 nanoseconds. - Weight::from_ref_time(99_070_000 as u64) - .saturating_add(RocksDbWeight::get().reads(12 as u64)) - .saturating_add(RocksDbWeight::get().writes(8 as u64)) + // Minimum execution time: 99_960 nanoseconds. + Weight::from_ref_time(101_022_000) + .saturating_add(RocksDbWeight::get().reads(12)) + .saturating_add(RocksDbWeight::get().writes(8)) } // Storage: Staking Ledger (r:1 w:1) // Storage: Staking CurrentEra (r:1 w:0) @@ -534,12 +531,12 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_update(s: u32, ) -> Weight { - // Minimum execution time: 45_058 nanoseconds. - Weight::from_ref_time(46_592_713 as u64) - // Standard Error: 413 - .saturating_add(Weight::from_ref_time(63_036 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 45_819 nanoseconds. + Weight::from_ref_time(48_073_614) + // Standard Error: 1_410 + .saturating_add(Weight::from_ref_time(62_881).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().writes(3)) } // Storage: Staking Ledger (r:1 w:1) // Storage: Staking CurrentEra (r:1 w:0) @@ -556,10 +553,10 @@ impl WeightInfo for () { // Storage: Staking Payee (r:0 w:1) /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_kill(_s: u32, ) -> Weight { - // Minimum execution time: 86_087 nanoseconds. - Weight::from_ref_time(87_627_894 as u64) - .saturating_add(RocksDbWeight::get().reads(13 as u64)) - .saturating_add(RocksDbWeight::get().writes(11 as u64)) + // Minimum execution time: 86_035 nanoseconds. + Weight::from_ref_time(89_561_735) + .saturating_add(RocksDbWeight::get().reads(13)) + .saturating_add(RocksDbWeight::get().writes(11)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking MinValidatorBond (r:1 w:0) @@ -573,22 +570,22 @@ impl WeightInfo for () { // Storage: VoterList CounterForListNodes (r:1 w:1) // Storage: Staking CounterForValidators (r:1 w:1) fn validate() -> Weight { - // Minimum execution time: 67_690 nanoseconds. - Weight::from_ref_time(68_348_000 as u64) - .saturating_add(RocksDbWeight::get().reads(11 as u64)) - .saturating_add(RocksDbWeight::get().writes(5 as u64)) + // Minimum execution time: 68_748 nanoseconds. + Weight::from_ref_time(69_285_000) + .saturating_add(RocksDbWeight::get().reads(11)) + .saturating_add(RocksDbWeight::get().writes(5)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking Nominators (r:1 w:1) /// The range of component `k` is `[1, 128]`. fn kick(k: u32, ) -> Weight { - // Minimum execution time: 43_512 nanoseconds. - Weight::from_ref_time(47_300_477 as u64) - // Standard Error: 11_609 - .saturating_add(Weight::from_ref_time(6_770_405 as u64).saturating_mul(k as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(k as u64))) - .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(k as u64))) + // Minimum execution time: 41_641 nanoseconds. + Weight::from_ref_time(48_919_231) + // Standard Error: 11_548 + .saturating_add(Weight::from_ref_time(6_901_201).saturating_mul(k.into())) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(k.into()))) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(k.into()))) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking MinNominatorBond (r:1 w:0) @@ -603,13 +600,13 @@ impl WeightInfo for () { // Storage: Staking CounterForNominators (r:1 w:1) /// The range of component `n` is `[1, 16]`. fn nominate(n: u32, ) -> Weight { - // Minimum execution time: 74_296 nanoseconds. - Weight::from_ref_time(73_201_782 as u64) - // Standard Error: 5_007 - .saturating_add(Weight::from_ref_time(2_810_370 as u64).saturating_mul(n as u64)) - .saturating_add(RocksDbWeight::get().reads(12 as u64)) - .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(n as u64))) - .saturating_add(RocksDbWeight::get().writes(6 as u64)) + // Minimum execution time: 75_097 nanoseconds. + Weight::from_ref_time(74_052_497) + // Standard Error: 6_784 + .saturating_add(Weight::from_ref_time(2_842_146).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(12)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes(6)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking Validators (r:1 w:0) @@ -619,59 +616,59 @@ impl WeightInfo for () { // Storage: VoterList ListBags (r:1 w:1) // Storage: VoterList CounterForListNodes (r:1 w:1) fn chill() -> Weight { - // Minimum execution time: 66_605 nanoseconds. - Weight::from_ref_time(67_279_000 as u64) - .saturating_add(RocksDbWeight::get().reads(8 as u64)) - .saturating_add(RocksDbWeight::get().writes(6 as u64)) + // Minimum execution time: 67_307 nanoseconds. + Weight::from_ref_time(67_838_000) + .saturating_add(RocksDbWeight::get().reads(8)) + .saturating_add(RocksDbWeight::get().writes(6)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking Payee (r:0 w:1) fn set_payee() -> Weight { - // Minimum execution time: 18_897 nanoseconds. - Weight::from_ref_time(19_357_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 18_831 nanoseconds. + Weight::from_ref_time(19_047_000) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Staking Bonded (r:1 w:1) // Storage: Staking Ledger (r:2 w:2) fn set_controller() -> Weight { - // Minimum execution time: 26_509 nanoseconds. - Weight::from_ref_time(26_961_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 27_534 nanoseconds. + Weight::from_ref_time(27_806_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(3)) } // Storage: Staking ValidatorCount (r:0 w:1) fn set_validator_count() -> Weight { - // Minimum execution time: 5_025 nanoseconds. - Weight::from_ref_time(5_240_000 as u64) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 5_211 nanoseconds. + Weight::from_ref_time(5_372_000) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Staking ForceEra (r:0 w:1) fn force_no_eras() -> Weight { - // Minimum execution time: 5_107 nanoseconds. - Weight::from_ref_time(5_320_000 as u64) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 5_382 nanoseconds. + Weight::from_ref_time(5_654_000) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Staking ForceEra (r:0 w:1) fn force_new_era() -> Weight { - // Minimum execution time: 5_094 nanoseconds. - Weight::from_ref_time(5_377_000 as u64) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 5_618 nanoseconds. + Weight::from_ref_time(5_714_000) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Staking ForceEra (r:0 w:1) fn force_new_era_always() -> Weight { - // Minimum execution time: 5_219 nanoseconds. - Weight::from_ref_time(5_434_000 as u64) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 5_589 nanoseconds. + Weight::from_ref_time(5_776_000) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Staking Invulnerables (r:0 w:1) /// The range of component `v` is `[0, 1000]`. fn set_invulnerables(v: u32, ) -> Weight { - // Minimum execution time: 5_122 nanoseconds. - Weight::from_ref_time(5_977_533 as u64) - // Standard Error: 34 - .saturating_add(Weight::from_ref_time(10_205 as u64).saturating_mul(v as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 5_541 nanoseconds. + Weight::from_ref_time(6_479_253) + // Standard Error: 49 + .saturating_add(Weight::from_ref_time(10_125).saturating_mul(v.into())) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Staking Bonded (r:1 w:1) // Storage: Staking SlashingSpans (r:1 w:0) @@ -688,23 +685,23 @@ impl WeightInfo for () { // Storage: Staking SpanSlash (r:0 w:2) /// The range of component `s` is `[0, 100]`. fn force_unstake(s: u32, ) -> Weight { - // Minimum execution time: 80_216 nanoseconds. - Weight::from_ref_time(86_090_609 as u64) - // Standard Error: 2_006 - .saturating_add(Weight::from_ref_time(1_039_308 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(11 as u64)) - .saturating_add(RocksDbWeight::get().writes(12 as u64)) - .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(s as u64))) + // Minimum execution time: 81_041 nanoseconds. + Weight::from_ref_time(88_526_481) + // Standard Error: 11_494 + .saturating_add(Weight::from_ref_time(1_095_933).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(11)) + .saturating_add(RocksDbWeight::get().writes(12)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into()))) } // Storage: Staking UnappliedSlashes (r:1 w:1) /// The range of component `s` is `[1, 1000]`. fn cancel_deferred_slash(s: u32, ) -> Weight { - // Minimum execution time: 92_034 nanoseconds. - Weight::from_ref_time(896_585_370 as u64) - // Standard Error: 58_231 - .saturating_add(Weight::from_ref_time(4_908_277 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 92_308 nanoseconds. + Weight::from_ref_time(900_351_007) + // Standard Error: 59_145 + .saturating_add(Weight::from_ref_time(4_944_988).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Staking CurrentEra (r:1 w:0) // Storage: Staking ErasValidatorReward (r:1 w:0) @@ -717,14 +714,14 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) /// The range of component `n` is `[0, 256]`. fn payout_stakers_dead_controller(n: u32, ) -> Weight { - // Minimum execution time: 127_936 nanoseconds. - Weight::from_ref_time(184_556_084 as u64) - // Standard Error: 26_981 - .saturating_add(Weight::from_ref_time(21_786_304 as u64).saturating_mul(n as u64)) - .saturating_add(RocksDbWeight::get().reads(9 as u64)) - .saturating_add(RocksDbWeight::get().reads((3 as u64).saturating_mul(n as u64))) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(n as u64))) + // Minimum execution time: 131_855 nanoseconds. + Weight::from_ref_time(197_412_779) + // Standard Error: 21_283 + .saturating_add(Weight::from_ref_time(22_093_758).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(9)) + .saturating_add(RocksDbWeight::get().reads((3_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes(2)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into()))) } // Storage: Staking CurrentEra (r:1 w:0) // Storage: Staking ErasValidatorReward (r:1 w:0) @@ -738,14 +735,14 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) /// The range of component `n` is `[0, 256]`. fn payout_stakers_alive_staked(n: u32, ) -> Weight { - // Minimum execution time: 157_778 nanoseconds. - Weight::from_ref_time(223_306_359 as u64) - // Standard Error: 27_216 - .saturating_add(Weight::from_ref_time(30_612_663 as u64).saturating_mul(n as u64)) - .saturating_add(RocksDbWeight::get().reads(10 as u64)) - .saturating_add(RocksDbWeight::get().reads((5 as u64).saturating_mul(n as u64))) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) - .saturating_add(RocksDbWeight::get().writes((3 as u64).saturating_mul(n as u64))) + // Minimum execution time: 163_118 nanoseconds. + Weight::from_ref_time(229_356_697) + // Standard Error: 30_740 + .saturating_add(Weight::from_ref_time(31_575_360).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(10)) + .saturating_add(RocksDbWeight::get().reads((5_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes(3)) + .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(n.into()))) } // Storage: Staking Ledger (r:1 w:1) // Storage: Balances Locks (r:1 w:1) @@ -755,12 +752,12 @@ impl WeightInfo for () { // Storage: VoterList ListBags (r:2 w:2) /// The range of component `l` is `[1, 32]`. fn rebond(l: u32, ) -> Weight { - // Minimum execution time: 92_880 nanoseconds. - Weight::from_ref_time(94_434_663 as u64) - // Standard Error: 1_734 - .saturating_add(Weight::from_ref_time(34_453 as u64).saturating_mul(l as u64)) - .saturating_add(RocksDbWeight::get().reads(9 as u64)) - .saturating_add(RocksDbWeight::get().writes(8 as u64)) + // Minimum execution time: 94_048 nanoseconds. + Weight::from_ref_time(95_784_236) + // Standard Error: 2_313 + .saturating_add(Weight::from_ref_time(52_798).saturating_mul(l.into())) + .saturating_add(RocksDbWeight::get().reads(9)) + .saturating_add(RocksDbWeight::get().writes(8)) } // Storage: System Account (r:1 w:1) // Storage: Staking Bonded (r:1 w:1) @@ -777,16 +774,15 @@ impl WeightInfo for () { // Storage: Staking SpanSlash (r:0 w:1) /// The range of component `s` is `[1, 100]`. fn reap_stash(s: u32, ) -> Weight { - // Minimum execution time: 92_334 nanoseconds. - Weight::from_ref_time(95_207_614 as u64) - // Standard Error: 1_822 - .saturating_add(Weight::from_ref_time(1_036_787 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(12 as u64)) - .saturating_add(RocksDbWeight::get().writes(12 as u64)) - .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(s as u64))) + // Minimum execution time: 93_342 nanoseconds. + Weight::from_ref_time(95_756_184) + // Standard Error: 2_067 + .saturating_add(Weight::from_ref_time(1_090_785).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(12)) + .saturating_add(RocksDbWeight::get().writes(12)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into()))) } // Storage: VoterList CounterForListNodes (r:1 w:0) - // Storage: Staking SlashingSpans (r:1 w:0) // Storage: VoterList ListBags (r:200 w:0) // Storage: VoterList ListNodes (r:101 w:0) // Storage: Staking Nominators (r:101 w:0) @@ -805,20 +801,19 @@ impl WeightInfo for () { /// The range of component `v` is `[1, 10]`. /// The range of component `n` is `[0, 100]`. fn new_era(v: u32, n: u32, ) -> Weight { - // Minimum execution time: 535_169 nanoseconds. - Weight::from_ref_time(548_667_000 as u64) - // Standard Error: 1_759_252 - .saturating_add(Weight::from_ref_time(58_283_319 as u64).saturating_mul(v as u64)) - // Standard Error: 175_299 - .saturating_add(Weight::from_ref_time(13_578_512 as u64).saturating_mul(n as u64)) - .saturating_add(RocksDbWeight::get().reads(207 as u64)) - .saturating_add(RocksDbWeight::get().reads((5 as u64).saturating_mul(v as u64))) - .saturating_add(RocksDbWeight::get().reads((4 as u64).saturating_mul(n as u64))) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) - .saturating_add(RocksDbWeight::get().writes((3 as u64).saturating_mul(v as u64))) + // Minimum execution time: 506_874 nanoseconds. + Weight::from_ref_time(507_798_000) + // Standard Error: 1_802_261 + .saturating_add(Weight::from_ref_time(59_874_736).saturating_mul(v.into())) + // Standard Error: 179_585 + .saturating_add(Weight::from_ref_time(13_668_574).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(206)) + .saturating_add(RocksDbWeight::get().reads((5_u64).saturating_mul(v.into()))) + .saturating_add(RocksDbWeight::get().reads((4_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes(3)) + .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(v.into()))) } // Storage: VoterList CounterForListNodes (r:1 w:0) - // Storage: Staking SlashingSpans (r:21 w:0) // Storage: VoterList ListBags (r:200 w:0) // Storage: VoterList ListNodes (r:1500 w:0) // Storage: Staking Nominators (r:1500 w:0) @@ -827,29 +822,27 @@ impl WeightInfo for () { // Storage: Staking Ledger (r:1500 w:0) /// The range of component `v` is `[500, 1000]`. /// The range of component `n` is `[500, 1000]`. - /// The range of component `s` is `[1, 20]`. - fn get_npos_voters(v: u32, n: u32, s: u32, ) -> Weight { - // Minimum execution time: 25_323_129 nanoseconds. - Weight::from_ref_time(25_471_672_000 as u64) - // Standard Error: 266_391 - .saturating_add(Weight::from_ref_time(6_665_504 as u64).saturating_mul(v as u64)) - // Standard Error: 266_391 - .saturating_add(Weight::from_ref_time(6_956_606 as u64).saturating_mul(n as u64)) - .saturating_add(RocksDbWeight::get().reads(202 as u64)) - .saturating_add(RocksDbWeight::get().reads((5 as u64).saturating_mul(v as u64))) - .saturating_add(RocksDbWeight::get().reads((4 as u64).saturating_mul(n as u64))) - .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(s as u64))) + fn get_npos_voters(v: u32, n: u32, ) -> Weight { + // Minimum execution time: 24_634_585 nanoseconds. + Weight::from_ref_time(24_718_377_000) + // Standard Error: 324_839 + .saturating_add(Weight::from_ref_time(3_654_508).saturating_mul(v.into())) + // Standard Error: 324_839 + .saturating_add(Weight::from_ref_time(2_927_535).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(201)) + .saturating_add(RocksDbWeight::get().reads((5_u64).saturating_mul(v.into()))) + .saturating_add(RocksDbWeight::get().reads((4_u64).saturating_mul(n.into()))) } // Storage: Staking CounterForValidators (r:1 w:0) // Storage: Staking Validators (r:501 w:0) /// The range of component `v` is `[500, 1000]`. fn get_npos_targets(v: u32, ) -> Weight { - // Minimum execution time: 4_905_036 nanoseconds. - Weight::from_ref_time(78_163_554 as u64) - // Standard Error: 23_723 - .saturating_add(Weight::from_ref_time(9_784_870 as u64).saturating_mul(v as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(v as u64))) + // Minimum execution time: 4_805_490 nanoseconds. + Weight::from_ref_time(118_475_494) + // Standard Error: 26_332 + .saturating_add(Weight::from_ref_time(9_635_188).saturating_mul(v.into())) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(v.into()))) } // Storage: Staking MinCommission (r:0 w:1) // Storage: Staking MinValidatorBond (r:0 w:1) @@ -858,9 +851,9 @@ impl WeightInfo for () { // Storage: Staking MaxNominatorsCount (r:0 w:1) // Storage: Staking MinNominatorBond (r:0 w:1) fn set_staking_configs_all_set() -> Weight { - // Minimum execution time: 10_096 nanoseconds. - Weight::from_ref_time(10_538_000 as u64) - .saturating_add(RocksDbWeight::get().writes(6 as u64)) + // Minimum execution time: 10_816 nanoseconds. + Weight::from_ref_time(11_242_000) + .saturating_add(RocksDbWeight::get().writes(6)) } // Storage: Staking MinCommission (r:0 w:1) // Storage: Staking MinValidatorBond (r:0 w:1) @@ -869,9 +862,9 @@ impl WeightInfo for () { // Storage: Staking MaxNominatorsCount (r:0 w:1) // Storage: Staking MinNominatorBond (r:0 w:1) fn set_staking_configs_all_remove() -> Weight { - // Minimum execution time: 9_045 nanoseconds. - Weight::from_ref_time(9_379_000 as u64) - .saturating_add(RocksDbWeight::get().writes(6 as u64)) + // Minimum execution time: 9_581 nanoseconds. + Weight::from_ref_time(10_383_000) + .saturating_add(RocksDbWeight::get().writes(6)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking Nominators (r:1 w:1) @@ -884,17 +877,17 @@ impl WeightInfo for () { // Storage: VoterList ListBags (r:1 w:1) // Storage: VoterList CounterForListNodes (r:1 w:1) fn chill_other() -> Weight { - // Minimum execution time: 81_457 nanoseconds. - Weight::from_ref_time(82_410_000 as u64) - .saturating_add(RocksDbWeight::get().reads(11 as u64)) - .saturating_add(RocksDbWeight::get().writes(6 as u64)) + // Minimum execution time: 83_669 nanoseconds. + Weight::from_ref_time(84_772_000) + .saturating_add(RocksDbWeight::get().reads(11)) + .saturating_add(RocksDbWeight::get().writes(6)) } // Storage: Staking MinCommission (r:1 w:0) // Storage: Staking Validators (r:1 w:1) fn force_apply_min_commission() -> Weight { - // Minimum execution time: 19_684 nanoseconds. - Weight::from_ref_time(20_059_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 20_553 nanoseconds. + Weight::from_ref_time(20_933_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) } } diff --git a/utils/frame/remote-externalities/src/lib.rs b/utils/frame/remote-externalities/src/lib.rs index 4f95331c03..db062e246c 100644 --- a/utils/frame/remote-externalities/src/lib.rs +++ b/utils/frame/remote-externalities/src/lib.rs @@ -776,6 +776,8 @@ impl Builder { /// Inject a hashed prefix. This is treated as-is, and should be pre-hashed. /// + /// Only relevant is `Mode::Online` is being used. Noop otherwise. + /// /// This should be used to inject a "PREFIX", like a storage (double) map. pub fn inject_hashed_prefix(mut self, hashed: &[u8]) -> Self { self.hashed_prefixes.push(hashed.to_vec()); @@ -785,6 +787,8 @@ impl Builder { /// Just a utility wrapper of [`Self::inject_hashed_prefix`] that injects /// [`DEFAULT_CHILD_STORAGE_KEY_PREFIX`] as a prefix. /// + /// Only relevant is `Mode::Online` is being used. Noop otherwise. + /// /// If set, this will guarantee that the child-tree data of ALL pallets will be downloaded. /// /// This is not needed if the entire state is being downloaded. @@ -800,6 +804,8 @@ impl Builder { /// Inject a hashed key to scrape. This is treated as-is, and should be pre-hashed. /// + /// Only relevant is `Mode::Online` is being used. Noop otherwise. + /// /// This should be used to inject a "KEY", like a storage value. pub fn inject_hashed_key(mut self, hashed: &[u8]) -> Self { self.hashed_keys.push(hashed.to_vec()); @@ -951,7 +957,6 @@ mod tests { #[cfg(all(test, feature = "remote-test"))] mod remote_tests { use super::test_prelude::*; - const REMOTE_INACCESSIBLE: &'static str = "Can't reach the remote node. Is it running?"; #[tokio::test] async fn offline_else_online_works() { @@ -970,7 +975,7 @@ mod remote_tests { )) .build() .await - .expect(REMOTE_INACCESSIBLE) + .unwrap() .execute_with(|| {}); // this shows that in the second run, we are not using the remote @@ -988,7 +993,7 @@ mod remote_tests { )) .build() .await - .expect(REMOTE_INACCESSIBLE) + .unwrap() .execute_with(|| {}); let to_delete = std::fs::read_dir(Path::new(".")) @@ -1018,7 +1023,7 @@ mod remote_tests { })) .build() .await - .expect(REMOTE_INACCESSIBLE) + .unwrap() .execute_with(|| {}); } @@ -1033,7 +1038,7 @@ mod remote_tests { })) .build() .await - .expect(REMOTE_INACCESSIBLE) + .unwrap() .execute_with(|| {}); Builder::::new() @@ -1044,7 +1049,7 @@ mod remote_tests { })) .build() .await - .expect(REMOTE_INACCESSIBLE) + .unwrap() .execute_with(|| {}); } @@ -1059,7 +1064,7 @@ mod remote_tests { })) .build() .await - .expect(REMOTE_INACCESSIBLE) + .unwrap() .execute_with(|| {}); Builder::::new() @@ -1070,7 +1075,7 @@ mod remote_tests { })) .build() .await - .expect(REMOTE_INACCESSIBLE) + .unwrap() .execute_with(|| {}); } @@ -1085,7 +1090,7 @@ mod remote_tests { })) .build() .await - .expect(REMOTE_INACCESSIBLE) + .unwrap() .execute_with(|| {}); let to_delete = std::fs::read_dir(Path::new(".")) @@ -1126,7 +1131,7 @@ mod remote_tests { .inject_default_child_tree_prefix() .build() .await - .expect(REMOTE_INACCESSIBLE) + .unwrap() .execute_with(|| {}); let to_delete = std::fs::read_dir(Path::new(".")) @@ -1164,7 +1169,7 @@ mod remote_tests { })) .build() .await - .expect(REMOTE_INACCESSIBLE) + .unwrap() .execute_with(|| {}); let to_delete = std::fs::read_dir(Path::new(".")) @@ -1203,7 +1208,7 @@ mod remote_tests { })) .build() .await - .expect(REMOTE_INACCESSIBLE) + .unwrap() .execute_with(|| {}); } } From 2a0eeff4008573f6ead70eb9bd43cf6d268d2e7d Mon Sep 17 00:00:00 2001 From: Muharem Ismailov Date: Mon, 12 Dec 2022 23:20:21 +0100 Subject: [PATCH 031/127] bounties calls docs fix (#12909) Co-authored-by: parity-processbot <> --- frame/bounties/src/lib.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/frame/bounties/src/lib.rs b/frame/bounties/src/lib.rs index eb92c774f8..c3c2c08d24 100644 --- a/frame/bounties/src/lib.rs +++ b/frame/bounties/src/lib.rs @@ -151,8 +151,7 @@ pub enum BountyStatus { Approved, /// The bounty is funded and waiting for curator assignment. Funded, - /// A curator has been proposed by the `ApproveOrigin`. Waiting for acceptance from the - /// curator. + /// A curator has been proposed. Waiting for acceptance from the curator. CuratorProposed { /// The assigned curator of this bounty. curator: AccountId, @@ -348,7 +347,7 @@ pub mod pallet { /// Approve a bounty proposal. At a later time, the bounty will be funded and become active /// and the original deposit will be returned. /// - /// May only be called from `T::ApproveOrigin`. + /// May only be called from `T::SpendOrigin`. /// /// # /// - O(1). @@ -380,7 +379,7 @@ pub mod pallet { /// Assign a curator to a funded bounty. /// - /// May only be called from `T::ApproveOrigin`. + /// May only be called from `T::SpendOrigin`. /// /// # /// - O(1). From 01efa856c6bb6fd120203afddd06c7b9719eddc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dino=20Pa=C4=8Dandi?= <3002868+Dinonard@users.noreply.github.com> Date: Tue, 13 Dec 2022 12:09:26 +0100 Subject: [PATCH 032/127] pallet-contracts migration pre-upgrade fix for v8 (#12905) * Only run pre-v8 migration check for versions older than 8 * Logix fix --- frame/contracts/src/migration.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/contracts/src/migration.rs b/frame/contracts/src/migration.rs index aa04d8b9b1..56d688abc7 100644 --- a/frame/contracts/src/migration.rs +++ b/frame/contracts/src/migration.rs @@ -69,7 +69,7 @@ impl OnRuntimeUpgrade for Migration { fn pre_upgrade() -> Result, &'static str> { let version = >::on_chain_storage_version(); - if version == 8 { + if version == 7 { v8::pre_upgrade::()?; } From 13664c388d59b4c83dcb90a27700a9b68abe0305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulo=20Silva=20de=20Souza?= <77391175+joao-paulo-parity@users.noreply.github.com> Date: Tue, 13 Dec 2022 08:42:50 -0300 Subject: [PATCH 033/127] use custom environment for publishing crates (#12912) --- scripts/ci/gitlab/pipeline/publish.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/ci/gitlab/pipeline/publish.yml b/scripts/ci/gitlab/pipeline/publish.yml index 9053035a61..381a1bc420 100644 --- a/scripts/ci/gitlab/pipeline/publish.yml +++ b/scripts/ci/gitlab/pipeline/publish.yml @@ -211,6 +211,9 @@ update-node-template: # to roughly 202 minutes of delay, or 3h and 22 minutes. As such, the job needs to have a much # higher timeout than average. timeout: 5h + # A custom publishing environment is used for us to be able to set up protected secrets + # specifically for it + environment: publish-crates script: - rusty-cachier snapshot create - git clone From 93fa104fd4d4762c4d5c01257820bd3acf24a17b Mon Sep 17 00:00:00 2001 From: Sasha Gryaznov Date: Tue, 13 Dec 2022 17:54:50 +0200 Subject: [PATCH 034/127] [contracts] Add debug buffer limit + enforcement (#12845) * Add debug buffer limit + enforcement Add debug buffer limit + enforcement * use BoundedVec for the debug buffer * revert schedule (debug buf len limit not needed anymore) * return DispatchError * addressed review comments --- bin/node/runtime/src/lib.rs | 1 + frame/contracts/src/exec.rs | 84 ++++++++++++++++++++++------- frame/contracts/src/lib.rs | 22 +++++--- frame/contracts/src/tests.rs | 1 + frame/contracts/src/wasm/mod.rs | 4 +- frame/contracts/src/wasm/runtime.rs | 4 +- 6 files changed, 86 insertions(+), 30 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 7cd42be73a..00d2a54d1e 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1216,6 +1216,7 @@ impl pallet_contracts::Config for Runtime { type MaxCodeLen = ConstU32<{ 128 * 1024 }>; type MaxStorageKeyLen = ConstU32<128>; type UnsafeUnstableInterface = ConstBool; + type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>; } impl pallet_sudo::Config for Runtime { diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index c0cf6a9f4c..945095dc20 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -18,8 +18,8 @@ use crate::{ gas::GasMeter, storage::{self, Storage, WriteOutcome}, - BalanceOf, CodeHash, Config, ContractInfo, ContractInfoOf, Determinism, Error, Event, Nonce, - Pallet as Contracts, Schedule, + BalanceOf, CodeHash, Config, ContractInfo, ContractInfoOf, DebugBufferVec, Determinism, Error, + Event, Nonce, Pallet as Contracts, Schedule, }; use frame_support::{ crypto::ecdsa::ECDSAExt, @@ -279,7 +279,7 @@ pub trait Ext: sealing::Sealed { /// when the code is executing on-chain. /// /// Returns `true` if debug message recording is enabled. Otherwise `false` is returned. - fn append_debug_buffer(&mut self, msg: &str) -> bool; + fn append_debug_buffer(&mut self, msg: &str) -> Result; /// Call some dispatchable and return the result. fn call_runtime(&self, call: ::RuntimeCall) -> DispatchResultWithPostInfo; @@ -409,7 +409,7 @@ pub struct Stack<'a, T: Config, E> { /// /// All the bytes added to this field should be valid UTF-8. The buffer has no defined /// structure and is intended to be shown to users as-is for debugging purposes. - debug_message: Option<&'a mut Vec>, + debug_message: Option<&'a mut DebugBufferVec>, /// The determinism requirement of this call stack. determinism: Determinism, /// No executable is held by the struct but influences its behaviour. @@ -617,7 +617,7 @@ where schedule: &'a Schedule, value: BalanceOf, input_data: Vec, - debug_message: Option<&'a mut Vec>, + debug_message: Option<&'a mut DebugBufferVec>, determinism: Determinism, ) -> Result { let (mut stack, executable) = Self::new( @@ -652,7 +652,7 @@ where value: BalanceOf, input_data: Vec, salt: &[u8], - debug_message: Option<&'a mut Vec>, + debug_message: Option<&'a mut DebugBufferVec>, ) -> Result<(T::AccountId, ExecReturnValue), ExecError> { let (mut stack, executable) = Self::new( FrameArgs::Instantiate { @@ -681,7 +681,7 @@ where storage_meter: &'a mut storage::meter::Meter, schedule: &'a Schedule, value: BalanceOf, - debug_message: Option<&'a mut Vec>, + debug_message: Option<&'a mut DebugBufferVec>, determinism: Determinism, ) -> Result<(Self, E), ExecError> { let (first_frame, executable, nonce) = Self::new_frame( @@ -1328,14 +1328,16 @@ where &mut self.top_frame_mut().nested_gas } - fn append_debug_buffer(&mut self, msg: &str) -> bool { + fn append_debug_buffer(&mut self, msg: &str) -> Result { if let Some(buffer) = &mut self.debug_message { if !msg.is_empty() { - buffer.extend(msg.as_bytes()); + buffer + .try_extend(&mut msg.bytes()) + .map_err(|_| Error::::DebugBufferExhausted)?; } - true + Ok(true) } else { - false + Ok(false) } } @@ -2503,12 +2505,16 @@ mod tests { #[test] fn printing_works() { let code_hash = MockLoader::insert(Call, |ctx, _| { - ctx.ext.append_debug_buffer("This is a test"); - ctx.ext.append_debug_buffer("More text"); + ctx.ext + .append_debug_buffer("This is a test") + .expect("Maximum allowed debug buffer size exhausted!"); + ctx.ext + .append_debug_buffer("More text") + .expect("Maximum allowed debug buffer size exhausted!"); exec_success() }); - let mut debug_buffer = Vec::new(); + let mut debug_buffer = DebugBufferVec::::try_from(Vec::new()).unwrap(); ExtBuilder::default().build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); @@ -2531,18 +2537,22 @@ mod tests { .unwrap(); }); - assert_eq!(&String::from_utf8(debug_buffer).unwrap(), "This is a testMore text"); + assert_eq!(&String::from_utf8(debug_buffer.to_vec()).unwrap(), "This is a testMore text"); } #[test] fn printing_works_on_fail() { let code_hash = MockLoader::insert(Call, |ctx, _| { - ctx.ext.append_debug_buffer("This is a test"); - ctx.ext.append_debug_buffer("More text"); + ctx.ext + .append_debug_buffer("This is a test") + .expect("Maximum allowed debug buffer size exhausted!"); + ctx.ext + .append_debug_buffer("More text") + .expect("Maximum allowed debug buffer size exhausted!"); exec_trapped() }); - let mut debug_buffer = Vec::new(); + let mut debug_buffer = DebugBufferVec::::try_from(Vec::new()).unwrap(); ExtBuilder::default().build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); @@ -2565,7 +2575,43 @@ mod tests { assert!(result.is_err()); }); - assert_eq!(&String::from_utf8(debug_buffer).unwrap(), "This is a testMore text"); + assert_eq!(&String::from_utf8(debug_buffer.to_vec()).unwrap(), "This is a testMore text"); + } + + #[test] + fn debug_buffer_is_limited() { + let code_hash = MockLoader::insert(Call, move |ctx, _| { + ctx.ext.append_debug_buffer("overflowing bytes")?; + exec_success() + }); + + // Pre-fill the buffer up to its limit + let mut debug_buffer = + DebugBufferVec::::try_from(vec![0u8; DebugBufferVec::::bound()]).unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let schedule: Schedule = ::Schedule::get(); + let min_balance = ::Currency::minimum_balance(); + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + set_balance(&ALICE, min_balance * 10); + place_contract(&BOB, code_hash); + let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); + assert_err!( + MockStack::run_call( + ALICE, + BOB, + &mut gas_meter, + &mut storage_meter, + &schedule, + 0, + vec![], + Some(&mut debug_buffer), + Determinism::Deterministic, + ) + .map_err(|e| e.error), + Error::::DebugBufferExhausted + ); + }); } #[test] diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index 06d817785c..b76acf9d1d 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -142,6 +142,7 @@ type BalanceOf = type CodeVec = BoundedVec::MaxCodeLen>; type RelaxedCodeVec = WeakBoundedVec::MaxCodeLen>; type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; +type DebugBufferVec = BoundedVec::MaxDebugBufferLen>; /// Used as a sentinel value when reading and writing contract memory. /// @@ -344,6 +345,10 @@ pub mod pallet { /// Do **not** set to `true` on productions chains. #[pallet::constant] type UnsafeUnstableInterface: Get; + + /// The maximum length of the debug buffer in bytes. + #[pallet::constant] + type MaxDebugBufferLen: Get; } #[pallet::hooks] @@ -863,6 +868,9 @@ pub mod pallet { CodeRejected, /// An indetermistic code was used in a context where this is not permitted. Indeterministic, + /// The debug buffer size used during contract execution exceeded the limit determined by + /// the `MaxDebugBufferLen` pallet config parameter. + DebugBufferExhausted, } /// A mapping from an original code hash to the original code, untouched by instrumentation. @@ -961,7 +969,7 @@ where debug: bool, determinism: Determinism, ) -> ContractExecResult> { - let mut debug_message = if debug { Some(Vec::new()) } else { None }; + let mut debug_message = if debug { Some(DebugBufferVec::::default()) } else { None }; let output = Self::internal_call( origin, dest, @@ -977,7 +985,7 @@ where gas_consumed: output.gas_meter.gas_consumed(), gas_required: output.gas_meter.gas_required(), storage_deposit: output.storage_deposit, - debug_message: debug_message.unwrap_or_default(), + debug_message: debug_message.unwrap_or_default().to_vec(), } } @@ -1003,7 +1011,7 @@ where salt: Vec, debug: bool, ) -> ContractInstantiateResult> { - let mut debug_message = if debug { Some(Vec::new()) } else { None }; + let mut debug_message = if debug { Some(DebugBufferVec::::default()) } else { None }; let output = Self::internal_instantiate( origin, value, @@ -1022,7 +1030,7 @@ where gas_consumed: output.gas_meter.gas_consumed(), gas_required: output.gas_meter.gas_required(), storage_deposit: output.storage_deposit, - debug_message: debug_message.unwrap_or_default(), + debug_message: debug_message.unwrap_or_default().to_vec(), } } @@ -1113,7 +1121,7 @@ where gas_limit: Weight, storage_deposit_limit: Option>, data: Vec, - debug_message: Option<&mut Vec>, + debug_message: Option<&mut DebugBufferVec>, determinism: Determinism, ) -> InternalCallOutput { let mut gas_meter = GasMeter::new(gas_limit); @@ -1156,7 +1164,7 @@ where code: Code>, data: Vec, salt: Vec, - mut debug_message: Option<&mut Vec>, + mut debug_message: Option<&mut DebugBufferVec>, ) -> InternalInstantiateOutput { let mut storage_deposit = Default::default(); let mut gas_meter = GasMeter::new(gas_limit); @@ -1172,7 +1180,7 @@ where TryInstantiate::Skip, ) .map_err(|(err, msg)| { - debug_message.as_mut().map(|buffer| buffer.extend(msg.as_bytes())); + debug_message.as_mut().map(|buffer| buffer.try_extend(&mut msg.bytes())); err })?; // The open deposit will be charged during execution when the diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index a467800dfe..6121d880ca 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -415,6 +415,7 @@ impl Config for Test { type MaxCodeLen = ConstU32<{ 128 * 1024 }>; type MaxStorageKeyLen = ConstU32<128>; type UnsafeUnstableInterface = UnstableInterface; + type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>; } pub const ALICE: AccountId32 = AccountId32::new([1u8; 32]); diff --git a/frame/contracts/src/wasm/mod.rs b/frame/contracts/src/wasm/mod.rs index e9e6b42dc3..d85dac95cc 100644 --- a/frame/contracts/src/wasm/mod.rs +++ b/frame/contracts/src/wasm/mod.rs @@ -594,9 +594,9 @@ mod tests { fn gas_meter(&mut self) -> &mut GasMeter { &mut self.gas_meter } - fn append_debug_buffer(&mut self, msg: &str) -> bool { + fn append_debug_buffer(&mut self, msg: &str) -> Result { self.debug_buffer.extend(msg.as_bytes()); - true + Ok(true) } fn call_runtime( &self, diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index b933688eb6..50ad9996e6 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -2395,11 +2395,11 @@ pub mod env { str_len: u32, ) -> Result { ctx.charge_gas(RuntimeCosts::DebugMessage)?; - if ctx.ext.append_debug_buffer("") { + if ctx.ext.append_debug_buffer("")? { let data = ctx.read_sandbox_memory(memory, str_ptr, str_len)?; let msg = core::str::from_utf8(&data).map_err(|_| >::DebugMessageInvalidUTF8)?; - ctx.ext.append_debug_buffer(msg); + ctx.ext.append_debug_buffer(msg)?; return Ok(ReturnCode::Success) } Ok(ReturnCode::LoggingDisabled) From 89498c0d756c649d71e82340bee44fcc7cfe8037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Tue, 13 Dec 2022 22:47:51 +0100 Subject: [PATCH 035/127] Fixup some wrong dependencies (#12899) * Fixup some wrong dependencies Dev dependencies should not appear in the feature list. If features are required, they should be directly enabled for the `dev-dependency`. * More fixups * Fix fix * Remove deprecated feature * Make all work properly and nice!! * FMT * Fix formatting --- Cargo.lock | 2 +- frame/assets/Cargo.toml | 3 +- frame/assets/src/lib.rs | 2 + frame/bags-list/src/lib.rs | 58 ++++++++++--------- frame/election-provider-support/Cargo.toml | 3 +- frame/election-provider-support/src/lib.rs | 3 + frame/fast-unstake/Cargo.toml | 6 -- frame/nomination-pools/Cargo.toml | 2 +- .../nomination-pools/benchmarking/Cargo.toml | 2 - frame/staking/Cargo.toml | 8 +-- frame/staking/src/pallet/impls.rs | 41 +++++++------ .../asset-tx-payment/Cargo.toml | 4 -- .../asset-tx-payment/src/tests.rs | 5 +- primitives/core/src/lib.rs | 46 +++++++++++++++ primitives/staking/Cargo.toml | 2 + primitives/staking/src/lib.rs | 2 + utils/wasm-builder/src/wasm_project.rs | 9 +-- 17 files changed, 120 insertions(+), 78 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2f2b4aa7cf..ea34146f16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5176,7 +5176,6 @@ dependencies = [ "frame-support", "frame-system", "log", - "pallet-assets", "pallet-balances", "pallet-staking", "pallet-staking-reward-curve", @@ -9475,6 +9474,7 @@ version = "4.0.0-dev" dependencies = [ "parity-scale-codec", "scale-info", + "sp-core", "sp-runtime", "sp-std", ] diff --git a/frame/assets/Cargo.toml b/frame/assets/Cargo.toml index 715149b20c..84bfd9535a 100644 --- a/frame/assets/Cargo.toml +++ b/frame/assets/Cargo.toml @@ -23,9 +23,9 @@ frame-support = { version = "4.0.0-dev", default-features = false, path = "../su # `system` module provides us with all sorts of useful stuff and macros depend on it being around. frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } [dev-dependencies] -sp-core = { version = "7.0.0", path = "../../primitives/core" } sp-std = { version = "5.0.0", path = "../../primitives/std" } sp-io = { version = "7.0.0", path = "../../primitives/io" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } @@ -35,6 +35,7 @@ default = ["std"] std = [ "codec/std", "scale-info/std", + "sp-core/std", "sp-std/std", "sp-runtime/std", "frame-support/std", diff --git a/frame/assets/src/lib.rs b/frame/assets/src/lib.rs index 629a0243cf..ab589c0eef 100644 --- a/frame/assets/src/lib.rs +++ b/frame/assets/src/lib.rs @@ -1487,3 +1487,5 @@ pub mod pallet { } } } + +sp_core::generate_feature_enabled_macro!(runtime_benchmarks_enabled, feature = "runtime-benchmarks", $); diff --git a/frame/bags-list/src/lib.rs b/frame/bags-list/src/lib.rs index 1ffdf29345..14f8a613eb 100644 --- a/frame/bags-list/src/lib.rs +++ b/frame/bags-list/src/lib.rs @@ -359,25 +359,26 @@ impl, I: 'static> SortedListProvider for Pallet List::::unsafe_clear() } - #[cfg(feature = "runtime-benchmarks")] - fn score_update_worst_case(who: &T::AccountId, is_increase: bool) -> Self::Score { - use frame_support::traits::Get as _; - let thresholds = T::BagThresholds::get(); - let node = list::Node::::get(who).unwrap(); - let current_bag_idx = thresholds - .iter() - .chain(sp_std::iter::once(&T::Score::max_value())) - .position(|w| w == &node.bag_upper()) - .unwrap(); - - if is_increase { - let next_threshold_idx = current_bag_idx + 1; - assert!(thresholds.len() > next_threshold_idx); - thresholds[next_threshold_idx] - } else { - assert!(current_bag_idx != 0); - let prev_threshold_idx = current_bag_idx - 1; - thresholds[prev_threshold_idx] + frame_election_provider_support::runtime_benchmarks_enabled! { + fn score_update_worst_case(who: &T::AccountId, is_increase: bool) -> Self::Score { + use frame_support::traits::Get as _; + let thresholds = T::BagThresholds::get(); + let node = list::Node::::get(who).unwrap(); + let current_bag_idx = thresholds + .iter() + .chain(sp_std::iter::once(&T::Score::max_value())) + .position(|w| w == &node.bag_upper) + .unwrap(); + + if is_increase { + let next_threshold_idx = current_bag_idx + 1; + assert!(thresholds.len() > next_threshold_idx); + thresholds[next_threshold_idx] + } else { + assert!(current_bag_idx != 0); + let prev_threshold_idx = current_bag_idx - 1; + thresholds[prev_threshold_idx] + } } } } @@ -389,14 +390,15 @@ impl, I: 'static> ScoreProvider for Pallet { Node::::get(id).map(|node| node.score()).unwrap_or_default() } - #[cfg(any(feature = "runtime-benchmarks", feature = "fuzz", test))] - fn set_score_of(id: &T::AccountId, new_score: T::Score) { - ListNodes::::mutate(id, |maybe_node| { - if let Some(node) = maybe_node.as_mut() { - node.set_score(new_score) - } else { - panic!("trying to mutate {:?} which does not exists", id); - } - }) + frame_election_provider_support::runtime_benchmarks_or_fuzz_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() { + node.score = new_score; + } else { + panic!("trying to mutate {:?} which does not exists", id); + } + }) + } } } diff --git a/frame/election-provider-support/Cargo.toml b/frame/election-provider-support/Cargo.toml index b9584c899e..33a6f25ed0 100644 --- a/frame/election-provider-support/Cargo.toml +++ b/frame/election-provider-support/Cargo.toml @@ -21,10 +21,10 @@ sp-arithmetic = { version = "6.0.0", default-features = false, path = "../../pri sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../primitives/npos-elections" } sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } [dev-dependencies] rand = "0.7.3" -sp-core = { version = "7.0.0", path = "../../primitives/core" } sp-io = { version = "7.0.0", path = "../../primitives/io" } sp-npos-elections = { version = "4.0.0-dev", path = "../../primitives/npos-elections" } @@ -38,6 +38,7 @@ std = [ "scale-info/std", "sp-arithmetic/std", "sp-npos-elections/std", + "sp-core/std", "sp-runtime/std", "sp-std/std", ] diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index 8b26148844..9d5d6c018e 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -671,3 +671,6 @@ pub type BoundedSupportsOf = BoundedSupports< ::AccountId, ::MaxWinners, >; + +sp_core::generate_feature_enabled_macro!(runtime_benchmarks_enabled, feature = "runtime-benchmarks", $); +sp_core::generate_feature_enabled_macro!(runtime_benchmarks_or_fuzz_enabled, any(feature = "runtime-benchmarks", feature = "fuzzing"), $); diff --git a/frame/fast-unstake/Cargo.toml b/frame/fast-unstake/Cargo.toml index 61bc823cc1..b61060e775 100644 --- a/frame/fast-unstake/Cargo.toml +++ b/frame/fast-unstake/Cargo.toml @@ -25,10 +25,7 @@ sp-std = { version = "5.0.0", default-features = false, path = "../../primitives sp-staking = { default-features = false, path = "../../primitives/staking" } frame-election-provider-support = { default-features = false, path = "../election-provider-support" } -# optional dependencies for cargo features frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } -pallet-staking = { default-features = false, optional = true, path = "../staking" } -pallet-assets = { default-features = false, optional = true, path = "../assets" } [dev-dependencies] pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../staking/reward-curve" } @@ -38,8 +35,6 @@ sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } pallet-staking = { path = "../staking" } pallet-balances = { path = "../balances" } pallet-timestamp = { path = "../timestamp" } -pallet-assets = { path = "../assets" } - [features] default = ["std"] @@ -64,6 +59,5 @@ runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-system/runtime-benchmarks", "sp-staking/runtime-benchmarks", - "pallet-staking/runtime-benchmarks" ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/nomination-pools/Cargo.toml b/frame/nomination-pools/Cargo.toml index 4894e3d97f..3eb2d4bc5f 100644 --- a/frame/nomination-pools/Cargo.toml +++ b/frame/nomination-pools/Cargo.toml @@ -26,7 +26,7 @@ sp-core = { version = "7.0.0", default-features = false, path = "../../primitive sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } log = { version = "0.4.0", default-features = false } -# Optional: usef for testing and/or fuzzing +# Optional: use for testing and/or fuzzing pallet-balances = { version = "4.0.0-dev", path = "../balances", optional = true } sp-tracing = { version = "6.0.0", path = "../../primitives/tracing", optional = true } diff --git a/frame/nomination-pools/benchmarking/Cargo.toml b/frame/nomination-pools/benchmarking/Cargo.toml index be52d9777a..74b71a353f 100644 --- a/frame/nomination-pools/benchmarking/Cargo.toml +++ b/frame/nomination-pools/benchmarking/Cargo.toml @@ -31,7 +31,6 @@ sp-runtime = { version = "7.0.0", default-features = false, path = "../../../pri sp-runtime-interface = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime-interface" } sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/staking" } sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } -sp-io = { optional = true, default-features = false, path = "../../../primitives/io" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../../balances" } @@ -53,7 +52,6 @@ std = [ "pallet-nomination-pools/std", "sp-runtime/std", "sp-runtime-interface/std", - "sp-io/std", "sp-staking/std", "sp-std/std", ] diff --git a/frame/staking/Cargo.toml b/frame/staking/Cargo.toml index 3ad63ad94a..a7fca045cc 100644 --- a/frame/staking/Cargo.toml +++ b/frame/staking/Cargo.toml @@ -32,10 +32,9 @@ sp-application-crypto = { version = "7.0.0", default-features = false, path = ". frame-election-provider-support = { version = "4.0.0-dev", default-features = false, path = "../election-provider-support" } log = { version = "0.4.17", default-features = false } -# optional dependencies for cargo features +# Optional imports for benchmarking frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } rand_chacha = { version = "0.2", default-features = false, optional = true } -pallet-bags-list = { default-features = false, optional = true, path = "../bags-list" } [dev-dependencies] sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } @@ -75,10 +74,5 @@ runtime-benchmarks = [ "frame-election-provider-support/runtime-benchmarks", "rand_chacha", "sp-staking/runtime-benchmarks", - "pallet-bags-list/runtime-benchmarks", ] try-runtime = ["frame-support/try-runtime"] -fuzz = [ - "pallet-bags-list/fuzz", - "frame-election-provider-support/fuzz", -] diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 6729a2ca32..1a4086ad2a 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -1329,7 +1329,7 @@ impl ScoreProvider for Pallet { Self::weight_of(who) } - #[cfg(any(feature = "runtime-benchmarks", feature = "fuzz"))] + #[cfg(feature = "runtime-benchmarks")] fn set_score_of(who: &T::AccountId, weight: Self::Score) { // this will clearly results in an inconsistent state, but it should not matter for a // benchmark. @@ -1594,28 +1594,27 @@ impl StakingInterface for Pallet { Self::nominate(RawOrigin::Signed(ctrl).into(), targets) } - #[cfg(feature = "runtime-benchmarks")] - fn nominations(who: Self::AccountId) -> Option> { - Nominators::::get(who).map(|n| n.targets.into_inner()) - } + sp_staking::runtime_benchmarks_enabled! { + fn nominations(who: Self::AccountId) -> Option> { + Nominators::::get(who).map(|n| n.targets.into_inner()) + } - #[cfg(feature = "runtime-benchmarks")] - fn add_era_stakers( - current_era: &EraIndex, - stash: &T::AccountId, - exposures: Vec<(Self::AccountId, Self::Balance)>, - ) { - let others = exposures - .iter() - .map(|(who, value)| IndividualExposure { who: who.clone(), value: value.clone() }) - .collect::>(); - let exposure = Exposure { total: Default::default(), own: Default::default(), others }; - Self::add_era_stakers(current_era.clone(), stash.clone(), exposure) - } + fn add_era_stakers( + current_era: &EraIndex, + stash: &T::AccountId, + exposures: Vec<(Self::AccountId, Self::Balance)>, + ) { + let others = exposures + .iter() + .map(|(who, value)| IndividualExposure { who: who.clone(), value: value.clone() }) + .collect::>(); + let exposure = Exposure { total: Default::default(), own: Default::default(), others }; + >::insert(¤t_era, &stash, &exposure); + } - #[cfg(feature = "runtime-benchmarks")] - fn set_current_era(era: EraIndex) { - CurrentEra::::put(era); + fn set_current_era(era: EraIndex) { + CurrentEra::::put(era); + } } } diff --git a/frame/transaction-payment/asset-tx-payment/Cargo.toml b/frame/transaction-payment/asset-tx-payment/Cargo.toml index b192c4e9cd..8e4645a267 100644 --- a/frame/transaction-payment/asset-tx-payment/Cargo.toml +++ b/frame/transaction-payment/asset-tx-payment/Cargo.toml @@ -19,12 +19,10 @@ sp-io = { version = "7.0.0", default-features = false, path = "../../../primitiv sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } -# optional dependencies for cargo features frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } pallet-transaction-payment = { version = "4.0.0-dev", default-features = false, path = ".." } frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../benchmarking", optional = true } -pallet-assets = { default-features = false, optional = true, path = "../../assets" } # Other dependencies codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } @@ -40,7 +38,6 @@ pallet-assets = { version = "4.0.0-dev", path = "../../assets" } pallet-authorship = { version = "4.0.0-dev", path = "../../authorship" } pallet-balances = { version = "4.0.0-dev", path = "../../balances" } - [features] default = ["std"] std = [ @@ -60,6 +57,5 @@ runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "frame-system/runtime-benchmarks", - "pallet-assets/runtime-benchmarks", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/transaction-payment/asset-tx-payment/src/tests.rs b/frame/transaction-payment/asset-tx-payment/src/tests.rs index 02e15654f3..b70a88d02c 100644 --- a/frame/transaction-payment/asset-tx-payment/src/tests.rs +++ b/frame/transaction-payment/asset-tx-payment/src/tests.rs @@ -173,8 +173,9 @@ impl pallet_assets::Config for Runtime { type Extra = (); type WeightInfo = (); type RemoveItemsLimit = ConstU32<1000>; - #[cfg(feature = "runtime-benchmarks")] - type BenchmarkHelper = (); + pallet_assets::runtime_benchmarks_enabled! { + type BenchmarkHelper = (); + } } pub struct HardcodedAuthor; diff --git a/primitives/core/src/lib.rs b/primitives/core/src/lib.rs index fda7604d53..30d0cc199b 100644 --- a/primitives/core/src/lib.rs +++ b/primitives/core/src/lib.rs @@ -622,3 +622,49 @@ macro_rules! bounded_btree_map { } }; } + +/// Generates a macro for checking if a certain feature is enabled. +/// +/// These feature checking macros can be used to conditionally enable/disable code in a dependent +/// crate based on a feature in the crate where the macro is called. +#[macro_export] +// We need to skip formatting this macro because of this bug: +// https://github.com/rust-lang/rustfmt/issues/5283 +#[rustfmt::skip] +macro_rules! generate_feature_enabled_macro { + ( $macro_name:ident, $feature_name:meta, $d:tt ) => { + /// Enable/disable the given code depending on + #[doc = concat!("`", stringify!($feature_name), "`")] + /// being enabled for the crate or not. + /// + /// # Example + /// + /// ```nocompile + /// // Will add the code depending on the feature being enabled or not. + #[doc = concat!(stringify!($macro_name), "!( println!(\"Hello\") )")] + /// ``` + #[cfg($feature_name)] + #[macro_export] + macro_rules! $macro_name { + ( $d ( $d input:tt )* ) => { + $d ( $d input )* + } + } + + /// Enable/disable the given code depending on + #[doc = concat!("`", stringify!($feature_name), "`")] + /// being enabled for the crate or not. + /// + /// # Example + /// + /// ```nocompile + /// // Will add the code depending on the feature being enabled or not. + #[doc = concat!(stringify!($macro_name), "!( println!(\"Hello\") )")] + /// ``` + #[cfg(not($feature_name))] + #[macro_export] + macro_rules! $macro_name { + ( $d ( $d input:tt )* ) => {}; + } + }; +} diff --git a/primitives/staking/Cargo.toml b/primitives/staking/Cargo.toml index 550c1485e9..35feae43eb 100644 --- a/primitives/staking/Cargo.toml +++ b/primitives/staking/Cargo.toml @@ -15,6 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +sp-core = { version = "7.0.0", default-features = false, path = "../core" } sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } sp-std = { version = "5.0.0", default-features = false, path = "../std" } @@ -23,6 +24,7 @@ default = ["std"] std = [ "codec/std", "scale-info/std", + "sp-core/std", "sp-runtime/std", "sp-std/std", ] diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 703f0abe80..9eb4a4890c 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -190,3 +190,5 @@ pub trait StakingInterface { #[cfg(feature = "runtime-benchmarks")] fn set_current_era(era: EraIndex); } + +sp_core::generate_feature_enabled_macro!(runtime_benchmarks_enabled, feature = "runtime-benchmarks", $); diff --git a/utils/wasm-builder/src/wasm_project.rs b/utils/wasm-builder/src/wasm_project.rs index 7688069dd7..d17997360d 100644 --- a/utils/wasm-builder/src/wasm_project.rs +++ b/utils/wasm-builder/src/wasm_project.rs @@ -379,14 +379,15 @@ fn find_package_by_manifest_path<'a>( if let Some(pkg) = crate_metadata.packages.iter().find(|p| p.manifest_path == manifest_path) { return pkg } + let pkgs_by_name = crate_metadata .packages .iter() .filter(|p| p.name == pkg_name) .collect::>(); - let mut pkgs = pkgs_by_name.iter(); - if let Some(pkg) = pkgs.next() { - if pkgs.next().is_some() { + + if let Some(pkg) = pkgs_by_name.first() { + if pkgs_by_name.len() > 1 { panic!( "Found multiple packages matching the name {pkg_name} ({manifest_path:?}): {:?}", pkgs_by_name @@ -395,7 +396,7 @@ fn find_package_by_manifest_path<'a>( return pkg } } else { - panic!("Failed to find entry for package {pkg_name} ({manifest_path:?})"); + panic!("Failed to find entry for package {pkg_name} ({manifest_path:?})."); } } From b65c9f044804b6f401197b2567e1ab10b4776159 Mon Sep 17 00:00:00 2001 From: Alexander Popiak Date: Wed, 14 Dec 2022 10:59:04 +0100 Subject: [PATCH 036/127] add numerator and denominator to Rational128 Debug impl and increase precision of float representation (#12914) --- primitives/arithmetic/src/rational.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/primitives/arithmetic/src/rational.rs b/primitives/arithmetic/src/rational.rs index 54cabfc621..447b37551b 100644 --- a/primitives/arithmetic/src/rational.rs +++ b/primitives/arithmetic/src/rational.rs @@ -94,14 +94,14 @@ pub struct Rational128(u128, u128); #[cfg(feature = "std")] impl sp_std::fmt::Debug for Rational128 { fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { - write!(f, "Rational128({:.4})", self.0 as f32 / self.1 as f32) + write!(f, "Rational128({} / {} ≈ {:.8})", self.0, self.1, self.0 as f64 / self.1 as f64) } } #[cfg(not(feature = "std"))] impl sp_std::fmt::Debug for Rational128 { fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { - write!(f, "Rational128(..)") + write!(f, "Rational128({} / {})", self.0, self.1) } } From 59b590300c368f285e885a5304d231dd994b6020 Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Wed, 14 Dec 2022 12:03:16 +0100 Subject: [PATCH 037/127] Fix state-db pinning (#12927) * Pin all canonicalized blocks * Added a test * Docs --- client/db/src/lib.rs | 1 + client/state-db/src/lib.rs | 10 +++++ client/state-db/src/noncanonical.rs | 65 +++++++++++++++++++++-------- 3 files changed, 58 insertions(+), 18 deletions(-) diff --git a/client/db/src/lib.rs b/client/db/src/lib.rs index 426876f5cb..4452d5dcb3 100644 --- a/client/db/src/lib.rs +++ b/client/db/src/lib.rs @@ -2002,6 +2002,7 @@ impl sc_client_api::backend::Backend for Backend { .map_err(sp_blockchain::Error::from_state_db)?; Err(e) } else { + self.storage.state_db.sync(); Ok(()) } } diff --git a/client/state-db/src/lib.rs b/client/state-db/src/lib.rs index 94d4178770..5e01a0e063 100644 --- a/client/state-db/src/lib.rs +++ b/client/state-db/src/lib.rs @@ -470,6 +470,10 @@ impl StateDbSync { } } + fn sync(&mut self) { + self.non_canonical.sync(); + } + pub fn get( &self, key: &Q, @@ -573,6 +577,12 @@ impl StateDb { self.db.write().unpin(hash) } + /// Confirm that all changes made to commit sets are on disk. Allows for temporarily pinned + /// blocks to be released. + pub fn sync(&self) { + self.db.write().sync() + } + /// Get a value from non-canonical/pruning overlay or the backing DB. pub fn get( &self, diff --git a/client/state-db/src/noncanonical.rs b/client/state-db/src/noncanonical.rs index 7cb3017966..84ba94c052 100644 --- a/client/state-db/src/noncanonical.rs +++ b/client/state-db/src/noncanonical.rs @@ -38,7 +38,7 @@ pub struct NonCanonicalOverlay { // would be deleted but kept around because block is pinned, ref counted. pinned: HashMap, pinned_insertions: HashMap, u32)>, - last_canon_pinned: Option, + pinned_canonincalized: Vec, } #[cfg_attr(test, derive(PartialEq, Debug))] @@ -226,7 +226,7 @@ impl NonCanonicalOverlay { pinned: Default::default(), pinned_insertions: Default::default(), values, - last_canon_pinned: None, + pinned_canonincalized: Default::default(), }) } @@ -350,6 +350,18 @@ impl NonCanonicalOverlay { self.last_canonicalized.as_ref().map(|&(_, n)| n) } + /// Confirm that all changes made to commit sets are on disk. Allows for temporarily pinned + /// blocks to be released. + pub fn sync(&mut self) { + let mut pinned = std::mem::take(&mut self.pinned_canonincalized); + for hash in pinned.iter() { + self.unpin(hash) + } + pinned.clear(); + // Reuse the same memory buffer + self.pinned_canonincalized = pinned; + } + /// Select a top-level root and canonicalized it. Discards all sibling subtrees and the root. /// Add a set of changes of the canonicalized block to `CommitSet` /// Return the block number of the canonicalized block @@ -371,13 +383,9 @@ impl NonCanonicalOverlay { // No failures are possible beyond this point. - // Unpin previously canonicalized block - if let Some(prev_hash) = self.last_canon_pinned.take() { - self.unpin(&prev_hash); - } // Force pin canonicalized block so that it is no discarded immediately self.pin(hash); - self.last_canon_pinned = Some(hash.clone()); + self.pinned_canonincalized.push(hash.clone()); let mut discarded_journals = Vec::new(); let mut discarded_blocks = Vec::new(); @@ -720,16 +728,17 @@ mod tests { let mut commit = CommitSet::default(); overlay.canonicalize(&h1, &mut commit).unwrap(); db.commit(&commit); - assert!(contains(&overlay, 5)); + overlay.sync(); + assert!(!contains(&overlay, 5)); assert!(contains(&overlay, 7)); assert_eq!(overlay.levels.len(), 1); - assert_eq!(overlay.parents.len(), 2); + assert_eq!(overlay.parents.len(), 1); let mut commit = CommitSet::default(); overlay.canonicalize(&h2, &mut commit).unwrap(); - assert!(!contains(&overlay, 5)); db.commit(&commit); + overlay.sync(); assert_eq!(overlay.levels.len(), 0); - assert_eq!(overlay.parents.len(), 1); + assert_eq!(overlay.parents.len(), 0); assert!(db.data_eq(&make_db(&[1, 4, 6, 7, 8]))); } @@ -746,8 +755,7 @@ mod tests { let mut commit = CommitSet::default(); overlay.canonicalize(&h_1, &mut commit).unwrap(); db.commit(&commit); - // explicitly unpin last block - overlay.unpin(&h_1); + overlay.sync(); assert!(!contains(&overlay, 1)); } @@ -834,9 +842,8 @@ mod tests { // canonicalize 1. 2 and all its children should be discarded let mut commit = CommitSet::default(); overlay.canonicalize(&h_1, &mut commit).unwrap(); - // explicitly unpin last block - overlay.unpin(&h_1); db.commit(&commit); + overlay.sync(); assert_eq!(overlay.levels.len(), 2); assert_eq!(overlay.parents.len(), 6); assert!(!contains(&overlay, 1)); @@ -856,8 +863,8 @@ mod tests { // canonicalize 1_2. 1_1 and all its children should be discarded let mut commit = CommitSet::default(); overlay.canonicalize(&h_1_2, &mut commit).unwrap(); - overlay.unpin(&h_1_2); db.commit(&commit); + overlay.sync(); assert_eq!(overlay.levels.len(), 1); assert_eq!(overlay.parents.len(), 3); assert!(!contains(&overlay, 11)); @@ -873,8 +880,8 @@ mod tests { // canonicalize 1_2_2 let mut commit = CommitSet::default(); overlay.canonicalize(&h_1_2_2, &mut commit).unwrap(); - overlay.unpin(&h_1_2_2); db.commit(&commit); + overlay.sync(); assert_eq!(overlay.levels.len(), 0); assert_eq!(overlay.parents.len(), 0); assert!(db.data_eq(&make_db(&[1, 12, 122]))); @@ -958,6 +965,28 @@ mod tests { assert!(!contains(&overlay, 1)); } + #[test] + fn pins_canonicalized() { + let mut db = make_db(&[]); + + let (h_1, c_1) = (H256::random(), make_changeset(&[1], &[])); + let (h_2, c_2) = (H256::random(), make_changeset(&[2], &[])); + + let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); + db.commit(&overlay.insert(&h_1, 1, &H256::default(), c_1).unwrap()); + db.commit(&overlay.insert(&h_2, 2, &h_1, c_2).unwrap()); + + let mut commit = CommitSet::default(); + overlay.canonicalize(&h_1, &mut commit).unwrap(); + overlay.canonicalize(&h_2, &mut commit).unwrap(); + assert!(contains(&overlay, 1)); + assert!(contains(&overlay, 2)); + db.commit(&commit); + overlay.sync(); + assert!(!contains(&overlay, 1)); + assert!(!contains(&overlay, 2)); + } + #[test] fn pin_keeps_parent() { let mut db = make_db(&[]); @@ -1019,8 +1048,8 @@ mod tests { let mut commit = CommitSet::default(); overlay.canonicalize(&h21, &mut commit).unwrap(); // h11 should stay in the DB - overlay.unpin(&h21); db.commit(&commit); + overlay.sync(); assert!(!contains(&overlay, 21)); } From 2e21c35f879e904101d8305eef7a203d95dd6cd6 Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Wed, 14 Dec 2022 12:36:33 +0100 Subject: [PATCH 038/127] [ci] add job switcher (#12922) --- .gitlab-ci.yml | 9 ++++++++- scripts/ci/gitlab/pipeline/build.yml | 3 +++ scripts/ci/gitlab/pipeline/publish.yml | 1 + scripts/ci/gitlab/pipeline/test.yml | 2 ++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 25d61cf349..dcc0cbb7c9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -81,8 +81,14 @@ default: paths: - artifacts/ +.job-switcher: + before_script: + - if echo "$CI_DISABLED_JOBS" | grep -xF "$CI_JOB_NAME"; then echo "The job has been cancelled in CI settings"; exit 0; fi + .kubernetes-env: image: "${CI_IMAGE}" + before_script: + - !reference [.job-switcher, before_script] tags: - kubernetes-parity-build @@ -95,6 +101,7 @@ default: .pipeline-stopper-vars: script: + - !reference [.job-switcher, before_script] - echo "Collecting env variables for the cancel-pipeline job" - echo "FAILED_JOB_URL=${CI_JOB_URL}" > pipeline-stopper.env - echo "FAILED_JOB_NAME=${CI_JOB_NAME}" >> pipeline-stopper.env @@ -110,6 +117,7 @@ default: before_script: # TODO: remove unset invocation when we'll be free from 'ENV RUSTC_WRAPPER=sccache' & sccache itself in all images - unset RUSTC_WRAPPER + - !reference [.job-switcher, before_script] - !reference [.rust-info-script, script] - !reference [.rusty-cachier, before_script] - !reference [.pipeline-stopper-vars, script] @@ -300,7 +308,6 @@ rusty-cachier-notify: PR_NUM: "${PR_NUM}" trigger: project: "parity/infrastructure/ci_cd/pipeline-stopper" - branch: "as-improve" remove-cancel-pipeline-message: stage: .post diff --git a/scripts/ci/gitlab/pipeline/build.yml b/scripts/ci/gitlab/pipeline/build.yml index 2f8cff7b3f..ba529569d0 100644 --- a/scripts/ci/gitlab/pipeline/build.yml +++ b/scripts/ci/gitlab/pipeline/build.yml @@ -62,6 +62,7 @@ build-linux-substrate: - job: test-linux-stable artifacts: false before_script: + - !reference [.job-switcher, before_script] - mkdir -p ./artifacts/substrate/ - !reference [.rusty-cachier, before_script] # tldr: we need to checkout the branch HEAD explicitly because of our dynamic versioning approach while building the substrate binary @@ -94,6 +95,7 @@ build-linux-substrate: # this variable gets overriden by "rusty-cachier environment inject", use the value as default CARGO_TARGET_DIR: "$CI_PROJECT_DIR/target" before_script: + - !reference [.job-switcher, before_script] - mkdir -p ./artifacts/subkey - !reference [.rusty-cachier, before_script] script: @@ -118,6 +120,7 @@ build-subkey-macos: # duplicating before_script & script sections from .build-subkey hidden job # to overwrite rusty-cachier integration as it doesn't work on macos before_script: + - !reference [.job-switcher, before_script] - mkdir -p ./artifacts/subkey script: - cd ./bin/utils/subkey diff --git a/scripts/ci/gitlab/pipeline/publish.yml b/scripts/ci/gitlab/pipeline/publish.yml index 381a1bc420..6a0d6d6341 100644 --- a/scripts/ci/gitlab/pipeline/publish.yml +++ b/scripts/ci/gitlab/pipeline/publish.yml @@ -12,6 +12,7 @@ DOCKERFILE: $PRODUCT.Dockerfile IMAGE_NAME: docker.io/$IMAGE_PATH before_script: + - !reference [.job-switcher, before_script] - cd ./artifacts/$PRODUCT/ - VERSION="$(cat ./VERSION)" - echo "${PRODUCT} version = ${VERSION}" diff --git a/scripts/ci/gitlab/pipeline/test.yml b/scripts/ci/gitlab/pipeline/test.yml index ea1240af8b..a468a7b04c 100644 --- a/scripts/ci/gitlab/pipeline/test.yml +++ b/scripts/ci/gitlab/pipeline/test.yml @@ -65,6 +65,7 @@ cargo-check-benches: before_script: # perform rusty-cachier operations before any further modifications to the git repo to make cargo feel cheated not so much - !reference [.rust-info-script, script] + - !reference [.job-switcher, before_script] - !reference [.rusty-cachier, before_script] - !reference [.pipeline-stopper-vars, script] # merges in the master branch on PRs @@ -414,6 +415,7 @@ cargo-check-each-crate-macos: - .collect-artifacts - .pipeline-stopper-artifacts before_script: + - !reference [.job-switcher, before_script] - !reference [.rust-info-script, script] - !reference [.pipeline-stopper-vars, script] variables: From 2f6105bbc6a230210d022e48c568404acd501a81 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Wed, 14 Dec 2022 14:56:17 +0100 Subject: [PATCH 039/127] Use LOG_TARGET in consensus related crates (#12875) * Use shared LOG_TARGET in consensus related crates * Rename target from "afg" to "grandpa" --- client/consensus/aura/src/import_queue.rs | 7 +- client/consensus/aura/src/lib.rs | 8 +- client/consensus/babe/src/aux_schema.rs | 4 +- client/consensus/babe/src/lib.rs | 86 +++++++++++-------- client/consensus/babe/src/tests.rs | 10 +-- client/consensus/babe/src/verification.rs | 15 ++-- client/consensus/common/src/import_queue.rs | 37 +++++--- .../common/src/import_queue/basic_queue.rs | 25 +++--- .../manual-seal/src/consensus/babe.rs | 6 +- client/consensus/manual-seal/src/lib.rs | 2 + client/consensus/pow/src/lib.rs | 20 +++-- client/consensus/pow/src/worker.rs | 34 ++------ client/consensus/slots/src/lib.rs | 16 ++-- client/consensus/slots/src/slots.rs | 4 +- client/finality-grandpa/src/authorities.rs | 20 +++-- client/finality-grandpa/src/aux_schema.rs | 13 +-- .../src/communication/gossip.rs | 47 ++++++---- .../finality-grandpa/src/communication/mod.rs | 39 ++++++--- .../src/communication/periodic.rs | 8 +- client/finality-grandpa/src/environment.rs | 80 ++++++++++++----- client/finality-grandpa/src/finality_proof.rs | 10 +-- client/finality-grandpa/src/import.rs | 17 ++-- client/finality-grandpa/src/lib.rs | 25 ++++-- client/finality-grandpa/src/observer.rs | 6 +- client/finality-grandpa/src/until_imported.rs | 6 +- frame/aura/src/lib.rs | 4 +- frame/babe/src/equivocation.rs | 16 ++-- frame/babe/src/lib.rs | 2 + frame/grandpa/src/equivocation.rs | 16 ++-- frame/grandpa/src/lib.rs | 2 + frame/grandpa/src/migrations/v4.rs | 5 +- 31 files changed, 343 insertions(+), 247 deletions(-) diff --git a/client/consensus/aura/src/import_queue.rs b/client/consensus/aura/src/import_queue.rs index 07f982542c..d5cf40f333 100644 --- a/client/consensus/aura/src/import_queue.rs +++ b/client/consensus/aura/src/import_queue.rs @@ -20,6 +20,7 @@ use crate::{ aura_err, authorities, find_pre_digest, slot_author, AuthorityId, CompatibilityMode, Error, + LOG_TARGET, }; use codec::{Codec, Decode, Encode}; use log::{debug, info, trace}; @@ -88,7 +89,7 @@ where .map_err(Error::Client)? { info!( - target: "aura", + target: LOG_TARGET, "Slot author is equivocating at slot {} with headers {:?} and {:?}", slot, equivocation_proof.first_header.hash(), @@ -256,7 +257,7 @@ where block.body = Some(inner_body); } - trace!(target: "aura", "Checked {:?}; importing.", pre_header); + trace!(target: LOG_TARGET, "Checked {:?}; importing.", pre_header); telemetry!( self.telemetry; CONSENSUS_TRACE; @@ -272,7 +273,7 @@ where Ok((block, None)) }, CheckedHeader::Deferred(a, b) => { - debug!(target: "aura", "Checking {:?} failed; {:?}, {:?}.", hash, a, b); + debug!(target: LOG_TARGET, "Checking {:?} failed; {:?}, {:?}.", hash, a, b); telemetry!( self.telemetry; CONSENSUS_DEBUG; diff --git a/client/consensus/aura/src/lib.rs b/client/consensus/aura/src/lib.rs index 46b9124f90..a8ed80d7c0 100644 --- a/client/consensus/aura/src/lib.rs +++ b/client/consensus/aura/src/lib.rs @@ -72,6 +72,8 @@ pub use sp_consensus_aura::{ AuraApi, ConsensusLog, SlotDuration, AURA_ENGINE_ID, }; +const LOG_TARGET: &str = "aura"; + type AuthorityId

=

::Public; /// Run `AURA` in a compatibility mode. @@ -530,7 +532,7 @@ where } fn aura_err(error: Error) -> Error { - debug!(target: "aura", "{}", error); + debug!(target: LOG_TARGET, "{}", error); error } @@ -580,10 +582,10 @@ pub fn find_pre_digest(header: &B::Header) -> Resul let mut pre_digest: Option = None; for log in header.digest().logs() { - trace!(target: "aura", "Checking log {:?}", log); + trace!(target: LOG_TARGET, "Checking log {:?}", log); match (CompatibleDigestItem::::as_aura_pre_digest(log), pre_digest.is_some()) { (Some(_), true) => return Err(aura_err(Error::MultipleHeaders)), - (None, _) => trace!(target: "aura", "Ignoring digest not meant for us"), + (None, _) => trace!(target: LOG_TARGET, "Ignoring digest not meant for us"), (s, false) => pre_digest = s, } } diff --git a/client/consensus/babe/src/aux_schema.rs b/client/consensus/babe/src/aux_schema.rs index fef84bda86..2a09aa738f 100644 --- a/client/consensus/babe/src/aux_schema.rs +++ b/client/consensus/babe/src/aux_schema.rs @@ -21,7 +21,7 @@ use codec::{Decode, Encode}; use log::info; -use crate::{migration::EpochV0, Epoch}; +use crate::{migration::EpochV0, Epoch, LOG_TARGET}; use sc_client_api::backend::AuxStore; use sc_consensus_epochs::{ migration::{EpochChangesV0For, EpochChangesV1For}, @@ -82,7 +82,7 @@ pub fn load_epoch_changes( let epoch_changes = SharedEpochChanges::::new(maybe_epoch_changes.unwrap_or_else(|| { info!( - target: "babe", + target: LOG_TARGET, "👶 Creating empty BABE epoch changes on what appears to be first startup.", ); EpochChangesFor::::default() diff --git a/client/consensus/babe/src/lib.rs b/client/consensus/babe/src/lib.rs index 84b6893648..b50874ae34 100644 --- a/client/consensus/babe/src/lib.rs +++ b/client/consensus/babe/src/lib.rs @@ -149,6 +149,8 @@ pub mod aux_schema; #[cfg(test)] mod tests; +const LOG_TARGET: &str = "babe"; + /// BABE epoch information #[derive(Decode, Encode, PartialEq, Eq, Clone, Debug)] pub struct Epoch { @@ -323,7 +325,7 @@ impl From> for String { } fn babe_err(error: Error) -> Error { - debug!(target: "babe", "{}", error); + debug!(target: LOG_TARGET, "{}", error); error } @@ -345,7 +347,7 @@ where let block_id = if client.usage_info().chain.finalized_state.is_some() { BlockId::Hash(client.usage_info().chain.best_hash) } else { - debug!(target: "babe", "No finalized state is available. Reading config from genesis"); + debug!(target: LOG_TARGET, "No finalized state is available. Reading config from genesis"); BlockId::Hash(client.usage_info().chain.genesis_hash) }; @@ -486,7 +488,7 @@ where telemetry, }; - info!(target: "babe", "👶 Starting BABE Authorship worker"); + info!(target: LOG_TARGET, "👶 Starting BABE Authorship worker"); let slot_worker = sc_consensus_slots::start_slot_worker( babe_link.config.slot_duration(), @@ -523,12 +525,8 @@ fn aux_storage_cleanup + HeaderBackend, Block: B Ok(meta) => { hashes.insert(meta.parent); }, - Err(err) => warn!( - target: "babe", - "Failed to lookup metadata for block `{:?}`: {}", - first, - err, - ), + Err(err) => + warn!(target: LOG_TARGET, "Failed to lookup metadata for block `{:?}`: {}", first, err,), } // Cleans data for finalized block's ancestors @@ -716,7 +714,7 @@ where type AuxData = ViableEpochDescriptor, Epoch>; fn logging_target(&self) -> &'static str { - "babe" + LOG_TARGET } fn block_import(&mut self) -> &mut Self::BlockImport { @@ -749,7 +747,7 @@ where slot: Slot, epoch_descriptor: &ViableEpochDescriptor, Epoch>, ) -> Option { - debug!(target: "babe", "Attempting to claim slot {}", slot); + debug!(target: LOG_TARGET, "Attempting to claim slot {}", slot); let s = authorship::claim_slot( slot, self.epoch_changes @@ -760,7 +758,7 @@ where ); if s.is_some() { - debug!(target: "babe", "Claimed slot {}", slot); + debug!(target: LOG_TARGET, "Claimed slot {}", slot); } s @@ -777,7 +775,7 @@ where Ok(()) => true, Err(e) => if e.is_full() { - warn!(target: "babe", "Trying to notify a slot but the channel is full"); + warn!(target: LOG_TARGET, "Trying to notify a slot but the channel is full"); true } else { false @@ -904,10 +902,10 @@ pub fn find_pre_digest(header: &B::Header) -> Result = None; for log in header.digest().logs() { - trace!(target: "babe", "Checking log {:?}, looking for pre runtime digest", log); + trace!(target: LOG_TARGET, "Checking log {:?}, looking for pre runtime digest", log); match (log.as_babe_pre_digest(), pre_digest.is_some()) { (Some(_), true) => return Err(babe_err(Error::MultiplePreRuntimeDigests)), - (None, _) => trace!(target: "babe", "Ignoring digest not meant for us"), + (None, _) => trace!(target: LOG_TARGET, "Ignoring digest not meant for us"), (s, false) => pre_digest = s, } } @@ -920,13 +918,13 @@ fn find_next_epoch_digest( ) -> Result, Error> { let mut epoch_digest: Option<_> = None; for log in header.digest().logs() { - trace!(target: "babe", "Checking log {:?}, looking for epoch change digest.", log); + trace!(target: LOG_TARGET, "Checking log {:?}, looking for epoch change digest.", log); let log = log.try_to::(OpaqueDigestItemId::Consensus(&BABE_ENGINE_ID)); match (log, epoch_digest.is_some()) { (Some(ConsensusLog::NextEpochData(_)), true) => return Err(babe_err(Error::MultipleEpochChangeDigests)), (Some(ConsensusLog::NextEpochData(epoch)), false) => epoch_digest = Some(epoch), - _ => trace!(target: "babe", "Ignoring digest not meant for us"), + _ => trace!(target: LOG_TARGET, "Ignoring digest not meant for us"), } } @@ -939,13 +937,13 @@ fn find_next_config_digest( ) -> Result, Error> { let mut config_digest: Option<_> = None; for log in header.digest().logs() { - trace!(target: "babe", "Checking log {:?}, looking for epoch change digest.", log); + trace!(target: LOG_TARGET, "Checking log {:?}, looking for epoch change digest.", log); let log = log.try_to::(OpaqueDigestItemId::Consensus(&BABE_ENGINE_ID)); match (log, config_digest.is_some()) { (Some(ConsensusLog::NextConfigData(_)), true) => return Err(babe_err(Error::MultipleConfigChangeDigests)), (Some(ConsensusLog::NextConfigData(config)), false) => config_digest = Some(config), - _ => trace!(target: "babe", "Ignoring digest not meant for us"), + _ => trace!(target: LOG_TARGET, "Ignoring digest not meant for us"), } } @@ -1075,7 +1073,10 @@ where None => match generate_key_owner_proof(&best_id)? { Some(proof) => proof, None => { - debug!(target: "babe", "Equivocation offender is not part of the authority set."); + debug!( + target: LOG_TARGET, + "Equivocation offender is not part of the authority set." + ); return Ok(()) }, }, @@ -1091,7 +1092,7 @@ where ) .map_err(Error::RuntimeApi)?; - info!(target: "babe", "Submitted equivocation report for author {:?}", author); + info!(target: LOG_TARGET, "Submitted equivocation report for author {:?}", author); Ok(()) } @@ -1121,7 +1122,7 @@ where mut block: BlockImportParams, ) -> BlockVerificationResult { trace!( - target: "babe", + target: LOG_TARGET, "Verifying origin: {:?} header: {:?} justification(s): {:?} body: {:?}", block.origin, block.header, @@ -1140,7 +1141,11 @@ where return Ok((block, Default::default())) } - debug!(target: "babe", "We have {:?} logs in this header", block.header.digest().logs().len()); + debug!( + target: LOG_TARGET, + "We have {:?} logs in this header", + block.header.digest().logs().len() + ); let create_inherent_data_providers = self .create_inherent_data_providers @@ -1204,7 +1209,10 @@ where ) .await { - warn!(target: "babe", "Error checking/reporting BABE equivocation: {}", err); + warn!( + target: LOG_TARGET, + "Error checking/reporting BABE equivocation: {}", err + ); } if let Some(inner_body) = block.body { @@ -1233,7 +1241,7 @@ where block.body = Some(inner_body); } - trace!(target: "babe", "Checked {:?}; importing.", pre_header); + trace!(target: LOG_TARGET, "Checked {:?}; importing.", pre_header); telemetry!( self.telemetry; CONSENSUS_TRACE; @@ -1252,7 +1260,7 @@ where Ok((block, Default::default())) }, CheckedHeader::Deferred(a, b) => { - debug!(target: "babe", "Checking {:?} failed; {:?}, {:?}.", hash, a, b); + debug!(target: LOG_TARGET, "Checking {:?} failed; {:?}, {:?}.", hash, a, b); telemetry!( self.telemetry; CONSENSUS_DEBUG; @@ -1520,21 +1528,23 @@ where log::Level::Info }; - log!(target: "babe", - log_level, - "👶 New epoch {} launching at block {} (block slot {} >= start slot {}).", - viable_epoch.as_ref().epoch_index, - hash, - slot, - viable_epoch.as_ref().start_slot, + log!( + target: LOG_TARGET, + log_level, + "👶 New epoch {} launching at block {} (block slot {} >= start slot {}).", + viable_epoch.as_ref().epoch_index, + hash, + slot, + viable_epoch.as_ref().start_slot, ); let next_epoch = viable_epoch.increment((next_epoch_descriptor, epoch_config)); - log!(target: "babe", - log_level, - "👶 Next epoch starts at slot {}", - next_epoch.as_ref().start_slot, + log!( + target: LOG_TARGET, + log_level, + "👶 Next epoch starts at slot {}", + next_epoch.as_ref().start_slot, ); // prune the tree of epochs not part of the finalized chain or @@ -1565,7 +1575,7 @@ where }; if let Err(e) = prune_and_import() { - debug!(target: "babe", "Failed to launch next epoch: {}", e); + debug!(target: LOG_TARGET, "Failed to launch next epoch: {}", e); *epoch_changes = old_epoch_changes.expect("set `Some` above and not taken; qed"); return Err(e) diff --git a/client/consensus/babe/src/tests.rs b/client/consensus/babe/src/tests.rs index 7f51eb2c51..d7691235a5 100644 --- a/client/consensus/babe/src/tests.rs +++ b/client/consensus/babe/src/tests.rs @@ -323,7 +323,7 @@ impl TestNetFactory for BabeTestNet { use substrate_test_runtime_client::DefaultTestClientBuilderExt; let client = client.as_client(); - trace!(target: "babe", "Creating a verifier"); + trace!(target: LOG_TARGET, "Creating a verifier"); // ensure block import and verifier are linked correctly. let data = maybe_link @@ -352,12 +352,12 @@ impl TestNetFactory for BabeTestNet { } fn peer(&mut self, i: usize) -> &mut BabePeer { - trace!(target: "babe", "Retrieving a peer"); + trace!(target: LOG_TARGET, "Retrieving a peer"); &mut self.peers[i] } fn peers(&self) -> &Vec { - trace!(target: "babe", "Retrieving peers"); + trace!(target: LOG_TARGET, "Retrieving peers"); &self.peers } @@ -583,7 +583,7 @@ fn can_author_block() { // with secondary slots enabled it should never be empty match claim_slot(i.into(), &epoch, &keystore) { None => i += 1, - Some(s) => debug!(target: "babe", "Authored block {:?}", s.0), + Some(s) => debug!(target: LOG_TARGET, "Authored block {:?}", s.0), } // otherwise with only vrf-based primary slots we might need to try a couple @@ -593,7 +593,7 @@ fn can_author_block() { match claim_slot(i.into(), &epoch, &keystore) { None => i += 1, Some(s) => { - debug!(target: "babe", "Authored block {:?}", s.0); + debug!(target: LOG_TARGET, "Authored block {:?}", s.0); break }, } diff --git a/client/consensus/babe/src/verification.rs b/client/consensus/babe/src/verification.rs index 53ec3002e6..e77a70c8e4 100644 --- a/client/consensus/babe/src/verification.rs +++ b/client/consensus/babe/src/verification.rs @@ -17,9 +17,9 @@ // along with this program. If not, see . //! Verification for BABE headers. -use super::{ +use crate::{ authorship::{calculate_primary_threshold, check_primary_threshold, secondary_slot_author}, - babe_err, find_pre_digest, BlockT, Epoch, Error, + babe_err, find_pre_digest, BlockT, Epoch, Error, LOG_TARGET, }; use log::{debug, trace}; use sc_consensus_slots::CheckedHeader; @@ -67,7 +67,7 @@ pub(super) fn check_header( let authorities = &epoch.authorities; let pre_digest = pre_digest.map(Ok).unwrap_or_else(|| find_pre_digest::(&header))?; - trace!(target: "babe", "Checking header"); + trace!(target: LOG_TARGET, "Checking header"); let seal = header .digest_mut() .pop() @@ -93,7 +93,8 @@ pub(super) fn check_header( match &pre_digest { PreDigest::Primary(primary) => { - debug!(target: "babe", + debug!( + target: LOG_TARGET, "Verifying primary block #{} at slot: {}", header.number(), primary.slot, @@ -104,7 +105,8 @@ pub(super) fn check_header( PreDigest::SecondaryPlain(secondary) if epoch.config.allowed_slots.is_secondary_plain_slots_allowed() => { - debug!(target: "babe", + debug!( + target: LOG_TARGET, "Verifying secondary plain block #{} at slot: {}", header.number(), secondary.slot, @@ -115,7 +117,8 @@ pub(super) fn check_header( PreDigest::SecondaryVRF(secondary) if epoch.config.allowed_slots.is_secondary_vrf_slots_allowed() => { - debug!(target: "babe", + debug!( + target: LOG_TARGET, "Verifying secondary VRF block #{} at slot: {}", header.number(), secondary.slot, diff --git a/client/consensus/common/src/import_queue.rs b/client/consensus/common/src/import_queue.rs index d49b240ef3..02309cc6a3 100644 --- a/client/consensus/common/src/import_queue.rs +++ b/client/consensus/common/src/import_queue.rs @@ -45,6 +45,8 @@ use crate::{ pub use basic_queue::BasicQueue; use sp_consensus::{error::Error as ConsensusError, BlockOrigin, CacheKeyId}; +const LOG_TARGET: &str = "sync::import-queue"; + /// A commonly-used Import Queue type. /// /// This defines the transaction type of the `BasicQueue` to be the transaction type for a client. @@ -247,15 +249,15 @@ pub(crate) async fn import_single_block_metered< (Some(header), justifications) => (header, justifications), (None, _) => { if let Some(ref peer) = peer { - debug!(target: "sync", "Header {} was not provided by {} ", block.hash, peer); + debug!(target: LOG_TARGET, "Header {} was not provided by {} ", block.hash, peer); } else { - debug!(target: "sync", "Header {} was not provided ", block.hash); + debug!(target: LOG_TARGET, "Header {} was not provided ", block.hash); } return Err(BlockImportError::IncompleteHeader(peer)) }, }; - trace!(target: "sync", "Header {} has {:?} logs", block.hash, header.digest().logs().len()); + trace!(target: LOG_TARGET, "Header {} has {:?} logs", block.hash, header.digest().logs().len()); let number = *header.number(); let hash = block.hash; @@ -263,27 +265,31 @@ pub(crate) async fn import_single_block_metered< let import_handler = |import| match import { Ok(ImportResult::AlreadyInChain) => { - trace!(target: "sync", "Block already in chain {}: {:?}", number, hash); + trace!(target: LOG_TARGET, "Block already in chain {}: {:?}", number, hash); Ok(BlockImportStatus::ImportedKnown(number, peer)) }, Ok(ImportResult::Imported(aux)) => Ok(BlockImportStatus::ImportedUnknown(number, aux, peer)), Ok(ImportResult::MissingState) => { - debug!(target: "sync", "Parent state is missing for {}: {:?}, parent: {:?}", - number, hash, parent_hash); + debug!( + target: LOG_TARGET, + "Parent state is missing for {}: {:?}, parent: {:?}", number, hash, parent_hash + ); Err(BlockImportError::MissingState) }, Ok(ImportResult::UnknownParent) => { - debug!(target: "sync", "Block with unknown parent {}: {:?}, parent: {:?}", - number, hash, parent_hash); + debug!( + target: LOG_TARGET, + "Block with unknown parent {}: {:?}, parent: {:?}", number, hash, parent_hash + ); Err(BlockImportError::UnknownParent) }, Ok(ImportResult::KnownBad) => { - debug!(target: "sync", "Peer gave us a bad block {}: {:?}", number, hash); + debug!(target: LOG_TARGET, "Peer gave us a bad block {}: {:?}", number, hash); Err(BlockImportError::BadBlock(peer)) }, Err(e) => { - debug!(target: "sync", "Error importing block {}: {:?}: {}", number, hash, e); + debug!(target: LOG_TARGET, "Error importing block {}: {:?}: {}", number, hash, e); Err(BlockImportError::Other(e)) }, }; @@ -324,9 +330,16 @@ pub(crate) async fn import_single_block_metered< let (import_block, maybe_keys) = verifier.verify(import_block).await.map_err(|msg| { if let Some(ref peer) = peer { - trace!(target: "sync", "Verifying {}({}) from {} failed: {}", number, hash, peer, msg); + trace!( + target: LOG_TARGET, + "Verifying {}({}) from {} failed: {}", + number, + hash, + peer, + msg + ); } else { - trace!(target: "sync", "Verifying {}({}) failed: {}", number, hash, msg); + trace!(target: LOG_TARGET, "Verifying {}({}) failed: {}", number, hash, msg); } if let Some(metrics) = metrics.as_ref() { metrics.report_verification(false, started.elapsed()); diff --git a/client/consensus/common/src/import_queue/basic_queue.rs b/client/consensus/common/src/import_queue/basic_queue.rs index 20e8d262ca..b63bc192b2 100644 --- a/client/consensus/common/src/import_queue/basic_queue.rs +++ b/client/consensus/common/src/import_queue/basic_queue.rs @@ -35,7 +35,7 @@ use crate::{ buffered_link::{self, BufferedLinkReceiver, BufferedLinkSender}, import_single_block_metered, BlockImportError, BlockImportStatus, BoxBlockImport, BoxJustificationImport, ImportQueue, ImportQueueService, IncomingBlock, Link, - RuntimeOrigin, Verifier, + RuntimeOrigin, Verifier, LOG_TARGET, }, metrics::Metrics, }; @@ -129,14 +129,14 @@ impl ImportQueueService for BasicQueueHandle { return } - trace!(target: "sync", "Scheduling {} blocks for import", blocks.len()); + trace!(target: LOG_TARGET, "Scheduling {} blocks for import", blocks.len()); let res = self .block_import_sender .unbounded_send(worker_messages::ImportBlocks(origin, blocks)); if res.is_err() { log::error!( - target: "sync", + target: LOG_TARGET, "import_blocks: Background import task is no longer alive" ); } @@ -156,7 +156,7 @@ impl ImportQueueService for BasicQueueHandle { if res.is_err() { log::error!( - target: "sync", + target: LOG_TARGET, "import_justification: Background import task is no longer alive" ); } @@ -179,7 +179,10 @@ impl ImportQueue for BasicQueue /// Poll actions from network. fn poll_actions(&mut self, cx: &mut Context, link: &mut dyn Link) { if self.result_port.poll_actions(cx, link).is_err() { - log::error!(target: "sync", "poll_actions: Background import task is no longer alive"); + log::error!( + target: LOG_TARGET, + "poll_actions: Background import task is no longer alive" + ); } } @@ -231,7 +234,7 @@ async fn block_import_process( Some(blocks) => blocks, None => { log::debug!( - target: "block-import", + target: LOG_TARGET, "Stopping block import because the import channel was closed!", ); return @@ -305,7 +308,7 @@ impl BlockImportWorker { // down and we should end this future. if worker.result_sender.is_closed() { log::debug!( - target: "block-import", + target: LOG_TARGET, "Stopping block import because result channel was closed!", ); return @@ -318,7 +321,7 @@ impl BlockImportWorker { worker.import_justification(who, hash, number, justification).await, None => { log::debug!( - target: "block-import", + target: LOG_TARGET, "Stopping block import because justification channel was closed!", ); return @@ -353,7 +356,7 @@ impl BlockImportWorker { .await .map_err(|e| { debug!( - target: "sync", + target: LOG_TARGET, "Justification import failed for hash = {:?} with number = {:?} coming from node = {:?} with error: {}", hash, number, @@ -407,7 +410,7 @@ async fn import_many_blocks, Transaction: Send + 'stat _ => Default::default(), }; - trace!(target: "sync", "Starting import of {} blocks {}", count, blocks_range); + trace!(target: LOG_TARGET, "Starting import of {} blocks {}", count, blocks_range); let mut imported = 0; let mut results = vec![]; @@ -447,7 +450,7 @@ async fn import_many_blocks, Transaction: Send + 'stat if import_result.is_ok() { trace!( - target: "sync", + target: LOG_TARGET, "Block imported successfully {:?} ({})", block_number, block_hash, diff --git a/client/consensus/manual-seal/src/consensus/babe.rs b/client/consensus/manual-seal/src/consensus/babe.rs index 206f5163a1..d2bea3a3a3 100644 --- a/client/consensus/manual-seal/src/consensus/babe.rs +++ b/client/consensus/manual-seal/src/consensus/babe.rs @@ -20,7 +20,7 @@ //! that expect babe-specific digests. use super::ConsensusDataProvider; -use crate::Error; +use crate::{Error, LOG_TARGET}; use codec::Encode; use sc_client_api::{AuxStore, UsageProvider}; use sc_consensus_babe::{ @@ -179,7 +179,7 @@ where let epoch = epoch_changes .viable_epoch(&epoch_descriptor, |slot| Epoch::genesis(&self.config, slot)) .ok_or_else(|| { - log::info!(target: "babe", "create_digest: no viable_epoch :("); + log::info!(target: LOG_TARGET, "create_digest: no viable_epoch :("); sp_consensus::Error::InvalidAuthoritiesSet })?; @@ -290,7 +290,7 @@ where let has_authority = epoch.authorities.iter().any(|(id, _)| *id == *authority); if !has_authority { - log::info!(target: "manual-seal", "authority not found"); + log::info!(target: LOG_TARGET, "authority not found"); let timestamp = inherents .timestamp_inherent_data()? .ok_or_else(|| Error::StringError("No timestamp inherent data".into()))?; diff --git a/client/consensus/manual-seal/src/lib.rs b/client/consensus/manual-seal/src/lib.rs index 09ab139b91..700b94cf1d 100644 --- a/client/consensus/manual-seal/src/lib.rs +++ b/client/consensus/manual-seal/src/lib.rs @@ -49,6 +49,8 @@ pub use self::{ use sc_transaction_pool_api::TransactionPool; use sp_api::{ProvideRuntimeApi, TransactionFor}; +const LOG_TARGET: &str = "manual-seal"; + /// The `ConsensusEngineId` of Manual Seal. pub const MANUAL_SEAL_ENGINE_ID: ConsensusEngineId = [b'm', b'a', b'n', b'l']; diff --git a/client/consensus/pow/src/lib.rs b/client/consensus/pow/src/lib.rs index ac7ce3b411..ace00a3445 100644 --- a/client/consensus/pow/src/lib.rs +++ b/client/consensus/pow/src/lib.rs @@ -67,6 +67,8 @@ use sp_runtime::{ }; use std::{cmp::Ordering, collections::HashMap, marker::PhantomData, sync::Arc, time::Duration}; +const LOG_TARGET: &str = "pow"; + #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Header uses the wrong engine {0:?}")] @@ -531,7 +533,7 @@ where } if sync_oracle.is_major_syncing() { - debug!(target: "pow", "Skipping proposal due to sync."); + debug!(target: LOG_TARGET, "Skipping proposal due to sync."); worker.on_major_syncing(); continue } @@ -540,7 +542,7 @@ where Ok(x) => x, Err(err) => { warn!( - target: "pow", + target: LOG_TARGET, "Unable to pull new block for authoring. \ Select best chain error: {}", err @@ -561,7 +563,7 @@ where Ok(x) => x, Err(err) => { warn!( - target: "pow", + target: LOG_TARGET, "Unable to propose new block for authoring. \ Fetch difficulty failed: {}", err, @@ -577,7 +579,7 @@ where Ok(x) => x, Err(err) => { warn!( - target: "pow", + target: LOG_TARGET, "Unable to propose new block for authoring. \ Creating inherent data providers failed: {}", err, @@ -590,7 +592,7 @@ where Ok(r) => r, Err(e) => { warn!( - target: "pow", + target: LOG_TARGET, "Unable to propose new block for authoring. \ Creating inherent data failed: {}", e, @@ -610,7 +612,7 @@ where Ok(x) => x, Err(err) => { warn!( - target: "pow", + target: LOG_TARGET, "Unable to propose new block for authoring. \ Creating proposer failed: {:?}", err, @@ -624,7 +626,7 @@ where Ok(x) => x, Err(err) => { warn!( - target: "pow", + target: LOG_TARGET, "Unable to propose new block for authoring. \ Creating proposal failed: {}", err, @@ -654,14 +656,14 @@ where fn find_pre_digest(header: &B::Header) -> Result>, Error> { let mut pre_digest: Option<_> = None; for log in header.digest().logs() { - trace!(target: "pow", "Checking log {:?}, looking for pre runtime digest", log); + trace!(target: LOG_TARGET, "Checking log {:?}, looking for pre runtime digest", log); match (log, pre_digest.is_some()) { (DigestItem::PreRuntime(POW_ENGINE_ID, _), true) => return Err(Error::MultiplePreRuntimeDigests), (DigestItem::PreRuntime(POW_ENGINE_ID, v), false) => { pre_digest = Some(v.clone()); }, - (_, _) => trace!(target: "pow", "Ignoring digest not meant for us"), + (_, _) => trace!(target: LOG_TARGET, "Ignoring digest not meant for us"), } } diff --git a/client/consensus/pow/src/worker.rs b/client/consensus/pow/src/worker.rs index a00da6e702..b53227bb3c 100644 --- a/client/consensus/pow/src/worker.rs +++ b/client/consensus/pow/src/worker.rs @@ -41,7 +41,7 @@ use std::{ time::Duration, }; -use crate::{PowAlgorithm, PowIntermediate, Seal, INTERMEDIATE_KEY, POW_ENGINE_ID}; +use crate::{PowAlgorithm, PowIntermediate, Seal, INTERMEDIATE_KEY, LOG_TARGET, POW_ENGINE_ID}; /// Mining metadata. This is the information needed to start an actual mining loop. #[derive(Clone, Eq, PartialEq)] @@ -159,26 +159,16 @@ where ) { Ok(true) => (), Ok(false) => { - warn!( - target: "pow", - "Unable to import mined block: seal is invalid", - ); + warn!(target: LOG_TARGET, "Unable to import mined block: seal is invalid",); return false }, Err(err) => { - warn!( - target: "pow", - "Unable to import mined block: {}", - err, - ); + warn!(target: LOG_TARGET, "Unable to import mined block: {}", err,); return false }, } } else { - warn!( - target: "pow", - "Unable to import mined block: metadata does not exist", - ); + warn!(target: LOG_TARGET, "Unable to import mined block: metadata does not exist",); return false } @@ -192,10 +182,7 @@ where } { build } else { - warn!( - target: "pow", - "Unable to import mined block: build does not exist", - ); + warn!(target: LOG_TARGET, "Unable to import mined block: build does not exist",); return false }; @@ -225,18 +212,13 @@ where ); info!( - target: "pow", - "✅ Successfully mined block on top of: {}", - build.metadata.best_hash + target: LOG_TARGET, + "✅ Successfully mined block on top of: {}", build.metadata.best_hash ); true }, Err(err) => { - warn!( - target: "pow", - "Unable to import mined block: {}", - err, - ); + warn!(target: LOG_TARGET, "Unable to import mined block: {}", err,); false }, } diff --git a/client/consensus/slots/src/lib.rs b/client/consensus/slots/src/lib.rs index bc68797dc7..6126647e61 100644 --- a/client/consensus/slots/src/lib.rs +++ b/client/consensus/slots/src/lib.rs @@ -48,6 +48,8 @@ use std::{ time::{Duration, Instant}, }; +const LOG_TARGET: &str = "slots"; + /// The changes that need to applied to the storage to create the state for a block. /// /// See [`sp_state_machine::StorageChanges`] for more information. @@ -198,9 +200,9 @@ pub trait SimpleSlotWorker { > { let slot = slot_info.slot; let telemetry = self.telemetry(); - let logging_target = self.logging_target(); + let log_target = self.logging_target(); - let inherent_data = Self::create_inherent_data(&slot_info, &logging_target).await?; + let inherent_data = Self::create_inherent_data(&slot_info, &log_target).await?; let proposing_remaining_duration = self.proposing_remaining_duration(&slot_info); let logs = self.pre_digest_data(slot, claim); @@ -220,19 +222,19 @@ pub trait SimpleSlotWorker { let proposal = match futures::future::select(proposing, proposing_remaining).await { Either::Left((Ok(p), _)) => p, Either::Left((Err(err), _)) => { - warn!(target: logging_target, "Proposing failed: {}", err); + warn!(target: log_target, "Proposing failed: {}", err); return None }, Either::Right(_) => { info!( - target: logging_target, + target: log_target, "⌛️ 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")] info!( - target: logging_target, + target: log_target, "👉 Recompile your node in `--release` mode to mitigate this problem.", ); telemetry!( @@ -525,13 +527,13 @@ pub async fn start_slot_worker( let slot_info = match slots.next_slot().await { Ok(r) => r, Err(e) => { - warn!(target: "slots", "Error while polling for next slot: {}", e); + warn!(target: LOG_TARGET, "Error while polling for next slot: {}", e); return }, }; if sync_oracle.is_major_syncing() { - debug!(target: "slots", "Skipping proposal slot due to sync."); + debug!(target: LOG_TARGET, "Skipping proposal slot due to sync."); continue } diff --git a/client/consensus/slots/src/slots.rs b/client/consensus/slots/src/slots.rs index f8f366d89c..9bb5650b31 100644 --- a/client/consensus/slots/src/slots.rs +++ b/client/consensus/slots/src/slots.rs @@ -20,7 +20,7 @@ //! //! This is used instead of `futures_timer::Interval` because it was unreliable. -use super::{InherentDataProviderExt, Slot}; +use super::{InherentDataProviderExt, Slot, LOG_TARGET}; use sp_consensus::{Error, SelectChain}; use sp_inherents::{CreateInherentDataProviders, InherentDataProvider}; use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; @@ -146,7 +146,7 @@ where Ok(x) => x, Err(e) => { log::warn!( - target: "slots", + target: LOG_TARGET, "Unable to author block in slot. No best block header: {}", e, ); diff --git a/client/finality-grandpa/src/authorities.rs b/client/finality-grandpa/src/authorities.rs index 0803e6b3c2..a61c66979b 100644 --- a/client/finality-grandpa/src/authorities.rs +++ b/client/finality-grandpa/src/authorities.rs @@ -29,7 +29,7 @@ use sc_consensus::shared_data::{SharedData, SharedDataLocked}; use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_INFO}; use sp_finality_grandpa::{AuthorityId, AuthorityList}; -use crate::SetId; +use crate::{SetId, LOG_TARGET}; /// Error type returned on operations on the `AuthoritySet`. #[derive(Debug, thiserror::Error)] @@ -314,7 +314,7 @@ where let number = pending.canon_height.clone(); debug!( - target: "afg", + target: LOG_TARGET, "Inserting potential standard set change signaled at block {:?} (delayed by {:?} blocks).", (&number, &hash), pending.delay, @@ -323,7 +323,7 @@ where self.pending_standard_changes.import(hash, number, pending, is_descendent_of)?; debug!( - target: "afg", + target: LOG_TARGET, "There are now {} alternatives for the next pending standard change (roots), and a \ total of {} pending standard changes (across all forks).", self.pending_standard_changes.roots().count(), @@ -362,7 +362,7 @@ where .unwrap_or_else(|i| i); debug!( - target: "afg", + target: LOG_TARGET, "Inserting potential forced set change at block {:?} (delayed by {:?} blocks).", (&pending.canon_height, &pending.canon_hash), pending.delay, @@ -370,7 +370,11 @@ where self.pending_forced_changes.insert(idx, pending); - debug!(target: "afg", "There are now {} pending forced changes.", self.pending_forced_changes.len()); + debug!( + target: LOG_TARGET, + "There are now {} pending forced changes.", + self.pending_forced_changes.len() + ); Ok(()) } @@ -475,7 +479,7 @@ where if standard_change.effective_number() <= median_last_finalized && is_descendent_of(&standard_change.canon_hash, &change.canon_hash)? { - log::info!(target: "afg", + log::info!(target: LOG_TARGET, "Not applying authority set change forced at block #{:?}, due to pending standard change at block #{:?}", change.canon_height, standard_change.effective_number(), @@ -488,7 +492,7 @@ where } // apply this change: make the set canonical - afg_log!( + grandpa_log!( initial_sync, "👴 Applying authority set change forced at block #{:?}", change.canon_height, @@ -570,7 +574,7 @@ where } if let Some(change) = change { - afg_log!( + grandpa_log!( initial_sync, "👴 Applying authority set change scheduled at block #{:?}", change.canon_height, diff --git a/client/finality-grandpa/src/aux_schema.rs b/client/finality-grandpa/src/aux_schema.rs index 235453ea35..a7357a7fa5 100644 --- a/client/finality-grandpa/src/aux_schema.rs +++ b/client/finality-grandpa/src/aux_schema.rs @@ -38,7 +38,7 @@ use crate::{ CompletedRound, CompletedRounds, CurrentRounds, HasVoted, SharedVoterSetState, VoterSetState, }, - GrandpaJustification, NewAuthoritySet, + GrandpaJustification, NewAuthoritySet, LOG_TARGET, }; const VERSION_KEY: &[u8] = b"grandpa_schema_version"; @@ -100,8 +100,8 @@ where // previously we only supported at most one pending change per fork &|_, _| Ok(false), ) { - warn!(target: "afg", "Error migrating pending authority set change: {}", err); - warn!(target: "afg", "Node is in a potentially inconsistent state."); + warn!(target: LOG_TARGET, "Error migrating pending authority set change: {}", err); + warn!(target: LOG_TARGET, "Node is in a potentially inconsistent state."); } } @@ -384,8 +384,11 @@ where } // genesis. - info!(target: "afg", "👴 Loading GRANDPA authority set \ - from genesis on what appears to be first startup."); + info!( + target: LOG_TARGET, + "👴 Loading GRANDPA authority set \ + from genesis on what appears to be first startup." + ); let genesis_authorities = genesis_authorities()?; let genesis_set = AuthoritySet::genesis(genesis_authorities) diff --git a/client/finality-grandpa/src/communication/gossip.rs b/client/finality-grandpa/src/communication/gossip.rs index 218b4b668c..408cbda745 100644 --- a/client/finality-grandpa/src/communication/gossip.rs +++ b/client/finality-grandpa/src/communication/gossip.rs @@ -99,7 +99,7 @@ use sp_finality_grandpa::AuthorityId; use sp_runtime::traits::{Block as BlockT, NumberFor, Zero}; use super::{benefit, cost, Round, SetId, NEIGHBOR_REBROADCAST_PERIOD}; -use crate::{environment, CatchUp, CompactCommit, SignedMessage}; +use crate::{environment, CatchUp, CompactCommit, SignedMessage, LOG_TARGET}; use std::{ collections::{HashSet, VecDeque}, @@ -578,8 +578,13 @@ impl Peers { last_update: Some(now), }; - trace!(target: "afg", "Peer {} updated view. Now at {:?}, {:?}", - who, peer.view.round, peer.view.set_id); + trace!( + target: LOG_TARGET, + "Peer {} updated view. Now at {:?}, {:?}", + who, + peer.view.round, + peer.view.set_id + ); Ok(Some(&peer.view)) } @@ -801,8 +806,12 @@ impl Inner { let set_id = local_view.set_id; - debug!(target: "afg", "Voter {} noting beginning of round {:?} to network.", - self.config.name(), (round, set_id)); + debug!( + target: LOG_TARGET, + "Voter {} noting beginning of round {:?} to network.", + self.config.name(), + (round, set_id) + ); local_view.update_round(round); @@ -824,7 +833,7 @@ impl Inner { authorities.iter().collect::>(); if diff_authorities { - debug!(target: "afg", + debug!(target: LOG_TARGET, "Gossip validator noted set {:?} twice with different authorities. \ Was the authority set hard forked?", set_id, @@ -912,7 +921,7 @@ impl Inner { // ensure authority is part of the set. if !self.authorities.contains(&full.message.id) { - debug!(target: "afg", "Message from unknown voter: {}", full.message.id); + debug!(target: LOG_TARGET, "Message from unknown voter: {}", full.message.id); telemetry!( self.config.telemetry; CONSENSUS_DEBUG; @@ -929,7 +938,7 @@ impl Inner { full.round.0, full.set_id.0, ) { - debug!(target: "afg", "Bad message signature {}", full.message.id); + debug!(target: LOG_TARGET, "Bad message signature {}", full.message.id); telemetry!( self.config.telemetry; CONSENSUS_DEBUG; @@ -964,7 +973,7 @@ impl Inner { if full.message.precommits.len() != full.message.auth_data.len() || full.message.precommits.is_empty() { - debug!(target: "afg", "Malformed compact commit"); + debug!(target: LOG_TARGET, "Malformed compact commit"); telemetry!( self.config.telemetry; CONSENSUS_DEBUG; @@ -1023,9 +1032,9 @@ impl Inner { PendingCatchUp::Processing { .. } => { self.pending_catch_up = PendingCatchUp::None; }, - state => debug!(target: "afg", - "Noted processed catch up message when state was: {:?}", - state, + state => debug!( + target: LOG_TARGET, + "Noted processed catch up message when state was: {:?}", state, ), } } @@ -1067,7 +1076,9 @@ impl Inner { return (None, Action::Discard(Misbehavior::OutOfScopeMessage.cost())) } - trace!(target: "afg", "Replying to catch-up request for round {} from {} with round {}", + trace!( + target: LOG_TARGET, + "Replying to catch-up request for round {} from {} with round {}", request.round.0, who, last_completed_round.number, @@ -1141,9 +1152,9 @@ impl Inner { let (catch_up_allowed, catch_up_report) = self.note_catch_up_request(who, &request); if catch_up_allowed { - debug!(target: "afg", "Sending catch-up request for round {} to {}", - round, - who, + debug!( + target: LOG_TARGET, + "Sending catch-up request for round {} to {}", round, who, ); catch_up = Some(GossipMessage::::CatchUpRequest(request)); @@ -1347,7 +1358,7 @@ impl GossipValidator { let metrics = match prometheus_registry.map(Metrics::register) { Some(Ok(metrics)) => Some(metrics), Some(Err(e)) => { - debug!(target: "afg", "Failed to register metrics: {:?}", e); + debug!(target: LOG_TARGET, "Failed to register metrics: {:?}", e); None }, None => None, @@ -1466,7 +1477,7 @@ impl GossipValidator { }, Err(e) => { message_name = None; - debug!(target: "afg", "Error decoding message: {}", e); + debug!(target: LOG_TARGET, "Error decoding message: {}", e); telemetry!( self.telemetry; CONSENSUS_DEBUG; diff --git a/client/finality-grandpa/src/communication/mod.rs b/client/finality-grandpa/src/communication/mod.rs index 75a7697812..e7e3c12989 100644 --- a/client/finality-grandpa/src/communication/mod.rs +++ b/client/finality-grandpa/src/communication/mod.rs @@ -54,7 +54,7 @@ use sp_runtime::traits::{Block as BlockT, Hash as HashT, Header as HeaderT, Numb use crate::{ environment::HasVoted, CatchUp, Commit, CommunicationIn, CommunicationOutH, CompactCommit, - Error, Message, SignedMessage, + Error, Message, SignedMessage, LOG_TARGET, }; use gossip::{ FullCatchUpMessage, FullCommitMessage, GossipMessage, GossipValidator, PeerReport, VoteMessage, @@ -274,7 +274,8 @@ impl> NetworkBridge { gossip_engine.lock().register_gossip_message(topic, message.encode()); } - trace!(target: "afg", + trace!( + target: LOG_TARGET, "Registered {} messages for topic {:?} (round: {}, set_id: {})", round.votes.len(), topic, @@ -340,13 +341,19 @@ impl> NetworkBridge { match decoded { Err(ref e) => { - debug!(target: "afg", "Skipping malformed message {:?}: {}", notification, e); + debug!( + target: LOG_TARGET, + "Skipping malformed message {:?}: {}", notification, e + ); future::ready(None) }, Ok(GossipMessage::Vote(msg)) => { // check signature. if !voters.contains(&msg.message.id) { - debug!(target: "afg", "Skipping message from unknown voter {}", msg.message.id); + debug!( + target: LOG_TARGET, + "Skipping message from unknown voter {}", msg.message.id + ); return future::ready(None) } @@ -388,7 +395,7 @@ impl> NetworkBridge { future::ready(Some(msg.message)) }, _ => { - debug!(target: "afg", "Skipping unknown message type"); + debug!(target: LOG_TARGET, "Skipping unknown message type"); future::ready(None) }, } @@ -631,7 +638,12 @@ fn incoming_global( // this could be optimized by decoding piecewise. let decoded = GossipMessage::::decode(&mut ¬ification.message[..]); if let Err(ref e) = decoded { - trace!(target: "afg", "Skipping malformed commit message {:?}: {}", notification, e); + trace!( + target: LOG_TARGET, + "Skipping malformed commit message {:?}: {}", + notification, + e + ); } future::ready(decoded.map(move |d| (notification, d)).ok()) }) @@ -642,7 +654,7 @@ fn incoming_global( GossipMessage::CatchUp(msg) => process_catch_up(msg, notification, &gossip_engine, &gossip_validator, &voters), _ => { - debug!(target: "afg", "Skipping unknown message type"); + debug!(target: LOG_TARGET, "Skipping unknown message type"); None }, }) @@ -748,7 +760,7 @@ impl Sink> for OutgoingMessages { }); debug!( - target: "afg", + target: LOG_TARGET, "Announcing block {} to peers which we voted on in round {} in set {}", target_hash, self.round, @@ -813,7 +825,7 @@ fn check_compact_commit( return Err(cost::MALFORMED_COMMIT) } } else { - debug!(target: "afg", "Skipping commit containing unknown voter {}", id); + debug!(target: LOG_TARGET, "Skipping commit containing unknown voter {}", id); return Err(cost::MALFORMED_COMMIT) } } @@ -838,7 +850,7 @@ fn check_compact_commit( set_id.0, &mut buf, ) { - debug!(target: "afg", "Bad commit message signature {}", id); + debug!(target: LOG_TARGET, "Bad commit message signature {}", id); telemetry!( telemetry; CONSENSUS_DEBUG; @@ -886,7 +898,10 @@ fn check_catch_up( return Err(cost::MALFORMED_CATCH_UP) } } else { - debug!(target: "afg", "Skipping catch up message containing unknown voter {}", id); + debug!( + target: LOG_TARGET, + "Skipping catch up message containing unknown voter {}", id + ); return Err(cost::MALFORMED_CATCH_UP) } } @@ -922,7 +937,7 @@ fn check_catch_up( if !sp_finality_grandpa::check_message_signature_with_buffer( &msg, id, sig, round, set_id, buf, ) { - debug!(target: "afg", "Bad catch up message signature {}", id); + debug!(target: LOG_TARGET, "Bad catch up message signature {}", id); telemetry!( telemetry; CONSENSUS_DEBUG; diff --git a/client/finality-grandpa/src/communication/periodic.rs b/client/finality-grandpa/src/communication/periodic.rs index c001796b5c..7e50abb96e 100644 --- a/client/finality-grandpa/src/communication/periodic.rs +++ b/client/finality-grandpa/src/communication/periodic.rs @@ -21,17 +21,19 @@ use futures::{future::FutureExt as _, prelude::*, ready, stream::Stream}; use futures_timer::Delay; use log::debug; -use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; use std::{ pin::Pin, task::{Context, Poll}, time::Duration, }; -use super::gossip::{GossipMessage, NeighborPacket}; use sc_network::PeerId; +use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; use sp_runtime::traits::{Block as BlockT, NumberFor}; +use super::gossip::{GossipMessage, NeighborPacket}; +use crate::LOG_TARGET; + /// A sender used to send neighbor packets to a background job. #[derive(Clone)] pub(super) struct NeighborPacketSender( @@ -46,7 +48,7 @@ impl NeighborPacketSender { neighbor_packet: NeighborPacket>, ) { if let Err(err) = self.0.unbounded_send((who, neighbor_packet)) { - debug!(target: "afg", "Failed to send neighbor packet: {:?}", err); + debug!(target: LOG_TARGET, "Failed to send neighbor packet: {:?}", err); } } } diff --git a/client/finality-grandpa/src/environment.rs b/client/finality-grandpa/src/environment.rs index f235c3a86c..110d0eb2c9 100644 --- a/client/finality-grandpa/src/environment.rs +++ b/client/finality-grandpa/src/environment.rs @@ -60,7 +60,7 @@ use crate::{ until_imported::UntilVoteTargetImported, voting_rule::VotingRule as VotingRuleT, ClientForGrandpa, CommandOrError, Commit, Config, Error, NewAuthoritySet, Precommit, Prevote, - PrimaryPropose, SignedMessage, VoterCommand, + PrimaryPropose, SignedMessage, VoterCommand, LOG_TARGET, }; type HistoricalVotes = finality_grandpa::HistoricalVotes< @@ -551,7 +551,10 @@ where { Some(proof) => proof, None => { - debug!(target: "afg", "Equivocation offender is not part of the authority set."); + debug!( + target: LOG_TARGET, + "Equivocation offender is not part of the authority set." + ); return Ok(()) }, }; @@ -609,8 +612,13 @@ where let tree_route = match tree_route_res { Ok(tree_route) => tree_route, Err(e) => { - debug!(target: "afg", "Encountered error computing ancestry between block {:?} and base {:?}: {}", - block, base, e); + debug!( + target: LOG_TARGET, + "Encountered error computing ancestry between block {:?} and base {:?}: {}", + block, + base, + e + ); return Err(GrandpaError::NotDescendent) }, @@ -955,7 +963,8 @@ where historical_votes: &HistoricalVotes, ) -> Result<(), Self::Error> { debug!( - target: "afg", "Voter {} completed round {} in set {}. Estimate = {:?}, Finalized in round = {:?}", + target: LOG_TARGET, + "Voter {} completed round {} in set {}. Estimate = {:?}, Finalized in round = {:?}", self.config.name(), round, self.set_id, @@ -1016,7 +1025,8 @@ where historical_votes: &HistoricalVotes, ) -> Result<(), Self::Error> { debug!( - target: "afg", "Voter {} concluded round {} in set {}. Estimate = {:?}, Finalized in round = {:?}", + target: LOG_TARGET, + "Voter {} concluded round {} in set {}. Estimate = {:?}, Finalized in round = {:?}", self.config.name(), round, self.set_id, @@ -1102,9 +1112,12 @@ where Self::Signature, >, ) { - warn!(target: "afg", "Detected prevote equivocation in the finality worker: {:?}", equivocation); + warn!( + target: LOG_TARGET, + "Detected prevote equivocation in the finality worker: {:?}", equivocation + ); if let Err(err) = self.report_equivocation(equivocation.into()) { - warn!(target: "afg", "Error reporting prevote equivocation: {}", err); + warn!(target: LOG_TARGET, "Error reporting prevote equivocation: {}", err); } } @@ -1117,9 +1130,12 @@ where Self::Signature, >, ) { - warn!(target: "afg", "Detected precommit equivocation in the finality worker: {:?}", equivocation); + warn!( + target: LOG_TARGET, + "Detected precommit equivocation in the finality worker: {:?}", equivocation + ); if let Err(err) = self.report_equivocation(equivocation.into()) { - warn!(target: "afg", "Error reporting precommit equivocation: {}", err); + warn!(target: LOG_TARGET, "Error reporting precommit equivocation: {}", err); } } } @@ -1158,7 +1174,8 @@ where let base_header = match client.header(BlockId::Hash(block))? { Some(h) => h, None => { - debug!(target: "afg", + debug!( + target: LOG_TARGET, "Encountered error finding best chain containing {:?}: couldn't find base block", block, ); @@ -1172,7 +1189,10 @@ where // proceed onwards. most of the time there will be no pending transition. the limit, if any, is // guaranteed to be higher than or equal to the given base number. let limit = authority_set.current_limit(*base_header.number()); - debug!(target: "afg", "Finding best chain containing block {:?} with number limit {:?}", block, limit); + debug!( + target: LOG_TARGET, + "Finding best chain containing block {:?} with number limit {:?}", block, limit + ); let result = match select_chain.finality_target(block, None).await { Ok(best_hash) => { @@ -1234,7 +1254,10 @@ where .or_else(|| Some((target_header.hash(), *target_header.number()))) }, Err(e) => { - warn!(target: "afg", "Encountered error finding best chain containing {:?}: {}", block, e); + warn!( + target: LOG_TARGET, + "Encountered error finding best chain containing {:?}: {}", block, e + ); None }, }; @@ -1273,7 +1296,7 @@ where // This can happen after a forced change (triggered manually from the runtime when // finality is stalled), since the voter will be restarted at the median last finalized // block, which can be lower than the local best finalized block. - warn!(target: "afg", "Re-finalized block #{:?} ({:?}) in the canonical chain, current best finalized is #{:?}", + warn!(target: LOG_TARGET, "Re-finalized block #{:?} ({:?}) in the canonical chain, current best finalized is #{:?}", hash, number, status.finalized_number, @@ -1303,7 +1326,10 @@ where ) { if let Some(sender) = justification_sender { if let Err(err) = sender.notify(justification) { - warn!(target: "afg", "Error creating justification for subscriber: {}", err); + warn!( + target: LOG_TARGET, + "Error creating justification for subscriber: {}", err + ); } } } @@ -1354,11 +1380,16 @@ where client .apply_finality(import_op, hash, persisted_justification, true) .map_err(|e| { - warn!(target: "afg", "Error applying finality to block {:?}: {}", (hash, number), e); + warn!( + target: LOG_TARGET, + "Error applying finality to block {:?}: {}", + (hash, number), + e + ); e })?; - debug!(target: "afg", "Finalizing blocks up to ({:?}, {})", number, hash); + debug!(target: LOG_TARGET, "Finalizing blocks up to ({:?}, {})", number, hash); telemetry!( telemetry; @@ -1376,13 +1407,17 @@ where let (new_id, set_ref) = authority_set.current(); if set_ref.len() > 16 { - afg_log!( + grandpa_log!( initial_sync, "👴 Applying GRANDPA set change to new set with {} authorities", set_ref.len(), ); } else { - afg_log!(initial_sync, "👴 Applying GRANDPA set change to new set {:?}", set_ref); + grandpa_log!( + initial_sync, + "👴 Applying GRANDPA set change to new set {:?}", + set_ref + ); } telemetry!( @@ -1411,8 +1446,11 @@ where ); if let Err(e) = write_result { - warn!(target: "afg", "Failed to write updated authority set to disk. Bailing."); - warn!(target: "afg", "Node is in a potentially inconsistent state."); + warn!( + target: LOG_TARGET, + "Failed to write updated authority set to disk. Bailing." + ); + warn!(target: LOG_TARGET, "Node is in a potentially inconsistent state."); return Err(e.into()) } diff --git a/client/finality-grandpa/src/finality_proof.rs b/client/finality-grandpa/src/finality_proof.rs index 453b41bc63..43ed0ed319 100644 --- a/client/finality-grandpa/src/finality_proof.rs +++ b/client/finality-grandpa/src/finality_proof.rs @@ -52,7 +52,7 @@ use crate::{ authorities::{AuthoritySetChangeId, AuthoritySetChanges}, best_justification, justification::GrandpaJustification, - SharedAuthoritySet, + SharedAuthoritySet, LOG_TARGET, }; const MAX_UNKNOWN_HEADERS: usize = 100_000; @@ -163,7 +163,7 @@ where "Requested finality proof for descendant of #{} while we only have finalized #{}.", block, info.finalized_number, ); - trace!(target: "afg", "{}", &err); + trace!(target: LOG_TARGET, "{}", &err); return Err(FinalityProofError::BlockNotYetFinalized) } @@ -175,7 +175,7 @@ where justification } else { trace!( - target: "afg", + target: LOG_TARGET, "No justification found for the latest finalized block. \ Returning empty proof.", ); @@ -194,7 +194,7 @@ where grandpa_justification } else { trace!( - target: "afg", + target: LOG_TARGET, "No justification found when making finality proof for {}. \ Returning empty proof.", block, @@ -205,7 +205,7 @@ where }, AuthoritySetChangeId::Unknown => { warn!( - target: "afg", + target: LOG_TARGET, "AuthoritySetChanges does not cover the requested block #{} due to missing data. \ You need to resync to populate AuthoritySetChanges properly.", block, diff --git a/client/finality-grandpa/src/import.rs b/client/finality-grandpa/src/import.rs index 3715287eea..e061c105ee 100644 --- a/client/finality-grandpa/src/import.rs +++ b/client/finality-grandpa/src/import.rs @@ -45,6 +45,7 @@ use crate::{ justification::GrandpaJustification, notification::GrandpaJustificationSender, AuthoritySetChanges, ClientForGrandpa, CommandOrError, Error, NewAuthoritySet, VoterCommand, + LOG_TARGET, }; /// A block-import handler for GRANDPA. @@ -589,18 +590,16 @@ where Ok(ImportResult::Imported(aux)) => aux, Ok(r) => { debug!( - target: "afg", - "Restoring old authority set after block import result: {:?}", - r, + target: LOG_TARGET, + "Restoring old authority set after block import result: {:?}", r, ); pending_changes.revert(); return Ok(r) }, Err(e) => { debug!( - target: "afg", - "Restoring old authority set after block import error: {}", - e, + target: LOG_TARGET, + "Restoring old authority set after block import error: {}", e, ); pending_changes.revert(); return Err(ConsensusError::ClientImport(e.to_string())) @@ -665,7 +664,7 @@ where import_res.unwrap_or_else(|err| { if needs_justification { debug!( - target: "afg", + target: LOG_TARGET, "Requesting justification from peers due to imported block #{} that enacts authority set change with invalid justification: {}", number, err @@ -678,7 +677,7 @@ where None => if needs_justification { debug!( - target: "afg", + target: LOG_TARGET, "Imported unjustified block #{} that enacts authority set change, waiting for finality for enactment.", number, ); @@ -803,7 +802,7 @@ where match result { Err(CommandOrError::VoterCommand(command)) => { - afg_log!( + grandpa_log!( initial_sync, "👴 Imported justification for block #{} that triggers \ command {}, signaling voter.", diff --git a/client/finality-grandpa/src/lib.rs b/client/finality-grandpa/src/lib.rs index c1b4962d04..efc46d8f93 100644 --- a/client/finality-grandpa/src/lib.rs +++ b/client/finality-grandpa/src/lib.rs @@ -93,10 +93,12 @@ use std::{ time::Duration, }; +const LOG_TARGET: &str = "grandpa"; + // utility logging macro that takes as first argument a conditional to // decide whether to log under debug or info level (useful to restrict // logging under initial sync). -macro_rules! afg_log { +macro_rules! grandpa_log { ($condition:expr, $($msg: expr),+ $(,)?) => { { let log_level = if $condition { @@ -105,7 +107,7 @@ macro_rules! afg_log { log::Level::Info }; - log::log!(target: "afg", log_level, $($msg),+); + log::log!(target: LOG_TARGET, log_level, $($msg),+); } }; } @@ -803,10 +805,11 @@ where ); let voter_work = voter_work.map(|res| match res { - Ok(()) => error!(target: "afg", + Ok(()) => error!( + target: LOG_TARGET, "GRANDPA voter future has concluded naturally, this should be unreachable." ), - Err(e) => error!(target: "afg", "GRANDPA voter error: {}", e), + Err(e) => error!(target: LOG_TARGET, "GRANDPA voter error: {}", e), }); // Make sure that `telemetry_task` doesn't accidentally finish and kill grandpa. @@ -871,7 +874,7 @@ where let metrics = match prometheus_registry.as_ref().map(Metrics::register) { Some(Ok(metrics)) => Some(metrics), Some(Err(e)) => { - debug!(target: "afg", "Failed to register metrics: {:?}", e); + debug!(target: LOG_TARGET, "Failed to register metrics: {:?}", e); None }, None => None, @@ -913,7 +916,12 @@ where /// state. This method should be called when we know that the authority set /// has changed (e.g. as signalled by a voter command). fn rebuild_voter(&mut self) { - debug!(target: "afg", "{}: Starting new voter with set ID {}", self.env.config.name(), self.env.set_id); + debug!( + target: LOG_TARGET, + "{}: Starting new voter with set ID {}", + self.env.config.name(), + self.env.set_id + ); let maybe_authority_id = local_authority_id(&self.env.voters, self.env.config.keystore.as_ref()); @@ -974,7 +982,8 @@ where // Repoint shared_voter_state so that the RPC endpoint can query the state if self.shared_voter_state.reset(voter.voter_state()).is_none() { - info!(target: "afg", + info!( + target: LOG_TARGET, "Timed out trying to update shared GRANDPA voter state. \ RPC endpoints may return stale data." ); @@ -1043,7 +1052,7 @@ where Ok(()) }, VoterCommand::Pause(reason) => { - info!(target: "afg", "Pausing old validator set: {}", reason); + info!(target: LOG_TARGET, "Pausing old validator set: {}", reason); // not racing because old voter is shut down. self.env.update_voter_set_state(|voter_set_state| { diff --git a/client/finality-grandpa/src/observer.rs b/client/finality-grandpa/src/observer.rs index 9bcb03c055..1efb71e590 100644 --- a/client/finality-grandpa/src/observer.rs +++ b/client/finality-grandpa/src/observer.rs @@ -43,7 +43,7 @@ use crate::{ environment, global_communication, notification::GrandpaJustificationSender, ClientForGrandpa, CommandOrError, CommunicationIn, Config, Error, LinkHalf, VoterCommand, - VoterSetState, + VoterSetState, LOG_TARGET, }; struct ObserverChain<'a, Block: BlockT, Client> { @@ -145,7 +145,7 @@ where // proceed processing with new finalized block number future::ok(finalized_number) } else { - debug!(target: "afg", "Received invalid commit: ({:?}, {:?})", round, commit); + debug!(target: LOG_TARGET, "Received invalid commit: ({:?}, {:?})", round, commit); finality_grandpa::process_commit_validation_result(validation_result, callback); @@ -317,7 +317,7 @@ where // update it on-disk in case we restart as validator in the future. self.persistent_data.set_state = match command { VoterCommand::Pause(reason) => { - info!(target: "afg", "Pausing old validator set: {}", reason); + info!(target: LOG_TARGET, "Pausing old validator set: {}", reason); let completed_rounds = self.persistent_data.set_state.read().completed_rounds(); let set_state = VoterSetState::Paused { completed_rounds }; diff --git a/client/finality-grandpa/src/until_imported.rs b/client/finality-grandpa/src/until_imported.rs index df0b63348e..95b658e922 100644 --- a/client/finality-grandpa/src/until_imported.rs +++ b/client/finality-grandpa/src/until_imported.rs @@ -24,7 +24,7 @@ use super::{ BlockStatus as BlockStatusT, BlockSyncRequester as BlockSyncRequesterT, CommunicationIn, Error, - SignedMessage, + SignedMessage, LOG_TARGET, }; use finality_grandpa::voter; @@ -296,7 +296,7 @@ where let next_log = *last_log + LOG_PENDING_INTERVAL; if Instant::now() >= next_log { debug!( - target: "afg", + target: LOG_TARGET, "Waiting to import block {} before {} {} messages can be imported. \ Requesting network sync service to retrieve block from. \ Possible fork?", @@ -346,7 +346,7 @@ where fn warn_authority_wrong_target(hash: H, id: AuthorityId) { warn!( - target: "afg", + target: LOG_TARGET, "Authority {:?} signed GRANDPA message with \ wrong block number for hash {}", id, diff --git a/frame/aura/src/lib.rs b/frame/aura/src/lib.rs index ff2c5df04a..635e22c5d5 100644 --- a/frame/aura/src/lib.rs +++ b/frame/aura/src/lib.rs @@ -58,6 +58,8 @@ mod tests; pub use pallet::*; +const LOG_TARGET: &str = "runtime::aura"; + #[frame_support::pallet] pub mod pallet { use super::*; @@ -222,7 +224,7 @@ impl OneSessionHandler for Pallet { if last_authorities != next_authorities { if next_authorities.len() as u32 > T::MaxAuthorities::get() { log::warn!( - target: "runtime::aura", + target: LOG_TARGET, "next authorities list larger than {}, truncating", T::MaxAuthorities::get(), ); diff --git a/frame/babe/src/equivocation.rs b/frame/babe/src/equivocation.rs index f55bda7518..70f087fd46 100644 --- a/frame/babe/src/equivocation.rs +++ b/frame/babe/src/equivocation.rs @@ -48,7 +48,7 @@ use sp_staking::{ }; use sp_std::prelude::*; -use crate::{Call, Config, Pallet}; +use crate::{Call, Config, Pallet, LOG_TARGET}; /// A trait with utility methods for handling equivocation reports in BABE. /// The trait provides methods for reporting an offence triggered by a valid @@ -161,15 +161,9 @@ where }; match SubmitTransaction::>::submit_unsigned_transaction(call.into()) { - Ok(()) => log::info!( - target: "runtime::babe", - "Submitted BABE equivocation report.", - ), - Err(e) => log::error!( - target: "runtime::babe", - "Error submitting equivocation report: {:?}", - e, - ), + Ok(()) => log::info!(target: LOG_TARGET, "Submitted BABE equivocation report.",), + Err(e) => + log::error!(target: LOG_TARGET, "Error submitting equivocation report: {:?}", e,), } Ok(()) @@ -192,7 +186,7 @@ impl Pallet { TransactionSource::Local | TransactionSource::InBlock => { /* allowed */ }, _ => { log::warn!( - target: "runtime::babe", + target: LOG_TARGET, "rejecting unsigned report equivocation transaction because it is not local/in-block.", ); diff --git a/frame/babe/src/lib.rs b/frame/babe/src/lib.rs index 1a9b320008..16b2b21197 100644 --- a/frame/babe/src/lib.rs +++ b/frame/babe/src/lib.rs @@ -50,6 +50,8 @@ use sp_consensus_vrf::schnorrkel; pub use sp_consensus_babe::{AuthorityId, PUBLIC_KEY_LENGTH, RANDOMNESS_LENGTH, VRF_OUTPUT_LENGTH}; +const LOG_TARGET: &str = "runtime::babe"; + mod default_weights; mod equivocation; mod randomness; diff --git a/frame/grandpa/src/equivocation.rs b/frame/grandpa/src/equivocation.rs index 181d22fba5..23801a4982 100644 --- a/frame/grandpa/src/equivocation.rs +++ b/frame/grandpa/src/equivocation.rs @@ -52,7 +52,7 @@ use sp_staking::{ SessionIndex, }; -use super::{Call, Config, Pallet}; +use super::{Call, Config, Pallet, LOG_TARGET}; /// A trait with utility methods for handling equivocation reports in GRANDPA. /// The offence type is generic, and the trait provides , reporting an offence @@ -170,15 +170,9 @@ where }; match SubmitTransaction::>::submit_unsigned_transaction(call.into()) { - Ok(()) => log::info!( - target: "runtime::afg", - "Submitted GRANDPA equivocation report.", - ), - Err(e) => log::error!( - target: "runtime::afg", - "Error submitting equivocation report: {:?}", - e, - ), + Ok(()) => log::info!(target: LOG_TARGET, "Submitted GRANDPA equivocation report.",), + Err(e) => + log::error!(target: LOG_TARGET, "Error submitting equivocation report: {:?}", e,), } Ok(()) @@ -211,7 +205,7 @@ impl Pallet { TransactionSource::Local | TransactionSource::InBlock => { /* allowed */ }, _ => { log::warn!( - target: "runtime::afg", + target: LOG_TARGET, "rejecting unsigned report equivocation transaction because it is not local/in-block." ); diff --git a/frame/grandpa/src/lib.rs b/frame/grandpa/src/lib.rs index c6b7fd2516..716cf54865 100644 --- a/frame/grandpa/src/lib.rs +++ b/frame/grandpa/src/lib.rs @@ -70,6 +70,8 @@ pub use equivocation::{ pub use pallet::*; +const LOG_TARGET: &str = "runtime::grandpa"; + #[frame_support::pallet] pub mod pallet { use super::*; diff --git a/frame/grandpa/src/migrations/v4.rs b/frame/grandpa/src/migrations/v4.rs index 81dbd3bab4..33e200b728 100644 --- a/frame/grandpa/src/migrations/v4.rs +++ b/frame/grandpa/src/migrations/v4.rs @@ -15,6 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::LOG_TARGET; use frame_support::{ traits::{Get, StorageVersion}, weights::Weight, @@ -34,14 +35,14 @@ pub const OLD_PREFIX: &[u8] = b"GrandpaFinality"; pub fn migrate>(new_pallet_name: N) -> Weight { if new_pallet_name.as_ref().as_bytes() == OLD_PREFIX { log::info!( - target: "runtime::afg", + target: LOG_TARGET, "New pallet name is equal to the old prefix. No migration needs to be done.", ); return Weight::zero() } let storage_version = StorageVersion::get::>(); log::info!( - target: "runtime::afg", + target: LOG_TARGET, "Running migration to v3.1 for grandpa with storage version {:?}", storage_version, ); From bb94ac73b65a4b21186efaf5a870b5550aa056aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Wed, 14 Dec 2022 22:55:24 +0000 Subject: [PATCH 040/127] Staking: store last `min-active-bond` on-chain (#12889) * Staking: store last min-active-bond on-chain Storing the `min-active-bond` onchain helps the UIs with minimal on-chain costs. Closes https://github.com/paritytech/substrate/issues/12746 * Avoid relying on sorting to set the * Addresses PR comments --- frame/staking/src/pallet/impls.rs | 14 +++++++++++++- frame/staking/src/pallet/mod.rs | 4 ++++ frame/staking/src/tests.rs | 26 ++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 1a4086ad2a..830b33ceb6 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -704,6 +704,9 @@ impl Pallet { /// /// `maybe_max_len` can imposes a cap on the number of voters returned; /// + /// Sets `MinimumActiveStake` to the minimum active nominator stake in the returned set of + /// nominators. + /// /// This function is self-weighing as [`DispatchClass::Mandatory`]. pub fn get_npos_voters(maybe_max_len: Option) -> Vec> { let max_allowed_len = { @@ -719,6 +722,7 @@ impl Pallet { let mut voters_seen = 0u32; let mut validators_taken = 0u32; let mut nominators_taken = 0u32; + let mut min_active_stake = u64::MAX; let mut sorted_voters = T::VoterList::iter(); while all_voters.len() < max_allowed_len && @@ -733,12 +737,15 @@ impl Pallet { }; if let Some(Nominations { targets, .. }) = >::get(&voter) { + let voter_weight = weight_of(&voter); if !targets.is_empty() { - all_voters.push((voter.clone(), weight_of(&voter), targets)); + all_voters.push((voter.clone(), voter_weight, targets)); nominators_taken.saturating_inc(); } else { // Technically should never happen, but not much we can do about it. } + min_active_stake = + if voter_weight < min_active_stake { voter_weight } else { min_active_stake }; } else if Validators::::contains_key(&voter) { // if this voter is a validator: let self_vote = ( @@ -769,6 +776,11 @@ impl Pallet { Self::register_weight(T::WeightInfo::get_npos_voters(validators_taken, nominators_taken)); + let min_active_stake: T::CurrencyBalance = + if all_voters.len() == 0 { 0u64.into() } else { min_active_stake.into() }; + + MinimumActiveStake::::put(min_active_stake); + log!( info, "generated {} npos voters, {} from validators and {} nominators", diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index fda455ca3c..2daa992f4e 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -295,6 +295,10 @@ pub mod pallet { #[pallet::storage] pub type MinValidatorBond = StorageValue<_, BalanceOf, ValueQuery>; + /// The minimum active nominator stake of the last successful election. + #[pallet::storage] + pub type MinimumActiveStake = StorageValue<_, BalanceOf, ValueQuery>; + /// The minimum amount of commission that validators can set. /// /// If set to `0`, no limit exists. diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 3e0a62f53d..74d8dc8a81 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4463,6 +4463,32 @@ mod election_data_provider { ); } + #[test] + fn set_minimum_active_stake_is_correct() { + ExtBuilder::default() + .nominate(false) + .add_staker(61, 60, 2_000, StakerStatus::::Nominator(vec![21])) + .add_staker(71, 70, 10, StakerStatus::::Nominator(vec![21])) + .add_staker(81, 80, 50, StakerStatus::::Nominator(vec![21])) + .build_and_execute(|| { + assert_ok!(::electing_voters(None)); + assert_eq!(MinimumActiveStake::::get(), 10); + + // remove staker with lower bond by limiting the number of voters and check + // `MinimumActiveStake` again after electing voters. + assert_ok!(::electing_voters(Some(5))); + assert_eq!(MinimumActiveStake::::get(), 50); + }); + } + + #[test] + fn set_minimum_active_stake_zero_correct() { + ExtBuilder::default().has_stakers(false).build_and_execute(|| { + assert_ok!(::electing_voters(None)); + assert_eq!(MinimumActiveStake::::get(), 0); + }); + } + #[test] fn voters_include_self_vote() { ExtBuilder::default().nominate(false).build_and_execute(|| { From cd2fdcf85eb96c53ce2a5d418d4338eb92f5d4f5 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Wed, 14 Dec 2022 23:59:01 +0000 Subject: [PATCH 041/127] Try-runtime Revamp and Facelift (#12537) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix online/offline confusion * unified cache file * multi-threaded babyyy * checkpoint for niklas * compiles * all tests pass with --test-threads 1 * child-tree scrape is also multi-threaded now. * better thread splitting * some suggestions (#12532) * some suggestions * tokio multithread * move unused dependencies * snapshot command * fix rem * a bit of cleanup * support optional checks * fix * OCW command migrated to wasm-only, as an example * state-version management fully in remote-ext * almost everything move to wasm executor, some CLI flags reduced * follow-chain works as well * Master.into() * everything builds now * concurrent insertion and download for remote builds * minor fix * fix a bug * checkpoint * some updates * fmt * review comments * fmt * fix * fmt * update * fmt * rename * fix the damn UI tests * fmt * remoe the thread abstraction for the time being * cleanup * fix CI * fmt * fix * fix a few more things * tweak log levels * better error handling * address grumbles: use futures::mpsc * review comments * fmt * Apply suggestions from code review Co-authored-by: Bastian Köcher * Update utils/frame/try-runtime/cli/src/lib.rs Co-authored-by: Bastian Köcher * better api version stuff * some doc update * a whole lot of docs * fmt * fix all docs * fmt * rpc rebase: Try-runtime Revamp and Facelift (#12921) * Introduce sensible weight constants (#12868) * Introduce sensible weight constants * cargo fmt * Remove unused import * Add missing import * ".git/.scripts/bench-bot.sh" pallet dev pallet_lottery Co-authored-by: command-bot <> * Checkout to the branch HEAD explicitly in `build-linux-substrate` (#12876) * cli: Improve pruning documentation (#12819) * cli: Improve pruning documentation Signed-off-by: Alexandru Vasile * cli: Keep `finalized` notation and remove `canonical` one * cli: Fix cargo doc * cli: `PruningModeClap` IR enum Signed-off-by: Alexandru Vasile * cli: Convert PruningModeClap into pruning modes Signed-off-by: Alexandru Vasile * cli: Use `PruningModeClap` Signed-off-by: Alexandru Vasile * cli: Rename to `DatabasePruningMode` Signed-off-by: Alexandru Vasile * cli: Implement `FromStr` instead of `clap::ValueEnum` Signed-off-by: Alexandru Vasile * Update client/cli/src/params/pruning_params.rs Co-authored-by: Bastian Köcher * Fix clippy Signed-off-by: Alexandru Vasile * cli: Add option documentation back Signed-off-by: Alexandru Vasile * Apply suggestions from code review Signed-off-by: Alexandru Vasile Co-authored-by: Bastian Köcher * Revert "Move LockableCurrency trait to fungibles::Lockable and deprecate LockableCurrency (#12798)" (#12882) This reverts commit ea3ca3f757ff9d9559665719a77da81f4cf0f0ce. * Don't indefinitely block on shutting down Tokio (#12885) * Don't indefinitely on shutting down Tokio Now we wait in maximum 60 seconds before we shutdown the node. Tasks are may be leaked and leading to some data corruption. * Drink less :thinking_face: * General Message Queue Pallet (#12485) * The message queue * Make fully generic * Refactor * Docs * Refactor * Use iter not slice * Per-origin queues * Multi-queue processing * Introduce MaxReady * Remove MaxReady in favour of ready ring * Cleanups * ReadyRing and tests * Stale page reaping * from_components -> from_parts Signed-off-by: Oliver Tale-Yazdi * Move WeightCounter to sp_weights Signed-off-by: Oliver Tale-Yazdi * Add MockedWeightInfo Signed-off-by: Oliver Tale-Yazdi * Deploy to kitchensink Signed-off-by: Oliver Tale-Yazdi * Use WeightCounter Signed-off-by: Oliver Tale-Yazdi * Small fixes and logging Signed-off-by: Oliver Tale-Yazdi * Add service_page Signed-off-by: Oliver Tale-Yazdi * Typo Signed-off-by: Oliver Tale-Yazdi * Move service_page below service_queue Signed-off-by: Oliver Tale-Yazdi * Add service_message Signed-off-by: Oliver Tale-Yazdi * Use correct weight function Signed-off-by: Oliver Tale-Yazdi * Overweight execution * Refactor * Missing file * Fix WeightCounter usage in scheduler Signed-off-by: Oliver Tale-Yazdi * Fix peek_index Take into account that decoding from a mutable slice modifies it. Signed-off-by: Oliver Tale-Yazdi * Add tests and bench service_page_item Signed-off-by: Oliver Tale-Yazdi * Add debug_info Signed-off-by: Oliver Tale-Yazdi * Add no-progress check to service_queues Signed-off-by: Oliver Tale-Yazdi * Add more benches Signed-off-by: Oliver Tale-Yazdi * Bound from_message and try_append_message Signed-off-by: Oliver Tale-Yazdi * Add PageReaped event Signed-off-by: Oliver Tale-Yazdi * Rename BookStateOf and BookStateFor Signed-off-by: Oliver Tale-Yazdi * Update tests and remove logging Signed-off-by: Oliver Tale-Yazdi * Remove redundant per-message origins; add footprint() and sweep_queue() * Move testing stuff to mock.rs Signed-off-by: Oliver Tale-Yazdi * Add integration test Signed-off-by: Oliver Tale-Yazdi * Fix no-progress check Signed-off-by: Oliver Tale-Yazdi * Fix debug_info Signed-off-by: Oliver Tale-Yazdi * Fixup merge and tests Signed-off-by: Oliver Tale-Yazdi * Fix footprint tracking * Introduce * Formatting * OverweightEnqueued event, auto-servicing config item * Update tests and benchmarks Signed-off-by: Oliver Tale-Yazdi * Clippy Signed-off-by: Oliver Tale-Yazdi * Add tests Signed-off-by: Oliver Tale-Yazdi * Provide change handler * Add missing BookStateFor::insert and call QueueChangeHandler Signed-off-by: Oliver Tale-Yazdi * Docs Signed-off-by: Oliver Tale-Yazdi * Update benchmarks and weights Signed-off-by: Oliver Tale-Yazdi * More tests... Signed-off-by: Oliver Tale-Yazdi * Use weight metering functions Signed-off-by: Oliver Tale-Yazdi * weightInfo::process_message_payload is gone Signed-off-by: Oliver Tale-Yazdi * Add defensive_saturating_accrue Signed-off-by: Oliver Tale-Yazdi * Rename WeightCounter to WeightMeter Ctr+Shift+H should do the trick. Signed-off-by: Oliver Tale-Yazdi * Test on_initialize Signed-off-by: Oliver Tale-Yazdi * Add module docs Signed-off-by: Oliver Tale-Yazdi * Remove origin from MaxMessageLen The message origin is not encoded into the heap and does therefore not influence the max message length anymore. Signed-off-by: Oliver Tale-Yazdi * Add BoundedVec::as_slice Signed-off-by: Oliver Tale-Yazdi * Test Page::{from_message, try_append_message} Signed-off-by: Oliver Tale-Yazdi * Fixup docs Signed-off-by: Oliver Tale-Yazdi * Docs * Do nothing in sweep_queue if the queue does not exist ... otherwise it inserts default values into the storage. Signed-off-by: Oliver Tale-Yazdi * Test ring (un)knitting Signed-off-by: Oliver Tale-Yazdi * Upgrade stress-test Change the test to not assume that all queued messages will be processed in the next block but split it over multiple. Signed-off-by: Oliver Tale-Yazdi * More tests... Signed-off-by: Oliver Tale-Yazdi * Beauty fixes Signed-off-by: Oliver Tale-Yazdi * clippy Signed-off-by: Oliver Tale-Yazdi * Rename BoundedVec::as_slice to as_bounded_slice Conflicts with deref().as_slice() otherwise. Signed-off-by: Oliver Tale-Yazdi * Fix imports Signed-off-by: Oliver Tale-Yazdi * Remove ReadyRing struct Was used for testing only. Instead use 'fn assert_ring' which also check the service head and backlinks. Signed-off-by: Oliver Tale-Yazdi * Beauty fixes Signed-off-by: Oliver Tale-Yazdi * Fix stale page watermark Signed-off-by: Oliver Tale-Yazdi * Cleanup Signed-off-by: Oliver Tale-Yazdi * Fix test feature and clippy Signed-off-by: Oliver Tale-Yazdi * QueueChanged handler is called correctly Signed-off-by: Oliver Tale-Yazdi * Update benches Signed-off-by: Oliver Tale-Yazdi * Abstract testing functions Signed-off-by: Oliver Tale-Yazdi * More tests Signed-off-by: Oliver Tale-Yazdi * Cleanup Signed-off-by: Oliver Tale-Yazdi * Clippy Signed-off-by: Oliver Tale-Yazdi * fmt Signed-off-by: Oliver Tale-Yazdi * Simplify tests Signed-off-by: Oliver Tale-Yazdi * Make stuff compile Signed-off-by: Oliver Tale-Yazdi * Extend overweight execution benchmark Signed-off-by: Oliver Tale-Yazdi * Remove TODOs Signed-off-by: Oliver Tale-Yazdi * Test service queue with faulty MessageProcessor Signed-off-by: Oliver Tale-Yazdi * fmt Signed-off-by: Oliver Tale-Yazdi * Update pallet ui tests to 1.65 Signed-off-by: Oliver Tale-Yazdi * More docs Signed-off-by: Oliver Tale-Yazdi * Review doc fixes Co-authored-by: Robert Klotzner Signed-off-by: Oliver Tale-Yazdi * Add weight_limit to extrinsic weight of execute_overweight * Correctly return unused weight * Return actual weight consumed in do_execute_overweight * Review fixes Signed-off-by: Oliver Tale-Yazdi * Set version 7.0.0-dev Signed-off-by: Oliver Tale-Yazdi * Make it compile Signed-off-by: Oliver Tale-Yazdi * Switch message_size to u64 Signed-off-by: Oliver Tale-Yazdi * Switch message_count to u64 Signed-off-by: Oliver Tale-Yazdi * Fix benchmarks Signed-off-by: Oliver Tale-Yazdi * Make CI green Signed-off-by: Oliver Tale-Yazdi * Docs * Update tests Signed-off-by: Oliver Tale-Yazdi * ".git/.scripts/bench-bot.sh" pallet dev pallet_message_queue * Dont mention README.md in the Cargo.toml Signed-off-by: Oliver Tale-Yazdi * Remove reference to readme Signed-off-by: Oliver Tale-Yazdi Co-authored-by: Oliver Tale-Yazdi Co-authored-by: parity-processbot <> Co-authored-by: Robert Klotzner Co-authored-by: Keith Yeung * zombienet timings adjusted (#12890) * zombinet tests: add some timeout to allow net spin-up Sometimes tests are failing at first try, as the pods were not up yet. Adding timeout should allow the network to spin up properly. * initial timeout increased to 30s * Move import queue out of `sc-network` (#12764) * Move import queue out of `sc-network` Add supplementary asynchronous API for the import queue which means it can be run as an independent task and communicated with through the `ImportQueueService`. This commit removes removes block and justification imports from `sc-network` and provides `ChainSync` with a handle to import queue so it can import blocks and justifications. Polling of the import queue is moved complete out of `sc-network` and `sc_consensus::Link` is implemented for `ChainSyncInterfaceHandled` so the import queue can still influence the syncing process. * Fix tests * Apply review comments * Apply suggestions from code review Co-authored-by: Bastian Köcher * Update client/network/sync/src/lib.rs Co-authored-by: Bastian Köcher Co-authored-by: Bastian Köcher * Trace response payload in default `jsonrpsee` middleware (#12886) * Trace result in default `jsonrpsee` middleware * `rpc_metrics::extra` Co-authored-by: Bastian Köcher Co-authored-by: Bastian Köcher * Ensure that we inform all tasks to stop before starting the 60 seconds shutdown (#12897) * Ensure that we inform all tasks to stop before starting the 60 seconds shutdown The change of waiting in maximum 60 seconds for the node to shutdown actually introduced a bug. We were actually waiting always 60 seconds as we didn't informed our tasks to shutdown. The solution to this problem is to drop the task manager as this will then inform all tasks to end. It also adds tests to ensure that the behaviors work as expected. (This should already have been done in the first pr! :() * ".git/.scripts/fmt.sh" 1 Co-authored-by: command-bot <> * Safe desired targets call (#12826) * checked call for desired targets * fix compile * fmt * fix tests * cleaner with and_then * Fix typo (#12900) * ValidateUnsigned: Improve docs. (#12870) * ValidateUnsigned: Improve docs. * Review comments * rpc server with HTTP/WS on the same socket (#12663) * jsonrpsee v0.16 add backwards compatibility run old http server on http only * cargo fmt * update jsonrpsee 0.16.1 * less verbose cors log * fix nit in log: WS -> HTTP * revert needless changes in Cargo.lock * remove unused features in tower * fix nits; add client-core feature * jsonrpsee v0.16.2 * `pallet-message-queue`: Fix license (#12895) * Fix license Signed-off-by: Oliver Tale-Yazdi * Add mock doc Signed-off-by: Oliver Tale-Yazdi Signed-off-by: Oliver Tale-Yazdi * Use explicit call indices (#12891) * frame-system: explicit call index Signed-off-by: Oliver Tale-Yazdi * Use explicit call indices Signed-off-by: Oliver Tale-Yazdi * pallet-template: explicit call index Signed-off-by: Oliver Tale-Yazdi * DNM: Temporarily require call_index Signed-off-by: Oliver Tale-Yazdi * Revert "DNM: Temporarily require call_index" This reverts commit c4934e312e12af72ca05a8029d7da753a9c99346. Signed-off-by: Oliver Tale-Yazdi * Pin canonincalized block (#12902) * Remove implicit approval chilling upon slash. (#12420) * don't read slashing spans when taking election snapshot * update cargo.toml * bring back remote test * fix merge stuff * fix npos-voters function sig * remove as much redundant diff as you can * Update frame/staking/src/pallet/mod.rs Co-authored-by: Andronik * fix * Update frame/staking/src/pallet/impls.rs * update lock * fix all tests * review comments * fmt * fix offence bench * clippy * ".git/.scripts/bench-bot.sh" pallet dev pallet_staking Co-authored-by: Andronik Co-authored-by: Ankan Co-authored-by: command-bot <> * bounties calls docs fix (#12909) Co-authored-by: parity-processbot <> * pallet-contracts migration pre-upgrade fix for v8 (#12905) * Only run pre-v8 migration check for versions older than 8 * Logix fix * use custom environment for publishing crates (#12912) * [contracts] Add debug buffer limit + enforcement (#12845) * Add debug buffer limit + enforcement Add debug buffer limit + enforcement * use BoundedVec for the debug buffer * revert schedule (debug buf len limit not needed anymore) * return DispatchError * addressed review comments * frame/remote-externalities: Fix clippy Signed-off-by: Alexandru Vasile * frame/rpc: Add previous export Signed-off-by: Alexandru Vasile * Fixup some wrong dependencies (#12899) * Fixup some wrong dependencies Dev dependencies should not appear in the feature list. If features are required, they should be directly enabled for the `dev-dependency`. * More fixups * Fix fix * Remove deprecated feature * Make all work properly and nice!! * FMT * Fix formatting * add numerator and denominator to Rational128 Debug impl and increase precision of float representation (#12914) * Fix state-db pinning (#12927) * Pin all canonicalized blocks * Added a test * Docs * [ci] add job switcher (#12922) Signed-off-by: Alexandru Vasile Signed-off-by: Oliver Tale-Yazdi Co-authored-by: Keith Yeung Co-authored-by: Vlad Co-authored-by: Bastian Köcher Co-authored-by: Anthony Alaribe Co-authored-by: Gavin Wood Co-authored-by: Oliver Tale-Yazdi Co-authored-by: Robert Klotzner Co-authored-by: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Co-authored-by: Aaro Altonen <48052676+altonen@users.noreply.github.com> Co-authored-by: tgmichel Co-authored-by: Ankan <10196091+Ank4n@users.noreply.github.com> Co-authored-by: Luke Schoen Co-authored-by: Niklas Adolfsson Co-authored-by: Arkadiy Paronyan Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Co-authored-by: Andronik Co-authored-by: Ankan Co-authored-by: Muharem Ismailov Co-authored-by: Dino Pačandi <3002868+Dinonard@users.noreply.github.com> Co-authored-by: João Paulo Silva de Souza <77391175+joao-paulo-parity@users.noreply.github.com> Co-authored-by: Sasha Gryaznov Co-authored-by: Alexander Popiak Co-authored-by: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> * Revert "rpc rebase: Try-runtime Revamp and Facelift (#12921)" This reverts commit 4ce770a9cb8daf1401529bda7d974b8c703f6b3e. * Lexnv/kiz revamp try runtime stuff (#12932) * Introduce sensible weight constants (#12868) * Introduce sensible weight constants * cargo fmt * Remove unused import * Add missing import * ".git/.scripts/bench-bot.sh" pallet dev pallet_lottery Co-authored-by: command-bot <> * Checkout to the branch HEAD explicitly in `build-linux-substrate` (#12876) * cli: Improve pruning documentation (#12819) * cli: Improve pruning documentation Signed-off-by: Alexandru Vasile * cli: Keep `finalized` notation and remove `canonical` one * cli: Fix cargo doc * cli: `PruningModeClap` IR enum Signed-off-by: Alexandru Vasile * cli: Convert PruningModeClap into pruning modes Signed-off-by: Alexandru Vasile * cli: Use `PruningModeClap` Signed-off-by: Alexandru Vasile * cli: Rename to `DatabasePruningMode` Signed-off-by: Alexandru Vasile * cli: Implement `FromStr` instead of `clap::ValueEnum` Signed-off-by: Alexandru Vasile * Update client/cli/src/params/pruning_params.rs Co-authored-by: Bastian Köcher * Fix clippy Signed-off-by: Alexandru Vasile * cli: Add option documentation back Signed-off-by: Alexandru Vasile * Apply suggestions from code review Signed-off-by: Alexandru Vasile Co-authored-by: Bastian Köcher * Revert "Move LockableCurrency trait to fungibles::Lockable and deprecate LockableCurrency (#12798)" (#12882) This reverts commit ea3ca3f757ff9d9559665719a77da81f4cf0f0ce. * Don't indefinitely block on shutting down Tokio (#12885) * Don't indefinitely on shutting down Tokio Now we wait in maximum 60 seconds before we shutdown the node. Tasks are may be leaked and leading to some data corruption. * Drink less :thinking_face: * General Message Queue Pallet (#12485) * The message queue * Make fully generic * Refactor * Docs * Refactor * Use iter not slice * Per-origin queues * Multi-queue processing * Introduce MaxReady * Remove MaxReady in favour of ready ring * Cleanups * ReadyRing and tests * Stale page reaping * from_components -> from_parts Signed-off-by: Oliver Tale-Yazdi * Move WeightCounter to sp_weights Signed-off-by: Oliver Tale-Yazdi * Add MockedWeightInfo Signed-off-by: Oliver Tale-Yazdi * Deploy to kitchensink Signed-off-by: Oliver Tale-Yazdi * Use WeightCounter Signed-off-by: Oliver Tale-Yazdi * Small fixes and logging Signed-off-by: Oliver Tale-Yazdi * Add service_page Signed-off-by: Oliver Tale-Yazdi * Typo Signed-off-by: Oliver Tale-Yazdi * Move service_page below service_queue Signed-off-by: Oliver Tale-Yazdi * Add service_message Signed-off-by: Oliver Tale-Yazdi * Use correct weight function Signed-off-by: Oliver Tale-Yazdi * Overweight execution * Refactor * Missing file * Fix WeightCounter usage in scheduler Signed-off-by: Oliver Tale-Yazdi * Fix peek_index Take into account that decoding from a mutable slice modifies it. Signed-off-by: Oliver Tale-Yazdi * Add tests and bench service_page_item Signed-off-by: Oliver Tale-Yazdi * Add debug_info Signed-off-by: Oliver Tale-Yazdi * Add no-progress check to service_queues Signed-off-by: Oliver Tale-Yazdi * Add more benches Signed-off-by: Oliver Tale-Yazdi * Bound from_message and try_append_message Signed-off-by: Oliver Tale-Yazdi * Add PageReaped event Signed-off-by: Oliver Tale-Yazdi * Rename BookStateOf and BookStateFor Signed-off-by: Oliver Tale-Yazdi * Update tests and remove logging Signed-off-by: Oliver Tale-Yazdi * Remove redundant per-message origins; add footprint() and sweep_queue() * Move testing stuff to mock.rs Signed-off-by: Oliver Tale-Yazdi * Add integration test Signed-off-by: Oliver Tale-Yazdi * Fix no-progress check Signed-off-by: Oliver Tale-Yazdi * Fix debug_info Signed-off-by: Oliver Tale-Yazdi * Fixup merge and tests Signed-off-by: Oliver Tale-Yazdi * Fix footprint tracking * Introduce * Formatting * OverweightEnqueued event, auto-servicing config item * Update tests and benchmarks Signed-off-by: Oliver Tale-Yazdi * Clippy Signed-off-by: Oliver Tale-Yazdi * Add tests Signed-off-by: Oliver Tale-Yazdi * Provide change handler * Add missing BookStateFor::insert and call QueueChangeHandler Signed-off-by: Oliver Tale-Yazdi * Docs Signed-off-by: Oliver Tale-Yazdi * Update benchmarks and weights Signed-off-by: Oliver Tale-Yazdi * More tests... Signed-off-by: Oliver Tale-Yazdi * Use weight metering functions Signed-off-by: Oliver Tale-Yazdi * weightInfo::process_message_payload is gone Signed-off-by: Oliver Tale-Yazdi * Add defensive_saturating_accrue Signed-off-by: Oliver Tale-Yazdi * Rename WeightCounter to WeightMeter Ctr+Shift+H should do the trick. Signed-off-by: Oliver Tale-Yazdi * Test on_initialize Signed-off-by: Oliver Tale-Yazdi * Add module docs Signed-off-by: Oliver Tale-Yazdi * Remove origin from MaxMessageLen The message origin is not encoded into the heap and does therefore not influence the max message length anymore. Signed-off-by: Oliver Tale-Yazdi * Add BoundedVec::as_slice Signed-off-by: Oliver Tale-Yazdi * Test Page::{from_message, try_append_message} Signed-off-by: Oliver Tale-Yazdi * Fixup docs Signed-off-by: Oliver Tale-Yazdi * Docs * Do nothing in sweep_queue if the queue does not exist ... otherwise it inserts default values into the storage. Signed-off-by: Oliver Tale-Yazdi * Test ring (un)knitting Signed-off-by: Oliver Tale-Yazdi * Upgrade stress-test Change the test to not assume that all queued messages will be processed in the next block but split it over multiple. Signed-off-by: Oliver Tale-Yazdi * More tests... Signed-off-by: Oliver Tale-Yazdi * Beauty fixes Signed-off-by: Oliver Tale-Yazdi * clippy Signed-off-by: Oliver Tale-Yazdi * Rename BoundedVec::as_slice to as_bounded_slice Conflicts with deref().as_slice() otherwise. Signed-off-by: Oliver Tale-Yazdi * Fix imports Signed-off-by: Oliver Tale-Yazdi * Remove ReadyRing struct Was used for testing only. Instead use 'fn assert_ring' which also check the service head and backlinks. Signed-off-by: Oliver Tale-Yazdi * Beauty fixes Signed-off-by: Oliver Tale-Yazdi * Fix stale page watermark Signed-off-by: Oliver Tale-Yazdi * Cleanup Signed-off-by: Oliver Tale-Yazdi * Fix test feature and clippy Signed-off-by: Oliver Tale-Yazdi * QueueChanged handler is called correctly Signed-off-by: Oliver Tale-Yazdi * Update benches Signed-off-by: Oliver Tale-Yazdi * Abstract testing functions Signed-off-by: Oliver Tale-Yazdi * More tests Signed-off-by: Oliver Tale-Yazdi * Cleanup Signed-off-by: Oliver Tale-Yazdi * Clippy Signed-off-by: Oliver Tale-Yazdi * fmt Signed-off-by: Oliver Tale-Yazdi * Simplify tests Signed-off-by: Oliver Tale-Yazdi * Make stuff compile Signed-off-by: Oliver Tale-Yazdi * Extend overweight execution benchmark Signed-off-by: Oliver Tale-Yazdi * Remove TODOs Signed-off-by: Oliver Tale-Yazdi * Test service queue with faulty MessageProcessor Signed-off-by: Oliver Tale-Yazdi * fmt Signed-off-by: Oliver Tale-Yazdi * Update pallet ui tests to 1.65 Signed-off-by: Oliver Tale-Yazdi * More docs Signed-off-by: Oliver Tale-Yazdi * Review doc fixes Co-authored-by: Robert Klotzner Signed-off-by: Oliver Tale-Yazdi * Add weight_limit to extrinsic weight of execute_overweight * Correctly return unused weight * Return actual weight consumed in do_execute_overweight * Review fixes Signed-off-by: Oliver Tale-Yazdi * Set version 7.0.0-dev Signed-off-by: Oliver Tale-Yazdi * Make it compile Signed-off-by: Oliver Tale-Yazdi * Switch message_size to u64 Signed-off-by: Oliver Tale-Yazdi * Switch message_count to u64 Signed-off-by: Oliver Tale-Yazdi * Fix benchmarks Signed-off-by: Oliver Tale-Yazdi * Make CI green Signed-off-by: Oliver Tale-Yazdi * Docs * Update tests Signed-off-by: Oliver Tale-Yazdi * ".git/.scripts/bench-bot.sh" pallet dev pallet_message_queue * Dont mention README.md in the Cargo.toml Signed-off-by: Oliver Tale-Yazdi * Remove reference to readme Signed-off-by: Oliver Tale-Yazdi Co-authored-by: Oliver Tale-Yazdi Co-authored-by: parity-processbot <> Co-authored-by: Robert Klotzner Co-authored-by: Keith Yeung * zombienet timings adjusted (#12890) * zombinet tests: add some timeout to allow net spin-up Sometimes tests are failing at first try, as the pods were not up yet. Adding timeout should allow the network to spin up properly. * initial timeout increased to 30s * Move import queue out of `sc-network` (#12764) * Move import queue out of `sc-network` Add supplementary asynchronous API for the import queue which means it can be run as an independent task and communicated with through the `ImportQueueService`. This commit removes removes block and justification imports from `sc-network` and provides `ChainSync` with a handle to import queue so it can import blocks and justifications. Polling of the import queue is moved complete out of `sc-network` and `sc_consensus::Link` is implemented for `ChainSyncInterfaceHandled` so the import queue can still influence the syncing process. * Fix tests * Apply review comments * Apply suggestions from code review Co-authored-by: Bastian Köcher * Update client/network/sync/src/lib.rs Co-authored-by: Bastian Köcher Co-authored-by: Bastian Köcher * Trace response payload in default `jsonrpsee` middleware (#12886) * Trace result in default `jsonrpsee` middleware * `rpc_metrics::extra` Co-authored-by: Bastian Köcher Co-authored-by: Bastian Köcher * Ensure that we inform all tasks to stop before starting the 60 seconds shutdown (#12897) * Ensure that we inform all tasks to stop before starting the 60 seconds shutdown The change of waiting in maximum 60 seconds for the node to shutdown actually introduced a bug. We were actually waiting always 60 seconds as we didn't informed our tasks to shutdown. The solution to this problem is to drop the task manager as this will then inform all tasks to end. It also adds tests to ensure that the behaviors work as expected. (This should already have been done in the first pr! :() * ".git/.scripts/fmt.sh" 1 Co-authored-by: command-bot <> * Safe desired targets call (#12826) * checked call for desired targets * fix compile * fmt * fix tests * cleaner with and_then * Fix typo (#12900) * ValidateUnsigned: Improve docs. (#12870) * ValidateUnsigned: Improve docs. * Review comments * rpc server with HTTP/WS on the same socket (#12663) * jsonrpsee v0.16 add backwards compatibility run old http server on http only * cargo fmt * update jsonrpsee 0.16.1 * less verbose cors log * fix nit in log: WS -> HTTP * revert needless changes in Cargo.lock * remove unused features in tower * fix nits; add client-core feature * jsonrpsee v0.16.2 * `pallet-message-queue`: Fix license (#12895) * Fix license Signed-off-by: Oliver Tale-Yazdi * Add mock doc Signed-off-by: Oliver Tale-Yazdi Signed-off-by: Oliver Tale-Yazdi * Use explicit call indices (#12891) * frame-system: explicit call index Signed-off-by: Oliver Tale-Yazdi * Use explicit call indices Signed-off-by: Oliver Tale-Yazdi * pallet-template: explicit call index Signed-off-by: Oliver Tale-Yazdi * DNM: Temporarily require call_index Signed-off-by: Oliver Tale-Yazdi * Revert "DNM: Temporarily require call_index" This reverts commit c4934e312e12af72ca05a8029d7da753a9c99346. Signed-off-by: Oliver Tale-Yazdi * Pin canonincalized block (#12902) * Remove implicit approval chilling upon slash. (#12420) * don't read slashing spans when taking election snapshot * update cargo.toml * bring back remote test * fix merge stuff * fix npos-voters function sig * remove as much redundant diff as you can * Update frame/staking/src/pallet/mod.rs Co-authored-by: Andronik * fix * Update frame/staking/src/pallet/impls.rs * update lock * fix all tests * review comments * fmt * fix offence bench * clippy * ".git/.scripts/bench-bot.sh" pallet dev pallet_staking Co-authored-by: Andronik Co-authored-by: Ankan Co-authored-by: command-bot <> * bounties calls docs fix (#12909) Co-authored-by: parity-processbot <> * pallet-contracts migration pre-upgrade fix for v8 (#12905) * Only run pre-v8 migration check for versions older than 8 * Logix fix * use custom environment for publishing crates (#12912) * [contracts] Add debug buffer limit + enforcement (#12845) * Add debug buffer limit + enforcement Add debug buffer limit + enforcement * use BoundedVec for the debug buffer * revert schedule (debug buf len limit not needed anymore) * return DispatchError * addressed review comments * frame/remote-externalities: Fix clippy Signed-off-by: Alexandru Vasile * frame/rpc: Add previous export Signed-off-by: Alexandru Vasile * Fixup some wrong dependencies (#12899) * Fixup some wrong dependencies Dev dependencies should not appear in the feature list. If features are required, they should be directly enabled for the `dev-dependency`. * More fixups * Fix fix * Remove deprecated feature * Make all work properly and nice!! * FMT * Fix formatting * add numerator and denominator to Rational128 Debug impl and increase precision of float representation (#12914) * Fix state-db pinning (#12927) * Pin all canonicalized blocks * Added a test * Docs * [ci] add job switcher (#12922) * Use LOG_TARGET in consensus related crates (#12875) * Use shared LOG_TARGET in consensus related crates * Rename target from "afg" to "grandpa" Signed-off-by: Alexandru Vasile Signed-off-by: Oliver Tale-Yazdi Co-authored-by: Keith Yeung Co-authored-by: Vlad Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Co-authored-by: Bastian Köcher Co-authored-by: Anthony Alaribe Co-authored-by: Gavin Wood Co-authored-by: Oliver Tale-Yazdi Co-authored-by: Robert Klotzner Co-authored-by: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Co-authored-by: Aaro Altonen <48052676+altonen@users.noreply.github.com> Co-authored-by: tgmichel Co-authored-by: Ankan <10196091+Ank4n@users.noreply.github.com> Co-authored-by: Luke Schoen Co-authored-by: Niklas Adolfsson Co-authored-by: Arkadiy Paronyan Co-authored-by: Andronik Co-authored-by: Ankan Co-authored-by: Muharem Ismailov Co-authored-by: Dino Pačandi <3002868+Dinonard@users.noreply.github.com> Co-authored-by: João Paulo Silva de Souza <77391175+joao-paulo-parity@users.noreply.github.com> Co-authored-by: Sasha Gryaznov Co-authored-by: Alexandru Vasile Co-authored-by: Alexander Popiak Co-authored-by: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Co-authored-by: Davide Galassi * Revert "Lexnv/kiz revamp try runtime stuff (#12932)" This reverts commit 378cfb26d984bcde467781f07ef8ddb6998212cb. * fmt * update * fix publish Signed-off-by: Alexandru Vasile Signed-off-by: Oliver Tale-Yazdi Co-authored-by: Niklas Adolfsson Co-authored-by: Bastian Köcher Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Co-authored-by: Keith Yeung Co-authored-by: Vlad Co-authored-by: Anthony Alaribe Co-authored-by: Gavin Wood Co-authored-by: Oliver Tale-Yazdi Co-authored-by: Robert Klotzner Co-authored-by: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Co-authored-by: Aaro Altonen <48052676+altonen@users.noreply.github.com> Co-authored-by: tgmichel Co-authored-by: Ankan <10196091+Ank4n@users.noreply.github.com> Co-authored-by: Luke Schoen Co-authored-by: Arkadiy Paronyan Co-authored-by: Andronik Co-authored-by: Ankan Co-authored-by: Muharem Ismailov Co-authored-by: Dino Pačandi <3002868+Dinonard@users.noreply.github.com> Co-authored-by: João Paulo Silva de Souza <77391175+joao-paulo-parity@users.noreply.github.com> Co-authored-by: Sasha Gryaznov Co-authored-by: Alexander Popiak Co-authored-by: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Co-authored-by: Alexandru Vasile Co-authored-by: Davide Galassi --- Cargo.lock | 79 +- bin/node-template/node/Cargo.toml | 1 + bin/node-template/node/src/command.rs | 10 +- bin/node-template/runtime/src/lib.rs | 7 +- bin/node/cli/Cargo.toml | 1 + bin/node/cli/src/command.rs | 9 +- bin/node/runtime/src/lib.rs | 14 +- frame/bags-list/remote-tests/src/migration.rs | 2 +- frame/bags-list/remote-tests/src/snapshot.rs | 18 +- frame/bags-list/remote-tests/src/try_state.rs | 8 +- frame/executive/Cargo.toml | 2 +- frame/executive/src/lib.rs | 94 +- frame/staking/Cargo.toml | 1 - frame/staking/src/pallet/impls.rs | 11 +- frame/state-trie-migration/src/lib.rs | 9 +- .../procedural/src/pallet/expand/hooks.rs | 2 +- frame/support/src/dispatch.rs | 2 +- frame/support/src/traits/hooks.rs | 177 +-- frame/support/src/traits/try_runtime.rs | 2 + ...age_ensure_span_are_ok_on_wrong_gen.stderr | 4 +- ...re_span_are_ok_on_wrong_gen_unnamed.stderr | 4 +- frame/try-runtime/src/lib.rs | 19 +- primitives/runtime/Cargo.toml | 1 + .../src/generic/unchecked_extrinsic.rs | 16 + primitives/runtime/src/testing.rs | 8 + primitives/runtime/src/traits.rs | 22 + primitives/storage/src/lib.rs | 1 + utils/frame/remote-externalities/Cargo.toml | 4 +- utils/frame/remote-externalities/src/lib.rs | 1194 ++++++++++------- .../remote-externalities/test_data/proxy_test | Bin 0 -> 70206 bytes .../test_data/proxy_test.top | Bin 39 -> 0 bytes utils/frame/rpc/client/src/lib.rs | 1 + utils/frame/try-runtime/cli/Cargo.toml | 16 +- .../cli/src/commands/create_snapshot.rs | 78 ++ .../cli/src/commands/execute_block.rs | 197 +-- .../cli/src/commands/follow_chain.rs | 128 +- .../frame/try-runtime/cli/src/commands/mod.rs | 9 +- .../cli/src/commands/offchain_worker.rs | 112 +- .../cli/src/commands/on_runtime_upgrade.rs | 56 +- utils/frame/try-runtime/cli/src/lib.rs | 923 +++++++------ 40 files changed, 1779 insertions(+), 1463 deletions(-) create mode 100644 utils/frame/remote-externalities/test_data/proxy_test delete mode 100644 utils/frame/remote-externalities/test_data/proxy_test.top create mode 100644 utils/frame/try-runtime/cli/src/commands/create_snapshot.rs diff --git a/Cargo.lock b/Cargo.lock index ea34146f16..b840cadce3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -269,9 +269,9 @@ checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0" [[package]] name = "async-trait" -version = "0.1.58" +version = "0.1.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" +checksum = "31e6e93155431f3931513b243d371981bb2770112b370c82745a1d19d2f99364" dependencies = [ "proc-macro2", "quote", @@ -2031,6 +2031,7 @@ version = "0.10.0-dev" dependencies = [ "env_logger", "frame-support", + "futures", "log", "pallet-elections-phragmen", "parity-scale-codec", @@ -2042,6 +2043,7 @@ dependencies = [ "sp-version", "substrate-rpc-client", "tokio", + "tracing-subscriber 0.3.16", ] [[package]] @@ -2874,9 +2876,9 @@ checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" [[package]] name = "itertools" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] @@ -3788,6 +3790,15 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + [[package]] name = "matches" version = "0.1.8" @@ -4271,6 +4282,7 @@ dependencies = [ "sp-core", "sp-finality-grandpa", "sp-inherents", + "sp-io", "sp-keyring", "sp-keystore", "sp-runtime", @@ -4427,6 +4439,7 @@ dependencies = [ "sp-core", "sp-finality-grandpa", "sp-inherents", + "sp-io", "sp-keyring", "sp-runtime", "sp-timestamp", @@ -4529,6 +4542,16 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-bigint" version = "0.4.3" @@ -4667,6 +4690,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "pallet-alliance" version = "4.0.0-dev" @@ -7585,7 +7614,7 @@ dependencies = [ "substrate-test-runtime", "tempfile", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.2.25", "wasmi 0.13.0", "wat", ] @@ -8337,7 +8366,7 @@ dependencies = [ "thiserror", "tracing", "tracing-log", - "tracing-subscriber", + "tracing-subscriber 0.2.25", ] [[package]] @@ -8705,9 +8734,9 @@ dependencies = [ [[package]] name = "sharded-slab" -version = "0.1.1" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79c719719ee05df97490f80a45acfc99e5a30ce98a1e4fb67aee422745ae14e3" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" dependencies = [ "lazy_static", ] @@ -8763,9 +8792,9 @@ checksum = "03b634d87b960ab1a38c4fe143b508576f075e7c978bfad18217645ebfdfa2ec" [[package]] name = "smallvec" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "snap" @@ -9555,7 +9584,7 @@ dependencies = [ "sp-std", "tracing", "tracing-core", - "tracing-subscriber", + "tracing-subscriber 0.2.25", ] [[package]] @@ -10431,9 +10460,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.28" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" dependencies = [ "once_cell", "valuable", @@ -10479,7 +10508,7 @@ dependencies = [ "ansi_term", "chrono", "lazy_static", - "matchers", + "matchers 0.0.1", "parking_lot 0.11.2", "regex", "serde", @@ -10493,6 +10522,24 @@ dependencies = [ "tracing-serde", ] +[[package]] +name = "tracing-subscriber" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +dependencies = [ + "matchers 0.1.0", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + [[package]] name = "treeline" version = "0.1.0" @@ -10605,6 +10652,7 @@ dependencies = [ "clap 4.0.11", "frame-remote-externalities", "frame-try-runtime", + "hex", "log", "parity-scale-codec", "sc-chain-spec", @@ -10612,10 +10660,13 @@ dependencies = [ "sc-executor", "sc-service", "serde", + "sp-api", "sp-core", + "sp-debug-derive", "sp-externalities", "sp-io", "sp-keystore", + "sp-rpc", "sp-runtime", "sp-state-machine", "sp-version", diff --git a/bin/node-template/node/Cargo.toml b/bin/node-template/node/Cargo.toml index 2ea841093d..364cfa25d3 100644 --- a/bin/node-template/node/Cargo.toml +++ b/bin/node-template/node/Cargo.toml @@ -36,6 +36,7 @@ sc-finality-grandpa = { version = "0.10.0-dev", path = "../../../client/finality sp-finality-grandpa = { version = "4.0.0-dev", path = "../../../primitives/finality-grandpa" } sc-client-api = { version = "4.0.0-dev", path = "../../../client/api" } sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-io = { version = "7.0.0", path = "../../../primitives/io" } sp-timestamp = { version = "4.0.0-dev", path = "../../../primitives/timestamp" } sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } sp-keyring = { version = "7.0.0", path = "../../../primitives/keyring" } diff --git a/bin/node-template/node/src/command.rs b/bin/node-template/node/src/command.rs index 6d293b7b85..15cd69b34b 100644 --- a/bin/node-template/node/src/command.rs +++ b/bin/node-template/node/src/command.rs @@ -174,6 +174,8 @@ pub fn run() -> sc_cli::Result<()> { }, #[cfg(feature = "try-runtime")] Some(Subcommand::TryRuntime(cmd)) => { + use crate::service::ExecutorDispatch; + use sc_executor::{sp_wasm_interface::ExtendedHostFunctions, NativeExecutionDispatch}; let runner = cli.create_runner(cmd)?; runner.async_run(|config| { // we don't need any of the components of new_partial, just a runtime, or a task @@ -182,7 +184,13 @@ pub fn run() -> sc_cli::Result<()> { let task_manager = sc_service::TaskManager::new(config.tokio_handle.clone(), registry) .map_err(|e| sc_cli::Error::Service(sc_service::Error::Prometheus(e)))?; - Ok((cmd.run::(config), task_manager)) + Ok(( + cmd.run::::ExtendHostFunctions, + >>(), + task_manager, + )) }) }, #[cfg(not(feature = "try-runtime"))] diff --git a/bin/node-template/runtime/src/lib.rs b/bin/node-template/runtime/src/lib.rs index 938282c662..f76b2c449e 100644 --- a/bin/node-template/runtime/src/lib.rs +++ b/bin/node-template/runtime/src/lib.rs @@ -539,22 +539,23 @@ impl_runtime_apis! { #[cfg(feature = "try-runtime")] impl frame_try_runtime::TryRuntime for Runtime { - fn on_runtime_upgrade() -> (Weight, Weight) { + fn on_runtime_upgrade(checks: bool) -> (Weight, Weight) { // NOTE: intentional unwrap: we don't want to propagate the error backwards, and want to // have a backtrace here. If any of the pre/post migration checks fail, we shall stop // right here and right now. - let weight = Executive::try_runtime_upgrade().unwrap(); + let weight = Executive::try_runtime_upgrade(checks).unwrap(); (weight, BlockWeights::get().max_block) } fn execute_block( block: Block, state_root_check: bool, + signature_check: bool, select: frame_try_runtime::TryStateSelect ) -> Weight { // NOTE: intentional unwrap: we don't want to propagate the error backwards, and want to // have a backtrace here. - Executive::try_execute_block(block, state_root_check, select).expect("execute-block failed") + Executive::try_execute_block(block, state_root_check, signature_check, select).expect("execute-block failed") } } } diff --git a/bin/node/cli/Cargo.toml b/bin/node/cli/Cargo.toml index 4ee4bcd033..6b50115fd9 100644 --- a/bin/node/cli/Cargo.toml +++ b/bin/node/cli/Cargo.toml @@ -59,6 +59,7 @@ sp-keystore = { version = "0.13.0", path = "../../../primitives/keystore" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } sp-transaction-pool = { version = "4.0.0-dev", path = "../../../primitives/transaction-pool" } sp-transaction-storage-proof = { version = "4.0.0-dev", path = "../../../primitives/transaction-storage-proof" } +sp-io = { path = "../../../primitives/io" } # client dependencies sc-client-api = { version = "4.0.0-dev", path = "../../../client/api" } diff --git a/bin/node/cli/src/command.rs b/bin/node/cli/src/command.rs index 108d774384..fd464bbc91 100644 --- a/bin/node/cli/src/command.rs +++ b/bin/node/cli/src/command.rs @@ -227,6 +227,7 @@ pub fn run() -> Result<()> { }, #[cfg(feature = "try-runtime")] Some(Subcommand::TryRuntime(cmd)) => { + use sc_executor::{sp_wasm_interface::ExtendedHostFunctions, NativeExecutionDispatch}; let runner = cli.create_runner(cmd)?; runner.async_run(|config| { // we don't need any of the components of new_partial, just a runtime, or a task @@ -236,7 +237,13 @@ pub fn run() -> Result<()> { sc_service::TaskManager::new(config.tokio_handle.clone(), registry) .map_err(|e| sc_cli::Error::Service(sc_service::Error::Prometheus(e)))?; - Ok((cmd.run::(config), task_manager)) + Ok(( + cmd.run::::ExtendHostFunctions, + >>(), + task_manager, + )) }) }, #[cfg(not(feature = "try-runtime"))] diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 00d2a54d1e..0e3bee8821 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -2158,29 +2158,23 @@ impl_runtime_apis! { #[cfg(feature = "try-runtime")] impl frame_try_runtime::TryRuntime for Runtime { - fn on_runtime_upgrade() -> (Weight, Weight) { + fn on_runtime_upgrade(checks: bool) -> (Weight, Weight) { // NOTE: intentional unwrap: we don't want to propagate the error backwards, and want to // have a backtrace here. If any of the pre/post migration checks fail, we shall stop // right here and right now. - let weight = Executive::try_runtime_upgrade().unwrap(); + let weight = Executive::try_runtime_upgrade(checks).unwrap(); (weight, RuntimeBlockWeights::get().max_block) } fn execute_block( block: Block, state_root_check: bool, + signature_check: bool, select: frame_try_runtime::TryStateSelect ) -> Weight { - log::info!( - target: "node-runtime", - "try-runtime: executing block {:?} / root checks: {:?} / try-state-select: {:?}", - block.header.hash(), - state_root_check, - select, - ); // NOTE: intentional unwrap: we don't want to propagate the error backwards, and want to // have a backtrace here. - Executive::try_execute_block(block, state_root_check, select).unwrap() + Executive::try_execute_block(block, state_root_check, signature_check, select).unwrap() } } diff --git a/frame/bags-list/remote-tests/src/migration.rs b/frame/bags-list/remote-tests/src/migration.rs index b013472b4c..759906a4ef 100644 --- a/frame/bags-list/remote-tests/src/migration.rs +++ b/frame/bags-list/remote-tests/src/migration.rs @@ -30,7 +30,7 @@ pub async fn execute( ws_url: String, ) where Runtime: RuntimeT, - Block: BlockT, + Block: BlockT + DeserializeOwned, Block::Header: DeserializeOwned, { let mut ext = Builder::::new() diff --git a/frame/bags-list/remote-tests/src/snapshot.rs b/frame/bags-list/remote-tests/src/snapshot.rs index cfe065924b..0163ca200a 100644 --- a/frame/bags-list/remote-tests/src/snapshot.rs +++ b/frame/bags-list/remote-tests/src/snapshot.rs @@ -25,7 +25,7 @@ use sp_runtime::{traits::Block as BlockT, DeserializeOwned}; pub async fn execute(voter_limit: Option, currency_unit: u64, ws_url: String) where Runtime: crate::RuntimeT, - Block: BlockT, + Block: BlockT + DeserializeOwned, Block::Header: DeserializeOwned, { use frame_support::storage::generator::StorageMap; @@ -38,14 +38,18 @@ where pallets: vec![pallet_bags_list::Pallet::::name() .to_string()], at: None, + hashed_prefixes: vec![ + >::prefix_hash(), + >::prefix_hash(), + >::map_storage_final_prefix(), + >::map_storage_final_prefix(), + ], + hashed_keys: vec![ + >::counter_storage_final_key().to_vec(), + >::counter_storage_final_key().to_vec(), + ], ..Default::default() })) - .inject_hashed_prefix(&>::prefix_hash()) - .inject_hashed_prefix(&>::prefix_hash()) - .inject_hashed_prefix(&>::map_storage_final_prefix()) - .inject_hashed_prefix(&>::map_storage_final_prefix()) - .inject_hashed_key(&>::counter_storage_final_key()) - .inject_hashed_key(&>::counter_storage_final_key()) .build() .await .unwrap(); diff --git a/frame/bags-list/remote-tests/src/try_state.rs b/frame/bags-list/remote-tests/src/try_state.rs index d3fb63f045..514c80d72a 100644 --- a/frame/bags-list/remote-tests/src/try_state.rs +++ b/frame/bags-list/remote-tests/src/try_state.rs @@ -31,7 +31,7 @@ pub async fn execute( ws_url: String, ) where Runtime: crate::RuntimeT, - Block: BlockT, + Block: BlockT + DeserializeOwned, Block::Header: DeserializeOwned, { let mut ext = Builder::::new() @@ -39,10 +39,12 @@ pub async fn execute( transport: ws_url.to_string().into(), pallets: vec![pallet_bags_list::Pallet::::name() .to_string()], + hashed_prefixes: vec![ + >::prefix_hash(), + >::prefix_hash(), + ], ..Default::default() })) - .inject_hashed_prefix(&>::prefix_hash()) - .inject_hashed_prefix(&>::prefix_hash()) .build() .await .unwrap(); diff --git a/frame/executive/Cargo.toml b/frame/executive/Cargo.toml index b3e4247445..a6d16e9b07 100644 --- a/frame/executive/Cargo.toml +++ b/frame/executive/Cargo.toml @@ -49,4 +49,4 @@ std = [ "sp-std/std", "sp-tracing/std", ] -try-runtime = ["frame-support/try-runtime", "frame-try-runtime/try-runtime" ] +try-runtime = ["frame-support/try-runtime", "frame-try-runtime/try-runtime", "sp-runtime/try-runtime"] diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index 6f59ac72eb..1f20e93f43 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -227,27 +227,71 @@ where { /// Execute given block, but don't as strict is the normal block execution. /// - /// Some consensus related checks such as the state root check can be switched off via - /// `try_state_root`. Some additional non-consensus checks can be additionally enabled via - /// `try_state`. + /// Some checks can be disabled via: + /// + /// - `state_root_check` + /// - `signature_check` /// /// Should only be used for testing ONLY. pub fn try_execute_block( block: Block, - try_state_root: bool, + state_root_check: bool, + signature_check: bool, select: frame_try_runtime::TryStateSelect, - ) -> Result { - use frame_support::traits::TryState; + ) -> Result { + frame_support::log::info!( + target: "frame::executive", + "try-runtime: executing block #{:?} / state root check: {:?} / signature check: {:?} / try-state-select: {:?}", + block.header().number(), + state_root_check, + signature_check, + select, + ); Self::initialize_block(block.header()); Self::initial_checks(&block); let (header, extrinsics) = block.deconstruct(); - Self::execute_extrinsics_with_book_keeping(extrinsics, *header.number()); + let try_apply_extrinsic = |uxt: Block::Extrinsic| -> ApplyExtrinsicResult { + sp_io::init_tracing(); + let encoded = uxt.encode(); + let encoded_len = encoded.len(); + + // skip signature verification. + let xt = if signature_check { + uxt.check(&Default::default()) + } else { + uxt.unchecked_into_checked_i_know_what_i_am_doing(&Default::default()) + }?; + >::note_extrinsic(encoded); + + let dispatch_info = xt.get_dispatch_info(); + let r = Applyable::apply::(xt, &dispatch_info, encoded_len)?; - // run the try-state checks of all pallets. - >::try_state( + >::note_applied_extrinsic(&r, dispatch_info); + + Ok(r.map(|_| ()).map_err(|e| e.error)) + }; + + for e in extrinsics { + if let Err(err) = try_apply_extrinsic(e.clone()) { + frame_support::log::error!( + target: "runtime::executive", "executing transaction {:?} failed due to {:?}. Aborting the rest of the block execution.", + e, + err, + ); + break + } + } + + // post-extrinsics book-keeping + >::note_finished_extrinsics(); + Self::idle_and_finalize_hook(*header.number()); + + // run the try-state checks of all pallets, ensuring they don't alter any state. + let _guard = frame_support::StorageNoopGuard::default(); + >::try_state( *header.number(), select, ) @@ -255,6 +299,7 @@ where frame_support::log::error!(target: "runtime::executive", "failure: {:?}", e); e })?; + drop(_guard); // do some of the checks that would normally happen in `final_checks`, but perhaps skip // the state root check. @@ -266,7 +311,7 @@ where assert!(header_item == computed_item, "Digest item must match that calculated."); } - if try_state_root { + if state_root_check { let storage_root = new_header.state_root(); header.state_root().check_equal(storage_root); assert!( @@ -286,9 +331,30 @@ where /// Execute all `OnRuntimeUpgrade` of this runtime, including the pre and post migration checks. /// - /// This should only be used for testing. - pub fn try_runtime_upgrade() -> Result { - let weight = Self::execute_on_runtime_upgrade(); + /// Runs the try-state code both before and after the migration function if `checks` is set to + /// `true`. Also, if set to `true`, it runs the `pre_upgrade` and `post_upgrade` hooks. + pub fn try_runtime_upgrade(checks: bool) -> Result { + if checks { + let _guard = frame_support::StorageNoopGuard::default(); + >::try_state( + frame_system::Pallet::::block_number(), + frame_try_runtime::TryStateSelect::All, + )?; + } + + let weight = + <(COnRuntimeUpgrade, AllPalletsWithSystem) as OnRuntimeUpgrade>::try_on_runtime_upgrade( + checks, + )?; + + if checks { + let _guard = frame_support::StorageNoopGuard::default(); + >::try_state( + frame_system::Pallet::::block_number(), + frame_try_runtime::TryStateSelect::All, + )?; + } + Ok(weight) } } @@ -314,7 +380,7 @@ where UnsignedValidator: ValidateUnsigned>, { /// Execute all `OnRuntimeUpgrade` of this runtime, and return the aggregate weight. - pub fn execute_on_runtime_upgrade() -> frame_support::weights::Weight { + pub fn execute_on_runtime_upgrade() -> Weight { <(COnRuntimeUpgrade, AllPalletsWithSystem) as OnRuntimeUpgrade>::on_runtime_upgrade() } diff --git a/frame/staking/Cargo.toml b/frame/staking/Cargo.toml index a7fca045cc..f6b3b95d0b 100644 --- a/frame/staking/Cargo.toml +++ b/frame/staking/Cargo.toml @@ -62,7 +62,6 @@ std = [ "sp-runtime/std", "sp-staking/std", "pallet-session/std", - "pallet-bags-list/std", "frame-system/std", "pallet-authorship/std", "sp-application-crypto/std", diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 830b33ceb6..d3b9b6a2b1 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -1636,9 +1636,9 @@ impl Pallet { ensure!( T::VoterList::iter() .all(|x| >::contains_key(&x) || >::contains_key(&x)), - "VoterList contains non-nominators" + "VoterList contains non-staker" ); - T::VoterList::try_state()?; + Self::check_nominators()?; Self::check_exposures()?; Self::check_ledgers()?; @@ -1651,7 +1651,10 @@ impl Pallet { Nominators::::count() + Validators::::count(), "wrong external count" ); - + ensure!( + ::TargetList::count() == Validators::::count(), + "wrong external count" + ); ensure!( ValidatorCount::::get() <= ::MaxWinners::get(), @@ -1692,7 +1695,7 @@ impl Pallet { >::iter() .filter_map( |(nominator, nomination)| { - if nomination.submitted_in > era { + if nomination.submitted_in < era { Some(nominator) } else { None diff --git a/frame/state-trie-migration/src/lib.rs b/frame/state-trie-migration/src/lib.rs index 823ea08a0b..23f73bb56b 100644 --- a/frame/state-trie-migration/src/lib.rs +++ b/frame/state-trie-migration/src/lib.rs @@ -1619,7 +1619,10 @@ pub(crate) mod remote_tests { use frame_system::Pallet as System; use remote_externalities::Mode; use sp_core::H256; - use sp_runtime::traits::{Block as BlockT, HashFor, Header as _, One, Zero}; + use sp_runtime::{ + traits::{Block as BlockT, HashFor, Header as _, One, Zero}, + DeserializeOwned, + }; use thousands::Separable; #[allow(dead_code)] @@ -1648,12 +1651,12 @@ pub(crate) mod remote_tests { pub(crate) async fn run_with_limits(limits: MigrationLimits, mode: Mode) where Runtime: crate::Config, - Block: BlockT, + Block: BlockT + DeserializeOwned, Block::Header: serde::de::DeserializeOwned, { let mut ext = remote_externalities::Builder::::new() .mode(mode) - .state_version(sp_core::storage::StateVersion::V0) + .overwrite_state_version(sp_core::storage::StateVersion::V0) .build() .await .unwrap(); diff --git a/frame/support/procedural/src/pallet/expand/hooks.rs b/frame/support/procedural/src/pallet/expand/hooks.rs index d8d009cf3c..0aa7c1e7aa 100644 --- a/frame/support/procedural/src/pallet/expand/hooks.rs +++ b/frame/support/procedural/src/pallet/expand/hooks.rs @@ -50,7 +50,7 @@ pub fn expand_hooks(def: &mut Def) -> proc_macro2::TokenStream { } else { // default. quote::quote! { - #frame_support::log::info!( + #frame_support::log::debug!( target: #frame_support::LOG_TARGET, "✅ no migration for {}", pallet_name, diff --git a/frame/support/src/dispatch.rs b/frame/support/src/dispatch.rs index 1696e9a639..93cf08c131 100644 --- a/frame/support/src/dispatch.rs +++ b/frame/support/src/dispatch.rs @@ -2188,7 +2188,7 @@ macro_rules! decl_module { $system::Config >::PalletInfo as $crate::traits::PalletInfo>::name::().unwrap_or(""); - $crate::log::info!( + $crate::log::debug!( target: $crate::LOG_TARGET, "✅ no migration for {}", pallet_name, diff --git a/frame/support/src/traits/hooks.rs b/frame/support/src/traits/hooks.rs index 3415682c0b..3f7db1fa04 100644 --- a/frame/support/src/traits/hooks.rs +++ b/frame/support/src/traits/hooks.rs @@ -22,9 +22,6 @@ use impl_trait_for_tuples::impl_for_tuples; use sp_runtime::traits::AtLeast32BitUnsigned; use sp_std::prelude::*; -#[cfg(all(feature = "try-runtime", test))] -use codec::{Decode, Encode}; - /// The block initialization trait. /// /// Implementing this lets you express what should happen for your pallet when the block is @@ -136,6 +133,29 @@ pub trait OnRuntimeUpgrade { Weight::zero() } + /// Same as `on_runtime_upgrade`, but perform the optional `pre_upgrade` and `post_upgrade` as + /// well. + #[cfg(feature = "try-runtime")] + fn try_on_runtime_upgrade(checks: bool) -> Result { + let maybe_state = if checks { + let _guard = frame_support::StorageNoopGuard::default(); + let state = Self::pre_upgrade()?; + Some(state) + } else { + None + }; + + let weight = Self::on_runtime_upgrade(); + + if let Some(state) = maybe_state { + let _guard = frame_support::StorageNoopGuard::default(); + // we want to panic if any checks fail right here right now. + Self::post_upgrade(state)? + } + + Ok(weight) + } + /// Execute some pre-checks prior to a runtime upgrade. /// /// Return a `Vec` that can contain arbitrary encoded data (usually some pre-upgrade state), @@ -143,6 +163,9 @@ pub trait OnRuntimeUpgrade { /// should be returned if there is no such need. /// /// This hook is never meant to be executed on-chain but is meant to be used by testing tools. + /// + /// This hook must not write to any state, as it would make the main `on_runtime_upgrade` path + /// inaccurate. #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, &'static str> { Ok(Vec::new()) @@ -155,6 +178,9 @@ pub trait OnRuntimeUpgrade { /// be passed in, in such case `post_upgrade` should ignore it. /// /// This hook is never meant to be executed on-chain but is meant to be used by testing tools. + /// + /// This hook must not write to any state, as it would make the main `on_runtime_upgrade` path + /// inaccurate. #[cfg(feature = "try-runtime")] fn post_upgrade(_state: Vec) -> Result<(), &'static str> { Ok(()) @@ -165,7 +191,6 @@ pub trait OnRuntimeUpgrade { #[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))] #[cfg_attr(feature = "tuples-128", impl_for_tuples(128))] impl OnRuntimeUpgrade for Tuple { - #[cfg(not(feature = "try-runtime"))] fn on_runtime_upgrade() -> Weight { let mut weight = Weight::zero(); for_tuples!( #( weight = weight.saturating_add(Tuple::on_runtime_upgrade()); )* ); @@ -176,40 +201,10 @@ impl OnRuntimeUpgrade for Tuple { /// We are executing pre- and post-checks sequentially in order to be able to test several /// consecutive migrations for the same pallet without errors. Therefore pre and post upgrade /// hooks for tuples are a noop. - fn on_runtime_upgrade() -> Weight { - use scale_info::prelude::format; - + fn try_on_runtime_upgrade(checks: bool) -> Result { let mut weight = Weight::zero(); - // migration index in the tuple, start with 1 for better readability - let mut i = 1; - for_tuples!( #( - let _guard = frame_support::StorageNoopGuard::default(); - // we want to panic if any checks fail right here right now. - let state = Tuple::pre_upgrade().expect(&format!("PreUpgrade failed for migration #{}", i)); - drop(_guard); - - weight = weight.saturating_add(Tuple::on_runtime_upgrade()); - - let _guard = frame_support::StorageNoopGuard::default(); - // we want to panic if any checks fail right here right now. - Tuple::post_upgrade(state).expect(&format!("PostUpgrade failed for migration #{}", i)); - drop(_guard); - - i += 1; - )* ); - weight - } - - #[cfg(feature = "try-runtime")] - /// noop - fn pre_upgrade() -> Result, &'static str> { - Ok(Vec::new()) - } - - #[cfg(feature = "try-runtime")] - /// noop - fn post_upgrade(_state: Vec) -> Result<(), &'static str> { - Ok(()) + for_tuples!( #( weight = weight.saturating_add(Tuple::try_on_runtime_upgrade(checks)?); )* ); + Ok(weight) } } @@ -274,6 +269,8 @@ pub trait Hooks { /// /// It should focus on certain checks to ensure that the state is sensible. This is never /// executed in a consensus code-path, therefore it can consume as much weight as it needs. + /// + /// This hook should not alter any storage. #[cfg(feature = "try-runtime")] fn try_state(_n: BlockNumber) -> Result<(), &'static str> { Ok(()) @@ -440,110 +437,4 @@ mod tests { ON_IDLE_INVOCATION_ORDER.clear(); } } - - #[cfg(feature = "try-runtime")] - #[test] - #[allow(dead_code)] - fn on_runtime_upgrade_tuple() { - use frame_support::parameter_types; - use sp_io::TestExternalities; - - struct Test1; - struct Test2; - struct Test3; - - parameter_types! { - static Test1Assertions: u8 = 0; - static Test2Assertions: u8 = 0; - static Test3Assertions: u8 = 0; - static EnableSequentialTest: bool = false; - static SequentialAssertions: u8 = 0; - } - - impl OnRuntimeUpgrade for Test1 { - fn pre_upgrade() -> Result, &'static str> { - Ok("Test1".encode()) - } - fn post_upgrade(state: Vec) -> Result<(), &'static str> { - let s: String = Decode::decode(&mut state.as_slice()).unwrap(); - Test1Assertions::mutate(|val| *val += 1); - if EnableSequentialTest::get() { - SequentialAssertions::mutate(|val| *val += 1); - } - assert_eq!(s, "Test1"); - Ok(()) - } - } - - impl OnRuntimeUpgrade for Test2 { - fn pre_upgrade() -> Result, &'static str> { - Ok(100u32.encode()) - } - fn post_upgrade(state: Vec) -> Result<(), &'static str> { - let s: u32 = Decode::decode(&mut state.as_slice()).unwrap(); - Test2Assertions::mutate(|val| *val += 1); - if EnableSequentialTest::get() { - assert_eq!(SequentialAssertions::get(), 1); - SequentialAssertions::mutate(|val| *val += 1); - } - assert_eq!(s, 100); - Ok(()) - } - } - - impl OnRuntimeUpgrade for Test3 { - fn pre_upgrade() -> Result, &'static str> { - Ok(true.encode()) - } - fn post_upgrade(state: Vec) -> Result<(), &'static str> { - let s: bool = Decode::decode(&mut state.as_slice()).unwrap(); - Test3Assertions::mutate(|val| *val += 1); - if EnableSequentialTest::get() { - assert_eq!(SequentialAssertions::get(), 2); - SequentialAssertions::mutate(|val| *val += 1); - } - assert_eq!(s, true); - Ok(()) - } - } - - TestExternalities::default().execute_with(|| { - type TestEmpty = (); - let origin_state = ::pre_upgrade().unwrap(); - assert!(origin_state.is_empty()); - ::post_upgrade(origin_state).unwrap(); - - type Test1Tuple = (Test1,); - let origin_state = ::pre_upgrade().unwrap(); - assert!(origin_state.is_empty()); - ::post_upgrade(origin_state).unwrap(); - assert_eq!(Test1Assertions::get(), 0); - ::on_runtime_upgrade(); - assert_eq!(Test1Assertions::take(), 1); - - type Test321 = (Test3, Test2, Test1); - ::on_runtime_upgrade(); - assert_eq!(Test1Assertions::take(), 1); - assert_eq!(Test2Assertions::take(), 1); - assert_eq!(Test3Assertions::take(), 1); - - // enable sequential tests - EnableSequentialTest::mutate(|val| *val = true); - - type Test123 = (Test1, Test2, Test3); - ::on_runtime_upgrade(); - assert_eq!(Test1Assertions::take(), 1); - assert_eq!(Test2Assertions::take(), 1); - assert_eq!(Test3Assertions::take(), 1); - - // reset assertions - SequentialAssertions::take(); - - type TestNested123 = (Test1, (Test2, Test3)); - ::on_runtime_upgrade(); - assert_eq!(Test1Assertions::take(), 1); - assert_eq!(Test2Assertions::take(), 1); - assert_eq!(Test3Assertions::take(), 1); - }); - } } diff --git a/frame/support/src/traits/try_runtime.rs b/frame/support/src/traits/try_runtime.rs index 640bb566a6..f741ca56a5 100644 --- a/frame/support/src/traits/try_runtime.rs +++ b/frame/support/src/traits/try_runtime.rs @@ -85,6 +85,8 @@ impl sp_std::str::FromStr for Select { /// /// Usually, these checks should check all of the invariants that are expected to be held on all of /// the storage items of your pallet. +/// +/// This hook should not alter any storage. pub trait TryState { /// Execute the state checks. fn try_state(_: BlockNumber, _: Select) -> Result<(), &'static str>; diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr index 999d8585c2..a3af9897be 100644 --- a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr @@ -28,7 +28,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 279 others + and 280 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` @@ -103,7 +103,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 279 others + and 280 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr index e2870ffb9e..9e87f87825 100644 --- a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr @@ -28,7 +28,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 279 others + and 280 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` @@ -103,7 +103,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 279 others + and 280 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` diff --git a/frame/try-runtime/src/lib.rs b/frame/try-runtime/src/lib.rs index ed1247bd8e..99c68d4dc6 100644 --- a/frame/try-runtime/src/lib.rs +++ b/frame/try-runtime/src/lib.rs @@ -33,12 +33,21 @@ sp_api::decl_runtime_apis! { /// /// Returns the consumed weight of the migration in case of a successful one, combined with /// the total allowed block weight of the runtime. - fn on_runtime_upgrade() -> (Weight, Weight); + /// + /// If `checks` is `true`, `pre_migrate` and `post_migrate` of each migration and + /// `try_state` of all pallets will be executed. Else, no. If checks are executed, the PoV + /// tracking is likely inaccurate. + fn on_runtime_upgrade(checks: bool) -> (Weight, Weight); - /// Execute the given block, but don't check that its state root matches that of yours. + /// Execute the given block, but optionally disable state-root and signature checks. /// - /// This is only sensible where the incoming block is from a different network, yet it has - /// the same block format as the runtime implementing this API. - fn execute_block(block: Block, state_root_check: bool, try_state: TryStateSelect) -> Weight; + /// Optionally, a number of `try_state` hooks can also be executed after the block + /// execution. + fn execute_block( + block: Block, + state_root_check: bool, + signature_check: bool, + try_state: TryStateSelect, + ) -> Weight; } } diff --git a/primitives/runtime/Cargo.toml b/primitives/runtime/Cargo.toml index 8ea6ed3eb3..4202110cd6 100644 --- a/primitives/runtime/Cargo.toml +++ b/primitives/runtime/Cargo.toml @@ -41,6 +41,7 @@ substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/ru [features] runtime-benchmarks = [] +try-runtime = [] default = ["std"] std = [ "codec/std", diff --git a/primitives/runtime/src/generic/unchecked_extrinsic.rs b/primitives/runtime/src/generic/unchecked_extrinsic.rs index 5d378410e4..bab4411167 100644 --- a/primitives/runtime/src/generic/unchecked_extrinsic.rs +++ b/primitives/runtime/src/generic/unchecked_extrinsic.rs @@ -149,6 +149,22 @@ where None => CheckedExtrinsic { signed: None, function: self.function }, }) } + + #[cfg(feature = "try-runtime")] + fn unchecked_into_checked_i_know_what_i_am_doing( + self, + lookup: &Lookup, + ) -> Result { + Ok(match self.signature { + Some((signed, _, extra)) => { + let signed = lookup.lookup(signed)?; + let raw_payload = SignedPayload::new(self.function, extra)?; + let (function, extra, _) = raw_payload.deconstruct(); + CheckedExtrinsic { signed: Some((signed, extra)), function } + }, + None => CheckedExtrinsic { signed: None, function: self.function }, + }) + } } impl ExtrinsicMetadata diff --git a/primitives/runtime/src/testing.rs b/primitives/runtime/src/testing.rs index 0cd78ba626..81762b3fc4 100644 --- a/primitives/runtime/src/testing.rs +++ b/primitives/runtime/src/testing.rs @@ -326,6 +326,14 @@ impl Checkable for TestXt Result { Ok(self) } + + #[cfg(feature = "try-runtime")] + fn unchecked_into_checked_i_know_what_i_am_doing( + self, + _: &Context, + ) -> Result { + unreachable!() + } } impl traits::Extrinsic for TestXt { diff --git a/primitives/runtime/src/traits.rs b/primitives/runtime/src/traits.rs index c69f8616b4..375475141b 100644 --- a/primitives/runtime/src/traits.rs +++ b/primitives/runtime/src/traits.rs @@ -990,6 +990,20 @@ pub trait Checkable: Sized { /// Check self, given an instance of Context. fn check(self, c: &Context) -> Result; + + /// Blindly check self. + /// + /// ## WARNING + /// + /// DO NOT USE IN PRODUCTION. This is only meant to be used in testing environments. A runtime + /// compiled with `try-runtime` should never be in production. Moreover, the name of this + /// function is deliberately chosen to prevent developers from ever calling it in consensus + /// code-paths. + #[cfg(feature = "try-runtime")] + fn unchecked_into_checked_i_know_what_i_am_doing( + self, + c: &Context, + ) -> Result; } /// A "checkable" piece of information, used by the standard Substrate Executive in order to @@ -1011,6 +1025,14 @@ impl Checkable for T { fn check(self, _c: &Context) -> Result { BlindCheckable::check(self) } + + #[cfg(feature = "try-runtime")] + fn unchecked_into_checked_i_know_what_i_am_doing( + self, + _: &Context, + ) -> Result { + unreachable!(); + } } /// A lazy call (module function and argument values) that can be executed via its `dispatch` diff --git a/primitives/storage/src/lib.rs b/primitives/storage/src/lib.rs index 79c1012196..237787710a 100644 --- a/primitives/storage/src/lib.rs +++ b/primitives/storage/src/lib.rs @@ -407,6 +407,7 @@ impl ChildTrieParentKeyId { /// V0 and V1 uses a same trie implementation, but V1 will write external value node in the trie for /// value with size at least `TRIE_VALUE_NODE_THRESHOLD`. #[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[cfg_attr(feature = "std", derive(Encode, Decode))] pub enum StateVersion { /// Old state version, no value nodes. V0 = 0, diff --git a/utils/frame/remote-externalities/Cargo.toml b/utils/frame/remote-externalities/Cargo.toml index 4cb847867f..ad8230fe29 100644 --- a/utils/frame/remote-externalities/Cargo.toml +++ b/utils/frame/remote-externalities/Cargo.toml @@ -22,12 +22,14 @@ sp-core = { version = "7.0.0", path = "../../../primitives/core" } sp-io = { version = "7.0.0", path = "../../../primitives/io" } sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } sp-version = { version = "5.0.0", path = "../../../primitives/version" } +tokio = { version = "1.22.0", features = ["macros", "rt-multi-thread"] } substrate-rpc-client = { path = "../rpc/client" } +futures = "0.3" [dev-dependencies] -tokio = { version = "1.22.0", features = ["macros", "rt-multi-thread"] } frame-support = { version = "4.0.0-dev", path = "../../../frame/support" } pallet-elections-phragmen = { version = "5.0.0-dev", path = "../../../frame/elections-phragmen" } +tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } [features] remote-test = ["frame-support"] diff --git a/utils/frame/remote-externalities/src/lib.rs b/utils/frame/remote-externalities/src/lib.rs index db062e246c..fb63b42751 100644 --- a/utils/frame/remote-externalities/src/lib.rs +++ b/utils/frame/remote-externalities/src/lib.rs @@ -21,7 +21,7 @@ //! based chain, or a local state snapshot file. use codec::{Decode, Encode}; - +use futures::{channel::mpsc, stream::StreamExt}; use log::*; use serde::de::DeserializeOwned; use sp_core::{ @@ -36,8 +36,11 @@ pub use sp_io::TestExternalities; use sp_runtime::{traits::Block as BlockT, StateVersion}; use std::{ fs, + num::NonZeroUsize, + ops::{Deref, DerefMut}, path::{Path, PathBuf}, sync::Arc, + thread, }; use substrate_rpc_client::{ rpc_params, ws_client, BatchRequestBuilder, ChainApi, ClientT, StateApi, WsClient, @@ -48,18 +51,49 @@ type TopKeyValues = Vec; type ChildKeyValues = Vec<(ChildInfo, Vec)>; const LOG_TARGET: &str = "remote-ext"; -const DEFAULT_TARGET: &str = "wss://rpc.polkadot.io:443"; -const BATCH_SIZE: usize = 1000; -const PAGE: u32 = 1000; +const DEFAULT_WS_ENDPOINT: &str = "wss://rpc.polkadot.io:443"; +const DEFAULT_VALUE_DOWNLOAD_BATCH: usize = 4096; +// NOTE: increasing this value does not seem to impact speed all that much. +const DEFAULT_KEY_DOWNLOAD_PAGE: u32 = 1000; +/// The snapshot that we store on disk. +#[derive(Decode, Encode)] +struct Snapshot { + state_version: StateVersion, + block_hash: B::Hash, + top: TopKeyValues, + child: ChildKeyValues, +} + +/// An externalities that acts exactly the same as [`sp_io::TestExternalities`] but has a few extra +/// bits and pieces to it, and can be loaded remotely. +pub struct RemoteExternalities { + /// The inner externalities. + pub inner_ext: TestExternalities, + /// The block hash it which we created this externality env. + pub block_hash: B::Hash, +} + +impl Deref for RemoteExternalities { + type Target = TestExternalities; + fn deref(&self) -> &Self::Target { + &self.inner_ext + } +} + +impl DerefMut for RemoteExternalities { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner_ext + } +} /// The execution mode. #[derive(Clone)] pub enum Mode { - /// Online. Potentially writes to a cache file. + /// Online. Potentially writes to a snapshot file. Online(OnlineConfig), /// Offline. Uses a state snapshot file and needs not any client config. Offline(OfflineConfig), - /// Prefer using a cache file if it exists, else use a remote server. + /// Prefer using a snapshot file if it exists, else use a remote server. OfflineOrElseOnline(OfflineConfig, OnlineConfig), } @@ -95,6 +129,13 @@ impl Transport { } } + fn as_client_cloned(&self) -> Option> { + match self { + Self::RemoteClient(client) => Some(client.clone()), + _ => None, + } + } + // Open a new WebSocket connection if it's not connected. async fn map_uri(&mut self) -> Result<(), &'static str> { if let Self::Uri(uri) = self { @@ -134,38 +175,56 @@ pub struct OnlineConfig { pub at: Option, /// An optional state snapshot file to WRITE to, not for reading. Not written if set to `None`. pub state_snapshot: Option, - /// The pallets to scrape. If empty, entire chain state will be scraped. + /// The pallets to scrape. These values are hashed and added to `hashed_prefix`. pub pallets: Vec, /// Transport config. pub transport: Transport, /// Lookout for child-keys, and scrape them as well if set to true. - pub scrape_children: bool, + pub child_trie: bool, + /// Storage entry key prefixes to be injected into the externalities. The *hashed* prefix must + /// be given. + pub hashed_prefixes: Vec>, + /// Storage entry keys to be injected into the externalities. The *hashed* key must be given. + pub hashed_keys: Vec>, } impl OnlineConfig { - /// Return rpc (ws) client. + /// Return rpc (ws) client reference. fn rpc_client(&self) -> &WsClient { self.transport .as_client() .expect("ws client must have been initialized by now; qed.") } + + /// Return a cloned rpc (ws) client, suitable for being moved to threads. + fn rpc_client_cloned(&self) -> Arc { + self.transport + .as_client_cloned() + .expect("ws client must have been initialized by now; qed.") + } + + fn at_expected(&self) -> B::Hash { + self.at.expect("block at must be initialized; qed") + } } impl Default for OnlineConfig { fn default() -> Self { Self { - transport: Transport::Uri(DEFAULT_TARGET.to_owned()), + transport: Transport::from(DEFAULT_WS_ENDPOINT.to_owned()), + child_trie: true, at: None, state_snapshot: None, - pallets: vec![], - scrape_children: true, + pallets: Default::default(), + hashed_keys: Default::default(), + hashed_prefixes: Default::default(), } } } impl From for OnlineConfig { - fn from(s: String) -> Self { - Self { transport: s.into(), ..Default::default() } + fn from(t: String) -> Self { + Self { transport: t.into(), ..Default::default() } } } @@ -196,20 +255,18 @@ impl Default for SnapshotConfig { /// Builder for remote-externalities. pub struct Builder { - /// Custom key-pairs to be injected into the externalities. The *hashed* keys and values must - /// be given. + /// Custom key-pairs to be injected into the final externalities. The *hashed* keys and values + /// must be given. hashed_key_values: Vec, - /// Storage entry key prefixes to be injected into the externalities. The *hashed* prefix must - /// be given. - hashed_prefixes: Vec>, - /// Storage entry keys to be injected into the externalities. The *hashed* key must be given. - hashed_keys: Vec>, /// The keys that will be excluded from the final externality. The *hashed* key must be given. hashed_blacklist: Vec>, - /// connectivity mode, online or offline. + /// Connectivity mode, online or offline. mode: Mode, - /// The state version being used. - state_version: StateVersion, + /// If provided, overwrite the state version with this. Otherwise, the state_version of the + /// remote node is used. All cache files also store their state version. + /// + /// Overwrite only with care. + overwrite_state_version: Option, } // NOTE: ideally we would use `DefaultNoBound` here, but not worth bringing in frame-support for @@ -219,10 +276,8 @@ impl Default for Builder { Self { mode: Default::default(), hashed_key_values: Default::default(), - hashed_prefixes: Default::default(), - hashed_keys: Default::default(), hashed_blacklist: Default::default(), - state_version: StateVersion::V1, + overwrite_state_version: None, } } } @@ -252,20 +307,22 @@ where B::Hash: DeserializeOwned, B::Header: DeserializeOwned, { + /// Get the number of threads to use. + fn threads() -> NonZeroUsize { + thread::available_parallelism() + .unwrap_or(NonZeroUsize::new(4usize).expect("4 is non-zero; qed")) + } + async fn rpc_get_storage( &self, key: StorageKey, maybe_at: Option, - ) -> Result { + ) -> Result, &'static str> { trace!(target: LOG_TARGET, "rpc: get_storage"); - match self.as_online().rpc_client().storage(key, maybe_at).await { - Ok(Some(res)) => Ok(res), - Ok(None) => Err("get_storage not found"), - Err(e) => { - error!(target: LOG_TARGET, "Error = {:?}", e); - Err("rpc get_storage failed.") - }, - } + self.as_online().rpc_client().storage(key, maybe_at).await.map_err(|e| { + error!(target: LOG_TARGET, "Error = {:?}", e); + "rpc get_storage failed." + }) } /// Get the latest finalized head. @@ -293,7 +350,12 @@ where let page = self .as_online() .rpc_client() - .storage_keys_paged(Some(prefix.clone()), PAGE, last_key.clone(), Some(at)) + .storage_keys_paged( + Some(prefix.clone()), + DEFAULT_KEY_DOWNLOAD_PAGE, + last_key.clone(), + Some(at), + ) .await .map_err(|e| { error!(target: LOG_TARGET, "Error = {:?}", e); @@ -303,7 +365,7 @@ where all_keys.extend(page); - if page_len < PAGE as usize { + if page_len < DEFAULT_KEY_DOWNLOAD_PAGE as usize { log::debug!(target: LOG_TARGET, "last page received: {}", page_len); break all_keys } else { @@ -322,7 +384,7 @@ where Ok(keys) } - /// Synonym of `rpc_get_pairs_unsafe` that uses paged queries to first get the keys, and then + /// Synonym of `getPairs` that uses paged queries to first get the keys, and then /// map them to values one by one. /// /// This can work with public nodes. But, expect it to be darn slow. @@ -330,79 +392,169 @@ where &self, prefix: StorageKey, at: B::Hash, + pending_ext: &mut TestExternalities, ) -> Result, &'static str> { - let keys = self.rpc_get_keys_paged(prefix, at).await?; - let keys_count = keys.len(); - log::debug!(target: LOG_TARGET, "Querying a total of {} keys", keys.len()); + let keys = self.rpc_get_keys_paged(prefix.clone(), at).await?; + if keys.is_empty() { + return Ok(Default::default()) + } - let mut key_values: Vec = vec![]; - let mut batch_success = true; + let client = self.as_online().rpc_client_cloned(); + let threads = Self::threads().get(); + let thread_chunk_size = (keys.len() + threads - 1) / threads; - let client = self.as_online().rpc_client(); - for chunk_keys in keys.chunks(BATCH_SIZE) { - let mut batch = BatchRequestBuilder::new(); + log::info!( + target: LOG_TARGET, + "Querying a total of {} keys from prefix {:?}, splitting among {} threads, {} keys per thread", + keys.len(), + HexDisplay::from(&prefix), + threads, + thread_chunk_size, + ); - for key in chunk_keys.iter() { - batch - .insert("state_getStorage", rpc_params![key, at]) - .map_err(|_| "Invalid batch params")?; - } + let mut handles = Vec::new(); + let keys_chunked: Vec> = + keys.chunks(thread_chunk_size).map(|s| s.into()).collect::>(); + + enum Message { + /// This thread completed the assigned work. + Terminated, + /// The thread produced the following batch response. + Batch(Vec<(Vec, Vec)>), + /// A request from the batch failed. + BatchFailed(String), + } - let batch_response = - client.batch_request::>(batch).await.map_err(|e| { - log::error!( - target: LOG_TARGET, - "failed to execute batch: {:?}. Error: {:?}", - chunk_keys.iter().map(HexDisplay::from).collect::>(), - e - ); - "batch failed." - })?; + let (tx, mut rx) = mpsc::unbounded::(); + + for thread_keys in keys_chunked { + let thread_client = client.clone(); + let thread_sender = tx.clone(); + let handle = std::thread::spawn(move || { + let rt = tokio::runtime::Runtime::new().unwrap(); + let mut thread_key_values = Vec::with_capacity(thread_keys.len()); + + for chunk_keys in thread_keys.chunks(DEFAULT_VALUE_DOWNLOAD_BATCH) { + let mut batch = BatchRequestBuilder::new(); + + for key in chunk_keys.iter() { + batch + .insert("state_getStorage", rpc_params![key, at]) + .map_err(|_| "Invalid batch params") + .unwrap(); + } + + let batch_response = rt + .block_on(thread_client.batch_request::>(batch)) + .map_err(|e| { + log::error!( + target: LOG_TARGET, + "failed to execute batch: {:?}. Error: {:?}", + chunk_keys.iter().map(HexDisplay::from).collect::>(), + e + ); + "batch failed." + }) + .unwrap(); + + // Check if we got responses for all submitted requests. + assert_eq!(chunk_keys.len(), batch_response.len()); + + let mut batch_kv = Vec::with_capacity(chunk_keys.len()); + for (key, maybe_value) in chunk_keys.into_iter().zip(batch_response) { + match maybe_value { + Ok(Some(data)) => { + thread_key_values.push((key.clone(), data.clone())); + batch_kv.push((key.clone().0, data.0)); + }, + Ok(None) => { + log::warn!( + target: LOG_TARGET, + "key {:?} had none corresponding value.", + &key + ); + let data = StorageData(vec![]); + thread_key_values.push((key.clone(), data.clone())); + batch_kv.push((key.clone().0, data.0)); + }, + Err(e) => { + let reason = format!("key {:?} failed: {:?}", &key, e); + log::error!(target: LOG_TARGET, "Reason: {}", reason); + // Signal failures to the main thread, stop aggregating (key, value) + // pairs and return immediately an error. + thread_sender.unbounded_send(Message::BatchFailed(reason)).unwrap(); + return Default::default() + }, + }; + + if thread_key_values.len() % (thread_keys.len() / 10).max(1) == 0 { + let ratio: f64 = + thread_key_values.len() as f64 / thread_keys.len() as f64; + log::debug!( + target: LOG_TARGET, + "[thread = {:?}] progress = {:.2} [{} / {}]", + std::thread::current().id(), + ratio, + thread_key_values.len(), + thread_keys.len(), + ); + } + } + + // Send this batch to the main thread to start inserting. + thread_sender.unbounded_send(Message::Batch(batch_kv)).unwrap(); + } - assert_eq!(chunk_keys.len(), batch_response.len()); + thread_sender.unbounded_send(Message::Terminated).unwrap(); + thread_key_values + }); - for (key, maybe_value) in chunk_keys.into_iter().zip(batch_response) { - match maybe_value { - Ok(Some(v)) => { - key_values.push((key.clone(), v)); - }, - Ok(None) => { - log::warn!( - target: LOG_TARGET, - "key {:?} had none corresponding value.", - &key - ); - key_values.push((key.clone(), StorageData(vec![]))); - }, - Err(e) => { - log::error!(target: LOG_TARGET, "key {:?} failed: {:?}", &key, e); - batch_success = false; - }, - }; + handles.push(handle); + } - if key_values.len() % (10 * BATCH_SIZE) == 0 { - let ratio: f64 = key_values.len() as f64 / keys_count as f64; - log::debug!( - target: LOG_TARGET, - "progress = {:.2} [{} / {}]", - ratio, - key_values.len(), - keys_count, - ); - } + // first, wait until all threads send a `Terminated` message, in the meantime populate + // `pending_ext`. + let mut terminated = 0usize; + let mut batch_failed = false; + loop { + match rx.next().await.unwrap() { + Message::Batch(kv) => { + for (k, v) in kv { + // skip writing the child root data. + if is_default_child_storage_key(k.as_ref()) { + continue + } + pending_ext.insert(k, v); + } + }, + Message::BatchFailed(error) => { + log::error!(target: LOG_TARGET, "Batch processing failed: {:?}", error); + batch_failed = true; + break + }, + Message::Terminated => { + terminated += 1; + if terminated == handles.len() { + break + } + }, } } - if batch_success { - Ok(key_values) - } else { - Err("batch failed.") + // Ensure all threads finished execution before returning. + let keys_and_values = + handles.into_iter().flat_map(|h| h.join().unwrap()).collect::>(); + + if batch_failed { + return Err("Batch failed.") } + + Ok(keys_and_values) } /// Get the values corresponding to `child_keys` at the given `prefixed_top_key`. pub(crate) async fn rpc_child_get_storage_paged( - &self, + client: &WsClient, prefixed_top_key: &StorageKey, child_keys: Vec, at: B::Hash, @@ -410,7 +562,7 @@ where let mut child_kv_inner = vec![]; let mut batch_success = true; - for batch_child_key in child_keys.chunks(BATCH_SIZE) { + for batch_child_key in child_keys.chunks(DEFAULT_VALUE_DOWNLOAD_BATCH) { let mut batch_request = BatchRequestBuilder::new(); for key in batch_child_key { @@ -426,12 +578,8 @@ where .map_err(|_| "Invalid batch params")?; } - let batch_response = self - .as_online() - .rpc_client() - .batch_request::>(batch_request) - .await - .map_err(|e| { + let batch_response = + client.batch_request::>(batch_request).await.map_err(|e| { log::error!( target: LOG_TARGET, "failed to execute batch: {:?}. Error: {:?}", @@ -472,7 +620,7 @@ where } pub(crate) async fn rpc_child_get_keys( - &self, + client: &WsClient, prefixed_top_key: &StorageKey, child_prefix: StorageKey, at: B::Hash, @@ -480,7 +628,7 @@ where // This is deprecated and will generate a warning which causes the CI to fail. #[allow(warnings)] let child_keys = substrate_rpc_client::ChildStateApi::storage_keys( - self.as_online().rpc_client(), + client, PrefixedStorageKey::new(prefixed_top_key.as_ref().to_vec()), child_prefix, Some(at), @@ -493,7 +641,8 @@ where debug!( target: LOG_TARGET, - "scraped {} child-keys of the child-bearing top key: {}", + "[thread = {:?}] scraped {} child-keys of the child-bearing top key: {}", + std::thread::current().id(), child_keys.len(), HexDisplay::from(prefixed_top_key) ); @@ -502,214 +651,341 @@ where } } -// Internal methods -impl Builder +impl Builder where B::Hash: DeserializeOwned, B::Header: DeserializeOwned, { - /// Save the given data to the top keys snapshot. - fn save_top_snapshot(&self, data: &[KeyValue], path: &PathBuf) -> Result<(), &'static str> { - let mut path = path.clone(); - let encoded = data.encode(); - path.set_extension("top"); - debug!( - target: LOG_TARGET, - "writing {} bytes to state snapshot file {:?}", - encoded.len(), - path - ); - fs::write(path, encoded).map_err(|_| "fs::write failed.")?; - Ok(()) - } - - /// Save the given data to the child keys snapshot. - fn save_child_snapshot( - &self, - data: &ChildKeyValues, - path: &PathBuf, - ) -> Result<(), &'static str> { - let mut path = path.clone(); - path.set_extension("child"); - let encoded = data.encode(); - debug!( - target: LOG_TARGET, - "writing {} bytes to state snapshot file {:?}", - encoded.len(), - path - ); - fs::write(path, encoded).map_err(|_| "fs::write failed.")?; - Ok(()) - } - - fn load_top_snapshot(&self, path: &PathBuf) -> Result { - let mut path = path.clone(); - path.set_extension("top"); - info!(target: LOG_TARGET, "loading top key-pairs from snapshot {:?}", path); - let bytes = fs::read(path).map_err(|_| "fs::read failed.")?; - Decode::decode(&mut &*bytes).map_err(|e| { - log::error!(target: LOG_TARGET, "{:?}", e); - "decode failed" - }) - } - - fn load_child_snapshot(&self, path: &PathBuf) -> Result { - let mut path = path.clone(); - path.set_extension("child"); - info!(target: LOG_TARGET, "loading child key-pairs from snapshot {:?}", path); - let bytes = fs::read(path).map_err(|_| "fs::read failed.")?; - Decode::decode(&mut &*bytes).map_err(|e| { - log::error!(target: LOG_TARGET, "{:?}", e); - "decode failed" - }) - } - - /// Load all the `top` keys from the remote config, and maybe write then to cache. - async fn load_top_remote_and_maybe_save(&self) -> Result { - let top_kv = self.load_top_remote().await?; - if let Some(c) = &self.as_online().state_snapshot { - self.save_top_snapshot(&top_kv, &c.path)?; - } - Ok(top_kv) - } - /// Load all of the child keys from the remote config, given the already scraped list of top key /// pairs. /// - /// Stores all values to cache as well, if provided. - async fn load_child_remote_and_maybe_save( + /// `top_kv` need not be only child-bearing top keys. It should be all of the top keys that are + /// included thus far. + /// + /// This function concurrently populates `pending_ext`. the return value is only for writing to + /// cache, we can also optimize further. + async fn load_child_remote( &self, top_kv: &[KeyValue], + pending_ext: &mut TestExternalities, ) -> Result { - let child_kv = self.load_child_remote(top_kv).await?; - if let Some(c) = &self.as_online().state_snapshot { - self.save_child_snapshot(&child_kv, &c.path)?; - } - Ok(child_kv) - } - - /// Load all of the child keys from the remote config, given the already scraped list of top key - /// pairs. - /// - /// `top_kv` need not be only child-bearing top keys. It should be all of the top keys that are - /// included thus far. - async fn load_child_remote(&self, top_kv: &[KeyValue]) -> Result { let child_roots = top_kv - .iter() - .filter_map(|(k, _)| is_default_child_storage_key(k.as_ref()).then(|| k)) + .into_iter() + .filter_map(|(k, _)| is_default_child_storage_key(k.as_ref()).then(|| k.clone())) .collect::>(); + if child_roots.is_empty() { + return Ok(Default::default()) + } + + // div-ceil simulation. + let threads = Self::threads().get(); + let child_roots_per_thread = (child_roots.len() + threads - 1) / threads; + info!( target: LOG_TARGET, - "👩‍👦 scraping child-tree data from {} top keys", - child_roots.len() + "👩‍👦 scraping child-tree data from {} top keys, split among {} threads, {} top keys per thread", + child_roots.len(), + threads, + child_roots_per_thread, ); - let mut child_kv = vec![]; - for prefixed_top_key in child_roots { - let at = self.as_online().at.expect("at must be initialized in online mode."); - let child_keys = - self.rpc_child_get_keys(prefixed_top_key, StorageKey(vec![]), at).await?; - let child_kv_inner = - self.rpc_child_get_storage_paged(prefixed_top_key, child_keys, at).await?; - - let prefixed_top_key = PrefixedStorageKey::new(prefixed_top_key.clone().0); - let un_prefixed = match ChildType::from_prefixed_key(&prefixed_top_key) { - Some((ChildType::ParentKeyId, storage_key)) => storage_key, - None => { - log::error!(target: LOG_TARGET, "invalid key: {:?}", prefixed_top_key); - return Err("Invalid child key") - }, - }; + // NOTE: the threading done here is the simpler, yet slightly un-elegant because we are + // splitting child root among threads, and it is very common for these root to have vastly + // different child tries underneath them, causing some threads to finish way faster than + // others. Certainly still better than single thread though. + let mut handles = vec![]; + let client = self.as_online().rpc_client_cloned(); + let at = self.as_online().at_expected(); + + enum Message { + Terminated, + Batch((ChildInfo, Vec<(Vec, Vec)>)), + } + let (tx, mut rx) = mpsc::unbounded::(); + + for thread_child_roots in child_roots + .chunks(child_roots_per_thread) + .map(|x| x.into()) + .collect::>>() + { + let thread_client = client.clone(); + let thread_sender = tx.clone(); + let handle = thread::spawn(move || { + let rt = tokio::runtime::Runtime::new().unwrap(); + let mut thread_child_kv = vec![]; + for prefixed_top_key in thread_child_roots { + let child_keys = rt.block_on(Self::rpc_child_get_keys( + &thread_client, + &prefixed_top_key, + StorageKey(vec![]), + at, + ))?; + let child_kv_inner = rt.block_on(Self::rpc_child_get_storage_paged( + &thread_client, + &prefixed_top_key, + child_keys, + at, + ))?; + + let prefixed_top_key = PrefixedStorageKey::new(prefixed_top_key.clone().0); + let un_prefixed = match ChildType::from_prefixed_key(&prefixed_top_key) { + Some((ChildType::ParentKeyId, storage_key)) => storage_key, + None => { + log::error!(target: LOG_TARGET, "invalid key: {:?}", prefixed_top_key); + return Err("Invalid child key") + }, + }; + + thread_sender + .unbounded_send(Message::Batch(( + ChildInfo::new_default(un_prefixed), + child_kv_inner + .iter() + .cloned() + .map(|(k, v)| (k.0, v.0)) + .collect::>(), + ))) + .unwrap(); + thread_child_kv.push((ChildInfo::new_default(un_prefixed), child_kv_inner)); + } + + thread_sender.unbounded_send(Message::Terminated).unwrap(); + Ok(thread_child_kv) + }); + handles.push(handle); + } - child_kv.push((ChildInfo::new_default(un_prefixed), child_kv_inner)); + // first, wait until all threads send a `Terminated` message, in the meantime populate + // `pending_ext`. + let mut terminated = 0usize; + loop { + match rx.next().await.unwrap() { + Message::Batch((info, kvs)) => + for (k, v) in kvs { + pending_ext.insert_child(info.clone(), k, v); + }, + Message::Terminated => { + terminated += 1; + if terminated == handles.len() { + break + } + }, + } } + let child_kv = handles + .into_iter() + .flat_map(|h| h.join().unwrap()) + .flatten() + .collect::>(); Ok(child_kv) } /// Build `Self` from a network node denoted by `uri`. - async fn load_top_remote(&self) -> Result { + /// + /// This function concurrently populates `pending_ext`. the return value is only for writing to + /// cache, we can also optimize further. + async fn load_top_remote( + &self, + pending_ext: &mut TestExternalities, + ) -> Result { let config = self.as_online(); let at = self .as_online() .at .expect("online config must be initialized by this point; qed."); - log::info!(target: LOG_TARGET, "scraping key-pairs from remote @ {:?}", at); - - let mut keys_and_values = if config.pallets.len() > 0 { - let mut filtered_kv = vec![]; - for p in config.pallets.iter() { - let hashed_prefix = StorageKey(twox_128(p.as_bytes()).to_vec()); - let pallet_kv = self.rpc_get_pairs_paged(hashed_prefix.clone(), at).await?; - log::info!( - target: LOG_TARGET, - "downloaded data for module {} (count: {} / prefix: {}).", - p, - pallet_kv.len(), - HexDisplay::from(&hashed_prefix), - ); - filtered_kv.extend(pallet_kv); - } - filtered_kv - } else { - log::info!(target: LOG_TARGET, "downloading data for all pallets."); - self.rpc_get_pairs_paged(StorageKey(vec![]), at).await? - }; + log::info!(target: LOG_TARGET, "scraping key-pairs from remote at block height {:?}", at); - for prefix in &self.hashed_prefixes { + let mut keys_and_values = Vec::new(); + for prefix in &config.hashed_prefixes { + let now = std::time::Instant::now(); + let additional_key_values = + self.rpc_get_pairs_paged(StorageKey(prefix.to_vec()), at, pending_ext).await?; + let elapsed = now.elapsed(); log::info!( target: LOG_TARGET, - "adding data for hashed prefix: {:?}", - HexDisplay::from(prefix) + "adding data for hashed prefix: {:?}, took {:?}s", + HexDisplay::from(prefix), + elapsed.as_secs() ); - let additional_key_values = - self.rpc_get_pairs_paged(StorageKey(prefix.to_vec()), at).await?; keys_and_values.extend(additional_key_values); } - for key in &self.hashed_keys { + for key in &config.hashed_keys { let key = StorageKey(key.to_vec()); log::info!( target: LOG_TARGET, "adding data for hashed key: {:?}", HexDisplay::from(&key) ); - let value = self.rpc_get_storage(key.clone(), Some(at)).await?; - keys_and_values.push((key, value)); + match self.rpc_get_storage(key.clone(), Some(at)).await? { + Some(value) => { + pending_ext.insert(key.clone().0, value.clone().0); + keys_and_values.push((key, value)); + }, + None => { + log::warn!( + target: LOG_TARGET, + "no data found for hashed key: {:?}", + HexDisplay::from(&key) + ); + }, + } } Ok(keys_and_values) } - pub(crate) async fn init_remote_client(&mut self) -> Result<(), &'static str> { + /// The entry point of execution, if `mode` is online. + /// + /// initializes the remote client in `transport`, and sets the `at` field, if not specified. + async fn init_remote_client(&mut self) -> Result<(), &'static str> { // First, initialize the ws client. self.as_online_mut().transport.map_uri().await?; // Then, if `at` is not set, set it. if self.as_online().at.is_none() { let at = self.rpc_get_head().await?; + log::info!( + target: LOG_TARGET, + "since no at is provided, setting it to latest finalized head, {:?}", + at + ); self.as_online_mut().at = Some(at); } + // Then, a few transformation that we want to perform in the online config: + let online_config = self.as_online_mut(); + online_config + .pallets + .iter() + .for_each(|p| online_config.hashed_prefixes.push(twox_128(p.as_bytes()).to_vec())); + + if online_config.child_trie { + online_config.hashed_prefixes.push(DEFAULT_CHILD_STORAGE_KEY_PREFIX.to_vec()); + } + + // Finally, if by now, we have put any limitations on prefixes that we are interested in, we + // download everything. + if online_config + .hashed_prefixes + .iter() + .filter(|p| *p != DEFAULT_CHILD_STORAGE_KEY_PREFIX) + .count() == 0 + { + log::info!( + target: LOG_TARGET, + "since no prefix is filtered, the data for all pallets will be downloaded" + ); + online_config.hashed_prefixes.push(vec![]); + } + Ok(()) } - pub(crate) async fn pre_build( - mut self, - ) -> Result<(TopKeyValues, ChildKeyValues), &'static str> { - let mut top_kv = match self.mode.clone() { - Mode::Offline(config) => self.load_top_snapshot(&config.state_snapshot.path)?, - Mode::Online(_) => { - self.init_remote_client().await?; - self.load_top_remote_and_maybe_save().await? - }, + /// Load the data from a remote server. The main code path is calling into `load_top_remote` and + /// `load_child_remote`. + /// + /// Must be called after `init_remote_client`. + async fn load_remote_and_maybe_save(&mut self) -> Result { + let state_version = + StateApi::::runtime_version(self.as_online().rpc_client(), None) + .await + .map_err(|e| { + error!(target: LOG_TARGET, "Error = {:?}", e); + "rpc runtime_version failed." + }) + .map(|v| v.state_version())?; + let mut pending_ext = TestExternalities::new_with_code_and_state( + Default::default(), + Default::default(), + self.overwrite_state_version.unwrap_or(state_version), + ); + let top_kv = self.load_top_remote(&mut pending_ext).await?; + let child_kv = self.load_child_remote(&top_kv, &mut pending_ext).await?; + + if let Some(path) = self.as_online().state_snapshot.clone().map(|c| c.path) { + let snapshot = Snapshot:: { + state_version, + top: top_kv, + child: child_kv, + block_hash: self + .as_online() + .at + .expect("set to `Some` in `init_remote_client`; must be called before; qed"), + }; + let encoded = snapshot.encode(); + log::info!( + target: LOG_TARGET, + "writing snapshot of {} bytes to {:?}", + encoded.len(), + path + ); + std::fs::write(path, encoded).map_err(|_| "fs::write failed")?; + } + + Ok(pending_ext) + } + + fn load_snapshot(&mut self, path: PathBuf) -> Result, &'static str> { + info!(target: LOG_TARGET, "loading data from snapshot {:?}", path); + let bytes = fs::read(path).map_err(|_| "fs::read failed.")?; + Decode::decode(&mut &*bytes).map_err(|_| "decode failed") + } + + async fn do_load_remote(&mut self) -> Result, &'static str> { + self.init_remote_client().await?; + let block_hash = self.as_online().at_expected(); + let inner_ext = self.load_remote_and_maybe_save().await?; + Ok(RemoteExternalities { block_hash, inner_ext }) + } + + fn do_load_offline( + &mut self, + config: OfflineConfig, + ) -> Result, &'static str> { + let Snapshot { block_hash, top, child, state_version } = + self.load_snapshot(config.state_snapshot.path.clone())?; + + let mut inner_ext = TestExternalities::new_with_code_and_state( + Default::default(), + Default::default(), + self.overwrite_state_version.unwrap_or(state_version), + ); + + info!(target: LOG_TARGET, "injecting a total of {} top keys", top.len()); + for (k, v) in top { + // skip writing the child root data. + if is_default_child_storage_key(k.as_ref()) { + continue + } + inner_ext.insert(k.0, v.0); + } + + info!( + target: LOG_TARGET, + "injecting a total of {} child keys", + child.iter().flat_map(|(_, kv)| kv).count() + ); + + for (info, key_values) in child { + for (k, v) in key_values { + inner_ext.insert_child(info.clone(), k.0, v.0); + } + } + + Ok(RemoteExternalities { inner_ext, block_hash }) + } + + pub(crate) async fn pre_build(mut self) -> Result, &'static str> { + let mut ext = match self.mode.clone() { + Mode::Offline(config) => self.do_load_offline(config)?, + Mode::Online(_) => self.do_load_remote().await?, Mode::OfflineOrElseOnline(offline_config, _) => { - if let Ok(kv) = self.load_top_snapshot(&offline_config.state_snapshot.path) { - kv - } else { - self.init_remote_client().await?; - self.load_top_remote_and_maybe_save().await? + match self.do_load_offline(offline_config) { + Ok(x) => x, + Err(_) => self.do_load_remote().await?, } }, }; @@ -721,7 +997,9 @@ where "extending externalities with {} manually injected key-values", self.hashed_key_values.len() ); - top_kv.extend(self.hashed_key_values.clone()); + for (k, v) in self.hashed_key_values { + ext.insert(k.0, v.0); + } } // exclude manual key values. @@ -731,87 +1009,34 @@ where "excluding externalities from {} keys", self.hashed_blacklist.len() ); - top_kv.retain(|(k, _)| !self.hashed_blacklist.contains(&k.0)) + for k in self.hashed_blacklist { + ext.execute_with(|| sp_io::storage::clear(&k)); + } } - let child_kv = match self.mode.clone() { - Mode::Online(_) => self.load_child_remote_and_maybe_save(&top_kv).await?, - Mode::OfflineOrElseOnline(offline_config, _) => { - if let Ok(kv) = self.load_child_snapshot(&offline_config.state_snapshot.path) { - kv - } else { - self.load_child_remote_and_maybe_save(&top_kv).await? - } - }, - Mode::Offline(ref config) => self - .load_child_snapshot(&config.state_snapshot.path) - .map_err(|why| { - log::warn!( - target: LOG_TARGET, - "failed to load child-key file due to {:?}.", - why - ) - }) - .unwrap_or_default(), - }; - - Ok((top_kv, child_kv)) + Ok(ext) } } // Public methods -impl Builder { +impl Builder +where + B::Hash: DeserializeOwned, + B::Header: DeserializeOwned, +{ /// Create a new builder. pub fn new() -> Self { Default::default() } /// Inject a manual list of key and values to the storage. - pub fn inject_hashed_key_value(mut self, injections: &[KeyValue]) -> Self { + pub fn inject_hashed_key_value(mut self, injections: Vec) -> Self { for i in injections { self.hashed_key_values.push(i.clone()); } self } - /// Inject a hashed prefix. This is treated as-is, and should be pre-hashed. - /// - /// Only relevant is `Mode::Online` is being used. Noop otherwise. - /// - /// This should be used to inject a "PREFIX", like a storage (double) map. - pub fn inject_hashed_prefix(mut self, hashed: &[u8]) -> Self { - self.hashed_prefixes.push(hashed.to_vec()); - self - } - - /// Just a utility wrapper of [`Self::inject_hashed_prefix`] that injects - /// [`DEFAULT_CHILD_STORAGE_KEY_PREFIX`] as a prefix. - /// - /// Only relevant is `Mode::Online` is being used. Noop otherwise. - /// - /// If set, this will guarantee that the child-tree data of ALL pallets will be downloaded. - /// - /// This is not needed if the entire state is being downloaded. - /// - /// Otherwise, the only other way to make sure a child-tree is manually included is to inject - /// its root (`DEFAULT_CHILD_STORAGE_KEY_PREFIX`, plus some other postfix) into - /// [`Self::inject_hashed_key`]. Unfortunately, there's no federated way of managing child tree - /// roots as of now and each pallet does its own thing. Therefore, it is not possible for this - /// library to automatically include child trees of pallet X, when its top keys are included. - pub fn inject_default_child_tree_prefix(self) -> Self { - self.inject_hashed_prefix(DEFAULT_CHILD_STORAGE_KEY_PREFIX) - } - - /// Inject a hashed key to scrape. This is treated as-is, and should be pre-hashed. - /// - /// Only relevant is `Mode::Online` is being used. Noop otherwise. - /// - /// This should be used to inject a "KEY", like a storage value. - pub fn inject_hashed_key(mut self, hashed: &[u8]) -> Self { - self.hashed_keys.push(hashed.to_vec()); - self - } - /// Blacklist this hashed key from the final externalities. This is treated as-is, and should be /// pre-hashed. pub fn blacklist_hashed_key(mut self, hashed: &[u8]) -> Self { @@ -826,64 +1051,20 @@ impl Builder { } /// The state version to use. - pub fn state_version(mut self, version: StateVersion) -> Self { - self.state_version = version; - self - } - - /// overwrite the `at` value, if `mode` is set to [`Mode::Online`]. - /// - /// noop if `mode` is [`Mode::Offline`] - pub fn overwrite_online_at(mut self, at: B::Hash) -> Self { - if let Mode::Online(mut online) = self.mode.clone() { - online.at = Some(at); - self.mode = Mode::Online(online); - } + pub fn overwrite_state_version(mut self, version: StateVersion) -> Self { + self.overwrite_state_version = Some(version); self } -} - -// Public methods -impl Builder -where - B::Header: DeserializeOwned, -{ - /// Build the test externalities. - pub async fn build(self) -> Result { - let state_version = self.state_version; - let (top_kv, child_kv) = self.pre_build().await?; - let mut ext = TestExternalities::new_with_code_and_state( - Default::default(), - Default::default(), - state_version, - ); - - info!(target: LOG_TARGET, "injecting a total of {} top keys", top_kv.len()); - for (k, v) in top_kv { - // skip writing the child root data. - if is_default_child_storage_key(k.as_ref()) { - continue - } - ext.insert(k.0, v.0); - } - - info!( - target: LOG_TARGET, - "injecting a total of {} child keys", - child_kv.iter().flat_map(|(_, kv)| kv).count() - ); - - for (info, key_values) in child_kv { - for (k, v) in key_values { - ext.insert_child(info.clone(), k.0, v.0); - } - } + pub async fn build(self) -> Result, &'static str> { + let mut ext = self.pre_build().await?; ext.commit_all().unwrap(); + info!( target: LOG_TARGET, - "initialized state externalities with storage root {:?}", - ext.as_backend().root() + "initialized state externalities with storage root {:?} and state_version {:?}", + ext.as_backend().root(), + ext.state_version ); Ok(ext) @@ -892,16 +1073,16 @@ where #[cfg(test)] mod test_prelude { + use tracing_subscriber::EnvFilter; + pub(crate) use super::*; pub(crate) use sp_runtime::testing::{Block as RawBlock, ExtrinsicWrapper, H256 as Hash}; - pub(crate) type Block = RawBlock>; pub(crate) fn init_logger() { - let _ = env_logger::Builder::from_default_env() - .format_module_path(true) - .format_level(true) - .filter_module(LOG_TARGET, log::LevelFilter::Debug) + let _ = tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_default_env()) + .with_level(true) .try_init(); } } @@ -910,7 +1091,7 @@ mod test_prelude { mod tests { use super::test_prelude::*; - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn can_load_state_snapshot() { init_logger(); Builder::::new() @@ -919,15 +1100,15 @@ mod tests { })) .build() .await - .expect("Can't read state snapshot file") + .unwrap() .execute_with(|| {}); } - #[tokio::test] - async fn can_exclude_from_cache() { + #[tokio::test(flavor = "multi_thread")] + async fn can_exclude_from_snapshot() { init_logger(); - // get the first key from the cache file. + // get the first key from the snapshot file. let some_key = Builder::::new() .mode(Mode::Offline(OfflineConfig { state_snapshot: SnapshotConfig::new("test_data/proxy_test"), @@ -957,19 +1138,88 @@ mod tests { #[cfg(all(test, feature = "remote-test"))] mod remote_tests { use super::test_prelude::*; + use std::os::unix::fs::MetadataExt; + + #[tokio::test(flavor = "multi_thread")] + async fn state_version_is_kept_and_can_be_altered() { + const CACHE: &'static str = "state_version_is_kept_and_can_be_altered"; + init_logger(); + + // first, build a snapshot. + let ext = Builder::::new() + .mode(Mode::Online(OnlineConfig { + pallets: vec!["Proxy".to_owned()], + child_trie: false, + state_snapshot: Some(SnapshotConfig::new(CACHE)), + ..Default::default() + })) + .build() + .await + .unwrap(); + + // now re-create the same snapshot. + let cached_ext = Builder::::new() + .mode(Mode::Offline(OfflineConfig { state_snapshot: SnapshotConfig::new(CACHE) })) + .build() + .await + .unwrap(); + + assert_eq!(ext.state_version, cached_ext.state_version); + + // now overwrite it + let other = match ext.state_version { + StateVersion::V0 => StateVersion::V1, + StateVersion::V1 => StateVersion::V0, + }; + let cached_ext = Builder::::new() + .mode(Mode::Offline(OfflineConfig { state_snapshot: SnapshotConfig::new(CACHE) })) + .overwrite_state_version(other) + .build() + .await + .unwrap(); + + assert_eq!(cached_ext.state_version, other); + } + + #[tokio::test(flavor = "multi_thread")] + async fn snapshot_block_hash_works() { + const CACHE: &'static str = "snapshot_block_hash_works"; + init_logger(); + + // first, build a snapshot. + let ext = Builder::::new() + .mode(Mode::Online(OnlineConfig { + pallets: vec!["Proxy".to_owned()], + child_trie: false, + state_snapshot: Some(SnapshotConfig::new(CACHE)), + ..Default::default() + })) + .build() + .await + .unwrap(); - #[tokio::test] + // now re-create the same snapshot. + let cached_ext = Builder::::new() + .mode(Mode::Offline(OfflineConfig { state_snapshot: SnapshotConfig::new(CACHE) })) + .build() + .await + .unwrap(); + + assert_eq!(ext.block_hash, cached_ext.block_hash); + } + + #[tokio::test(flavor = "multi_thread")] async fn offline_else_online_works() { + const CACHE: &'static str = "offline_else_online_works_data"; init_logger(); - // this shows that in the second run, we use the remote and create a cache. + // this shows that in the second run, we use the remote and create a snapshot. Builder::::new() .mode(Mode::OfflineOrElseOnline( - OfflineConfig { - state_snapshot: SnapshotConfig::new("offline_else_online_works_data"), - }, + OfflineConfig { state_snapshot: SnapshotConfig::new(CACHE) }, OnlineConfig { pallets: vec!["Proxy".to_owned()], - state_snapshot: Some(SnapshotConfig::new("offline_else_online_works_data")), + child_trie: false, + state_snapshot: Some(SnapshotConfig::new(CACHE)), ..Default::default() }, )) @@ -981,12 +1231,8 @@ mod remote_tests { // this shows that in the second run, we are not using the remote Builder::::new() .mode(Mode::OfflineOrElseOnline( - OfflineConfig { - state_snapshot: SnapshotConfig::new("offline_else_online_works_data"), - }, + OfflineConfig { state_snapshot: SnapshotConfig::new(CACHE) }, OnlineConfig { - pallets: vec!["Proxy".to_owned()], - state_snapshot: Some(SnapshotConfig::new("offline_else_online_works_data")), transport: "ws://non-existent:666".to_owned().into(), ..Default::default() }, @@ -1000,51 +1246,20 @@ mod remote_tests { .unwrap() .into_iter() .map(|d| d.unwrap()) - .filter(|p| { - p.path().file_name().unwrap_or_default() == "offline_else_online_works_data" || - p.path().extension().unwrap_or_default() == "top" || - p.path().extension().unwrap_or_default() == "child" - }) + .filter(|p| p.path().file_name().unwrap_or_default() == CACHE) .collect::>(); - assert!(to_delete.len() > 0); - for d in to_delete { - std::fs::remove_file(d.path()).unwrap(); - } - } - #[tokio::test] - #[ignore = "too slow"] - async fn can_build_one_big_pallet() { - init_logger(); - Builder::::new() - .mode(Mode::Online(OnlineConfig { - pallets: vec!["System".to_owned()], - ..Default::default() - })) - .build() - .await - .unwrap() - .execute_with(|| {}); + assert!(to_delete.len() == 1); + std::fs::remove_file(to_delete[0].path()).unwrap(); } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn can_build_one_small_pallet() { init_logger(); Builder::::new() .mode(Mode::Online(OnlineConfig { - transport: "wss://kusama-rpc.polkadot.io:443".to_owned().into(), - pallets: vec!["Council".to_owned()], - ..Default::default() - })) - .build() - .await - .unwrap() - .execute_with(|| {}); - - Builder::::new() - .mode(Mode::Online(OnlineConfig { - transport: "wss://rpc.polkadot.io:443".to_owned().into(), - pallets: vec!["Council".to_owned()], + pallets: vec!["Proxy".to_owned()], + child_trie: false, ..Default::default() })) .build() @@ -1053,24 +1268,13 @@ mod remote_tests { .execute_with(|| {}); } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn can_build_few_pallet() { init_logger(); Builder::::new() .mode(Mode::Online(OnlineConfig { - transport: "wss://kusama-rpc.polkadot.io:443".to_owned().into(), - pallets: vec!["Proxy".to_owned(), "Multisig".to_owned()], - ..Default::default() - })) - .build() - .await - .unwrap() - .execute_with(|| {}); - - Builder::::new() - .mode(Mode::Online(OnlineConfig { - transport: "wss://rpc.polkadot.io:443".to_owned().into(), pallets: vec!["Proxy".to_owned(), "Multisig".to_owned()], + child_trie: false, ..Default::default() })) .build() @@ -1079,13 +1283,16 @@ mod remote_tests { .execute_with(|| {}); } - #[tokio::test] - async fn can_create_top_snapshot() { + #[tokio::test(flavor = "multi_thread")] + async fn can_create_snapshot() { + const CACHE: &'static str = "can_create_snapshot"; init_logger(); + Builder::::new() .mode(Mode::Online(OnlineConfig { - state_snapshot: Some(SnapshotConfig::new("can_create_top_snapshot_data")), + state_snapshot: Some(SnapshotConfig::new(CACHE)), pallets: vec!["Proxy".to_owned()], + child_trie: false, ..Default::default() })) .build() @@ -1097,38 +1304,29 @@ mod remote_tests { .unwrap() .into_iter() .map(|d| d.unwrap()) - .filter(|p| { - p.path().file_name().unwrap_or_default() == "can_create_top_snapshot_data" || - p.path().extension().unwrap_or_default() == "top" || - p.path().extension().unwrap_or_default() == "child" - }) + .filter(|p| p.path().file_name().unwrap_or_default() == CACHE) .collect::>(); - assert!(to_delete.len() > 0); + let snap: Snapshot = Builder::::new().load_snapshot(CACHE.into()).unwrap(); + assert!(matches!(snap, Snapshot { top, child, .. } if top.len() > 0 && child.len() == 0)); - for d in to_delete { - use std::os::unix::fs::MetadataExt; - if d.path().extension().unwrap_or_default() == "top" { - // if this is the top snapshot it must not be empty. - assert!(std::fs::metadata(d.path()).unwrap().size() > 1); - } else { - // the child is empty for this pallet. - assert!(std::fs::metadata(d.path()).unwrap().size() == 1); - } - std::fs::remove_file(d.path()).unwrap(); - } + assert!(to_delete.len() == 1); + let to_delete = to_delete.first().unwrap(); + assert!(std::fs::metadata(to_delete.path()).unwrap().size() > 1); + std::fs::remove_file(to_delete.path()).unwrap(); } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn can_create_child_snapshot() { + const CACHE: &'static str = "can_create_child_snapshot"; init_logger(); Builder::::new() .mode(Mode::Online(OnlineConfig { - state_snapshot: Some(SnapshotConfig::new("can_create_child_snapshot_data")), + state_snapshot: Some(SnapshotConfig::new(CACHE)), pallets: vec!["Crowdloan".to_owned()], + child_trie: true, ..Default::default() })) - .inject_default_child_tree_prefix() .build() .await .unwrap() @@ -1138,72 +1336,46 @@ mod remote_tests { .unwrap() .into_iter() .map(|d| d.unwrap()) - .filter(|p| { - p.path().file_name().unwrap_or_default() == "can_create_child_snapshot_data" || - p.path().extension().unwrap_or_default() == "top" || - p.path().extension().unwrap_or_default() == "child" - }) + .filter(|p| p.path().file_name().unwrap_or_default() == CACHE) .collect::>(); - assert!(to_delete.len() > 0); + let snap: Snapshot = Builder::::new().load_snapshot(CACHE.into()).unwrap(); + assert!(matches!(snap, Snapshot { top, child, .. } if top.len() > 0 && child.len() > 0)); - for d in to_delete { - use std::os::unix::fs::MetadataExt; - // if this is the top snapshot it must not be empty - if d.path().extension().unwrap_or_default() == "child" { - assert!(std::fs::metadata(d.path()).unwrap().size() > 1); - } else { - assert!(std::fs::metadata(d.path()).unwrap().size() > 1); - } - std::fs::remove_file(d.path()).unwrap(); - } + assert!(to_delete.len() == 1); + let to_delete = to_delete.first().unwrap(); + assert!(std::fs::metadata(to_delete.path()).unwrap().size() > 1); + std::fs::remove_file(to_delete.path()).unwrap(); } - #[tokio::test] - async fn can_fetch_all() { + #[tokio::test(flavor = "multi_thread")] + async fn can_build_big_pallet() { + if std::option_env!("TEST_WS").is_none() { + return + } init_logger(); Builder::::new() .mode(Mode::Online(OnlineConfig { - state_snapshot: Some(SnapshotConfig::new("can_fetch_all_data")), + transport: std::option_env!("TEST_WS").unwrap().to_owned().into(), + pallets: vec!["Staking".to_owned()], + child_trie: false, ..Default::default() })) .build() .await .unwrap() .execute_with(|| {}); - - let to_delete = std::fs::read_dir(Path::new(".")) - .unwrap() - .into_iter() - .map(|d| d.unwrap()) - .filter(|p| { - p.path().file_name().unwrap_or_default() == "can_fetch_all_data" || - p.path().extension().unwrap_or_default() == "top" || - p.path().extension().unwrap_or_default() == "child" - }) - .collect::>(); - - assert!(to_delete.len() > 0); - - for d in to_delete { - use std::os::unix::fs::MetadataExt; - // if we download everything, child tree must also be filled. - if d.path().extension().unwrap_or_default() == "child" { - assert!(std::fs::metadata(d.path()).unwrap().size() > 1); - } else { - assert!(std::fs::metadata(d.path()).unwrap().size() > 1); - } - std::fs::remove_file(d.path()).unwrap(); - } } - #[tokio::test] - async fn can_build_child_tree() { + #[tokio::test(flavor = "multi_thread")] + async fn can_fetch_all() { + if std::option_env!("TEST_WS").is_none() { + return + } init_logger(); Builder::::new() .mode(Mode::Online(OnlineConfig { - transport: "wss://rpc.polkadot.io:443".to_owned().into(), - pallets: vec!["Crowdloan".to_owned()], + transport: std::option_env!("TEST_WS").unwrap().to_owned().into(), ..Default::default() })) .build() diff --git a/utils/frame/remote-externalities/test_data/proxy_test b/utils/frame/remote-externalities/test_data/proxy_test new file mode 100644 index 0000000000000000000000000000000000000000..6673bd6765ad8df2a9e74f210d8795acfeb54a30 GIT binary patch literal 70206 zcmcG1Wmwf)^ER;wrMtVkyHk)xx3Ip8tLxtE&(aYO-h4=q;$Od!T0JZ-}ChdG6;SH7=o|Lw>LiF~`c`ij=~ULE^qz z2$pvCM*bazk|R)^PeBx}KUwE-7IF^Ur8lHQ41oyD(zGO5wdiRp)gM0#8TaO@AHHQG zavLuR#fb=nHpb${Tce*I9)*NL97i7rUpspJBc?=r2S0Md(8Y-x!)soyLhzP@ktZXd z2I7DJ);LG(qE_o>TRs!LS-lF5>%aNUP=;|_E2_l6=T9Z)3-!N6Ki`CiUJjd;Gvy-K zcnq;~8y?fHzt)J7=}*ny>Ucvp`m!jnxnYX=f2;rR-@o{lSV4ukT#aj*rJ4AE6P4qc z?o10+v4<8rI%_MG{>4|hc$!)*Y~xozKpa;+lt8Xwjz7aff@pJQ*;jsPr`#C2$~v1f zv?FCRDail*&AQW{(XhKk!j@uC#<6c0_D z%%bpV6e?x?N`7a=!=zRFYk>Q?BhD7%OQatVK>cQDVH9uYqB%Xkzr$NFx2;INAk4FL zn1!mg9jQFc=f&w@c;Krn|Af$cSkCVvuXuc^@F4dlv5|skoHTLyn)e3E{bpk`iX{>D zOn`tj0N$+_+`!6Z%Y#=i~t%iJpdJm*IpUX!Q`+UC6_4y z^R!&{u`!04!PP#ylsf-FTR8wDG01V4dj<%Q##-C%`IXNWreXVo?+a}x^zG+%L#ejq zd5R2kE&FH=Xz(xq^)td4{|GlhmGtZ#rMU<}P1`&fJ*Rwt!l7uHy@*V@#A^ z@!}H=a19WNt!yL+AZePjbIK~U9Q*F|+rPP%Fa83jbxx{E)%pl{janZTdA_V@V`Cfsvt z&!jJ@$RYgg4bresp*_1Mr6C#Ry#l~sqFWb%VZnZYMQsNghawx6cG)u-lN9Z0R;#ECZ0eG_-LDQ{vt{*x~H^Rc&s6;ubKO=oe0p*mN3U?9i zIf^$#%NXK`HG3kLT8>}dZQX1cI34(mGzx%8X_!(!$FP+2*rhf%(qtRQ46DjPpLgv- z4cUcH`$?`pBT`W}KX{pQxBPd2zKV&%fi9sA_mu>kvx{!o9E_Ntv$}L?VbHR)6{6WN zy5nNAgnGCG0p1M;o18$wo=fk#dW&)E3C|jNZZ$(l+?!rYi6EghMKC=8%S^XZbo+>7 zY$~0`SZedGR1(5y80)TW8+P~ACX>VtpS9*Qdfw9t5Mck^{JYr`osiMBO;n9Veq3!) zu&5hUNti%yWK}Yd&IC+K!ye!h0}%1kFuGhO5}MJcUa`%l!eMmF?Wc&VZ|vavFAz^3 z@SJk30s)qQBynC_JR%fSzR9@mXO4DL3_8ZGE$9P;Z8qnndU-Gl8=S4>r|p3N9apJM zyx)f|B$G()GihH(9wb$`Q+SXXM=a?J=(qsBz*;I6n&)wI@8y_t^B$ zHvs`?ak!$+gnIVfIVOFi+vii6>D~(XpWTM^uk15?lwi%kG~g8+^4Ym1mn|&9kJ&D< z9b`hz!FNJ7Bv_BIsW8~djX=-bV-_YtcU~Z1a=Zn^!Ct-!T5i^1*yCJSCeuA<|*zs_{~X~hkYjJ62o z(krTE+LZGDp~ZZFPGlrswBw|XKd%JJmk?-?(Se&}!6!v=uhTtVDFjr&S8AI!k58%D zmz)n-mc7)K$oOpD-hu!H6XD{u`mAO@YcFM$FS^@_sPjPYDjgV)7pVSyzI;u&Utm7v ze)(}M#T3?Y+ci%#$s2*M#^*@FB<1Z%X++{HsMpsnRU7}DuE6z^8J9+@6w5oNRK<$E z1AzHA0?wTV!p_1I(yO+`cB{CUIXn@7sO?{}$qq-mKH=%|my5BWx7 zKmb(n6EIMj4SwM*!}}&GbUy3?EiRYU0s?cGX%~TVau+aru-A9&9~1J$r^iBX-rkc8 zQk^ngDyZdaWCa9c`mbKImMsehDI0|8fDAa|EmWO(SW@j&`}m8O$~kx~LDU%zwR~P#|33IJqa(Q%MFQM%d4^|Gx%zyyb5jk;e=hu06 z0~^BWWEm5i4jA23p}T;w-)E}LK@d1#h5$KTP*}@4E;fjqP1h}USA)8F>U#M1lv^Z) zA^8uDZ`a(Y@z&w0$PXZG;cahsWUSA|t}NEjg+z3fXQmY-USRTWn9{%<%k6$>*NX*~ zR2LU8^pnc(2G(M-y<~f9b}r|!^kpKq1RAR6IrD5^7Onr}J>smV5yQltU{SpZJxHo< z%2S@RbClg)XMfOU%U-<;5O7D$DS}nU;NNPfBx1{p1E)gL{D6gc3ix@-yM7n`h6+qLp}zhqxYq`(IM#J z!669I(O!UY>}E00bcz*B1JH>^3Skm9F}6gcNSl-78=v+)A|+{s4qwUi3ED`IYSj_% zD<;4j3IPEz)_44Z=A1Dm1Gsz(+%&I$8_&8+btrST422>e!>qG`Yrs<2r^*IahV82y zrXQ4XJcs#47@i)Dr!>@$&Psl2PWhkwE>jd42tmaMOBQAYrzJHo2Tp#|GJ2cecPA0! z;Wgb@Da?>2|BN4)24FyQ53DH{DS8gVIAd8JH3sZ@H1sK2KzQx&xi z6#xO|sD)7S^!oKvq#CoF3h2mRyJ*=U7Vs^wP7~a){guE?DHyoT8zE9&o51J}u2x9r z2iLO$$_euh<$lxdUu6t_pFJ3n=2wcVSb>1HXReK2rNKkrg~NhN{Lf1CD)=9zyq`@~ zREa5@Ze}&XWHbgpTFMvYxgV5<@k2t*anZS-f>>}ZUzggK23KdUpOL8K$ORSn7bXA! z18>M9rSAx<5%Jl(IaDvnYA`W?Dfh|_f9?F(w@p}`OOb-2+Z)zIF%>=f(GzQ3YbTx~y*2|~e`o{}2-tQx zgk}A%vl5G3J4Q^mdm6T-%>LB$ORrNY zCyW*)m&Km$w;groE-eqOl2xXd7_`fn$d><^!iHtA^hRYzkc!-peUxg61mED>D8^!7 z5QV+l)_m%Ux6%!wpS@aNJ_Zm#d~lf!yVp`T>Zdj?3CS(t{&AUHp5@7Y<@-4uZEMvl zFfoA5WNcW*^7!Q3tpRJVRBof4c)@*aen2$T(ah=zzmm%CSJ(_>Fhcmx3M&pmq}^k% z@XTTPw|@FEA^)rQ;R~=_Nkev<9FW4*lh^u9_Q39yL`NW?!T!jLOHa93iRclBB|46* z!2UL@gK+)Seu#P!mc&>R%?7|XPx=2)?Rwc3hNs6982{!=~ zJ->Kp%n$$sY(`;i{NQ16w^?9aiK05(WH1}l(<)p~$_|}7h`(Nz1=oPZPh5E+7xPe- zkli7O=GSy$@0;l|-#T$=y>g!PmCRs z-3s5Q4FkfOl;{+w6%TF z3`C&4Q*eDieo9?H402P`3co!Q8Uq?lZX{u<%xD zKtjo%;29L%{jhj#i%eY-vQggr<|>~_SRvyCjcs2aSi9z4Yqc()!jZjL;0diWUl2kZGb&;@O^>0R!oLUiT#qd8b89xPT7n%>p=U*l7w6=e}Kun$OzR;ZO*?+be|3cJmC!PQMi^7L*7N4=-l5$A=XN)2s)fa`J zL}8%!KB|&W=lwm&7M6bd6u&MHYu#t(d2Jd9@Fa1!I%cqj2;b8$CVlLo z{ScpP-4Uh2A@#LEcE1Wv8O*d!P<+|*YtD}&a~Cvynfg)TVyTTp3 zXRZ7`i+l!nsFT~}IXFxKz)HXWL1B0Rm-Zox2Aab|NdUB|sTly&Aym+=0q0FN5B63E z>B7f#$miBxQ6*^c=@00GS$&B7RuQPz=BLO-D>u2lR%wdq^x0?p9Np-d@BN$KR0HN) zd?fcB5syHDTKciIEvZ+ID5_BkK{mp5^K`=)N&Ewo!$ivQr*UJF$cqJ6BDl3S2x5I9&p*|t| zJ1oPHg^%?&?Y-xlAhE0oVsgF1Ju||Y4aSWvA(z7j#xm6jT`a1f2>b{h<^J#X!ed0^~t&)Yqer*MvdF^Phi%?m+Z`TMwOs#>tn6yVUA-b?~cveL8^He#A=-yxopv;5dNPed^f_V%5lXc+Im`~}=gn`gYa4oQbW)Qbg#f-<{1P3{ z00J1uaxXBO87gZFaWVuSoK#dhAKAnk^`XY=)*>>f!m#`$t5Rb-Bs2t7Nq8@y72Bzbu=vlf|BjM>TK_xKB>y4O@MH$V#;?xrPcj3^u53-U#gU zr-5X=DoFvzl*^97?9u-OH6T`0gLC01J*JZN5wqk-C4DiR8E6FomgO0XQ5NshD8V#< z4(DO(Dc90wqE)WMZ0OQ>gdzGdgN$1di7Ks5X|<*_TcDu~Q-6vIo2O%RCi}Qsi-)dsa9Kd-QfMzdoQslncwxCoV!kC}$77&DCW!JY?i!+> zH(a2lmCNTSgx~`xrx9wXc(0wMnw;-&61>T4LQP{d)zcgKNYk8~TRvFG`e$NGvb>Mq z5oxdj`P26`$>AfH=!Q_w-o>b?l7m#6I=hyG7pU%*SlEs0uV=02(GO=eIvB;6Z*e=d zIE}2H>{V435WoI|=fBQrbUz>_QMYy8&1+X%L`O9SKZ{|Iq~2+pKxYJ}mv4=BZ7?X6 zSozG5fSM^sG=zlTrT4D9Y|zJa9aIBJ8~};1P6LASQaWx$v9D$?7W~tq7vBGUGewVb zv#1uqmzv{d)4ub)==IUAysMuczh%(lRxMrCs*O0g5ZzX6eKio^FRJ72C192m(`Rh% zNGK!mZZQvcp7QJo|C@;erV)oWn7s_W&PD&*TI8c)){Bzwfu0QAQ;7>>kOYzf@w~4d zZMr_=ipI9{4knWW0Z|o}!}^i7Wd=8GULFdV__7nF>WIQu6k{JAfXlYO<-s(7frTYw zxM+oyl@ynOR>680rk0lqs@Ule6ETog7(MFgE{)y<`?Uux5Wr8AGeAwioTM7SrsWkD zu(qz?bz8D^PI!5|wC7$ZDh+1pWfG)vJFRSZ$P{*1@^tLzdoF(7*_b zYpj-K^g=y71)=_(ib`?1RW$!0dc$uxV)5(Nl{vkgnw=k`b4trV5) zW@Rp~4(m^J1U@0(x{=xor-fp%TP&$j4ZQeim$~R`tMR5UlANfjfvvi(1lFkR`*#?h zdEiOOcVx5vl>5J*Q)G@_An9b5OqTduor~IMK|7?spR{vp9UWkDS0O|`0h&}IKVW3g>tG;ZK_z~R2tY(4>c$d<1~`=i^wsj z4G!(0x^E?PxW3CF^yAfkHz>)%OcI6_liMLJMMvMYTO3S z;xLajUX?Krz;0}l5*#1h8|1D=8t?C-w3sWMh{kh5Tji=94%sL@wvs`LkApZCd zx+B#}b0`vrJyep6^~k@Vuqp1x#}(st0zkX4TChcMT!G?f)^@SX&sbG^bX&ZyP%pGU z7{AjxOY<)xpeI$j!IJiq!Z?-7;n@!htoh~Y+;ZsuCz^j<$5^3h{9IMKzgmb4y-CM_ zQ{zzSqRGM`FKJ~HLgiE(mG0da2xTr{qXYtIMT0alNH}eEVENIj9w5T_`IonjfLdrD zTZ?4|grWW@cz=H?z=p@)XLz>B89L}K6N@dW)6)yvo!KFl!Rt!!+z>2&_Qg{6mrEKY z!GA_5ww|b2DE&I<&t*3eJ|Ezq^_!%>vTP7|Tw#Vqa#CiGt|$IX)nyk29n7HOUKdYJ z6kPe z*MK#(xumst2YP>Ex#+WRrx)>F_i0?A29I7Nm_R?HjTyYbmM1RFYajqN)j903cB&}1 zegL-}1x4QccsIq4uDQL^DYkqklMDk)La`qi=1nW{Oj*V^9K&s)X8?TOjvg2EyrQro z-)h?-vm3idZzmkfp8@T*KPg(FF<^cBDG?M2(8AtCQE~KI6q}_W*C#@4Fqc&U(*O?a z)*>r0l?kXm6X^?t=`Lz-CPusn=GZdir$EN&pNkAZ{Cah%qE4W0&O1U|9l%)s6e_}H zx>t`J&${Jf7pJ>Hhoxep)O z6deAj5`W*#ao8E(BF*;5Cr}07O)76=(f9;D_cv~^pq9_EqFtE`wp&@aYT!_a{xes= ztbUwOO?ZZ6K~uFK8by}%8G)!XE@Z=m-aP4{v%dnEC%6C1Lo*4rZV@d0}eDXcDm`E0%(L2Hy9DuFS%BoEd!` zh7&~)y;pdVvD zb=d_CIfsIoASbfJJh>-*KfUATUHp?)kI|p)$zKa-TpuaT83WWm7tB`(C%T}EE_SRZ zSPrB`OcoHEq%lQM_EDfVYRip*+HPw7@|&oRv0uq3j1U#Zj=A(!Qas;!;durH@W0du z(sF~DQ@GAM>ru(rI%#hXi#n?&ZfyDmQG9a79z4w7xEDbSvVC=OP}t1PQ!PSO6DS4j9B>71_t60iyVQQQ@Bk8(2t?LRUf+KrUZ`t7Ajqol-K)$ z7pq^RXF2V~6rl%_-G2LB4Fd!e>>mtVW~+Scjc`zXwH@?$LzIYtPT)6aiusf~>}jq9 zCRDlYKZ!|+WNB&n;EkEA6RzOq%4ROn)ZBAWDV~ zGCi;wne}U``Dj0oSvCG=Cm!5`O$O3ah!C*ULfBWHmK3F_A!8c3T>1kGn(eTwMFLH< zSPOyCil9!hE9#-U&7#K}C2g%a3997T>-3Hwdgn|dh0RK*)X?BPm>}eNL+CftC{oXP zr-*tkA!cPSJx`FzLX&1?6bW%WH$$F!FqNOjbP zOi91TZ~b6==)e%I24)C&Bd96yt4_Q*R}}1R-AKI%ej=5G>7eL={54$7+kkqaNW`#Y5+LB8R)ASC1U=80^9jnoGFMKMF3i<+ zpQYRq&-j%#xxf#^YG$bueHY^+XdOI9WR28HrmX?D=SU|Qq-#hbfv~1 zQtq8n0;%}uFXJd2L>9-lBR z{Uik@3xtcq_A_3`&mzhm-uU?o0vZ-<*zqv z!Zo_p$al8}=WQ(0Z^h83KE-zsY#(0`uN*1Sz^2bi%@iF$b>rhHfxe{%JJ&;}`0*a4 z&>^!@Y_L0!bag-!5)i}bb@6;I0{2Hr|EEQ8|Fg5k8@nLOsW3HJ^>+&HHvR$@*?b(j zUb%H>hIPGuVC)T;-y#Cv+|Z+D%CvN{2Gdbu|FbKTQY=~I2%u9HrZQkubz;LLh9(zL z1-fm+JXBBrSif~3!p@X6QOq#n-u zUu-;HT<}jDkC#P%-_MGwoT4$Gy>h6b_)fQ&^X+roRLb(q=x09})|sM(^}95;XPSt` zMHkQ>`9EqWC203JOhr4Lqo`8EXF3s3lgHtG@SbSuLjcAnnaVtyZ$8>v{q&Ix12xo- z@XaS*%U8Z`W`t?&?dCZ6bQ{)A3Y#@yYti!%c z%A7dqQBiX}?fQ|Ud_bJ&8kVM892BU38cTSIL2V{_ZRDKP;RVuejRjj0Dl&O7-&A6zP47nF-EuC)5d9ZeLFm`5rl-3 z1cgeo+Bl0Lu+^H9#q|oMSy_=7u6R1ld<=+yb>KlJXo~&9X_;NsY^ZYvuz6rwJvH6WUJQT%jgzFkxFJ79y@8}bZgJ5@!ypUxTKz) zCW0mfn$Ke`I&VUmY;#5}6UjB_%@)ncS{t(CBIyJR$Q z&?+t#11Z(~Tt*V(*$l89*Jbj>f{y2<3!p$(t71NAFTc9JSQ;j46)UQ~9+FvLhPiwQ zauw@!4T7(oa(My$-%E&ZvGmotp<7|e=v-7*r9-q_MJ{2rWl{4{yIONQ7vBn&U|2Th z%?*)(xcS*_716>$mO@pHsFkBJ>IlZiQC~#c)%@%0(`?;QLdcg6r!UVll|=F!in=ba zvMYT^LNlf^JE-T2-a$TR4(ulegO^2rpG4ovlrwJ*Jm-JUj=mVL{k#mP?FF6gFuRCO z7p&1v2a+MVKvS*1Ii!F9ASE?FLr#5`L|yK^Hld}fU?HGy!d8&J)LyHJ`U@Ktn1Om* zJ&%fR+EtUAR)g^=OgOsrJz#;k6xnDR4WNA1-Aj7s6R*A_LIni14geR%fdqcIC*X5N z{Z4InPC3jE5#^BnpIEK%TU7GMilE!t_eHwo)a!! zw{%#)nE7S64K?qd=@=iT|H(`Rg`FPMYNMOhX6u z%!O5cOdEAa?bdcXl~|lqmfea|%PGiR5LQK~g?qmS)YglUhqJ~<^-f~%Al=yHsuct; z_;;~=Xq#+tuU7i`Yy&3k;X(pp!s&zd)j|^r}OOPmkZ_4%z3rK&t?mKz* z6j_0|{(Kwy_+-PX=)CnUzAtT)B_1b!ryUQ>04Zu=gqhssvr#lkLZRP$Md%Dc54U>z zOi$r*{H+%IN~`0s>g*h|V;oc&{?%;q5yW6EZ+w)K-tAUDPmbbMUJl0A`BohOF1`Jc zD{ckrPZRUkk2xrXxE}+vc!w!8fxk@1EPLgn1WDvV#Ub z|0A7B>5NMa8!LL!S22XuC#RjRCUx@?X2|6t!+jy32L0Gj&-lS|z=!WG@#*BN%iG+<^^xd;sOPTbDA83X{eZTWRXTkJ#0n0m$dX@b^|?nXx)6L*B1Fl|e{>jX8Hc|8hfR z!J%Ak64mPQVQU0K*&U_|Re>ZX9OEN4G%Gy>1cR1W8RqNf#4-3b%;e3EXtar(nQ2Pz zLYxp22YZe`^A`|*&nQ(B>bTr*vyuzcGHMSRQd4G}1aY5z7Q3)ae|#W?6D}8(xP)SU zFB}U5+?-O%B_%R=TZ}%>5d9=x(Yxo*%M~$BJENIU8PpVq+lU{K41n)wVI=2lWNa7*Ql6Pud%_K4*JS8w%sPTl5&A%Hk0ot;0xKt$BPw^ zoc2#w8Nu|xJIJ6qh*&KQ>R?kyZ^1#Q%R?3|Pz&*^X6fPlzY zg!iVq=<-HnA5Kw?gB#vt@cHT;#Glgojz(H1zGem2fN^VYS3*&^z<>xTU+Pglf^qVgdS!PABRFAm9NqMa32+*=G4B@#@`VjWT1+q!iM!g-Z8&K0@}@ zcXV(Kxa=e9baVLn2}$j3#19_pTi2R_t$E(lz zn7I1F+i$vr9KU8wX*_c?v+lgG)WUjZ`! zM!W*rX+1cBR&vy#GZ-By37*xki*y#;JJo8BaapzQ^|43^d8!J^L>1q{ffl52DQvfm zti0=0t0yfVKHXOr9!vrO$isNb(}IoFQ`eV~3|${hn>WzO?x9>F0(^BU?-QNgg6RQu zgW8ShgVHg6neO3b6F99C>F)>)uwLP1Y>huv%t!I6)jx_yFsnlY0Z%nYlEbU94|0>2LH4OOhrgTgi8JW!nfQfHUADhNgX*U8pbVg+#&oX zb2YIaV#_AAOhRv5Ep$QeX}E65v4$>tFF|BO<}0%lMlYw*Hl_w>qG@>l#Bq=owg(d_ znv6sf22viPtmViSA-m@%5+h+57&<>xs`6+iMR^+dqPL50d?8nxL4G1^9&cNre@3NL zz&@nl%V2=MEU9w}3UnJ$#AbTSb)|rw;QsaQq<+bJ>(JerF=o%H~ z=$d^4@e7{*zOrie#4Y+p5lno7)gE)}G&170!$&NK7kZp;>bpVWaUzcE9><^Qhyxu| z0rz1E*LAP1WdPzuOs~+%Xrg*?*$FZPE<*1QUfIHfJD#mIlSQQU;FA(YsUzIi?IuI% zRY9}c*foy^30>F0_jT+HDoSuNof948Cwm$}28@~a>HEIjm)VTi($H_!H^M&(Puk9m z!sigO8N48Rv?{OX+4ME-YS8O?x?5`V+ThY7VkD27`cWrcx`1qSF#V*nHAVe-hQYM~ zO9_4aB%CAWM~PnNyqxg>Dz3khmlKTl3Hx~Uk7g(k@r}&*%;b{g5GGt?>!Pbn^)LbT zPf$M-YNzBh0zo(Gz{Ha-E1_#exuQS%SR)5-1yZ5YjFp=Vr$ggu6Hmow^qcZ-I}EMJ zJq>oyTSj=N{0};PEGh8C`vpxyXq@*k31i8=p0y)8ETYy7T!P`=SnI zf*;CZn?H|aW9AyC2<(*^p=(4E$`VN(C1)*&BTSZo6u*Cx9>7d}`V~62J&{M9T0|OW z9W^fBT)VukGBSG@g;)yoZ6KYsnH{z=dTUHT*T^**!&6q5#|4cXva5v8c=x?pbIb(y zgCV@XeCMoI#rcy`{@w!yF=!w1noHL@5T`Id`A>a2e>aTBe;9c&SlS73UKmRcP#HiJ zg+guzsm%ZX1muMBatbI@=$KMxetZLSq|-X+XUlG6sD7KX4>=2`-BT~1uZU+*q_)r- zkd>Ro9~eoX@t~a_N5ArLc}HZlu)GDE{<7%rRc3?Yh+qAj*^)gA7E7A4v1E1PDVwty zp_9rzdUMoBcRu$k&EK0wctIzaqzWb|m*d{ufqaKeHt2p5On580o_dkt28JFzA_rfE zzzn&e7;RoGK~zxAfNr7O|JiUwfwK8JHxy% z#pHNJwv9)wD%?S{bgs~@i;eqsO}p6!Rtyzqb%;p5-JJXMS)YDM z(qynu{Xu!@O9p7O30;hXE~j_6DWH0iscrq%0@A4~|J|<^U|V0)xmPF-xY69&f6I&_ z@nO3*E`Nhc=`D;tvYw`F!3F=8rxE(QcM-_pkeD{tlb|W~6}TR>?t*TZ{T+&|llOKC zcVBWN8S>97efjM--z(R%f~gxBLC|>>)@NXA8IR~L23}12Y#x$3N`mEn7kKM1WlN*Sd(2Q;iVhEM_pO2OaKxx|hEH zrSF@eI#d*P8 z!xz^5eWo=@?PPd+@W!do>S}w^<~V8e{uFY9U9Bz*l^k)g4!#$~Nv+pI^i?*9_vp-z z2T-edrHwYEf$96GpIS0?LKAlJ{1AsHWuc4rv0up2u6}yDlp~hBDrS7me(xRMl$Rap zM|CM&hpNKBd?Nm5&gmGj2ACT_nJt{LKXS{j5eCl_TH3cAyD;FUsv|G)N#qah{+jnD z1&DODkBo_R02m_mzG1{4_DCGkpA{2GMgkB*3&so@)x17DywL}}|MHs5vnGz$4w+gi z)Y1}@zeD$G1pi#Ml@T;ICk}B*2LO}zCg<88ilPot80pm;H%NCmh@G8H~T~b=^e`t2&4b)$c$@nqQZxKFAU|0f@N>Nt%)2-j7QU)oW{Gr%uMU z(Es=V?%<#quH$hbQMEGPh2*<5k=Pcs;NKRRDGDs(Wl|6ip>Jdd3oZ?WOO-$edjIqW zFPQ1ctkiw%>LkNRgM*To0ad2b>Xzgv)(?okD|Fo#iVan#;4`O>Fnxpk|Fyp;uFH<- z^*mC;u(OOQLMm&eGIufb$#iauZSWh~mV#(|VekY8+^zj1a&3da^CpFu1XOWdE^#a0 zN7c1OOd8re2jiDTf8Sr2ov?pz#FrkCFAX~EO@X1%Mu6OyO0yYA#ySf!uCQaYk7hMJ za9zX&0!*xitkTP)M!JP)zP83Wbp8^Ra535;`i(EksuV%`>;z__GsjufduN#9_ZvoI z3zD_DPegH$l!F$>B<+1&QI;;$+v>jTy{@Y`UC@wPVRs^gO~4$=1AW``od2n<#W@av zata)L!W}-Iege2%y?IriV=Y>EneMBsVeKodux{K>E-4MzJc5fCm6s)^4~^c83W#}0 zQ$WDJsI5AwJxk#(Oa)UP7OdrlJH*V;T!*t_Kkix8NC>#MFwDDS@`?*8uehBht|7Qu ze~`E_zdk-Kct1Qa7@o5bkqC2XXOjH}nG*<*+@LuAi5J}O`c{Yih@pTUpRq1TenD6K zQmc1gAvR1MOwgOpd|s~HbX7=+PyNaEm0Sxa(#R8unGrgKR4jbonc=5EJCS3+k}jyL zS5=aXct32ElatX2_yLWM;s5TF*P8c}MMV%vzGlS<1(+DHV0R0m_h1=qlhA4*Dtyx> zeUq{>EI2>%mi`&efNJ`dNgUx7@`NVnrsRJ#a401aUtnIZSSwH~(YrsK<^` zQ{jRNQGad-Xzefiq3oNUk?b9iLs0G5;vZU`XSAYMkQ*f_3w{?om^exh9(RisI z=AHf9V+@cCvs#ACYb7&ei}rC3Ivz=(`+E;}M>*aw?6n(bOm82!9hbGC!q~;?qCUIh z=H3Dy>KO1gI?DgiDnZGIhgW791$vD<)c{)@-xmn5x%l94&Rkr;QrOCn=(!S>}EtgWMz+I5h*CJ)!ADMA=a+zKYp=ZHT zy$71Lo4DlS_+h^uN0m|`K@4|UdbL+>cbVuq`CbQY-`k*u0n9eZmMEf|z2FOd=wajg zEA@_bm@jirEQF+7p)A%8YUxHEx<4~E)8KnOK-HJH1OaIcnbbFC(7VU5YlvH!u@1Xk zoLf9T+3P75PV~=HN&j@S{DrLlN-FuMVUL&p>F=wrt?GA76u#V*s9g*N-`{U?l_64y zJ$|C~TPlSWueiwx2qO1eP9gY!etYC!9TNY2mv3vig@ZaH=OKB7JoPq5GJN6eu*)|YCVFk(wvgo9 zvCHiRYR#htczTqD7>3|~#(rd(5lvVb;YNjw0<7vH=5J zA=S^5>Jaf}Gtw_JzUY!bKpx%K(0zp%-S`(pW$4{Ix=DvxKG*J zOOoUi$Y;F`g6)15|8VU|4S|dgU(bx8AvbSw8svQcu7!p8(4KkmKP!v9DJfb!zx$Q7 zb!$@U(G@YU;dXW!qzqE={ZxKDDsm+@893Gkv2tMvt|NX!WG83M2eoz0Ziz|5yGY_yc zp*|>VHcs{QV8)5HAXW7{<<*PAf{4MPZj0zHF>|N=dE^FPw+JtX+-H3k>J*tfnT6|& z5=@1~@jhStyYKJu^$=yN!t9*YYjPMefue;Dfyn_0?CRu??PP{QhCOH^K!6hi;if%i z)o-%wwTZLZ+0DPAr`_b?8O1c2Wh0lA>Wwz4)T(NG z?b~fcO7Khc4aD-Bc)HQ0P>{gEyWm%c1{FcfhP7qf4mt9i&2gxCL!7sg%xsjPH0J-O z+${)wCTZ3~;GMUkZ9V@Od>wqCH+GDjpJexEcdnMCjM#J(+qVy_DdG2UWX`b}#2trMMX1!re% zP*g(D{p6`HEGhrz-UZD5)unVmkI96~PJe?2yIcUjh}}MH6fSw{jsjP&QeUFjVd;PY zF8A~|5R{&-;VHL9>>@rnSoorhzRn!TiDheA-?(4}R*aB-qjUnZdvmEKMMeM!a;*_E zl57V9SyxV;(|3NI_sdyK*en(?tsv8fr23xci2rAk0j@E6*0RVu>N|M**mM)c>>3z2 z*sU|Ba<5HbY-SzV)iflzdwW6IdY2Du-=ws3H zDheZ*&68WGw?=dsPDtDF`3@2fJ3udza+ZDu+b2ztEe!6mO1`}Lgp9?2*9i2`mPg5@ zy;01u`XL@t*1M1BD8H^`i7@K5Tbti^5R8rJfr$aPvSY{@{$~_x8rvvsBfE|etc&(F zhHlm*>%BzF%-=oG3p+{QkA9i{XAf<;i^EB(OiP;At@~`x16kDUM~IAG{UXzC=I+v! zFxj>Dk+%QJ^u|O96oT}?VCrYmk@YSIC9C8Q;;frNDW0DW#T1Cg#*rCA{)lXU-%Yv4 z7Uqr`i-hTxnQ9M;FU6f5N6o|=FRVKzQH!w7>`v@tV9mu$`V)ahlvIaBR@iI(gXOG8 z6F-fGi2b%ruY6{ASYeD~doL`)31$d9Nb6LO-ue038Kkxly5}C6VNp4hNkTPt=1qln zO6R?=Lyr}mHf)av7F;hA7Yk#i%z5St5 z;N$G|&U}tMWQ&eGGuXbk{R!hsS8R?WqEhI?pI^F~2$IdTMc1@|aT;;?(8EfEht5=t z{V^#gNxFGQfOh%qpKtl=oaV8)s--EiJ>s@dYVnO%-yzfw3k8m5@mq=3kUkz2YlC=d zj%~){K_=)|>2x*Kt~`c{V4U`qFi%+Jns)t@9r#SD_Ifg9v>pVp;%?WNg!{Wla{SChJ^56?9({sqaqQq_-EEUn_*; z^uPcAs)la-P;f+X7@5&Df0)qoN4tviSHmIAB~5{t-n00W$K>@=BG1d?^2B9c%PrVi zrS;>+m;_H_WEh}X(})`5cC9_dA^z!|{`yXjxA)Ig59XjHKD$@Tya;EMD`XT~2O+aY z&Tg#pr z2pDcT3NB!n+I>rAdZ&6!ZgZC9K(gkpVR;}o1fu=t^`6I`gJr!>GkWkQBPjzR(Qjfm zC>f0VGz=OoB~r}61ieo<+Aa&VgT1zNBL~E9?0v7bT$8R(>!ZQ+sF4M5+S52x`El5a z0e=6FxUY<=a_icrr9)6qKP?(XjH?(XjH-gHVxcf+?2e*e#5kH_); zbc}n*+-qGk){JWcD+eUq@amT zv`eudNVkm1`g%80=EsgeL)08JioAe%L>M2TzunhKQ6Saz{pOW>h(cj^I`f zQwGMDf%8uvWKMsNKq>v1M18pcF1d{@CRX@AR7M#6GkY}bWURr@PYh0F`cs*ON ziHu`EpKu<(gSS4hVZ6=^hd{b+=r2h5BK-{+sTf`D3RrB#qFs$2YqxVOuM6H$kaHed zjmG04vosrBhFd~Y7Y(HV(bBrCtRUDE+z7d(py{-{ht(^MQwr^Q6wy2$H9LPi;6K&P z2{^B9BKiP;f}ObR)Nca-_F^sxzjzMXN>s19v&wSXTLr)R$go8XY6`&B$(5%oa4CEJ zy5xAg3D4pSnE(>c`Y`bi&GV7AZ#_7HER!_-jVojaPtxDGBu_4D@sx>d?Z22Frbz-#YcC zqEW?MAJQjl=Eh_4AJNAnJJGbSl!~u@dXGIIB?E9m24oRT5{l8r1>Smcv z`=)JMV;`0VOPRqM8D;9=i!@rLb2(RO!#3htC_-7}(awN5F_169_jR>Zjo%L?{Uhu_ z)y-z;Bk>#fH-M{6Xy(th8XMc0s@Z7 z4Cl3_)Ul$!RF#V0h5%4-!=sy&wE`TPNDMvOKhG$!YMj zW5dMeJFupCl#1-NRDclAoJf&WusVVS@%c455>!FBhOb^sc|e2x#qAPfe}RheCtvfB zM(GnOFC*$>uan(c(XTpU)3^y?5Cw4m!9pK-#-GD~t6dSdLz{}{yqrlIhImL)EpKfi zCdbV(?E96=aTEsV{Se`sNf`p6)z+DFlZ^vX+6$dor+Q9np77>$T@wx^?VhpvZ)u9> zk3zgZ%ta7{5*Qu^X-jRr(qILHfnVc^TcwLaPs_s*`1uF*FZO=!k6gKP>tE_ezC&u> zdo|9yLc)rNWM3c<4i^LDJ~3VI1h;HsAu#>Z5A=or=`Fpf(+SDg78C_~UuqL~_xEv( zidVy`3uCi*>27XrMC?f@{A1S>X5bRI*2! zdTMCCWg3fcdEWTzdGUd3)XucU&$_56Y#Z#zT0Cu+dfUt;DqXOq@|8 zK>XDrCQ)QWe5L^8-dVU~qw1`Zi8NMBVH8N=_o%0mouA1BqQ1p`cgg+k4aUkMCgXI- z2vt?fjr*{1A)jrjqk0w=r~^lyz_n!xV}@_t0Ga3X7NU94n^lR$qq*dqcN1@Be#wrv z{Une4fbJA$j`*4qY zsV9WfezBEaEaL!6*M3x#t7kZ!dA&Nq9F(656nii8hD|925v>p{P7{-TYU1^D=d8)e z^G7eQ$$Dj;s2}*wCAm;V(>DET45+WsNyS33XZ(@ndFh&NT!6ZHTifhP0|$UgpuA4% z1Yb6QAUWArbLURG@(gE9sU*cZAU%LR9tFb(5p!M@W>9_HlH3{Du#rrTe$+LD;35M* z)fTzPjJ3!hlZZnFNX(jK@CgA>uY=+CPtq?o-xaBAdGFc!dk9ESwe3PUIk6%}5Lr5c zn)dN(ZG+^A2pT{RXIkW*ek(dYdos@*oOm1=92N4IYe{ZJl#w|8IT{$5{@WKIMy6hq z?quMNt#8UL)RBH+tgg5PzFWA@Ft7km$-Y_NGirlYcf|#-Itc*QhAPujN|BHEp=cA zDO-dCnE!f8R3DI=7?p6~?nc~^tNVfyr6U$;g=}sxjJ3xoJMzaCX5R#;?0MdVbfJ${ z%ccMJC2vIFSzL2qXov9iIjLgvP5iaU)Z2Zs-sqG7m7sR9r!UD7 z%LoCHA_)>s(DM5QFMSqOIY3*f+Hd+AnEZ@lDUjGw^N`R%vt!`!bpbmmOpX{tTULWf zD8LDGsK)?N4SmW3vT{j+EKIPi39acwG22ydI<~X;jM*>Hp(Xq;dmBlHUFbG>x&Eu+ z`?srn))nszs*o_d5Bp&QGchdo;*$MGM7U}kmo}fY3A(6D-T&CMa&xgFL$jY(QG)E> z!PKpxnzLs<%u8w!onFct6G<(!gJ}4Cws4U3&elr%Fmfg&X>BTL(hX*0n%ZT+_FKJ| za%i{5TxExOsPuq6?=fA@Z~23jUHPQmaq#Wk*gd>L`l1X_dA)*1ayR3!V*#St`|1Xw zeu0Jlc|+u#ltV+j#PLbWZ2T_JU{P%&ZOU+2Z~}`aw_}{G9{^=Rgu?Y&Xk*ih{LR;E z%V@sbj<4q_HT1mSVpB^Rs^vEbW<=tOjIfa+gqAcBUO#Z#dIWr zF#b9n(v6P(3B&}8Z;fOS7QyD#rkG7EV-=oE3RGOjojayy(ZQV-Y+%2aIiUO3sOWSH zFvR4jf1lu~O-L-KiJ#Sr&1mz*g+@;!$BHikkLQb8#J%A&p8iuy!^=ZV--#>H(!Bcc zILWsnRPm$57%XTX7~cB;oKcGKI4%WQul+VP>^bDofk*zOsMcnp|INE$eQ*@F#Y4wO z8?sIhO);N9>F6o#jLuUNqczuIX|L|xjy>gB!^F6y!uu>Tu6Uu+rw+!Ll)wn z4YF-~ht&rOBE?;g-#H_wqi!}Tz#0u^)3s;d5%Y?ALqz^qj2B1sewpRf(oC82uhV@7 z7c$+71!hf}Me!zn_@dzntR@X6*=)5zl@&n%him>Vo$&lqUT(Dgk%g-udzG`@pg&${ zR3yViZ@@uvN$KYQZVG!xduZDLV}S`9NPo>i!=r{uAukk2qMh0;m|B?D9~WvAX) z0_oJx+;D^JR>L~L51SS9Wpj6tIX zOSc#OWMfk1soSSmyUX|hZq2dnQG;8&%#K`Y%D?tb4>?)Uib z?@L8TzRsIOMNTkDvnd2>z*^yL*Aoy?B@Kegza4~^XHUrW74@(P-l0IQJ#&%U73wan zUQ=+XZBSkuV`{)Ot5pKhR_ed{&Hu#lSE!>x*M*^48i{ho;RJU0oA7;N1=11~dM6jA!6hS6bBHsK;5`F>Q67HMynN;F3bjZ2q6My-GA&8D9hjlBR zYpCB|#q%tzX?_JI;ok9{m47F4{p!OaI)(*JTi|ZPH878g*}PZr#qe^45#DT+EuzEB zEN6SXOJ!FI5iH62RMrT@D-U8%8Xg-IlMl3kxO7g|HYC?OtBy-$ww1loxXYr``kcm`&Uao3L))t$kAEZU5s$w zR#2cbO^V6RTa)hVf%r%bS)y9YFQi!BStp@+tITNc&|3=KkV}z(CFK3;bOyp_ZFak z7OaH134AMmjZT@j@ynIt73&i+-SbEP7SKKamj8s_mt!upt@cyhYHSDB16EgactrYx zOU!RH4>b-~Il|aUo>+2|InT<@hhIQ~^PlPbE=>b+!vnppkw@3@M(p^K5H`+b%W%b9 z7xs^EE*YdQ5f8S?2&iVX=y4Hztsa6Xyzo$k^PaZp|NcGz zQGjr^8peh}4(xGg+a+ffadHWogja?v?0w!xb>&?~^CnXGyi=Eoh#!Gh&c4L>O3{<^ zqFdLDcU8=5w=#v=TPil4eM^Q6Eu3$-`G0(yhGmW%Pqcj_p@Jdbs01HQz6lRw!UwaN zz5)}tFxpGOd;W7Tzw&U&Q+U>@BY&43r>$wZPe`lR9d}}E465hxW|27v&A#D=B6oN_ z^1i?yjKbI%&7?X|?_d6eAyU+2AL_lG@A7&HPMXnl)YXgW&qA~R9egV%9IH#i=C+)B zxpwS-*M}5(ou7jb@zM%ARyej8qj!S(gKPVY#@lXC6}35yP+66K;n;7;YK}H}zL{h6 ztS3W(A6CF#w9d!S1-2q!kv`__evoE`a*2$?VAV~oG~d#C$dY!LZI#Pvo8$%2szewU z?6sPj?CIX4#ge7|Bx>X=46vQaPnDYlH-J77$TQ;A2R8=Dj{G~uK@6uNyl)BmybFjz z$XC88N-lN8XB6Pf5AJimU)3IkW$trP6RO9%Qew9SKtV{VQ};TkpW|Jjveruu1xVh{ zDZq}HLG0z{Ym`^oyaM$E^6PvBwHG1v^iJfM57$_hobZj~C3A`xqxLMA!**BoAHuX( zHNb$L#cx8vaApa7(nGYP;LcQ3IGQk#G8PAbGld~58LlJnXOq;+3*jU9l_v*fD_kB#8EcN(MS&QYO4E-I|CSud@)0@CmmCgZ)0Raa z5la1w$9raWw24R+uzwc6yj+0DaRw;JP(|bJJFR4-_`UO07tD&}sr}!wkVRtyqTYJW z6g3|zpf}@ zOO2uEVRT9M{!imMmvo+P^fC+h0mEUKOg3-q}7i- z7F3Rq3v93OLoS4wp0$Oj;56B8XOF8c$S~&Tk6vzQqrYx5Iwk9l-g-wy&U;SCY_X38 z{igeR4t;3v7?1m!2_&ZG2@Wg#EZnFDPYDbB&U$u7L=m0w^y_J8X)KH}b=MJhY zjJ^X=?W1)*iwp1NDcxE)XRC2FUTJOyNFSlU@)hiitITCol-}ygtG@d}Yz%-JE3TZ4 z)G$4tHysOt%y!Q*t>=_g*zD)bUz~maK<8SE%5uG_Y?$JRc zp_j2v&!DUFZ9L}t@wy7^O@d?f>C{Khfsw_!LJIuQEdnG-Et)@@eYetJGvjjzDiUYe zm*I^K>2l;|9n{Cz9xldCm1--Cum9-Jiy!6DHeB&}Z}EGH>Q93^X(W`l(kOMqKI@32 zju|I>`-DN6+<;$%hI=Ou+*PQlk=dSvabsAC?FyIAs9IXT^Ct%1igYBv}; zo+Dyw3+f{yFvX+*sFYv)3_NREDp)ERS%bEY;k{R{lGgn$5O8iSxu3AN+ z)ZtELWBXpn?DLo1v!_&i{lIX;Sk%;p_2oT$1%bu*3 zY3B@^H6}LI@mTS;>Xv*liJ8bWc{3nsgLd@SB8k{_bLxtfD7i3ik9=rhGOIRqz>jDI z$3{p-dazEtL-P~6 z5==hZ`}Wou3ILQ?o+(7o+0la}vwmq~rW#@R>5|!N-bNQCE=l%Xx)7c}mhiy#f2kYyKt&VQ2l_rbck<75@%G5HS!RhUWEyGQyVS z9a&PvmexXhx0N+art?+SBw9h=X_8S(pgC0ZueZR$CSn^Ouh&l+(yr7xm45D5fJXQg z3Rt4z{A1kuR-0M~O%N?%{6eP-hJvRnn0`p#X=C_tIJQn*+V3HUiR#x9Te#|xge^fo zdHO1$fs4@)1?#XvBC!;~^GtR2VM;lR#+_X-DOa}c~bPu`9ylNsQ(&QYn z87~k67Qg6=bnqT8Ykfl~EkA#iWGktW^`mXyO%_v8DHzcQ^#p2&TB{xL=;j2$lj;$=q5>rV5i z41+7pv3ybxL7328GuTh}S$rA%+}G2k#9FD&(c1)5x(Wa2?h=eIHwoZWEe?;?2yF4^ z+k}RX@sUkLkCZzySd}}VZ>H(Lknt%L2@q)0rz1cF5rm09HoLr%;AIyTlKeJQGn5sL zXWou&d@MqK$OO1o<0)ifGyCKDG~xjHM!sjulGgjEeUD{e+ZLlf=?0eJ!)+q z7~S#nVdL03VuxOuXSn7ZI|yKqmU10Au^Vmb5=ciyBf---v_>TqofIb0>q-%-YJ z+mv}BDHqckhrXG7^vOP=eLknV{X;hnl1+KD?h>eGD7B*9(=z@rFJDaVi<1)>T~}EU zqq`*e$}w%Gig_ne`IC>U(_57tn36@~k1P|GfpYsQ zcRZkgrsh=(iLcW=0+a%2v7IP*XadwSgE2Z9yaY*&Z5e`HTKH!vklFQEcVQV&|^yVqx_$6S)A`gJZ~-TFUmFaNiXp8eR%>(=Sa^Bv(n zgnb!NV*9N`shZ9^(D8hy^SU^apJ^~xF|#p5?N2%U7jyroS&f}Mm&4&yOHK`3MnRF1$Lbz*>-8URp9F{QX~%hl1a+z+J;VnSZ4O&7Bd05p zIx%W7l)jb$(?7)kMaVDVN6kgv8&Ts3C{d~gV0?kuvQk(S(N$bxz!<=|DsPS!kCg8* zChuJ6dgAFS0csOVdg6!W9cMMNB0UPYV%(i{Zf_GW!J5qYycK=K@$(5Og6n$&*s}6} z*uhz2ZF?}e`Pjt#w$hLwf=Mm&1uKUYe&F{^+L}`lHvFj*452^}%$5hgO%gv++V5*X zs}WuhGVw)O_hx}PNPaQ|mCZSQtM^-2=9?t=8hBZAtM=OIr0m;lzLPI*^jb zl zuht1I=*Q^CXWdjYb4&Y<`+ah7TW!$+43&Gw*I7q_;Kcnse+M6Fa%u z`u$3%EFD3)cRTEa0A_j44FEFj_vJ9%f$J450->ANjSMdlu%NRQ()YgH|FC`=jtPF# zMZn@Uph4L~#dXVZe8(bYoeD(Xk{MHBu<)Bp=2mOsiVn8wzT!Lxdwn%5SeD?>ool*` z$N5@AeMr^}IBxTonAS7z=ZKg>_w8>x(k>SYiMox6LC+b zjk^GK^Z#<%B`YnBvX^~4{NIY#04Qd+#8;2W?>sH__spXD(R-w@sjV4j3R&6T%E-Xi zvUWYg>E%&K_Q4f z={AM?e%1}aTBomG8T?@cdVHs}b=%nJ`#Gl(kzw%t7#;;$A2MY|F>ytgrks7c$n*d7 z@{leEx4WX7+is7I^}hM%YqlyCvs>wUt8cWUA3B2&KJtXiRp=C@tL0w+pn5tNrb6R+ zSPK%ZBGB}%wfQ=uW$uLKSZk!i84D%VB|$vQ90STnNZkarQ&Fcy*{_r$)XN>h+9j1( zBtLOf&6vYueOm_w)pcgA08nP9l30yi9qFQ%JvpIn9yEg1%5Rn{MoExG?8m03PX0gb z_KOowxzzqULGQrxz%+ANKEY6K+}*|ETV*$SXIu_JN)LJ!B zeJW1As|*@Lrmpi<9)EP;K|oyvb2bxs2p5PigxoM}jqO1>DH?f+wfpe6NsTF$zNf(B zj6Sp9SqA}gm9%wZZ?|HKfpydofttz)HeSXkE*N@{=f$wcY!2Dx+ z|5K~0eM0@^+1p@K)j86)*VwPEeyHPl8Zvz$U0Y<2J%}hYc~;h!$FBw40;}O}v_Ano z4ocpMWc*^UPtxqxW0Qk$8;PLW)-<=(aiTU7@wERMA6`KdJoFM9>Ze=0Z`z^XRWjIY zYDx8C+bUhSBUF7J-*XB}E_9 z7PN*JN8khKTu#(dBYa|S;TC%-sNQ85v`^BeE8m102f9hV1?nb%kjP!X$KSAOI-Esg zwFbSiH{_gssftm0yFV1XFG2^ztsX^QPTdK0i#8=wc3+r{G!#ktd8w`yue(-v?{a6k zO{FoN%m|Vma*zQ~Hq)mg-J623;^OSrY6)NCYmY_9na$>CT~7cX8pp}Bo)7%x-nQsc ztT+=OKp=pdhM9CafB8N=MEN)(x2jT>5sW8U8mXrwF;!=fR}cV|^m8nth3W0qeTwJr zS{Nm|=3uU9b>Wi=ZAOy-8g_MfNU4n<^sa<@+R|So6A+$`gFP4taq-pjuqo_L4uKB7;`!F zQcpgxZ}~3;hNThB3B)7^ur{Tk{j~172pZAScv2NcxmvKjE3G-e4gm9Au@dV0Lx7{&(k>{h4t)2#o4~EnN z`;yVtrgdRh(BLuSWcjv)Iy1(Ld!vDB5AQ8*0b3X0c&h=o8^^hA;Q-DVClJE=k6ABK!5OmN%K+xXVIf~P3JB% zKa&t&$|DnbcG#=Jl~{UAP?VCWJ+ z8xHG0?mM-A71zgi>_&N;Ag*V6xS{N5HQM4b#|L8UR5j#t*S1?__R!G^)dS~1&NkFY zeK;bnL3nXH9?)TPsT!E&7rIC!0f0g+aGQj`$qSi^COv`WAbO8_vydu66h-F*j@#Lv z^j;gp=&@?#Os7S=jTefHpSJqVcX#rRS&axd52i0}72z!RXiHk0=qY<@JHVW*o8m{m z)DqRk`Zay1gedXOM<^jE`NJVGW%=U<(4UkljU6~xKfR#8Rc z?&UOKTjx5%%5rCQX!Z2ovH_qjg$@GxlFZfgQOcac(z?5ipETy#u;c9swD@!TBSS!) z{jU}mz<1&pBA9!s^J6o?1uOIUCNHck%}%dv(RB4{3@zC>A#KkfG9w%*_f=V_Bq>_d{}{%x&3KfbVoPlhoo|NVW~0a z_ije4F?MVEEGddWz8ty&nPS7b@L(#TU5q$Gtah}PRb~%u#HQ3*?$ymMbPz#U>!Amc zV=$KY7_Oy50f$<8sLMN>CH|>n7#*2Xsi0Way-7<`PE+D7aInQH#vbZxNaEtEdJTST zFP)%1=h1T2_i$=7r$_^dRb>!8RP8j8^eX+Y$uJ)h-&e!{Tb4CpJ&z&ht5$|%ZqI|D zngyObIvD5J&%pI>hr|S7wY9|_G|6bvY7OdJUz^ta&Y-d%G!SoAXeRnWG(@$hVz+V9 z&>vGUJs5g92>ogKlATM!EbQb3zI4m4Xt*B8iH&bcDcAkAp8LA6=6JW5>DJ1)fbXXS z!bGu{%{~ydMO-L7+(HSvPa(+=6xmQS*k1uq$72>h(q7H*BzzV+wHn^07G$^Ber!+s z4c|;~^n0H4|5vA8{E)A!U2xMy#TlcRE!X~}64Y;|qbu7@sy~dv2WW%}7k&*0g7*(U zM+N3=x!fMPKECtxF2V`G_#k>N9pk>Y9E$X zPf)etNIW{vpO@;O&NCG7IEJ?boL#B)D8~W!Gz4Jk^+)Y9y|NVWJmJ=qojXbDyvLDH z5hnT2+=h|1jSZp})WaD^Syb~D9jgFNZr=3ac=&}A#W$GWtaZ=|hEda0wxo+1cs3aU zGYL_84eqyoo*!!{2(HT@>!zZq?VZ09s+uQSw!G34ZIJ*`3+m%8i_oM-KDZy7;$&aJ zNru+41?9GvTGE5PsZb+W6Ig1|Xty3F83bm=z*6GA=UqX#?sN0`uMJ6h zFc3g(Z*O>W-?wz2Ew*lnB@37x1*DR3mhfB@xr}cY@gH519APR$r~!k}sbKrQgu21yq@@i8 zPQIR`B7q0%Y~9!lj>PG)8*(_vXFUBUBGQ*9B^ovgyBCL?b3=?Yh&P(Rt2RTkZ57KR z^bgnihoeclx-JOEgW)8+RR77H{Fij>f6BDJT+AkDI#mNlvG-#pWSFek)!UsUv;9)y zZIsn5Hf2j&^7fp8oA%d<*pYTU3JMz(MODw7lTU>2 zxJBIlZqjZenF@e94CpJCMK4eZ$kbX7DE+pFnlm^Q^f3@QV>z2aF2$7}MDx^?DUP!v zE`UPx_(d8qCu`56J9eBV|5t>9C_neIzo|p^><#&-@0t^ku7>)76 z8RYP%+%qHgyOX96=U}1k4$YNUe$lVr#}(GM^vCq?W}J(JSG4 zm&QKqb^1m8>gF^2R{;2tZD@$gg$H=^>;~6c*HXSTT>c2pL4F`qS$9@^)%!3=9ZMiZ zI#_-QjpUalS_f4+ZXyL#NPSAie@^FcLi9mgefzar_`yb7M{RTOwLemwLynLiIU5u* zV6=|<`JUnE4&#Rd5h<-ql3@j$=ZJ^jHIYm{DVoc*Lu^B~ zR<~&Ce-?lJC#LAW%h)@n&OAx7-2&AEQ=zMRqJA<$71Rt7L++Nvk=#GU$Cp=FT6esd z)lgc9vP7Yo(K+ZofVsODUJbcgo2@5jVFEU(F1YQ?Y4Mv&fKy5uS6sf_UXr3<8ZcZ( zp2Aci=^8T3ALTqTTG4Mjtq?$r4BOz;zvT!ZuUIW4ph0DR4CMbXqBhXD@e6i>c8%rd zB$@S-u5ey3y5B#!1jC){iR<#hvqt?f!k@$Zg3_Fk3xRkk5BDicM4dit&k|H+LJXAO zLHT##fUTt-hXW%s^PgNSRQ-gFOcMy91=;fem8+e*Q3znUF)P~57bn2Jb%+q=8 zdeE^rFl0k2`q3dpUP>qqXT^PFyc+=jZ&DM-W|$Id8oAn0fwm^ZJx zTrjKhJ8a7WX*pcv0k7VG#eYaMv$frCuu&i0WEoGdY?QV>mfB34 z5$7?2f|c*l7tbH9R`}j|?;pVT3nBZHxC%6|#CG`|eWu=$Q75zP+*yJ8bLTG(UfL6M z-)TJwE>inST(F~Qzv4Jo(z1!yYl6qQ3~ls9N01A5PIzBzSq^}@-Hnr*v96gL$b}}s zVoWP66Th=7X);$e0{OS%(7z>q>k=){zmgexdGn5Y)T&DkpI9Rajl z{3TCOZ>ijBRDe&?juFu!>C_dDX&G4|uL;66$+Pu7u5gA5RbyOU_@8Iai$x`@!h=Cc#4_7t}hwXdDUgKJmCWX4gPQz_JDI*WD%FrRmGuDGB}P zd99*obEzYgWd1!r*E~F%raD16+tzk-7sZaZ#UB< zPq%}Vya?Nm91~$-&_S=~#)x zE6eHkg0F@q1d(UsVVVwyzuo=haT9z%4X(3iPt*H94q$t<42E?@5{PpBtGl#GB_wHJ zQ=8n))K!r*QvoK%^sUBr? z`(7!r#}P#DjPNza_Z6k$GKd+eUp0n8%*XFddf_W1sjg){%cEra9sadEa&I^-JlVCn zgiE=bBidP;uy3kcfh%sCALg$FXexFI__~lPjDe#as6NDKC{re{yvirP-<}KuZc(rW=jSC&)r^H9pC) z9HE`~$$lVI#gJ>4J`<$!u!Anqm z049g={v>TB!fJMi3fGJ}XIH@;`sO%7s^@&L`FZ0My9)XI0NnyG->#~jnBIu<1(IL+ z6rGq~<4Zf_b}EPN4|#hdn#`bPRZ>s|uw<~32Or>NzD|yMo6o*~i{P1S3~4{wx(KMY z_z}|ia8~QT9z9_K92Il8uI{%U`Vz)Ss%_h1ci$UcO<&cIoZn$LFLK~vYz3mO3^?RQ z9wMKwCNRr}q_Q^JU`uLcu2-vyMNSawzq75!d^N9H75mnD7ywl?9o8>yNg4NQP+^C! zQWdv!A$b_i+XZ*AI#v)R%v}q_;AS9^w`r3C9p7iMdBoaW*lk4a1M{W9l(R#N{H}~q z(T6pZcm;0E5;mZ+k|jJem8RooNRKZmu9Oan?%2KAN-czaz}{C5K-x0reOA_g8>f2y z=;g{f&~7LPXMZ(!n0N2eGsw~Q!ME5|!+4ZE!{)MJJeIU&nFf7D;mukhuu-l#$@l(^ z$kN-zppa1c=&f{emWwAoMp*-Ny|X|{u2~R6pn>+RrUYLwE^B*1dPDheB!O`Ihy0R- zPlbe;i>aP;#kTkR+~Qb6kih9?KiVtHRu=DM2kXr9Qo1AhKue$DtHzb`(86&^k~K;Y z1sLL#Jodxp)_U(!+Y*+8oY__NZG|=z5aD+uaN*8pJQvg{hVZR18kq9f67LskhyQ53 z<$21oO(8HdgUzvcMf(tB86pyLZ}#RJh)^9ey9O%~<_XYKp81qY|Esl;zZ{NJn{Kg4 zD&#C@=F1OE4XSD;-(DpEl-&i)ssX+umyioFdwej4MGM84-vttqb3*bX!r)gm4iKR_ zR7-g8H%v$aBLbO)d~Qgc{|+9PRITwda1~fiLO@sl5tbPh&Z1|l&8i03q7M*)Q~MYb0T@w1 z#rTO|(+`K>G`+V;9k>p z^trlgqbQZjIAVyGi9@vN>CWMC>H`{3$?-1Ii~i6!9H(?-TeV$Sy4wq8PSg4B z3TiaW>&Ar$8bk&LS&4F){Bemder}IVP9Ggq#LF#@nukSrxNAeRAiwS=iR#-(rMo6( zXu+!7pyA@zXoUqp{o5BH9_F~d1V?ATwt|zH`Ft4#COJ(KEq592>r8*uGdAX?sF?&?eLs!8Y_?k zR!i!2&*tg20FhOZw_2}Gv?=ROji-F1^v%Cod-CJTS%G*06O;Z(N+jl4#9%}14Bp5b zZ?>~2!aBnn^h~cTT&xmTSqDGjj+N*h0HCylOa^JZR1!NrSmO;{kGu)SP&mrmNK0ee|%+UZWY@txl=G7n|e|C1Ih>08opH zb~PJzUFU`d{(|6`Qc@ACA1ex}f(t&LRj+d*#l8hGE}MK!^;_GJUk=vPz(~$@3p|lR zp>48PLHurj_0aWa5O-W1#giCM2e1bIZ}a0Ip1`Ego>##;Rks|^a`@14Yen8c6#OtB zx!r7!dgrKVd!Yzr=lBdpqCvn| zgbHfso^I854bawx#i&hQN?+D-jK?mUx5&3(-WQUH;o*Bvm$v4$ zZL-2>kuufAc4A-DQKk#1d|wx$|0}22$T1OOIW=-* zwk8vwSEyvMNp)}bisyei+p|Vy^)Qsid8CHVW1(9TYJ`KZXM;|z1P)mF0-L{{_v!gC z?_fTzc4k(F*gIXXSbeE*6V^bAd@Nxi9-APAH5YK{{&V6l-kdkp9VaCD-M|aCiiznH zZ6 z5$PtkAFbLgzAr)~@xNwZ{7(xs{%u>wpQGEWLHLu<{PRzIa4fi4Paz8j+0t zLR>$M(F)rW?^D1Qg(f+o_q65Fqb72#R$L>;`YPLusq)81t3iPn{~{|s@7FahkgN*J z8rF3ZK-ac9pg&~k?2L(&>Ig^Vn$#qVL8X(^R0YUxJWVl~cL9}berB&lVP9AnZiltU zLB28cA|iXS!=z~~K7Yi4#gfj~Q#6OlEe7@gI6@x7xb%TUtYJ+bO0v`9R0bR_bt9j6 zjke7Q-d|znZWSCvvo(|yip@>ZcNvK%OwQ)vWq6UHRU0qtHb)V1i8 zYq||hpk4jHYy_BX!jBlyCe0lg$5fe9VJLkOEk-T5AY>8UN*U2+}om~pD)T&E1& z!S2J=cU@i3d>m~)+&}gdTeX0%|H{TH5G%C}PoS2}21KL)7~8=!O3vOt-nhzf$i|gc znwy!>qSRn;B#5PJYAHt$;4hYz|28ZJMq$lcmVT=W3Z|6=uPeoj<$c%wF-Xm{*w}BjgT_+<+KVC~C>75OVph zvaiPV?Asiaw0TbIK5w^Pp=Q~Aaoc9|HX8(!5mI2_1M|^Mg42Ee=;hDNMPWrx$qcj( z2x?Me_m3uwAMrCv-r8i*7knlFKw&fs{@4=VP7~)n&hCL=`2CccXx4=Oi6D!F zCZPIi#u~(nS|&U3H*p5+gnr(d5<3!nq?5vih!yR%wtv_L4!PNs5d;CcaIBa?< z_G?y)uq6q?B>mRnbv)<0Q=k0v1SuEzHzs(iI%?u(ORrnNviWVZoukAZ2U&n+1yb2<)rqF z!S)ACO1w5>n!j9m``&1bqn_hmD|)OfUgqI`L0&aHH6-5`Mwy@3`W$QZ-f!hK|8ES( zJ0Z4uhcE6wVxWP|^CPx_+{u|7$yue`r(}Rz0r{czleVMSoc>s#zf{1|)amEF3Xg?c;k2>q_4K_xW#YnIH zq!t=nm6H;yYM5|^t+Ic|GpKO#-Mr|tSicRT%$Q1*hD@5whSoa~-<@&>x08gI9a35% z*4RX-V+RsW0hS7oS&c+`OwhcxA;Xa*TF2X1nhM|b!CYB0g|v5Qf-1m>PncP|z|-6j zq>y)p>KVGLek(r!94ic)0jgBilhdAU6z0M6@*VItxg2)U?C#gdQ+@ad8>|v@0@?@d zU01y9AXTC>80);KpKr>L`0NatVpLkLYdIvb+D$kC0xNCp!rA&7OcbKIa}*WcG$RcZzb+-tcpP{R{w36+_E{k3f!zbrd=j~K9F z1E9;ef{g22i@bLyb4ZVETA;f(LL0+B`pF=#ty|Rs1^5~$E+-X%$2V}O9fXNU`*UE2 zP>ae`YMO1Wd!7R!vk}DG{N;L&M4e0}t4Fc44-+ia(|LuP0yV&=Ru7^2M9a)gymI5v z6K?+MAGV|a%PF*9Ny6%Ag()o*fRDztUh6X&L^}lT zL-V>iDqDT*IUSQx{X~Q+zy~`ofiFH_*Ps8^S&%Q()Bwa_YU?6|e&De}Sa`#sFu8!sgRe<^;6ZhL|MOKtsr1%_}6!&dfU7JUoyA)3j}*Go`DFG6THGn%%`PN(5c{qFB;eCG5)6GfM2!_8QEymM&SK@$l^t z4~aGXPON*O_j+JHNm=Y!*LM$OLOr4xtqio0nMM0%+;AGjV!(HF0u*&KPMD)nJ@zzR zLzP)+H7tcWb)E{&)#XU#yS9wz_I%SS*pN&Ld3uhPuO2_hjdLM{W=?w-=_~pkn__L~ z>{_u`fctc&yKYi&%5JfpiM33zJ<+rvHPn zV}9B>e7L#|)d~L9IVZwpDjafI0k`#qcDE7)TBRwq@uu{;L_N6w34^(aLn&RAkFYWf z&|>e+izbmJR-N&)5?b7>eWk-h_o_jl{WGs-#pb251&T3c4RabhsBlR2oEz=9i@wk*n-VlK7?U-Vjnc;Wjv^^OMOKg{AptpAKN0M{p z=6+x+7X6}(#}m#{d=cid(wXw;fkCd&xGwO}-QZreR+n3)wLGW%a+^_E|q=e7{&8E1FcM$81b{Ge?_#Cro8H z(E9%cP_N9(E1RQg^SEzSz-}!gqKHeluTbbji70_ zWr&>q=(eesV43ZRXW|q?wQsx0TEBAPS3Bru#2&u)gTPTVyfZ^qdyknm|LJ&!vy>A@ z^YI%gdO5wd>Jzh^?C{Qt`2ndE%U?)fP;h-2)X-CHPE=^!tBu`eZX1Xv17{j~0h=V@0^0 z5^IAWYVJfRWf~~XarJP;H&vG5n0B~cPHym7S=_4vu=F6&MmMXo^p#zJ$pfi z4DifWsdUE}MLliJ3Y||vI?vqWZ6*jVc~ccSMi4lb6Tzf9A)Tyfp7Es(@#afop-5lp z9Pl)7BJg1Aw2OiX-I6=y=m=_d~aG zvYflT)7eqf!1mAN^F2-n*G&iTS#SB3j<`w2$kWRC(LVc7_39DdG&ufeK#Sv@nLl;} z+xw#*CPl<=NLrea*wiG};_9DgvwU4-W{RQ1sDZvef5RtDd?MQV{ErE+BisL21++uM zu6QW2VbfBp?}ty6LS7d+TiZrVWH7prCT=LCp}v(VD`ELF$*A~;lXSjUWE zF`q;^Ko$3*3qOxDTB=jrZ=EpdhbwXfN@hIQ-{yPg$0Q04(9{iiFO5Ns<8CssPoM56 zB6ln2NwaK+npfP${-Id;fFmz)Ev$mOf&tN>C26~e=7 zL#?@y9(Ft%jfLygBID#|`Kd&K-*f`=huU>rP)yViDts>!=!trsX<30*b-ouRxk+0* zA|lb5o;+Rc=G`ZJA7-$-v~Yz?RsT>NKqR?**N%sIW+rj)maB%}1YfR?e_(M*oFRAHjz@}vj;_WWUlMJnO}J1~IZpaG^M1y0i8QG<*y zx3PSx>7f6eJqf`6?!jhd;wU?ccjj)mR1Nno;8YmAf)mEb+MZ$QG$249YuFZbPCNl) zljCDaNT(Kj8x8|hHDh5-K$ajU$uis()vfaGtf{g;xOu3bHsJCMpS=a5sDa%9;@Jb) z$7Z;nST@*~`2-7YOj<9G-Oc8$WD$`v#NuAt-zR)68c>fcy{apu!xx1Q)xAivtYaX_ zq7Zl1C2VL9Bw~Bpcw3DDMI+q9_nH}0`(e!I6*U zYVTXiFfr$|>%aSbOxVv)={)rbHQF4E6TK4{InVin6uU1LEr!H` zG}_b{ol2Qg8pBjHSdk_1L#>?VG{|KTQM(uD6)T!l`mMUMc|$Af>CA+)+V!rqihy zYkI0UATqlXABl(F%>(O=`d~q&fTC~iM`-j2g4ofPsi}$F{7#770)%2`T8b(5fl>An zfNA?s?l!*wM>TaM*%Ut}Ls4$n%h9z^BC?!juThVk+`{*TL;414?dbjuq$v94K~1*7 z62}+D1iC%a_@z5HQkdAqEG4mum_i-*MfQt{0phg2&xbmtV1S~|c|#sDA0bRr6%1*_ z^OTZ9z0?+`U{BdX#EOd24ys*=LrRIY9=LR%W`?Dw7->dw^OAmx8M%1kqX3Mjq6W1B z*iA4CVa5hSt&32BJyt z-aF$)VqZq|_t+K7u{RK$l`Yq8l@nF2QC?6CO ziHjLvTDS_`uQ>DD@3X@gwodZnz+ybD;n}&HSwa;YFGW`qr3EY}ic@Q7Q9RAV&Ruv8Gih)v^>Z$I z$vZqa6zf#c2DJ%*a= z-=>ug((oFJ${Zs4-0>uUMf(OpUd0i`J!HP|XGV*Tc|Xda18dTYWyM_w0)G1E=c30u z@`Tv(Pdrc2P4peSi9NO+;<(HX12}rwO0rR=QJZ*EsmrHdR&yJdS#)5|0sT&m++nZ$ zQC;nGd|v_(8DqZ{J4vG>!;ZtxV5cDCc?+OOrAyAN|t1qi&zkA!3SX~%<@Z4WC({#yt+1h!qrD6BZPQZWfs5=4?A?y^vb4X<`bJdwEc!i@#*NHMP*ja_!szF`>5{WIu$MV8x=|o zwtQv_tX!O#4i2LrgV)jqg%OWIVu(ZU8NAQ%PC!8iNkc#gf=pk&*9i#3_(xKL~(sNV(P4>Uk#7ZTHq@KH$2B zm!DNnI3$mXOccJWC8<&cc`N&^=*)rH5O!4dqpPy1I@gBr`}xLcGSjEv^zrgI-YQ%0 zY*;*VU-&oY9P=-8iw`BDpRQ7RE2{e54fI4(ZX>+99bV=`BwdIFM*1YoZ%ahtO~D_q z=|=IOux?7!nw-ia3pUV2(QyQiRKMU`HJg^j`Y52xbi z%=96aclKb*Jkc+78H{O3Oqw+j>PTvGvc0J!0-wU@&3Q!AJwc8?_G~?m@koO0Pkwo{ z)%HUVvn8=Z;zMrYkVfW1@KKYGt4NfUr8QTVH-;BdPcdWU)^A$i%;OScv2xo}{eYEf ztb30&u?P9dnrmSx%Gcqk@5uOFWL^(p>edIHSm2J)?ya|Pade!UMTQTREALPLA}#v; z7dHasjU{>+b1b-iwr;6DK<@F$Y5Wgv`e55cJP}?1o-1 zbsOcf=8>Qloxuk@C!!H1J#&;wjO(QskU9#-G+M0zWx+Wi2Z_l=6a~2Y81845o3zq@ z+t8IHJ`T5f&>NOmTT;YUS|ir$p9{;+qlZIS0ZmAjo39}u^8(R$2Be^mCs8E_bR_MJ z)cm?x6%PY*-0SB5|3_5t{|4-82PBZ;x+?Z;NL9ZIaxdma?uh@tsNO<63qe=fvba4S zRhUb6JH7k*N|(Uog5cjR@p^rO=I=IvmDf1=z7Dm?Yba^TWqKx(L95om^HohE?n;e2 z?GV1BjGLr^_IKX^fF=aiv~?sO$ro6MG}c$?_w~dPUya^x^aaKe^j@-MeEiS9K}fFo zw{lEQXC3A0kIt+!MW~pKWJ1gaw4W2WfgbeLM2Pq;>IiWC8{Y^CpGY%+EoL|~!$YBr z59C3&E2`nD;d{o8E@`PjYDQsrPJb(hu=s>5#*4>a$W`jB@Al40?CB!z+I_Tue9*)& V3ArEafJTFa^6EoE{wI_h?O!-)7>WP@ literal 0 HcmV?d00001 diff --git a/utils/frame/remote-externalities/test_data/proxy_test.top b/utils/frame/remote-externalities/test_data/proxy_test.top deleted file mode 100644 index 548ce9cdba4f157e3ff0018d314f3fecfbed9f8f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39 vcmZQ+kl?)D>{e98_qB(Af%W>u%I&?*zKN<^Se*X}{?*hKULwHEz`y_iDLxHx diff --git a/utils/frame/rpc/client/src/lib.rs b/utils/frame/rpc/client/src/lib.rs index a211fc6c69..a6f73ba678 100644 --- a/utils/frame/rpc/client/src/lib.rs +++ b/utils/frame/rpc/client/src/lib.rs @@ -46,6 +46,7 @@ pub use jsonrpsee::{ core::{ client::{ClientT, Subscription, SubscriptionClientT}, params::BatchRequestBuilder, + Error, RpcResult, }, rpc_params, ws_client::{WsClient, WsClientBuilder}, diff --git a/utils/frame/try-runtime/cli/Cargo.toml b/utils/frame/try-runtime/cli/Cargo.toml index 2b095fc941..d6f211392c 100644 --- a/utils/frame/try-runtime/cli/Cargo.toml +++ b/utils/frame/try-runtime/cli/Cargo.toml @@ -12,11 +12,6 @@ description = "Cli command runtime testing and dry-running" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -clap = { version = "4.0.9", features = ["derive"] } -log = "0.4.17" -parity-scale-codec = "3.0.0" -serde = "1.0.136" -zstd = { version = "0.11.2", default-features = false } remote-externalities = { version = "0.10.0-dev", path = "../../remote-externalities", package = "frame-remote-externalities" } sc-chain-spec = { version = "4.0.0-dev", path = "../../../../client/chain-spec" } sc-cli = { version = "0.10.0-dev", path = "../../../../client/cli" } @@ -27,16 +22,27 @@ sp-externalities = { version = "0.13.0", path = "../../../../primitives/external sp-io = { version = "7.0.0", path = "../../../../primitives/io" } sp-keystore = { version = "0.13.0", path = "../../../../primitives/keystore" } sp-runtime = { version = "7.0.0", path = "../../../../primitives/runtime" } +sp-rpc = { version = "6.0.0", path = "../../../../primitives/rpc" } sp-state-machine = { version = "0.13.0", path = "../../../../primitives/state-machine" } sp-version = { version = "5.0.0", path = "../../../../primitives/version" } +sp-debug-derive = { path = "../../../../primitives/debug-derive" } +sp-api = { path = "../../../../primitives/api" } sp-weights = { version = "4.0.0", path = "../../../../primitives/weights" } frame-try-runtime = { optional = true, path = "../../../../frame/try-runtime" } substrate-rpc-client = { path = "../../rpc/client" } +parity-scale-codec = "3.0.0" +hex = "0.4.3" +clap = { version = "4.0.9", features = ["derive"] } +log = "0.4.17" +serde = "1.0.136" +zstd = { version = "0.11.2", default-features = false } + [dev-dependencies] tokio = "1.22.0" [features] try-runtime = [ + "sp-debug-derive/force-debug", "frame-try-runtime/try-runtime", ] diff --git a/utils/frame/try-runtime/cli/src/commands/create_snapshot.rs b/utils/frame/try-runtime/cli/src/commands/create_snapshot.rs new file mode 100644 index 0000000000..ef39c3d984 --- /dev/null +++ b/utils/frame/try-runtime/cli/src/commands/create_snapshot.rs @@ -0,0 +1,78 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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::{build_executor, LiveState, SharedParams, State, LOG_TARGET}; +use sc_executor::sp_wasm_interface::HostFunctions; +use sp_runtime::traits::{Block as BlockT, NumberFor}; +use std::{fmt::Debug, str::FromStr}; +use substrate_rpc_client::{ws_client, StateApi}; + +/// Configurations of the [`crate::Command::CreateSnapshot`]. +#[derive(Debug, Clone, clap::Parser)] +pub struct CreateSnapshotCmd { + /// The source of the snapshot. Must be a remote node. + #[clap(flatten)] + pub from: LiveState, + + /// The snapshot path to write to. + /// + /// If not provided `-@.snap` will be used. + pub snapshot_path: Option, +} + +/// inner command for `Command::CreateSnapshot`. +pub(crate) async fn create_snapshot( + shared: SharedParams, + command: CreateSnapshotCmd, +) -> sc_cli::Result<()> +where + Block: BlockT + serde::de::DeserializeOwned, + Block::Hash: FromStr + serde::de::DeserializeOwned, + Block::Header: serde::de::DeserializeOwned, + ::Err: Debug, + NumberFor: FromStr, + as FromStr>::Err: Debug, + HostFns: HostFunctions, +{ + let snapshot_path = command.snapshot_path; + if !matches!(shared.runtime, crate::Runtime::Existing) { + return Err("creating a snapshot is only possible with --runtime existing.".into()) + } + + let path = match snapshot_path { + Some(path) => path, + None => { + let rpc = ws_client(&command.from.uri).await.unwrap(); + let remote_spec = StateApi::::runtime_version(&rpc, None).await.unwrap(); + let path_str = format!( + "{}-{}@{}.snap", + remote_spec.spec_name.to_lowercase(), + remote_spec.spec_version, + command.from.at.clone().unwrap_or("latest".to_owned()) + ); + log::info!(target: LOG_TARGET, "snapshot path not provided (-s), using '{}'", path_str); + path_str.into() + }, + }; + + let executor = build_executor::(&shared); + let _ = State::Live(command.from) + .into_ext::(&shared, &executor, Some(path.into())) + .await?; + + Ok(()) +} diff --git a/utils/frame/try-runtime/cli/src/commands/execute_block.rs b/utils/frame/try-runtime/cli/src/commands/execute_block.rs index 56d88b9cb8..80d34002fa 100644 --- a/utils/frame/try-runtime/cli/src/commands/execute_block.rs +++ b/utils/frame/try-runtime/cli/src/commands/execute_block.rs @@ -16,30 +16,25 @@ // limitations under the License. use crate::{ - build_executor, ensure_matching_spec, extract_code, full_extensions, hash_of, local_spec, - state_machine_call_with_proof, SharedParams, State, LOG_TARGET, + build_executor, full_extensions, rpc_err_handler, state_machine_call_with_proof, LiveState, + SharedParams, State, LOG_TARGET, }; use parity_scale_codec::Encode; -use sc_service::{Configuration, NativeExecutionDispatch}; -use sp_core::storage::well_known_keys; -use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor}; +use sc_executor::sp_wasm_interface::HostFunctions; +use sp_rpc::{list::ListOrValue, number::NumberOrHex}; +use sp_runtime::{ + generic::SignedBlock, + traits::{Block as BlockT, Header as HeaderT, NumberFor}, +}; use std::{fmt::Debug, str::FromStr}; use substrate_rpc_client::{ws_client, ChainApi}; -/// Configurations of the [`Command::ExecuteBlock`]. +/// Configurations of the [`crate::Command::ExecuteBlock`]. /// /// This will always call into `TryRuntime_execute_block`, which can optionally skip the state-root /// check (useful for trying a unreleased runtime), and can execute runtime sanity checks as well. #[derive(Debug, Clone, clap::Parser)] pub struct ExecuteBlockCmd { - /// Overwrite the wasm code in state or not. - #[arg(long)] - overwrite_wasm_code: bool, - - /// If set the state root check is disabled. - #[arg(long)] - no_state_root_check: bool, - /// Which try-state targets to execute when running this command. /// /// Expected values: @@ -49,69 +44,28 @@ pub struct ExecuteBlockCmd { /// `Staking, System`). /// - `rr-[x]` where `[x]` is a number. Then, the given number of pallets are checked in a /// round-robin fashion. - #[arg(long, default_value = "none")] - try_state: frame_try_runtime::TryStateSelect, - - /// The block hash at which to fetch the block. - /// - /// If the `live` state type is being used, then this can be omitted, and is equal to whatever - /// the `state::at` is. Only use this (with care) when combined with a snapshot. - #[arg( - long, - value_parser = crate::parse::hash - )] - block_at: Option, + #[arg(long, default_value = "all")] + pub try_state: frame_try_runtime::TryStateSelect, /// The ws uri from which to fetch the block. /// - /// If the `live` state type is being used, then this can be omitted, and is equal to whatever - /// the `state::uri` is. Only use this (with care) when combined with a snapshot. + /// This will always fetch the next block of whatever `state` is referring to, because this is + /// the only sensible combination. In other words, if you have the state of block `n`, you + /// should execute block `n+1` on top of it. + /// + /// If `state` is `Live`, this can be ignored and the same uri is used for both. #[arg( long, value_parser = crate::parse::url )] - block_ws_uri: Option, + pub block_ws_uri: Option, /// The state type to use. - /// - /// For this command only, if the `live` is used, then state of the parent block is fetched. - /// - /// If `block_at` is provided, then the [`State::Live::at`] is being ignored. #[command(subcommand)] - state: State, + pub state: State, } impl ExecuteBlockCmd { - async fn block_at(&self, ws_uri: String) -> sc_cli::Result - where - Block::Hash: FromStr + serde::de::DeserializeOwned, - ::Err: Debug, - Block::Header: serde::de::DeserializeOwned, - { - let rpc = ws_client(&ws_uri).await?; - - match (&self.block_at, &self.state) { - (Some(block_at), State::Snap { .. }) => hash_of::(block_at), - (Some(block_at), State::Live { .. }) => { - log::warn!(target: LOG_TARGET, "--block-at is provided while state type is live. the `Live::at` will be ignored"); - hash_of::(block_at) - }, - (None, State::Live { at: None, .. }) => { - log::warn!( - target: LOG_TARGET, - "No --block-at or --at provided, using the latest finalized block instead" - ); - ChainApi::<(), Block::Hash, Block::Header, ()>::finalized_head(&rpc) - .await - .map_err(|e| e.to_string().into()) - }, - (None, State::Live { at: Some(at), .. }) => hash_of::(at), - _ => { - panic!("either `--block-at` must be provided, or state must be `live with a proper `--at``"); - }, - } - } - fn block_ws_uri(&self) -> String where Block::Hash: FromStr, @@ -123,7 +77,7 @@ impl ExecuteBlockCmd { log::error!(target: LOG_TARGET, "--block-uri is provided while state type is live, Are you sure you know what you are doing?"); block_ws_uri.to_owned() }, - (None, State::Live { uri, .. }) => uri.clone(), + (None, State::Live(LiveState { uri, .. })) => uri.clone(), (None, State::Snap { .. }) => { panic!("either `--block-uri` must be provided, or state must be `live`"); }, @@ -131,10 +85,9 @@ impl ExecuteBlockCmd { } } -pub(crate) async fn execute_block( +pub(crate) async fn execute_block( shared: SharedParams, command: ExecuteBlockCmd, - config: Configuration, ) -> sc_cli::Result<()> where Block: BlockT + serde::de::DeserializeOwned, @@ -142,79 +95,77 @@ where ::Err: Debug, Block::Hash: serde::de::DeserializeOwned, Block::Header: serde::de::DeserializeOwned, - NumberFor: FromStr, - as FromStr>::Err: Debug, - ExecDispatch: NativeExecutionDispatch + 'static, + as TryInto>::Error: Debug, + HostFns: HostFunctions, { - let executor = build_executor::(&shared, &config); - let execution = shared.execution; + let executor = build_executor::(&shared); + let ext = command.state.into_ext::(&shared, &executor, None).await?; + // get the block number associated with this block. let block_ws_uri = command.block_ws_uri::(); - let block_at = command.block_at::(block_ws_uri.clone()).await?; let rpc = ws_client(&block_ws_uri).await?; - let block: Block = ChainApi::<(), Block::Hash, Block::Header, _>::block(&rpc, Some(block_at)) - .await - .unwrap() - .unwrap(); - let parent_hash = block.header().parent_hash(); - log::info!( - target: LOG_TARGET, - "fetched block #{:?} from {:?}, parent_hash to fetch the state {:?}", - block.header().number(), - block_ws_uri, - parent_hash - ); - - let ext = { - let builder = command - .state - .builder::()? - // make sure the state is being build with the parent hash, if it is online. - .overwrite_online_at(parent_hash.to_owned()) - .state_version(shared.state_version); - - let builder = if command.overwrite_wasm_code { - log::info!( - target: LOG_TARGET, - "replacing the in-storage :code: with the local code from {}'s chain_spec (your local repo)", - config.chain_spec.name(), - ); - let (code_key, code) = extract_code(&config.chain_spec)?; - builder.inject_hashed_key_value(&[(code_key, code)]) - } else { - builder.inject_hashed_key(well_known_keys::CODE) - }; - - builder.build().await? - }; + let next_hash = next_hash_of::(&rpc, ext.block_hash).await?; + + log::info!(target: LOG_TARGET, "fetching next block: {:?} ", next_hash); + + let block = ChainApi::<(), Block::Hash, Block::Header, SignedBlock>::block( + &rpc, + Some(next_hash), + ) + .await + .map_err(rpc_err_handler)? + .expect("header exists, block should also exist; qed") + .block; // A digest item gets added when the runtime is processing the block, so we need to pop // the last one to be consistent with what a gossiped block would contain. let (mut header, extrinsics) = block.deconstruct(); header.digest_mut().pop(); let block = Block::new(header, extrinsics); - let payload = (block.clone(), !command.no_state_root_check, command.try_state).encode(); - - let (expected_spec_name, expected_spec_version, _) = - local_spec::(&ext, &executor); - ensure_matching_spec::( - block_ws_uri.clone(), - expected_spec_name, - expected_spec_version, - shared.no_spec_check_panic, - ) - .await; - let _ = state_machine_call_with_proof::( + // for now, hardcoded for the sake of simplicity. We might customize them one day. + let state_root_check = false; + let signature_check = false; + let payload = (block.clone(), state_root_check, signature_check, command.try_state).encode(); + + let _ = state_machine_call_with_proof::( &ext, &executor, - execution, "TryRuntime_execute_block", &payload, full_extensions(), )?; - log::info!(target: LOG_TARGET, "Core_execute_block executed without errors."); - Ok(()) } + +pub(crate) async fn next_hash_of( + rpc: &substrate_rpc_client::WsClient, + hash: Block::Hash, +) -> sc_cli::Result +where + Block: BlockT + serde::de::DeserializeOwned, + Block::Header: serde::de::DeserializeOwned, +{ + let number = ChainApi::<(), Block::Hash, Block::Header, ()>::header(rpc, Some(hash)) + .await + .map_err(rpc_err_handler) + .and_then(|maybe_header| maybe_header.ok_or("header_not_found").map(|h| *h.number()))?; + + let next = number + sp_runtime::traits::One::one(); + + let next_hash = match ChainApi::<(), Block::Hash, Block::Header, ()>::block_hash( + rpc, + Some(ListOrValue::Value(NumberOrHex::Number( + next.try_into().map_err(|_| "failed to convert number to block number")?, + ))), + ) + .await + .map_err(rpc_err_handler)? + { + ListOrValue::Value(t) => t.expect("value passed in; value comes out; qed"), + _ => unreachable!(), + }; + + Ok(next_hash) +} diff --git a/utils/frame/try-runtime/cli/src/commands/follow_chain.rs b/utils/frame/try-runtime/cli/src/commands/follow_chain.rs index 1cc371c8f2..4eb3b3a8f3 100644 --- a/utils/frame/try-runtime/cli/src/commands/follow_chain.rs +++ b/utils/frame/try-runtime/cli/src/commands/follow_chain.rs @@ -16,32 +16,33 @@ // limitations under the License. use crate::{ - build_executor, ensure_matching_spec, extract_code, full_extensions, local_spec, parse, - state_machine_call_with_proof, SharedParams, LOG_TARGET, + build_executor, full_extensions, parse, rpc_err_handler, state_machine_call_with_proof, + LiveState, SharedParams, State, LOG_TARGET, }; use parity_scale_codec::{Decode, Encode}; -use remote_externalities::{Builder, Mode, OnlineConfig}; -use sc_executor::NativeExecutionDispatch; -use sc_service::Configuration; +use sc_executor::sp_wasm_interface::HostFunctions; use serde::{de::DeserializeOwned, Serialize}; use sp_core::H256; -use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor}; +use sp_runtime::{ + generic::SignedBlock, + traits::{Block as BlockT, Header as HeaderT, NumberFor}, +}; use std::{fmt::Debug, str::FromStr}; use substrate_rpc_client::{ws_client, ChainApi, FinalizedHeaders, Subscription, WsClient}; const SUB: &str = "chain_subscribeFinalizedHeads"; const UN_SUB: &str = "chain_unsubscribeFinalizedHeads"; -/// Configurations of the [`Command::FollowChain`]. +/// Configurations of the [`crate::Command::FollowChain`]. #[derive(Debug, Clone, clap::Parser)] pub struct FollowChainCmd { /// The url to connect to. #[arg(short, long, value_parser = parse::url)] - uri: String, + pub uri: String, /// If set, then the state root check is enabled. #[arg(long)] - state_root_check: bool, + pub state_root_check: bool, /// Which try-state targets to execute when running this command. /// @@ -52,12 +53,12 @@ pub struct FollowChainCmd { /// `Staking, System`). /// - `rr-[x]` where `[x]` is a number. Then, the given number of pallets are checked in a /// round-robin fashion. - #[arg(long, default_value = "none")] - try_state: frame_try_runtime::TryStateSelect, + #[arg(long, default_value = "all")] + pub try_state: frame_try_runtime::TryStateSelect, /// If present, a single connection to a node will be kept and reused for fetching blocks. #[arg(long)] - keep_connection: bool, + pub keep_connection: bool, } /// Start listening for with `SUB` at `url`. @@ -77,10 +78,9 @@ async fn start_subscribing( +pub(crate) async fn follow_chain( shared: SharedParams, command: FollowChainCmd, - config: Configuration, ) -> sc_cli::Result<()> where Block: BlockT + DeserializeOwned, @@ -89,26 +89,35 @@ where ::Err: Debug, NumberFor: FromStr, as FromStr>::Err: Debug, - ExecDispatch: NativeExecutionDispatch + 'static, + HostFns: HostFunctions, { - let mut maybe_state_ext = None; let (rpc, subscription) = start_subscribing::(&command.uri).await?; - - let (code_key, code) = extract_code(&config.chain_spec)?; - let executor = build_executor::(&shared, &config); - let execution = shared.execution; - let mut finalized_headers: FinalizedHeaders = FinalizedHeaders::new(&rpc, subscription); + let mut maybe_state_ext = None; + let executor = build_executor::(&shared); + while let Some(header) = finalized_headers.next().await { let hash = header.hash(); let number = header.number(); - let block: Block = ChainApi::<(), Block::Hash, Block::Header, _>::block(&rpc, Some(hash)) - .await - .unwrap() - .unwrap(); + let block = + ChainApi::<(), Block::Hash, Block::Header, SignedBlock>::block(&rpc, Some(hash)) + .await + .or_else(|e| { + if matches!(e, substrate_rpc_client::Error::ParseError(_)) { + log::error!( + "failed to parse the block format of remote against the local \ + codebase. The block format has changed, and follow-chain cannot run in \ + this case. Try running this command in a branch of your codebase that has \ + the same block format as the remote chain. For now, we replace the block with an empty one" + ); + } + Err(rpc_err_handler(e)) + })? + .expect("if header exists, block should also exist.") + .block; log::debug!( target: LOG_TARGET, @@ -120,49 +129,40 @@ where // create an ext at the state of this block, whatever is the first subscription event. if maybe_state_ext.is_none() { - let builder = Builder::::new() - .mode(Mode::Online(OnlineConfig { - transport: command.uri.clone().into(), - at: Some(*header.parent_hash()), - ..Default::default() - })) - .state_version(shared.state_version); - - let new_ext = builder - .inject_hashed_key_value(&[(code_key.clone(), code.clone())]) - .build() - .await?; - log::info!( - target: LOG_TARGET, - "initialized state externalities at {:?}, storage root {:?}", - number, - new_ext.as_backend().root() - ); - - let (expected_spec_name, expected_spec_version, spec_state_version) = - local_spec::(&new_ext, &executor); - ensure_matching_spec::( - command.uri.clone(), - expected_spec_name, - expected_spec_version, - shared.no_spec_check_panic, - ) - .await; - - maybe_state_ext = Some((new_ext, spec_state_version)); + let state = State::Live(LiveState { + uri: command.uri.clone(), + // a bit dodgy, we have to un-parse the has to a string again and re-parse it + // inside. + at: Some(hex::encode(header.parent_hash().encode())), + pallet: vec![], + child_tree: true, + }); + let ext = state.into_ext::(&shared, &executor, None).await?; + maybe_state_ext = Some(ext); } - let (state_ext, spec_state_version) = + let state_ext = maybe_state_ext.as_mut().expect("state_ext either existed or was just created"); - let (mut changes, encoded_result) = state_machine_call_with_proof::( + let result = state_machine_call_with_proof::( state_ext, &executor, - execution, "TryRuntime_execute_block", (block, command.state_root_check, command.try_state.clone()).encode().as_ref(), full_extensions(), - )?; + ); + + if let Err(why) = result { + log::error!( + target: LOG_TARGET, + "failed to execute block {:?} due to {:?}", + number, + why + ); + continue + } + + let (mut changes, encoded_result) = result.expect("checked to be Ok; qed"); let consumed_weight = ::decode(&mut &*encoded_result) .map_err(|e| format!("failed to decode weight: {:?}", e))?; @@ -171,13 +171,13 @@ where .drain_storage_changes( &state_ext.backend, &mut Default::default(), - // Note that in case a block contains a runtime upgrade, - // state version could potentially be incorrect here, - // this is very niche and would only result in unaligned - // roots, so this use case is ignored for now. - *spec_state_version, + // Note that in case a block contains a runtime upgrade, state version could + // potentially be incorrect here, this is very niche and would only result in + // unaligned roots, so this use case is ignored for now. + state_ext.state_version, ) .unwrap(); + state_ext.backend.apply_transaction( storage_changes.transaction_storage_root, storage_changes.transaction, diff --git a/utils/frame/try-runtime/cli/src/commands/mod.rs b/utils/frame/try-runtime/cli/src/commands/mod.rs index 4861d94f07..ab0a066585 100644 --- a/utils/frame/try-runtime/cli/src/commands/mod.rs +++ b/utils/frame/try-runtime/cli/src/commands/mod.rs @@ -15,7 +15,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -pub(crate) mod execute_block; -pub(crate) mod follow_chain; -pub(crate) mod offchain_worker; -pub(crate) mod on_runtime_upgrade; +pub mod create_snapshot; +pub mod execute_block; +pub mod follow_chain; +pub mod offchain_worker; +pub mod on_runtime_upgrade; diff --git a/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs b/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs index 8d2585372b..c55de7da64 100644 --- a/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs +++ b/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs @@ -16,34 +16,18 @@ // limitations under the License. use crate::{ - build_executor, ensure_matching_spec, extract_code, full_extensions, hash_of, local_spec, - parse, state_machine_call, SharedParams, State, LOG_TARGET, + build_executor, commands::execute_block::next_hash_of, full_extensions, parse, rpc_err_handler, + state_machine_call, LiveState, SharedParams, State, LOG_TARGET, }; use parity_scale_codec::Encode; -use sc_executor::NativeExecutionDispatch; -use sc_service::Configuration; -use sp_core::storage::well_known_keys; -use sp_runtime::traits::{Block as BlockT, Header, NumberFor}; +use sc_executor::sp_wasm_interface::HostFunctions; +use sp_runtime::traits::{Block as BlockT, NumberFor}; use std::{fmt::Debug, str::FromStr}; use substrate_rpc_client::{ws_client, ChainApi}; -/// Configurations of the [`Command::OffchainWorker`]. +/// Configurations of the [`crate::Command::OffchainWorker`]. #[derive(Debug, Clone, clap::Parser)] pub struct OffchainWorkerCmd { - /// Overwrite the wasm code in state or not. - #[arg(long)] - overwrite_wasm_code: bool, - - /// The block hash at which to fetch the header. - /// - /// If the `live` state type is being used, then this can be omitted, and is equal to whatever - /// the `state::at` is. Only use this (with care) when combined with a snapshot. - #[arg( - long, - value_parser = parse::hash - )] - header_at: Option, - /// The ws uri from which to fetch the header. /// /// If the `live` state type is being used, then this can be omitted, and is equal to whatever @@ -52,7 +36,7 @@ pub struct OffchainWorkerCmd { long, value_parser = parse::url )] - header_ws_uri: Option, + pub header_ws_uri: Option, /// The state type to use. #[command(subcommand)] @@ -60,24 +44,6 @@ pub struct OffchainWorkerCmd { } impl OffchainWorkerCmd { - fn header_at(&self) -> sc_cli::Result - where - Block::Hash: FromStr, - ::Err: Debug, - { - match (&self.header_at, &self.state) { - (Some(header_at), State::Snap { .. }) => hash_of::(header_at), - (Some(header_at), State::Live { .. }) => { - log::error!(target: LOG_TARGET, "--header-at is provided while state type is live, this will most likely lead to a nonsensical result."); - hash_of::(header_at) - }, - (None, State::Live { at: Some(at), .. }) => hash_of::(at), - _ => { - panic!("either `--header-at` must be provided, or state must be `live` with a proper `--at`"); - }, - } - } - fn header_ws_uri(&self) -> String where Block::Hash: FromStr, @@ -89,7 +55,7 @@ impl OffchainWorkerCmd { log::error!(target: LOG_TARGET, "--header-uri is provided while state type is live, this will most likely lead to a nonsensical result."); header_ws_uri.to_owned() }, - (None, State::Live { uri, .. }) => uri.clone(), + (None, State::Live(LiveState { uri, .. })) => uri.clone(), (None, State::Snap { .. }) => { panic!("either `--header-uri` must be provided, or state must be `live`"); }, @@ -97,76 +63,42 @@ impl OffchainWorkerCmd { } } -pub(crate) async fn offchain_worker( +pub(crate) async fn offchain_worker( shared: SharedParams, command: OffchainWorkerCmd, - config: Configuration, ) -> sc_cli::Result<()> where Block: BlockT + serde::de::DeserializeOwned, - Block::Hash: FromStr, Block::Header: serde::de::DeserializeOwned, + Block::Hash: FromStr, ::Err: Debug, NumberFor: FromStr, as FromStr>::Err: Debug, - ExecDispatch: NativeExecutionDispatch + 'static, + HostFns: HostFunctions, { - let executor = build_executor(&shared, &config); - let execution = shared.execution; + let executor = build_executor(&shared); + // we first build the externalities with the remote code. + let ext = command.state.into_ext::(&shared, &executor, None).await?; - let header_at = command.header_at::()?; let header_ws_uri = command.header_ws_uri::(); let rpc = ws_client(&header_ws_uri).await?; - let header = ChainApi::<(), Block::Hash, Block::Header, ()>::header(&rpc, Some(header_at)) - .await - .unwrap() - .unwrap(); - log::info!( - target: LOG_TARGET, - "fetched header from {:?}, block number: {:?}", - header_ws_uri, - header.number() - ); - - let ext = { - let builder = command.state.builder::()?.state_version(shared.state_version); - - let builder = if command.overwrite_wasm_code { - log::info!( - target: LOG_TARGET, - "replacing the in-storage :code: with the local code from {}'s chain_spec (your local repo)", - config.chain_spec.name(), - ); - let (code_key, code) = extract_code(&config.chain_spec)?; - builder.inject_hashed_key_value(&[(code_key, code)]) - } else { - builder.inject_hashed_key(well_known_keys::CODE) - }; + let next_hash = next_hash_of::(&rpc, ext.block_hash).await?; + log::info!(target: LOG_TARGET, "fetching next header: {:?} ", next_hash); - builder.build().await? - }; - - let (expected_spec_name, expected_spec_version, _) = - local_spec::(&ext, &executor); - ensure_matching_spec::( - header_ws_uri, - expected_spec_name, - expected_spec_version, - shared.no_spec_check_panic, - ) - .await; + let header = ChainApi::<(), Block::Hash, Block::Header, ()>::header(&rpc, Some(next_hash)) + .await + .map_err(rpc_err_handler) + .map(|maybe_header| maybe_header.ok_or("Header does not exist"))??; + let payload = header.encode(); - let _ = state_machine_call::( + let _ = state_machine_call::( &ext, &executor, - execution, "OffchainWorkerApi_offchain_worker", - header.encode().as_ref(), + &payload, full_extensions(), )?; - log::info!(target: LOG_TARGET, "OffchainWorkerApi_offchain_worker executed without errors."); - Ok(()) } diff --git a/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs b/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs index fba34ddfb5..80fb5d31f7 100644 --- a/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs +++ b/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs @@ -15,31 +15,31 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::{fmt::Debug, str::FromStr}; - -use parity_scale_codec::Decode; -use sc_executor::NativeExecutionDispatch; -use sc_service::Configuration; +use crate::{build_executor, state_machine_call_with_proof, SharedParams, State, LOG_TARGET}; +use parity_scale_codec::{Decode, Encode}; +use sc_executor::sp_wasm_interface::HostFunctions; use sp_runtime::traits::{Block as BlockT, NumberFor}; use sp_weights::Weight; +use std::{fmt::Debug, str::FromStr}; -use crate::{ - build_executor, ensure_matching_spec, extract_code, local_spec, state_machine_call_with_proof, - SharedParams, State, LOG_TARGET, -}; - -/// Configurations of the [`Command::OnRuntimeUpgrade`]. +/// Configurations of the [`crate::Command::OnRuntimeUpgrade`]. #[derive(Debug, Clone, clap::Parser)] pub struct OnRuntimeUpgradeCmd { /// The state type to use. #[command(subcommand)] pub state: State, + + /// Execute `try_state`, `pre_upgrade` and `post_upgrade` checks as well. + /// + /// This will perform more checks, but it will also makes the reported PoV/Weight be + /// inaccurate. + #[clap(long)] + pub checks: bool, } -pub(crate) async fn on_runtime_upgrade( +pub(crate) async fn on_runtime_upgrade( shared: SharedParams, command: OnRuntimeUpgradeCmd, - config: Configuration, ) -> sc_cli::Result<()> where Block: BlockT + serde::de::DeserializeOwned, @@ -48,40 +48,22 @@ where Block::Header: serde::de::DeserializeOwned, NumberFor: FromStr, as FromStr>::Err: Debug, - ExecDispatch: NativeExecutionDispatch + 'static, + HostFns: HostFunctions, { - let executor = build_executor(&shared, &config); - let execution = shared.execution; - - let ext = { - let builder = command.state.builder::()?.state_version(shared.state_version); - let (code_key, code) = extract_code(&config.chain_spec)?; - builder.inject_hashed_key_value(&[(code_key, code)]).build().await? - }; + let executor = build_executor(&shared); + let ext = command.state.into_ext::(&shared, &executor, None).await?; - if let Some(uri) = command.state.live_uri() { - let (expected_spec_name, expected_spec_version, _) = - local_spec::(&ext, &executor); - ensure_matching_spec::( - uri, - expected_spec_name, - expected_spec_version, - shared.no_spec_check_panic, - ) - .await; - } - - let (_, encoded_result) = state_machine_call_with_proof::( + let (_, encoded_result) = state_machine_call_with_proof::( &ext, &executor, - execution, "TryRuntime_on_runtime_upgrade", - &[], + command.checks.encode().as_ref(), Default::default(), // we don't really need any extensions here. )?; let (weight, total_weight) = <(Weight, Weight) as Decode>::decode(&mut &*encoded_result) .map_err(|e| format!("failed to decode weight: {:?}", e))?; + log::info!( target: LOG_TARGET, "TryRuntime_on_runtime_upgrade executed without errors. Consumed weight = ({} ps, {} byte), total weight = ({} ps, {} byte) ({:.2} %, {:.2} %).", diff --git a/utils/frame/try-runtime/cli/src/lib.rs b/utils/frame/try-runtime/cli/src/lib.rs index f54354342b..47a9dfa3f6 100644 --- a/utils/frame/try-runtime/cli/src/lib.rs +++ b/utils/frame/try-runtime/cli/src/lib.rs @@ -22,7 +22,7 @@ //! > As the name suggests, `try-runtime` is a detailed testing framework that gives you a lot of //! control over what is being executed in which environment. It is recommended that user's first //! familiarize themselves with substrate in depth, particularly the execution model. It is critical -//! to deeply understand how the wasm/native interactions, and the runtime apis work in the +//! to deeply understand how the wasm/client/runtime interactions, and the runtime apis work in the //! substrate runtime, before commencing to working with `try-runtime`. //! //! #### Resources @@ -35,101 +35,102 @@ //! //! --- //! -//! ## Overview +//! ## Background Knowledge //! //! The basis of all try-runtime commands is the same: connect to a live node, scrape its *state* //! and put it inside a `TestExternalities`, then call into a *specific runtime-api* using the given //! state and some *runtime*. //! +//! Alternatively, the state could come from a snapshot file. +//! //! All of the variables in the above statement are made *italic*. Let's look at each of them: //! //! 1. **State** is the key-value pairs of data that comprise the canonical information that any //! blockchain is keeping. A state can be full (all key-value pairs), or be partial (only pairs -//! related to some pallets). Moreover, some keys are special and are not related to specific -//! pallets, known as [`well_known_keys`] in substrate. The most important of these is the -//! `:CODE:` key, which contains the code used for execution, when wasm execution is chosen. +//! related to some pallets/prefixes). Moreover, some keys are especial and are not related to +//! specific pallets, known as [`well_known_keys`] in substrate. The most important of these is +//! the `:CODE:` key, which contains the code used for execution, when wasm execution is chosen. //! //! 2. *A runtime-api* call is a call into a function defined in the runtime, *on top of a given //! state*. Each subcommand of `try-runtime` utilizes a specific *runtime-api*. //! //! 3. Finally, the **runtime** is the actual code that is used to execute the aforementioned -//! runtime-api. All substrate based chains always have two runtimes: native and wasm. The -//! decision of which one is chosen is non-trivial. First, let's look at the options: -//! -//! 1. Native: this means that the runtime that is **in your codebase**, aka whatever you see in -//! your editor, is being used. This runtime is easier for diagnostics. We refer to this as -//! the "local runtime". -//! -//! 2. Wasm: this means that whatever is stored in the `:CODE:` key of the state that your -//! scrape is being used. In plain sight, since the entire state (including `:CODE:`) is -//! scraped from a remote chain, you could conclude that the wasm runtime, if used, is always -//! equal to the canonical runtime of the live chain (i.e. NOT the "local runtime"). That's -//! factually true, but then the testing would be quite lame. Typically, with try-runtime, -//! you don't want to execute whatever code is already on the live chain. Instead, you want -//! your local runtime (which typically includes a non-released feature) to be used. This is -//! why try-runtime overwrites the wasm runtime (at `:CODE:`) with the local runtime as well. -//! That being said, this behavior can be controlled in certain subcommands with a special -//! flag (`--overwrite-wasm-code`). -//! -//! The decision of which runtime is eventually used is based on two facts: -//! -//! 1. `--execution` flag. If you specify `wasm`, then it is *always* wasm. If it is `native`, then -//! if and ONLY IF the spec versions match, then the native runtime is used. Else, wasm runtime -//! is used again. -//! 2. `--chain` flag (if present in your cli), which determines *which local runtime*, is selected. -//! This will specify: -//! 1. which native runtime is used, if you select `--execution Native` -//! 2. which wasm runtime is used to replace the `:CODE:`, if try-runtime is instructed to do -//! so. -//! -//! All in all, if the term "local runtime" is used in the rest of this crate's documentation, it -//! means either the native runtime, or the wasm runtime when overwritten inside `:CODE:`. In other -//! words, it means your... well, "local runtime", regardless of wasm or native. -//! -//! //! See [`Command`] for more information about each command's specific customization flags, and -//! assumptions regarding the runtime being used. +//! runtime-api. Everything in this crate assumes wasm execution, which means the runtime that +//! you use is the one stored onchain, namely under the `:CODE:` key. +//! +//! To recap, a typical try-runtime command does the following: +//! +//! 1. Download the state of a live chain, and write to an `externalities`. +//! 2. Overwrite the `:CODE:` with a given wasm blob +//! 3. Test some functionality via calling a runtime-api. +//! +//! ## Usage +//! +//! To use any of the provided commands, [`SharedParams`] must be provided. The most important of +//! which being [`SharedParams::runtime`], which specifies which runtime to use. Furthermore, +//! [`SharedParams::overwrite_state_version`] can be used to alter the state-version (see +//! for more info). +//! +//! Then, the specific command has to be specified. See [`Command`] for more information about each +//! command's specific customization flags, and assumptions regarding the runtime being used. +//! +//! Said briefly, this CLI is capable of executing: +//! +//! * [`Command::OnRuntimeUpgrade`]: execute all the `on_runtime_upgrade` hooks. +//! * [`Command::ExecuteBlock`]: re-execute the given block. +//! * [`Command::OffchainWorker`]: re-execute the given block's offchain worker code path. +//! * [`Command::FollowChain`]: continuously execute the blocks of a remote chain on top of a given +//! runtime. +//! * [`Command::CreateSnapshot`]: Create a snapshot file from a remote node. //! //! Finally, To make sure there are no errors regarding this, always run any `try-runtime` command //! with `executor=trace` logging targets, which will specify which runtime is being used per api -//! call. -//! -//! Furthermore, other relevant log targets are: `try-runtime::cli`, `remote-ext`, and `runtime`. +//! call. Moreover, `remote-ext`, `try-runtime` and `runtime` logs targets will also be useful. //! //! ## Spec name check //! //! A common pitfall is that you might be running some test on top of the state of chain `x`, with //! the runtime of chain `y`. To avoid this all commands do a spec-name check before executing -//! anything by default. This will check the spec name of the remote node your are connected to, -//! with the spec name of your local runtime and ensure that they match. +//! anything by default. This will check the, if any alterations are being made to the `:CODE:`, +//! then the spec names match. The spec versions are warned, but are not mandated to match. //! -//! Should you need to disable this on certain occasions, a top level flag of `--no-spec-name-check` -//! can be used. +//! > If anything, in most cases, we expect spec-versions to NOT match, because try-runtime is all +//! > about testing unreleased runtimes. //! -//! The spec version is also always inspected, but if it is a mismatch, it will only emit a warning. -//! -//! ## Note nodes that operate with `try-runtime` +//! ## Note on nodes that respond to `try-runtime` requests. //! //! There are a number of flags that need to be preferably set on a running node in order to work //! well with try-runtime's expensive RPC queries: //! -//! - set `--rpc-max-payload 1000` to ensure large RPC queries can work. -//! - set `--ws-max-out-buffer-capacity 1000` to ensure the websocket connection can handle large -//! RPC queries. +//! - set `--rpc-max-response-size 1000` and +//! - `--rpc-max-request-size 1000` to ensure connections are not dropped in case the state is +//! large. //! - set `--rpc-cors all` to ensure ws connections can come through. //! //! Note that *none* of the try-runtime operations need unsafe RPCs. //! -//! ## Migration Best Practices +//! ## Note on signature and state-root checks +//! +//! All of the commands calling into `TryRuntime_execute_block` ([`Command::ExecuteBlock`] and +//! [`Command::FollowChain`]) disable both state root and signature checks. This is because in 99% +//! of the cases, the runtime that is being tested is different from the one that is stored in the +//! canonical chain state. This implies: +//! +//! 1. the state root will NEVER match, because `:CODE:` is different between the two. +//! 2. replaying all transactions will fail, because the spec-version is part of the transaction +//! signature. +//! +//! ## Best Practices //! -//! One of the main use-cases of try-runtime is using it for testing storage migrations. The -//! following points makes sure you can *effectively* test your migrations with try-runtime. +//! Try-runtime is all about battle-testing unreleased runtime. The following list of suggestions +//! help developers maximize the testing coverage and make base use of `try-runtime`. //! //! #### Adding pre/post hooks //! //! One of the gems that come only in the `try-runtime` feature flag is the `pre_upgrade` and -//! `post_upgrade` hooks for `OnRuntimeUpgrade`. This trait is implemented either inside the -//! pallet, or manually in a runtime, to define a migration. In both cases, these functions can be -//! added, given the right flag: +//! `post_upgrade` hooks for `OnRuntimeUpgrade`. This trait is implemented either inside the pallet, +//! or manually in a runtime, to define a migration. In both cases, these functions can be added, +//! given the right flag: //! //! ```ignore //! @@ -147,6 +148,19 @@ //! encoded data (usually some pre-upgrade state) which will be passed to `post_upgrade` after //! upgrading and used for post checking. //! +//! ## State Consistency +//! +//! Similarly, each pallet can expose a function in `#[pallet::hooks]` section as follows: +//! +//! ``` +//! #[cfg(feature = try-runtime)] +//! fn try_state(_) -> Result<(), &'static str> {} +//! ``` +//! +//! which is called on numerous code paths in the try-runtime tool. These checks should ensure that +//! the state of the pallet is consistent and correct. See `frame_support::try_runtime::TryState` +//! for more info. +//! //! #### Logging //! //! It is super helpful to make sure your migration code uses logging (always with a `runtime` log @@ -161,216 +175,260 @@ //! //! ## Examples //! -//! Run the migrations of the local runtime on the state of polkadot, from the polkadot repo where -//! we have `--chain polkadot-dev`, on the latest finalized block's state +//! For the following examples, we assume the existence of the following: //! -//! ```sh -//! RUST_LOG=runtime=trace,try-runtime::cli=trace,executor=trace \ -//! cargo run try-runtime \ -//! --execution Native \ -//! --chain polkadot-dev \ -//! on-runtime-upgrade \ -//! live \ -//! --uri wss://rpc.polkadot.io -//! # note that we don't pass any --at, nothing means latest block. +//! 1. a substrate node compiled without `--feature try-runtime`, called `substrate`. This will be +//! the running node that you connect to. then, after some changes to this node, you compile it with +//! `--features try-runtime`. This gives you: +//! 2. a substrate binary that has the try-runtime sub-command enabled. +//! 3. a wasm blob that has try-runtime functionality. +//! +//! ```bash +//! # this is like your running deployed node. +//! cargo build --release && cp target/release/substrate . +//! +//! # this is like your WIP branch. +//! cargo build --release --features try-runtime +//! cp target/release/substrate substrate-try-runtime +//! cp ./target/release/wbuild/kitchensink-runtime/kitchensink_runtime.wasm runtime-try-runtime.wasm //! ``` //! -//! Same as previous one, but let's say we want to run this command from the substrate repo, where -//! we don't have a matching spec name/version. +//! > The above example is with `substrate`'s `kitchensink-runtime`, but is applicable to any +//! > substrate-based chain that has implemented `try-runtime-cli`. +//! +//! * If you run `try-runtime` subcommand against `substrate` binary listed above, you get the +//! following error. +//! +//! ```bash +//! [substrate] ./substrate try-runtime +//! Error: Input("TryRuntime wasn't enabled when building the node. You can enable it with `--features try-runtime`.") +//! ``` //! -//! ```sh -//! RUST_LOG=runtime=trace,try-runtime::cli=trace,executor=trace \ -//! cargo run try-runtime \ -//! --execution Native \ -//! --chain dev \ -//! --no-spec-name-check \ # mind this one! +//! * If you run the same against `substrate-try-runtime`, it will work. +//! +//! ```bash +//! [substrate] ./substrate-try-runtime try-runtime +//! Try some command against runtime state +//! +//! Usage: substrate-try-runtime try-runtime [OPTIONS] --runtime +//! +//! Commands: +//! on-runtime-upgrade Execute the migrations of the "local runtime" +//! execute-block Executes the given block against some state +//! offchain-worker Executes *the offchain worker hooks* of a given block against some state +//! follow-chain Follow the given chain's finalized blocks and apply all of its extrinsics +//! create-snapshot Create a new snapshot file +//! help Print this message or the help of the given subcommand(s) +//! +//! Options: +//! --chain +//! Specify the chain specification +//! --dev +//! Specify the development chain +//! -d, --base-path +//! Specify custom base path +//! -l, --log ... +//! Sets a custom logging filter. Syntax is `=`, e.g. -lsync=debug +//! --detailed-log-output +//! Enable detailed log output +//! --disable-log-color +//! Disable log color output +//! --enable-log-reloading +//! Enable feature to dynamically update and reload the log filter +//! --tracing-targets +//! Sets a custom profiling filter. Syntax is the same as for logging: `=` +//! --tracing-receiver +//! Receiver to process tracing messages [default: log] [possible values: log] +//! --runtime +//! The runtime to use +//! --wasm-execution +//! Type of wasm execution used [default: compiled] [possible values: interpreted-i-know-what-i-do, compiled] +//! --wasm-instantiation-strategy +//! The WASM instantiation method to use [default: pooling-copy-on-write] [possible values: pooling-copy-on-write, recreate-instance-copy-on-write, pooling, recreate-instance, legacy-instance-reuse] +//! --heap-pages +//! The number of 64KB pages to allocate for Wasm execution. Defaults to [`sc_service::Configuration.default_heap_pages`] +//! --overwrite-state-version +//! Overwrite the `state_version` +//! -h, --help +//! Print help information (use `--help` for more detail) +//! -V, --version +//! Print version information +//! ``` +//! +//! * Run the migrations of a given runtime on top of a live state. +//! +//! ```bash +//! # assuming there's `./substrate --dev --tmp --ws-port 9999` or similar running. +//! ./substrate-try-runtime \ +//! try-runtime \ +//! --runtime kitchensink_runtime.wasm \ +//! -lruntime=debug \ //! on-runtime-upgrade \ -//! live \ -//! --uri wss://rpc.polkadot.io +//! live --uri ws://localhost:9999 //! ``` //! -//! Same as the previous one, but run it at specific block number's state. This means that this +//! * Same as the previous one, but run it at specific block number's state. This means that this //! block hash's state shall not yet have been pruned in `rpc.polkadot.io`. //! -//! ```sh -//! RUST_LOG=runtime=trace,try-runtime::cli=trace,executor=trace \ -//! cargo run try-runtime \ -//! --execution Native \ -//! --chain dev \ -//! --no-spec-name-check \ # mind this one! on-runtime-upgrade \ +//! ```bash +//! ./substrate-try-runtime \ +//! try-runtime \ +//! --runtime kitchensink_runtime.wasm \ +//! -lruntime=debug \ //! on-runtime-upgrade \ -//! live \ -//! --uri wss://rpc.polkadot.io \ -//! --at +//! live --uri ws://localhost:9999 \ +//! # replace with your desired block hash! +//! --at 0xa1b16c1efd889a9f17375ec4dd5c1b4351a2be17fa069564fced10d23b9b3836 //! ``` //! -//! Moving to `execute-block` and `offchain-workers`. For these commands, you always needs to -//! specify a block hash. For the rest of these examples, we assume we're in the polkadot repo. -//! -//! First, let's assume you are in a branch that has the same spec name/version as the live polkadot -//! network. -//! -//! ```sh -//! RUST_LOG=runtime=trace,try-runtime::cli=trace,executor=trace \ -//! cargo run try-runtime \ -//! --execution Wasm \ -//! --chain polkadot-dev \ -//! --uri wss://rpc.polkadot.io \ -//! execute-block \ -//! live \ -//! --at +//! * Executing the same command with the [`Runtime::Existing`] will fail because the existing +//! runtime, stored onchain in `substrate` binary that we compiled earlier does not have +//! `try-runtime` feature! +//! +//! ```bash +//! ./substrate-try-runtime try-runtime --runtime existing -lruntime=debug on-runtime-upgrade live --uri ws://localhost:9999 +//! ... +//! Error: Input("given runtime is NOT compiled with try-runtime feature!") //! ``` //! -//! This is wasm, so it will technically execute the code that lives on the live network. Let's say -//! you want to execute your local runtime. Since you have a matching spec versions, you can simply -//! change `--execution Wasm` to `--execution Native` to achieve this. Your logs of `executor=trace` -//! should show something among the lines of: +//! * Now, let's use a snapshot file. First, we create the snapshot: //! -//! ```text -//! Request for native execution succeeded (native: polkadot-9900 (parity-polkadot-0.tx7.au0), chain: polkadot-9900 (parity-polkadot-0.tx7.au0)) +//! ```bash +//! ./substrate-try-runtime try-runtime --runtime existing -lruntime=debug create-snapshot --uri ws://localhost:9999 +//! 2022-12-13 10:28:17.516 INFO main try-runtime::cli: snapshot path not provided (-s), using 'node-268@latest.snap' +//! 2022-12-13 10:28:17.516 INFO main remote-ext: since no at is provided, setting it to latest finalized head, 0xe7d0b614dfe89af65b33577aae46a6f958c974bf52f8a5e865a0f4faeb578d22 +//! 2022-12-13 10:28:17.516 INFO main remote-ext: since no prefix is filtered, the data for all pallets will be downloaded +//! 2022-12-13 10:28:17.550 INFO main remote-ext: writing snapshot of 1611464 bytes to "node-268@latest.snap" +//! 2022-12-13 10:28:17.551 INFO main remote-ext: initialized state externalities with storage root 0x925e4e95de4c08474fb7f976c4472fa9b8a1091619cd7820a793bf796ee6d932 and state_version V1 //! ``` //! -//! If you don't have matching spec versions, then are doomed to execute wasm. In this case, you can -//! manually overwrite the wasm code with your local runtime: -//! -//! ```sh -//! RUST_LOG=runtime=trace,try-runtime::cli=trace,executor=trace \ -//! cargo run try-runtime \ -//! --execution Wasm \ -//! --chain polkadot-dev \ -//! execute-block \ -//! live \ -//! --uri wss://rpc.polkadot.io \ -//! --at \ -//! --overwrite-wasm-code +//! > Note that the snapshot contains the `existing` runtime, which does not have the correct +//! > `try-runtime` feature. In the following commands, we still need to overwrite the runtime. +//! +//! Then, we can use it to have the same command as before, `on-runtime-upgrade` +//! +//! ```bash +//! try-runtime \ +//! --runtime runtime-try-runtime.wasm \ +//! -lruntime=debug \ +//! on-runtime-upgrade \ +//! snap -s node-268@latest.snap //! ``` //! -//! For all of these blocks, the block with hash `` is being used, and the initial state -//! is the state of the parent hash. This is because by omitting `ExecuteBlockCmd::block_at`, the -//! `--at` is used for both. This should be good enough for 99% of the cases. The only case where -//! you need to specify `block-at` and `block-ws-uri` is with snapshots. Let's say you have a file -//! `snap` and you know it corresponds to the state of the parent block of `X`. Then you'd do: -//! -//! ```sh -//! RUST_LOG=runtime=trace,try-runtime::cli=trace,executor=trace \ -//! cargo run try-runtime \ -//! --execution Wasm \ -//! --chain polkadot-dev \ -//! --uri wss://rpc.polkadot.io \ -//! execute-block \ -//! --block-at \ -//! --block-ws-uri wss://rpc.polkadot.io \ -//! --overwrite-wasm-code \ -//! snap \ -//! -s snap \ +//! * Execute the latest finalized block with the given runtime. +//! +//! ```bash +//! ./substrate-try-runtime try-runtime \ +//! --runtime runtime-try-runtime.wasm \ +//! -lruntime=debug \ +//! execute-block live \ +//! --uri ws://localhost:999 +//! ``` +//! +//! This can still be customized at a given block with `--at`. If you want to use a snapshot, you +//! can still use `--block-ws-uri` to provide a node form which the block data can be fetched. +//! +//! Moreover, this runs the `frame_support::try_runtime::TryState` hooks as well. The hooks to run +//! can be customized with the `--try-state`. For example: +//! +//! ```bash +//! ./substrate-try-runtime try-runtime \ +//! --runtime runtime-try-runtime.wasm \ +//! -lruntime=debug \ +//! execute-block live \ +//! --try-state System,Staking \ +//! --uri ws://localhost:999 +//! ``` +//! +//! Will only run the `try-state` of the two given pallets. See +//! [`frame_try_runtime::TryStateSelect`] for more information. +//! +//! * Follow our live chain's blocks using `follow-chain`, whilst running the try-state of 3 pallets +//! in a round robin fashion +//! +//! ```bash +//! ./substrate-try-runtime \ +//! try-runtime \ +//! --runtime runtime-try-runtime.wasm \ +//! -lruntime=debug \ +//! follow-chain \ +//! --uri ws://localhost:9999 \ +//! --try-state rr-3 //! ``` #![cfg(feature = "try-runtime")] use parity_scale_codec::Decode; use remote_externalities::{ - Builder, Mode, OfflineConfig, OnlineConfig, SnapshotConfig, TestExternalities, + Builder, Mode, OfflineConfig, OnlineConfig, RemoteExternalities, SnapshotConfig, + TestExternalities, }; -use sc_chain_spec::ChainSpec; use sc_cli::{ - execution_method_from_cli, CliConfiguration, ExecutionStrategy, WasmExecutionMethod, - WasmtimeInstantiationStrategy, DEFAULT_WASMTIME_INSTANTIATION_STRATEGY, - DEFAULT_WASM_EXECUTION_METHOD, + CliConfiguration, RuntimeVersion, WasmExecutionMethod, WasmtimeInstantiationStrategy, + DEFAULT_WASMTIME_INSTANTIATION_STRATEGY, DEFAULT_WASM_EXECUTION_METHOD, }; -use sc_executor::NativeElseWasmExecutor; -use sc_service::{Configuration, NativeExecutionDispatch}; +use sc_executor::{sp_wasm_interface::HostFunctions, WasmExecutor}; +use sp_api::HashT; use sp_core::{ + hexdisplay::HexDisplay, offchain::{ testing::{TestOffchainExt, TestTransactionPoolExt}, OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, }, - storage::{well_known_keys, StorageData, StorageKey}, + storage::well_known_keys, testing::TaskExecutor, - traits::TaskExecutorExt, + traits::{ReadRuntimeVersion, TaskExecutorExt}, twox_128, H256, }; use sp_externalities::Extensions; use sp_keystore::{testing::KeyStore, KeystoreExt}; use sp_runtime::{ - traits::{Block as BlockT, NumberFor}, + traits::{BlakeTwo256, Block as BlockT, NumberFor}, DeserializeOwned, }; -use sp_state_machine::{OverlayedChanges, StateMachine, TrieBackendBuilder}; +use sp_state_machine::{CompactProof, OverlayedChanges, StateMachine, TrieBackendBuilder}; use sp_version::StateVersion; use std::{fmt::Debug, path::PathBuf, str::FromStr}; -use substrate_rpc_client::{ws_client, StateApi}; -mod commands; +pub mod commands; pub(crate) mod parse; pub(crate) const LOG_TARGET: &str = "try-runtime::cli"; /// Possible commands of `try-runtime`. #[derive(Debug, Clone, clap::Subcommand)] pub enum Command { - /// Execute the migrations of the "local runtime". + /// Execute the migrations of the given runtime /// - /// This uses a custom runtime api call, namely "TryRuntime_on_runtime_upgrade". + /// This uses a custom runtime api call, namely "TryRuntime_on_runtime_upgrade". The code path + /// only triggers all of the `on_runtime_upgrade` hooks in the runtime, and optionally + /// `try_state`. /// - /// This always overwrites the wasm code with the local runtime (specified by `--chain`), to - /// ensure the new migrations are being executed. Re-executing already existing migrations is - /// evidently not very exciting. + /// See [`frame_try_runtime::TryRuntime`] and + /// [`commands::on_runtime_upgrade::OnRuntimeUpgradeCmd`] for more information. OnRuntimeUpgrade(commands::on_runtime_upgrade::OnRuntimeUpgradeCmd), /// Executes the given block against some state. /// - /// Unlike [`Command::OnRuntimeUpgrade`], this command needs two inputs: the state, and the - /// block data. Since the state could be cached (see [`State::Snap`]), different flags are - /// provided for both. `--block-at` and `--block-uri`, if provided, are only used for fetching - /// the block. For convenience, these flags can be both emitted, if the [`State::Live`] is - /// being used. - /// - /// Note that by default, this command does not overwrite the code, so in wasm execution, the - /// live chain's code is used. This can be disabled if desired, see - /// `ExecuteBlockCmd::overwrite_wasm_code`. + /// This uses a custom runtime api call, namely "TryRuntime_execute_block". Some checks, such + /// as state-root and signature checks are always disabled, and additional checks like + /// `try-state` can be enabled. /// - /// Note that if you do overwrite the wasm code, or generally use the local runtime for this, - /// you might - /// - not be able to decode the block, if the block format has changed. - /// - quite possibly will get a signature verification failure, since the spec and - /// transaction version are part of the signature's payload, and if they differ between - /// your local runtime and the remote counterparts, the signatures cannot be verified. - /// - almost certainly will get a state root mismatch, since, well, you are executing a - /// different state transition function. - /// - /// To make testing slightly more dynamic, you can disable the state root check by enabling - /// `ExecuteBlockCmd::no_check`. If you get signature verification errors, you should manually - /// tweak your local runtime's spec version to fix this. - /// - /// A subtle detail of execute block is that if you want to execute block 100 of a live chain - /// again, you need to scrape the state of block 99. This is already done automatically if you - /// use [`State::Live`], and the parent hash of the target block is used to scrape the state. - /// If [`State::Snap`] is being used, then this needs to be manually taken into consideration. - /// - /// This does not execute the same runtime api as normal block import do, namely - /// `Core_execute_block`. Instead, it uses `TryRuntime_execute_block`, which can optionally - /// skip state-root check (useful for trying a unreleased runtime), and can execute runtime - /// sanity checks as well. + /// See [`frame_try_runtime::TryRuntime`] and [`commands::execute_block::ExecuteBlockCmd`] for + /// more information. ExecuteBlock(commands::execute_block::ExecuteBlockCmd), /// Executes *the offchain worker hooks* of a given block against some state. /// - /// Similar to [`Command::ExecuteBlock`], this command needs two inputs: the state, and the - /// header data. Likewise, `--header-at` and `--header-uri` can be filled, or omitted if - /// [`State::Live`] is used. - /// - /// Similar to [`Command::ExecuteBlock`], this command does not overwrite the code, so in wasm - /// execution, the live chain's code is used. This can be disabled if desired, see - /// `OffchainWorkerCmd::overwrite_wasm_code`. - /// /// This executes the same runtime api as normal block import, namely /// `OffchainWorkerApi_offchain_worker`. + /// + /// See [`frame_try_runtime::TryRuntime`] and [`commands::offchain_worker::OffchainWorkerCmd`] + /// for more information. OffchainWorker(commands::offchain_worker::OffchainWorkerCmd), /// Follow the given chain's finalized blocks and apply all of its extrinsics. /// - /// This is essentially repeated calls to [`Command::ExecuteBlock`], whilst the local runtime - /// is always at use, the state root check is disabled, and the state is persisted between - /// executions. + /// This is essentially repeated calls to [`Command::ExecuteBlock`]. /// /// This allows the behavior of a new runtime to be inspected over a long period of time, with /// realistic transactions coming as input. @@ -382,7 +440,38 @@ pub enum Command { /// connections, starts listening for finalized block events. Upon first block notification, it /// initializes the state from the remote node, and starts applying that block, plus all the /// blocks that follow, to the same growing state. + /// + /// This can only work if the block format between the remote chain and the new runtime being + /// tested has remained the same, otherwise block decoding might fail. FollowChain(commands::follow_chain::FollowChainCmd), + + /// Create a new snapshot file. + CreateSnapshot(commands::create_snapshot::CreateSnapshotCmd), +} + +#[derive(Debug, Clone)] +pub enum Runtime { + /// Use the given path to the wasm binary file. + /// + /// It must have been compiled with `try-runtime`. + Path(PathBuf), + + /// Use the code of the remote node, or the snapshot. + /// + /// In almost all cases, this is not what you want, because the code in the remote node does + /// not have any of the try-runtime custom runtime APIs. + Existing, +} + +impl FromStr for Runtime { + type Err = String; + + fn from_str(s: &str) -> Result { + Ok(match s.to_lowercase().as_ref() { + "existing" => Runtime::Existing, + x @ _ => Runtime::Path(x.into()), + }) + } } /// Shared parameters of the `try-runtime` commands @@ -390,13 +479,23 @@ pub enum Command { #[group(skip)] pub struct SharedParams { /// Shared parameters of substrate cli. + /// + /// TODO: this is only needed because try-runtime is embedded in the substrate CLI. It should + /// go away. #[allow(missing_docs)] #[clap(flatten)] pub shared_params: sc_cli::SharedParams, - /// The execution strategy that should be used. - #[arg(long, value_name = "STRATEGY", value_enum, ignore_case = true, default_value_t = ExecutionStrategy::Wasm)] - pub execution: ExecutionStrategy, + /// The runtime to use. + /// + /// Must be a path to a wasm blob, compiled with `try-runtime` feature flag. + /// + /// Or, `existing`, indicating that you don't want to overwrite the runtime. This will use + /// whatever comes from the remote node, or the snapshot file. This will most likely not work + /// against a remote node, as no (sane) blockchain should compile its onchain wasm with + /// `try-runtime` feature. + #[arg(long)] + pub runtime: Runtime, /// Type of wasm execution used. #[arg( @@ -424,13 +523,11 @@ pub struct SharedParams { #[arg(long)] pub heap_pages: Option, - /// When enabled, the spec check will not panic, and instead only show a warning. - #[arg(long)] - pub no_spec_check_panic: bool, - - /// State version that is used by the chain. - #[arg(long, default_value_t = StateVersion::V1, value_parser = parse::state_version)] - pub state_version: StateVersion, + /// Overwrite the `state_version`. + /// + /// Otherwise `remote-externalities` will automatically set the correct state version. + #[arg(long, value_parser = parse::state_version)] + pub overwrite_state_version: Option, } /// Our `try-runtime` command. @@ -445,6 +542,41 @@ pub struct TryRuntimeCmd { pub command: Command, } +/// A `Live` variant [`State`] +#[derive(Debug, Clone, clap::Args)] +pub struct LiveState { + /// The url to connect to. + #[arg( + short, + long, + value_parser = parse::url, + )] + uri: String, + + /// The block hash at which to fetch the state. + /// + /// If non provided, then the latest finalized head is used. + #[arg( + short, + long, + value_parser = parse::hash, + )] + at: Option, + + /// A pallet to scrape. Can be provided multiple times. If empty, entire chain state will + /// be scraped. + #[arg(short, long, num_args = 1..)] + pallet: Vec, + + /// Fetch the child-keys as well. + /// + /// Default is `false`, if specific `--pallets` are specified, `true` otherwise. In other + /// words, if you scrape the whole state the child tree data is included out of the box. + /// Otherwise, it must be enabled explicitly using this flag. + #[arg(long)] + child_tree: bool, +} + /// The source of runtime *state* to use. #[derive(Debug, Clone, clap::Subcommand)] pub enum State { @@ -457,128 +589,167 @@ pub enum State { }, /// Use a live chain as the source of runtime state. - Live { - /// The url to connect to. - #[arg( - short, - long, - value_parser = parse::url, - )] - uri: String, - - /// The block hash at which to fetch the state. - /// - /// If non provided, then the latest finalized head is used. This is particularly useful - /// for [`Command::OnRuntimeUpgrade`]. - #[arg( - short, - long, - value_parser = parse::hash, - )] - at: Option, - - /// An optional state snapshot file to WRITE to. Not written if set to `None`. - #[arg(short, long)] - snapshot_path: Option, - - /// A pallet to scrape. Can be provided multiple times. If empty, entire chain state will - /// be scraped. - #[arg(short, long, num_args = 1..)] - pallet: Vec, - - /// Fetch the child-keys as well. - /// - /// Default is `false`, if specific `--pallets` are specified, `true` otherwise. In other - /// words, if you scrape the whole state the child tree data is included out of the box. - /// Otherwise, it must be enabled explicitly using this flag. - #[arg(long)] - child_tree: bool, - }, + Live(LiveState), } impl State { - /// Create the [`remote_externalities::Builder`] from self. - pub(crate) fn builder(&self) -> sc_cli::Result> + /// Create the [`remote_externalities::RemoteExternalities`] using [`remote-externalities`] from + /// self. + /// + /// This will override the code as it sees fit based on [`SharedParams::Runtime`]. It will also + /// check the spec-version and name. + pub(crate) async fn into_ext( + &self, + shared: &SharedParams, + executor: &WasmExecutor, + state_snapshot: Option, + ) -> sc_cli::Result> where Block::Hash: FromStr, + Block::Header: DeserializeOwned, + Block::Hash: DeserializeOwned, ::Err: Debug, { - Ok(match self { + let builder = match self { State::Snap { snapshot_path } => Builder::::new().mode(Mode::Offline(OfflineConfig { state_snapshot: SnapshotConfig::new(snapshot_path), })), - State::Live { snapshot_path, pallet, uri, at, child_tree } => { + State::Live(LiveState { pallet, uri, at, child_tree }) => { let at = match at { Some(at_str) => Some(hash_of::(at_str)?), None => None, }; - let mut builder = Builder::::new() - .mode(Mode::Online(OnlineConfig { - transport: uri.to_owned().into(), - state_snapshot: snapshot_path.as_ref().map(SnapshotConfig::new), - pallets: pallet.clone(), - scrape_children: true, - at, - })) - .inject_hashed_key( - &[twox_128(b"System"), twox_128(b"LastRuntimeUpgrade")].concat(), - ); - if *child_tree { - builder = builder.inject_default_child_tree_prefix(); - } - builder + Builder::::new().mode(Mode::Online(OnlineConfig { + at, + transport: uri.to_owned().into(), + state_snapshot, + pallets: pallet.clone(), + child_trie: *child_tree, + hashed_keys: vec![ + // we always download the code, but we almost always won't use it, based on + // `Runtime`. + well_known_keys::CODE.to_vec(), + // we will always download this key, since it helps detect if we should do + // runtime migration or not. + [twox_128(b"System"), twox_128(b"LastRuntimeUpgrade")].concat(), + [twox_128(b"System"), twox_128(b"Number")].concat(), + ], + hashed_prefixes: vec![], + })) }, - }) - } + }; + + // possibly overwrite the state version, should hardly be needed. + let builder = if let Some(state_version) = shared.overwrite_state_version { + log::warn!( + target: LOG_TARGET, + "overwriting state version to {:?}, you better know what you are doing.", + state_version + ); + builder.overwrite_state_version(state_version) + } else { + builder + }; + + // then, we prepare to replace the code based on what the CLI wishes. + let maybe_code_to_overwrite = match shared.runtime { + Runtime::Path(ref path) => Some(std::fs::read(path).map_err(|e| { + format!("error while reading runtime file from {:?}: {:?}", path, e) + })?), + Runtime::Existing => None, + }; + + // build the main ext. + let mut ext = builder.build().await?; + + // actually replace the code if needed. + if let Some(new_code) = maybe_code_to_overwrite { + let original_code = ext + .execute_with(|| sp_io::storage::get(well_known_keys::CODE)) + .expect("':CODE:' is always downloaded in try-runtime-cli; qed"); + + // NOTE: see the impl notes of `read_runtime_version`, the ext is almost not used here, + // only as a backup. + ext.insert(well_known_keys::CODE.to_vec(), new_code.clone()); + let old_version = ::decode( + &mut &*executor.read_runtime_version(&original_code, &mut ext.ext()).unwrap(), + ) + .unwrap(); + log::info!( + target: LOG_TARGET, + "original spec: {:?}-{:?}, code hash: {:?}", + old_version.spec_name, + old_version.spec_version, + HexDisplay::from(BlakeTwo256::hash(&original_code).as_fixed_bytes()), + ); + let new_version = ::decode( + &mut &*executor.read_runtime_version(&new_code, &mut ext.ext()).unwrap(), + ) + .unwrap(); + log::info!( + target: LOG_TARGET, + "new spec: {:?}-{:?}, code hash: {:?}", + new_version.spec_name, + new_version.spec_version, + HexDisplay::from(BlakeTwo256::hash(&new_code).as_fixed_bytes()) + ); - /// Get the uri, if self is `Live`. - pub(crate) fn live_uri(&self) -> Option { - match self { - State::Live { uri, .. } => Some(uri.clone()), - _ => None, + if new_version.spec_name != old_version.spec_name { + return Err("Spec names must match.".into()) + } + } + + // whatever runtime we have in store now must have been compiled with try-runtime feature. + if !ensure_try_runtime::(&executor, &mut ext) { + return Err("given runtime is NOT compiled with try-runtime feature!".into()) } + + Ok(ext) } } impl TryRuntimeCmd { - pub async fn run(&self, config: Configuration) -> sc_cli::Result<()> + pub async fn run(&self) -> sc_cli::Result<()> where Block: BlockT + DeserializeOwned, Block::Header: DeserializeOwned, Block::Hash: FromStr, ::Err: Debug, - NumberFor: FromStr, as FromStr>::Err: Debug, - ExecDispatch: NativeExecutionDispatch + 'static, + as TryInto>::Error: Debug, + NumberFor: FromStr, + HostFns: HostFunctions, { match &self.command { Command::OnRuntimeUpgrade(ref cmd) => - commands::on_runtime_upgrade::on_runtime_upgrade::( + commands::on_runtime_upgrade::on_runtime_upgrade::( self.shared.clone(), cmd.clone(), - config, ) .await, Command::OffchainWorker(cmd) => - commands::offchain_worker::offchain_worker::( + commands::offchain_worker::offchain_worker::( self.shared.clone(), cmd.clone(), - config, ) .await, Command::ExecuteBlock(cmd) => - commands::execute_block::execute_block::( + commands::execute_block::execute_block::( self.shared.clone(), cmd.clone(), - config, ) .await, Command::FollowChain(cmd) => - commands::follow_chain::follow_chain::( + commands::follow_chain::follow_chain::( + self.shared.clone(), + cmd.clone(), + ) + .await, + Command::CreateSnapshot(cmd) => + commands::create_snapshot::create_snapshot::( self.shared.clone(), cmd.clone(), - config, ) .await, } @@ -598,22 +769,6 @@ impl CliConfiguration for TryRuntimeCmd { } } -/// Extract `:code` from the given chain spec and return as `StorageData` along with the -/// corresponding `StorageKey`. -pub(crate) fn extract_code(spec: &Box) -> sc_cli::Result<(StorageKey, StorageData)> { - let genesis_storage = spec.build_storage()?; - let code = StorageData( - genesis_storage - .top - .get(well_known_keys::CODE) - .expect("code key must exist in genesis storage; qed") - .to_vec(), - ); - let code_key = StorageKey(well_known_keys::CODE.to_vec()); - - Ok((code_key, code)) -} - /// Get the hash type of the generic `Block` from a `hash_str`. pub(crate) fn hash_of(hash_str: &str) -> sc_cli::Result where @@ -625,67 +780,6 @@ where .map_err(|e| format!("Could not parse block hash: {:?}", e).into()) } -/// Check the spec_name of an `ext` -/// -/// If the spec names don't match, if `relaxed`, then it emits a warning, else it panics. -/// If the spec versions don't match, it only ever emits a warning. -pub(crate) async fn ensure_matching_spec( - uri: String, - expected_spec_name: String, - expected_spec_version: u32, - relaxed: bool, -) { - let rpc = ws_client(&uri).await.unwrap(); - match StateApi::::runtime_version(&rpc, None) - .await - .map(|version| (String::from(version.spec_name.clone()), version.spec_version)) - .map(|(spec_name, spec_version)| (spec_name.to_lowercase(), spec_version)) - { - Ok((name, version)) => { - // first, deal with spec name - if expected_spec_name.to_lowercase() == name { - log::info!(target: LOG_TARGET, "found matching spec name: {:?}", name); - } else { - let msg = format!( - "version mismatch: remote spec name: '{}', expected (local chain spec, aka. `--chain`): '{}'", - name, - expected_spec_name - ); - if relaxed { - log::warn!(target: LOG_TARGET, "{}", msg); - } else { - panic!("{}", msg); - } - } - - if expected_spec_version == version { - log::info!(target: LOG_TARGET, "found matching spec version: {:?}", version); - } else { - let msg = format!( - "spec version mismatch (local {} != remote {}). This could cause some issues.", - expected_spec_version, version - ); - if relaxed { - log::warn!(target: LOG_TARGET, "{}", msg); - } else { - panic!("{}", msg); - } - } - }, - Err(why) => { - let msg = format!( - "failed to fetch runtime version from {}: {:?}. Skipping the check", - uri, why - ); - if relaxed { - log::error!(target: LOG_TARGET, "{}", msg); - } else { - panic!("{}", msg); - } - }, - } -} - /// Build all extensions that we typically use. pub(crate) fn full_extensions() -> Extensions { let mut extensions = Extensions::default(); @@ -700,29 +794,43 @@ pub(crate) fn full_extensions() -> Extensions { extensions } -/// Build a default execution that we typically use. -pub(crate) fn build_executor( - shared: &SharedParams, - config: &sc_service::Configuration, -) -> NativeElseWasmExecutor { - let heap_pages = shared.heap_pages.or(config.default_heap_pages); - let max_runtime_instances = config.max_runtime_instances; - let runtime_cache_size = config.runtime_cache_size; - - NativeElseWasmExecutor::::new( - execution_method_from_cli(shared.wasm_method, shared.wasmtime_instantiation_strategy), +pub(crate) fn build_executor(shared: &SharedParams) -> WasmExecutor { + let heap_pages = shared.heap_pages.or(Some(2048)); + let max_runtime_instances = 8; + let runtime_cache_size = 2; + + WasmExecutor::new( + sc_executor::WasmExecutionMethod::Interpreted, heap_pages, max_runtime_instances, + None, runtime_cache_size, ) } +/// Ensure that the given `ext` is compiled with `try-runtime` +fn ensure_try_runtime( + executor: &WasmExecutor, + ext: &mut TestExternalities, +) -> bool { + use sp_api::RuntimeApiInfo; + let final_code = ext + .execute_with(|| sp_io::storage::get(well_known_keys::CODE)) + .expect("':CODE:' is always downloaded in try-runtime-cli; qed"); + let final_version = ::decode( + &mut &*executor.read_runtime_version(&final_code, &mut ext.ext()).unwrap(), + ) + .unwrap(); + final_version + .api_version(&>::ID) + .is_some() +} + /// Execute the given `method` and `data` on top of `ext`, returning the results (encoded) and the /// state `changes`. -pub(crate) fn state_machine_call( +pub(crate) fn state_machine_call( ext: &TestExternalities, - executor: &NativeElseWasmExecutor, - execution: sc_cli::ExecutionStrategy, + executor: &WasmExecutor, method: &'static str, data: &[u8], extensions: Extensions, @@ -738,7 +846,7 @@ pub(crate) fn state_machine_call(Into::into)?; @@ -749,28 +857,23 @@ pub(crate) fn state_machine_call( +pub(crate) fn state_machine_call_with_proof( ext: &TestExternalities, - executor: &NativeElseWasmExecutor, - execution: sc_cli::ExecutionStrategy, + executor: &WasmExecutor, method: &'static str, data: &[u8], extensions: Extensions, ) -> sc_cli::Result<(OverlayedChanges, Vec)> { use parity_scale_codec::Encode; - use sp_core::hexdisplay::HexDisplay; let mut changes = Default::default(); let backend = ext.backend.clone(); let runtime_code_backend = sp_state_machine::backend::BackendRuntimeCode::new(&backend); - let proving_backend = TrieBackendBuilder::wrap(&backend).with_recorder(Default::default()).build(); - let runtime_code = runtime_code_backend.runtime_code()?; let pre_root = *backend.root(); - let encoded_results = StateMachine::new( &proving_backend, &mut changes, @@ -781,7 +884,7 @@ pub(crate) fn state_machine_call_with_proof(Into::into)?; @@ -792,11 +895,24 @@ pub(crate) fn state_machine_call_with_proof(pre_root) - .map_err(|e| format!("failed to generate compact proof {}: {:?}", method, e))?; + .map_err(|e| { + log::error!(target: LOG_TARGET, "failed to generate compact proof {}: {:?}", method, e); + e + }) + .unwrap_or(CompactProof { encoded_nodes: Default::default() }); let compact_proof_size = compact_proof.encoded_size(); let compressed_proof = zstd::stream::encode_all(&compact_proof.encode()[..], 0) - .map_err(|e| format!("failed to generate compact proof {}: {:?}", method, e))?; + .map_err(|e| { + log::error!( + target: LOG_TARGET, + "failed to generate compressed proof {}: {:?}", + method, + e + ); + e + }) + .unwrap_or_default(); let proof_nodes = proof.into_nodes(); @@ -814,8 +930,8 @@ pub(crate) fn state_machine_call_with_proof>()), + "proof: 0x{}... / {} nodes", + HexDisplay::from(&proof_nodes.iter().flatten().cloned().take(10).collect::>()), proof_nodes.len() ); log::debug!(target: LOG_TARGET, "proof size: {}", humanize(proof_size)); @@ -825,28 +941,13 @@ pub(crate) fn state_machine_call_with_proof( - ext: &TestExternalities, - executor: &NativeElseWasmExecutor, -) -> (String, u32, sp_core::storage::StateVersion) { - let (_, encoded) = state_machine_call::( - ext, - executor, - sc_cli::ExecutionStrategy::NativeElseWasm, - "Core_version", - &[], - Default::default(), - ) - .expect("all runtimes should have version; qed"); - ::decode(&mut &*encoded) - .map_err(|e| format!("failed to decode output: {:?}", e)) - .map(|v| { - let state_version = v.state_version(); - (v.spec_name.into(), v.spec_version, state_version) - }) - .expect("all runtimes should have version; qed") +pub(crate) fn rpc_err_handler(error: impl Debug) -> &'static str { + log::error!(target: LOG_TARGET, "rpc error: {:?}", error); + "rpc error." } From f763ff622700d2c880122b291a1e13291b6a388a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Thu, 15 Dec 2022 08:57:19 +0000 Subject: [PATCH 042/127] Automatic `withdraw_unbonded` upon `unbond` (#12582) * Prevents max unbonding chunk slots from being filled when unbonding in the staking pallet * hardcode num_slashing to unlock chunks automatically * refactor withdraw logic to do_withdraw; idiomatic rust improvements * a * callable unbond() to return a DispatchWithPostInfo to dynamically update the consumed weight * refunds overpaid fees when unbond with withdraw * fetches real slashing spans before withdrawal call * nits * addresses PR comments * Adds more testing * fixes doc comments * Fixes weight refunding logic for fn unbond * generalizes to return used weight or dispatch error * Update frame/staking/src/pallet/mod.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Update frame/staking/src/pallet/mod.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Addresses PR comments * Add comment to speculative num spans * adds missing add_slashing_spans in withdraw_unbonded_kill benchmarks * ".git/.scripts/bench-bot.sh" pallet dev pallet_staking * fix publish Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Co-authored-by: command-bot <> --- frame/nomination-pools/src/lib.rs | 9 +- frame/staking/src/benchmarking.rs | 1 + frame/staking/src/pallet/impls.rs | 41 ++++ frame/staking/src/pallet/mod.rs | 80 ++++--- frame/staking/src/tests.rs | 58 +++-- frame/staking/src/weights.rs | 370 +++++++++++++++--------------- 6 files changed, 321 insertions(+), 238 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index fd533ee376..5e5385b62a 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1644,9 +1644,12 @@ pub mod pallet { /// # Note /// /// If there are too many unlocking chunks to unbond with the pool account, - /// [`Call::pool_withdraw_unbonded`] can be called to try and minimize unlocking chunks. If - /// there are too many unlocking chunks, the result of this call will likely be the - /// `NoMoreChunks` error from the staking system. + /// [`Call::pool_withdraw_unbonded`] can be called to try and minimize unlocking chunks. + /// The [`StakingInterface::unbond`] will implicitly call [`Call::pool_withdraw_unbonded`] + /// to try to free chunks if necessary (ie. if unbound was called and no unlocking chunks + /// are available). However, it may not be possible to release the current unlocking chunks, + /// in which case, the result of this call will likely be the `NoMoreChunks` error from the + /// staking system. #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::unbond())] pub fn unbond( diff --git a/frame/staking/src/benchmarking.rs b/frame/staking/src/benchmarking.rs index 8409b5413f..81fa0f9d81 100644 --- a/frame/staking/src/benchmarking.rs +++ b/frame/staking/src/benchmarking.rs @@ -316,6 +316,7 @@ benchmarks! { let scenario = ListScenario::::new(origin_weight, true)?; let controller = scenario.origin_controller1.clone(); let stash = scenario.origin_stash1; + add_slashing_spans::(&stash, s); assert!(T::VoterList::contains(&stash)); let ed = T::Currency::minimum_balance(); diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index d3b9b6a2b1..a7190d70c7 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -92,6 +92,45 @@ impl Pallet { Self::slashable_balance_of_vote_weight(who, issuance) } + pub(super) fn do_withdraw_unbonded( + controller: &T::AccountId, + num_slashing_spans: u32, + ) -> Result { + let mut ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; + let (stash, old_total) = (ledger.stash.clone(), ledger.total); + if let Some(current_era) = Self::current_era() { + ledger = ledger.consolidate_unlocked(current_era) + } + + let used_weight = + if ledger.unlocking.is_empty() && ledger.active < T::Currency::minimum_balance() { + // This account must have called `unbond()` with some value that caused the active + // portion to fall below existential deposit + will have no more unlocking chunks + // left. We can now safely remove all staking-related information. + Self::kill_stash(&stash, num_slashing_spans)?; + // Remove the lock. + T::Currency::remove_lock(STAKING_ID, &stash); + + T::WeightInfo::withdraw_unbonded_kill(num_slashing_spans) + } else { + // This was the consequence of a partial unbond. just update the ledger and move on. + Self::update_ledger(&controller, &ledger); + + // This is only an update, so we use less overall weight. + T::WeightInfo::withdraw_unbonded_update(num_slashing_spans) + }; + + // `old_total` should never be less than the new total because + // `consolidate_unlocked` strictly subtracts balance. + if ledger.total < old_total { + // Already checked that this won't overflow by entry condition. + let value = old_total - ledger.total; + Self::deposit_event(Event::::Withdrawn { stash, amount: value }); + } + + Ok(used_weight) + } + pub(super) fn do_payout_stakers( validator_stash: T::AccountId, era: EraIndex, @@ -1568,6 +1607,8 @@ impl StakingInterface for Pallet { fn unbond(who: &Self::AccountId, value: Self::Balance) -> DispatchResult { let ctrl = Self::bonded(who).ok_or(Error::::NotStash)?; Self::unbond(RawOrigin::Signed(ctrl).into(), value) + .map_err(|with_post| with_post.error) + .map(|_| ()) } fn chill(who: &Self::AccountId) -> DispatchResult { diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 2daa992f4e..1d5babe7ff 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -51,6 +51,10 @@ use crate::{ }; const STAKING_ID: LockIdentifier = *b"staking "; +// The speculative number of spans are used as an input of the weight annotation of +// [`Call::unbond`], as the post dipatch weight may depend on the number of slashing span on the +// account which is not provided as an input. The value set should be conservative but sensible. +pub(crate) const SPECULATIVE_NUM_SPANS: u32 = 32; #[frame_support::pallet] pub mod pallet { @@ -115,7 +119,6 @@ pub mod pallet { // we only accept an election provider that has staking as data provider. DataProvider = Pallet, >; - /// Something that provides the election functionality at genesis. type GenesisElectionProvider: ElectionProvider< AccountId = Self::AccountId, @@ -953,8 +956,8 @@ pub mod pallet { /// the funds out of management ready for transfer. /// /// No more than a limited number of unlocking chunks (see `MaxUnlockingChunks`) - /// can co-exists at the same time. In that case, [`Call::withdraw_unbonded`] need - /// to be called first to remove some of the chunks (if possible). + /// can co-exists at the same time. If there are no unlocking chunks slots available + /// [`Call::withdraw_unbonded`] is called to remove some of the chunks (if possible). /// /// If a user encounters the `InsufficientBond` error when calling this extrinsic, /// they should call `chill` first in order to free up their bonded funds. @@ -963,20 +966,39 @@ pub mod pallet { /// /// See also [`Call::withdraw_unbonded`]. #[pallet::call_index(2)] - #[pallet::weight(T::WeightInfo::unbond())] + #[pallet::weight( + T::WeightInfo::withdraw_unbonded_kill(SPECULATIVE_NUM_SPANS).saturating_add(T::WeightInfo::unbond())) + ] pub fn unbond( origin: OriginFor, #[pallet::compact] value: BalanceOf, - ) -> DispatchResult { + ) -> DispatchResultWithPostInfo { let controller = ensure_signed(origin)?; + let unlocking = Self::ledger(&controller) + .map(|l| l.unlocking.len()) + .ok_or(Error::::NotController)?; + + // if there are no unlocking chunks available, try to withdraw chunks older than + // `BondingDuration` to proceed with the unbonding. + let maybe_withdraw_weight = { + if unlocking == T::MaxUnlockingChunks::get() as usize { + let real_num_slashing_spans = Self::slashing_spans(&controller).iter().count(); + Some(Self::do_withdraw_unbonded(&controller, real_num_slashing_spans as u32)?) + } else { + None + } + }; + + // we need to fetch the ledger again because it may have been mutated in the call + // to `Self::do_withdraw_unbonded` above. let mut ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; + let mut value = value.min(ledger.active); + ensure!( ledger.unlocking.len() < T::MaxUnlockingChunks::get() as usize, Error::::NoMoreChunks, ); - let mut value = value.min(ledger.active); - if !value.is_zero() { ledger.active -= value; @@ -1024,7 +1046,14 @@ pub mod pallet { Self::deposit_event(Event::::Unbonded { stash: ledger.stash, amount: value }); } - Ok(()) + + let actual_weight = if let Some(withdraw_weight) = maybe_withdraw_weight { + Some(T::WeightInfo::unbond().saturating_add(withdraw_weight)) + } else { + Some(T::WeightInfo::unbond()) + }; + + Ok(actual_weight.into()) } /// Remove any unlocked chunks from the `unlocking` queue from our management. @@ -1049,40 +1078,9 @@ pub mod pallet { num_slashing_spans: u32, ) -> DispatchResultWithPostInfo { let controller = ensure_signed(origin)?; - let mut ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; - let (stash, old_total) = (ledger.stash.clone(), ledger.total); - if let Some(current_era) = Self::current_era() { - ledger = ledger.consolidate_unlocked(current_era) - } - - let post_info_weight = if ledger.unlocking.is_empty() && - ledger.active < T::Currency::minimum_balance() - { - // This account must have called `unbond()` with some value that caused the active - // portion to fall below existential deposit + will have no more unlocking chunks - // left. We can now safely remove all staking-related information. - Self::kill_stash(&stash, num_slashing_spans)?; - // Remove the lock. - T::Currency::remove_lock(STAKING_ID, &stash); - // This is worst case scenario, so we use the full weight and return None - None - } else { - // This was the consequence of a partial unbond. just update the ledger and move on. - Self::update_ledger(&controller, &ledger); - - // This is only an update, so we use less overall weight. - Some(T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)) - }; - - // `old_total` should never be less than the new total because - // `consolidate_unlocked` strictly subtracts balance. - if ledger.total < old_total { - // Already checked that this won't overflow by entry condition. - let value = old_total - ledger.total; - Self::deposit_event(Event::::Withdrawn { stash, amount: value }); - } - Ok(post_info_weight.into()) + let actual_weight = Self::do_withdraw_unbonded(&controller, num_slashing_spans)?; + Ok(Some(actual_weight).into()) } /// Declare the desire to validate for the origin controller. diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 74d8dc8a81..fc6fc68e66 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -1350,12 +1350,14 @@ fn bond_extra_and_withdraw_unbonded_works() { } #[test] -fn too_many_unbond_calls_should_not_work() { +fn many_unbond_calls_should_work() { ExtBuilder::default().build_and_execute(|| { let mut current_era = 0; // locked at era MaxUnlockingChunks - 1 until 3 - for i in 0..<::MaxUnlockingChunks as Get>::get() - 1 { + let max_unlocking_chunks = <::MaxUnlockingChunks as Get>::get(); + + for i in 0..max_unlocking_chunks - 1 { // There is only 1 chunk per era, so we need to be in a new era to create a chunk. current_era = i as u32; mock::start_active_era(current_era); @@ -1365,27 +1367,57 @@ fn too_many_unbond_calls_should_not_work() { current_era += 1; mock::start_active_era(current_era); - // This chunk is locked at `current_era` through `current_era + 2` (because BondingDuration - // == 3). + // This chunk is locked at `current_era` through `current_era + 2` (because + // `BondingDuration` == 3). assert_ok!(Staking::unbond(RuntimeOrigin::signed(10), 1)); assert_eq!( - Staking::ledger(&10).unwrap().unlocking.len(), + Staking::ledger(&10).map(|l| l.unlocking.len()).unwrap(), <::MaxUnlockingChunks as Get>::get() as usize ); - // can't do more. - assert_noop!(Staking::unbond(RuntimeOrigin::signed(10), 1), Error::::NoMoreChunks); - current_era += 2; + // even though the number of unlocked chunks is the same as `MaxUnlockingChunks`, + // unbonding works as expected. + for i in current_era..(current_era + max_unlocking_chunks) - 1 { + // There is only 1 chunk per era, so we need to be in a new era to create a chunk. + current_era = i as u32; + mock::start_active_era(current_era); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(10), 1)); + } + + // only slots within last `BondingDuration` are filled. + assert_eq!( + Staking::ledger(&10).map(|l| l.unlocking.len()).unwrap(), + <::BondingDuration>::get() as usize + ); + }) +} + +#[test] +fn auto_withdraw_may_not_unlock_all_chunks() { + ExtBuilder::default().build_and_execute(|| { + // set `MaxUnlockingChunks` to a low number to test case when the unbonding period + // is larger than the number of unlocking chunks available, which may result on a + // `Error::NoMoreChunks`, even when the auto-withdraw tries to release locked chunks. + MaxUnlockingChunks::set(1); + + let mut current_era = 0; + + // fills the chunking slots for account + mock::start_active_era(current_era); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(10), 1)); + + current_era += 1; mock::start_active_era(current_era); + // unbonding will fail because i) there are no remaining chunks and ii) no filled chunks + // can be released because current chunk hasn't stay in the queue for at least + // `BondingDuration` assert_noop!(Staking::unbond(RuntimeOrigin::signed(10), 1), Error::::NoMoreChunks); - // free up everything except the most recently added chunk. - assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(10), 0)); - assert_eq!(Staking::ledger(&10).unwrap().unlocking.len(), 1); - // Can add again. + // fast-forward a few eras for unbond to be successful with implicit withdraw + current_era += 10; + mock::start_active_era(current_era); assert_ok!(Staking::unbond(RuntimeOrigin::signed(10), 1)); - assert_eq!(Staking::ledger(&10).unwrap().unlocking.len(), 2); }) } diff --git a/frame/staking/src/weights.rs b/frame/staking/src/weights.rs index 21fc3d6f07..aebb8eeb9b 100644 --- a/frame/staking/src/weights.rs +++ b/frame/staking/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_staking //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-12-12, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-12-14, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 @@ -88,8 +88,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn bond() -> Weight { - // Minimum execution time: 56_034 nanoseconds. - Weight::from_ref_time(56_646_000) + // Minimum execution time: 54_402 nanoseconds. + Weight::from_ref_time(55_096_000) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -99,8 +99,8 @@ impl WeightInfo for SubstrateWeight { // Storage: VoterList ListNodes (r:3 w:3) // Storage: VoterList ListBags (r:2 w:2) fn bond_extra() -> Weight { - // Minimum execution time: 94_354 nanoseconds. - Weight::from_ref_time(95_318_000) + // Minimum execution time: 94_407 nanoseconds. + Weight::from_ref_time(95_209_000) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(7)) } @@ -114,8 +114,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking Bonded (r:1 w:0) // Storage: VoterList ListBags (r:2 w:2) fn unbond() -> Weight { - // Minimum execution time: 99_960 nanoseconds. - Weight::from_ref_time(101_022_000) + // Minimum execution time: 101_046 nanoseconds. + Weight::from_ref_time(101_504_000) .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().writes(8)) } @@ -125,10 +125,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_update(s: u32, ) -> Weight { - // Minimum execution time: 45_819 nanoseconds. - Weight::from_ref_time(48_073_614) - // Standard Error: 1_410 - .saturating_add(Weight::from_ref_time(62_881).saturating_mul(s.into())) + // Minimum execution time: 45_452 nanoseconds. + Weight::from_ref_time(47_031_537) + // Standard Error: 491 + .saturating_add(Weight::from_ref_time(67_148).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -145,12 +145,16 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) // Storage: Balances Locks (r:1 w:1) // Storage: Staking Payee (r:0 w:1) + // Storage: Staking SpanSlash (r:0 w:2) /// The range of component `s` is `[0, 100]`. - fn withdraw_unbonded_kill(_s: u32, ) -> Weight { - // Minimum execution time: 86_035 nanoseconds. - Weight::from_ref_time(89_561_735) + fn withdraw_unbonded_kill(s: u32, ) -> Weight { + // Minimum execution time: 88_067 nanoseconds. + Weight::from_ref_time(93_309_587) + // Standard Error: 4_762 + .saturating_add(Weight::from_ref_time(1_114_938).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(13)) - .saturating_add(T::DbWeight::get().writes(11)) + .saturating_add(T::DbWeight::get().writes(12)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking MinValidatorBond (r:1 w:0) @@ -164,8 +168,8 @@ impl WeightInfo for SubstrateWeight { // Storage: VoterList CounterForListNodes (r:1 w:1) // Storage: Staking CounterForValidators (r:1 w:1) fn validate() -> Weight { - // Minimum execution time: 68_748 nanoseconds. - Weight::from_ref_time(69_285_000) + // Minimum execution time: 67_308 nanoseconds. + Weight::from_ref_time(68_266_000) .saturating_add(T::DbWeight::get().reads(11)) .saturating_add(T::DbWeight::get().writes(5)) } @@ -173,10 +177,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking Nominators (r:1 w:1) /// The range of component `k` is `[1, 128]`. fn kick(k: u32, ) -> Weight { - // Minimum execution time: 41_641 nanoseconds. - Weight::from_ref_time(48_919_231) - // Standard Error: 11_548 - .saturating_add(Weight::from_ref_time(6_901_201).saturating_mul(k.into())) + // Minimum execution time: 40_913 nanoseconds. + Weight::from_ref_time(48_140_584) + // Standard Error: 13_396 + .saturating_add(Weight::from_ref_time(6_862_893).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) @@ -194,10 +198,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking CounterForNominators (r:1 w:1) /// The range of component `n` is `[1, 16]`. fn nominate(n: u32, ) -> Weight { - // Minimum execution time: 75_097 nanoseconds. - Weight::from_ref_time(74_052_497) - // Standard Error: 6_784 - .saturating_add(Weight::from_ref_time(2_842_146).saturating_mul(n.into())) + // Minimum execution time: 73_490 nanoseconds. + Weight::from_ref_time(72_520_864) + // Standard Error: 7_090 + .saturating_add(Weight::from_ref_time(2_800_566).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(6)) @@ -210,58 +214,58 @@ impl WeightInfo for SubstrateWeight { // Storage: VoterList ListBags (r:1 w:1) // Storage: VoterList CounterForListNodes (r:1 w:1) fn chill() -> Weight { - // Minimum execution time: 67_307 nanoseconds. - Weight::from_ref_time(67_838_000) + // Minimum execution time: 66_293 nanoseconds. + Weight::from_ref_time(66_946_000) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(6)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking Payee (r:0 w:1) fn set_payee() -> Weight { - // Minimum execution time: 18_831 nanoseconds. - Weight::from_ref_time(19_047_000) + // Minimum execution time: 18_134 nanoseconds. + Weight::from_ref_time(18_497_000) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Staking Bonded (r:1 w:1) // Storage: Staking Ledger (r:2 w:2) fn set_controller() -> Weight { - // Minimum execution time: 27_534 nanoseconds. - Weight::from_ref_time(27_806_000) + // Minimum execution time: 26_728 nanoseconds. + Weight::from_ref_time(27_154_000) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } // Storage: Staking ValidatorCount (r:0 w:1) fn set_validator_count() -> Weight { - // Minimum execution time: 5_211 nanoseconds. - Weight::from_ref_time(5_372_000) + // Minimum execution time: 4_877 nanoseconds. + Weight::from_ref_time(5_028_000) .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Staking ForceEra (r:0 w:1) fn force_no_eras() -> Weight { - // Minimum execution time: 5_382 nanoseconds. - Weight::from_ref_time(5_654_000) + // Minimum execution time: 5_000 nanoseconds. + Weight::from_ref_time(5_290_000) .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Staking ForceEra (r:0 w:1) fn force_new_era() -> Weight { - // Minimum execution time: 5_618 nanoseconds. - Weight::from_ref_time(5_714_000) + // Minimum execution time: 5_093 nanoseconds. + Weight::from_ref_time(5_378_000) .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Staking ForceEra (r:0 w:1) fn force_new_era_always() -> Weight { - // Minimum execution time: 5_589 nanoseconds. - Weight::from_ref_time(5_776_000) + // Minimum execution time: 5_144 nanoseconds. + Weight::from_ref_time(5_454_000) .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Staking Invulnerables (r:0 w:1) /// The range of component `v` is `[0, 1000]`. fn set_invulnerables(v: u32, ) -> Weight { - // Minimum execution time: 5_541 nanoseconds. - Weight::from_ref_time(6_479_253) - // Standard Error: 49 - .saturating_add(Weight::from_ref_time(10_125).saturating_mul(v.into())) + // Minimum execution time: 5_190 nanoseconds. + Weight::from_ref_time(5_960_962) + // Standard Error: 41 + .saturating_add(Weight::from_ref_time(10_329).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Staking Bonded (r:1 w:1) @@ -279,10 +283,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking SpanSlash (r:0 w:2) /// The range of component `s` is `[0, 100]`. fn force_unstake(s: u32, ) -> Weight { - // Minimum execution time: 81_041 nanoseconds. - Weight::from_ref_time(88_526_481) - // Standard Error: 11_494 - .saturating_add(Weight::from_ref_time(1_095_933).saturating_mul(s.into())) + // Minimum execution time: 80_516 nanoseconds. + Weight::from_ref_time(86_317_884) + // Standard Error: 2_212 + .saturating_add(Weight::from_ref_time(1_103_962).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(11)) .saturating_add(T::DbWeight::get().writes(12)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -290,10 +294,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking UnappliedSlashes (r:1 w:1) /// The range of component `s` is `[1, 1000]`. fn cancel_deferred_slash(s: u32, ) -> Weight { - // Minimum execution time: 92_308 nanoseconds. - Weight::from_ref_time(900_351_007) - // Standard Error: 59_145 - .saturating_add(Weight::from_ref_time(4_944_988).saturating_mul(s.into())) + // Minimum execution time: 91_795 nanoseconds. + Weight::from_ref_time(904_524_900) + // Standard Error: 59_193 + .saturating_add(Weight::from_ref_time(4_944_680).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -308,10 +312,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) /// The range of component `n` is `[0, 256]`. fn payout_stakers_dead_controller(n: u32, ) -> Weight { - // Minimum execution time: 131_855 nanoseconds. - Weight::from_ref_time(197_412_779) - // Standard Error: 21_283 - .saturating_add(Weight::from_ref_time(22_093_758).saturating_mul(n.into())) + // Minimum execution time: 127_774 nanoseconds. + Weight::from_ref_time(178_857_156) + // Standard Error: 15_229 + .saturating_add(Weight::from_ref_time(22_112_174).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(2)) @@ -329,10 +333,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) /// The range of component `n` is `[0, 256]`. fn payout_stakers_alive_staked(n: u32, ) -> Weight { - // Minimum execution time: 163_118 nanoseconds. - Weight::from_ref_time(229_356_697) - // Standard Error: 30_740 - .saturating_add(Weight::from_ref_time(31_575_360).saturating_mul(n.into())) + // Minimum execution time: 161_910 nanoseconds. + Weight::from_ref_time(217_635_072) + // Standard Error: 30_726 + .saturating_add(Weight::from_ref_time(31_244_329).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -346,10 +350,10 @@ impl WeightInfo for SubstrateWeight { // Storage: VoterList ListBags (r:2 w:2) /// The range of component `l` is `[1, 32]`. fn rebond(l: u32, ) -> Weight { - // Minimum execution time: 94_048 nanoseconds. - Weight::from_ref_time(95_784_236) - // Standard Error: 2_313 - .saturating_add(Weight::from_ref_time(52_798).saturating_mul(l.into())) + // Minimum execution time: 92_986 nanoseconds. + Weight::from_ref_time(94_880_481) + // Standard Error: 2_007 + .saturating_add(Weight::from_ref_time(31_421).saturating_mul(l.into())) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(8)) } @@ -368,10 +372,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking SpanSlash (r:0 w:1) /// The range of component `s` is `[1, 100]`. fn reap_stash(s: u32, ) -> Weight { - // Minimum execution time: 93_342 nanoseconds. - Weight::from_ref_time(95_756_184) - // Standard Error: 2_067 - .saturating_add(Weight::from_ref_time(1_090_785).saturating_mul(s.into())) + // Minimum execution time: 92_750 nanoseconds. + Weight::from_ref_time(95_115_568) + // Standard Error: 2_037 + .saturating_add(Weight::from_ref_time(1_086_488).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().writes(12)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -395,12 +399,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `v` is `[1, 10]`. /// The range of component `n` is `[0, 100]`. fn new_era(v: u32, n: u32, ) -> Weight { - // Minimum execution time: 506_874 nanoseconds. - Weight::from_ref_time(507_798_000) - // Standard Error: 1_802_261 - .saturating_add(Weight::from_ref_time(59_874_736).saturating_mul(v.into())) - // Standard Error: 179_585 - .saturating_add(Weight::from_ref_time(13_668_574).saturating_mul(n.into())) + // Minimum execution time: 506_543 nanoseconds. + Weight::from_ref_time(507_261_000) + // Standard Error: 1_766_631 + .saturating_add(Weight::from_ref_time(59_139_153).saturating_mul(v.into())) + // Standard Error: 176_035 + .saturating_add(Weight::from_ref_time(13_512_781).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(206)) .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into()))) @@ -417,12 +421,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `v` is `[500, 1000]`. /// The range of component `n` is `[500, 1000]`. fn get_npos_voters(v: u32, n: u32, ) -> Weight { - // Minimum execution time: 24_634_585 nanoseconds. - Weight::from_ref_time(24_718_377_000) - // Standard Error: 324_839 - .saturating_add(Weight::from_ref_time(3_654_508).saturating_mul(v.into())) - // Standard Error: 324_839 - .saturating_add(Weight::from_ref_time(2_927_535).saturating_mul(n.into())) + // Minimum execution time: 24_155_382 nanoseconds. + Weight::from_ref_time(24_252_568_000) + // Standard Error: 319_250 + .saturating_add(Weight::from_ref_time(3_596_056).saturating_mul(v.into())) + // Standard Error: 319_250 + .saturating_add(Weight::from_ref_time(2_852_023).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(201)) .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into()))) @@ -431,10 +435,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking Validators (r:501 w:0) /// The range of component `v` is `[500, 1000]`. fn get_npos_targets(v: u32, ) -> Weight { - // Minimum execution time: 4_805_490 nanoseconds. - Weight::from_ref_time(118_475_494) - // Standard Error: 26_332 - .saturating_add(Weight::from_ref_time(9_635_188).saturating_mul(v.into())) + // Minimum execution time: 4_741_111 nanoseconds. + Weight::from_ref_time(113_360_179) + // Standard Error: 25_375 + .saturating_add(Weight::from_ref_time(9_494_142).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(v.into()))) } @@ -445,8 +449,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking MaxNominatorsCount (r:0 w:1) // Storage: Staking MinNominatorBond (r:0 w:1) fn set_staking_configs_all_set() -> Weight { - // Minimum execution time: 10_816 nanoseconds. - Weight::from_ref_time(11_242_000) + // Minimum execution time: 11_074 nanoseconds. + Weight::from_ref_time(11_312_000) .saturating_add(T::DbWeight::get().writes(6)) } // Storage: Staking MinCommission (r:0 w:1) @@ -456,8 +460,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking MaxNominatorsCount (r:0 w:1) // Storage: Staking MinNominatorBond (r:0 w:1) fn set_staking_configs_all_remove() -> Weight { - // Minimum execution time: 9_581 nanoseconds. - Weight::from_ref_time(10_383_000) + // Minimum execution time: 9_795 nanoseconds. + Weight::from_ref_time(10_116_000) .saturating_add(T::DbWeight::get().writes(6)) } // Storage: Staking Ledger (r:1 w:0) @@ -471,16 +475,16 @@ impl WeightInfo for SubstrateWeight { // Storage: VoterList ListBags (r:1 w:1) // Storage: VoterList CounterForListNodes (r:1 w:1) fn chill_other() -> Weight { - // Minimum execution time: 83_669 nanoseconds. - Weight::from_ref_time(84_772_000) + // Minimum execution time: 82_914 nanoseconds. + Weight::from_ref_time(83_848_000) .saturating_add(T::DbWeight::get().reads(11)) .saturating_add(T::DbWeight::get().writes(6)) } // Storage: Staking MinCommission (r:1 w:0) // Storage: Staking Validators (r:1 w:1) fn force_apply_min_commission() -> Weight { - // Minimum execution time: 20_553 nanoseconds. - Weight::from_ref_time(20_933_000) + // Minimum execution time: 20_317 nanoseconds. + Weight::from_ref_time(20_639_000) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -494,8 +498,8 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn bond() -> Weight { - // Minimum execution time: 56_034 nanoseconds. - Weight::from_ref_time(56_646_000) + // Minimum execution time: 54_402 nanoseconds. + Weight::from_ref_time(55_096_000) .saturating_add(RocksDbWeight::get().reads(4)) .saturating_add(RocksDbWeight::get().writes(4)) } @@ -505,8 +509,8 @@ impl WeightInfo for () { // Storage: VoterList ListNodes (r:3 w:3) // Storage: VoterList ListBags (r:2 w:2) fn bond_extra() -> Weight { - // Minimum execution time: 94_354 nanoseconds. - Weight::from_ref_time(95_318_000) + // Minimum execution time: 94_407 nanoseconds. + Weight::from_ref_time(95_209_000) .saturating_add(RocksDbWeight::get().reads(8)) .saturating_add(RocksDbWeight::get().writes(7)) } @@ -520,8 +524,8 @@ impl WeightInfo for () { // Storage: Staking Bonded (r:1 w:0) // Storage: VoterList ListBags (r:2 w:2) fn unbond() -> Weight { - // Minimum execution time: 99_960 nanoseconds. - Weight::from_ref_time(101_022_000) + // Minimum execution time: 101_046 nanoseconds. + Weight::from_ref_time(101_504_000) .saturating_add(RocksDbWeight::get().reads(12)) .saturating_add(RocksDbWeight::get().writes(8)) } @@ -531,10 +535,10 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_update(s: u32, ) -> Weight { - // Minimum execution time: 45_819 nanoseconds. - Weight::from_ref_time(48_073_614) - // Standard Error: 1_410 - .saturating_add(Weight::from_ref_time(62_881).saturating_mul(s.into())) + // Minimum execution time: 45_452 nanoseconds. + Weight::from_ref_time(47_031_537) + // Standard Error: 491 + .saturating_add(Weight::from_ref_time(67_148).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(4)) .saturating_add(RocksDbWeight::get().writes(3)) } @@ -551,12 +555,16 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) // Storage: Balances Locks (r:1 w:1) // Storage: Staking Payee (r:0 w:1) + // Storage: Staking SpanSlash (r:0 w:2) /// The range of component `s` is `[0, 100]`. - fn withdraw_unbonded_kill(_s: u32, ) -> Weight { - // Minimum execution time: 86_035 nanoseconds. - Weight::from_ref_time(89_561_735) + fn withdraw_unbonded_kill(s: u32, ) -> Weight { + // Minimum execution time: 88_067 nanoseconds. + Weight::from_ref_time(93_309_587) + // Standard Error: 4_762 + .saturating_add(Weight::from_ref_time(1_114_938).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(13)) - .saturating_add(RocksDbWeight::get().writes(11)) + .saturating_add(RocksDbWeight::get().writes(12)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into()))) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking MinValidatorBond (r:1 w:0) @@ -570,8 +578,8 @@ impl WeightInfo for () { // Storage: VoterList CounterForListNodes (r:1 w:1) // Storage: Staking CounterForValidators (r:1 w:1) fn validate() -> Weight { - // Minimum execution time: 68_748 nanoseconds. - Weight::from_ref_time(69_285_000) + // Minimum execution time: 67_308 nanoseconds. + Weight::from_ref_time(68_266_000) .saturating_add(RocksDbWeight::get().reads(11)) .saturating_add(RocksDbWeight::get().writes(5)) } @@ -579,10 +587,10 @@ impl WeightInfo for () { // Storage: Staking Nominators (r:1 w:1) /// The range of component `k` is `[1, 128]`. fn kick(k: u32, ) -> Weight { - // Minimum execution time: 41_641 nanoseconds. - Weight::from_ref_time(48_919_231) - // Standard Error: 11_548 - .saturating_add(Weight::from_ref_time(6_901_201).saturating_mul(k.into())) + // Minimum execution time: 40_913 nanoseconds. + Weight::from_ref_time(48_140_584) + // Standard Error: 13_396 + .saturating_add(Weight::from_ref_time(6_862_893).saturating_mul(k.into())) .saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(k.into()))) @@ -600,10 +608,10 @@ impl WeightInfo for () { // Storage: Staking CounterForNominators (r:1 w:1) /// The range of component `n` is `[1, 16]`. fn nominate(n: u32, ) -> Weight { - // Minimum execution time: 75_097 nanoseconds. - Weight::from_ref_time(74_052_497) - // Standard Error: 6_784 - .saturating_add(Weight::from_ref_time(2_842_146).saturating_mul(n.into())) + // Minimum execution time: 73_490 nanoseconds. + Weight::from_ref_time(72_520_864) + // Standard Error: 7_090 + .saturating_add(Weight::from_ref_time(2_800_566).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(12)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(6)) @@ -616,58 +624,58 @@ impl WeightInfo for () { // Storage: VoterList ListBags (r:1 w:1) // Storage: VoterList CounterForListNodes (r:1 w:1) fn chill() -> Weight { - // Minimum execution time: 67_307 nanoseconds. - Weight::from_ref_time(67_838_000) + // Minimum execution time: 66_293 nanoseconds. + Weight::from_ref_time(66_946_000) .saturating_add(RocksDbWeight::get().reads(8)) .saturating_add(RocksDbWeight::get().writes(6)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking Payee (r:0 w:1) fn set_payee() -> Weight { - // Minimum execution time: 18_831 nanoseconds. - Weight::from_ref_time(19_047_000) + // Minimum execution time: 18_134 nanoseconds. + Weight::from_ref_time(18_497_000) .saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Staking Bonded (r:1 w:1) // Storage: Staking Ledger (r:2 w:2) fn set_controller() -> Weight { - // Minimum execution time: 27_534 nanoseconds. - Weight::from_ref_time(27_806_000) + // Minimum execution time: 26_728 nanoseconds. + Weight::from_ref_time(27_154_000) .saturating_add(RocksDbWeight::get().reads(3)) .saturating_add(RocksDbWeight::get().writes(3)) } // Storage: Staking ValidatorCount (r:0 w:1) fn set_validator_count() -> Weight { - // Minimum execution time: 5_211 nanoseconds. - Weight::from_ref_time(5_372_000) + // Minimum execution time: 4_877 nanoseconds. + Weight::from_ref_time(5_028_000) .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Staking ForceEra (r:0 w:1) fn force_no_eras() -> Weight { - // Minimum execution time: 5_382 nanoseconds. - Weight::from_ref_time(5_654_000) + // Minimum execution time: 5_000 nanoseconds. + Weight::from_ref_time(5_290_000) .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Staking ForceEra (r:0 w:1) fn force_new_era() -> Weight { - // Minimum execution time: 5_618 nanoseconds. - Weight::from_ref_time(5_714_000) + // Minimum execution time: 5_093 nanoseconds. + Weight::from_ref_time(5_378_000) .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Staking ForceEra (r:0 w:1) fn force_new_era_always() -> Weight { - // Minimum execution time: 5_589 nanoseconds. - Weight::from_ref_time(5_776_000) + // Minimum execution time: 5_144 nanoseconds. + Weight::from_ref_time(5_454_000) .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Staking Invulnerables (r:0 w:1) /// The range of component `v` is `[0, 1000]`. fn set_invulnerables(v: u32, ) -> Weight { - // Minimum execution time: 5_541 nanoseconds. - Weight::from_ref_time(6_479_253) - // Standard Error: 49 - .saturating_add(Weight::from_ref_time(10_125).saturating_mul(v.into())) + // Minimum execution time: 5_190 nanoseconds. + Weight::from_ref_time(5_960_962) + // Standard Error: 41 + .saturating_add(Weight::from_ref_time(10_329).saturating_mul(v.into())) .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Staking Bonded (r:1 w:1) @@ -685,10 +693,10 @@ impl WeightInfo for () { // Storage: Staking SpanSlash (r:0 w:2) /// The range of component `s` is `[0, 100]`. fn force_unstake(s: u32, ) -> Weight { - // Minimum execution time: 81_041 nanoseconds. - Weight::from_ref_time(88_526_481) - // Standard Error: 11_494 - .saturating_add(Weight::from_ref_time(1_095_933).saturating_mul(s.into())) + // Minimum execution time: 80_516 nanoseconds. + Weight::from_ref_time(86_317_884) + // Standard Error: 2_212 + .saturating_add(Weight::from_ref_time(1_103_962).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(11)) .saturating_add(RocksDbWeight::get().writes(12)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -696,10 +704,10 @@ impl WeightInfo for () { // Storage: Staking UnappliedSlashes (r:1 w:1) /// The range of component `s` is `[1, 1000]`. fn cancel_deferred_slash(s: u32, ) -> Weight { - // Minimum execution time: 92_308 nanoseconds. - Weight::from_ref_time(900_351_007) - // Standard Error: 59_145 - .saturating_add(Weight::from_ref_time(4_944_988).saturating_mul(s.into())) + // Minimum execution time: 91_795 nanoseconds. + Weight::from_ref_time(904_524_900) + // Standard Error: 59_193 + .saturating_add(Weight::from_ref_time(4_944_680).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().writes(1)) } @@ -714,10 +722,10 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) /// The range of component `n` is `[0, 256]`. fn payout_stakers_dead_controller(n: u32, ) -> Weight { - // Minimum execution time: 131_855 nanoseconds. - Weight::from_ref_time(197_412_779) - // Standard Error: 21_283 - .saturating_add(Weight::from_ref_time(22_093_758).saturating_mul(n.into())) + // Minimum execution time: 127_774 nanoseconds. + Weight::from_ref_time(178_857_156) + // Standard Error: 15_229 + .saturating_add(Weight::from_ref_time(22_112_174).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(9)) .saturating_add(RocksDbWeight::get().reads((3_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(2)) @@ -735,10 +743,10 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) /// The range of component `n` is `[0, 256]`. fn payout_stakers_alive_staked(n: u32, ) -> Weight { - // Minimum execution time: 163_118 nanoseconds. - Weight::from_ref_time(229_356_697) - // Standard Error: 30_740 - .saturating_add(Weight::from_ref_time(31_575_360).saturating_mul(n.into())) + // Minimum execution time: 161_910 nanoseconds. + Weight::from_ref_time(217_635_072) + // Standard Error: 30_726 + .saturating_add(Weight::from_ref_time(31_244_329).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(10)) .saturating_add(RocksDbWeight::get().reads((5_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(3)) @@ -752,10 +760,10 @@ impl WeightInfo for () { // Storage: VoterList ListBags (r:2 w:2) /// The range of component `l` is `[1, 32]`. fn rebond(l: u32, ) -> Weight { - // Minimum execution time: 94_048 nanoseconds. - Weight::from_ref_time(95_784_236) - // Standard Error: 2_313 - .saturating_add(Weight::from_ref_time(52_798).saturating_mul(l.into())) + // Minimum execution time: 92_986 nanoseconds. + Weight::from_ref_time(94_880_481) + // Standard Error: 2_007 + .saturating_add(Weight::from_ref_time(31_421).saturating_mul(l.into())) .saturating_add(RocksDbWeight::get().reads(9)) .saturating_add(RocksDbWeight::get().writes(8)) } @@ -774,10 +782,10 @@ impl WeightInfo for () { // Storage: Staking SpanSlash (r:0 w:1) /// The range of component `s` is `[1, 100]`. fn reap_stash(s: u32, ) -> Weight { - // Minimum execution time: 93_342 nanoseconds. - Weight::from_ref_time(95_756_184) - // Standard Error: 2_067 - .saturating_add(Weight::from_ref_time(1_090_785).saturating_mul(s.into())) + // Minimum execution time: 92_750 nanoseconds. + Weight::from_ref_time(95_115_568) + // Standard Error: 2_037 + .saturating_add(Weight::from_ref_time(1_086_488).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(12)) .saturating_add(RocksDbWeight::get().writes(12)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -801,12 +809,12 @@ impl WeightInfo for () { /// The range of component `v` is `[1, 10]`. /// The range of component `n` is `[0, 100]`. fn new_era(v: u32, n: u32, ) -> Weight { - // Minimum execution time: 506_874 nanoseconds. - Weight::from_ref_time(507_798_000) - // Standard Error: 1_802_261 - .saturating_add(Weight::from_ref_time(59_874_736).saturating_mul(v.into())) - // Standard Error: 179_585 - .saturating_add(Weight::from_ref_time(13_668_574).saturating_mul(n.into())) + // Minimum execution time: 506_543 nanoseconds. + Weight::from_ref_time(507_261_000) + // Standard Error: 1_766_631 + .saturating_add(Weight::from_ref_time(59_139_153).saturating_mul(v.into())) + // Standard Error: 176_035 + .saturating_add(Weight::from_ref_time(13_512_781).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(206)) .saturating_add(RocksDbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(RocksDbWeight::get().reads((4_u64).saturating_mul(n.into()))) @@ -823,12 +831,12 @@ impl WeightInfo for () { /// The range of component `v` is `[500, 1000]`. /// The range of component `n` is `[500, 1000]`. fn get_npos_voters(v: u32, n: u32, ) -> Weight { - // Minimum execution time: 24_634_585 nanoseconds. - Weight::from_ref_time(24_718_377_000) - // Standard Error: 324_839 - .saturating_add(Weight::from_ref_time(3_654_508).saturating_mul(v.into())) - // Standard Error: 324_839 - .saturating_add(Weight::from_ref_time(2_927_535).saturating_mul(n.into())) + // Minimum execution time: 24_155_382 nanoseconds. + Weight::from_ref_time(24_252_568_000) + // Standard Error: 319_250 + .saturating_add(Weight::from_ref_time(3_596_056).saturating_mul(v.into())) + // Standard Error: 319_250 + .saturating_add(Weight::from_ref_time(2_852_023).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(201)) .saturating_add(RocksDbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(RocksDbWeight::get().reads((4_u64).saturating_mul(n.into()))) @@ -837,10 +845,10 @@ impl WeightInfo for () { // Storage: Staking Validators (r:501 w:0) /// The range of component `v` is `[500, 1000]`. fn get_npos_targets(v: u32, ) -> Weight { - // Minimum execution time: 4_805_490 nanoseconds. - Weight::from_ref_time(118_475_494) - // Standard Error: 26_332 - .saturating_add(Weight::from_ref_time(9_635_188).saturating_mul(v.into())) + // Minimum execution time: 4_741_111 nanoseconds. + Weight::from_ref_time(113_360_179) + // Standard Error: 25_375 + .saturating_add(Weight::from_ref_time(9_494_142).saturating_mul(v.into())) .saturating_add(RocksDbWeight::get().reads(2)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(v.into()))) } @@ -851,8 +859,8 @@ impl WeightInfo for () { // Storage: Staking MaxNominatorsCount (r:0 w:1) // Storage: Staking MinNominatorBond (r:0 w:1) fn set_staking_configs_all_set() -> Weight { - // Minimum execution time: 10_816 nanoseconds. - Weight::from_ref_time(11_242_000) + // Minimum execution time: 11_074 nanoseconds. + Weight::from_ref_time(11_312_000) .saturating_add(RocksDbWeight::get().writes(6)) } // Storage: Staking MinCommission (r:0 w:1) @@ -862,8 +870,8 @@ impl WeightInfo for () { // Storage: Staking MaxNominatorsCount (r:0 w:1) // Storage: Staking MinNominatorBond (r:0 w:1) fn set_staking_configs_all_remove() -> Weight { - // Minimum execution time: 9_581 nanoseconds. - Weight::from_ref_time(10_383_000) + // Minimum execution time: 9_795 nanoseconds. + Weight::from_ref_time(10_116_000) .saturating_add(RocksDbWeight::get().writes(6)) } // Storage: Staking Ledger (r:1 w:0) @@ -877,16 +885,16 @@ impl WeightInfo for () { // Storage: VoterList ListBags (r:1 w:1) // Storage: VoterList CounterForListNodes (r:1 w:1) fn chill_other() -> Weight { - // Minimum execution time: 83_669 nanoseconds. - Weight::from_ref_time(84_772_000) + // Minimum execution time: 82_914 nanoseconds. + Weight::from_ref_time(83_848_000) .saturating_add(RocksDbWeight::get().reads(11)) .saturating_add(RocksDbWeight::get().writes(6)) } // Storage: Staking MinCommission (r:1 w:0) // Storage: Staking Validators (r:1 w:1) fn force_apply_min_commission() -> Weight { - // Minimum execution time: 20_553 nanoseconds. - Weight::from_ref_time(20_933_000) + // Minimum execution time: 20_317 nanoseconds. + Weight::from_ref_time(20_639_000) .saturating_add(RocksDbWeight::get().reads(2)) .saturating_add(RocksDbWeight::get().writes(1)) } From d29967dc93b3c3bbfa7e1c8d785df3339993415a Mon Sep 17 00:00:00 2001 From: Adrian Catangiu Date: Thu, 15 Dec 2022 12:12:36 +0200 Subject: [PATCH 043/127] documentation: add BEEFY 'spec' (#12920) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * beefy: add BEEFY 'spec' * Apply suggestions from code review Signed-off-by: Adrian Catangiu Co-authored-by: Tomasz Drwięga Co-authored-by: André Silva Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Co-authored-by: parity-processbot <> --- client/beefy/README.md | 373 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 373 insertions(+) create mode 100644 client/beefy/README.md diff --git a/client/beefy/README.md b/client/beefy/README.md new file mode 100644 index 0000000000..d9099dc7c6 --- /dev/null +++ b/client/beefy/README.md @@ -0,0 +1,373 @@ +# BEEFY +**BEEFY** (**B**ridge **E**fficiency **E**nabling **F**inality **Y**ielder) is a secondary +protocol running along GRANDPA Finality to support efficient bridging with non-Substrate +blockchains, currently mainly ETH mainnet. + +It can be thought of as an (optional) Bridge-specific Gadget to the GRANDPA Finality protocol. +The Protocol piggybacks on many assumptions provided by GRANDPA, and is required to be built +on top of it to work correctly. + +BEEFY is a consensus protocol designed with efficient trustless bridging in mind. It means +that building a light client of BEEFY protocol should be optimized for restricted environments +like Ethereum Smart Contracts or On-Chain State Transition Function (e.g. Substrate Runtime). +Note that BEEFY is not a standalone protocol, it is meant to be running alongside GRANDPA, a +finality gadget created for Substrate/Polkadot ecosystem. More details about GRANDPA can be found +in the [whitepaper](https://github.com/w3f/consensus/blob/master/pdf/grandpa.pdf). + +# Context + +## Bridges + +We want to be able to "bridge" different blockchains. We do so by safely sharing and verifying +information about each chain’s state, i.e. blockchain `A` should be able to verify that blockchain +`B` is at block #X. + +## Finality + +Finality in blockchains is a concept that means that after a given block #X has been finalized, +it will never be reverted (e.g. due to a re-org). As such, we can be assured that any transaction +that exists in this block will never be reverted. + +## GRANDPA + +GRANDPA is our finality gadget. It allows a set of nodes to come to BFT agreement on what is the +canonical chain. It requires that 2/3 of the validator set agrees on a prefix of the canonical +chain, which then becomes finalized. + +![img](https://miro.medium.com/max/955/1*NTg26i4xbO3JncF_Usu9MA.png) + +### Difficulties of GRANDPA finality proofs + +```rust +struct Justification { + round: u64, + commit: Commit, + votes_ancestries: Vec, +} + +struct Commit { + target_hash: Hash, + target_number: Number, + precommits: Vec>, +} + +struct SignedPrecommit { + precommit: Precommit, + signature: Signature, + id: Id, +} + +struct Precommit { + target_hash: Hash, + target_number: Number, +} +``` + +The main difficulty of verifying GRANDPA finality proofs comes from the fact that voters are +voting on different things. In GRANDPA each voter will vote for the block they think is the +latest one, and the protocol will come to agreement on what is the common ancestor which has > +2/3 support. + +This creates two sets of inefficiencies: + +- We may need to have each validator's vote data because they're all potentially different (i.e. + just the signature isn't enough). +- We may need to attach a couple of headers to the finality proof in order to be able to verify + all of the votes' ancestries. + +Additionally, since our interim goal is to bridge to Ethereum there is also a difficulty related +to "incompatible" crypto schemes. We use \`ed25519\` signatures in GRANDPA which we can't +efficiently verify in the EVM. + +Hence, + +### Goals of BEEFY + +1. Allow customisation of crypto to adapt for different targets. Support thresholds signatures as + well eventually. +1. Minimize the size of the "signed payload" and the finality proof. +1. Unify data types and use backward-compatible versioning so that the protocol can be extended + (additional payload, different crypto) without breaking existing light clients. + +And since BEEFY is required to be running on top of GRANDPA. This allows us to take couple of +shortcuts: +1. BEEFY validator set is **the same** as GRANDPA's (i.e. the same bonded actors), they might be + identified by different session keys though. +1. BEEFY runs on **finalized** canonical chain, i.e. no forks (note Misbehavior + section though). +1. From a single validator perspective, BEEFY has at most one active voting round. Since GRANDPA + validators are reaching finality, we assume they are on-line and well-connected and have + similar view of the state of the blockchain. + +# The BEEFY Protocol + +## Mental Model + +BEEFY should be considered as an extra voting round done by GRANDPA validators for the current +best finalized block. Similarily to how GRANDPA is lagging behind best produced (non-finalized) +block, BEEFY is going to lag behind best GRANDPA (finalized) block. + +``` + ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ + │ │ │ │ │ │ │ │ │ │ + │ B1 │ │ B2 │ │ B3 │ │ B4 │ │ B5 │ + │ │ │ │ │ │ │ │ │ │ + └──────┘ └───▲──┘ └──────┘ └───▲──┘ └───▲──┘ + │ │ │ + Best BEEFY block───────────────┘ │ │ + │ │ + Best GRANDPA block───────────────────────────────┘ │ + │ + Best produced block───────────────────────────────────────┘ + +``` + +A pseudo-algorithm of behaviour for a fully-synced BEEFY validator is: + +``` +loop { + let (best_beefy, best_grandpa) = wait_for_best_blocks(); + + let block_to_vote_on = choose_next_beefy_block( + best_beefy, + best_grandpa + ); + + let payload_to_vote_on = retrieve_payload(block_to_vote_on); + + let commitment = (block_to_vote_on, payload_to_vote_on); + + let signature = sign_with_current_session_key(commitment); + + broadcast_vote(commitment, signature); +} +``` + +## Details + +Before we jump into describing how BEEFY works in details, let's agree on the terms we are going +to use and actors in the system. All nodes in the network need to participate in the BEEFY +networking protocol, but we can identify two distinct actors though: **regular nodes** and +**BEEFY validators**. +Validators are expected to actively participate in the protocol, by producing and broadcasting +**votes**. Votes are simply their signatures over a **Commitment**. A Commitment consists of a +**payload** (an opaque blob of bytes extracted from a block or state at that block, expected to +be some form of crypto accumulator (like Merkle Tree Hash or Merkle Mountain Range Root Hash)) +and **block number** from which this payload originates. Additionally, Commitment contains BEEFY +**validator set id** at that particular block. Note the block is finalized, so there is no +ambiguity despite using block number instead of a hash. A collection of **votes**, or rather +a Commitment and a collection of signatures is going to be called **Signed Commitment**. A valid +(see later for the rules) Signed Commitment is also called a **BEEFY Justification** or +**BEEFY Finality Proof**. For more details on the actual data structures please see +[BEEFY primitives definitions](https://github.com/paritytech/substrate/tree/master/primitives/beefy/src). + +A **round** is an attempt by BEEFY validators to produce a BEEFY Justification. **Round number** +is simply defined as a block number the validators are voting for, or to be more precise, the +Commitment for that block number. Round ends when the next round is started, which may happen +when one of the events occur: +1. Either the node collects `2/3rd + 1` valid votes for that round. +2. Or the node receives a BEEFY Justification for a block greater than the current best BEEFY block. + +In both cases the node proceeds to determining the new round number using "Round Selection" +procedure. + +Regular nodes are expected to: +1. Receive & validate votes for the current round and broadcast them to their peers. +1. Receive & validate BEEFY Justifications and broadcast them to their peers. +1. Return BEEFY Justifications for **Mandatory Blocks** on demand. +1. Optionally return BEEFY Justifications for non-mandatory blocks on demand. + +Validators are expected to additionally: +1. Produce & broadcast vote for the current round. + +Both kinds of actors are expected to fully participate in the protocol ONLY IF they believe they +are up-to-date with the rest of the network, i.e. they are fully synced. Before this happens, +the node should continue processing imported BEEFY Justifications and votes without actively +voting themselves. + +### Round Selection + +Every node (both regular nodes and validators) need to determine locally what they believe +current round number is. The choice is based on their knowledge of: + +1. Best GRANDPA finalized block number (`best_grandpa`). +1. Best BEEFY finalized block number (`best_beefy`). +1. Starting block of current session (`session_start`). + +**Session** means a period of time (or rather number of blocks) where validator set (keys) do not change. +See `pallet_session` for implementation details in `FRAME` context. Since we piggy-back on +GRANDPA, session boundaries for BEEFY are exactly the same as the ones for GRANDPA. + +We define two kinds of blocks from the perspective of BEEFY protocol: +1. **Mandatory Blocks** +2. **Non-mandatory Blocks** + +Mandatory blocks are the ones that MUST have BEEFY justification. That means that the validators +will always start and conclude a round at mandatory blocks. For non-mandatory blocks, there may +or may not be a justification and validators may never choose these blocks to start a round. + +Every **first block in** each **session** is considered a **mandatory block**. All other blocks +in the session are non-mandatory, however validators are encouraged to finalize as many blocks as +possible to enable lower latency for light clients and hence end users. Since GRANDPA is +considering session boundary blocks as mandatory as well, `session_start` block will always have +both GRANDPA and BEEFY Justification. + +Therefore, to determine current round number nodes use a formula: + +``` +round_number = + (1 - M) * session_start + + M * (best_beefy + NEXT_POWER_OF_TWO((best_grandpa - best_beefy + 1) / 2)) +``` + +where: + +- `M` is `1` if mandatory block in current session is already finalized and `0` otherwise. +- `NEXT_POWER_OF_TWO(x)` returns the smallest number greater or equal to `x` that is a power of two. + +In other words, the next round number should be the oldest mandatory block without a justification, +or the highest GRANDPA-finalized block, whose block number difference with `best_beefy` block is +a power of two. The mental model for round selection is to first finalize the mandatory block and +then to attempt to pick a block taking into account how fast BEEFY catches up with GRANDPA. +In case GRANDPA makes progress, but BEEFY seems to be lagging behind, validators are changing +rounds less often to increase the chance of concluding them. + +As mentioned earlier, every time the node picks a new `round_number` (and validator casts a vote) +it ends the previous one, no matter if finality was reached (i.e. the round concluded) or not. +Votes for an inactive round should not be propagated. + +Note that since BEEFY only votes for GRANDPA-finalized blocks, `session_start` here actually means: +"the latest session for which the start of is GRANDPA-finalized", i.e. block production might +have already progressed, but BEEFY needs to first finalize the mandatory block of the older +session. + +In good networking conditions BEEFY may end up finalizing each and every block (if GRANDPA does +the same). Practically, with short block times, it's going to be rare and might be excessive, so +it's suggested for implementations to introduce a `min_delta` parameter which will limit the +frequency with which new rounds are started. The affected component of the formula would be: +`best_beefy + MAX(min_delta, NEXT_POWER_OF_TWO(...))`, so we start a new round only if the +power-of-two component is greater than the min delta. Note that if `round_number > best_grandpa` +the validators are not expected to start any round. + +### Catch up + +Every session is guaranteed to have at least one BEEFY-finalized block. However it also means +that the round at mandatory block must be concluded even though, a new session has already started +(i.e. the on-chain component has selected a new validator set and GRANDPA might have already +finalized the transition). In such case BEEFY must "catch up" the previous sessions and make sure to +conclude rounds for mandatory blocks. Note that older sessions must obviously be finalized by the +validator set at that point in time, not the latest/current one. + +### Initial Sync + +It's all rainbows and unicorns when the node is fully synced with the network. However during cold +startup it will have hard time determining the current round number. Because of that nodes that +are not fully synced should not participate in BEEFY protocol at all. + +During the sync we should make sure to also fetch BEEFY justifications for all mandatory blocks. +This can happen asynchronously, but validators, before starting to vote, need to be certain +about the last session that contains a concluded round on mandatory block in order to initiate the +catch up procedure. + +### Gossip + +Nodes participating in BEEFY protocol are expected to gossip messages around. +The protocol defines following messages: + +1. Votes for the current round, +2. BEEFY Justifications for recently concluded rounds, +3. BEEFY Justification for the latest mandatory block, + +Each message is additionally associated with a **topic**, which can be either: +1. the round number (i.e. topic associated with a particular round), +2. or the global topic (independent from the rounds). + +Round-specific topic should only be used to gossip the votes, other messages are gossiped +periodically on the global topic. Let's now dive into description of the messages. + +- **Votes** + - Votes are sent on the round-specific topic. + - Vote is considered valid when: + - The commitment matches local commitment. + - The validator is part of the current validator set. + - The signature is correct. + +- **BEEFY Justification** + - Justifications are sent on the global topic. + - Justification is considered worthwhile to gossip when: + - It is for a recent (implementation specific) round or the latest mandatory round. + - All signatures are valid and there is at least `2/3rd + 1` of them. + - Signatorees are part of the current validator set. + - Mandatory justifications should be announced periodically. + +## Misbehavior + +Similarily to other PoS protocols, BEEFY considers casting two different votes in the same round a +misbehavior. I.e. for a particular `round_number`, the validator produces signatures for 2 different +`Commitment`s and broadcasts them. This is called **equivocation**. + +On top of this, voting on an incorrect **payload** is considered a misbehavior as well, and since +we piggy-back on GRANDPA there is no ambiguity in terms of the fork validators should be voting for. + +Misbehavior should be penalized. If more validators misbehave in the exact same `round` the +penalty should be more severe, up to the entire bonded stake in case we reach `1/3rd + 1` +validators misbehaving. + +## Ethereum + +Initial version of BEEFY was made to enable efficient bridging with Ethereum, where the light +client is a Solidity Smart Contract compiled to EVM bytecode. Hence the choice of the initial +cryptography for BEEFY: `secp256k1` and usage of `keccak256` hashing function. + +### Future: Supporting multiple crypto + +While BEEFY currently works with `secp256k1` signatures, we intend in the future to support +multiple signature schemes. +This means that multiple kinds of `SignedCommitment`s might exist and only together they form a +full `BEEFY Justification`. + +## BEEFY Key + +The current cryptographic scheme used by BEEFY is `ecdsa`. This is **different** from other +schemes like `sr25519` and `ed25519` which are commonly used in Substrate configurations for +other pallets (BABE, GRANDPA, AuRa, etc). The most noticeable difference is that an `ecdsa` +public key is `33` bytes long, instead of `32` bytes for a `sr25519` based public key. So, a +BEEFY key [sticks out](https://github.com/paritytech/polkadot/blob/25951e45b1907853f120c752aaa01631a0b3e783/node/service/src/chain_spec.rs#L738) +among the other public keys a bit. + +For other crypto (using the default Substrate configuration) the `AccountId` (32-bytes) matches +the `PublicKey`, but note that it's not the case for BEEFY. As a consequence of this, you can +**not** convert the `AccountId` raw bytes into a BEEFY `PublicKey`. + +The easiest way to generate or view hex-encoded or SS58-encoded BEEFY Public Key is by using the +[Subkey](https://substrate.dev/docs/en/knowledgebase/integrate/subkey) tool. Generate a BEEFY key +using the following command + +```sh +subkey generate --scheme ecdsa +``` + +The output will look something like + +```sh +Secret phrase `sunset anxiety liberty mention dwarf actress advice stove peasant olive kite rebuild` is account: + Secret seed: 0x9f844e21444683c8fcf558c4c11231a14ed9dea6f09a8cc505604368ef204a61 + Public key (hex): 0x02d69740c3bbfbdbb365886c8270c4aafd17cbffb2e04ecef581e6dced5aded2cd + Public key (SS58): KW7n1vMENCBLQpbT5FWtmYWHNvEyGjSrNL4JE32mDds3xnXTf + Account ID: 0x295509ae9a9b04ade5f1756b5f58f4161cf57037b4543eac37b3b555644f6aed + SS58 Address: 5Czu5hudL79ETnQt6GAkVJHGhDQ6Qv3VWq54zN1CPKzKzYGu + +``` + +In case your BEEFY keys are using the wrong cryptographic scheme, you will see an invalid public +key format message at node startup. Basically something like + +```sh +... +2021-05-28 12:37:51 [Relaychain] Invalid BEEFY PublicKey format! +... +``` + +# BEEFY Light Client + +TODO From cb4f2491b00af7d7817f3a54209c26b20faa1f51 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 15 Dec 2022 11:38:21 +0100 Subject: [PATCH 044/127] Warn on missing `pallet::call_index` (#12894) * Warn on missing call_index Signed-off-by: Oliver Tale-Yazdi * Suppress camel case warning Signed-off-by: Oliver Tale-Yazdi * Simplify code Signed-off-by: Oliver Tale-Yazdi * Disallow warnings in pallet-ui tests Signed-off-by: Oliver Tale-Yazdi * Add pallet UI test Signed-off-by: Oliver Tale-Yazdi * Update Pallet UI Signed-off-by: Oliver Tale-Yazdi * fmt Signed-off-by: Oliver Tale-Yazdi * Use module instead of function Signed-off-by: Oliver Tale-Yazdi * Update pallet-ui Signed-off-by: Oliver Tale-Yazdi Signed-off-by: Oliver Tale-Yazdi --- .../procedural/src/pallet/expand/call.rs | 31 +++++++++++++++++++ .../procedural/src/pallet/parse/call.rs | 4 +++ frame/support/test/tests/pallet_ui.rs | 3 ++ .../pallet_ui/call_argument_invalid_bound.rs | 3 +- .../call_argument_invalid_bound.stderr | 18 +++++------ .../call_argument_invalid_bound_2.rs | 3 +- .../call_argument_invalid_bound_2.stderr | 24 +++++++------- .../call_argument_invalid_bound_3.rs | 3 +- .../call_argument_invalid_bound_3.stderr | 6 ++-- .../tests/pallet_ui/call_missing_index.rs | 22 +++++++++++++ .../tests/pallet_ui/call_missing_index.stderr | 12 +++++++ ...ev_mode_without_arg_max_encoded_len.stderr | 13 ++++++++ 12 files changed, 115 insertions(+), 27 deletions(-) create mode 100644 frame/support/test/tests/pallet_ui/call_missing_index.rs create mode 100644 frame/support/test/tests/pallet_ui/call_missing_index.stderr diff --git a/frame/support/procedural/src/pallet/expand/call.rs b/frame/support/procedural/src/pallet/expand/call.rs index 6b166e6726..30ef1a8fca 100644 --- a/frame/support/procedural/src/pallet/expand/call.rs +++ b/frame/support/procedural/src/pallet/expand/call.rs @@ -54,6 +54,29 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { .map(|fn_name| format!("Create a call with the variant `{}`.", fn_name)) .collect::>(); + let mut warning_structs = Vec::new(); + let mut warning_names = Vec::new(); + // Emit a warning for each call that is missing `call_index` when not in dev-mode. + for method in &methods { + if method.explicit_call_index || def.dev_mode { + continue + } + + let name = syn::Ident::new(&format!("{}", method.name), method.name.span()); + let warning: syn::ItemStruct = syn::parse_quote!( + #[deprecated(note = r" + Implicit call indices are deprecated in favour of explicit ones. + Please ensure that all calls have the `pallet::call_index` attribute or that the + `dev-mode` of the pallet is enabled. For more info see: + and + .")] + #[allow(non_camel_case_types)] + struct #name; + ); + warning_names.push(name); + warning_structs.push(warning); + } + let fn_weight = methods.iter().map(|method| &method.weight); let fn_doc = methods.iter().map(|method| &method.docs).collect::>(); @@ -178,6 +201,14 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { .collect::>(); quote::quote_spanned!(span => + mod warnings { + #( + #warning_structs + // This triggers each deprecated warning once. + const _: Option<#warning_names> = None; + )* + } + #[doc(hidden)] pub mod __substrate_call_check { #[macro_export] diff --git a/frame/support/procedural/src/pallet/parse/call.rs b/frame/support/procedural/src/pallet/parse/call.rs index fbca9a52c7..9620038edf 100644 --- a/frame/support/procedural/src/pallet/parse/call.rs +++ b/frame/support/procedural/src/pallet/parse/call.rs @@ -59,6 +59,8 @@ pub struct CallVariantDef { pub weight: syn::Expr, /// Call index of the dispatchable. pub call_index: u8, + /// Whether an explicit call index was specified. + pub explicit_call_index: bool, /// Docs, used for metadata. pub docs: Vec, /// Attributes annotated at the top of the dispatchable function. @@ -243,6 +245,7 @@ impl CallDef { FunctionAttr::CallIndex(idx) => idx, _ => unreachable!("checked during creation of the let binding"), }); + let explicit_call_index = call_index.is_some(); let final_index = match call_index { Some(i) => i, @@ -296,6 +299,7 @@ impl CallDef { name: method.sig.ident.clone(), weight, call_index: final_index, + explicit_call_index, args, docs, attrs: method.attrs.clone(), diff --git a/frame/support/test/tests/pallet_ui.rs b/frame/support/test/tests/pallet_ui.rs index 2db1d3cb05..26d016c5a7 100644 --- a/frame/support/test/tests/pallet_ui.rs +++ b/frame/support/test/tests/pallet_ui.rs @@ -27,6 +27,9 @@ fn pallet_ui() { // As trybuild is using `cargo check`, we don't need the real WASM binaries. std::env::set_var("SKIP_WASM_BUILD", "1"); + // Deny all warnings since we emit warnings as part of a Pallet's UI. + std::env::set_var("RUSTFLAGS", "--deny warnings"); + let t = trybuild::TestCases::new(); t.compile_fail("tests/pallet_ui/*.rs"); t.pass("tests/pallet_ui/pass/*.rs"); diff --git a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.rs b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.rs index ee9d692eba..4f18f72818 100644 --- a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.rs +++ b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.rs @@ -17,7 +17,8 @@ mod pallet { #[pallet::call] impl Pallet { #[pallet::weight(0)] - pub fn foo(origin: OriginFor, bar: T::Bar) -> DispatchResultWithPostInfo { + #[pallet::call_index(0)] + pub fn foo(origin: OriginFor, _bar: T::Bar) -> DispatchResultWithPostInfo { Ok(().into()) } } diff --git a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.stderr b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.stderr index 62d8649f8a..aed53b07b5 100644 --- a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.stderr +++ b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.stderr @@ -1,21 +1,21 @@ error[E0277]: `::Bar` doesn't implement `std::fmt::Debug` - --> tests/pallet_ui/call_argument_invalid_bound.rs:20:36 + --> tests/pallet_ui/call_argument_invalid_bound.rs:21:36 | -20 | pub fn foo(origin: OriginFor, bar: T::Bar) -> DispatchResultWithPostInfo { - | ^^^ `::Bar` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug` +21 | pub fn foo(origin: OriginFor, _bar: T::Bar) -> DispatchResultWithPostInfo { + | ^^^^ `::Bar` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug` | = help: the trait `std::fmt::Debug` is not implemented for `::Bar` = note: required for `&::Bar` to implement `std::fmt::Debug` = note: required for the cast from `&::Bar` to the object type `dyn std::fmt::Debug` error[E0277]: the trait bound `::Bar: Clone` is not satisfied - --> tests/pallet_ui/call_argument_invalid_bound.rs:20:36 + --> tests/pallet_ui/call_argument_invalid_bound.rs:21:36 | -20 | pub fn foo(origin: OriginFor, bar: T::Bar) -> DispatchResultWithPostInfo { - | ^^^ the trait `Clone` is not implemented for `::Bar` +21 | pub fn foo(origin: OriginFor, _bar: T::Bar) -> DispatchResultWithPostInfo { + | ^^^^ the trait `Clone` is not implemented for `::Bar` error[E0369]: binary operation `==` cannot be applied to type `&::Bar` - --> tests/pallet_ui/call_argument_invalid_bound.rs:20:36 + --> tests/pallet_ui/call_argument_invalid_bound.rs:21:36 | -20 | pub fn foo(origin: OriginFor, bar: T::Bar) -> DispatchResultWithPostInfo { - | ^^^ +21 | pub fn foo(origin: OriginFor, _bar: T::Bar) -> DispatchResultWithPostInfo { + | ^^^^ diff --git a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.rs b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.rs index d981b55c48..20568908e7 100644 --- a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.rs +++ b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.rs @@ -17,7 +17,8 @@ mod pallet { #[pallet::call] impl Pallet { #[pallet::weight(0)] - pub fn foo(origin: OriginFor, bar: T::Bar) -> DispatchResultWithPostInfo { + #[pallet::call_index(0)] + pub fn foo(origin: OriginFor, _bar: T::Bar) -> DispatchResultWithPostInfo { Ok(().into()) } } diff --git a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr index f486c07163..3e2e70432a 100644 --- a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr +++ b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr @@ -1,27 +1,27 @@ error[E0277]: `::Bar` doesn't implement `std::fmt::Debug` - --> tests/pallet_ui/call_argument_invalid_bound_2.rs:20:36 + --> tests/pallet_ui/call_argument_invalid_bound_2.rs:21:36 | -20 | pub fn foo(origin: OriginFor, bar: T::Bar) -> DispatchResultWithPostInfo { - | ^^^ `::Bar` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug` +21 | pub fn foo(origin: OriginFor, _bar: T::Bar) -> DispatchResultWithPostInfo { + | ^^^^ `::Bar` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug` | = help: the trait `std::fmt::Debug` is not implemented for `::Bar` = note: required for `&::Bar` to implement `std::fmt::Debug` = note: required for the cast from `&::Bar` to the object type `dyn std::fmt::Debug` error[E0277]: the trait bound `::Bar: Clone` is not satisfied - --> tests/pallet_ui/call_argument_invalid_bound_2.rs:20:36 + --> tests/pallet_ui/call_argument_invalid_bound_2.rs:21:36 | -20 | pub fn foo(origin: OriginFor, bar: T::Bar) -> DispatchResultWithPostInfo { - | ^^^ the trait `Clone` is not implemented for `::Bar` +21 | pub fn foo(origin: OriginFor, _bar: T::Bar) -> DispatchResultWithPostInfo { + | ^^^^ the trait `Clone` is not implemented for `::Bar` error[E0369]: binary operation `==` cannot be applied to type `&::Bar` - --> tests/pallet_ui/call_argument_invalid_bound_2.rs:20:36 + --> tests/pallet_ui/call_argument_invalid_bound_2.rs:21:36 | -20 | pub fn foo(origin: OriginFor, bar: T::Bar) -> DispatchResultWithPostInfo { - | ^^^ +21 | pub fn foo(origin: OriginFor, _bar: T::Bar) -> DispatchResultWithPostInfo { + | ^^^^ error[E0277]: the trait bound `::Bar: WrapperTypeEncode` is not satisfied - --> tests/pallet_ui/call_argument_invalid_bound_2.rs:20:36 + --> tests/pallet_ui/call_argument_invalid_bound_2.rs:21:36 | 1 | / #[frame_support::pallet] 2 | | mod pallet { @@ -32,8 +32,8 @@ error[E0277]: the trait bound `::Bar: WrapperTypeEncode` is 17 | | #[pallet::call] | |__________________- required by a bound introduced by this call ... -20 | pub fn foo(origin: OriginFor, bar: T::Bar) -> DispatchResultWithPostInfo { - | ^^^ the trait `WrapperTypeEncode` is not implemented for `::Bar` +21 | pub fn foo(origin: OriginFor, _bar: T::Bar) -> DispatchResultWithPostInfo { + | ^^^^ the trait `WrapperTypeEncode` is not implemented for `::Bar` | = note: required for `::Bar` to implement `Encode` diff --git a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.rs b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.rs index 1cdfb369fe..64b6642b0a 100644 --- a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.rs +++ b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.rs @@ -19,7 +19,8 @@ mod pallet { #[pallet::call] impl Pallet { #[pallet::weight(0)] - pub fn foo(origin: OriginFor, bar: Bar) -> DispatchResultWithPostInfo { + #[pallet::call_index(0)] + pub fn foo(origin: OriginFor, _bar: Bar) -> DispatchResultWithPostInfo { Ok(().into()) } } diff --git a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.stderr b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.stderr index 6e51bf2dbf..395da09cb0 100644 --- a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.stderr +++ b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.stderr @@ -1,8 +1,8 @@ error[E0277]: `Bar` doesn't implement `std::fmt::Debug` - --> tests/pallet_ui/call_argument_invalid_bound_3.rs:22:36 + --> tests/pallet_ui/call_argument_invalid_bound_3.rs:23:36 | -22 | pub fn foo(origin: OriginFor, bar: Bar) -> DispatchResultWithPostInfo { - | ^^^ `Bar` cannot be formatted using `{:?}` +23 | pub fn foo(origin: OriginFor, _bar: Bar) -> DispatchResultWithPostInfo { + | ^^^^ `Bar` cannot be formatted using `{:?}` | = help: the trait `std::fmt::Debug` is not implemented for `Bar` = note: add `#[derive(Debug)]` to `Bar` or manually `impl std::fmt::Debug for Bar` diff --git a/frame/support/test/tests/pallet_ui/call_missing_index.rs b/frame/support/test/tests/pallet_ui/call_missing_index.rs new file mode 100644 index 0000000000..7e305a573d --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_missing_index.rs @@ -0,0 +1,22 @@ +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::DispatchResult; + use frame_system::pallet_prelude::OriginFor; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::call] + impl Pallet { + #[pallet::weight(0)] + pub fn foo(_: OriginFor) -> DispatchResult { + Ok(()) + } + } +} + +fn main() { +} diff --git a/frame/support/test/tests/pallet_ui/call_missing_index.stderr b/frame/support/test/tests/pallet_ui/call_missing_index.stderr new file mode 100644 index 0000000000..9d00204979 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_missing_index.stderr @@ -0,0 +1,12 @@ +error: use of deprecated struct `pallet::warnings::foo`: + Implicit call indices are deprecated in favour of explicit ones. + Please ensure that all calls have the `pallet::call_index` attribute or that the + `dev-mode` of the pallet is enabled. For more info see: + and + . + --> tests/pallet_ui/call_missing_index.rs:15:10 + | +15 | pub fn foo(_: OriginFor) -> DispatchResult { + | ^^^ + | + = note: `-D deprecated` implied by `-D warnings` diff --git a/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr b/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr index a5ec31a9bb..170555665d 100644 --- a/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr +++ b/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr @@ -1,3 +1,16 @@ +error: use of deprecated struct `pallet::warnings::my_call`: + Implicit call indices are deprecated in favour of explicit ones. + Please ensure that all calls have the `pallet::call_index` attribute or that the + `dev-mode` of the pallet is enabled. For more info see: + and + . + --> tests/pallet_ui/dev_mode_without_arg_max_encoded_len.rs:25:10 + | +25 | pub fn my_call(_origin: OriginFor) -> DispatchResult { + | ^^^^^^^ + | + = note: `-D deprecated` implied by `-D warnings` + error[E0277]: the trait bound `Vec: MaxEncodedLen` is not satisfied --> tests/pallet_ui/dev_mode_without_arg_max_encoded_len.rs:11:12 | From 47ebf8742144e1b18414ecf97f43c16de624a180 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulo=20Silva=20de=20Souza?= <77391175+joao-paulo-parity@users.noreply.github.com> Date: Thu, 15 Dec 2022 09:19:50 -0300 Subject: [PATCH 045/127] increase timeout for publishing (#12934) necessary when lots of crates need to be published, as demonstrated by https://gitlab.parity.io/parity/mirrors/substrate/-/jobs/2158667 --- scripts/ci/gitlab/pipeline/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ci/gitlab/pipeline/publish.yml b/scripts/ci/gitlab/pipeline/publish.yml index 6a0d6d6341..68a10e8aa1 100644 --- a/scripts/ci/gitlab/pipeline/publish.yml +++ b/scripts/ci/gitlab/pipeline/publish.yml @@ -211,7 +211,7 @@ update-node-template: # taking into account the 202 (as of Dec 07, 2022) publishable Substrate crates, that would equate # to roughly 202 minutes of delay, or 3h and 22 minutes. As such, the job needs to have a much # higher timeout than average. - timeout: 5h + timeout: 9h # A custom publishing environment is used for us to be able to set up protected secrets # specifically for it environment: publish-crates From f050628b6763176da175ab90d641c3eef608c610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Thu, 15 Dec 2022 14:06:58 +0100 Subject: [PATCH 046/127] Node template isn't licensed, remove left-over GPL header (#12937) --- bin/node-template/node/src/benchmarking.rs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/bin/node-template/node/src/benchmarking.rs b/bin/node-template/node/src/benchmarking.rs index 480e547c9c..37e0e46596 100644 --- a/bin/node-template/node/src/benchmarking.rs +++ b/bin/node-template/node/src/benchmarking.rs @@ -1,21 +1,3 @@ -// This file is part of Substrate. - -// Copyright (C) 2022 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 . - //! Setup code for [`super::command`] which would otherwise bloat that module. //! //! Should only be used for benchmarking as it may break in other contexts. From 5d3624c7fb7648d6f3170007790e95e726d84f35 Mon Sep 17 00:00:00 2001 From: bernardo Date: Fri, 16 Dec 2022 05:49:36 -0300 Subject: [PATCH 047/127] improve ocw validator/collator CLI description (#12931) * improve ocw validator/collator CLI description * rename WhenValidating to WhenAuthoring * Update client/cli/src/arg_enums.rs Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> --- client/cli/src/arg_enums.rs | 5 +++-- client/cli/src/params/offchain_worker_params.rs | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/client/cli/src/arg_enums.rs b/client/cli/src/arg_enums.rs index 20f68bc7fb..372e8645e3 100644 --- a/client/cli/src/arg_enums.rs +++ b/client/cli/src/arg_enums.rs @@ -232,8 +232,9 @@ pub enum OffchainWorkerEnabled { Always, /// Never enable the offchain worker. Never, - /// Only enable the offchain worker when running as validator. - WhenValidating, + /// Only enable the offchain worker when running as a validator (or collator, if this is a + /// parachain node). + WhenAuthority, } /// Syncing mode. diff --git a/client/cli/src/params/offchain_worker_params.rs b/client/cli/src/params/offchain_worker_params.rs index a660320fde..b18e036c06 100644 --- a/client/cli/src/params/offchain_worker_params.rs +++ b/client/cli/src/params/offchain_worker_params.rs @@ -40,7 +40,7 @@ pub struct OffchainWorkerParams { value_name = "ENABLED", value_enum, ignore_case = true, - default_value_t = OffchainWorkerEnabled::WhenValidating + default_value_t = OffchainWorkerEnabled::WhenAuthority )] pub enabled: OffchainWorkerEnabled, @@ -56,10 +56,10 @@ impl OffchainWorkerParams { /// Load spec to `Configuration` from `OffchainWorkerParams` and spec factory. pub fn offchain_worker(&self, role: &Role) -> error::Result { let enabled = match (&self.enabled, role) { - (OffchainWorkerEnabled::WhenValidating, Role::Authority { .. }) => true, + (OffchainWorkerEnabled::WhenAuthority, Role::Authority { .. }) => true, (OffchainWorkerEnabled::Always, _) => true, (OffchainWorkerEnabled::Never, _) => false, - (OffchainWorkerEnabled::WhenValidating, _) => false, + (OffchainWorkerEnabled::WhenAuthority, _) => false, }; let indexing_enabled = self.indexing_enabled; From 8ef806c40eb58803d64328250b9e60a7d1170504 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulo=20Silva=20de=20Souza?= <77391175+joao-paulo-parity@users.noreply.github.com> Date: Fri, 16 Dec 2022 09:29:55 -0300 Subject: [PATCH 048/127] Set WASM_BUILD_WORKSPACE_HINT before starting CI jobs (#12816) * set WASM_BUILD_WORKSPACE_HINT this enables wasm-builder to find the Cargo.lock from within generated packages * wip: try out https://github.com/paritytech/pipeline-scripts/pull/90 * wip: fixup * wip * revert ci skip * set up WASM_BUILD_WORKSPACE_HINT for .kubernetes-env as well * Update scripts/ci/gitlab/pipeline/publish.yml Co-authored-by: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Co-authored-by: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> --- .gitlab-ci.yml | 13 +++++++++++-- scripts/ci/gitlab/pipeline/publish.yml | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index dcc0cbb7c9..145a25c809 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -81,6 +81,15 @@ default: paths: - artifacts/ +.prepare-env: + before_script: + # TODO: remove unset invocation when we'll be free from 'ENV RUSTC_WRAPPER=sccache' & sccache + # itself in all images + - unset RUSTC_WRAPPER + # $WASM_BUILD_WORKSPACE_HINT enables wasm-builder to find the Cargo.lock from within generated + # packages + - export WASM_BUILD_WORKSPACE_HINT="$PWD" + .job-switcher: before_script: - if echo "$CI_DISABLED_JOBS" | grep -xF "$CI_JOB_NAME"; then echo "The job has been cancelled in CI settings"; exit 0; fi @@ -89,6 +98,7 @@ default: image: "${CI_IMAGE}" before_script: - !reference [.job-switcher, before_script] + - !reference [.prepare-env, before_script] tags: - kubernetes-parity-build @@ -115,9 +125,8 @@ default: .docker-env: image: "${CI_IMAGE}" before_script: - # TODO: remove unset invocation when we'll be free from 'ENV RUSTC_WRAPPER=sccache' & sccache itself in all images - - unset RUSTC_WRAPPER - !reference [.job-switcher, before_script] + - !reference [.prepare-env, before_script] - !reference [.rust-info-script, script] - !reference [.rusty-cachier, before_script] - !reference [.pipeline-stopper-vars, script] diff --git a/scripts/ci/gitlab/pipeline/publish.yml b/scripts/ci/gitlab/pipeline/publish.yml index 68a10e8aa1..f2fd64bc34 100644 --- a/scripts/ci/gitlab/pipeline/publish.yml +++ b/scripts/ci/gitlab/pipeline/publish.yml @@ -12,7 +12,7 @@ DOCKERFILE: $PRODUCT.Dockerfile IMAGE_NAME: docker.io/$IMAGE_PATH before_script: - - !reference [.job-switcher, before_script] + - !reference [.kubernetes-env, before_script] - cd ./artifacts/$PRODUCT/ - VERSION="$(cat ./VERSION)" - echo "${PRODUCT} version = ${VERSION}" From d57f8a9f96e66bb23f74a6ad59d009c6564fc151 Mon Sep 17 00:00:00 2001 From: Dmitry Markin Date: Fri, 16 Dec 2022 16:29:39 +0300 Subject: [PATCH 049/127] Clean up tests from `runtime.block_on()` and moving around `Runtime` and `Handle` (#12941) --- client/beefy/src/aux_schema.rs | 8 +- client/beefy/src/tests.rs | 269 ++++------ client/beefy/src/worker.rs | 44 +- client/consensus/aura/src/lib.rs | 61 +-- client/consensus/babe/src/tests.rs | 295 +++++----- client/finality-grandpa/src/tests.rs | 292 +++++----- client/network-gossip/src/bridge.rs | 2 +- .../notifications/upgrade/notifications.rs | 185 +++---- .../network/src/service/tests/chain_sync.rs | 19 +- client/network/src/service/tests/mod.rs | 33 +- client/network/src/service/tests/service.rs | 352 ++++++------ client/network/test/src/lib.rs | 63 +-- client/network/test/src/sync.rs | 503 +++++++++--------- 13 files changed, 987 insertions(+), 1139 deletions(-) diff --git a/client/beefy/src/aux_schema.rs b/client/beefy/src/aux_schema.rs index 9d6a4292f3..67deee19b5 100644 --- a/client/beefy/src/aux_schema.rs +++ b/client/beefy/src/aux_schema.rs @@ -77,7 +77,6 @@ pub(crate) mod tests { use super::*; use crate::tests::BeefyTestNet; use sc_network_test::TestNetFactory; - use tokio::runtime::Runtime; // also used in tests.rs pub fn verify_persisted_version>(backend: &BE) -> bool { @@ -85,10 +84,9 @@ pub(crate) mod tests { version == CURRENT_VERSION } - #[test] - fn should_load_persistent_sanity_checks() { - let runtime = Runtime::new().unwrap(); - let mut net = BeefyTestNet::new(runtime.handle().clone(), 1); + #[tokio::test] + async fn should_load_persistent_sanity_checks() { + let mut net = BeefyTestNet::new(1); let backend = net.peer(0).client().as_backend(); // version not available in db -> None diff --git a/client/beefy/src/tests.rs b/client/beefy/src/tests.rs index f6ab0dd102..d2befc979b 100644 --- a/client/beefy/src/tests.rs +++ b/client/beefy/src/tests.rs @@ -47,7 +47,7 @@ use sc_consensus::{ use sc_network::{config::RequestResponseConfig, ProtocolName}; use sc_network_test::{ Block, BlockImportAdapter, FullPeerConfig, PassThroughVerifier, Peer, PeersClient, - PeersFullClient, TestNetFactory, WithRuntime, + PeersFullClient, TestNetFactory, }; use sc_utils::notification::NotificationReceiver; use serde::{Deserialize, Serialize}; @@ -64,10 +64,7 @@ use sp_runtime::{ }; use std::{collections::HashMap, marker::PhantomData, sync::Arc, task::Poll}; use substrate_test_runtime_client::{runtime::Header, ClientExt}; -use tokio::{ - runtime::{Handle, Runtime}, - time::Duration, -}; +use tokio::time::Duration; const GENESIS_HASH: H256 = H256::zero(); fn beefy_gossip_proto_name() -> ProtocolName { @@ -106,23 +103,14 @@ pub(crate) struct PeerData { Mutex>>, } +#[derive(Default)] pub(crate) struct BeefyTestNet { - rt_handle: Handle, peers: Vec, } -impl WithRuntime for BeefyTestNet { - fn with_runtime(rt_handle: Handle) -> Self { - BeefyTestNet { rt_handle, peers: Vec::new() } - } - fn rt_handle(&self) -> &Handle { - &self.rt_handle - } -} - impl BeefyTestNet { - pub(crate) fn new(rt_handle: Handle, n_authority: usize) -> Self { - let mut net = BeefyTestNet::with_runtime(rt_handle); + pub(crate) fn new(n_authority: usize) -> Self { + let mut net = BeefyTestNet { peers: Vec::with_capacity(n_authority) }; for i in 0..n_authority { let (rx, cfg) = on_demand_justifications_protocol_config(GENESIS_HASH, None); @@ -151,13 +139,12 @@ impl BeefyTestNet { }); } - pub(crate) fn generate_blocks_and_sync( + pub(crate) async fn generate_blocks_and_sync( &mut self, count: usize, session_length: u64, validator_set: &BeefyValidatorSet, include_mmr_digest: bool, - runtime: &mut Runtime, ) { self.peer(0).generate_blocks(count, BlockOrigin::File, |builder| { let mut block = builder.build().unwrap().block; @@ -175,7 +162,7 @@ impl BeefyTestNet { block }); - runtime.block_on(self.wait_until_sync()); + self.run_until_sync().await; } } @@ -404,17 +391,16 @@ where tasks.for_each(|_| async move {}) } -fn block_until(future: impl Future + Unpin, net: &Arc>, runtime: &mut Runtime) { +async fn run_until(future: impl Future + Unpin, net: &Arc>) { let drive_to_completion = futures::future::poll_fn(|cx| { net.lock().poll(cx); Poll::<()>::Pending }); - runtime.block_on(future::select(future, drive_to_completion)); + let _ = future::select(future, drive_to_completion).await; } -fn run_for(duration: Duration, net: &Arc>, runtime: &mut Runtime) { - let sleep = runtime.spawn(async move { tokio::time::sleep(duration).await }); - block_until(sleep, net, runtime); +async fn run_for(duration: Duration, net: &Arc>) { + run_until(Box::pin(tokio::time::sleep(duration)), net).await; } pub(crate) fn get_beefy_streams( @@ -435,10 +421,9 @@ pub(crate) fn get_beefy_streams( (best_block_streams, versioned_finality_proof_streams) } -fn wait_for_best_beefy_blocks( +async fn wait_for_best_beefy_blocks( streams: Vec>, net: &Arc>, - runtime: &mut Runtime, expected_beefy_blocks: &[u64], ) { let mut wait_for = Vec::new(); @@ -458,13 +443,12 @@ fn wait_for_best_beefy_blocks( }))); }); let wait_for = futures::future::join_all(wait_for); - block_until(wait_for, net, runtime); + run_until(wait_for, net).await; } -fn wait_for_beefy_signed_commitments( +async fn wait_for_beefy_signed_commitments( streams: Vec>>, net: &Arc>, - runtime: &mut Runtime, expected_commitment_block_nums: &[u64], ) { let mut wait_for = Vec::new(); @@ -484,34 +468,33 @@ fn wait_for_beefy_signed_commitments( }))); }); let wait_for = futures::future::join_all(wait_for); - block_until(wait_for, net, runtime); + run_until(wait_for, net).await; } -fn streams_empty_after_timeout( +async fn streams_empty_after_timeout( streams: Vec>, net: &Arc>, - runtime: &mut Runtime, timeout: Option, ) where T: std::fmt::Debug, T: std::cmp::PartialEq, { if let Some(timeout) = timeout { - run_for(timeout, net, runtime); + run_for(timeout, net).await; } - streams.into_iter().for_each(|mut stream| { - runtime.block_on(future::poll_fn(move |cx| { + for mut stream in streams.into_iter() { + future::poll_fn(move |cx| { assert_eq!(stream.poll_next_unpin(cx), Poll::Pending); Poll::Ready(()) - })); - }); + }) + .await; + } } -fn finalize_block_and_wait_for_beefy( +async fn finalize_block_and_wait_for_beefy( net: &Arc>, // peer index and key peers: impl Iterator + Clone, - runtime: &mut Runtime, finalize_targets: &[u64], expected_beefy: &[u64], ) { @@ -528,33 +511,32 @@ fn finalize_block_and_wait_for_beefy( if expected_beefy.is_empty() { // run for quarter second then verify no new best beefy block available let timeout = Some(Duration::from_millis(250)); - streams_empty_after_timeout(best_blocks, &net, runtime, timeout); - streams_empty_after_timeout(versioned_finality_proof, &net, runtime, None); + streams_empty_after_timeout(best_blocks, &net, timeout).await; + streams_empty_after_timeout(versioned_finality_proof, &net, None).await; } else { // run until expected beefy blocks are received - wait_for_best_beefy_blocks(best_blocks, &net, runtime, expected_beefy); - wait_for_beefy_signed_commitments(versioned_finality_proof, &net, runtime, expected_beefy); + wait_for_best_beefy_blocks(best_blocks, &net, expected_beefy).await; + wait_for_beefy_signed_commitments(versioned_finality_proof, &net, expected_beefy).await; } } -#[test] -fn beefy_finalizing_blocks() { +#[tokio::test] +async fn beefy_finalizing_blocks() { sp_tracing::try_init_simple(); - let mut runtime = Runtime::new().unwrap(); let peers = [BeefyKeyring::Alice, BeefyKeyring::Bob]; let validator_set = ValidatorSet::new(make_beefy_ids(&peers), 0).unwrap(); let session_len = 10; let min_block_delta = 4; - let mut net = BeefyTestNet::new(runtime.handle().clone(), 2); + let mut net = BeefyTestNet::new(2); let api = Arc::new(two_validators::TestApi {}); let beefy_peers = peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect(); - runtime.spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta)); + tokio::spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta)); // push 42 blocks including `AuthorityChange` digests every 10 blocks. - net.generate_blocks_and_sync(42, session_len, &validator_set, true, &mut runtime); + net.generate_blocks_and_sync(42, session_len, &validator_set, true).await; let net = Arc::new(Mutex::new(net)); @@ -562,51 +544,44 @@ fn beefy_finalizing_blocks() { let peers = peers.into_iter().enumerate(); // finalize block #5 -> BEEFY should finalize #1 (mandatory) and #5 from diff-power-of-two rule. - finalize_block_and_wait_for_beefy(&net, peers.clone(), &mut runtime, &[1, 5], &[1, 5]); + finalize_block_and_wait_for_beefy(&net, peers.clone(), &[1, 5], &[1, 5]).await; // GRANDPA finalize #10 -> BEEFY finalize #10 (mandatory) - finalize_block_and_wait_for_beefy(&net, peers.clone(), &mut runtime, &[10], &[10]); + finalize_block_and_wait_for_beefy(&net, peers.clone(), &[10], &[10]).await; // GRANDPA finalize #18 -> BEEFY finalize #14, then #18 (diff-power-of-two rule) - finalize_block_and_wait_for_beefy(&net, peers.clone(), &mut runtime, &[18], &[14, 18]); + finalize_block_and_wait_for_beefy(&net, peers.clone(), &[18], &[14, 18]).await; // GRANDPA finalize #20 -> BEEFY finalize #20 (mandatory) - finalize_block_and_wait_for_beefy(&net, peers.clone(), &mut runtime, &[20], &[20]); + finalize_block_and_wait_for_beefy(&net, peers.clone(), &[20], &[20]).await; // GRANDPA finalize #21 -> BEEFY finalize nothing (yet) because min delta is 4 - finalize_block_and_wait_for_beefy(&net, peers, &mut runtime, &[21], &[]); + finalize_block_and_wait_for_beefy(&net, peers, &[21], &[]).await; } -#[test] -fn lagging_validators() { +#[tokio::test] +async fn lagging_validators() { sp_tracing::try_init_simple(); - let mut runtime = Runtime::new().unwrap(); let peers = [BeefyKeyring::Alice, BeefyKeyring::Bob]; let validator_set = ValidatorSet::new(make_beefy_ids(&peers), 0).unwrap(); let session_len = 30; let min_block_delta = 1; - let mut net = BeefyTestNet::new(runtime.handle().clone(), 2); + let mut net = BeefyTestNet::new(2); let api = Arc::new(two_validators::TestApi {}); let beefy_peers = peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect(); - runtime.spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta)); + tokio::spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta)); // push 62 blocks including `AuthorityChange` digests every 30 blocks. - net.generate_blocks_and_sync(62, session_len, &validator_set, true, &mut runtime); + net.generate_blocks_and_sync(62, session_len, &validator_set, true).await; let net = Arc::new(Mutex::new(net)); let peers = peers.into_iter().enumerate(); // finalize block #15 -> BEEFY should finalize #1 (mandatory) and #9, #13, #14, #15 from // diff-power-of-two rule. - finalize_block_and_wait_for_beefy( - &net, - peers.clone(), - &mut runtime, - &[1, 15], - &[1, 9, 13, 14, 15], - ); + finalize_block_and_wait_for_beefy(&net, peers.clone(), &[1, 15], &[1, 9, 13, 14, 15]).await; // Alice finalizes #25, Bob lags behind let finalize = net @@ -620,18 +595,18 @@ fn lagging_validators() { net.lock().peer(0).client().as_client().finalize_block(finalize, None).unwrap(); // verify nothing gets finalized by BEEFY let timeout = Some(Duration::from_millis(250)); - streams_empty_after_timeout(best_blocks, &net, &mut runtime, timeout); - streams_empty_after_timeout(versioned_finality_proof, &net, &mut runtime, None); + streams_empty_after_timeout(best_blocks, &net, timeout).await; + streams_empty_after_timeout(versioned_finality_proof, &net, None).await; // Bob catches up and also finalizes #25 let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers.clone()); net.lock().peer(1).client().as_client().finalize_block(finalize, None).unwrap(); // expected beefy finalizes block #17 from diff-power-of-two - wait_for_best_beefy_blocks(best_blocks, &net, &mut runtime, &[23, 24, 25]); - wait_for_beefy_signed_commitments(versioned_finality_proof, &net, &mut runtime, &[23, 24, 25]); + wait_for_best_beefy_blocks(best_blocks, &net, &[23, 24, 25]).await; + wait_for_beefy_signed_commitments(versioned_finality_proof, &net, &[23, 24, 25]).await; // Both finalize #30 (mandatory session) and #32 -> BEEFY finalize #30 (mandatory), #31, #32 - finalize_block_and_wait_for_beefy(&net, peers.clone(), &mut runtime, &[30, 32], &[30, 31, 32]); + finalize_block_and_wait_for_beefy(&net, peers.clone(), &[30, 32], &[30, 31, 32]).await; // Verify that session-boundary votes get buffered by client and only processed once // session-boundary block is GRANDPA-finalized (this guarantees authenticity for the new session @@ -649,28 +624,27 @@ fn lagging_validators() { net.lock().peer(0).client().as_client().finalize_block(finalize, None).unwrap(); // verify nothing gets finalized by BEEFY let timeout = Some(Duration::from_millis(250)); - streams_empty_after_timeout(best_blocks, &net, &mut runtime, timeout); - streams_empty_after_timeout(versioned_finality_proof, &net, &mut runtime, None); + streams_empty_after_timeout(best_blocks, &net, timeout).await; + streams_empty_after_timeout(versioned_finality_proof, &net, None).await; // Bob catches up and also finalizes #60 (and should have buffered Alice's vote on #60) let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers); net.lock().peer(1).client().as_client().finalize_block(finalize, None).unwrap(); // verify beefy skips intermediary votes, and successfully finalizes mandatory block #60 - wait_for_best_beefy_blocks(best_blocks, &net, &mut runtime, &[60]); - wait_for_beefy_signed_commitments(versioned_finality_proof, &net, &mut runtime, &[60]); + wait_for_best_beefy_blocks(best_blocks, &net, &[60]).await; + wait_for_beefy_signed_commitments(versioned_finality_proof, &net, &[60]).await; } -#[test] -fn correct_beefy_payload() { +#[tokio::test] +async fn correct_beefy_payload() { sp_tracing::try_init_simple(); - let mut runtime = Runtime::new().unwrap(); let peers = [BeefyKeyring::Alice, BeefyKeyring::Bob, BeefyKeyring::Charlie, BeefyKeyring::Dave]; let validator_set = ValidatorSet::new(make_beefy_ids(&peers), 0).unwrap(); let session_len = 20; let min_block_delta = 2; - let mut net = BeefyTestNet::new(runtime.handle().clone(), 4); + let mut net = BeefyTestNet::new(4); // Alice, Bob, Charlie will vote on good payloads let good_api = Arc::new(four_validators::TestApi {}); @@ -679,20 +653,20 @@ fn correct_beefy_payload() { .enumerate() .map(|(id, key)| (id, key, good_api.clone())) .collect(); - runtime.spawn(initialize_beefy(&mut net, good_peers, min_block_delta)); + tokio::spawn(initialize_beefy(&mut net, good_peers, min_block_delta)); // Dave will vote on bad mmr roots let bad_api = Arc::new(bad_four_validators::TestApi {}); let bad_peers = vec![(3, &BeefyKeyring::Dave, bad_api)]; - runtime.spawn(initialize_beefy(&mut net, bad_peers, min_block_delta)); + tokio::spawn(initialize_beefy(&mut net, bad_peers, min_block_delta)); // push 12 blocks - net.generate_blocks_and_sync(12, session_len, &validator_set, false, &mut runtime); + net.generate_blocks_and_sync(12, session_len, &validator_set, false).await; let net = Arc::new(Mutex::new(net)); let peers = peers.into_iter().enumerate(); // with 3 good voters and 1 bad one, consensus should happen and best blocks produced. - finalize_block_and_wait_for_beefy(&net, peers, &mut runtime, &[1, 10], &[1, 9]); + finalize_block_and_wait_for_beefy(&net, peers, &[1, 10], &[1, 9]).await; let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), [(0, BeefyKeyring::Alice)].into_iter()); @@ -711,8 +685,8 @@ fn correct_beefy_payload() { // verify consensus is _not_ reached let timeout = Some(Duration::from_millis(250)); - streams_empty_after_timeout(best_blocks, &net, &mut runtime, timeout); - streams_empty_after_timeout(versioned_finality_proof, &net, &mut runtime, None); + streams_empty_after_timeout(best_blocks, &net, timeout).await; + streams_empty_after_timeout(versioned_finality_proof, &net, None).await; // 3rd good validator catches up and votes as well let (best_blocks, versioned_finality_proof) = @@ -720,21 +694,19 @@ fn correct_beefy_payload() { net.lock().peer(2).client().as_client().finalize_block(hashof11, None).unwrap(); // verify consensus is reached - wait_for_best_beefy_blocks(best_blocks, &net, &mut runtime, &[11]); - wait_for_beefy_signed_commitments(versioned_finality_proof, &net, &mut runtime, &[11]); + wait_for_best_beefy_blocks(best_blocks, &net, &[11]).await; + wait_for_beefy_signed_commitments(versioned_finality_proof, &net, &[11]).await; } -#[test] -fn beefy_importing_blocks() { +#[tokio::test] +async fn beefy_importing_blocks() { use futures::{future::poll_fn, task::Poll}; use sc_block_builder::BlockBuilderProvider; use sc_client_api::BlockBackend; sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - - let mut net = BeefyTestNet::new(runtime.handle().clone(), 2); + let mut net = BeefyTestNet::new(2); let client = net.peer(0).client().clone(); let (mut block_import, _, peer_data) = net.make_block_import(client.clone()); @@ -759,16 +731,15 @@ fn beefy_importing_blocks() { // Import without justifications. let mut justif_recv = justif_stream.subscribe(); assert_eq!( - runtime - .block_on(block_import.import_block(params(block.clone(), None), HashMap::new())) + block_import + .import_block(params(block.clone(), None), HashMap::new()) + .await .unwrap(), ImportResult::Imported(ImportedAux { is_new_best: true, ..Default::default() }), ); assert_eq!( - runtime - .block_on(block_import.import_block(params(block, None), HashMap::new())) - .unwrap(), - ImportResult::AlreadyInChain + block_import.import_block(params(block, None), HashMap::new()).await.unwrap(), + ImportResult::AlreadyInChain, ); // Verify no BEEFY justifications present: { @@ -781,10 +752,11 @@ fn beefy_importing_blocks() { None ); // and none sent to BEEFY worker. - runtime.block_on(poll_fn(move |cx| { + poll_fn(move |cx| { assert_eq!(justif_recv.poll_next_unpin(cx), Poll::Pending); Poll::Ready(()) - })); + }) + .await; } // Import with valid justification. @@ -802,9 +774,7 @@ fn beefy_importing_blocks() { let hashof2 = block.header.hash(); let mut justif_recv = justif_stream.subscribe(); assert_eq!( - runtime - .block_on(block_import.import_block(params(block, justif), HashMap::new())) - .unwrap(), + block_import.import_block(params(block, justif), HashMap::new()).await.unwrap(), ImportResult::Imported(ImportedAux { bad_justification: false, is_new_best: true, @@ -823,13 +793,14 @@ fn beefy_importing_blocks() { ); // but sent to BEEFY worker // (worker will append it to backend when all previous mandatory justifs are there as well). - runtime.block_on(poll_fn(move |cx| { + poll_fn(move |cx| { match justif_recv.poll_next_unpin(cx) { Poll::Ready(Some(_justification)) => (), v => panic!("unexpected value: {:?}", v), } Poll::Ready(()) - })); + }) + .await; } // Import with invalid justification (incorrect validator set). @@ -847,9 +818,7 @@ fn beefy_importing_blocks() { let hashof3 = block.header.hash(); let mut justif_recv = justif_stream.subscribe(); assert_eq!( - runtime - .block_on(block_import.import_block(params(block, justif), HashMap::new())) - .unwrap(), + block_import.import_block(params(block, justif), HashMap::new()).await.unwrap(), ImportResult::Imported(ImportedAux { // Still `false` because we don't want to fail import on bad BEEFY justifications. bad_justification: false, @@ -868,33 +837,33 @@ fn beefy_importing_blocks() { None ); // and none sent to BEEFY worker. - runtime.block_on(poll_fn(move |cx| { + poll_fn(move |cx| { assert_eq!(justif_recv.poll_next_unpin(cx), Poll::Pending); Poll::Ready(()) - })); + }) + .await; } } -#[test] -fn voter_initialization() { +#[tokio::test] +async fn voter_initialization() { sp_tracing::try_init_simple(); // Regression test for voter initialization where finality notifications were dropped // after waiting for BEEFY pallet availability. - let mut runtime = Runtime::new().unwrap(); let peers = [BeefyKeyring::Alice, BeefyKeyring::Bob]; let validator_set = ValidatorSet::new(make_beefy_ids(&peers), 0).unwrap(); let session_len = 5; // Should vote on all mandatory blocks no matter the `min_block_delta`. let min_block_delta = 10; - let mut net = BeefyTestNet::new(runtime.handle().clone(), 2); + let mut net = BeefyTestNet::new(2); let api = Arc::new(two_validators::TestApi {}); let beefy_peers = peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect(); - runtime.spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta)); + tokio::spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta)); // push 26 blocks - net.generate_blocks_and_sync(26, session_len, &validator_set, false, &mut runtime); + net.generate_blocks_and_sync(26, session_len, &validator_set, false).await; let net = Arc::new(Mutex::new(net)); // Finalize multiple blocks at once to get a burst of finality notifications right from start. @@ -903,31 +872,30 @@ fn voter_initialization() { finalize_block_and_wait_for_beefy( &net, peers.into_iter().enumerate(), - &mut runtime, &[1, 6, 10, 17, 24, 26], &[1, 5, 10, 15, 20, 25], - ); + ) + .await; } -#[test] -fn on_demand_beefy_justification_sync() { +#[tokio::test] +async fn on_demand_beefy_justification_sync() { sp_tracing::try_init_simple(); - let mut runtime = Runtime::new().unwrap(); let all_peers = [BeefyKeyring::Alice, BeefyKeyring::Bob, BeefyKeyring::Charlie, BeefyKeyring::Dave]; let validator_set = ValidatorSet::new(make_beefy_ids(&all_peers), 0).unwrap(); let session_len = 5; let min_block_delta = 5; - let mut net = BeefyTestNet::new(runtime.handle().clone(), 4); + let mut net = BeefyTestNet::new(4); // Alice, Bob, Charlie start first and make progress through voting. let api = Arc::new(four_validators::TestApi {}); let fast_peers = [BeefyKeyring::Alice, BeefyKeyring::Bob, BeefyKeyring::Charlie]; let voting_peers = fast_peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect(); - runtime.spawn(initialize_beefy(&mut net, voting_peers, min_block_delta)); + tokio::spawn(initialize_beefy(&mut net, voting_peers, min_block_delta)); // Dave will start late and have to catch up using on-demand justification requests (since // in this test there is no block import queue to automatically import justifications). @@ -937,7 +905,7 @@ fn on_demand_beefy_justification_sync() { let dave_index = 3; // push 30 blocks - net.generate_blocks_and_sync(30, session_len, &validator_set, false, &mut runtime); + net.generate_blocks_and_sync(30, session_len, &validator_set, false).await; let fast_peers = fast_peers.into_iter().enumerate(); let net = Arc::new(Mutex::new(net)); @@ -946,15 +914,15 @@ fn on_demand_beefy_justification_sync() { finalize_block_and_wait_for_beefy( &net, fast_peers.clone(), - &mut runtime, &[1, 6, 10, 17, 24], &[1, 5, 10, 15, 20], - ); + ) + .await; // Spawn Dave, he's now way behind voting and can only catch up through on-demand justif sync. - runtime.spawn(dave_task); + tokio::spawn(dave_task); // give Dave a chance to spawn and init. - run_for(Duration::from_millis(400), &net, &mut runtime); + run_for(Duration::from_millis(400), &net).await; let (dave_best_blocks, _) = get_beefy_streams(&mut net.lock(), [(dave_index, BeefyKeyring::Dave)].into_iter()); @@ -962,41 +930,40 @@ fn on_demand_beefy_justification_sync() { let hashof1 = client.expect_block_hash_from_id(&BlockId::number(1)).unwrap(); client.finalize_block(hashof1, None).unwrap(); // Give Dave task some cpu cycles to process the finality notification, - run_for(Duration::from_millis(100), &net, &mut runtime); + run_for(Duration::from_millis(100), &net).await; // freshly spun up Dave now needs to listen for gossip to figure out the state of his peers. // Have the other peers do some gossip so Dave finds out about their progress. - finalize_block_and_wait_for_beefy(&net, fast_peers, &mut runtime, &[25], &[25]); + finalize_block_and_wait_for_beefy(&net, fast_peers, &[25], &[25]).await; // Now verify Dave successfully finalized #1 (through on-demand justification request). - wait_for_best_beefy_blocks(dave_best_blocks, &net, &mut runtime, &[1]); + wait_for_best_beefy_blocks(dave_best_blocks, &net, &[1]).await; // Give Dave all tasks some cpu cycles to burn through their events queues, - run_for(Duration::from_millis(100), &net, &mut runtime); + run_for(Duration::from_millis(100), &net).await; // then verify Dave catches up through on-demand justification requests. finalize_block_and_wait_for_beefy( &net, [(dave_index, BeefyKeyring::Dave)].into_iter(), - &mut runtime, &[6, 10, 17, 24, 26], &[5, 10, 15, 20, 25], - ); + ) + .await; let all_peers = all_peers.into_iter().enumerate(); // Now that Dave has caught up, sanity check voting works for all of them. - finalize_block_and_wait_for_beefy(&net, all_peers, &mut runtime, &[30], &[30]); + finalize_block_and_wait_for_beefy(&net, all_peers, &[30], &[30]).await; } -#[test] -fn should_initialize_voter_at_genesis() { +#[tokio::test] +async fn should_initialize_voter_at_genesis() { let keys = &[BeefyKeyring::Alice]; let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); - let mut runtime = Runtime::new().unwrap(); - let mut net = BeefyTestNet::new(runtime.handle().clone(), 1); + let mut net = BeefyTestNet::new(1); let backend = net.peer(0).client().as_backend(); // push 15 blocks with `AuthorityChange` digests every 10 blocks - net.generate_blocks_and_sync(15, 10, &validator_set, false, &mut runtime); + net.generate_blocks_and_sync(15, 10, &validator_set, false).await; let mut finality = net.peer(0).client().as_client().finality_notification_stream().fuse(); @@ -1033,16 +1000,15 @@ fn should_initialize_voter_at_genesis() { assert_eq!(state, persisted_state); } -#[test] -fn should_initialize_voter_when_last_final_is_session_boundary() { +#[tokio::test] +async fn should_initialize_voter_when_last_final_is_session_boundary() { let keys = &[BeefyKeyring::Alice]; let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); - let mut runtime = Runtime::new().unwrap(); - let mut net = BeefyTestNet::new(runtime.handle().clone(), 1); + let mut net = BeefyTestNet::new(1); let backend = net.peer(0).client().as_backend(); // push 15 blocks with `AuthorityChange` digests every 10 blocks - net.generate_blocks_and_sync(15, 10, &validator_set, false, &mut runtime); + net.generate_blocks_and_sync(15, 10, &validator_set, false).await; let mut finality = net.peer(0).client().as_client().finality_notification_stream().fuse(); @@ -1094,16 +1060,15 @@ fn should_initialize_voter_when_last_final_is_session_boundary() { assert_eq!(state, persisted_state); } -#[test] -fn should_initialize_voter_at_latest_finalized() { +#[tokio::test] +async fn should_initialize_voter_at_latest_finalized() { let keys = &[BeefyKeyring::Alice]; let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); - let mut runtime = Runtime::new().unwrap(); - let mut net = BeefyTestNet::new(runtime.handle().clone(), 1); + let mut net = BeefyTestNet::new(1); let backend = net.peer(0).client().as_backend(); // push 15 blocks with `AuthorityChange` digests every 10 blocks - net.generate_blocks_and_sync(15, 10, &validator_set, false, &mut runtime); + net.generate_blocks_and_sync(15, 10, &validator_set, false).await; let mut finality = net.peer(0).client().as_client().finality_notification_stream().fuse(); diff --git a/client/beefy/src/worker.rs b/client/beefy/src/worker.rs index bba3563a8f..df465b2dbf 100644 --- a/client/beefy/src/worker.rs +++ b/client/beefy/src/worker.rs @@ -983,7 +983,6 @@ pub(crate) mod tests { runtime::{Block, Digest, DigestItem, Header, H256}, Backend, }; - use tokio::runtime::Runtime; impl PersistedState { pub fn voting_oracle(&self) -> &VoterOracle { @@ -1295,12 +1294,11 @@ pub(crate) mod tests { assert_eq!(extracted, Some(validator_set)); } - #[test] - fn keystore_vs_validator_set() { + #[tokio::test] + async fn keystore_vs_validator_set() { let keys = &[Keyring::Alice]; let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); - let runtime = Runtime::new().unwrap(); - let mut net = BeefyTestNet::new(runtime.handle().clone(), 1); + let mut net = BeefyTestNet::new(1); let mut worker = create_beefy_worker(&net.peer(0), &keys[0], 1, validator_set.clone()); // keystore doesn't contain other keys than validators' @@ -1319,12 +1317,11 @@ pub(crate) mod tests { assert_eq!(worker.verify_validator_set(&1, &validator_set), expected_err); } - #[test] - fn should_finalize_correctly() { + #[tokio::test] + async fn should_finalize_correctly() { let keys = [Keyring::Alice]; let validator_set = ValidatorSet::new(make_beefy_ids(&keys), 0).unwrap(); - let runtime = Runtime::new().unwrap(); - let mut net = BeefyTestNet::new(runtime.handle().clone(), 1); + let mut net = BeefyTestNet::new(1); let backend = net.peer(0).client().as_backend(); let mut worker = create_beefy_worker(&net.peer(0), &keys[0], 1, validator_set.clone()); // remove default session, will manually add custom one. @@ -1347,11 +1344,12 @@ pub(crate) mod tests { // no 'best beefy block' or finality proofs assert_eq!(worker.best_beefy_block(), 0); - runtime.block_on(poll_fn(move |cx| { + poll_fn(move |cx| { assert_eq!(best_block_stream.poll_next_unpin(cx), Poll::Pending); assert_eq!(finality_proof.poll_next_unpin(cx), Poll::Pending); Poll::Ready(()) - })); + }) + .await; // unknown hash for block #1 let (mut best_block_streams, mut finality_proofs) = @@ -1368,7 +1366,7 @@ pub(crate) mod tests { worker.finalize(justif.clone()).unwrap(); // verify block finalized assert_eq!(worker.best_beefy_block(), 1); - runtime.block_on(poll_fn(move |cx| { + poll_fn(move |cx| { // unknown hash -> nothing streamed assert_eq!(best_block_stream.poll_next_unpin(cx), Poll::Pending); // commitment streamed @@ -1378,7 +1376,8 @@ pub(crate) mod tests { v => panic!("unexpected value: {:?}", v), } Poll::Ready(()) - })); + }) + .await; // generate 2 blocks, try again expect success let (mut best_block_streams, _) = get_beefy_streams(&mut net, keys); @@ -1400,7 +1399,7 @@ pub(crate) mod tests { assert_eq!(worker.active_rounds().unwrap().session_start(), 2); // verify block finalized assert_eq!(worker.best_beefy_block(), 2); - runtime.block_on(poll_fn(move |cx| { + poll_fn(move |cx| { match best_block_stream.poll_next_unpin(cx) { // expect Some(hash-of-block-2) Poll::Ready(Some(hash)) => { @@ -1410,19 +1409,19 @@ pub(crate) mod tests { v => panic!("unexpected value: {:?}", v), } Poll::Ready(()) - })); + }) + .await; // check BEEFY justifications are also appended to backend let justifs = backend.blockchain().justifications(hashof2).unwrap().unwrap(); assert!(justifs.get(BEEFY_ENGINE_ID).is_some()) } - #[test] - fn should_init_session() { + #[tokio::test] + async fn should_init_session() { let keys = &[Keyring::Alice, Keyring::Bob]; let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); - let runtime = Runtime::new().unwrap(); - let mut net = BeefyTestNet::new(runtime.handle().clone(), 1); + let mut net = BeefyTestNet::new(1); let mut worker = create_beefy_worker(&net.peer(0), &keys[0], 1, validator_set.clone()); let worker_rounds = worker.active_rounds().unwrap(); @@ -1449,12 +1448,11 @@ pub(crate) mod tests { assert_eq!(rounds.validator_set_id(), new_validator_set.id()); } - #[test] - fn should_triage_votes_and_process_later() { + #[tokio::test] + async fn should_triage_votes_and_process_later() { let keys = &[Keyring::Alice, Keyring::Bob]; let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); - let runtime = Runtime::new().unwrap(); - let mut net = BeefyTestNet::new(runtime.handle().clone(), 1); + let mut net = BeefyTestNet::new(1); let mut worker = create_beefy_worker(&net.peer(0), &keys[0], 1, validator_set.clone()); // remove default session, will manually add custom one. worker.persisted_state.voting_oracle.sessions.clear(); diff --git a/client/consensus/aura/src/lib.rs b/client/consensus/aura/src/lib.rs index a8ed80d7c0..212376dcd0 100644 --- a/client/consensus/aura/src/lib.rs +++ b/client/consensus/aura/src/lib.rs @@ -660,7 +660,6 @@ mod tests { runtime::{Header, H256}, TestClient, }; - use tokio::runtime::{Handle, Runtime}; const SLOT_DURATION_MS: u64 = 1000; @@ -718,20 +717,11 @@ mod tests { >; type AuraPeer = Peer<(), PeersClient>; + #[derive(Default)] pub struct AuraTestNet { - rt_handle: Handle, peers: Vec, } - impl WithRuntime for AuraTestNet { - fn with_runtime(rt_handle: Handle) -> Self { - AuraTestNet { rt_handle, peers: Vec::new() } - } - fn rt_handle(&self) -> &Handle { - &self.rt_handle - } - } - impl TestNetFactory for AuraTestNet { type Verifier = AuraVerifier; type PeerData = (); @@ -780,11 +770,10 @@ mod tests { } } - #[test] - fn authoring_blocks() { + #[tokio::test] + async fn authoring_blocks() { sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - let net = AuraTestNet::new(runtime.handle().clone(), 3); + let net = AuraTestNet::new(3); let peers = &[(0, Keyring::Alice), (1, Keyring::Bob), (2, Keyring::Charlie)]; @@ -850,13 +839,14 @@ mod tests { ); } - runtime.block_on(future::select( + future::select( future::poll_fn(move |cx| { net.lock().poll(cx); Poll::<()>::Pending }), future::select(future::join_all(aura_futures), future::join_all(import_notifications)), - )); + ) + .await; } #[test] @@ -875,10 +865,9 @@ mod tests { ); } - #[test] - fn current_node_authority_should_claim_slot() { - let runtime = Runtime::new().unwrap(); - let net = AuraTestNet::new(runtime.handle().clone(), 4); + #[tokio::test] + async fn current_node_authority_should_claim_slot() { + let net = AuraTestNet::new(4); let mut authorities = vec![ Keyring::Alice.public().into(), @@ -922,20 +911,19 @@ mod tests { Default::default(), Default::default(), ); - assert!(runtime.block_on(worker.claim_slot(&head, 0.into(), &authorities)).is_none()); - assert!(runtime.block_on(worker.claim_slot(&head, 1.into(), &authorities)).is_none()); - assert!(runtime.block_on(worker.claim_slot(&head, 2.into(), &authorities)).is_none()); - assert!(runtime.block_on(worker.claim_slot(&head, 3.into(), &authorities)).is_some()); - assert!(runtime.block_on(worker.claim_slot(&head, 4.into(), &authorities)).is_none()); - assert!(runtime.block_on(worker.claim_slot(&head, 5.into(), &authorities)).is_none()); - assert!(runtime.block_on(worker.claim_slot(&head, 6.into(), &authorities)).is_none()); - assert!(runtime.block_on(worker.claim_slot(&head, 7.into(), &authorities)).is_some()); + assert!(worker.claim_slot(&head, 0.into(), &authorities).await.is_none()); + assert!(worker.claim_slot(&head, 1.into(), &authorities).await.is_none()); + assert!(worker.claim_slot(&head, 2.into(), &authorities).await.is_none()); + assert!(worker.claim_slot(&head, 3.into(), &authorities).await.is_some()); + assert!(worker.claim_slot(&head, 4.into(), &authorities).await.is_none()); + assert!(worker.claim_slot(&head, 5.into(), &authorities).await.is_none()); + assert!(worker.claim_slot(&head, 6.into(), &authorities).await.is_none()); + assert!(worker.claim_slot(&head, 7.into(), &authorities).await.is_some()); } - #[test] - fn on_slot_returns_correct_block() { - let runtime = Runtime::new().unwrap(); - let net = AuraTestNet::new(runtime.handle().clone(), 4); + #[tokio::test] + async fn on_slot_returns_correct_block() { + let net = AuraTestNet::new(4); let keystore_path = tempfile::tempdir().expect("Creates keystore path"); let keystore = LocalKeystore::open(keystore_path.path(), None).expect("Creates keystore."); @@ -971,15 +959,16 @@ mod tests { let head = client.header(&BlockId::Number(0)).unwrap().unwrap(); - let res = runtime - .block_on(worker.on_slot(SlotInfo { + let res = worker + .on_slot(SlotInfo { slot: 0.into(), ends_at: Instant::now() + Duration::from_secs(100), create_inherent_data: Box::new(()), duration: Duration::from_millis(1000), chain_head: head, block_size_limit: None, - })) + }) + .await .unwrap(); // The returned block should be imported and we should be able to get its header by now. diff --git a/client/consensus/babe/src/tests.rs b/client/consensus/babe/src/tests.rs index d7691235a5..6861379ebb 100644 --- a/client/consensus/babe/src/tests.rs +++ b/client/consensus/babe/src/tests.rs @@ -49,7 +49,6 @@ use sp_runtime::{ }; use sp_timestamp::Timestamp; use std::{cell::RefCell, task::Poll, time::Duration}; -use tokio::runtime::{Handle, Runtime}; type Item = DigestItem; @@ -227,20 +226,11 @@ where type BabePeer = Peer, BabeBlockImport>; +#[derive(Default)] pub struct BabeTestNet { - rt_handle: Handle, peers: Vec, } -impl WithRuntime for BabeTestNet { - fn with_runtime(rt_handle: Handle) -> Self { - BabeTestNet { rt_handle, peers: Vec::new() } - } - fn rt_handle(&self) -> &Handle { - &self.rt_handle - } -} - type TestHeader = ::Header; type TestSelectChain = @@ -366,12 +356,11 @@ impl TestNetFactory for BabeTestNet { } } -#[test] +#[tokio::test] #[should_panic] -fn rejects_empty_block() { +async fn rejects_empty_block() { sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - let mut net = BabeTestNet::new(runtime.handle().clone(), 3); + let mut net = BabeTestNet::new(3); let block_builder = |builder: BlockBuilder<_, _, _>| builder.build().unwrap().block; net.mut_peers(|peer| { peer[0].generate_blocks(1, BlockOrigin::NetworkInitialSync, block_builder); @@ -385,14 +374,13 @@ fn create_keystore(authority: Sr25519Keyring) -> SyncCryptoStorePtr { keystore } -fn run_one_test(mutator: impl Fn(&mut TestHeader, Stage) + Send + Sync + 'static) { +async fn run_one_test(mutator: impl Fn(&mut TestHeader, Stage) + Send + Sync + 'static) { sp_tracing::try_init_simple(); let mutator = Arc::new(mutator) as Mutator; MUTATOR.with(|m| *m.borrow_mut() = mutator.clone()); - let runtime = Runtime::new().unwrap(); - let net = BabeTestNet::new(runtime.handle().clone(), 3); + let net = BabeTestNet::new(3); let peers = [Sr25519Keyring::Alice, Sr25519Keyring::Bob, Sr25519Keyring::Charlie]; @@ -469,7 +457,7 @@ fn run_one_test(mutator: impl Fn(&mut TestHeader, Stage) + Send + Sync + 'static .expect("Starts babe"), ); } - runtime.block_on(future::select( + future::select( futures::future::poll_fn(move |cx| { let mut net = net.lock(); net.poll(cx); @@ -482,17 +470,18 @@ fn run_one_test(mutator: impl Fn(&mut TestHeader, Stage) + Send + Sync + 'static Poll::<()>::Pending }), future::select(future::join_all(import_notifications), future::join_all(babe_futures)), - )); + ) + .await; } -#[test] -fn authoring_blocks() { - run_one_test(|_, _| ()) +#[tokio::test] +async fn authoring_blocks() { + run_one_test(|_, _| ()).await; } -#[test] +#[tokio::test] #[should_panic] -fn rejects_missing_inherent_digest() { +async fn rejects_missing_inherent_digest() { run_one_test(|header: &mut TestHeader, stage| { let v = std::mem::take(&mut header.digest_mut().logs); header.digest_mut().logs = v @@ -500,11 +489,12 @@ fn rejects_missing_inherent_digest() { .filter(|v| stage == Stage::PostSeal || v.as_babe_pre_digest().is_none()) .collect() }) + .await; } -#[test] +#[tokio::test] #[should_panic] -fn rejects_missing_seals() { +async fn rejects_missing_seals() { run_one_test(|header: &mut TestHeader, stage| { let v = std::mem::take(&mut header.digest_mut().logs); header.digest_mut().logs = v @@ -512,18 +502,20 @@ fn rejects_missing_seals() { .filter(|v| stage == Stage::PreSeal || v.as_babe_seal().is_none()) .collect() }) + .await; } -#[test] +#[tokio::test] #[should_panic] -fn rejects_missing_consensus_digests() { +async fn rejects_missing_consensus_digests() { run_one_test(|header: &mut TestHeader, stage| { let v = std::mem::take(&mut header.digest_mut().logs); header.digest_mut().logs = v .into_iter() .filter(|v| stage == Stage::PostSeal || v.as_next_epoch_descriptor().is_none()) .collect() - }); + }) + .await; } #[test] @@ -601,14 +593,13 @@ fn can_author_block() { } // Propose and import a new BABE block on top of the given parent. -fn propose_and_import_block( +async fn propose_and_import_block( parent: &TestHeader, slot: Option, proposer_factory: &mut DummyFactory, block_import: &mut BoxBlockImport, - runtime: &Runtime, ) -> Hash { - let mut proposer = runtime.block_on(proposer_factory.init(parent)).unwrap(); + let mut proposer = proposer_factory.init(parent).await.unwrap(); let slot = slot.unwrap_or_else(|| { let parent_pre_digest = find_pre_digest::(parent).unwrap(); @@ -624,7 +615,7 @@ fn propose_and_import_block( let parent_hash = parent.hash(); - let mut block = runtime.block_on(proposer.propose_with(pre_digest)).unwrap().block; + let mut block = proposer.propose_with(pre_digest).await.unwrap().block; let epoch_descriptor = proposer_factory .epoch_changes @@ -660,8 +651,7 @@ fn propose_and_import_block( import .insert_intermediate(INTERMEDIATE_KEY, BabeIntermediate:: { epoch_descriptor }); import.fork_choice = Some(ForkChoiceStrategy::LongestChain); - let import_result = - runtime.block_on(block_import.import_block(import, Default::default())).unwrap(); + let import_result = block_import.import_block(import, Default::default()).await.unwrap(); match import_result { ImportResult::Imported(_) => {}, @@ -674,20 +664,19 @@ fn propose_and_import_block( // Propose and import n valid BABE blocks that are built on top of the given parent. // The proposer takes care of producing epoch change digests according to the epoch // duration (which is set to 6 slots in the test runtime). -fn propose_and_import_blocks( +async fn propose_and_import_blocks( client: &PeersFullClient, proposer_factory: &mut DummyFactory, block_import: &mut BoxBlockImport, parent_id: BlockId, n: usize, - runtime: &Runtime, ) -> Vec { let mut hashes = Vec::with_capacity(n); let mut parent_header = client.header(&parent_id).unwrap().unwrap(); for _ in 0..n { let block_hash = - propose_and_import_block(&parent_header, None, proposer_factory, block_import, runtime); + propose_and_import_block(&parent_header, None, proposer_factory, block_import).await; hashes.push(block_hash); parent_header = client.header(&BlockId::Hash(block_hash)).unwrap().unwrap(); } @@ -695,10 +684,9 @@ fn propose_and_import_blocks( hashes } -#[test] -fn importing_block_one_sets_genesis_epoch() { - let runtime = Runtime::new().unwrap(); - let mut net = BabeTestNet::new(runtime.handle().clone(), 1); +#[tokio::test] +async fn importing_block_one_sets_genesis_epoch() { + let mut net = BabeTestNet::new(1); let peer = net.peer(0); let data = peer.data.as_ref().expect("babe link set up during initialization"); @@ -720,8 +708,8 @@ fn importing_block_one_sets_genesis_epoch() { Some(999.into()), &mut proposer_factory, &mut block_import, - &runtime, - ); + ) + .await; let genesis_epoch = Epoch::genesis(&data.link.config, 999.into()); @@ -736,10 +724,9 @@ fn importing_block_one_sets_genesis_epoch() { assert_eq!(epoch_for_second_block, genesis_epoch); } -#[test] -fn revert_prunes_epoch_changes_and_removes_weights() { - let runtime = Runtime::new().unwrap(); - let mut net = BabeTestNet::new(runtime.handle().clone(), 1); +#[tokio::test] +async fn revert_prunes_epoch_changes_and_removes_weights() { + let mut net = BabeTestNet::new(1); let peer = net.peer(0); let data = peer.data.as_ref().expect("babe link set up during initialization"); @@ -756,17 +743,6 @@ fn revert_prunes_epoch_changes_and_removes_weights() { mutator: Arc::new(|_, _| ()), }; - let mut propose_and_import_blocks_wrap = |parent_id, n| { - propose_and_import_blocks( - &client, - &mut proposer_factory, - &mut block_import, - parent_id, - n, - &runtime, - ) - }; - // Test scenario. // Information for epoch 19 is produced on three different forks at block #13. // One branch starts before the revert point (epoch data should be maintained). @@ -779,10 +755,38 @@ fn revert_prunes_epoch_changes_and_removes_weights() { // \ revert *---- G(#13) ---- H(#19) ---#20 < fork #3 // \ to #10 // *-----E(#7)---#11 < fork #1 - let canon = propose_and_import_blocks_wrap(BlockId::Number(0), 21); - let fork1 = propose_and_import_blocks_wrap(BlockId::Hash(canon[0]), 10); - let fork2 = propose_and_import_blocks_wrap(BlockId::Hash(canon[7]), 10); - let fork3 = propose_and_import_blocks_wrap(BlockId::Hash(canon[11]), 8); + let canon = propose_and_import_blocks( + &client, + &mut proposer_factory, + &mut block_import, + BlockId::Number(0), + 21, + ) + .await; + let fork1 = propose_and_import_blocks( + &client, + &mut proposer_factory, + &mut block_import, + BlockId::Hash(canon[0]), + 10, + ) + .await; + let fork2 = propose_and_import_blocks( + &client, + &mut proposer_factory, + &mut block_import, + BlockId::Hash(canon[7]), + 10, + ) + .await; + let fork3 = propose_and_import_blocks( + &client, + &mut proposer_factory, + &mut block_import, + BlockId::Hash(canon[11]), + 8, + ) + .await; // We should be tracking a total of 9 epochs in the fork tree assert_eq!(epoch_changes.shared_data().tree().iter().count(), 8); @@ -824,10 +828,9 @@ fn revert_prunes_epoch_changes_and_removes_weights() { assert!(weight_data_check(&fork3, false)); } -#[test] -fn revert_not_allowed_for_finalized() { - let runtime = Runtime::new().unwrap(); - let mut net = BabeTestNet::new(runtime.handle().clone(), 1); +#[tokio::test] +async fn revert_not_allowed_for_finalized() { + let mut net = BabeTestNet::new(1); let peer = net.peer(0); let data = peer.data.as_ref().expect("babe link set up during initialization"); @@ -843,18 +846,14 @@ fn revert_not_allowed_for_finalized() { mutator: Arc::new(|_, _| ()), }; - let mut propose_and_import_blocks_wrap = |parent_id, n| { - propose_and_import_blocks( - &client, - &mut proposer_factory, - &mut block_import, - parent_id, - n, - &runtime, - ) - }; - - let canon = propose_and_import_blocks_wrap(BlockId::Number(0), 3); + let canon = propose_and_import_blocks( + &client, + &mut proposer_factory, + &mut block_import, + BlockId::Number(0), + 3, + ) + .await; // Finalize best block client.finalize_block(canon[2], None, false).unwrap(); @@ -870,10 +869,9 @@ fn revert_not_allowed_for_finalized() { assert!(weight_data_check(&canon, true)); } -#[test] -fn importing_epoch_change_block_prunes_tree() { - let runtime = Runtime::new().unwrap(); - let mut net = BabeTestNet::new(runtime.handle().clone(), 1); +#[tokio::test] +async fn importing_epoch_change_block_prunes_tree() { + let mut net = BabeTestNet::new(1); let peer = net.peer(0); let data = peer.data.as_ref().expect("babe link set up during initialization"); @@ -889,17 +887,6 @@ fn importing_epoch_change_block_prunes_tree() { mutator: Arc::new(|_, _| ()), }; - let mut propose_and_import_blocks_wrap = |parent_id, n| { - propose_and_import_blocks( - &client, - &mut proposer_factory, - &mut block_import, - parent_id, - n, - &runtime, - ) - }; - // This is the block tree that we're going to use in this test. Each node // represents an epoch change block, the epoch duration is 6 slots. // @@ -912,12 +899,40 @@ fn importing_epoch_change_block_prunes_tree() { // Create and import the canon chain and keep track of fork blocks (A, C, D) // from the diagram above. - let canon = propose_and_import_blocks_wrap(BlockId::Number(0), 30); + let canon = propose_and_import_blocks( + &client, + &mut proposer_factory, + &mut block_import, + BlockId::Number(0), + 30, + ) + .await; // Create the forks - let fork_1 = propose_and_import_blocks_wrap(BlockId::Hash(canon[0]), 10); - let fork_2 = propose_and_import_blocks_wrap(BlockId::Hash(canon[12]), 15); - let fork_3 = propose_and_import_blocks_wrap(BlockId::Hash(canon[18]), 10); + let fork_1 = propose_and_import_blocks( + &client, + &mut proposer_factory, + &mut block_import, + BlockId::Hash(canon[0]), + 10, + ) + .await; + let fork_2 = propose_and_import_blocks( + &client, + &mut proposer_factory, + &mut block_import, + BlockId::Hash(canon[12]), + 15, + ) + .await; + let fork_3 = propose_and_import_blocks( + &client, + &mut proposer_factory, + &mut block_import, + BlockId::Hash(canon[18]), + 10, + ) + .await; // We should be tracking a total of 9 epochs in the fork tree assert_eq!(epoch_changes.shared_data().tree().iter().count(), 9); @@ -928,7 +943,14 @@ fn importing_epoch_change_block_prunes_tree() { // We finalize block #13 from the canon chain, so on the next epoch // change the tree should be pruned, to not contain F (#7). client.finalize_block(canon[12], None, false).unwrap(); - propose_and_import_blocks_wrap(BlockId::Hash(client.chain_info().best_hash), 7); + propose_and_import_blocks( + &client, + &mut proposer_factory, + &mut block_import, + BlockId::Hash(client.chain_info().best_hash), + 7, + ) + .await; let nodes: Vec<_> = epoch_changes.shared_data().tree().iter().map(|(h, _, _)| *h).collect(); @@ -941,7 +963,14 @@ fn importing_epoch_change_block_prunes_tree() { // finalizing block #25 from the canon chain should prune out the second fork client.finalize_block(canon[24], None, false).unwrap(); - propose_and_import_blocks_wrap(BlockId::Hash(client.chain_info().best_hash), 8); + propose_and_import_blocks( + &client, + &mut proposer_factory, + &mut block_import, + BlockId::Hash(client.chain_info().best_hash), + 8, + ) + .await; let nodes: Vec<_> = epoch_changes.shared_data().tree().iter().map(|(h, _, _)| *h).collect(); @@ -954,11 +983,10 @@ fn importing_epoch_change_block_prunes_tree() { assert!(nodes.iter().any(|h| *h == canon[24])); } -#[test] +#[tokio::test] #[should_panic] -fn verify_slots_are_strictly_increasing() { - let runtime = Runtime::new().unwrap(); - let mut net = BabeTestNet::new(runtime.handle().clone(), 1); +async fn verify_slots_are_strictly_increasing() { + let mut net = BabeTestNet::new(1); let peer = net.peer(0); let data = peer.data.as_ref().expect("babe link set up during initialization"); @@ -981,20 +1009,14 @@ fn verify_slots_are_strictly_increasing() { Some(999.into()), &mut proposer_factory, &mut block_import, - &runtime, - ); + ) + .await; let b1 = client.header(&BlockId::Hash(b1)).unwrap().unwrap(); // we should fail to import this block since the slot number didn't increase. // we will panic due to the `PanickingBlockImport` defined above. - propose_and_import_block( - &b1, - Some(999.into()), - &mut proposer_factory, - &mut block_import, - &runtime, - ); + propose_and_import_block(&b1, Some(999.into()), &mut proposer_factory, &mut block_import).await; } #[test] @@ -1027,10 +1049,9 @@ fn babe_transcript_generation_match() { debug_assert!(test(orig_transcript) == test(transcript_from_data(new_transcript))); } -#[test] -fn obsolete_blocks_aux_data_cleanup() { - let runtime = Runtime::new().unwrap(); - let mut net = BabeTestNet::new(runtime.handle().clone(), 1); +#[tokio::test] +async fn obsolete_blocks_aux_data_cleanup() { + let mut net = BabeTestNet::new(1); let peer = net.peer(0); let data = peer.data.as_ref().expect("babe link set up during initialization"); @@ -1052,17 +1073,6 @@ fn obsolete_blocks_aux_data_cleanup() { let mut block_import = data.block_import.lock().take().expect("import set up during init"); - let mut propose_and_import_blocks_wrap = |parent_id, n| { - propose_and_import_blocks( - &client, - &mut proposer_factory, - &mut block_import, - parent_id, - n, - &runtime, - ) - }; - let aux_data_check = |hashes: &[Hash], expected: bool| { hashes.iter().all(|hash| { aux_schema::load_block_weight(&*peer.client().as_backend(), hash) @@ -1077,9 +1087,30 @@ fn obsolete_blocks_aux_data_cleanup() { // G --- A1 --- A2 --- A3 --- A4 ( < fork1 ) // \-----C4 --- C5 ( < fork3 ) - let fork1_hashes = propose_and_import_blocks_wrap(BlockId::Number(0), 4); - let fork2_hashes = propose_and_import_blocks_wrap(BlockId::Number(0), 2); - let fork3_hashes = propose_and_import_blocks_wrap(BlockId::Number(3), 2); + let fork1_hashes = propose_and_import_blocks( + &client, + &mut proposer_factory, + &mut block_import, + BlockId::Number(0), + 4, + ) + .await; + let fork2_hashes = propose_and_import_blocks( + &client, + &mut proposer_factory, + &mut block_import, + BlockId::Number(0), + 2, + ) + .await; + let fork3_hashes = propose_and_import_blocks( + &client, + &mut proposer_factory, + &mut block_import, + BlockId::Number(3), + 2, + ) + .await; // Check that aux data is present for all but the genesis block. assert!(aux_data_check(&[client.chain_info().genesis_hash], false)); diff --git a/client/finality-grandpa/src/tests.rs b/client/finality-grandpa/src/tests.rs index 6b577fd712..97c0c9fe7c 100644 --- a/client/finality-grandpa/src/tests.rs +++ b/client/finality-grandpa/src/tests.rs @@ -30,7 +30,7 @@ use sc_consensus::{ use sc_network::config::Role; use sc_network_test::{ Block, BlockImportAdapter, FullPeerConfig, Hash, PassThroughVerifier, Peer, PeersClient, - PeersFullClient, TestClient, TestNetFactory, WithRuntime, + PeersFullClient, TestClient, TestNetFactory, }; use sp_api::{ApiRef, ProvideRuntimeApi}; use sp_blockchain::Result; @@ -52,7 +52,7 @@ use std::{ pin::Pin, }; use substrate_test_runtime_client::runtime::BlockNumber; -use tokio::runtime::{Handle, Runtime}; +use tokio::runtime::Handle; use authorities::AuthoritySet; use communication::grandpa_protocol_name; @@ -71,26 +71,16 @@ type GrandpaBlockImport = crate::GrandpaBlockImport< LongestChain, >; +#[derive(Default)] struct GrandpaTestNet { peers: Vec, test_config: TestApi, - rt_handle: Handle, -} - -impl WithRuntime for GrandpaTestNet { - fn with_runtime(rt_handle: Handle) -> Self { - GrandpaTestNet { peers: Vec::new(), test_config: TestApi::default(), rt_handle } - } - fn rt_handle(&self) -> &Handle { - &self.rt_handle - } } impl GrandpaTestNet { - fn new(test_config: TestApi, n_authority: usize, n_full: usize, rt_handle: Handle) -> Self { - let mut net = GrandpaTestNet::with_runtime(rt_handle); - net.peers = Vec::with_capacity(n_authority + n_full); - net.test_config = test_config; + fn new(test_config: TestApi, n_authority: usize, n_full: usize) -> Self { + let mut net = + GrandpaTestNet { peers: Vec::with_capacity(n_authority + n_full), test_config }; for _ in 0..n_authority { net.add_authority_peer(); @@ -228,16 +218,12 @@ fn create_keystore(authority: Ed25519Keyring) -> SyncCryptoStorePtr { keystore } -fn block_until_complete( - future: impl Future + Unpin, - net: &Arc>, - runtime: &mut Runtime, -) { +async fn run_until_complete(future: impl Future + Unpin, net: &Arc>) { let drive_to_completion = futures::future::poll_fn(|cx| { net.lock().poll(cx); Poll::<()>::Pending }); - runtime.block_on(future::select(future, drive_to_completion)); + future::select(future, drive_to_completion).await; } // Spawns grandpa voters. Returns a future to spawn on the runtime. @@ -289,8 +275,7 @@ fn initialize_grandpa( // run the voters to completion. provide a closure to be invoked after // the voters are spawned but before blocking on them. -fn run_to_completion_with( - runtime: &mut Runtime, +async fn run_to_completion_with( blocks: u64, net: Arc>, peers: &[Ed25519Keyring], @@ -303,7 +288,7 @@ where let highest_finalized = Arc::new(RwLock::new(0)); - if let Some(f) = (with)(runtime.handle().clone()) { + if let Some(f) = (with)(Handle::current()) { wait_for.push(f); }; @@ -329,18 +314,17 @@ where // wait for all finalized on each. let wait_for = ::futures::future::join_all(wait_for); - block_until_complete(wait_for, &net, runtime); + run_until_complete(wait_for, &net).await; let highest_finalized = *highest_finalized.read(); highest_finalized } -fn run_to_completion( - runtime: &mut Runtime, +async fn run_to_completion( blocks: u64, net: Arc>, peers: &[Ed25519Keyring], ) -> u64 { - run_to_completion_with(runtime, blocks, net, peers, |_| None) + run_to_completion_with(blocks, net, peers, |_| None).await } fn add_scheduled_change(block: &mut Block, change: ScheduledChange) { @@ -361,17 +345,16 @@ fn add_forced_change( )); } -#[test] -fn finalize_3_voters_no_observers() { +#[tokio::test] +async fn finalize_3_voters_no_observers() { sp_tracing::try_init_simple(); - let mut runtime = Runtime::new().unwrap(); let peers = &[Ed25519Keyring::Alice, Ed25519Keyring::Bob, Ed25519Keyring::Charlie]; let voters = make_ids(peers); - let mut net = GrandpaTestNet::new(TestApi::new(voters), 3, 0, runtime.handle().clone()); - runtime.spawn(initialize_grandpa(&mut net, peers)); + let mut net = GrandpaTestNet::new(TestApi::new(voters), 3, 0); + tokio::spawn(initialize_grandpa(&mut net, peers)); net.peer(0).push_blocks(20, false); - runtime.block_on(net.wait_until_sync()); + net.run_until_sync().await; let hashof20 = net.peer(0).client().info().best_hash; for i in 0..3 { @@ -380,7 +363,7 @@ fn finalize_3_voters_no_observers() { } let net = Arc::new(Mutex::new(net)); - run_to_completion(&mut runtime, 20, net.clone(), peers); + run_to_completion(20, net.clone(), peers).await; // normally there's no justification for finalized blocks assert!( @@ -389,17 +372,15 @@ fn finalize_3_voters_no_observers() { ); } -#[test] -fn finalize_3_voters_1_full_observer() { - let mut runtime = Runtime::new().unwrap(); - +#[tokio::test] +async fn finalize_3_voters_1_full_observer() { let peers = &[Ed25519Keyring::Alice, Ed25519Keyring::Bob, Ed25519Keyring::Charlie]; let voters = make_ids(peers); - let mut net = GrandpaTestNet::new(TestApi::new(voters), 3, 1, runtime.handle().clone()); - runtime.spawn(initialize_grandpa(&mut net, peers)); + let mut net = GrandpaTestNet::new(TestApi::new(voters), 3, 1); + tokio::spawn(initialize_grandpa(&mut net, peers)); - runtime.spawn({ + tokio::spawn({ let peer_id = 3; let net_service = net.peers[peer_id].network_service().clone(); let link = net.peers[peer_id].data.lock().take().expect("link initialized at startup; qed"); @@ -444,7 +425,7 @@ fn finalize_3_voters_1_full_observer() { // wait for all finalized on each. let wait_for = futures::future::join_all(finality_notifications).map(|_| ()); - block_until_complete(wait_for, &net, &mut runtime); + run_until_complete(wait_for, &net).await; // all peers should have stored the justification for the best finalized block #20 for peer_id in 0..4 { @@ -456,8 +437,8 @@ fn finalize_3_voters_1_full_observer() { } } -#[test] -fn transition_3_voters_twice_1_full_observer() { +#[tokio::test] +async fn transition_3_voters_twice_1_full_observer() { sp_tracing::try_init_simple(); let peers_a = &[Ed25519Keyring::Alice, Ed25519Keyring::Bob, Ed25519Keyring::Charlie]; @@ -478,8 +459,7 @@ fn transition_3_voters_twice_1_full_observer() { let genesis_voters = make_ids(peers_a); let api = TestApi::new(genesis_voters); - let mut runtime = Runtime::new().unwrap(); - let net = Arc::new(Mutex::new(GrandpaTestNet::new(api, 8, 1, runtime.handle().clone()))); + let net = Arc::new(Mutex::new(GrandpaTestNet::new(api, 8, 1))); let mut voters = Vec::new(); for (peer_id, local_key) in all_peers.clone().into_iter().enumerate() { @@ -516,7 +496,7 @@ fn transition_3_voters_twice_1_full_observer() { } net.lock().peer(0).push_blocks(1, false); - runtime.block_on(net.lock().wait_until_sync()); + net.lock().run_until_sync().await; for (i, peer) in net.lock().peers().iter().enumerate() { let full_client = peer.client().as_client(); @@ -577,13 +557,13 @@ fn transition_3_voters_twice_1_full_observer() { future::ready(()) }); - runtime.spawn(block_production); + tokio::spawn(block_production); } let mut finality_notifications = Vec::new(); for voter in voters { - runtime.spawn(voter); + tokio::spawn(voter); } for (peer_id, _) in all_peers.into_iter().enumerate() { @@ -607,24 +587,23 @@ fn transition_3_voters_twice_1_full_observer() { // wait for all finalized on each. let wait_for = ::futures::future::join_all(finality_notifications); - block_until_complete(wait_for, &net, &mut runtime); + run_until_complete(wait_for, &net).await; } -#[test] -fn justification_is_generated_periodically() { - let mut runtime = Runtime::new().unwrap(); +#[tokio::test] +async fn justification_is_generated_periodically() { let peers = &[Ed25519Keyring::Alice, Ed25519Keyring::Bob, Ed25519Keyring::Charlie]; let voters = make_ids(peers); - let mut net = GrandpaTestNet::new(TestApi::new(voters), 3, 0, runtime.handle().clone()); - runtime.spawn(initialize_grandpa(&mut net, peers)); + let mut net = GrandpaTestNet::new(TestApi::new(voters), 3, 0); + tokio::spawn(initialize_grandpa(&mut net, peers)); net.peer(0).push_blocks(32, false); - runtime.block_on(net.wait_until_sync()); + net.run_until_sync().await; let hashof32 = net.peer(0).client().info().best_hash; let net = Arc::new(Mutex::new(net)); - run_to_completion(&mut runtime, 32, net.clone(), peers); + run_to_completion(32, net.clone(), peers).await; // when block#32 (justification_period) is finalized, justification // is required => generated @@ -633,16 +612,15 @@ fn justification_is_generated_periodically() { } } -#[test] -fn sync_justifications_on_change_blocks() { - let mut runtime = Runtime::new().unwrap(); +#[tokio::test] +async fn sync_justifications_on_change_blocks() { let peers_a = &[Ed25519Keyring::Alice, Ed25519Keyring::Bob, Ed25519Keyring::Charlie]; let peers_b = &[Ed25519Keyring::Alice, Ed25519Keyring::Bob]; let voters = make_ids(peers_b); // 4 peers, 3 of them are authorities and participate in grandpa let api = TestApi::new(voters); - let mut net = GrandpaTestNet::new(api, 3, 1, runtime.handle().clone()); + let mut net = GrandpaTestNet::new(api, 3, 1); let voters = initialize_grandpa(&mut net, peers_a); // add 20 blocks @@ -660,15 +638,15 @@ fn sync_justifications_on_change_blocks() { // add more blocks on top of it (until we have 25) net.peer(0).push_blocks(4, false); - runtime.block_on(net.wait_until_sync()); + net.run_until_sync().await; for i in 0..4 { assert_eq!(net.peer(i).client().info().best_number, 25, "Peer #{} failed to sync", i); } let net = Arc::new(Mutex::new(net)); - runtime.spawn(voters); - run_to_completion(&mut runtime, 25, net.clone(), peers_a); + tokio::spawn(voters); + run_to_completion(25, net.clone(), peers_a).await; // the first 3 peers are grandpa voters and therefore have already finalized // block 21 and stored a justification @@ -677,20 +655,20 @@ fn sync_justifications_on_change_blocks() { } // the last peer should get the justification by syncing from other peers - futures::executor::block_on(futures::future::poll_fn(move |cx| { + futures::future::poll_fn(move |cx| { if net.lock().peer(3).client().justifications(hashof21).unwrap().is_none() { net.lock().poll(cx); Poll::Pending } else { Poll::Ready(()) } - })) + }) + .await; } -#[test] -fn finalizes_multiple_pending_changes_in_order() { +#[tokio::test] +async fn finalizes_multiple_pending_changes_in_order() { sp_tracing::try_init_simple(); - let mut runtime = Runtime::new().unwrap(); let peers_a = &[Ed25519Keyring::Alice, Ed25519Keyring::Bob, Ed25519Keyring::Charlie]; let peers_b = &[Ed25519Keyring::Dave, Ed25519Keyring::Eve, Ed25519Keyring::Ferdie]; @@ -710,8 +688,8 @@ fn finalizes_multiple_pending_changes_in_order() { // but all of them will be part of the voter set eventually so they should be // all added to the network as authorities let api = TestApi::new(genesis_voters); - let mut net = GrandpaTestNet::new(api, 6, 0, runtime.handle().clone()); - runtime.spawn(initialize_grandpa(&mut net, all_peers)); + let mut net = GrandpaTestNet::new(api, 6, 0); + tokio::spawn(initialize_grandpa(&mut net, all_peers)); // add 20 blocks net.peer(0).push_blocks(20, false); @@ -742,7 +720,7 @@ fn finalizes_multiple_pending_changes_in_order() { // add more blocks on top of it (until we have 30) net.peer(0).push_blocks(4, false); - runtime.block_on(net.wait_until_sync()); + net.run_until_sync().await; // all peers imported both change blocks for i in 0..6 { @@ -750,13 +728,12 @@ fn finalizes_multiple_pending_changes_in_order() { } let net = Arc::new(Mutex::new(net)); - run_to_completion(&mut runtime, 30, net.clone(), all_peers); + run_to_completion(30, net.clone(), all_peers).await; } -#[test] -fn force_change_to_new_set() { +#[tokio::test] +async fn force_change_to_new_set() { sp_tracing::try_init_simple(); - let mut runtime = Runtime::new().unwrap(); // two of these guys are offline. let genesis_authorities = &[ Ed25519Keyring::Alice, @@ -769,7 +746,7 @@ fn force_change_to_new_set() { let api = TestApi::new(make_ids(genesis_authorities)); let voters = make_ids(peers_a); - let mut net = GrandpaTestNet::new(api, 3, 0, runtime.handle().clone()); + let mut net = GrandpaTestNet::new(api, 3, 0); let voters_future = initialize_grandpa(&mut net, peers_a); let net = Arc::new(Mutex::new(net)); @@ -793,7 +770,7 @@ fn force_change_to_new_set() { }); net.lock().peer(0).push_blocks(25, false); - runtime.block_on(net.lock().wait_until_sync()); + net.lock().run_until_sync().await; for (i, peer) in net.lock().peers().iter().enumerate() { assert_eq!(peer.client().info().best_number, 26, "Peer #{} failed to sync", i); @@ -809,18 +786,17 @@ fn force_change_to_new_set() { // it will only finalize if the forced transition happens. // we add_blocks after the voters are spawned because otherwise // the link-halves have the wrong AuthoritySet - runtime.spawn(voters_future); - run_to_completion(&mut runtime, 25, net, peers_a); + tokio::spawn(voters_future); + run_to_completion(25, net, peers_a).await; } -#[test] -fn allows_reimporting_change_blocks() { +#[tokio::test] +async fn allows_reimporting_change_blocks() { let peers_a = &[Ed25519Keyring::Alice, Ed25519Keyring::Bob, Ed25519Keyring::Charlie]; let peers_b = &[Ed25519Keyring::Alice, Ed25519Keyring::Bob]; let voters = make_ids(peers_a); let api = TestApi::new(voters); - let runtime = Runtime::new().unwrap(); - let mut net = GrandpaTestNet::new(api.clone(), 3, 0, runtime.handle().clone()); + let mut net = GrandpaTestNet::new(api.clone(), 3, 0); let client = net.peer(0).client().clone(); let (mut block_import, ..) = net.make_block_import(client.clone()); @@ -845,7 +821,7 @@ fn allows_reimporting_change_blocks() { }; assert_eq!( - runtime.block_on(block_import.import_block(block(), HashMap::new())).unwrap(), + block_import.import_block(block(), HashMap::new()).await.unwrap(), ImportResult::Imported(ImportedAux { needs_justification: true, clear_justification_requests: false, @@ -856,19 +832,18 @@ fn allows_reimporting_change_blocks() { ); assert_eq!( - runtime.block_on(block_import.import_block(block(), HashMap::new())).unwrap(), + block_import.import_block(block(), HashMap::new()).await.unwrap(), ImportResult::AlreadyInChain ); } -#[test] -fn test_bad_justification() { +#[tokio::test] +async fn test_bad_justification() { let peers_a = &[Ed25519Keyring::Alice, Ed25519Keyring::Bob, Ed25519Keyring::Charlie]; let peers_b = &[Ed25519Keyring::Alice, Ed25519Keyring::Bob]; let voters = make_ids(peers_a); let api = TestApi::new(voters); - let runtime = Runtime::new().unwrap(); - let mut net = GrandpaTestNet::new(api.clone(), 3, 0, runtime.handle().clone()); + let mut net = GrandpaTestNet::new(api.clone(), 3, 0); let client = net.peer(0).client().clone(); let (mut block_import, ..) = net.make_block_import(client.clone()); @@ -895,7 +870,7 @@ fn test_bad_justification() { }; assert_eq!( - runtime.block_on(block_import.import_block(block(), HashMap::new())).unwrap(), + block_import.import_block(block(), HashMap::new()).await.unwrap(), ImportResult::Imported(ImportedAux { needs_justification: true, clear_justification_requests: false, @@ -906,18 +881,17 @@ fn test_bad_justification() { ); assert_eq!( - runtime.block_on(block_import.import_block(block(), HashMap::new())).unwrap(), + block_import.import_block(block(), HashMap::new()).await.unwrap(), ImportResult::AlreadyInChain ); } -#[test] -fn voter_persists_its_votes() { +#[tokio::test] +async fn voter_persists_its_votes() { use futures::future; use std::sync::atomic::{AtomicUsize, Ordering}; sp_tracing::try_init_simple(); - let mut runtime = Runtime::new().unwrap(); // we have two authorities but we'll only be running the voter for alice // we are going to be listening for the prevotes it casts @@ -925,7 +899,7 @@ fn voter_persists_its_votes() { let voters = make_ids(peers); // alice has a chain with 20 blocks - let mut net = GrandpaTestNet::new(TestApi::new(voters.clone()), 2, 0, runtime.handle().clone()); + let mut net = GrandpaTestNet::new(TestApi::new(voters.clone()), 2, 0); // create the communication layer for bob, but don't start any // voter. instead we'll listen for the prevote that alice casts @@ -1042,10 +1016,10 @@ fn voter_persists_its_votes() { }) } - runtime.spawn(alice_voter1); + tokio::spawn(alice_voter1); net.peer(0).push_blocks(20, false); - runtime.block_on(net.wait_until_sync()); + net.run_until_sync().await; assert_eq!(net.peer(0).client().info().best_number, 20, "Peer #{} failed to sync", 0); @@ -1062,7 +1036,7 @@ fn voter_persists_its_votes() { HasVoted::No, ); - runtime.spawn(bob_network); + tokio::spawn(bob_network); let round_tx = Arc::new(Mutex::new(round_tx)); let exit_tx = Arc::new(Mutex::new(Some(exit_tx))); @@ -1070,15 +1044,13 @@ fn voter_persists_its_votes() { let net = net.clone(); let state = Arc::new(AtomicUsize::new(0)); - let runtime_handle = runtime.handle().clone(); - runtime.spawn(round_rx.for_each(move |signed| { + tokio::spawn(round_rx.for_each(move |signed| { let net2 = net.clone(); let net = net.clone(); let abort = abort.clone(); let round_tx = round_tx.clone(); let state = state.clone(); let exit_tx = exit_tx.clone(); - let runtime_handle = runtime_handle.clone(); async move { if state.compare_exchange(0, 1, Ordering::SeqCst, Ordering::SeqCst).unwrap() == 0 { @@ -1115,7 +1087,7 @@ fn voter_persists_its_votes() { // we restart alice's voter abort.abort(); - runtime_handle.spawn(alice_voter2(peers, net.clone())); + tokio::spawn(alice_voter2(peers, net.clone())); // and we push our own prevote for block 30 let prevote = @@ -1164,17 +1136,16 @@ fn voter_persists_its_votes() { })); } - block_until_complete(exit_rx.into_future(), &net, &mut runtime); + run_until_complete(exit_rx.into_future(), &net).await; } -#[test] -fn finalize_3_voters_1_light_observer() { +#[tokio::test] +async fn finalize_3_voters_1_light_observer() { sp_tracing::try_init_simple(); - let mut runtime = Runtime::new().unwrap(); let authorities = &[Ed25519Keyring::Alice, Ed25519Keyring::Bob, Ed25519Keyring::Charlie]; let voters = make_ids(authorities); - let mut net = GrandpaTestNet::new(TestApi::new(voters), 3, 1, runtime.handle().clone()); + let mut net = GrandpaTestNet::new(TestApi::new(voters), 3, 1); let voters = initialize_grandpa(&mut net, authorities); let observer = observer::run_grandpa_observer( Config { @@ -1192,7 +1163,7 @@ fn finalize_3_voters_1_light_observer() { ) .unwrap(); net.peer(0).push_blocks(20, false); - runtime.block_on(net.wait_until_sync()); + net.run_until_sync().await; for i in 0..4 { assert_eq!(net.peer(i).client().info().best_number, 20, "Peer #{} failed to sync", i); @@ -1200,20 +1171,19 @@ fn finalize_3_voters_1_light_observer() { let net = Arc::new(Mutex::new(net)); - runtime.spawn(voters); - runtime.spawn(observer); - run_to_completion(&mut runtime, 20, net.clone(), authorities); + tokio::spawn(voters); + tokio::spawn(observer); + run_to_completion(20, net.clone(), authorities).await; } -#[test] -fn voter_catches_up_to_latest_round_when_behind() { +#[tokio::test] +async fn voter_catches_up_to_latest_round_when_behind() { sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); let peers = &[Ed25519Keyring::Alice, Ed25519Keyring::Bob]; let voters = make_ids(peers); - let net = GrandpaTestNet::new(TestApi::new(voters), 2, 0, runtime.handle().clone()); + let net = GrandpaTestNet::new(TestApi::new(voters), 2, 0); let net = Arc::new(Mutex::new(net)); let mut finality_notifications = Vec::new(); @@ -1265,11 +1235,11 @@ fn voter_catches_up_to_latest_round_when_behind() { let voter = voter(Some(keystore), peer_id, link, net.clone()); - runtime.spawn(voter); + tokio::spawn(voter); } net.lock().peer(0).push_blocks(50, false); - runtime.block_on(net.lock().wait_until_sync()); + net.lock().run_until_sync().await; // wait for them to finalize block 50. since they'll vote on 3/4 of the // unfinalized chain it will take at least 4 rounds to do it. @@ -1279,7 +1249,6 @@ fn voter_catches_up_to_latest_round_when_behind() { // able to catch up to the latest round let test = { let net = net.clone(); - let runtime = runtime.handle().clone(); wait_for_finality.then(move |_| { net.lock().add_authority_peer(); @@ -1290,7 +1259,7 @@ fn voter_catches_up_to_latest_round_when_behind() { link.take().expect("link initialized at startup; qed") }; let set_state = link.persistent_data.set_state.clone(); - runtime.spawn(voter(None, 2, link, net.clone())); + tokio::spawn(voter(None, 2, link, net.clone())); let start_time = std::time::Instant::now(); let timeout = Duration::from_secs(5 * 60); @@ -1315,7 +1284,7 @@ fn voter_catches_up_to_latest_round_when_behind() { net.lock().poll(cx); Poll::<()>::Pending }); - runtime.block_on(future::select(test, drive_to_completion)); + future::select(test, drive_to_completion).await; } type TestEnvironment = Environment< @@ -1370,15 +1339,14 @@ where } } -#[test] -fn grandpa_environment_respects_voting_rules() { +#[tokio::test] +async fn grandpa_environment_respects_voting_rules() { use finality_grandpa::voter::Environment; let peers = &[Ed25519Keyring::Alice]; let voters = make_ids(peers); - let runtime = Runtime::new().unwrap(); - let mut net = GrandpaTestNet::new(TestApi::new(voters), 1, 0, runtime.handle().clone()); + let mut net = GrandpaTestNet::new(TestApi::new(voters), 1, 0); let peer = net.peer(0); let network_service = peer.network_service().clone(); let link = peer.data.lock().take().unwrap(); @@ -1408,8 +1376,9 @@ fn grandpa_environment_respects_voting_rules() { // the unrestricted environment should just return the best block assert_eq!( - runtime - .block_on(unrestricted_env.best_chain_containing(peer.client().info().finalized_hash)) + unrestricted_env + .best_chain_containing(peer.client().info().finalized_hash) + .await .unwrap() .unwrap() .1, @@ -1419,8 +1388,9 @@ fn grandpa_environment_respects_voting_rules() { // both the other environments should return block 16, which is 3/4 of the // way in the unfinalized chain assert_eq!( - runtime - .block_on(three_quarters_env.best_chain_containing(peer.client().info().finalized_hash)) + three_quarters_env + .best_chain_containing(peer.client().info().finalized_hash) + .await .unwrap() .unwrap() .1, @@ -1428,8 +1398,9 @@ fn grandpa_environment_respects_voting_rules() { ); assert_eq!( - runtime - .block_on(default_env.best_chain_containing(peer.client().info().finalized_hash)) + default_env + .best_chain_containing(peer.client().info().finalized_hash) + .await .unwrap() .unwrap() .1, @@ -1446,8 +1417,9 @@ fn grandpa_environment_respects_voting_rules() { // the 3/4 environment should propose block 21 for voting assert_eq!( - runtime - .block_on(three_quarters_env.best_chain_containing(peer.client().info().finalized_hash)) + three_quarters_env + .best_chain_containing(peer.client().info().finalized_hash) + .await .unwrap() .unwrap() .1, @@ -1457,8 +1429,9 @@ fn grandpa_environment_respects_voting_rules() { // while the default environment will always still make sure we don't vote // on the best block (2 behind) assert_eq!( - runtime - .block_on(default_env.best_chain_containing(peer.client().info().finalized_hash)) + default_env + .best_chain_containing(peer.client().info().finalized_hash) + .await .unwrap() .unwrap() .1, @@ -1477,8 +1450,9 @@ fn grandpa_environment_respects_voting_rules() { // best block, there's a hard rule that we can't cast any votes lower than // the given base (#21). assert_eq!( - runtime - .block_on(default_env.best_chain_containing(peer.client().info().finalized_hash)) + default_env + .best_chain_containing(peer.client().info().finalized_hash) + .await .unwrap() .unwrap() .1, @@ -1486,15 +1460,14 @@ fn grandpa_environment_respects_voting_rules() { ); } -#[test] -fn grandpa_environment_never_overwrites_round_voter_state() { +#[tokio::test] +async fn grandpa_environment_never_overwrites_round_voter_state() { use finality_grandpa::voter::Environment; let peers = &[Ed25519Keyring::Alice]; let voters = make_ids(peers); - let runtime = Runtime::new().unwrap(); - let mut net = GrandpaTestNet::new(TestApi::new(voters), 1, 0, runtime.handle().clone()); + let mut net = GrandpaTestNet::new(TestApi::new(voters), 1, 0); let peer = net.peer(0); let network_service = peer.network_service().clone(); let link = peer.data.lock().take().unwrap(); @@ -1549,16 +1522,15 @@ fn grandpa_environment_never_overwrites_round_voter_state() { assert_matches!(get_current_round(2).unwrap(), HasVoted::Yes(_, _)); } -#[test] -fn justification_with_equivocation() { +#[tokio::test] +async fn justification_with_equivocation() { use sp_application_crypto::Pair; // we have 100 authorities let pairs = (0..100).map(|n| AuthorityPair::from_seed(&[n; 32])).collect::>(); let voters = pairs.iter().map(AuthorityPair::public).map(|id| (id, 1)).collect::>(); let api = TestApi::new(voters.clone()); - let runtime = Runtime::new().unwrap(); - let mut net = GrandpaTestNet::new(api.clone(), 1, 0, runtime.handle().clone()); + let mut net = GrandpaTestNet::new(api.clone(), 1, 0); // we create a basic chain with 3 blocks (no forks) net.peer(0).push_blocks(3, false); @@ -1617,16 +1589,15 @@ fn justification_with_equivocation() { assert!(justification.verify(set_id, &voters).is_ok()); } -#[test] -fn imports_justification_for_regular_blocks_on_import() { +#[tokio::test] +async fn imports_justification_for_regular_blocks_on_import() { // NOTE: this is a regression test since initially we would only import // justifications for authority change blocks, and would discard any // existing justification otherwise. let peers = &[Ed25519Keyring::Alice]; let voters = make_ids(peers); let api = TestApi::new(voters); - let runtime = Runtime::new().unwrap(); - let mut net = GrandpaTestNet::new(api.clone(), 1, 0, runtime.handle().clone()); + let mut net = GrandpaTestNet::new(api.clone(), 1, 0); let client = net.peer(0).client().clone(); let (mut block_import, ..) = net.make_block_import(client.clone()); @@ -1675,7 +1646,7 @@ fn imports_justification_for_regular_blocks_on_import() { import.fork_choice = Some(ForkChoiceStrategy::LongestChain); assert_eq!( - runtime.block_on(block_import.import_block(import, HashMap::new())).unwrap(), + block_import.import_block(import, HashMap::new()).await.unwrap(), ImportResult::Imported(ImportedAux { needs_justification: false, clear_justification_requests: false, @@ -1689,17 +1660,15 @@ fn imports_justification_for_regular_blocks_on_import() { assert!(client.justifications(block_hash).unwrap().is_some()); } -#[test] -fn grandpa_environment_doesnt_send_equivocation_reports_for_itself() { +#[tokio::test] +async fn grandpa_environment_doesnt_send_equivocation_reports_for_itself() { use finality_grandpa::voter::Environment; let alice = Ed25519Keyring::Alice; let voters = make_ids(&[alice]); - let runtime = Runtime::new().unwrap(); - let environment = { - let mut net = GrandpaTestNet::new(TestApi::new(voters), 1, 0, runtime.handle().clone()); + let mut net = GrandpaTestNet::new(TestApi::new(voters), 1, 0); let peer = net.peer(0); let network_service = peer.network_service().clone(); let link = peer.data.lock().take().unwrap(); @@ -1737,10 +1706,9 @@ fn grandpa_environment_doesnt_send_equivocation_reports_for_itself() { assert!(environment.report_equivocation(equivocation_proof).is_ok()); } -#[test] -fn revert_prunes_authority_changes() { +#[tokio::test] +async fn revert_prunes_authority_changes() { sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); let peers = &[Ed25519Keyring::Alice, Ed25519Keyring::Bob, Ed25519Keyring::Charlie]; @@ -1757,8 +1725,8 @@ fn revert_prunes_authority_changes() { let api = TestApi::new(make_ids(peers)); - let mut net = GrandpaTestNet::new(api, 3, 0, runtime.handle().clone()); - runtime.spawn(initialize_grandpa(&mut net, peers)); + let mut net = GrandpaTestNet::new(api, 3, 0); + tokio::spawn(initialize_grandpa(&mut net, peers)); let peer = net.peer(0); let client = peer.client().as_client(); diff --git a/client/network-gossip/src/bridge.rs b/client/network-gossip/src/bridge.rs index 462677f53c..5d7fd96c13 100644 --- a/client/network-gossip/src/bridge.rs +++ b/client/network-gossip/src/bridge.rs @@ -489,7 +489,7 @@ mod tests { })) } - #[tokio::test(flavor = "multi_thread")] + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn keeps_multiple_subscribers_per_topic_updated_with_both_old_and_new_messages() { let topic = H256::default(); let protocol = ProtocolName::from("/my_protocol"); diff --git a/client/network/src/protocol/notifications/upgrade/notifications.rs b/client/network/src/protocol/notifications/upgrade/notifications.rs index 5d61e10727..5a4ec832db 100644 --- a/client/network/src/protocol/notifications/upgrade/notifications.rs +++ b/client/network/src/protocol/notifications/upgrade/notifications.rs @@ -483,20 +483,15 @@ mod tests { use super::{NotificationsIn, NotificationsInOpen, NotificationsOut, NotificationsOutOpen}; use futures::{channel::oneshot, prelude::*}; use libp2p::core::upgrade; - use tokio::{ - net::{TcpListener, TcpStream}, - runtime::Runtime, - }; + use tokio::net::{TcpListener, TcpStream}; use tokio_util::compat::TokioAsyncReadCompatExt; - #[test] - fn basic_works() { + #[tokio::test] + async fn basic_works() { const PROTO_NAME: &str = "/test/proto/1"; let (listener_addr_tx, listener_addr_rx) = oneshot::channel(); - let runtime = Runtime::new().unwrap(); - - let client = runtime.spawn(async move { + let client = tokio::spawn(async move { let socket = TcpStream::connect(listener_addr_rx.await.unwrap()).await.unwrap(); let NotificationsOutOpen { handshake, mut substream, .. } = upgrade::apply_outbound( socket.compat(), @@ -510,38 +505,34 @@ mod tests { substream.send(b"test message".to_vec()).await.unwrap(); }); - runtime.block_on(async move { - let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); - listener_addr_tx.send(listener.local_addr().unwrap()).unwrap(); + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + listener_addr_tx.send(listener.local_addr().unwrap()).unwrap(); - let (socket, _) = listener.accept().await.unwrap(); - let NotificationsInOpen { handshake, mut substream, .. } = upgrade::apply_inbound( - socket.compat(), - NotificationsIn::new(PROTO_NAME, Vec::new(), 1024 * 1024), - ) - .await - .unwrap(); + let (socket, _) = listener.accept().await.unwrap(); + let NotificationsInOpen { handshake, mut substream, .. } = upgrade::apply_inbound( + socket.compat(), + NotificationsIn::new(PROTO_NAME, Vec::new(), 1024 * 1024), + ) + .await + .unwrap(); - assert_eq!(handshake, b"initial message"); - substream.send_handshake(&b"hello world"[..]); + assert_eq!(handshake, b"initial message"); + substream.send_handshake(&b"hello world"[..]); - let msg = substream.next().await.unwrap().unwrap(); - assert_eq!(msg.as_ref(), b"test message"); - }); + let msg = substream.next().await.unwrap().unwrap(); + assert_eq!(msg.as_ref(), b"test message"); - runtime.block_on(client).unwrap(); + client.await.unwrap(); } - #[test] - fn empty_handshake() { + #[tokio::test] + async fn empty_handshake() { // Check that everything still works when the handshake messages are empty. const PROTO_NAME: &str = "/test/proto/1"; let (listener_addr_tx, listener_addr_rx) = oneshot::channel(); - let runtime = Runtime::new().unwrap(); - - let client = runtime.spawn(async move { + let client = tokio::spawn(async move { let socket = TcpStream::connect(listener_addr_rx.await.unwrap()).await.unwrap(); let NotificationsOutOpen { handshake, mut substream, .. } = upgrade::apply_outbound( socket.compat(), @@ -555,36 +546,32 @@ mod tests { substream.send(Default::default()).await.unwrap(); }); - runtime.block_on(async move { - let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); - listener_addr_tx.send(listener.local_addr().unwrap()).unwrap(); + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + listener_addr_tx.send(listener.local_addr().unwrap()).unwrap(); - let (socket, _) = listener.accept().await.unwrap(); - let NotificationsInOpen { handshake, mut substream, .. } = upgrade::apply_inbound( - socket.compat(), - NotificationsIn::new(PROTO_NAME, Vec::new(), 1024 * 1024), - ) - .await - .unwrap(); + let (socket, _) = listener.accept().await.unwrap(); + let NotificationsInOpen { handshake, mut substream, .. } = upgrade::apply_inbound( + socket.compat(), + NotificationsIn::new(PROTO_NAME, Vec::new(), 1024 * 1024), + ) + .await + .unwrap(); - assert!(handshake.is_empty()); - substream.send_handshake(vec![]); + assert!(handshake.is_empty()); + substream.send_handshake(vec![]); - let msg = substream.next().await.unwrap().unwrap(); - assert!(msg.as_ref().is_empty()); - }); + let msg = substream.next().await.unwrap().unwrap(); + assert!(msg.as_ref().is_empty()); - runtime.block_on(client).unwrap(); + client.await.unwrap(); } - #[test] - fn refused() { + #[tokio::test] + async fn refused() { const PROTO_NAME: &str = "/test/proto/1"; let (listener_addr_tx, listener_addr_rx) = oneshot::channel(); - let runtime = Runtime::new().unwrap(); - - let client = runtime.spawn(async move { + let client = tokio::spawn(async move { let socket = TcpStream::connect(listener_addr_rx.await.unwrap()).await.unwrap(); let outcome = upgrade::apply_outbound( socket.compat(), @@ -599,35 +586,31 @@ mod tests { assert!(outcome.is_err()); }); - runtime.block_on(async move { - let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); - listener_addr_tx.send(listener.local_addr().unwrap()).unwrap(); + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + listener_addr_tx.send(listener.local_addr().unwrap()).unwrap(); - let (socket, _) = listener.accept().await.unwrap(); - let NotificationsInOpen { handshake, substream, .. } = upgrade::apply_inbound( - socket.compat(), - NotificationsIn::new(PROTO_NAME, Vec::new(), 1024 * 1024), - ) - .await - .unwrap(); + let (socket, _) = listener.accept().await.unwrap(); + let NotificationsInOpen { handshake, substream, .. } = upgrade::apply_inbound( + socket.compat(), + NotificationsIn::new(PROTO_NAME, Vec::new(), 1024 * 1024), + ) + .await + .unwrap(); - assert_eq!(handshake, b"hello"); + assert_eq!(handshake, b"hello"); - // We successfully upgrade to the protocol, but then close the substream. - drop(substream); - }); + // We successfully upgrade to the protocol, but then close the substream. + drop(substream); - runtime.block_on(client).unwrap(); + client.await.unwrap(); } - #[test] - fn large_initial_message_refused() { + #[tokio::test] + async fn large_initial_message_refused() { const PROTO_NAME: &str = "/test/proto/1"; let (listener_addr_tx, listener_addr_rx) = oneshot::channel(); - let runtime = Runtime::new().unwrap(); - - let client = runtime.spawn(async move { + let client = tokio::spawn(async move { let socket = TcpStream::connect(listener_addr_rx.await.unwrap()).await.unwrap(); let ret = upgrade::apply_outbound( socket.compat(), @@ -644,30 +627,26 @@ mod tests { assert!(ret.is_err()); }); - runtime.block_on(async move { - let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); - listener_addr_tx.send(listener.local_addr().unwrap()).unwrap(); + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + listener_addr_tx.send(listener.local_addr().unwrap()).unwrap(); - let (socket, _) = listener.accept().await.unwrap(); - let ret = upgrade::apply_inbound( - socket.compat(), - NotificationsIn::new(PROTO_NAME, Vec::new(), 1024 * 1024), - ) - .await; - assert!(ret.is_err()); - }); + let (socket, _) = listener.accept().await.unwrap(); + let ret = upgrade::apply_inbound( + socket.compat(), + NotificationsIn::new(PROTO_NAME, Vec::new(), 1024 * 1024), + ) + .await; + assert!(ret.is_err()); - runtime.block_on(client).unwrap(); + client.await.unwrap(); } - #[test] - fn large_handshake_refused() { + #[tokio::test] + async fn large_handshake_refused() { const PROTO_NAME: &str = "/test/proto/1"; let (listener_addr_tx, listener_addr_rx) = oneshot::channel(); - let runtime = Runtime::new().unwrap(); - - let client = runtime.spawn(async move { + let client = tokio::spawn(async move { let socket = TcpStream::connect(listener_addr_rx.await.unwrap()).await.unwrap(); let ret = upgrade::apply_outbound( socket.compat(), @@ -678,24 +657,22 @@ mod tests { assert!(ret.is_err()); }); - runtime.block_on(async move { - let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); - listener_addr_tx.send(listener.local_addr().unwrap()).unwrap(); + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + listener_addr_tx.send(listener.local_addr().unwrap()).unwrap(); - let (socket, _) = listener.accept().await.unwrap(); - let NotificationsInOpen { handshake, mut substream, .. } = upgrade::apply_inbound( - socket.compat(), - NotificationsIn::new(PROTO_NAME, Vec::new(), 1024 * 1024), - ) - .await - .unwrap(); - assert_eq!(handshake, b"initial message"); + let (socket, _) = listener.accept().await.unwrap(); + let NotificationsInOpen { handshake, mut substream, .. } = upgrade::apply_inbound( + socket.compat(), + NotificationsIn::new(PROTO_NAME, Vec::new(), 1024 * 1024), + ) + .await + .unwrap(); + assert_eq!(handshake, b"initial message"); - // We check that a handshake that is too large gets refused. - substream.send_handshake((0..32768).map(|_| 0).collect::>()); - let _ = substream.next().await; - }); + // We check that a handshake that is too large gets refused. + substream.send_handshake((0..32768).map(|_| 0).collect::>()); + let _ = substream.next().await; - runtime.block_on(client).unwrap(); + client.await.unwrap(); } } diff --git a/client/network/src/service/tests/chain_sync.rs b/client/network/src/service/tests/chain_sync.rs index 0f47b64c35..ce4430dd87 100644 --- a/client/network/src/service/tests/chain_sync.rs +++ b/client/network/src/service/tests/chain_sync.rs @@ -44,7 +44,6 @@ use std::{ time::Duration, }; use substrate_test_runtime_client::{TestClientBuilder, TestClientBuilderExt as _}; -use tokio::runtime::Handle; fn set_default_expecations_no_peers( chain_sync: &mut MockChainSync, @@ -72,7 +71,7 @@ async fn normal_network_poll_no_peers() { let chain_sync_service = Box::new(MockChainSyncInterface::::new()); - let mut network = TestNetworkBuilder::new(Handle::current()) + let mut network = TestNetworkBuilder::new() .with_chain_sync((chain_sync, chain_sync_service)) .build(); @@ -104,7 +103,7 @@ async fn request_justification() { let mut chain_sync = MockChainSync::::new(); set_default_expecations_no_peers(&mut chain_sync); - let mut network = TestNetworkBuilder::new(Handle::current()) + let mut network = TestNetworkBuilder::new() .with_chain_sync((Box::new(chain_sync), chain_sync_service)) .build(); @@ -135,7 +134,7 @@ async fn clear_justification_requests() { Box::new(MockChainSync::::new()); set_default_expecations_no_peers(&mut chain_sync); - let mut network = TestNetworkBuilder::new(Handle::current()) + let mut network = TestNetworkBuilder::new() .with_chain_sync((chain_sync, chain_sync_service)) .build(); @@ -174,7 +173,7 @@ async fn set_sync_fork_request() { .once() .returning(|_, _, _| ()); - let mut network = TestNetworkBuilder::new(Handle::current()) + let mut network = TestNetworkBuilder::new() .with_chain_sync((chain_sync, Box::new(chain_sync_service))) .build(); @@ -218,7 +217,7 @@ async fn on_block_finalized() { .returning(|_, _| ()); set_default_expecations_no_peers(&mut chain_sync); - let mut network = TestNetworkBuilder::new(Handle::current()) + let mut network = TestNetworkBuilder::new() .with_client(client) .with_chain_sync((chain_sync, chain_sync_service)) .build(); @@ -316,7 +315,7 @@ async fn invalid_justification_imported() { let justification_info = Arc::new(RwLock::new(None)); let listen_addr = config::build_multiaddr![Memory(rand::random::())]; - let (service1, mut event_stream1) = TestNetworkBuilder::new(Handle::current()) + let (service1, mut event_stream1) = TestNetworkBuilder::new() .with_import_queue(Box::new(DummyImportQueue( justification_info.clone(), DummyImportQueueHandle {}, @@ -325,7 +324,7 @@ async fn invalid_justification_imported() { .build() .start_network(); - let (service2, mut event_stream2) = TestNetworkBuilder::new(Handle::current()) + let (service2, mut event_stream2) = TestNetworkBuilder::new() .with_set_config(SetConfig { reserved_nodes: vec![MultiaddrWithPeerId { multiaddr: listen_addr, @@ -393,7 +392,7 @@ async fn disconnect_peer_using_chain_sync_handle() { ) .unwrap(); - let (node1, mut event_stream1) = TestNetworkBuilder::new(Handle::current()) + let (node1, mut event_stream1) = TestNetworkBuilder::new() .with_listen_addresses(vec![listen_addr.clone()]) .with_chain_sync((Box::new(chain_sync), Box::new(chain_sync_service))) .with_chain_sync_network((chain_sync_network_provider, chain_sync_network_handle)) @@ -401,7 +400,7 @@ async fn disconnect_peer_using_chain_sync_handle() { .build() .start_network(); - let (node2, mut event_stream2) = TestNetworkBuilder::new(Handle::current()) + let (node2, mut event_stream2) = TestNetworkBuilder::new() .with_set_config(SetConfig { reserved_nodes: vec![MultiaddrWithPeerId { multiaddr: listen_addr, diff --git a/client/network/src/service/tests/mod.rs b/client/network/src/service/tests/mod.rs index fa1486a791..9c97a7f738 100644 --- a/client/network/src/service/tests/mod.rs +++ b/client/network/src/service/tests/mod.rs @@ -44,7 +44,6 @@ use substrate_test_runtime_client::{ runtime::{Block as TestBlock, Hash as TestHash}, TestClient, TestClientBuilder, TestClientBuilderExt as _, }; -use tokio::runtime::Handle; #[cfg(test)] mod chain_sync; @@ -59,12 +58,11 @@ const PROTOCOL_NAME: &str = "/foo"; struct TestNetwork { network: TestNetworkWorker, - rt_handle: Handle, } impl TestNetwork { - pub fn new(network: TestNetworkWorker, rt_handle: Handle) -> Self { - Self { network, rt_handle } + pub fn new(network: TestNetworkWorker) -> Self { + Self { network } } pub fn service(&self) -> &Arc { @@ -82,7 +80,7 @@ impl TestNetwork { let service = worker.service().clone(); let event_stream = service.event_stream("test"); - self.rt_handle.spawn(async move { + tokio::spawn(async move { futures::pin_mut!(worker); let _ = worker.await; }); @@ -100,11 +98,10 @@ struct TestNetworkBuilder { chain_sync: Option<(Box>, Box>)>, chain_sync_network: Option<(NetworkServiceProvider, NetworkServiceHandle)>, config: Option, - rt_handle: Handle, } impl TestNetworkBuilder { - pub fn new(rt_handle: Handle) -> Self { + pub fn new() -> Self { Self { import_queue: None, link: None, @@ -114,7 +111,6 @@ impl TestNetworkBuilder { chain_sync: None, chain_sync_network: None, config: None, - rt_handle, } } @@ -229,21 +225,21 @@ impl TestNetworkBuilder { let block_request_protocol_config = { let (handler, protocol_config) = BlockRequestHandler::new(&protocol_id, None, client.clone(), 50); - self.rt_handle.spawn(handler.run().boxed()); + tokio::spawn(handler.run().boxed()); protocol_config }; let state_request_protocol_config = { let (handler, protocol_config) = StateRequestHandler::new(&protocol_id, None, client.clone(), 50); - self.rt_handle.spawn(handler.run().boxed()); + tokio::spawn(handler.run().boxed()); protocol_config }; let light_client_request_protocol_config = { let (handler, protocol_config) = LightClientRequestHandler::new(&protocol_id, None, client.clone()); - self.rt_handle.spawn(handler.run().boxed()); + tokio::spawn(handler.run().boxed()); protocol_config }; @@ -310,11 +306,6 @@ impl TestNetworkBuilder { .link .unwrap_or(Box::new(sc_network_sync::service::mock::MockChainSyncInterface::new())); - let handle = self.rt_handle.clone(); - let executor = move |f| { - handle.spawn(f); - }; - let worker = NetworkWorker::< substrate_test_runtime_client::runtime::Block, substrate_test_runtime_client::runtime::Hash, @@ -322,7 +313,9 @@ impl TestNetworkBuilder { >::new(config::Params { block_announce_config, role: config::Role::Full, - executor: Box::new(executor), + executor: Box::new(|f| { + tokio::spawn(f); + }), network_config, chain: client.clone(), protocol_id, @@ -340,10 +333,10 @@ impl TestNetworkBuilder { .unwrap(); let service = worker.service().clone(); - self.rt_handle.spawn(async move { + tokio::spawn(async move { let _ = chain_sync_network_provider.run(service).await; }); - self.rt_handle.spawn(async move { + tokio::spawn(async move { loop { futures::future::poll_fn(|cx| { import_queue.poll_actions(cx, &mut *link); @@ -354,6 +347,6 @@ impl TestNetworkBuilder { } }); - TestNetwork::new(worker, self.rt_handle) + TestNetwork::new(worker) } } diff --git a/client/network/src/service/tests/service.rs b/client/network/src/service/tests/service.rs index aa74e595ff..bdf3009635 100644 --- a/client/network/src/service/tests/service.rs +++ b/client/network/src/service/tests/service.rs @@ -26,7 +26,6 @@ use sc_network_common::{ service::{NetworkNotification, NetworkPeers, NetworkStateInfo}, }; use std::{sync::Arc, time::Duration}; -use tokio::runtime::Handle; type TestNetworkService = NetworkService< substrate_test_runtime_client::runtime::Block, @@ -38,9 +37,7 @@ const PROTOCOL_NAME: &str = "/foo"; /// Builds two nodes and their associated events stream. /// The nodes are connected together and have the `PROTOCOL_NAME` protocol registered. -fn build_nodes_one_proto( - rt_handle: &Handle, -) -> ( +fn build_nodes_one_proto() -> ( Arc, impl Stream, Arc, @@ -48,12 +45,12 @@ fn build_nodes_one_proto( ) { let listen_addr = config::build_multiaddr![Memory(rand::random::())]; - let (node1, events_stream1) = TestNetworkBuilder::new(rt_handle.clone()) + let (node1, events_stream1) = TestNetworkBuilder::new() .with_listen_addresses(vec![listen_addr.clone()]) .build() .start_network(); - let (node2, events_stream2) = TestNetworkBuilder::new(rt_handle.clone()) + let (node2, events_stream2) = TestNetworkBuilder::new() .with_set_config(SetConfig { reserved_nodes: vec![MultiaddrWithPeerId { multiaddr: listen_addr, @@ -67,15 +64,12 @@ fn build_nodes_one_proto( (node1, events_stream1, node2, events_stream2) } -#[test] -fn notifications_state_consistent() { +#[tokio::test] +async fn notifications_state_consistent() { // Runs two nodes and ensures that events are propagated out of the API in a consistent // correct order, which means no notification received on a closed substream. - let runtime = tokio::runtime::Runtime::new().unwrap(); - - let (node1, mut events_stream1, node2, mut events_stream2) = - build_nodes_one_proto(runtime.handle()); + let (node1, mut events_stream1, node2, mut events_stream2) = build_nodes_one_proto(); // Write some initial notifications that shouldn't get through. for _ in 0..(rand::random::() % 5) { @@ -93,140 +87,130 @@ fn notifications_state_consistent() { ); } - runtime.block_on(async move { - // True if we have an active substream from node1 to node2. - let mut node1_to_node2_open = false; - // True if we have an active substream from node2 to node1. - let mut node2_to_node1_open = false; - // We stop the test after a certain number of iterations. - let mut iterations = 0; - // Safe guard because we don't want the test to pass if no substream has been open. - let mut something_happened = false; + // True if we have an active substream from node1 to node2. + let mut node1_to_node2_open = false; + // True if we have an active substream from node2 to node1. + let mut node2_to_node1_open = false; + // We stop the test after a certain number of iterations. + let mut iterations = 0; + // Safe guard because we don't want the test to pass if no substream has been open. + let mut something_happened = false; + + loop { + iterations += 1; + if iterations >= 1_000 { + assert!(something_happened); + break + } - loop { - iterations += 1; - if iterations >= 1_000 { - assert!(something_happened); - break - } + // Start by sending a notification from node1 to node2 and vice-versa. Part of the + // test consists in ensuring that notifications get ignored if the stream isn't open. + if rand::random::() % 5 >= 3 { + node1.write_notification( + node2.local_peer_id(), + PROTOCOL_NAME.into(), + b"hello world".to_vec(), + ); + } + if rand::random::() % 5 >= 3 { + node2.write_notification( + node1.local_peer_id(), + PROTOCOL_NAME.into(), + b"hello world".to_vec(), + ); + } - // Start by sending a notification from node1 to node2 and vice-versa. Part of the - // test consists in ensuring that notifications get ignored if the stream isn't open. - if rand::random::() % 5 >= 3 { - node1.write_notification( - node2.local_peer_id(), - PROTOCOL_NAME.into(), - b"hello world".to_vec(), - ); - } - if rand::random::() % 5 >= 3 { - node2.write_notification( - node1.local_peer_id(), - PROTOCOL_NAME.into(), - b"hello world".to_vec(), - ); - } + // Also randomly disconnect the two nodes from time to time. + if rand::random::() % 20 == 0 { + node1.disconnect_peer(node2.local_peer_id(), PROTOCOL_NAME.into()); + } + if rand::random::() % 20 == 0 { + node2.disconnect_peer(node1.local_peer_id(), PROTOCOL_NAME.into()); + } - // Also randomly disconnect the two nodes from time to time. - if rand::random::() % 20 == 0 { - node1.disconnect_peer(node2.local_peer_id(), PROTOCOL_NAME.into()); + // Grab next event from either `events_stream1` or `events_stream2`. + let next_event = { + let next1 = events_stream1.next(); + let next2 = events_stream2.next(); + // We also await on a small timer, otherwise it is possible for the test to wait + // forever while nothing at all happens on the network. + let continue_test = futures_timer::Delay::new(Duration::from_millis(20)); + match future::select(future::select(next1, next2), continue_test).await { + future::Either::Left((future::Either::Left((Some(ev), _)), _)) => + future::Either::Left(ev), + future::Either::Left((future::Either::Right((Some(ev), _)), _)) => + future::Either::Right(ev), + future::Either::Right(_) => continue, + _ => break, } - if rand::random::() % 20 == 0 { - node2.disconnect_peer(node1.local_peer_id(), PROTOCOL_NAME.into()); - } - - // Grab next event from either `events_stream1` or `events_stream2`. - let next_event = { - let next1 = events_stream1.next(); - let next2 = events_stream2.next(); - // We also await on a small timer, otherwise it is possible for the test to wait - // forever while nothing at all happens on the network. - let continue_test = futures_timer::Delay::new(Duration::from_millis(20)); - match future::select(future::select(next1, next2), continue_test).await { - future::Either::Left((future::Either::Left((Some(ev), _)), _)) => - future::Either::Left(ev), - future::Either::Left((future::Either::Right((Some(ev), _)), _)) => - future::Either::Right(ev), - future::Either::Right(_) => continue, - _ => break, - } - }; - - match next_event { - future::Either::Left(Event::NotificationStreamOpened { - remote, protocol, .. - }) => - if protocol == PROTOCOL_NAME.into() { - something_happened = true; - assert!(!node1_to_node2_open); - node1_to_node2_open = true; - assert_eq!(remote, node2.local_peer_id()); - }, - future::Either::Right(Event::NotificationStreamOpened { - remote, protocol, .. - }) => - if protocol == PROTOCOL_NAME.into() { - something_happened = true; - assert!(!node2_to_node1_open); - node2_to_node1_open = true; - assert_eq!(remote, node1.local_peer_id()); - }, - future::Either::Left(Event::NotificationStreamClosed { - remote, protocol, .. - }) => - if protocol == PROTOCOL_NAME.into() { - assert!(node1_to_node2_open); - node1_to_node2_open = false; - assert_eq!(remote, node2.local_peer_id()); - }, - future::Either::Right(Event::NotificationStreamClosed { - remote, protocol, .. - }) => - if protocol == PROTOCOL_NAME.into() { - assert!(node2_to_node1_open); - node2_to_node1_open = false; - assert_eq!(remote, node1.local_peer_id()); - }, - future::Either::Left(Event::NotificationsReceived { remote, .. }) => { + }; + + match next_event { + future::Either::Left(Event::NotificationStreamOpened { remote, protocol, .. }) => + if protocol == PROTOCOL_NAME.into() { + something_happened = true; + assert!(!node1_to_node2_open); + node1_to_node2_open = true; + assert_eq!(remote, node2.local_peer_id()); + }, + future::Either::Right(Event::NotificationStreamOpened { remote, protocol, .. }) => + if protocol == PROTOCOL_NAME.into() { + something_happened = true; + assert!(!node2_to_node1_open); + node2_to_node1_open = true; + assert_eq!(remote, node1.local_peer_id()); + }, + future::Either::Left(Event::NotificationStreamClosed { remote, protocol, .. }) => + if protocol == PROTOCOL_NAME.into() { assert!(node1_to_node2_open); + node1_to_node2_open = false; assert_eq!(remote, node2.local_peer_id()); - if rand::random::() % 5 >= 4 { - node1.write_notification( - node2.local_peer_id(), - PROTOCOL_NAME.into(), - b"hello world".to_vec(), - ); - } }, - future::Either::Right(Event::NotificationsReceived { remote, .. }) => { + future::Either::Right(Event::NotificationStreamClosed { remote, protocol, .. }) => + if protocol == PROTOCOL_NAME.into() { assert!(node2_to_node1_open); + node2_to_node1_open = false; assert_eq!(remote, node1.local_peer_id()); - if rand::random::() % 5 >= 4 { - node2.write_notification( - node1.local_peer_id(), - PROTOCOL_NAME.into(), - b"hello world".to_vec(), - ); - } }, + future::Either::Left(Event::NotificationsReceived { remote, .. }) => { + assert!(node1_to_node2_open); + assert_eq!(remote, node2.local_peer_id()); + if rand::random::() % 5 >= 4 { + node1.write_notification( + node2.local_peer_id(), + PROTOCOL_NAME.into(), + b"hello world".to_vec(), + ); + } + }, + future::Either::Right(Event::NotificationsReceived { remote, .. }) => { + assert!(node2_to_node1_open); + assert_eq!(remote, node1.local_peer_id()); + if rand::random::() % 5 >= 4 { + node2.write_notification( + node1.local_peer_id(), + PROTOCOL_NAME.into(), + b"hello world".to_vec(), + ); + } + }, - // Add new events here. - future::Either::Left(Event::SyncConnected { .. }) => {}, - future::Either::Right(Event::SyncConnected { .. }) => {}, - future::Either::Left(Event::SyncDisconnected { .. }) => {}, - future::Either::Right(Event::SyncDisconnected { .. }) => {}, - future::Either::Left(Event::Dht(_)) => {}, - future::Either::Right(Event::Dht(_)) => {}, - }; - } - }); + // Add new events here. + future::Either::Left(Event::SyncConnected { .. }) => {}, + future::Either::Right(Event::SyncConnected { .. }) => {}, + future::Either::Left(Event::SyncDisconnected { .. }) => {}, + future::Either::Right(Event::SyncDisconnected { .. }) => {}, + future::Either::Left(Event::Dht(_)) => {}, + future::Either::Right(Event::Dht(_)) => {}, + }; + } } #[tokio::test] async fn lots_of_incoming_peers_works() { let listen_addr = config::build_multiaddr![Memory(rand::random::())]; - let (main_node, _) = TestNetworkBuilder::new(Handle::current()) + let (main_node, _) = TestNetworkBuilder::new() .with_listen_addresses(vec![listen_addr.clone()]) .with_set_config(SetConfig { in_peers: u32::MAX, ..Default::default() }) .build() @@ -239,7 +223,7 @@ async fn lots_of_incoming_peers_works() { let mut background_tasks_to_wait = Vec::new(); for _ in 0..32 { - let (_dialing_node, event_stream) = TestNetworkBuilder::new(Handle::current()) + let (_dialing_node, event_stream) = TestNetworkBuilder::new() .with_set_config(SetConfig { reserved_nodes: vec![MultiaddrWithPeerId { multiaddr: listen_addr.clone(), @@ -286,20 +270,17 @@ async fn lots_of_incoming_peers_works() { future::join_all(background_tasks_to_wait).await; } -#[test] -fn notifications_back_pressure() { +#[tokio::test] +async fn notifications_back_pressure() { // Node 1 floods node 2 with notifications. Random sleeps are done on node 2 to simulate the // node being busy. We make sure that all notifications are received. const TOTAL_NOTIFS: usize = 10_000; - let runtime = tokio::runtime::Runtime::new().unwrap(); - - let (node1, mut events_stream1, node2, mut events_stream2) = - build_nodes_one_proto(runtime.handle()); + let (node1, mut events_stream1, node2, mut events_stream2) = build_nodes_one_proto(); let node2_id = node2.local_peer_id(); - let receiver = runtime.spawn(async move { + let receiver = tokio::spawn(async move { let mut received_notifications = 0; while received_notifications < TOTAL_NOTIFS { @@ -320,40 +301,36 @@ fn notifications_back_pressure() { } }); - runtime.block_on(async move { - // Wait for the `NotificationStreamOpened`. - loop { - match events_stream1.next().await.unwrap() { - Event::NotificationStreamOpened { .. } => break, - _ => {}, - }; - } + // Wait for the `NotificationStreamOpened`. + loop { + match events_stream1.next().await.unwrap() { + Event::NotificationStreamOpened { .. } => break, + _ => {}, + }; + } - // Sending! - for num in 0..TOTAL_NOTIFS { - let notif = node1.notification_sender(node2_id, PROTOCOL_NAME.into()).unwrap(); - notif - .ready() - .await - .unwrap() - .send(format!("hello #{}", num).into_bytes()) - .unwrap(); - } + // Sending! + for num in 0..TOTAL_NOTIFS { + let notif = node1.notification_sender(node2_id, PROTOCOL_NAME.into()).unwrap(); + notif + .ready() + .await + .unwrap() + .send(format!("hello #{}", num).into_bytes()) + .unwrap(); + } - receiver.await.unwrap(); - }); + receiver.await.unwrap(); } -#[test] -fn fallback_name_working() { +#[tokio::test] +async fn fallback_name_working() { // Node 1 supports the protocols "new" and "old". Node 2 only supports "old". Checks whether // they can connect. const NEW_PROTOCOL_NAME: &str = "/new-shiny-protocol-that-isnt-PROTOCOL_NAME"; - let runtime = tokio::runtime::Runtime::new().unwrap(); - let listen_addr = config::build_multiaddr![Memory(rand::random::())]; - let (node1, mut events_stream1) = TestNetworkBuilder::new(runtime.handle().clone()) + let (node1, mut events_stream1) = TestNetworkBuilder::new() .with_config(config::NetworkConfiguration { extra_sets: vec![NonDefaultSetConfig { notifications_protocol: NEW_PROTOCOL_NAME.into(), @@ -369,7 +346,7 @@ fn fallback_name_working() { .build() .start_network(); - let (_, mut events_stream2) = TestNetworkBuilder::new(runtime.handle().clone()) + let (_, mut events_stream2) = TestNetworkBuilder::new() .with_set_config(SetConfig { reserved_nodes: vec![MultiaddrWithPeerId { multiaddr: listen_addr, @@ -380,7 +357,7 @@ fn fallback_name_working() { .build() .start_network(); - let receiver = runtime.spawn(async move { + let receiver = tokio::spawn(async move { // Wait for the `NotificationStreamOpened`. loop { match events_stream2.next().await.unwrap() { @@ -394,30 +371,27 @@ fn fallback_name_working() { } }); - runtime.block_on(async move { - // Wait for the `NotificationStreamOpened`. - loop { - match events_stream1.next().await.unwrap() { - Event::NotificationStreamOpened { protocol, negotiated_fallback, .. } - if protocol == NEW_PROTOCOL_NAME.into() => - { - assert_eq!(negotiated_fallback, Some(PROTOCOL_NAME.into())); - break - }, - _ => {}, - }; - } + // Wait for the `NotificationStreamOpened`. + loop { + match events_stream1.next().await.unwrap() { + Event::NotificationStreamOpened { protocol, negotiated_fallback, .. } + if protocol == NEW_PROTOCOL_NAME.into() => + { + assert_eq!(negotiated_fallback, Some(PROTOCOL_NAME.into())); + break + }, + _ => {}, + }; + } - receiver.await.unwrap(); - }); + receiver.await.unwrap(); } // Disconnect peer by calling `Protocol::disconnect_peer()` with the supplied block announcement // protocol name and verify that `SyncDisconnected` event is emitted #[tokio::test] async fn disconnect_sync_peer_using_block_announcement_protocol_name() { - let (node1, mut events_stream1, node2, mut events_stream2) = - build_nodes_one_proto(&Handle::current()); + let (node1, mut events_stream1, node2, mut events_stream2) = build_nodes_one_proto(); async fn wait_for_events(stream: &mut (impl Stream + std::marker::Unpin)) { let mut notif_received = false; @@ -454,7 +428,7 @@ async fn disconnect_sync_peer_using_block_announcement_protocol_name() { async fn ensure_listen_addresses_consistent_with_transport_memory() { let listen_addr = config::build_multiaddr![Ip4([127, 0, 0, 1]), Tcp(0_u16)]; - let _ = TestNetworkBuilder::new(Handle::current()) + let _ = TestNetworkBuilder::new() .with_config(config::NetworkConfiguration { listen_addresses: vec![listen_addr.clone()], transport: TransportConfig::MemoryOnly, @@ -474,7 +448,7 @@ async fn ensure_listen_addresses_consistent_with_transport_memory() { async fn ensure_listen_addresses_consistent_with_transport_not_memory() { let listen_addr = config::build_multiaddr![Memory(rand::random::())]; - let _ = TestNetworkBuilder::new(Handle::current()) + let _ = TestNetworkBuilder::new() .with_config(config::NetworkConfiguration { listen_addresses: vec![listen_addr.clone()], ..config::NetworkConfiguration::new( @@ -497,7 +471,7 @@ async fn ensure_boot_node_addresses_consistent_with_transport_memory() { peer_id: PeerId::random(), }; - let _ = TestNetworkBuilder::new(Handle::current()) + let _ = TestNetworkBuilder::new() .with_config(config::NetworkConfiguration { listen_addresses: vec![listen_addr.clone()], transport: TransportConfig::MemoryOnly, @@ -522,7 +496,7 @@ async fn ensure_boot_node_addresses_consistent_with_transport_not_memory() { peer_id: PeerId::random(), }; - let _ = TestNetworkBuilder::new(Handle::current()) + let _ = TestNetworkBuilder::new() .with_config(config::NetworkConfiguration { listen_addresses: vec![listen_addr.clone()], boot_nodes: vec![boot_node], @@ -546,7 +520,7 @@ async fn ensure_reserved_node_addresses_consistent_with_transport_memory() { peer_id: PeerId::random(), }; - let _ = TestNetworkBuilder::new(Handle::current()) + let _ = TestNetworkBuilder::new() .with_config(config::NetworkConfiguration { listen_addresses: vec![listen_addr.clone()], transport: TransportConfig::MemoryOnly, @@ -574,7 +548,7 @@ async fn ensure_reserved_node_addresses_consistent_with_transport_not_memory() { peer_id: PeerId::random(), }; - let _ = TestNetworkBuilder::new(Handle::current()) + let _ = TestNetworkBuilder::new() .with_config(config::NetworkConfiguration { listen_addresses: vec![listen_addr.clone()], default_peers_set: SetConfig { @@ -598,7 +572,7 @@ async fn ensure_public_addresses_consistent_with_transport_memory() { let listen_addr = config::build_multiaddr![Memory(rand::random::())]; let public_address = config::build_multiaddr![Ip4([127, 0, 0, 1]), Tcp(0_u16)]; - let _ = TestNetworkBuilder::new(Handle::current()) + let _ = TestNetworkBuilder::new() .with_config(config::NetworkConfiguration { listen_addresses: vec![listen_addr.clone()], transport: TransportConfig::MemoryOnly, @@ -620,7 +594,7 @@ async fn ensure_public_addresses_consistent_with_transport_not_memory() { let listen_addr = config::build_multiaddr![Ip4([127, 0, 0, 1]), Tcp(0_u16)]; let public_address = config::build_multiaddr![Memory(rand::random::())]; - let _ = TestNetworkBuilder::new(Handle::current()) + let _ = TestNetworkBuilder::new() .with_config(config::NetworkConfiguration { listen_addresses: vec![listen_addr.clone()], public_addresses: vec![public_address], diff --git a/client/network/test/src/lib.rs b/client/network/test/src/lib.rs index 173ca81653..c3304df629 100644 --- a/client/network/test/src/lib.rs +++ b/client/network/test/src/lib.rs @@ -708,16 +708,8 @@ pub struct FullPeerConfig { pub storage_chain: bool, } -/// Trait for text fixtures with tokio runtime. -pub trait WithRuntime { - /// Construct with runtime handle. - fn with_runtime(rt_handle: tokio::runtime::Handle) -> Self; - /// Get runtime handle. - fn rt_handle(&self) -> &tokio::runtime::Handle; -} - #[async_trait::async_trait] -pub trait TestNetFactory: WithRuntime + Sized +pub trait TestNetFactory: Default + Sized where >::Transaction: Send, { @@ -747,9 +739,9 @@ where ); /// Create new test network with this many peers. - fn new(rt_handle: tokio::runtime::Handle, n: usize) -> Self { + fn new(n: usize) -> Self { trace!(target: "test_network", "Creating test network"); - let mut net = Self::with_runtime(rt_handle); + let mut net = Self::default(); for i in 0..n { trace!(target: "test_network", "Adding peer {}", i); @@ -905,14 +897,11 @@ where ) .unwrap(); - let handle = self.rt_handle().clone(); - let executor = move |f| { - handle.spawn(f); - }; - let network = NetworkWorker::new(sc_network::config::Params { role: if config.is_authority { Role::Authority } else { Role::Full }, - executor: Box::new(executor), + executor: Box::new(|f| { + tokio::spawn(f); + }), network_config, chain: client.clone(), protocol_id, @@ -934,10 +923,10 @@ where trace!(target: "test_network", "Peer identifier: {}", network.service().local_peer_id()); let service = network.service().clone(); - self.rt_handle().spawn(async move { + tokio::spawn(async move { chain_sync_network_provider.run(service).await; }); - self.rt_handle().spawn(async move { + tokio::spawn(async move { import_queue.run(Box::new(chain_sync_service)).await; }); @@ -968,7 +957,7 @@ where /// Used to spawn background tasks, e.g. the block request protocol handler. fn spawn_task(&self, f: BoxFuture<'static, ()>) { - self.rt_handle().spawn(f); + tokio::spawn(f); } /// Polls the testnet until all nodes are in sync. @@ -1027,11 +1016,11 @@ where Poll::Pending } - /// Wait until we are sync'ed. + /// Run the network until we are sync'ed. /// /// Calls `poll_until_sync` repeatedly. /// (If we've not synced within 10 mins then panic rather than hang.) - async fn wait_until_sync(&mut self) { + async fn run_until_sync(&mut self) { timeout( Duration::from_secs(10 * 60), futures::future::poll_fn::<(), _>(|cx| self.poll_until_sync(cx)), @@ -1040,17 +1029,17 @@ where .expect("sync didn't happen within 10 mins"); } - /// Wait until there are no pending packets. + /// Run the network until there are no pending packets. /// /// Calls `poll_until_idle` repeatedly with the runtime passed as parameter. - async fn wait_until_idle(&mut self) { + async fn run_until_idle(&mut self) { futures::future::poll_fn::<(), _>(|cx| self.poll_until_idle(cx)).await; } - /// Wait until all peers are connected to each other. + /// Run the network until all peers are connected to each other. /// /// Calls `poll_until_connected` repeatedly with the runtime passed as parameter. - async fn wait_until_connected(&mut self) { + async fn run_until_connected(&mut self) { futures::future::poll_fn::<(), _>(|cx| self.poll_until_connected(cx)).await; } @@ -1082,20 +1071,11 @@ where } } +#[derive(Default)] pub struct TestNet { - rt_handle: tokio::runtime::Handle, peers: Vec>, } -impl WithRuntime for TestNet { - fn with_runtime(rt_handle: tokio::runtime::Handle) -> Self { - TestNet { rt_handle, peers: Vec::new() } - } - fn rt_handle(&self) -> &tokio::runtime::Handle { - &self.rt_handle - } -} - impl TestNetFactory for TestNet { type Verifier = PassThroughVerifier; type PeerData = (); @@ -1150,16 +1130,9 @@ impl JustificationImport for ForceFinalized { .map_err(|_| ConsensusError::InvalidJustification) } } -pub struct JustificationTestNet(TestNet); -impl WithRuntime for JustificationTestNet { - fn with_runtime(rt_handle: tokio::runtime::Handle) -> Self { - JustificationTestNet(TestNet::with_runtime(rt_handle)) - } - fn rt_handle(&self) -> &tokio::runtime::Handle { - &self.0.rt_handle() - } -} +#[derive(Default)] +pub struct JustificationTestNet(TestNet); impl TestNetFactory for JustificationTestNet { type Verifier = PassThroughVerifier; diff --git a/client/network/test/src/sync.rs b/client/network/test/src/sync.rs index efe0e0577c..9a9b5782dc 100644 --- a/client/network/test/src/sync.rs +++ b/client/network/test/src/sync.rs @@ -21,12 +21,10 @@ use futures::Future; use sp_consensus::{block_validation::Validation, BlockOrigin}; use sp_runtime::Justifications; use substrate_test_runtime::Header; -use tokio::runtime::Runtime; -fn test_ancestor_search_when_common_is(n: usize) { +async fn test_ancestor_search_when_common_is(n: usize) { sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - let mut net = TestNet::new(runtime.handle().clone(), 3); + let mut net = TestNet::new(3); net.peer(0).push_blocks(n, false); net.peer(1).push_blocks(n, false); @@ -36,18 +34,17 @@ fn test_ancestor_search_when_common_is(n: usize) { net.peer(1).push_blocks(100, false); net.peer(2).push_blocks(100, false); - runtime.block_on(net.wait_until_sync()); + net.run_until_sync().await; let peer1 = &net.peers()[1]; assert!(net.peers()[0].blockchain_canon_equals(peer1)); } -#[test] -fn sync_peers_works() { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn sync_peers_works() { sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - let mut net = TestNet::new(runtime.handle().clone(), 3); + let mut net = TestNet::new(3); - runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { + futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); for peer in 0..3 { if net.peer(peer).num_peers() != 2 { @@ -55,14 +52,14 @@ fn sync_peers_works() { } } Poll::Ready(()) - })); + }) + .await; } -#[test] -fn sync_cycle_from_offline_to_syncing_to_offline() { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn sync_cycle_from_offline_to_syncing_to_offline() { sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - let mut net = TestNet::new(runtime.handle().clone(), 3); + let mut net = TestNet::new(3); for peer in 0..3 { // Offline, and not major syncing. assert!(net.peer(peer).is_offline()); @@ -73,7 +70,7 @@ fn sync_cycle_from_offline_to_syncing_to_offline() { net.peer(2).push_blocks(100, false); // Block until all nodes are online and nodes 0 and 1 and major syncing. - runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { + futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); for peer in 0..3 { // Online @@ -88,10 +85,11 @@ fn sync_cycle_from_offline_to_syncing_to_offline() { } } Poll::Ready(()) - })); + }) + .await; // Block until all nodes are done syncing. - runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { + futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); for peer in 0..3 { if net.peer(peer).is_major_syncing() { @@ -99,26 +97,27 @@ fn sync_cycle_from_offline_to_syncing_to_offline() { } } Poll::Ready(()) - })); + }) + .await; // Now drop nodes 1 and 2, and check that node 0 is offline. net.peers.remove(2); net.peers.remove(1); - runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { + futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if !net.peer(0).is_offline() { Poll::Pending } else { Poll::Ready(()) } - })); + }) + .await; } -#[test] -fn syncing_node_not_major_syncing_when_disconnected() { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn syncing_node_not_major_syncing_when_disconnected() { sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - let mut net = TestNet::new(runtime.handle().clone(), 3); + let mut net = TestNet::new(3); // Generate blocks. net.peer(2).push_blocks(100, false); @@ -127,136 +126,132 @@ fn syncing_node_not_major_syncing_when_disconnected() { assert!(!net.peer(1).is_major_syncing()); // Check that we switch to major syncing. - runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { + futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if !net.peer(1).is_major_syncing() { Poll::Pending } else { Poll::Ready(()) } - })); + }) + .await; // Destroy two nodes, and check that we switch to non-major syncing. net.peers.remove(2); net.peers.remove(0); - runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { + futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(0).is_major_syncing() { Poll::Pending } else { Poll::Ready(()) } - })); + }) + .await; } -#[test] -fn sync_from_two_peers_works() { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn sync_from_two_peers_works() { sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - let mut net = TestNet::new(runtime.handle().clone(), 3); + let mut net = TestNet::new(3); net.peer(1).push_blocks(100, false); net.peer(2).push_blocks(100, false); - runtime.block_on(net.wait_until_sync()); + net.run_until_sync().await; let peer1 = &net.peers()[1]; assert!(net.peers()[0].blockchain_canon_equals(peer1)); assert!(!net.peer(0).is_major_syncing()); } -#[test] -fn sync_from_two_peers_with_ancestry_search_works() { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn sync_from_two_peers_with_ancestry_search_works() { sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - let mut net = TestNet::new(runtime.handle().clone(), 3); + let mut net = TestNet::new(3); net.peer(0).push_blocks(10, true); net.peer(1).push_blocks(100, false); net.peer(2).push_blocks(100, false); - runtime.block_on(net.wait_until_sync()); + net.run_until_sync().await; let peer1 = &net.peers()[1]; assert!(net.peers()[0].blockchain_canon_equals(peer1)); } -#[test] -fn ancestry_search_works_when_backoff_is_one() { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn ancestry_search_works_when_backoff_is_one() { sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - let mut net = TestNet::new(runtime.handle().clone(), 3); + let mut net = TestNet::new(3); net.peer(0).push_blocks(1, false); net.peer(1).push_blocks(2, false); net.peer(2).push_blocks(2, false); - runtime.block_on(net.wait_until_sync()); + net.run_until_sync().await; let peer1 = &net.peers()[1]; assert!(net.peers()[0].blockchain_canon_equals(peer1)); } -#[test] -fn ancestry_search_works_when_ancestor_is_genesis() { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn ancestry_search_works_when_ancestor_is_genesis() { sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - let mut net = TestNet::new(runtime.handle().clone(), 3); + let mut net = TestNet::new(3); net.peer(0).push_blocks(13, true); net.peer(1).push_blocks(100, false); net.peer(2).push_blocks(100, false); - runtime.block_on(net.wait_until_sync()); + net.run_until_sync().await; let peer1 = &net.peers()[1]; assert!(net.peers()[0].blockchain_canon_equals(peer1)); } -#[test] -fn ancestry_search_works_when_common_is_one() { - test_ancestor_search_when_common_is(1); +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn ancestry_search_works_when_common_is_one() { + test_ancestor_search_when_common_is(1).await; } -#[test] -fn ancestry_search_works_when_common_is_two() { - test_ancestor_search_when_common_is(2); +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn ancestry_search_works_when_common_is_two() { + test_ancestor_search_when_common_is(2).await; } -#[test] -fn ancestry_search_works_when_common_is_hundred() { - test_ancestor_search_when_common_is(100); +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn ancestry_search_works_when_common_is_hundred() { + test_ancestor_search_when_common_is(100).await; } -#[test] -fn sync_long_chain_works() { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn sync_long_chain_works() { sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - let mut net = TestNet::new(runtime.handle().clone(), 2); + let mut net = TestNet::new(2); net.peer(1).push_blocks(500, false); - runtime.block_on(net.wait_until_sync()); + net.run_until_sync().await; let peer1 = &net.peers()[1]; assert!(net.peers()[0].blockchain_canon_equals(peer1)); } -#[test] -fn sync_no_common_longer_chain_fails() { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn sync_no_common_longer_chain_fails() { sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - let mut net = TestNet::new(runtime.handle().clone(), 3); + let mut net = TestNet::new(3); net.peer(0).push_blocks(20, true); net.peer(1).push_blocks(20, false); - runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { + futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(0).is_major_syncing() { Poll::Pending } else { Poll::Ready(()) } - })); + }) + .await; let peer1 = &net.peers()[1]; assert!(!net.peers()[0].blockchain_canon_equals(peer1)); } -#[test] -fn sync_justifications() { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn sync_justifications() { sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - let mut net = JustificationTestNet::new(runtime.handle().clone(), 3); + let mut net = JustificationTestNet::new(3); net.peer(0).push_blocks(20, false); - runtime.block_on(net.wait_until_sync()); + net.run_until_sync().await; let backend = net.peer(0).client().as_backend(); let hashof10 = backend.blockchain().expect_block_hash_from_id(&BlockId::Number(10)).unwrap(); @@ -282,7 +277,7 @@ fn sync_justifications() { net.peer(1).request_justification(&hashof15, 15); net.peer(1).request_justification(&hashof20, 20); - runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { + futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); for hash in [hashof10, hashof15, hashof20] { @@ -299,14 +294,14 @@ fn sync_justifications() { } Poll::Ready(()) - })); + }) + .await; } -#[test] -fn sync_justifications_across_forks() { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn sync_justifications_across_forks() { sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - let mut net = JustificationTestNet::new(runtime.handle().clone(), 3); + let mut net = JustificationTestNet::new(3); // we push 5 blocks net.peer(0).push_blocks(5, false); // and then two forks 5 and 6 blocks long @@ -315,7 +310,7 @@ fn sync_justifications_across_forks() { // peer 1 will only see the longer fork. but we'll request justifications // for both and finalize the small fork instead. - runtime.block_on(net.wait_until_sync()); + net.run_until_sync().await; let just = (*b"FRNK", Vec::new()); net.peer(0).client().finalize_block(f1_best, Some(just), true).unwrap(); @@ -323,7 +318,7 @@ fn sync_justifications_across_forks() { net.peer(1).request_justification(&f1_best, 10); net.peer(1).request_justification(&f2_best, 11); - runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { + futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(0).client().justifications(f1_best).unwrap() == @@ -335,14 +330,14 @@ fn sync_justifications_across_forks() { } else { Poll::Pending } - })); + }) + .await; } -#[test] -fn sync_after_fork_works() { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn sync_after_fork_works() { sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - let mut net = TestNet::new(runtime.handle().clone(), 3); + let mut net = TestNet::new(3); net.peer(0).push_blocks(30, false); net.peer(1).push_blocks(30, false); net.peer(2).push_blocks(30, false); @@ -355,25 +350,24 @@ fn sync_after_fork_works() { net.peer(2).push_blocks(1, false); // peer 1 has the best chain - runtime.block_on(net.wait_until_sync()); + net.run_until_sync().await; let peer1 = &net.peers()[1]; assert!(net.peers()[0].blockchain_canon_equals(peer1)); (net.peers()[1].blockchain_canon_equals(peer1)); (net.peers()[2].blockchain_canon_equals(peer1)); } -#[test] -fn syncs_all_forks() { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn syncs_all_forks() { sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - let mut net = TestNet::new(runtime.handle().clone(), 4); + let mut net = TestNet::new(4); net.peer(0).push_blocks(2, false); net.peer(1).push_blocks(2, false); let b1 = net.peer(0).push_blocks(2, true); let b2 = net.peer(1).push_blocks(4, false); - runtime.block_on(net.wait_until_sync()); + net.run_until_sync().await; // Check that all peers have all of the branches. assert!(net.peer(0).has_block(b1)); assert!(net.peer(0).has_block(b2)); @@ -381,16 +375,15 @@ fn syncs_all_forks() { assert!(net.peer(1).has_block(b2)); } -#[test] -fn own_blocks_are_announced() { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn own_blocks_are_announced() { sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - let mut net = TestNet::new(runtime.handle().clone(), 3); - runtime.block_on(net.wait_until_sync()); // connect'em + let mut net = TestNet::new(3); + net.run_until_sync().await; // connect'em net.peer(0) .generate_blocks(1, BlockOrigin::Own, |builder| builder.build().unwrap().block); - runtime.block_on(net.wait_until_sync()); + net.run_until_sync().await; assert_eq!(net.peer(0).client.info().best_number, 1); assert_eq!(net.peer(1).client.info().best_number, 1); @@ -399,11 +392,10 @@ fn own_blocks_are_announced() { (net.peers()[2].blockchain_canon_equals(peer0)); } -#[test] -fn can_sync_small_non_best_forks() { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn can_sync_small_non_best_forks() { sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - let mut net = TestNet::new(runtime.handle().clone(), 2); + let mut net = TestNet::new(2); net.peer(0).push_blocks(30, false); net.peer(1).push_blocks(30, false); @@ -421,14 +413,15 @@ fn can_sync_small_non_best_forks() { assert!(net.peer(1).client().header(&BlockId::Hash(small_hash)).unwrap().is_none()); // poll until the two nodes connect, otherwise announcing the block will not work - runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { + futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(0).num_peers() == 0 { Poll::Pending } else { Poll::Ready(()) } - })); + }) + .await; // synchronization: 0 synced to longer chain and 1 didn't sync to small chain. @@ -441,7 +434,7 @@ fn can_sync_small_non_best_forks() { // after announcing, peer 1 downloads the block. - runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { + futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); assert!(net.peer(0).client().header(&BlockId::Hash(small_hash)).unwrap().is_some()); @@ -449,29 +442,30 @@ fn can_sync_small_non_best_forks() { return Poll::Pending } Poll::Ready(()) - })); - runtime.block_on(net.wait_until_sync()); + }) + .await; + net.run_until_sync().await; let another_fork = net.peer(0).push_blocks_at(BlockId::Number(35), 2, true); net.peer(0).announce_block(another_fork, None); - runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { + futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(1).client().header(&BlockId::Hash(another_fork)).unwrap().is_none() { return Poll::Pending } Poll::Ready(()) - })); + }) + .await; } -#[test] -fn can_sync_forks_ahead_of_the_best_chain() { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn can_sync_forks_ahead_of_the_best_chain() { sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - let mut net = TestNet::new(runtime.handle().clone(), 2); + let mut net = TestNet::new(2); net.peer(0).push_blocks(1, false); net.peer(1).push_blocks(1, false); - runtime.block_on(net.wait_until_connected()); + net.run_until_connected().await; // Peer 0 is on 2-block fork which is announced with is_best=false let fork_hash = net.peer(0).generate_blocks_with_fork_choice( 2, @@ -486,21 +480,21 @@ fn can_sync_forks_ahead_of_the_best_chain() { assert_eq!(net.peer(1).client().info().best_number, 2); // after announcing, peer 1 downloads the block. - runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { + futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(1).client().header(&BlockId::Hash(fork_hash)).unwrap().is_none() { return Poll::Pending } Poll::Ready(()) - })); + }) + .await; } -#[test] -fn can_sync_explicit_forks() { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn can_sync_explicit_forks() { sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - let mut net = TestNet::new(runtime.handle().clone(), 2); + let mut net = TestNet::new(2); net.peer(0).push_blocks(30, false); net.peer(1).push_blocks(30, false); @@ -519,14 +513,15 @@ fn can_sync_explicit_forks() { assert!(net.peer(1).client().header(&BlockId::Hash(small_hash)).unwrap().is_none()); // poll until the two nodes connect, otherwise announcing the block will not work - runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { + futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(0).num_peers() == 0 || net.peer(1).num_peers() == 0 { Poll::Pending } else { Poll::Ready(()) } - })); + }) + .await; // synchronization: 0 synced to longer chain and 1 didn't sync to small chain. @@ -540,7 +535,7 @@ fn can_sync_explicit_forks() { net.peer(1).set_sync_fork_request(vec![first_peer_id], small_hash, small_number); // peer 1 downloads the block. - runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { + futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); assert!(net.peer(0).client().header(&BlockId::Hash(small_hash)).unwrap().is_some()); @@ -548,14 +543,14 @@ fn can_sync_explicit_forks() { return Poll::Pending } Poll::Ready(()) - })); + }) + .await; } -#[test] -fn syncs_header_only_forks() { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn syncs_header_only_forks() { sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - let mut net = TestNet::new(runtime.handle().clone(), 0); + let mut net = TestNet::new(0); net.add_full_peer_with_config(Default::default()); net.add_full_peer_with_config(FullPeerConfig { blocks_pruning: Some(3), ..Default::default() }); net.peer(0).push_blocks(2, false); @@ -567,19 +562,18 @@ fn syncs_header_only_forks() { // Peer 1 will sync the small fork even though common block state is missing while !net.peer(1).has_block(small_hash) { - runtime.block_on(net.wait_until_idle()); + net.run_until_idle().await; } - runtime.block_on(net.wait_until_sync()); + net.run_until_sync().await; assert_eq!(net.peer(0).client().info().best_hash, net.peer(1).client().info().best_hash); assert_ne!(small_hash, net.peer(0).client().info().best_hash); } -#[test] -fn does_not_sync_announced_old_best_block() { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn does_not_sync_announced_old_best_block() { sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - let mut net = TestNet::new(runtime.handle().clone(), 3); + let mut net = TestNet::new(3); let old_hash = net.peer(0).push_blocks(1, false); let old_hash_with_parent = net.peer(0).push_blocks(1, false); @@ -587,92 +581,93 @@ fn does_not_sync_announced_old_best_block() { net.peer(1).push_blocks(20, true); net.peer(0).announce_block(old_hash, None); - runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { + futures::future::poll_fn::<(), _>(|cx| { // poll once to import announcement net.poll(cx); Poll::Ready(()) - })); + }) + .await; assert!(!net.peer(1).is_major_syncing()); net.peer(0).announce_block(old_hash_with_parent, None); - runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { + futures::future::poll_fn::<(), _>(|cx| { // poll once to import announcement net.poll(cx); Poll::Ready(()) - })); + }) + .await; assert!(!net.peer(1).is_major_syncing()); } -#[test] -fn full_sync_requires_block_body() { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn full_sync_requires_block_body() { // Check that we don't sync headers-only in full mode. sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - let mut net = TestNet::new(runtime.handle().clone(), 2); + let mut net = TestNet::new(2); net.peer(0).push_headers(1); // Wait for nodes to connect - runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { + futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(0).num_peers() == 0 || net.peer(1).num_peers() == 0 { Poll::Pending } else { Poll::Ready(()) } - })); - runtime.block_on(net.wait_until_idle()); + }) + .await; + net.run_until_idle().await; assert_eq!(net.peer(1).client.info().best_number, 0); } -#[test] -fn imports_stale_once() { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn imports_stale_once() { sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - fn import_with_announce(runtime: &Runtime, net: &mut TestNet, hash: H256) { + async fn import_with_announce(net: &mut TestNet, hash: H256) { // Announce twice net.peer(0).announce_block(hash, None); net.peer(0).announce_block(hash, None); - runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { + futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(1).client().header(&BlockId::Hash(hash)).unwrap().is_some() { Poll::Ready(()) } else { Poll::Pending } - })); + }) + .await; } // given the network with 2 full nodes - let mut net = TestNet::new(runtime.handle().clone(), 2); + let mut net = TestNet::new(2); // let them connect to each other - runtime.block_on(net.wait_until_sync()); + net.run_until_sync().await; // check that NEW block is imported from announce message let new_hash = net.peer(0).push_blocks(1, false); - import_with_announce(&runtime, &mut net, new_hash); + import_with_announce(&mut net, new_hash).await; assert_eq!(net.peer(1).num_downloaded_blocks(), 1); // check that KNOWN STALE block is imported from announce message let known_stale_hash = net.peer(0).push_blocks_at(BlockId::Number(0), 1, true); - import_with_announce(&runtime, &mut net, known_stale_hash); + import_with_announce(&mut net, known_stale_hash).await; assert_eq!(net.peer(1).num_downloaded_blocks(), 2); } -#[test] -fn can_sync_to_peers_with_wrong_common_block() { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn can_sync_to_peers_with_wrong_common_block() { sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - let mut net = TestNet::new(runtime.handle().clone(), 2); + let mut net = TestNet::new(2); net.peer(0).push_blocks(2, true); net.peer(1).push_blocks(2, true); let fork_hash = net.peer(0).push_blocks_at(BlockId::Number(0), 2, false); net.peer(1).push_blocks_at(BlockId::Number(0), 2, false); // wait for connection - runtime.block_on(net.wait_until_connected()); + net.run_until_connected().await; // both peers re-org to the same fork without notifying each other let just = Some((*b"FRNK", Vec::new())); @@ -680,7 +675,7 @@ fn can_sync_to_peers_with_wrong_common_block() { net.peer(1).client().finalize_block(fork_hash, just, true).unwrap(); let final_hash = net.peer(0).push_blocks(1, false); - runtime.block_on(net.wait_until_sync()); + net.run_until_sync().await; assert!(net.peer(1).has_block(final_hash)); } @@ -722,11 +717,10 @@ impl BlockAnnounceValidator for FailingBlockAnnounceValidator { } } -#[test] -fn sync_blocks_when_block_announce_validator_says_it_is_new_best() { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn sync_blocks_when_block_announce_validator_says_it_is_new_best() { sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - let mut net = TestNet::new(runtime.handle().clone(), 0); + let mut net = TestNet::new(0); net.add_full_peer_with_config(Default::default()); net.add_full_peer_with_config(Default::default()); net.add_full_peer_with_config(FullPeerConfig { @@ -734,7 +728,7 @@ fn sync_blocks_when_block_announce_validator_says_it_is_new_best() { ..Default::default() }); - runtime.block_on(net.wait_until_connected()); + net.run_until_connected().await; // Add blocks but don't set them as best let block_hash = net.peer(0).generate_blocks_with_fork_choice( @@ -745,7 +739,7 @@ fn sync_blocks_when_block_announce_validator_says_it_is_new_best() { ); while !net.peer(2).has_block(block_hash) { - runtime.block_on(net.wait_until_idle()); + net.run_until_idle().await; } } @@ -767,18 +761,17 @@ impl BlockAnnounceValidator for DeferredBlockAnnounceValidator { } } -#[test] -fn wait_until_deferred_block_announce_validation_is_ready() { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn wait_until_deferred_block_announce_validation_is_ready() { sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - let mut net = TestNet::new(runtime.handle().clone(), 0); + let mut net = TestNet::new(0); net.add_full_peer_with_config(Default::default()); net.add_full_peer_with_config(FullPeerConfig { block_announce_validator: Some(Box::new(NewBestBlockAnnounceValidator)), ..Default::default() }); - runtime.block_on(net.wait_until_connected()); + net.run_until_connected().await; // Add blocks but don't set them as best let block_hash = net.peer(0).generate_blocks_with_fork_choice( @@ -789,17 +782,16 @@ fn wait_until_deferred_block_announce_validation_is_ready() { ); while !net.peer(1).has_block(block_hash) { - runtime.block_on(net.wait_until_idle()); + net.run_until_idle().await; } } /// When we don't inform the sync protocol about the best block, a node will not sync from us as the /// handshake is not does not contain our best block. -#[test] -fn sync_to_tip_requires_that_sync_protocol_is_informed_about_best_block() { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn sync_to_tip_requires_that_sync_protocol_is_informed_about_best_block() { sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - let mut net = TestNet::new(runtime.handle().clone(), 1); + let mut net = TestNet::new(1); // Produce some blocks let block_hash = @@ -807,18 +799,16 @@ fn sync_to_tip_requires_that_sync_protocol_is_informed_about_best_block() { .push_blocks_at_without_informing_sync(BlockId::Number(0), 3, true, true); // Add a node and wait until they are connected - runtime.block_on(async { - net.add_full_peer_with_config(Default::default()); - net.wait_until_connected().await; - net.wait_until_idle().await; - }); + net.add_full_peer_with_config(Default::default()); + net.run_until_connected().await; + net.run_until_idle().await; // The peer should not have synced the block. assert!(!net.peer(1).has_block(block_hash)); // Make sync protocol aware of the best block net.peer(0).network_service().new_best_block_imported(block_hash, 3); - runtime.block_on(net.wait_until_idle()); + net.run_until_idle().await; // Connect another node that should now sync to the tip net.add_full_peer_with_config(FullPeerConfig { @@ -826,14 +816,15 @@ fn sync_to_tip_requires_that_sync_protocol_is_informed_about_best_block() { ..Default::default() }); - runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { + futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(2).has_block(block_hash) { Poll::Ready(()) } else { Poll::Pending } - })); + }) + .await; // However peer 1 should still not have the block. assert!(!net.peer(1).has_block(block_hash)); @@ -841,12 +832,11 @@ fn sync_to_tip_requires_that_sync_protocol_is_informed_about_best_block() { /// Ensures that if we as a syncing node sync to the tip while we are connected to another peer /// that is currently also doing a major sync. -#[test] -fn sync_to_tip_when_we_sync_together_with_multiple_peers() { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn sync_to_tip_when_we_sync_together_with_multiple_peers() { sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - let mut net = TestNet::new(runtime.handle().clone(), 3); + let mut net = TestNet::new(3); let block_hash = net.peer(0) @@ -855,10 +845,8 @@ fn sync_to_tip_when_we_sync_together_with_multiple_peers() { net.peer(1) .push_blocks_at_without_informing_sync(BlockId::Number(0), 5_000, false, false); - runtime.block_on(async { - net.wait_until_connected().await; - net.wait_until_idle().await; - }); + net.run_until_connected().await; + net.run_until_idle().await; assert!(!net.peer(2).has_block(block_hash)); @@ -866,14 +854,14 @@ fn sync_to_tip_when_we_sync_together_with_multiple_peers() { net.peer(0).network_service().announce_block(block_hash, None); while !net.peer(2).has_block(block_hash) && !net.peer(1).has_block(block_hash) { - runtime.block_on(net.wait_until_idle()); + net.run_until_idle().await; } } /// Ensures that when we receive a block announcement with some data attached, that we propagate /// this data when reannouncing the block. -#[test] -fn block_announce_data_is_propagated() { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn block_announce_data_is_propagated() { struct TestBlockAnnounceValidator; impl BlockAnnounceValidator for TestBlockAnnounceValidator { @@ -897,8 +885,7 @@ fn block_announce_data_is_propagated() { } sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - let mut net = TestNet::new(runtime.handle().clone(), 1); + let mut net = TestNet::new(1); net.add_full_peer_with_config(FullPeerConfig { block_announce_validator: Some(Box::new(TestBlockAnnounceValidator)), @@ -912,7 +899,7 @@ fn block_announce_data_is_propagated() { }); // Wait until peer 1 is connected to both nodes. - runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { + futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(1).num_peers() == 2 && net.peer(0).num_peers() == 1 && @@ -922,18 +909,19 @@ fn block_announce_data_is_propagated() { } else { Poll::Pending } - })); + }) + .await; let block_hash = net.peer(0).push_blocks_at_without_announcing(BlockId::Number(0), 1, true); net.peer(0).announce_block(block_hash, Some(vec![137])); while !net.peer(1).has_block(block_hash) || !net.peer(2).has_block(block_hash) { - runtime.block_on(net.wait_until_idle()); + net.run_until_idle().await; } } -#[test] -fn continue_to_sync_after_some_block_announcement_verifications_failed() { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn continue_to_sync_after_some_block_announcement_verifications_failed() { struct TestBlockAnnounceValidator; impl BlockAnnounceValidator for TestBlockAnnounceValidator { @@ -958,22 +946,19 @@ fn continue_to_sync_after_some_block_announcement_verifications_failed() { } sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - let mut net = TestNet::new(runtime.handle().clone(), 1); + let mut net = TestNet::new(1); net.add_full_peer_with_config(FullPeerConfig { block_announce_validator: Some(Box::new(TestBlockAnnounceValidator)), ..Default::default() }); - runtime.block_on(async { - net.wait_until_connected().await; - net.wait_until_idle().await; - }); + net.run_until_connected().await; + net.run_until_idle().await; let block_hash = net.peer(0).push_blocks(500, true); - runtime.block_on(net.wait_until_sync()); + net.run_until_sync().await; assert!(net.peer(1).has_block(block_hash)); } @@ -981,13 +966,12 @@ fn continue_to_sync_after_some_block_announcement_verifications_failed() { /// this peer if the request was successful. In the case of a justification request for example, /// we ask our peers multiple times until we got the requested justification. This test ensures that /// asking for the same justification multiple times doesn't ban a peer. -#[test] -fn multiple_requests_are_accepted_as_long_as_they_are_not_fulfilled() { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn multiple_requests_are_accepted_as_long_as_they_are_not_fulfilled() { sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - let mut net = JustificationTestNet::new(runtime.handle().clone(), 2); + let mut net = JustificationTestNet::new(2); net.peer(0).push_blocks(10, false); - runtime.block_on(net.wait_until_sync()); + net.run_until_sync().await; let hashof10 = net.peer(1).client().header(&BlockId::Number(10)).unwrap().unwrap().hash(); @@ -1002,9 +986,9 @@ fn multiple_requests_are_accepted_as_long_as_they_are_not_fulfilled() { for _ in 0..5 { // We need to sleep 10 seconds as this is the time we wait between sending a new // justification request. - std::thread::sleep(std::time::Duration::from_secs(10)); + tokio::time::sleep(tokio::time::Duration::from_secs(10)).await; net.peer(0).push_blocks(1, false); - runtime.block_on(net.wait_until_sync()); + net.run_until_sync().await; assert_eq!(1, net.peer(0).num_peers()); } @@ -1021,7 +1005,7 @@ fn multiple_requests_are_accepted_as_long_as_they_are_not_fulfilled() { .finalize_block(hashof10, Some((*b"FRNK", Vec::new())), true) .unwrap(); - runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { + futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(1).client().justifications(hashof10).unwrap() != @@ -1031,47 +1015,47 @@ fn multiple_requests_are_accepted_as_long_as_they_are_not_fulfilled() { } Poll::Ready(()) - })); + }) + .await; } -#[test] -fn syncs_all_forks_from_single_peer() { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn syncs_all_forks_from_single_peer() { sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - let mut net = TestNet::new(runtime.handle().clone(), 2); + let mut net = TestNet::new(2); net.peer(0).push_blocks(10, false); net.peer(1).push_blocks(10, false); // poll until the two nodes connect, otherwise announcing the block will not work - runtime.block_on(net.wait_until_connected()); + net.run_until_connected().await; // Peer 0 produces new blocks and announces. let branch1 = net.peer(0).push_blocks_at(BlockId::Number(10), 2, true); // Wait till peer 1 starts downloading - runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { + futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(1).network().best_seen_block() != Some(12) { return Poll::Pending } Poll::Ready(()) - })); + }) + .await; // Peer 0 produces and announces another fork let branch2 = net.peer(0).push_blocks_at(BlockId::Number(10), 2, false); - runtime.block_on(net.wait_until_sync()); + net.run_until_sync().await; // Peer 1 should have both branches, assert!(net.peer(1).client().header(&BlockId::Hash(branch1)).unwrap().is_some()); assert!(net.peer(1).client().header(&BlockId::Hash(branch2)).unwrap().is_some()); } -#[test] -fn syncs_after_missing_announcement() { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn syncs_after_missing_announcement() { sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - let mut net = TestNet::new(runtime.handle().clone(), 0); + let mut net = TestNet::new(0); net.add_full_peer_with_config(Default::default()); // Set peer 1 to ignore announcement net.add_full_peer_with_config(FullPeerConfig { @@ -1081,23 +1065,22 @@ fn syncs_after_missing_announcement() { net.peer(0).push_blocks(10, false); net.peer(1).push_blocks(10, false); - runtime.block_on(net.wait_until_connected()); + net.run_until_connected().await; // Peer 0 produces a new block and announces. Peer 1 ignores announcement. net.peer(0).push_blocks_at(BlockId::Number(10), 1, false); // Peer 0 produces another block and announces. let final_block = net.peer(0).push_blocks_at(BlockId::Number(11), 1, false); net.peer(1).push_blocks_at(BlockId::Number(10), 1, true); - runtime.block_on(net.wait_until_sync()); + net.run_until_sync().await; assert!(net.peer(1).client().header(&BlockId::Hash(final_block)).unwrap().is_some()); } -#[test] -fn syncs_state() { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn syncs_state() { sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); for skip_proofs in &[false, true] { - let mut net = TestNet::new(runtime.handle().clone(), 0); + let mut net = TestNet::new(0); let mut genesis_storage: sp_core::storage::Storage = Default::default(); genesis_storage.top.insert(b"additional_key".to_vec(), vec![1]); let mut child_data: std::collections::BTreeMap, Vec> = Default::default(); @@ -1138,7 +1121,7 @@ fn syncs_state() { net.add_full_peer_with_config(config_two); net.peer(0).push_blocks(64, false); // Wait for peer 1 to sync header chain. - runtime.block_on(net.wait_until_sync()); + net.run_until_sync().await; assert!(!net.peer(1).client().has_state_at(&BlockId::Number(64))); let just = (*b"FRNK", Vec::new()); @@ -1151,33 +1134,34 @@ fn syncs_state() { .unwrap(); net.peer(1).client().finalize_block(hashof60, Some(just), true).unwrap(); // Wait for state sync. - runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { + futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(1).client.info().finalized_state.is_some() { Poll::Ready(()) } else { Poll::Pending } - })); + }) + .await; assert!(!net.peer(1).client().has_state_at(&BlockId::Number(64))); // Wait for the rest of the states to be imported. - runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { + futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(1).client().has_state_at(&BlockId::Number(64)) { Poll::Ready(()) } else { Poll::Pending } - })); + }) + .await; } } -#[test] -fn syncs_indexed_blocks() { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn syncs_indexed_blocks() { use sp_runtime::traits::Hash; sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - let mut net = TestNet::new(runtime.handle().clone(), 0); + let mut net = TestNet::new(0); let mut n: u64 = 0; net.add_full_peer_with_config(FullPeerConfig { storage_chain: true, ..Default::default() }); net.add_full_peer_with_config(FullPeerConfig { @@ -1216,7 +1200,7 @@ fn syncs_indexed_blocks() { .unwrap() .is_none()); - runtime.block_on(net.wait_until_sync()); + net.run_until_sync().await; assert!(net .peer(1) .client() @@ -1226,11 +1210,10 @@ fn syncs_indexed_blocks() { .is_some()); } -#[test] -fn warp_sync() { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn warp_sync() { sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - let mut net = TestNet::new(runtime.handle().clone(), 0); + let mut net = TestNet::new(0); // Create 3 synced peers and 1 peer trying to warp sync. net.add_full_peer_with_config(Default::default()); net.add_full_peer_with_config(Default::default()); @@ -1244,30 +1227,30 @@ fn warp_sync() { net.peer(1).push_blocks(64, false); net.peer(2).push_blocks(64, false); // Wait for peer 1 to sync state. - runtime.block_on(net.wait_until_sync()); + net.run_until_sync().await; assert!(!net.peer(3).client().has_state_at(&BlockId::Number(1))); assert!(net.peer(3).client().has_state_at(&BlockId::Number(64))); // Wait for peer 1 download block history - runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { + futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(3).has_body(gap_end) && net.peer(3).has_body(target) { Poll::Ready(()) } else { Poll::Pending } - })); + }) + .await; } -#[test] -fn syncs_huge_blocks() { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn syncs_huge_blocks() { use sp_core::storage::well_known_keys::HEAP_PAGES; use sp_runtime::codec::Encode; use substrate_test_runtime_client::BlockBuilderExt; sp_tracing::try_init_simple(); - let runtime = Runtime::new().unwrap(); - let mut net = TestNet::new(runtime.handle().clone(), 2); + let mut net = TestNet::new(2); // Increase heap space for bigger blocks. net.peer(0).generate_blocks(1, BlockOrigin::Own, |mut builder| { @@ -1284,7 +1267,7 @@ fn syncs_huge_blocks() { builder.build().unwrap().block }); - runtime.block_on(net.wait_until_sync()); + net.run_until_sync().await; assert_eq!(net.peer(0).client.info().best_number, 33); assert_eq!(net.peer(1).client.info().best_number, 33); } From 85f660fded10af9ee80d5acb6efcc16dc59fffd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 16 Dec 2022 15:01:31 +0100 Subject: [PATCH 050/127] Fix missing block number issue on forced canonicalization (#12949) * Fix missing block number issue on forced canonicalization There is this issue about missing block numbers on forced canonicalization. I looked over the code now 10000 times and there are possible ways this can be triggered, but I don't really know how this is triggered. So, this pr is going to solve the symptom and not the cause. The block number to hash mapping is set when we import a new best block. Forced canonicalization will now stop at the best block and it will canonicalize the other blocks later when the best block moved. As the error reports indicated that this issue mainly happened on major sync, there should not be any forks, so not doing the canonicalization directly shouldn't be that harmful. All known implementations should import all blocks as best block on major sync anyway (I mean somewhere there is the bug, but I didn't yet found it). I will also do some changes to Cumulus around some potential culprit for this issue. Closes: https://github.com/paritytech/substrate/issues/12613 * Add some docs * Fix fix * Review comments * Review comments --- client/db/src/lib.rs | 187 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 158 insertions(+), 29 deletions(-) diff --git a/client/db/src/lib.rs b/client/db/src/lib.rs index 4452d5dcb3..7c2635f843 100644 --- a/client/db/src/lib.rs +++ b/client/db/src/lib.rs @@ -1213,9 +1213,8 @@ impl Backend { let meta = self.blockchain.meta.read(); - if meta.best_number > best_number && - (meta.best_number - best_number).saturated_into::() > - self.canonicalization_delay + if meta.best_number.saturating_sub(best_number).saturated_into::() > + self.canonicalization_delay { return Err(sp_blockchain::Error::SetHeadTooOld) } @@ -1316,42 +1315,43 @@ impl Backend { fn force_delayed_canonicalize( &self, transaction: &mut Transaction, - hash: Block::Hash, - number: NumberFor, ) -> ClientResult<()> { - let number_u64 = number.saturated_into::(); - if number_u64 > self.canonicalization_delay { - let new_canonical = number_u64 - self.canonicalization_delay; + let best_canonical = || self.storage.state_db.best_canonical().unwrap_or(0); + let info = self.blockchain.info(); + let best_number: u64 = self.blockchain.info().best_number.saturated_into(); - if new_canonical <= self.storage.state_db.best_canonical().unwrap_or(0) { - return Ok(()) - } - let hash = if new_canonical == number_u64 { - hash - } else { - sc_client_api::blockchain::HeaderBackend::hash( - &self.blockchain, - new_canonical.saturated_into(), - )? - .ok_or_else(|| { - sp_blockchain::Error::Backend(format!( - "Can't canonicalize missing block number #{} when importing {:?} (#{})", - new_canonical, hash, number, - )) - })? - }; - if !sc_client_api::Backend::have_state_at(self, hash, new_canonical.saturated_into()) { + while best_number.saturating_sub(best_canonical()) > self.canonicalization_delay { + let to_canonicalize = best_canonical() + 1; + + let hash_to_canonicalize = sc_client_api::blockchain::HeaderBackend::hash( + &self.blockchain, + to_canonicalize.saturated_into(), + )? + .ok_or_else(|| { + let best_hash = info.best_hash; + + sp_blockchain::Error::Backend(format!( + "Can't canonicalize missing block number #{to_canonicalize} when for best block {best_hash:?} (#{best_number})", + )) + })?; + + if !sc_client_api::Backend::have_state_at( + self, + hash_to_canonicalize, + to_canonicalize.saturated_into(), + ) { return Ok(()) } - trace!(target: "db", "Canonicalize block #{} ({:?})", new_canonical, hash); - let commit = self.storage.state_db.canonicalize_block(&hash).map_err( + trace!(target: "db", "Canonicalize block #{} ({:?})", to_canonicalize, hash_to_canonicalize); + let commit = self.storage.state_db.canonicalize_block(&hash_to_canonicalize).map_err( sp_blockchain::Error::from_state_db::< sc_state_db::Error, >, )?; apply_state_commit(transaction, commit); } + Ok(()) } @@ -1559,7 +1559,7 @@ impl Backend { )?; } else { // canonicalize blocks which are old enough, regardless of finality. - self.force_delayed_canonicalize(&mut transaction, hash, *header.number())? + self.force_delayed_canonicalize(&mut transaction)? } if !existing_header { @@ -2759,6 +2759,33 @@ pub(crate) mod tests { .into(); let hash = header.hash(); + op.set_block_data(header, Some(vec![]), None, None, NewBlockState::Best) + .unwrap(); + + backend.commit_operation(op).unwrap(); + hash + }; + + let hashof4 = { + let mut op = backend.begin_operation().unwrap(); + backend.begin_state_operation(&mut op, hashof3).unwrap(); + let mut header = Header { + number: 4, + parent_hash: hashof3, + state_root: Default::default(), + digest: Default::default(), + extrinsics_root: Default::default(), + }; + + let storage: Vec<(_, _)> = vec![]; + + header.state_root = op + .old_state + .storage_root(storage.iter().cloned().map(|(x, y)| (x, Some(y))), state_version) + .0 + .into(); + let hash = header.hash(); + op.set_block_data(header, Some(vec![]), None, None, NewBlockState::Best) .unwrap(); @@ -2774,6 +2801,7 @@ pub(crate) mod tests { backend.finalize_block(hashof1, None).unwrap(); backend.finalize_block(hashof2, None).unwrap(); backend.finalize_block(hashof3, None).unwrap(); + backend.finalize_block(hashof4, None).unwrap(); assert!(backend .storage .db @@ -3801,4 +3829,105 @@ pub(crate) mod tests { assert_eq!(backend.blockchain().leaves().unwrap(), vec![block2]); assert_eq!(backend.blockchain().info().best_hash, block2); } + + #[test] + fn force_delayed_canonicalize_waiting_for_blocks_to_be_finalized() { + let backend = Backend::::new_test(10, 1); + + let genesis = + insert_block(&backend, 0, Default::default(), None, Default::default(), vec![], None) + .unwrap(); + + let block1 = { + let mut op = backend.begin_operation().unwrap(); + backend.begin_state_operation(&mut op, genesis).unwrap(); + let header = Header { + number: 1, + parent_hash: genesis, + state_root: BlakeTwo256::trie_root(Vec::new(), StateVersion::V1), + digest: Default::default(), + extrinsics_root: Default::default(), + }; + + op.set_block_data(header.clone(), Some(Vec::new()), None, None, NewBlockState::Normal) + .unwrap(); + + backend.commit_operation(op).unwrap(); + + header.hash() + }; + + assert_eq!(0, backend.storage.state_db.best_canonical().unwrap()); + + // This should not trigger any forced canonicalization as we didn't have imported any best + // block by now. + let block2 = { + let mut op = backend.begin_operation().unwrap(); + backend.begin_state_operation(&mut op, block1).unwrap(); + let header = Header { + number: 2, + parent_hash: block1, + state_root: BlakeTwo256::trie_root(Vec::new(), StateVersion::V1), + digest: Default::default(), + extrinsics_root: Default::default(), + }; + + op.set_block_data(header.clone(), Some(Vec::new()), None, None, NewBlockState::Normal) + .unwrap(); + + backend.commit_operation(op).unwrap(); + + header.hash() + }; + + assert_eq!(0, backend.storage.state_db.best_canonical().unwrap()); + + // This should also not trigger it yet, because we import a best block, but the best block + // from the POV of the db is still at `0`. + let block3 = { + let mut op = backend.begin_operation().unwrap(); + backend.begin_state_operation(&mut op, block2).unwrap(); + let header = Header { + number: 3, + parent_hash: block2, + state_root: BlakeTwo256::trie_root(Vec::new(), StateVersion::V1), + digest: Default::default(), + extrinsics_root: Default::default(), + }; + + op.set_block_data(header.clone(), Some(Vec::new()), None, None, NewBlockState::Best) + .unwrap(); + + backend.commit_operation(op).unwrap(); + + header.hash() + }; + + // Now it should kick in. + let block4 = { + let mut op = backend.begin_operation().unwrap(); + backend.begin_state_operation(&mut op, block3).unwrap(); + let header = Header { + number: 4, + parent_hash: block3, + state_root: BlakeTwo256::trie_root(Vec::new(), StateVersion::V1), + digest: Default::default(), + extrinsics_root: Default::default(), + }; + + op.set_block_data(header.clone(), Some(Vec::new()), None, None, NewBlockState::Best) + .unwrap(); + + backend.commit_operation(op).unwrap(); + + header.hash() + }; + + assert_eq!(2, backend.storage.state_db.best_canonical().unwrap()); + + assert_eq!(block1, backend.blockchain().hash(1).unwrap().unwrap()); + assert_eq!(block2, backend.blockchain().hash(2).unwrap().unwrap()); + assert_eq!(block3, backend.blockchain().hash(3).unwrap().unwrap()); + assert_eq!(block4, backend.blockchain().hash(4).unwrap().unwrap()); + } } From 0798d52690cad7db103f9d0e1eeb77f2351dfb2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 16 Dec 2022 19:52:21 +0100 Subject: [PATCH 051/127] Defensive traits: Ensure doc tests are also working without `debug_assertions` (#12952) * Defensive traits: Ensure doc tests are also working without `debug_assertions` Closes: https://github.com/paritytech/substrate/issues/12940 * Only run 'panics' tests if they will panic Signed-off-by: Oliver Tale-Yazdi Signed-off-by: Oliver Tale-Yazdi Co-authored-by: Oliver Tale-Yazdi --- frame/support/src/traits/misc.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/frame/support/src/traits/misc.rs b/frame/support/src/traits/misc.rs index 33e48faa7e..734b84ebd9 100644 --- a/frame/support/src/traits/misc.rs +++ b/frame/support/src/traits/misc.rs @@ -445,7 +445,7 @@ pub trait DefensiveMin { /// assert_eq!(4, 4_u32.defensive_min(4_u32)); /// ``` /// - /// ```should_panic + /// ```#[cfg_attr(debug_assertions, should_panic)] /// use frame_support::traits::DefensiveMin; /// // min(4, 3) panics. /// 4_u32.defensive_min(3_u32); @@ -462,7 +462,7 @@ pub trait DefensiveMin { /// assert_eq!(3, 3_u32.defensive_strict_min(4_u32)); /// ``` /// - /// ```should_panic + /// ```#[cfg_attr(debug_assertions, should_panic)] /// use frame_support::traits::DefensiveMin; /// // min(4, 4) panics. /// 4_u32.defensive_strict_min(4_u32); @@ -509,7 +509,7 @@ pub trait DefensiveMax { /// assert_eq!(4, 4_u32.defensive_max(4_u32)); /// ``` /// - /// ```should_panic + /// ```#[cfg_attr(debug_assertions, should_panic)] /// use frame_support::traits::DefensiveMax; /// // max(4, 5) panics. /// 4_u32.defensive_max(5_u32); @@ -526,7 +526,7 @@ pub trait DefensiveMax { /// assert_eq!(4, 4_u32.defensive_strict_max(3_u32)); /// ``` /// - /// ```should_panic + /// ```#[cfg_attr(debug_assertions, should_panic)] /// use frame_support::traits::DefensiveMax; /// // max(4, 4) panics. /// 4_u32.defensive_strict_max(4_u32); @@ -1353,6 +1353,7 @@ mod test { } #[test] + #[cfg(debug_assertions)] #[should_panic(expected = "Defensive failure has been triggered!: \"DefensiveMin\"")] fn defensive_min_panics() { 10_u32.defensive_min(9_u32); @@ -1365,6 +1366,7 @@ mod test { } #[test] + #[cfg(debug_assertions)] #[should_panic(expected = "Defensive failure has been triggered!: \"DefensiveMin strict\"")] fn defensive_strict_min_panics() { 9_u32.defensive_strict_min(9_u32); @@ -1377,6 +1379,7 @@ mod test { } #[test] + #[cfg(debug_assertions)] #[should_panic(expected = "Defensive failure has been triggered!: \"DefensiveMax\"")] fn defensive_max_panics() { 9_u32.defensive_max(10_u32); @@ -1389,6 +1392,7 @@ mod test { } #[test] + #[cfg(debug_assertions)] #[should_panic(expected = "Defensive failure has been triggered!: \"DefensiveMax strict\"")] fn defensive_strict_max_panics() { 9_u32.defensive_strict_max(9_u32); From 7f4bca1ee3122335a09c7cb65104deb389c2241e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Sat, 17 Dec 2022 08:25:08 +0100 Subject: [PATCH 052/127] Authority discovery: Log when we found addresses for an authority (#12960) --- client/authority-discovery/src/worker/addr_cache.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/authority-discovery/src/worker/addr_cache.rs b/client/authority-discovery/src/worker/addr_cache.rs index 19bbbf0b62..4859413a8c 100644 --- a/client/authority-discovery/src/worker/addr_cache.rs +++ b/client/authority-discovery/src/worker/addr_cache.rs @@ -69,6 +69,11 @@ impl AddrCache { ); } + log::debug!( + target: super::LOG_TARGET, + "Found addresses for authority {authority_id:?}: {addresses:?}", + ); + let old_addresses = self.authority_id_to_addresses.insert(authority_id.clone(), addresses); let old_peer_ids = addresses_to_peer_ids(&old_addresses.unwrap_or_default()); From a96cb5725407f8cc5ae7c9efc85a80548d071833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Sat, 17 Dec 2022 10:07:23 +0100 Subject: [PATCH 053/127] `frame_support::storage`: Add `StorageStreamIter` (#12721) * Save * Add some test * Yep * Move to its own file * More work * More * Finish implementation * Start resolving comments and fixing bugs * Fix all review comments * Update frame/support/src/storage/stream_iter.rs Co-authored-by: Koute * Update frame/support/src/storage/stream_iter.rs Co-authored-by: Koute * Review feedback * FMT * Okay, let's initialize the values... * Fix... Co-authored-by: Koute --- frame/support/src/storage/mod.rs | 2 + frame/support/src/storage/stream_iter.rs | 666 +++++++++++++++++++++++ frame/support/src/storage/unhashed.rs | 5 +- frame/system/src/lib.rs | 9 +- 4 files changed, 676 insertions(+), 6 deletions(-) create mode 100644 frame/support/src/storage/stream_iter.rs diff --git a/frame/support/src/storage/mod.rs b/frame/support/src/storage/mod.rs index 333f438255..8c0d6207c3 100644 --- a/frame/support/src/storage/mod.rs +++ b/frame/support/src/storage/mod.rs @@ -30,6 +30,7 @@ use sp_runtime::generic::{Digest, DigestItem}; use sp_std::{collections::btree_set::BTreeSet, marker::PhantomData, prelude::*}; pub use self::{ + stream_iter::StorageStreamIter, transactional::{ in_storage_layer, with_storage_layer, with_transaction, with_transaction_unchecked, }, @@ -47,6 +48,7 @@ pub mod generator; pub mod hashed; pub mod migration; pub mod storage_noop_guard; +mod stream_iter; pub mod transactional; pub mod types; pub mod unhashed; diff --git a/frame/support/src/storage/stream_iter.rs b/frame/support/src/storage/stream_iter.rs new file mode 100644 index 0000000000..4e25ca6ca1 --- /dev/null +++ b/frame/support/src/storage/stream_iter.rs @@ -0,0 +1,666 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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::{BoundedBTreeMap, BoundedBTreeSet, BoundedVec, WeakBoundedVec}; +use codec::Decode; +use sp_std::vec::Vec; + +/// Provides the sealed trait `StreamIter`. +mod private { + use super::*; + + /// Used as marker trait for types that support stream iteration. + pub trait StreamIter { + /// The actual iterator implementation. + type Iterator: sp_std::iter::Iterator; + + /// Create the stream iterator for the value found at `key`. + fn stream_iter(key: Vec) -> Self::Iterator; + } + + impl StreamIter for Vec { + type Iterator = ScaleContainerStreamIter; + + fn stream_iter(key: Vec) -> Self::Iterator { + ScaleContainerStreamIter::new(key) + } + } + + impl StreamIter for sp_std::collections::btree_set::BTreeSet { + type Iterator = ScaleContainerStreamIter; + + fn stream_iter(key: Vec) -> Self::Iterator { + ScaleContainerStreamIter::new(key) + } + } + + impl StreamIter + for sp_std::collections::btree_map::BTreeMap + { + type Iterator = ScaleContainerStreamIter<(K, V)>; + + fn stream_iter(key: Vec) -> Self::Iterator { + ScaleContainerStreamIter::new(key) + } + } + + impl StreamIter for BoundedVec { + type Iterator = ScaleContainerStreamIter; + + fn stream_iter(key: Vec) -> Self::Iterator { + ScaleContainerStreamIter::new(key) + } + } + + impl StreamIter for WeakBoundedVec { + type Iterator = ScaleContainerStreamIter; + + fn stream_iter(key: Vec) -> Self::Iterator { + ScaleContainerStreamIter::new(key) + } + } + + impl StreamIter for BoundedBTreeMap { + type Iterator = ScaleContainerStreamIter<(K, V)>; + + fn stream_iter(key: Vec) -> Self::Iterator { + ScaleContainerStreamIter::new(key) + } + } + + impl StreamIter for BoundedBTreeSet { + type Iterator = ScaleContainerStreamIter; + + fn stream_iter(key: Vec) -> Self::Iterator { + ScaleContainerStreamIter::new(key) + } + } +} + +/// An iterator that streams values directly from storage. +/// +/// Requires that `T` implements the sealed trait `StreamIter`. +/// +/// Instead of loading the entire `T` into memory, the iterator only loads a certain number of bytes +/// into memory to decode the next `T::Item`. The iterator implementation is allowed to have some +/// internal buffer to reduce the number of storage reads. The iterator should have an almost +/// constant memory usage over its lifetime. If at some point there is a decoding error, the +/// iterator will return `None` to signal that the iterator is finished. +pub trait StorageStreamIter { + /// Create the streaming iterator. + fn stream_iter() -> T::Iterator; +} + +impl> + StorageStreamIter for StorageValue +{ + fn stream_iter() -> T::Iterator { + T::stream_iter(Self::hashed_key().into()) + } +} + +/// A streaming iterator implementation for SCALE container types. +/// +/// SCALE container types follow the same type of encoding `Compact(len) ++ data`. +/// This type provides an [`Iterator`](sp_std::iter::Iterator) implementation that decodes +/// one item after another with each call to [`next`](Self::next). The bytes representing +/// the container are also not read at once into memory and instead being read in chunks. As long +/// as individual items are smaller than these chunks the memory usage of this iterator should +/// be constant. On decoding errors [`next`](Self::next) will return `None` to signal that the +/// iterator is finished. +pub struct ScaleContainerStreamIter { + marker: sp_std::marker::PhantomData, + input: StorageInput, + length: u32, + read: u32, +} + +impl ScaleContainerStreamIter { + /// Creates a new instance of the stream iterator. + /// + /// - `key`: Storage key of the container in the state. + /// + /// Same as [`Self::try_new`], but logs a potential error and sets the length to `0`. + pub fn new(key: Vec) -> Self { + let mut input = StorageInput::new(key); + let length = if input.exists() { + match codec::Compact::::decode(&mut input) { + Ok(length) => length.0, + Err(e) => { + // TODO #3700: error should be handleable. + log::error!( + target: "runtime::storage", + "Corrupted state at `{:?}`: failed to decode element count: {:?}", + input.key, + e, + ); + + 0 + }, + } + } else { + 0 + }; + + Self { marker: sp_std::marker::PhantomData, input, length, read: 0 } + } + + /// Creates a new instance of the stream iterator. + /// + /// - `key`: Storage key of the container in the state. + /// + /// Returns an error if the length of the container fails to decode. + pub fn new_try(key: Vec) -> Result { + let mut input = StorageInput::new(key); + let length = if input.exists() { codec::Compact::::decode(&mut input)?.0 } else { 0 }; + + Ok(Self { marker: sp_std::marker::PhantomData, input, length, read: 0 }) + } +} + +impl sp_std::iter::Iterator for ScaleContainerStreamIter { + type Item = T; + + fn next(&mut self) -> Option { + if self.read >= self.length { + return None + } + + match codec::Decode::decode(&mut self.input) { + Ok(r) => { + self.read += 1; + Some(r) + }, + Err(e) => { + log::error!( + target: "runtime::storage", + "Corrupted state at `{:?}`: failed to decode element {} (out of {} in total): {:?}", + self.input.key, + self.read, + self.length, + e, + ); + + self.read = self.length; + None + }, + } + } + + fn size_hint(&self) -> (usize, Option) { + let left = (self.length - self.read) as usize; + + (left, Some(left)) + } +} + +/// The size of the internal buffer used by [`StorageInput`]. +/// +/// This internal buffer is used to speed up implementation as reading from the +/// state for every access is too slow. +const STORAGE_INPUT_BUFFER_CAPACITY: usize = 2 * 1024; + +/// Implementation of [`codec::Input`] using [`sp_io::storage::read`]. +/// +/// Keeps an internal buffer with a size of [`STORAGE_INPUT_BUFFER_CAPACITY`]. All read accesses +/// are tried to be served by this buffer. If the buffer doesn't hold enough bytes to fullfill the +/// current read access, the buffer is re-filled from the state. A read request that is bigger than +/// the internal buffer is directly forwarded to the state to reduce the number of reads from the +/// state. +struct StorageInput { + key: Vec, + offset: u32, + total_length: u32, + exists: bool, + buffer: Vec, + buffer_pos: usize, +} + +impl StorageInput { + /// Create a new instance of the input. + /// + /// - `key`: The storage key of the storage item that this input will read. + fn new(key: Vec) -> Self { + let mut buffer = sp_std::vec![0; STORAGE_INPUT_BUFFER_CAPACITY]; + unsafe { + buffer.set_len(buffer.capacity()); + } + + let (total_length, exists) = + if let Some(total_length) = sp_io::storage::read(&key, &mut buffer, 0) { + (total_length, true) + } else { + (0, false) + }; + + if (total_length as usize) < buffer.len() { + unsafe { + buffer.set_len(total_length as usize); + } + } + + Self { total_length, offset: buffer.len() as u32, key, exists, buffer, buffer_pos: 0 } + } + + /// Fill the internal buffer from the state. + fn fill_buffer(&mut self) -> Result<(), codec::Error> { + self.buffer.copy_within(self.buffer_pos.., 0); + let present_bytes = self.buffer.len() - self.buffer_pos; + self.buffer_pos = 0; + + unsafe { + self.buffer.set_len(self.buffer.capacity()); + } + + if let Some(length_minus_offset) = + sp_io::storage::read(&self.key, &mut self.buffer[present_bytes..], self.offset) + { + let bytes_read = + sp_std::cmp::min(length_minus_offset as usize, self.buffer.len() - present_bytes); + let buffer_len = present_bytes + bytes_read; + unsafe { + self.buffer.set_len(buffer_len); + } + + self.ensure_total_length_did_not_change(length_minus_offset)?; + + self.offset += bytes_read as u32; + + Ok(()) + } else { + // The value was deleted, let's ensure we don't read anymore. + self.stop_reading(); + + Err("Value doesn't exist in the state?".into()) + } + } + + /// Returns if the value to read exists in the state. + fn exists(&self) -> bool { + self.exists + } + + /// Reads directly into the given slice `into`. + /// + /// Should be used when `into.len() > self.buffer.capacity()` to reduce the number of reads from + /// the state. + #[inline(never)] + fn read_big_item(&mut self, into: &mut [u8]) -> Result<(), codec::Error> { + let num_cached = self.buffer.len() - self.buffer_pos; + + let (out_already_read, mut out_remaining) = into.split_at_mut(num_cached); + out_already_read.copy_from_slice(&self.buffer[self.buffer_pos..]); + + self.buffer_pos = 0; + unsafe { + self.buffer.set_len(0); + } + + if let Some(length_minus_offset) = + sp_io::storage::read(&self.key, &mut out_remaining, self.offset) + { + if (length_minus_offset as usize) < out_remaining.len() { + return Err("Not enough data to fill the buffer".into()) + } + + self.ensure_total_length_did_not_change(length_minus_offset)?; + + self.offset += out_remaining.len() as u32; + + Ok(()) + } else { + // The value was deleted, let's ensure we don't read anymore. + self.stop_reading(); + + Err("Value doesn't exist in the state?".into()) + } + } + + /// Ensures that the expected total length of the value did not change. + /// + /// On error ensures that further reading is prohibited. + fn ensure_total_length_did_not_change( + &mut self, + length_minus_offset: u32, + ) -> Result<(), codec::Error> { + if self.total_length == self.offset + length_minus_offset { + Ok(()) + } else { + // The value total length changed, let's ensure we don't read anymore. + self.stop_reading(); + + Err("Storage value changed while it is being read!".into()) + } + } + + /// Ensure that we are stop reading from this value in the state. + /// + /// Should be used when there happened an unrecoverable error while reading. + fn stop_reading(&mut self) { + self.offset = self.total_length; + + self.buffer_pos = 0; + unsafe { + self.buffer.set_len(0); + } + } +} + +impl codec::Input for StorageInput { + fn remaining_len(&mut self) -> Result, codec::Error> { + Ok(Some(self.total_length.saturating_sub( + self.offset.saturating_sub((self.buffer.len() - self.buffer_pos) as u32), + ) as usize)) + } + + fn read(&mut self, into: &mut [u8]) -> Result<(), codec::Error> { + // If there is still data left to be read from the state. + if self.offset < self.total_length { + if into.len() > self.buffer.capacity() { + return self.read_big_item(into) + } else if self.buffer_pos + into.len() > self.buffer.len() { + self.fill_buffer()?; + } + } + + // Guard against `fill_buffer` not reading enough data or just not having enough data + // anymore. + if into.len() + self.buffer_pos > self.buffer.len() { + return Err("Not enough data to fill the buffer".into()) + } + + let end = self.buffer_pos + into.len(); + into.copy_from_slice(&self.buffer[self.buffer_pos..end]); + self.buffer_pos = end; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use codec::{Compact, CompactLen, Encode, Input}; + + #[crate::storage_alias] + pub type TestVecU32 = StorageValue>; + + #[crate::storage_alias] + pub type TestVecVecU8 = StorageValue>>; + + #[test] + fn remaining_len_works() { + sp_io::TestExternalities::default().execute_with(|| { + let data: Vec = vec![1, 2, 3, 4, 5]; + TestVecU32::put(&data); + + let mut input = StorageInput::new(TestVecU32::hashed_key().into()); + assert_eq!( + 5 * std::mem::size_of::() + Compact::::compact_len(&5) as usize, + input.remaining_len().ok().flatten().unwrap() + ); + + assert_eq!(5, Compact::::decode(&mut input).unwrap().0); + assert_eq!( + 5 * std::mem::size_of::(), + input.remaining_len().ok().flatten().unwrap() + ); + + for i in &data { + assert_eq!(*i, u32::decode(&mut input).unwrap()); + assert_eq!( + (5 - *i as usize) * std::mem::size_of::(), + input.remaining_len().ok().flatten().unwrap() + ); + } + + let data: Vec> = vec![ + vec![0; 20], + vec![1; STORAGE_INPUT_BUFFER_CAPACITY * 2], + vec![2; STORAGE_INPUT_BUFFER_CAPACITY * 2], + vec![3; 30], + vec![4; 30], + vec![5; STORAGE_INPUT_BUFFER_CAPACITY * 2], + vec![6; 30], + ]; + TestVecVecU8::put(&data); + + let mut input = StorageInput::new(TestVecVecU8::hashed_key().into()); + let total_data_len = data + .iter() + .map(|v| v.len() + Compact::::compact_len(&(v.len() as u32)) as usize) + .sum::(); + assert_eq!( + total_data_len + Compact::::compact_len(&(data.len() as u32)) as usize, + input.remaining_len().ok().flatten().unwrap() + ); + + assert_eq!(data.len(), Compact::::decode(&mut input).unwrap().0 as usize); + assert_eq!(total_data_len, input.remaining_len().ok().flatten().unwrap()); + + let mut remaining_len = total_data_len; + for i in data { + assert_eq!(i, Vec::::decode(&mut input).unwrap()); + + remaining_len -= i.len() + Compact::::compact_len(&(i.len() as u32)) as usize; + + assert_eq!(remaining_len, input.remaining_len().ok().flatten().unwrap()); + } + }) + } + + #[test] + fn detects_value_total_length_change() { + sp_io::TestExternalities::default().execute_with(|| { + let test_data: Vec>> = vec![ + vec![vec![0; 20], vec![1; STORAGE_INPUT_BUFFER_CAPACITY * 2]], + vec![ + vec![0; STORAGE_INPUT_BUFFER_CAPACITY - 1], + vec![1; STORAGE_INPUT_BUFFER_CAPACITY - 1], + ], + ]; + + for data in test_data { + TestVecVecU8::put(&data); + + let mut input = StorageInput::new(TestVecVecU8::hashed_key().into()); + + Compact::::decode(&mut input).unwrap(); + Vec::::decode(&mut input).unwrap(); + + TestVecVecU8::append(vec![1, 2, 3]); + + assert!(Vec::::decode(&mut input) + .unwrap_err() + .to_string() + .contains("Storage value changed while it is being read")); + + // Reading a second time should now prevent reading at all. + assert!(Vec::::decode(&mut input) + .unwrap_err() + .to_string() + .contains("Not enough data to fill the buffer")); + } + }) + } + + #[test] + fn stream_read_test() { + sp_io::TestExternalities::default().execute_with(|| { + let data: Vec = vec![1, 2, 3, 4, 5]; + TestVecU32::put(&data); + + assert_eq!(data, TestVecU32::stream_iter().collect::>()); + + let data: Vec> = vec![vec![0; 3000], vec![1; 2500]]; + TestVecVecU8::put(&data); + + assert_eq!(data, TestVecVecU8::stream_iter().collect::>()); + }) + } + + #[test] + fn reading_big_intermediate_value() { + sp_io::TestExternalities::default().execute_with(|| { + let data: Vec> = + vec![vec![0; 20], vec![1; STORAGE_INPUT_BUFFER_CAPACITY * 2], vec![2; 30]]; + TestVecVecU8::put(&data); + + assert_eq!(data, TestVecVecU8::stream_iter().collect::>()); + + let data: Vec> = vec![ + vec![0; 20], + vec![1; STORAGE_INPUT_BUFFER_CAPACITY * 2], + vec![2; STORAGE_INPUT_BUFFER_CAPACITY * 2], + vec![3; 30], + vec![4; 30], + vec![5; STORAGE_INPUT_BUFFER_CAPACITY * 2], + vec![6; 30], + ]; + TestVecVecU8::put(&data); + + assert_eq!(data, TestVecVecU8::stream_iter().collect::>()); + }) + } + + #[test] + fn reading_more_data_as_in_the_state_is_detected() { + sp_io::TestExternalities::default().execute_with(|| { + let data: Vec> = vec![vec![0; 20], vec![1; STORAGE_INPUT_BUFFER_CAPACITY * 2]]; + TestVecVecU8::put(&data); + + let mut input = StorageInput::new(TestVecVecU8::hashed_key().into()); + + Compact::::decode(&mut input).unwrap(); + + Vec::::decode(&mut input).unwrap(); + + let mut buffer = vec![0; STORAGE_INPUT_BUFFER_CAPACITY * 4]; + assert!(input + .read(&mut buffer) + .unwrap_err() + .to_string() + .contains("Not enough data to fill the buffer")); + }) + } + + #[test] + fn reading_invalid_data_from_state() { + sp_io::TestExternalities::default().execute_with(|| { + let data: Vec = vec![1, 2, 3, 4, 5]; + + let mut data_encoded = data.encode(); + data_encoded.truncate(data_encoded.len() - 2); + sp_io::storage::set(&TestVecU32::hashed_key(), &data_encoded); + assert_eq!( + data.iter().copied().take(data.len() - 1).collect::>(), + TestVecU32::stream_iter().collect::>() + ); + + let data_encoded = data.encode()[2..].to_vec(); + sp_io::storage::set(&TestVecU32::hashed_key(), &data_encoded); + assert!(TestVecU32::stream_iter().collect::>().is_empty()); + + let data: Vec> = vec![vec![0; 20], vec![1; STORAGE_INPUT_BUFFER_CAPACITY * 2]]; + let mut data_encoded = data.encode(); + data_encoded.truncate(data_encoded.len() - 100); + sp_io::storage::set(&TestVecVecU8::hashed_key(), &data_encoded); + + assert_eq!( + data.iter().cloned().take(1).collect::>(), + TestVecVecU8::stream_iter().collect::>() + ); + }) + } + + #[test] + fn reading_with_fill_buffer() { + sp_io::TestExternalities::default().execute_with(|| { + const BUFFER_SIZE: usize = 300; + // Ensure that the capacity isn't dividable by `300`. + assert!(STORAGE_INPUT_BUFFER_CAPACITY % BUFFER_SIZE != 0, "Please update buffer size"); + // Create some items where the last item is partially in the inner buffer so that + // we need to fill the buffer to read the entire item. + let data: Vec> = (0..=(STORAGE_INPUT_BUFFER_CAPACITY / BUFFER_SIZE)) + .into_iter() + .map(|i| vec![i as u8; BUFFER_SIZE]) + .collect::>>(); + TestVecVecU8::put(&data); + + assert_eq!(data, TestVecVecU8::stream_iter().collect::>()); + + let mut input = StorageInput::new(TestVecVecU8::hashed_key().into()); + + Compact::::decode(&mut input).unwrap(); + + (0..data.len() - 1).into_iter().for_each(|_| { + Vec::::decode(&mut input).unwrap(); + }); + + // Try reading a more data than there should be left. + let mut result_buffer = vec![0; BUFFER_SIZE * 2]; + assert!(input + .read(&mut result_buffer) + .unwrap_err() + .to_string() + .contains("Not enough data to fill the buffer")); + }) + } + + #[test] + fn detect_value_deleted_in_state() { + sp_io::TestExternalities::default().execute_with(|| { + let data: Vec> = vec![vec![0; 20], vec![1; STORAGE_INPUT_BUFFER_CAPACITY * 2]]; + TestVecVecU8::put(&data); + + let mut input = StorageInput::new(TestVecVecU8::hashed_key().into()); + TestVecVecU8::kill(); + + Compact::::decode(&mut input).unwrap(); + Vec::::decode(&mut input).unwrap(); + + assert!(Vec::::decode(&mut input) + .unwrap_err() + .to_string() + .contains("Value doesn't exist in the state?")); + + const BUFFER_SIZE: usize = 300; + // Ensure that the capacity isn't dividable by `300`. + assert!(STORAGE_INPUT_BUFFER_CAPACITY % BUFFER_SIZE != 0, "Please update buffer size"); + // Create some items where the last item is partially in the inner buffer so that + // we need to fill the buffer to read the entire item. + let data: Vec> = (0..=(STORAGE_INPUT_BUFFER_CAPACITY / BUFFER_SIZE)) + .into_iter() + .map(|i| vec![i as u8; BUFFER_SIZE]) + .collect::>>(); + TestVecVecU8::put(&data); + + let mut input = StorageInput::new(TestVecVecU8::hashed_key().into()); + TestVecVecU8::kill(); + + Compact::::decode(&mut input).unwrap(); + (0..data.len() - 1).into_iter().for_each(|_| { + Vec::::decode(&mut input).unwrap(); + }); + + assert!(Vec::::decode(&mut input) + .unwrap_err() + .to_string() + .contains("Value doesn't exist in the state?")); + }) + } +} diff --git a/frame/support/src/storage/unhashed.rs b/frame/support/src/storage/unhashed.rs index 089230bc9b..850e93e7d7 100644 --- a/frame/support/src/storage/unhashed.rs +++ b/frame/support/src/storage/unhashed.rs @@ -23,12 +23,13 @@ use sp_std::prelude::*; /// Return the value of the item in storage under `key`, or `None` if there is no explicit entry. pub fn get(key: &[u8]) -> Option { sp_io::storage::get(key).and_then(|val| { - Decode::decode(&mut &val[..]).map(Some).unwrap_or_else(|_| { + Decode::decode(&mut &val[..]).map(Some).unwrap_or_else(|e| { // TODO #3700: error should be handleable. log::error!( target: "runtime::storage", - "Corrupted state at {:?}", + "Corrupted state at `{:?}: {:?}`", key, + e, ); None }) diff --git a/frame/system/src/lib.rs b/frame/system/src/lib.rs index b41083538a..a77d1ca31b 100644 --- a/frame/system/src/lib.rs +++ b/frame/system/src/lib.rs @@ -88,7 +88,7 @@ use frame_support::{ extract_actual_pays_fee, extract_actual_weight, DispatchClass, DispatchInfo, DispatchResult, DispatchResultWithPostInfo, PerDispatchClass, }, - storage, + storage::{self, StorageStreamIter}, traits::{ ConstU32, Contains, EnsureOrigin, Get, HandleLifetime, OnKilledAccount, OnNewAccount, OriginTrait, PalletInfo, SortedMembers, StoredMap, TypedGet, @@ -1444,15 +1444,16 @@ impl Pallet { pub fn events() -> Vec> { // Dereferencing the events here is fine since we are not in the // memory-restricted runtime. - Self::read_events_no_consensus().into_iter().map(|e| *e).collect() + Self::read_events_no_consensus().map(|e| *e).collect() } /// Get the current events deposited by the runtime. /// /// Should only be called if you know what you are doing and outside of the runtime block /// execution else it can have a large impact on the PoV size of a block. - pub fn read_events_no_consensus() -> Vec>> { - Events::::get() + pub fn read_events_no_consensus( + ) -> impl sp_std::iter::Iterator>> { + Events::::stream_iter() } /// Set the block number to something in particular. Can be used as an alternative to From 8d11cec9713a93b9f6bee1b29fde840da5ba56e0 Mon Sep 17 00:00:00 2001 From: Qinxuan Chen Date: Mon, 19 Dec 2022 14:38:51 +0800 Subject: [PATCH 054/127] *: update `rand` to v0.8.5 (#12962) * *: update rand to v0.8.5 * *: remove useless deps from Cargo.toml * fix pallet-session-benchmarking * fix pallet-election-provider-support test * remove useless rand from dev-dependencies --- Cargo.lock | 167 ++++-------------- bin/node/bench/Cargo.toml | 2 +- client/api/Cargo.toml | 2 - client/authority-discovery/Cargo.toml | 2 +- client/beefy/Cargo.toml | 3 - client/beefy/rpc/Cargo.toml | 1 - client/chain-spec/Cargo.toml | 2 - client/cli/Cargo.toml | 4 +- client/consensus/babe/Cargo.toml | 3 - client/consensus/slots/Cargo.toml | 1 - client/db/Cargo.toml | 2 +- client/finality-grandpa/Cargo.toml | 3 +- client/finality-grandpa/rpc/Cargo.toml | 1 - client/informant/Cargo.toml | 1 - client/merkle-mountain-range/Cargo.toml | 1 - client/network/Cargo.toml | 9 +- client/network/bitswap/Cargo.toml | 1 - client/network/test/Cargo.toml | 2 +- client/network/transactions/Cargo.toml | 1 - client/offchain/Cargo.toml | 2 +- client/peerset/Cargo.toml | 2 +- client/rpc-api/Cargo.toml | 4 - client/rpc-servers/Cargo.toml | 1 - client/rpc/Cargo.toml | 5 +- client/service/Cargo.toml | 7 +- client/service/test/Cargo.toml | 2 - client/state-db/Cargo.toml | 1 - client/sysinfo/Cargo.toml | 4 +- client/telemetry/Cargo.toml | 2 +- client/telemetry/src/node.rs | 2 +- .../election-provider-multi-phase/Cargo.toml | 5 +- frame/election-provider-support/Cargo.toml | 2 +- frame/election-provider-support/src/mock.rs | 6 +- frame/election-provider-support/src/tests.rs | 2 +- frame/session/benchmarking/Cargo.toml | 2 +- primitives/arithmetic/Cargo.toml | 4 +- primitives/arithmetic/benches/bench.rs | 4 +- primitives/consensus/common/Cargo.toml | 1 - primitives/consensus/slots/Cargo.toml | 4 - primitives/core/Cargo.toml | 12 +- primitives/io/Cargo.toml | 6 - primitives/npos-elections/Cargo.toml | 2 +- primitives/runtime/Cargo.toml | 4 +- primitives/state-machine/Cargo.toml | 8 +- primitives/state-machine/src/lib.rs | 2 +- primitives/timestamp/Cargo.toml | 2 - primitives/weights/Cargo.toml | 1 - utils/frame/benchmarking-cli/Cargo.toml | 5 - utils/frame/generate-bags/Cargo.toml | 3 - utils/frame/remote-externalities/Cargo.toml | 3 - .../rpc/state-trie-migration-rpc/Cargo.toml | 2 - utils/frame/rpc/support/Cargo.toml | 1 - utils/frame/rpc/system/Cargo.toml | 2 - utils/frame/try-runtime/cli/Cargo.toml | 1 - utils/prometheus/Cargo.toml | 1 - 55 files changed, 74 insertions(+), 251 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b840cadce3..c62d71348f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -376,15 +376,12 @@ dependencies = [ "async-trait", "fnv", "futures", - "futures-timer", "log", "parity-scale-codec", "parking_lot 0.12.1", "sc-block-builder", - "sc-chain-spec", "sc-client-api", "sc-consensus", - "sc-finality-grandpa", "sc-keystore", "sc-network", "sc-network-common", @@ -425,7 +422,6 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.1", "sc-rpc", - "sc-utils", "serde", "serde_json", "sp-beefy", @@ -1904,16 +1900,13 @@ dependencies = [ "frame-system", "gethostname", "handlebars", - "hash-db", "itertools", - "kvdb", "lazy_static", "linked-hash-map", "log", - "memory-db", "parity-scale-codec", "rand 0.8.5", - "rand_pcg 0.3.1", + "rand_pcg", "sc-block-builder", "sc-cli", "sc-client-api", @@ -1923,7 +1916,6 @@ dependencies = [ "sc-sysinfo", "serde", "serde_json", - "serde_nanos", "sp-api", "sp-blockchain", "sp-core", @@ -1936,7 +1928,6 @@ dependencies = [ "sp-std", "sp-storage", "sp-trie", - "tempfile", "thiserror", "thousands", ] @@ -1965,7 +1956,7 @@ dependencies = [ "frame-support", "frame-system", "parity-scale-codec", - "rand 0.7.3", + "rand 0.8.5", "scale-info", "sp-arithmetic", "sp-core", @@ -2029,18 +2020,15 @@ dependencies = [ name = "frame-remote-externalities" version = "0.10.0-dev" dependencies = [ - "env_logger", "frame-support", "futures", "log", "pallet-elections-phragmen", "parity-scale-codec", "serde", - "serde_json", "sp-core", "sp-io", "sp-runtime", - "sp-version", "substrate-rpc-client", "tokio", "tracing-subscriber 0.3.16", @@ -2383,7 +2371,6 @@ dependencies = [ "git2", "num-format", "pallet-staking", - "sp-io", ] [[package]] @@ -2422,10 +2409,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ "cfg-if", - "js-sys", "libc", "wasi 0.9.0+wasi-snapshot-preview1", - "wasm-bindgen", ] [[package]] @@ -3919,7 +3904,6 @@ dependencies = [ "sp-blockchain", "sp-consensus", "sp-core", - "sp-io", "sp-mmr-primitives", "sp-runtime", "sp-tracing", @@ -4196,7 +4180,7 @@ dependencies = [ "node-primitives", "node-testing", "parity-db", - "rand 0.7.3", + "rand 0.8.5", "sc-basic-authorship", "sc-client-api", "sc-transaction-pool", @@ -5029,7 +5013,7 @@ dependencies = [ "parity-scale-codec", "pretty_assertions", "rand 0.8.5", - "rand_pcg 0.3.1", + "rand_pcg", "scale-info", "serde", "smallvec", @@ -5117,7 +5101,7 @@ dependencies = [ "pallet-election-provider-support-benchmarking", "parity-scale-codec", "parking_lot 0.12.1", - "rand 0.7.3", + "rand 0.8.5", "scale-info", "sp-arithmetic", "sp-core", @@ -5126,7 +5110,6 @@ dependencies = [ "sp-runtime", "sp-std", "sp-tracing", - "static_assertions", "strum", ] @@ -5799,7 +5782,7 @@ dependencies = [ "pallet-staking-reward-curve", "pallet-timestamp", "parity-scale-codec", - "rand 0.7.3", + "rand 0.8.5", "scale-info", "sp-core", "sp-io", @@ -6238,20 +6221,20 @@ checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" [[package]] name = "pbkdf2" -version = "0.4.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216eaa586a190f0a738f2f918511eecfa90f13295abec0e457cdebcceda80cbd" +checksum = "d95f5254224e617595d2cc3cc73ff0a5eaf2637519e25f03388154e9378b6ffa" dependencies = [ - "crypto-mac 0.8.0", + "crypto-mac 0.11.1", ] [[package]] name = "pbkdf2" -version = "0.8.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95f5254224e617595d2cc3cc73ff0a5eaf2637519e25f03388154e9378b6ffa" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ - "crypto-mac 0.11.1", + "digest 0.10.3", ] [[package]] @@ -6713,7 +6696,6 @@ dependencies = [ "rand_chacha 0.2.2", "rand_core 0.5.1", "rand_hc", - "rand_pcg 0.2.1", ] [[package]] @@ -6723,7 +6705,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha 0.3.0", + "rand_chacha 0.3.1", "rand_core 0.6.2", ] @@ -6739,9 +6721,9 @@ dependencies = [ [[package]] name = "rand_chacha" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core 0.6.2", @@ -6784,15 +6766,6 @@ dependencies = [ "rand_core 0.5.1", ] -[[package]] -name = "rand_pcg" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" -dependencies = [ - "rand_core 0.5.1", -] - [[package]] name = "rand_pcg" version = "0.3.1" @@ -7150,7 +7123,7 @@ dependencies = [ "prost", "prost-build", "quickcheck", - "rand 0.7.3", + "rand 0.8.5", "sc-client-api", "sc-network-common", "sp-api", @@ -7210,9 +7183,7 @@ dependencies = [ name = "sc-chain-spec" version = "4.0.0-dev" dependencies = [ - "impl-trait-for-tuples", "memmap2", - "parity-scale-codec", "sc-chain-spec-derive", "sc-network-common", "sc-telemetry", @@ -7246,7 +7217,7 @@ dependencies = [ "log", "names", "parity-scale-codec", - "rand 0.7.3", + "rand 0.8.5", "regex", "rpassword", "sc-client-api", @@ -7279,7 +7250,6 @@ version = "4.0.0-dev" dependencies = [ "fnv", "futures", - "hash-db", "log", "parity-scale-codec", "parking_lot 0.12.1", @@ -7297,7 +7267,6 @@ dependencies = [ "sp-state-machine", "sp-storage", "sp-test-primitives", - "sp-trie", "substrate-prometheus-endpoint", "substrate-test-runtime", "thiserror", @@ -7422,7 +7391,6 @@ dependencies = [ "sc-network-test", "sc-telemetry", "schnorrkel", - "serde", "sp-api", "sp-application-crypto", "sp-block-builder", @@ -7433,13 +7401,11 @@ dependencies = [ "sp-consensus-vrf", "sp-core", "sp-inherents", - "sp-io", "sp-keyring", "sp-keystore", "sp-runtime", "sp-timestamp", "sp-tracing", - "sp-version", "substrate-prometheus-endpoint", "substrate-test-runtime-client", "thiserror", @@ -7568,7 +7534,6 @@ dependencies = [ "sp-runtime", "sp-state-machine", "substrate-test-runtime-client", - "thiserror", ] [[package]] @@ -7686,7 +7651,6 @@ dependencies = [ "sc-chain-spec", "sc-client-api", "sc-consensus", - "sc-keystore", "sc-network", "sc-network-common", "sc-network-gossip", @@ -7726,7 +7690,6 @@ dependencies = [ "sc-finality-grandpa", "sc-rpc", "serde", - "serde_json", "sp-blockchain", "sp-core", "sp-finality-grandpa", @@ -7747,7 +7710,6 @@ dependencies = [ "log", "sc-client-api", "sc-network-common", - "sc-transaction-pool-api", "sp-blockchain", "sp-runtime", ] @@ -7775,25 +7737,19 @@ dependencies = [ "assert_matches", "async-trait", "asynchronous-codec", - "bitflags", "bytes", - "cid", "either", "fnv", - "fork-tree", "futures", "futures-timer", "ip_network", "libp2p", - "linked-hash-map", - "linked_hash_set", "log", "lru", "parity-scale-codec", "parking_lot 0.12.1", "pin-project", - "prost", - "rand 0.7.3", + "rand 0.8.5", "sc-block-builder", "sc-client-api", "sc-consensus", @@ -7846,7 +7802,6 @@ dependencies = [ "thiserror", "tokio", "unsigned-varint", - "void", ] [[package]] @@ -7961,7 +7916,7 @@ dependencies = [ "libp2p", "log", "parking_lot 0.12.1", - "rand 0.7.3", + "rand 0.8.5", "sc-block-builder", "sc-client-api", "sc-consensus", @@ -7987,7 +7942,6 @@ version = "0.10.0-dev" dependencies = [ "array-bytes", "futures", - "hex", "libp2p", "log", "parity-scale-codec", @@ -8016,7 +7970,7 @@ dependencies = [ "once_cell", "parity-scale-codec", "parking_lot 0.12.1", - "rand 0.7.3", + "rand 0.8.5", "sc-block-builder", "sc-client-api", "sc-client-db", @@ -8044,7 +7998,7 @@ dependencies = [ "futures", "libp2p", "log", - "rand 0.7.3", + "rand 0.8.5", "sc-utils", "serde_json", "wasm-timer", @@ -8065,9 +8019,7 @@ dependencies = [ "assert_matches", "env_logger", "futures", - "hash-db", "jsonrpsee", - "lazy_static", "log", "parity-scale-codec", "parking_lot 0.12.1", @@ -8101,11 +8053,8 @@ dependencies = [ name = "sc-rpc-api" version = "0.10.0-dev" dependencies = [ - "futures", "jsonrpsee", - "log", "parity-scale-codec", - "parking_lot 0.12.1", "sc-chain-spec", "sc-transaction-pool-api", "scale-info", @@ -8114,7 +8063,6 @@ dependencies = [ "sp-core", "sp-rpc", "sp-runtime", - "sp-tracing", "sp-version", "thiserror", ] @@ -8123,7 +8071,6 @@ dependencies = [ name = "sc-rpc-server" version = "4.0.0-dev" dependencies = [ - "futures", "http", "jsonrpsee", "log", @@ -8174,13 +8121,12 @@ dependencies = [ "exit-future", "futures", "futures-timer", - "hash-db", "jsonrpsee", "log", "parity-scale-codec", "parking_lot 0.12.1", "pin-project", - "rand 0.7.3", + "rand 0.8.5", "sc-block-builder", "sc-chain-spec", "sc-client-api", @@ -8208,19 +8154,15 @@ dependencies = [ "serde", "serde_json", "sp-api", - "sp-application-crypto", - "sp-block-builder", "sp-blockchain", "sp-consensus", "sp-core", "sp-externalities", - "sp-inherents", "sp-keystore", "sp-runtime", "sp-session", "sp-state-machine", "sp-storage", - "sp-tracing", "sp-transaction-pool", "sp-transaction-storage-proof", "sp-trie", @@ -8259,9 +8201,7 @@ dependencies = [ "sp-blockchain", "sp-consensus", "sp-core", - "sp-externalities", "sp-io", - "sp-panic-handler", "sp-runtime", "sp-state-machine", "sp-storage", @@ -8280,7 +8220,6 @@ dependencies = [ "log", "parity-scale-codec", "parking_lot 0.12.1", - "sc-client-api", "sp-core", ] @@ -8309,8 +8248,8 @@ dependencies = [ "futures", "libc", "log", - "rand 0.7.3", - "rand_pcg 0.2.1", + "rand 0.8.5", + "rand_pcg", "regex", "sc-telemetry", "serde", @@ -8331,7 +8270,7 @@ dependencies = [ "log", "parking_lot 0.12.1", "pin-project", - "rand 0.7.3", + "rand 0.8.5", "serde", "serde_json", "thiserror", @@ -8653,15 +8592,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_nanos" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e44969a61f5d316be20a42ff97816efb3b407a924d06824c3d8a49fa8450de0e" -dependencies = [ - "serde", -] - [[package]] name = "sha-1" version = "0.9.4" @@ -8929,11 +8859,10 @@ dependencies = [ "num-traits", "parity-scale-codec", "primitive-types", - "rand 0.7.3", + "rand 0.8.5", "scale-info", "serde", "sp-core", - "sp-debug-derive", "sp-std", "static_assertions", ] @@ -9023,7 +8952,6 @@ version = "0.10.0-dev" dependencies = [ "async-trait", "futures", - "futures-timer", "log", "parity-scale-codec", "sp-core", @@ -9093,8 +9021,6 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-arithmetic", - "sp-runtime", "sp-std", "sp-timestamp", ] @@ -9119,7 +9045,6 @@ dependencies = [ "base58", "bitflags", "blake2", - "byteorder", "criterion", "dyn-clonable", "ed25519-zebra", @@ -9131,11 +9056,10 @@ dependencies = [ "libsecp256k1", "log", "merlin", - "num-traits", "parity-scale-codec", "parking_lot 0.12.1", "primitive-types", - "rand 0.7.3", + "rand 0.8.5", "regex", "scale-info", "schnorrkel", @@ -9155,7 +9079,6 @@ dependencies = [ "substrate-bip39", "thiserror", "tiny-bip39", - "wasmi 0.13.0", "zeroize", ] @@ -9247,11 +9170,9 @@ dependencies = [ "bytes", "ed25519-dalek", "futures", - "hash-db", "libsecp256k1", "log", "parity-scale-codec", - "parking_lot 0.12.1", "secp256k1", "sp-core", "sp-externalities", @@ -9261,7 +9182,6 @@ dependencies = [ "sp-std", "sp-tracing", "sp-trie", - "sp-wasm-interface", "tracing", "tracing-core", ] @@ -9325,7 +9245,7 @@ name = "sp-npos-elections" version = "4.0.0-dev" dependencies = [ "parity-scale-codec", - "rand 0.7.3", + "rand 0.8.5", "scale-info", "serde", "sp-arithmetic", @@ -9386,7 +9306,7 @@ dependencies = [ "log", "parity-scale-codec", "paste", - "rand 0.7.3", + "rand 0.8.5", "scale-info", "serde", "serde_json", @@ -9516,11 +9436,10 @@ dependencies = [ "assert_matches", "hash-db", "log", - "num-traits", "parity-scale-codec", "parking_lot 0.12.1", "pretty_assertions", - "rand 0.7.3", + "rand 0.8.5", "smallvec", "sp-core", "sp-externalities", @@ -9531,7 +9450,6 @@ dependencies = [ "thiserror", "tracing", "trie-db", - "trie-root", ] [[package]] @@ -9569,7 +9487,6 @@ dependencies = [ "futures-timer", "log", "parity-scale-codec", - "sp-api", "sp-inherents", "sp-runtime", "sp-std", @@ -9680,7 +9597,6 @@ dependencies = [ name = "sp-weights" version = "4.0.0" dependencies = [ - "impl-trait-for-tuples", "parity-scale-codec", "scale-info", "serde", @@ -9855,7 +9771,6 @@ version = "3.0.0" dependencies = [ "frame-support", "frame-system", - "futures", "jsonrpsee", "parity-scale-codec", "sc-rpc-api", @@ -9877,11 +9792,9 @@ dependencies = [ "jsonrpsee", "log", "parity-scale-codec", - "sc-client-api", "sc-rpc-api", "sc-transaction-pool", "sc-transaction-pool-api", - "serde_json", "sp-api", "sp-block-builder", "sp-blockchain", @@ -9896,7 +9809,6 @@ dependencies = [ name = "substrate-prometheus-endpoint" version = "0.10.0-dev" dependencies = [ - "futures-util", "hyper", "log", "prometheus", @@ -9931,10 +9843,8 @@ dependencies = [ "serde", "serde_json", "sp-core", - "sp-io", "sp-runtime", "sp-state-machine", - "sp-std", "sp-trie", "trie-db", ] @@ -10252,17 +10162,17 @@ dependencies = [ [[package]] name = "tiny-bip39" -version = "0.8.2" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc59cb9dfc85bb312c3a78fd6aa8a8582e310b0fa885d5bb877f6dcc601839d" +checksum = "62cc94d358b5a1e84a5cb9109f559aa3c4d634d2b1b4de3d0fa4adc7c78e2861" dependencies = [ "anyhow", - "hmac 0.8.1", + "hmac 0.12.1", "once_cell", - "pbkdf2 0.4.0", - "rand 0.7.3", + "pbkdf2 0.11.0", + "rand 0.8.5", "rustc-hash", - "sha2 0.9.8", + "sha2 0.10.2", "thiserror", "unicode-normalization", "wasm-bindgen", @@ -10655,7 +10565,6 @@ dependencies = [ "hex", "log", "parity-scale-codec", - "sc-chain-spec", "sc-cli", "sc-executor", "sc-service", @@ -10751,9 +10660,9 @@ checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" [[package]] name = "unicode-normalization" -version = "0.1.17" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] diff --git a/bin/node/bench/Cargo.toml b/bin/node/bench/Cargo.toml index 265de731de..9732faf6e1 100644 --- a/bin/node/bench/Cargo.toml +++ b/bin/node/bench/Cargo.toml @@ -36,7 +36,7 @@ sp-tracing = { version = "6.0.0", path = "../../../primitives/tracing" } hash-db = "0.15.2" tempfile = "3.1.0" fs_extra = "1" -rand = { version = "0.7.2", features = ["small_rng"] } +rand = { version = "0.8.5", features = ["small_rng"] } lazy_static = "1.4.0" parity-db = "0.4.2" sc-transaction-pool = { version = "4.0.0-dev", path = "../../../client/transaction-pool" } diff --git a/client/api/Cargo.toml b/client/api/Cargo.toml index c57a1e7221..bd1f6f34b0 100644 --- a/client/api/Cargo.toml +++ b/client/api/Cargo.toml @@ -19,7 +19,6 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = ] } fnv = "1.0.6" futures = "0.3.21" -hash-db = { version = "0.15.2", default-features = false } log = "0.4.17" parking_lot = "0.12.1" prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } @@ -36,7 +35,6 @@ sp-keystore = { version = "0.13.0", default-features = false, path = "../../prim sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } sp-state-machine = { version = "0.13.0", path = "../../primitives/state-machine" } sp-storage = { version = "7.0.0", path = "../../primitives/storage" } -sp-trie = { version = "7.0.0", path = "../../primitives/trie" } [dev-dependencies] thiserror = "1.0.30" diff --git a/client/authority-discovery/Cargo.toml b/client/authority-discovery/Cargo.toml index 0da79bd70f..5b07e4b7bb 100644 --- a/client/authority-discovery/Cargo.toml +++ b/client/authority-discovery/Cargo.toml @@ -24,7 +24,7 @@ ip_network = "0.4.1" libp2p = { version = "0.49.0", default-features = false, features = ["kad"] } log = "0.4.17" prost = "0.11" -rand = "0.7.2" +rand = "0.8.5" thiserror = "1.0" prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } sc-client-api = { version = "4.0.0-dev", path = "../api" } diff --git a/client/beefy/Cargo.toml b/client/beefy/Cargo.toml index d0b36a9255..b039faa2d0 100644 --- a/client/beefy/Cargo.toml +++ b/client/beefy/Cargo.toml @@ -14,17 +14,14 @@ async-trait = "0.1.57" codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } fnv = "1.0.6" futures = "0.3" -futures-timer = "3.0.1" log = "0.4" parking_lot = "0.12.1" thiserror = "1.0" wasm-timer = "0.2.5" beefy-primitives = { version = "4.0.0-dev", path = "../../primitives/beefy", package = "sp-beefy" } prometheus = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } -sc-chain-spec = { version = "4.0.0-dev", path = "../../client/chain-spec" } sc-client-api = { version = "4.0.0-dev", path = "../api" } sc-consensus = { version = "0.10.0-dev", path = "../consensus/common" } -sc-finality-grandpa = { version = "0.10.0-dev", path = "../../client/finality-grandpa" } sc-keystore = { version = "4.0.0-dev", path = "../keystore" } sc-network = { version = "0.10.0-dev", path = "../network" } sc-network-common = { version = "0.10.0-dev", path = "../network/common" } diff --git a/client/beefy/rpc/Cargo.toml b/client/beefy/rpc/Cargo.toml index f5b5770153..f853af8a81 100644 --- a/client/beefy/rpc/Cargo.toml +++ b/client/beefy/rpc/Cargo.toml @@ -19,7 +19,6 @@ thiserror = "1.0" beefy-gadget = { version = "4.0.0-dev", path = "../." } beefy-primitives = { version = "4.0.0-dev", path = "../../../primitives/beefy", package = "sp-beefy" } sc-rpc = { version = "4.0.0-dev", path = "../../rpc" } -sc-utils = { version = "4.0.0-dev", path = "../../utils" } sp-core = { version = "7.0.0", path = "../../../primitives/core" } sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } diff --git a/client/chain-spec/Cargo.toml b/client/chain-spec/Cargo.toml index 3756a77837..0a4e54e8d6 100644 --- a/client/chain-spec/Cargo.toml +++ b/client/chain-spec/Cargo.toml @@ -13,8 +13,6 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.0.0" } -impl-trait-for-tuples = "0.2.2" memmap2 = "0.5.0" serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0.85" diff --git a/client/cli/Cargo.toml b/client/cli/Cargo.toml index fd84ff4d45..a5de78da48 100644 --- a/client/cli/Cargo.toml +++ b/client/cli/Cargo.toml @@ -22,13 +22,13 @@ libp2p = "0.49.0" log = "0.4.17" names = { version = "0.13.0", default-features = false } parity-scale-codec = "3.0.0" -rand = "0.7.3" +rand = "0.8.5" regex = "1.6.0" rpassword = "7.0.0" serde = "1.0.136" serde_json = "1.0.85" thiserror = "1.0.30" -tiny-bip39 = "0.8.2" +tiny-bip39 = "1.0.0" tokio = { version = "1.22.0", features = ["signal", "rt-multi-thread", "parking_lot"] } sc-client-api = { version = "4.0.0-dev", path = "../api" } sc-client-db = { version = "0.10.0-dev", default-features = false, path = "../db" } diff --git a/client/consensus/babe/Cargo.toml b/client/consensus/babe/Cargo.toml index c39802ba23..312cc25dd0 100644 --- a/client/consensus/babe/Cargo.toml +++ b/client/consensus/babe/Cargo.toml @@ -24,7 +24,6 @@ num-rational = "0.4.1" num-traits = "0.2.8" parking_lot = "0.12.1" schnorrkel = { version = "0.9.1", features = ["preaudit_deprecated"] } -serde = { version = "1.0.136", features = ["derive"] } thiserror = "1.0" fork-tree = { version = "3.0.0", path = "../../../utils/fork-tree" } prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../../utils/prometheus" } @@ -44,10 +43,8 @@ sp-consensus-slots = { version = "0.10.0-dev", path = "../../../primitives/conse sp-consensus-vrf = { version = "0.10.0-dev", path = "../../../primitives/consensus/vrf" } sp-core = { version = "7.0.0", path = "../../../primitives/core" } sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } -sp-io = { version = "7.0.0", path = "../../../primitives/io" } sp-keystore = { version = "0.13.0", path = "../../../primitives/keystore" } sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } -sp-version = { version = "5.0.0", path = "../../../primitives/version" } [dev-dependencies] rand_chacha = "0.2.2" diff --git a/client/consensus/slots/Cargo.toml b/client/consensus/slots/Cargo.toml index 4bb9387cf5..6acd656d21 100644 --- a/client/consensus/slots/Cargo.toml +++ b/client/consensus/slots/Cargo.toml @@ -19,7 +19,6 @@ codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.21" futures-timer = "3.0.1" log = "0.4.17" -thiserror = "1.0.30" sc-client-api = { version = "4.0.0-dev", path = "../../api" } sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } sc-telemetry = { version = "4.0.0-dev", path = "../../telemetry" } diff --git a/client/db/Cargo.toml b/client/db/Cargo.toml index ee879a161e..562a94f190 100644 --- a/client/db/Cargo.toml +++ b/client/db/Cargo.toml @@ -37,7 +37,7 @@ sp-trie = { version = "7.0.0", path = "../../primitives/trie" } [dev-dependencies] criterion = "0.3.3" kvdb-rocksdb = "0.17.0" -rand = "0.8.4" +rand = "0.8.5" tempfile = "3.1.0" quickcheck = { version = "1.0.3", default-features = false } kitchensink-runtime = { path = "../../bin/node/runtime" } diff --git a/client/finality-grandpa/Cargo.toml b/client/finality-grandpa/Cargo.toml index 0d5b8eaca5..b47c803248 100644 --- a/client/finality-grandpa/Cargo.toml +++ b/client/finality-grandpa/Cargo.toml @@ -24,7 +24,7 @@ futures-timer = "3.0.1" log = "0.4.17" parity-scale-codec = { version = "3.0.0", features = ["derive"] } parking_lot = "0.12.1" -rand = "0.8.4" +rand = "0.8.5" serde_json = "1.0.85" thiserror = "1.0" fork-tree = { version = "3.0.0", path = "../../utils/fork-tree" } @@ -33,7 +33,6 @@ sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } sc-chain-spec = { version = "4.0.0-dev", path = "../../client/chain-spec" } sc-client-api = { version = "4.0.0-dev", path = "../api" } sc-consensus = { version = "0.10.0-dev", path = "../consensus/common" } -sc-keystore = { version = "4.0.0-dev", path = "../keystore" } sc-network = { version = "0.10.0-dev", path = "../network" } sc-network-gossip = { version = "0.10.0-dev", path = "../network-gossip" } sc-network-common = { version = "0.10.0-dev", path = "../network/common" } diff --git a/client/finality-grandpa/rpc/Cargo.toml b/client/finality-grandpa/rpc/Cargo.toml index 252c5e3871..15b9d3887b 100644 --- a/client/finality-grandpa/rpc/Cargo.toml +++ b/client/finality-grandpa/rpc/Cargo.toml @@ -16,7 +16,6 @@ jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] log = "0.4.8" parity-scale-codec = { version = "3.0.0", features = ["derive"] } serde = { version = "1.0.105", features = ["derive"] } -serde_json = "1.0.50" thiserror = "1.0" sc-client-api = { version = "4.0.0-dev", path = "../../api" } sc-finality-grandpa = { version = "0.10.0-dev", path = "../" } diff --git a/client/informant/Cargo.toml b/client/informant/Cargo.toml index a432c2ada5..ca0ae03af3 100644 --- a/client/informant/Cargo.toml +++ b/client/informant/Cargo.toml @@ -19,6 +19,5 @@ futures-timer = "3.0.1" log = "0.4.17" sc-client-api = { version = "4.0.0-dev", path = "../api" } sc-network-common = { version = "0.10.0-dev", path = "../network/common" } -sc-transaction-pool-api = { version = "4.0.0-dev", path = "../transaction-pool/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } diff --git a/client/merkle-mountain-range/Cargo.toml b/client/merkle-mountain-range/Cargo.toml index 4fb423cee8..cd7d944249 100644 --- a/client/merkle-mountain-range/Cargo.toml +++ b/client/merkle-mountain-range/Cargo.toml @@ -20,7 +20,6 @@ sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } sp-core = { version = "7.0.0", path = "../../primitives/core" } -sp-io = { version = "7.0.0", path = "../../primitives/io" } sp-mmr-primitives = { version = "4.0.0-dev", path = "../../primitives/merkle-mountain-range" } sc-offchain = { version = "4.0.0-dev", path = "../offchain" } sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } diff --git a/client/network/Cargo.toml b/client/network/Cargo.toml index 1959e24bd6..be9a74d2c1 100644 --- a/client/network/Cargo.toml +++ b/client/network/Cargo.toml @@ -17,9 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] array-bytes = "4.1" async-trait = "0.1" asynchronous-codec = "0.6" -bitflags = "1.3.2" bytes = "1" -cid = "0.8.4" codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } either = "1.5.3" fnv = "1.0.6" @@ -27,21 +25,17 @@ futures = "0.3.21" futures-timer = "3.0.2" ip_network = "0.4.1" libp2p = { version = "0.49.0", features = ["dns", "identify", "kad", "mdns", "mplex", "noise", "ping", "tcp", "tokio", "yamux", "websocket"] } -linked_hash_set = "0.1.3" -linked-hash-map = "0.5.4" log = "0.4.17" lru = "0.8.1" parking_lot = "0.12.1" pin-project = "1.0.12" -prost = "0.11" -rand = "0.7.2" +rand = "0.8.5" serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0.85" smallvec = "1.8.0" thiserror = "1.0" unsigned-varint = { version = "0.7.1", features = ["futures", "asynchronous_codec"] } zeroize = "1.4.3" -fork-tree = { version = "3.0.0", path = "../../utils/fork-tree" } prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } sc-client-api = { version = "4.0.0-dev", path = "../api" } @@ -57,7 +51,6 @@ sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } [dev-dependencies] assert_matches = "1.3" -rand = "0.7.2" tempfile = "3.1.0" tokio = { version = "1.22.0", features = ["macros"] } tokio-util = { version = "0.7.4", features = ["compat"] } diff --git a/client/network/bitswap/Cargo.toml b/client/network/bitswap/Cargo.toml index 099b5cd5e8..c4c7850b71 100644 --- a/client/network/bitswap/Cargo.toml +++ b/client/network/bitswap/Cargo.toml @@ -23,7 +23,6 @@ log = "0.4.17" prost = "0.11" thiserror = "1.0" unsigned-varint = { version = "0.7.1", features = ["futures", "asynchronous_codec"] } -void = "1.0.2" sc-client-api = { version = "4.0.0-dev", path = "../../api" } sc-network-common = { version = "0.10.0-dev", path = "../common" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } diff --git a/client/network/test/Cargo.toml b/client/network/test/Cargo.toml index 86b5be37d2..25e4df00e0 100644 --- a/client/network/test/Cargo.toml +++ b/client/network/test/Cargo.toml @@ -20,7 +20,7 @@ futures-timer = "3.0.1" libp2p = { version = "0.49.0", default-features = false } log = "0.4.17" parking_lot = "0.12.1" -rand = "0.7.2" +rand = "0.8.5" sc-block-builder = { version = "0.10.0-dev", path = "../../block-builder" } sc-client-api = { version = "4.0.0-dev", path = "../../api" } sc-consensus = { version = "0.10.0-dev", path = "../../consensus/common" } diff --git a/client/network/transactions/Cargo.toml b/client/network/transactions/Cargo.toml index cb45abca02..f91651d150 100644 --- a/client/network/transactions/Cargo.toml +++ b/client/network/transactions/Cargo.toml @@ -16,7 +16,6 @@ targets = ["x86_64-unknown-linux-gnu"] array-bytes = "4.1" codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } futures = "0.3.21" -hex = "0.4.0" libp2p = "0.49.0" log = "0.4.17" pin-project = "1.0.12" diff --git a/client/offchain/Cargo.toml b/client/offchain/Cargo.toml index 6406d7dc47..baeec0b6d7 100644 --- a/client/offchain/Cargo.toml +++ b/client/offchain/Cargo.toml @@ -25,7 +25,7 @@ libp2p = { version = "0.49.0", default-features = false } num_cpus = "1.13" once_cell = "1.8" parking_lot = "0.12.1" -rand = "0.7.2" +rand = "0.8.5" threadpool = "1.7" tracing = "0.1.29" sc-client-api = { version = "4.0.0-dev", path = "../api" } diff --git a/client/peerset/Cargo.toml b/client/peerset/Cargo.toml index ade2bc3d78..8625fe40c2 100644 --- a/client/peerset/Cargo.toml +++ b/client/peerset/Cargo.toml @@ -22,4 +22,4 @@ wasm-timer = "0.2" sc-utils = { version = "4.0.0-dev", path = "../utils" } [dev-dependencies] -rand = "0.7.2" +rand = "0.8.5" diff --git a/client/rpc-api/Cargo.toml b/client/rpc-api/Cargo.toml index c46488db2d..f697f53e98 100644 --- a/client/rpc-api/Cargo.toml +++ b/client/rpc-api/Cargo.toml @@ -14,9 +14,6 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } -futures = "0.3.21" -log = "0.4.17" -parking_lot = "0.12.1" scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0.85" @@ -26,6 +23,5 @@ sc-transaction-pool-api = { version = "4.0.0-dev", path = "../transaction-pool/a sp-core = { version = "7.0.0", path = "../../primitives/core" } sp-rpc = { version = "6.0.0", path = "../../primitives/rpc" } sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } -sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } sp-version = { version = "5.0.0", path = "../../primitives/version" } jsonrpsee = { version = "0.16.2", features = ["server", "client-core", "macros"] } diff --git a/client/rpc-servers/Cargo.toml b/client/rpc-servers/Cargo.toml index b494749ffd..c6228d7390 100644 --- a/client/rpc-servers/Cargo.toml +++ b/client/rpc-servers/Cargo.toml @@ -13,7 +13,6 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -futures = "0.3.21" jsonrpsee = { version = "0.16.2", features = ["server"] } log = "0.4.17" serde_json = "1.0.85" diff --git a/client/rpc/Cargo.toml b/client/rpc/Cargo.toml index a241807cc2..d97170ddf4 100644 --- a/client/rpc/Cargo.toml +++ b/client/rpc/Cargo.toml @@ -15,9 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.21" -hash-db = { version = "0.15.2", default-features = false } jsonrpsee = { version = "0.16.2", features = ["server"] } -lazy_static = { version = "1.4.0", optional = true } log = "0.4.17" parking_lot = "0.12.1" serde_json = "1.0.85" @@ -43,7 +41,6 @@ tokio = { version = "1.22.0", optional = true } [dev-dependencies] env_logger = "0.9" assert_matches = "1.3.0" -lazy_static = "1.4.0" sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } sc-network = { version = "0.10.0-dev", path = "../network" } sc-network-common = { version = "0.10.0-dev", path = "../network/common" } @@ -54,4 +51,4 @@ sp-io = { version = "7.0.0", path = "../../primitives/io" } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } [features] -test-helpers = ["lazy_static", "tokio"] +test-helpers = ["tokio"] diff --git a/client/service/Cargo.toml b/client/service/Cargo.toml index 4d1d267d45..45b1c02620 100644 --- a/client/service/Cargo.toml +++ b/client/service/Cargo.toml @@ -25,13 +25,12 @@ runtime-benchmarks = ["sc-client-db/runtime-benchmarks"] jsonrpsee = { version = "0.16.2", features = ["server"] } thiserror = "1.0.30" futures = "0.3.21" -rand = "0.7.3" +rand = "0.8.5" parking_lot = "0.12.1" log = "0.4.17" futures-timer = "3.0.1" exit-future = "0.2.0" pin-project = "1.0.12" -hash-db = "0.15.2" serde = "1.0.136" serde_json = "1.0.85" sc-keystore = { version = "4.0.0-dev", path = "../keystore" } @@ -45,10 +44,8 @@ sp-core = { version = "7.0.0", path = "../../primitives/core" } sp-keystore = { version = "0.13.0", path = "../../primitives/keystore" } sp-session = { version = "4.0.0-dev", path = "../../primitives/session" } sp-state-machine = { version = "0.13.0", path = "../../primitives/state-machine" } -sp-application-crypto = { version = "7.0.0", path = "../../primitives/application-crypto" } sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } sc-consensus = { version = "0.10.0-dev", path = "../../client/consensus/common" } -sp-inherents = { version = "4.0.0-dev", path = "../../primitives/inherents" } sp-storage = { version = "7.0.0", path = "../../primitives/storage" } sc-network = { version = "0.10.0-dev", path = "../network" } sc-network-bitswap = { version = "0.10.0-dev", path = "../network/bitswap" } @@ -70,13 +67,11 @@ sc-rpc-server = { version = "4.0.0-dev", path = "../rpc-servers" } sc-rpc = { version = "4.0.0-dev", path = "../rpc" } sc-rpc-spec-v2 = { version = "0.10.0-dev", path = "../rpc-spec-v2" } sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } -sp-block-builder = { version = "4.0.0-dev", path = "../../primitives/block-builder" } sc-informant = { version = "0.10.0-dev", path = "../informant" } sc-telemetry = { version = "4.0.0-dev", path = "../telemetry" } sc-offchain = { version = "4.0.0-dev", path = "../offchain" } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus", version = "0.10.0-dev" } sc-tracing = { version = "4.0.0-dev", path = "../tracing" } -sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } sc-sysinfo = { version = "6.0.0-dev", path = "../sysinfo" } tracing = "0.1.29" tracing-futures = { version = "0.2.4" } diff --git a/client/service/test/Cargo.toml b/client/service/test/Cargo.toml index 46ba1b3393..d3bc39aa34 100644 --- a/client/service/test/Cargo.toml +++ b/client/service/test/Cargo.toml @@ -33,8 +33,6 @@ sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } sp-core = { version = "7.0.0", path = "../../../primitives/core" } -sp-externalities = { version = "0.13.0", path = "../../../primitives/externalities" } -sp-panic-handler = { version = "5.0.0", path = "../../../primitives/panic-handler" } sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } sp-state-machine = { version = "0.13.0", path = "../../../primitives/state-machine" } sp-storage = { version = "7.0.0", path = "../../../primitives/storage" } diff --git a/client/state-db/Cargo.toml b/client/state-db/Cargo.toml index b6d40a8106..fb70f5fc6a 100644 --- a/client/state-db/Cargo.toml +++ b/client/state-db/Cargo.toml @@ -16,5 +16,4 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } log = "0.4.17" parking_lot = "0.12.1" -sc-client-api = { version = "4.0.0-dev", path = "../api" } sp-core = { version = "7.0.0", path = "../../primitives/core" } diff --git a/client/sysinfo/Cargo.toml b/client/sysinfo/Cargo.toml index c59611ed1b..73aa7b8d10 100644 --- a/client/sysinfo/Cargo.toml +++ b/client/sysinfo/Cargo.toml @@ -17,8 +17,8 @@ targets = ["x86_64-unknown-linux-gnu"] futures = "0.3.19" libc = "0.2" log = "0.4.17" -rand = "0.7.3" -rand_pcg = "0.2.1" +rand = "0.8.5" +rand_pcg = "0.3.1" regex = "1" serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0.85" diff --git a/client/telemetry/Cargo.toml b/client/telemetry/Cargo.toml index 0a0b9284ef..1fc89980e2 100644 --- a/client/telemetry/Cargo.toml +++ b/client/telemetry/Cargo.toml @@ -20,7 +20,7 @@ libp2p = { version = "0.49.0", default-features = false, features = ["dns", "tcp log = "0.4.17" parking_lot = "0.12.1" pin-project = "1.0.12" -rand = "0.7.2" +rand = "0.8.5" serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0.85" thiserror = "1.0.30" diff --git a/client/telemetry/src/node.rs b/client/telemetry/src/node.rs index 0d71a363a1..71eed04128 100644 --- a/client/telemetry/src/node.rs +++ b/client/telemetry/src/node.rs @@ -76,7 +76,7 @@ enum NodeSocket { impl NodeSocket { fn wait_reconnect() -> NodeSocket { - let random_delay = rand::thread_rng().gen_range(10, 20); + let random_delay = rand::thread_rng().gen_range(10..20); let delay = Delay::new(Duration::from_secs(random_delay)); log::trace!(target: "telemetry", "Pausing for {} secs before reconnecting", random_delay); NodeSocket::WaitingReconnect(delay) diff --git a/frame/election-provider-multi-phase/Cargo.toml b/frame/election-provider-multi-phase/Cargo.toml index 42cd682a0f..a21740dd72 100644 --- a/frame/election-provider-multi-phase/Cargo.toml +++ b/frame/election-provider-multi-phase/Cargo.toml @@ -12,7 +12,6 @@ description = "PALLET two phase election providers" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -static_assertions = "1.1.0" codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } @@ -35,12 +34,12 @@ frame-election-provider-support = { version = "4.0.0-dev", default-features = fa # Optional imports for benchmarking frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } pallet-election-provider-support-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../election-provider-support/benchmarking", optional = true } -rand = { version = "0.7.3", default-features = false, features = ["alloc", "small_rng"], optional = true } +rand = { version = "0.8.5", default-features = false, features = ["alloc", "small_rng"], optional = true } strum = { version = "0.24.1", default-features = false, features = ["derive"], optional = true } [dev-dependencies] parking_lot = "0.12.1" -rand = { version = "0.7.3" } +rand = "0.8.5" sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } sp-io = { version = "7.0.0", path = "../../primitives/io" } sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../primitives/npos-elections" } diff --git a/frame/election-provider-support/Cargo.toml b/frame/election-provider-support/Cargo.toml index 33a6f25ed0..108a49eb08 100644 --- a/frame/election-provider-support/Cargo.toml +++ b/frame/election-provider-support/Cargo.toml @@ -24,7 +24,7 @@ sp-std = { version = "5.0.0", default-features = false, path = "../../primitives sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } [dev-dependencies] -rand = "0.7.3" +rand = { version = "0.8.5", features = ["small_rng"] } sp-io = { version = "7.0.0", path = "../../primitives/io" } sp-npos-elections = { version = "4.0.0-dev", path = "../../primitives/npos-elections" } diff --git a/frame/election-provider-support/src/mock.rs b/frame/election-provider-support/src/mock.rs index 7c834f06f3..0789ee90e6 100644 --- a/frame/election-provider-support/src/mock.rs +++ b/frame/election-provider-support/src/mock.rs @@ -97,7 +97,7 @@ pub fn generate_random_votes( // it's not interesting if a voter chooses 0 or all candidates, so rule those cases out. // also, let's not generate any cases which result in a compact overflow. let n_candidates_chosen = - rng.gen_range(1, candidates.len().min(::LIMIT)); + rng.gen_range(1..candidates.len().min(::LIMIT)); let mut chosen_candidates = Vec::with_capacity(n_candidates_chosen); chosen_candidates.extend(candidates.choose_multiple(&mut rng, n_candidates_chosen)); @@ -106,7 +106,7 @@ pub fn generate_random_votes( // always generate a sensible number of winners: elections are uninteresting if nobody wins, // or everybody wins - let num_winners = rng.gen_range(1, candidate_count); + let num_winners = rng.gen_range(1..candidate_count); let mut winners: HashSet = HashSet::with_capacity(num_winners); winners.extend(candidates.choose_multiple(&mut rng, num_winners)); assert_eq!(winners.len(), num_winners); @@ -123,7 +123,7 @@ pub fn generate_random_votes( let mut available_stake = 1000; let mut stake_distribution = Vec::with_capacity(num_chosen_winners); for _ in 0..num_chosen_winners - 1 { - let stake = rng.gen_range(0, available_stake).min(1); + let stake = rng.gen_range(0..available_stake).min(1); stake_distribution.push(TestAccuracy::from_perthousand(stake)); available_stake -= stake; } diff --git a/frame/election-provider-support/src/tests.rs b/frame/election-provider-support/src/tests.rs index 1ccff79f3e..c38444f011 100644 --- a/frame/election-provider-support/src/tests.rs +++ b/frame/election-provider-support/src/tests.rs @@ -92,7 +92,7 @@ mod solution_type { #[test] fn from_assignment_fail_too_many_voters() { - let rng = rand::rngs::SmallRng::seed_from_u64(0); + let rng = rand::rngs::SmallRng::seed_from_u64(1); // This will produce 24 voters.. let (voters, assignments, candidates) = generate_random_votes(10, 25, rng); diff --git a/frame/session/benchmarking/Cargo.toml b/frame/session/benchmarking/Cargo.toml index 90d6d95c07..53d6ff0cd1 100644 --- a/frame/session/benchmarking/Cargo.toml +++ b/frame/session/benchmarking/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -rand = { version = "0.7.2", default-features = false } +rand = { version = "0.8.5", default-features = false, features = ["std_rng"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } diff --git a/primitives/arithmetic/Cargo.toml b/primitives/arithmetic/Cargo.toml index 36c86230e9..2da8767fd2 100644 --- a/primitives/arithmetic/Cargo.toml +++ b/primitives/arithmetic/Cargo.toml @@ -23,14 +23,13 @@ num-traits = { version = "0.2.8", default-features = false } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", features = ["derive"], optional = true } static_assertions = "1.1.0" -sp-debug-derive = { version = "5.0.0", default-features = false, path = "../debug-derive" } sp-std = { version = "5.0.0", default-features = false, path = "../std" } [dev-dependencies] criterion = "0.3" primitive-types = "0.12.0" sp-core = { version = "7.0.0", features = ["full_crypto"], path = "../core" } -rand = "0.7.2" +rand = "0.8.5" [features] default = ["std"] @@ -39,7 +38,6 @@ std = [ "num-traits/std", "scale-info/std", "serde", - "sp-debug-derive/std", "sp-std/std", ] diff --git a/primitives/arithmetic/benches/bench.rs b/primitives/arithmetic/benches/bench.rs index 57556135f6..5551d20909 100644 --- a/primitives/arithmetic/benches/bench.rs +++ b/primitives/arithmetic/benches/bench.rs @@ -21,7 +21,7 @@ use sp_arithmetic::biguint::{BigUint, Single}; fn random_big_uint(size: usize) -> BigUint { let mut rng = rand::thread_rng(); - let digits: Vec<_> = (0..size).map(|_| rng.gen_range(0, Single::max_value())).collect(); + let digits: Vec<_> = (0..size).map(|_| rng.gen_range(0..Single::MAX)).collect(); BigUint::from_limbs(&digits) } @@ -64,7 +64,7 @@ fn bench_division(c: &mut Criterion) { group.throughput(Throughput::Elements(*size)); group.bench_with_input(BenchmarkId::from_parameter(size), size, |bencher, &size| { let a = random_big_uint(size as usize); - let b = random_big_uint(rand::thread_rng().gen_range(2, size as usize)); + let b = random_big_uint(rand::thread_rng().gen_range(2..size as usize)); bencher.iter(|| { let _ = a.clone().div(&b, true); diff --git a/primitives/consensus/common/Cargo.toml b/primitives/consensus/common/Cargo.toml index 6df4e5c723..9b4cac722a 100644 --- a/primitives/consensus/common/Cargo.toml +++ b/primitives/consensus/common/Cargo.toml @@ -19,7 +19,6 @@ codec = { package = "parity-scale-codec", version = "3.0.0", features = [ "derive", ] } futures = { version = "0.3.21", features = ["thread-pool"] } -futures-timer = "3.0.1" log = "0.4.17" thiserror = "1.0.30" sp-core = { version = "7.0.0", path = "../../core" } diff --git a/primitives/consensus/slots/Cargo.toml b/primitives/consensus/slots/Cargo.toml index a7b941b3f4..3360e80772 100644 --- a/primitives/consensus/slots/Cargo.toml +++ b/primitives/consensus/slots/Cargo.toml @@ -16,8 +16,6 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } scale-info = { version = "2.0.0", default-features = false, features = ["derive"] } serde = { version = "1.0", features = ["derive"], optional = true } -sp-arithmetic = { version = "6.0.0", default-features = false, path = "../../arithmetic" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../runtime" } sp-std = { version = "5.0.0", default-features = false, path = "../../std" } sp-timestamp = { version = "4.0.0-dev", default-features = false, path = "../../timestamp" } @@ -27,8 +25,6 @@ std = [ "codec/std", "scale-info/std", "serde", - "sp-arithmetic/std", - "sp-runtime/std", "sp-std/std", "sp-timestamp/std", ] diff --git a/primitives/core/Cargo.toml b/primitives/core/Cargo.toml index bfec09a13c..68c780c473 100644 --- a/primitives/core/Cargo.toml +++ b/primitives/core/Cargo.toml @@ -20,18 +20,15 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } log = { version = "0.4.17", default-features = false } serde = { version = "1.0.136", optional = true, features = ["derive"] } -byteorder = { version = "1.3.2", default-features = false } primitive-types = { version = "0.12.0", default-features = false, features = ["codec", "scale-info"] } impl-serde = { version = "0.4.0", optional = true } -wasmi = { version = "0.13", optional = true } hash-db = { version = "0.15.2", default-features = false } hash256-std-hasher = { version = "0.15.2", default-features = false } base58 = { version = "0.2.0", optional = true } -rand = { version = "0.7.3", optional = true, features = ["small_rng"] } +rand = { version = "0.8.5", optional = true, features = ["small_rng"] } substrate-bip39 = { version = "0.4.4", optional = true } -tiny-bip39 = { version = "0.8.2", optional = true } +tiny-bip39 = { version = "1.0.0", optional = true } regex = { version = "1.6.0", optional = true } -num-traits = { version = "0.2.8", default-features = false } zeroize = { version = "1.4.3", default-features = false } secrecy = { version = "0.8.0", default-features = false } lazy_static = { version = "1.4.0", default-features = false, optional = true } @@ -62,7 +59,7 @@ sp-runtime-interface = { version = "7.0.0", default-features = false, path = ".. [dev-dependencies] sp-serializer = { version = "4.0.0-dev", path = "../serializer" } -rand = "0.7.2" +rand = "0.8.5" criterion = "0.3.3" serde_json = "1.0" sp-core-hashing-proc-macro = { version = "5.0.0", path = "./hashing/proc-macro" } @@ -81,7 +78,6 @@ std = [ "full_crypto", "log/std", "thiserror", - "wasmi", "lazy_static", "parking_lot", "primitive-types/std", @@ -101,11 +97,9 @@ std = [ "base58", "substrate-bip39", "tiny-bip39", - "byteorder/std", "rand", "schnorrkel/std", "regex", - "num-traits/std", "secp256k1/std", "secp256k1/global-context", "sp-core-hashing/std", diff --git a/primitives/io/Cargo.toml b/primitives/io/Cargo.toml index cd900b8f15..57dc3604de 100644 --- a/primitives/io/Cargo.toml +++ b/primitives/io/Cargo.toml @@ -17,20 +17,17 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] bytes = { version = "1.1.0", default-features = false } codec = { package = "parity-scale-codec", version = "3.1.3", default-features = false, features = ["bytes"] } -hash-db = { version = "0.15.2", default-features = false } sp-core = { version = "7.0.0", default-features = false, path = "../core" } sp-keystore = { version = "0.13.0", default-features = false, optional = true, path = "../keystore" } sp-std = { version = "5.0.0", default-features = false, path = "../std" } libsecp256k1 = { version = "0.7", optional = true } sp-state-machine = { version = "0.13.0", default-features = false, optional = true, path = "../state-machine" } -sp-wasm-interface = { version = "7.0.0", path = "../wasm-interface", default-features = false } sp-runtime-interface = { version = "7.0.0", default-features = false, path = "../runtime-interface" } sp-trie = { version = "7.0.0", default-features = false, optional = true, path = "../trie" } sp-externalities = { version = "0.13.0", default-features = false, path = "../externalities" } sp-tracing = { version = "6.0.0", default-features = false, path = "../tracing" } log = { version = "0.4.17", optional = true } futures = { version = "0.3.21", features = ["thread-pool"], optional = true } -parking_lot = { version = "0.12.1", optional = true } secp256k1 = { version = "0.24.0", features = ["recovery", "global-context"], optional = true } tracing = { version = "0.1.29", default-features = false } tracing-core = { version = "0.1.28", default-features = false} @@ -45,19 +42,16 @@ std = [ "sp-keystore", "codec/std", "sp-std/std", - "hash-db/std", "sp-trie/std", "sp-state-machine/std", "libsecp256k1", "secp256k1", "sp-runtime-interface/std", - "sp-wasm-interface/std", "sp-tracing/std", "tracing/std", "tracing-core/std", "log", "futures", - "parking_lot", "ed25519-dalek", ] diff --git a/primitives/npos-elections/Cargo.toml b/primitives/npos-elections/Cargo.toml index b99b05e0e3..702986da30 100644 --- a/primitives/npos-elections/Cargo.toml +++ b/primitives/npos-elections/Cargo.toml @@ -22,7 +22,7 @@ sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" sp-std = { version = "5.0.0", default-features = false, path = "../std" } [dev-dependencies] -rand = "0.7.3" +rand = "0.8.5" substrate-test-utils = { version = "4.0.0-dev", path = "../../test-utils" } [features] diff --git a/primitives/runtime/Cargo.toml b/primitives/runtime/Cargo.toml index 4202110cd6..79bbda700d 100644 --- a/primitives/runtime/Cargo.toml +++ b/primitives/runtime/Cargo.toml @@ -20,7 +20,7 @@ hash256-std-hasher = { version = "0.15.2", default-features = false } impl-trait-for-tuples = "0.2.2" log = { version = "0.4.17", default-features = false } paste = "1.0" -rand = { version = "0.7.2", optional = true } +rand = { version = "0.8.5", optional = true } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", features = ["derive"], optional = true } sp-application-crypto = { version = "7.0.0", default-features = false, path = "../application-crypto" } @@ -31,7 +31,7 @@ sp-std = { version = "5.0.0", default-features = false, path = "../std" } sp-weights = { version = "4.0.0", default-features = false, path = "../weights" } [dev-dependencies] -rand = "0.7.2" +rand = "0.8.5" serde_json = "1.0.85" zstd = { version = "0.11.2", default-features = false } sp-api = { version = "4.0.0-dev", path = "../api" } diff --git a/primitives/state-machine/Cargo.toml b/primitives/state-machine/Cargo.toml index 98794db60d..06419c5925 100644 --- a/primitives/state-machine/Cargo.toml +++ b/primitives/state-machine/Cargo.toml @@ -17,13 +17,11 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } hash-db = { version = "0.15.2", default-features = false } log = { version = "0.4.17", optional = true } -num-traits = { version = "0.2.8", default-features = false } parking_lot = { version = "0.12.1", optional = true } -rand = { version = "0.7.2", optional = true } +rand = { version = "0.8.5", optional = true } smallvec = "1.8.0" thiserror = { version = "1.0.30", optional = true } tracing = { version = "0.1.29", optional = true } -trie-root = { version = "0.17.0", default-features = false } sp-core = { version = "7.0.0", default-features = false, path = "../core" } sp-externalities = { version = "0.13.0", default-features = false, path = "../externalities" } sp-panic-handler = { version = "5.0.0", optional = true, path = "../panic-handler" } @@ -33,7 +31,7 @@ sp-trie = { version = "7.0.0", default-features = false, path = "../trie" } [dev-dependencies] array-bytes = "4.1" pretty_assertions = "1.2.1" -rand = "0.7.2" +rand = "0.8.5" sp-runtime = { version = "7.0.0", path = "../runtime" } trie-db = "0.24.0" assert_matches = "1.5" @@ -44,7 +42,6 @@ std = [ "codec/std", "hash-db/std", "log", - "num-traits/std", "parking_lot", "rand", "sp-core/std", @@ -54,5 +51,4 @@ std = [ "sp-trie/std", "thiserror", "tracing", - "trie-root/std", ] diff --git a/primitives/state-machine/src/lib.rs b/primitives/state-machine/src/lib.rs index 225fe1582e..188fb2f4b8 100644 --- a/primitives/state-machine/src/lib.rs +++ b/primitives/state-machine/src/lib.rs @@ -1845,7 +1845,7 @@ mod tests { use rand::{rngs::SmallRng, RngCore, SeedableRng}; let mut storage: HashMap, BTreeMap> = Default::default(); - let mut seed = [0; 16]; + let mut seed = [0; 32]; for i in 0..50u32 { let mut child_infos = Vec::new(); let seed_partial = &mut seed[0..4]; diff --git a/primitives/timestamp/Cargo.toml b/primitives/timestamp/Cargo.toml index 72266a48b0..5d015086c4 100644 --- a/primitives/timestamp/Cargo.toml +++ b/primitives/timestamp/Cargo.toml @@ -18,7 +18,6 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = futures-timer = { version = "3.0.2", optional = true } log = { version = "0.4.17", optional = true } thiserror = { version = "1.0.30", optional = true } -sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../inherents" } sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } sp-std = { version = "5.0.0", default-features = false, path = "../std" } @@ -30,7 +29,6 @@ std = [ "codec/std", "futures-timer", "log", - "sp-api/std", "sp-inherents/std", "sp-runtime/std", "sp-std/std", diff --git a/primitives/weights/Cargo.toml b/primitives/weights/Cargo.toml index 0099645836..00da83069e 100644 --- a/primitives/weights/Cargo.toml +++ b/primitives/weights/Cargo.toml @@ -14,7 +14,6 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -impl-trait-for-tuples = "0.2.2" scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", optional = true, features = ["derive"] } smallvec = "1.8.0" diff --git a/utils/frame/benchmarking-cli/Cargo.toml b/utils/frame/benchmarking-cli/Cargo.toml index 5173340f7d..9e231b8173 100644 --- a/utils/frame/benchmarking-cli/Cargo.toml +++ b/utils/frame/benchmarking-cli/Cargo.toml @@ -19,20 +19,15 @@ clap = { version = "4.0.9", features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.0.0" } comfy-table = { version = "6.0.0", default-features = false } handlebars = "4.2.2" -hash-db = "0.15.2" Inflector = "0.11.4" itertools = "0.10.3" -kvdb = "0.13.0" lazy_static = "1.4.0" linked-hash-map = "0.5.4" log = "0.4.17" -memory-db = "0.31.0" rand = { version = "0.8.4", features = ["small_rng"] } rand_pcg = "0.3.1" serde = "1.0.136" serde_json = "1.0.85" -serde_nanos = "0.1.2" -tempfile = "3.2.0" thiserror = "1.0.30" thousands = "0.2.0" frame-benchmarking = { version = "4.0.0-dev", path = "../../../frame/benchmarking" } diff --git a/utils/frame/generate-bags/Cargo.toml b/utils/frame/generate-bags/Cargo.toml index 04d598c784..6006cdb5da 100644 --- a/utils/frame/generate-bags/Cargo.toml +++ b/utils/frame/generate-bags/Cargo.toml @@ -15,9 +15,6 @@ frame-election-provider-support = { version = "4.0.0-dev", path = "../../../fram frame-system = { version = "4.0.0-dev", path = "../../../frame/system" } pallet-staking = { version = "4.0.0-dev", path = "../../../frame/staking" } -# primitives -sp-io = { version = "7.0.0", path = "../../../primitives/io" } - # third party chrono = { version = "0.4.19" } git2 = { version = "0.14.2", default-features = false } diff --git a/utils/frame/remote-externalities/Cargo.toml b/utils/frame/remote-externalities/Cargo.toml index ad8230fe29..5a51dbc661 100644 --- a/utils/frame/remote-externalities/Cargo.toml +++ b/utils/frame/remote-externalities/Cargo.toml @@ -13,15 +13,12 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } -env_logger = "0.9" log = "0.4.17" serde = "1.0.136" -serde_json = "1.0" frame-support = { version = "4.0.0-dev", optional = true, path = "../../../frame/support" } sp-core = { version = "7.0.0", path = "../../../primitives/core" } sp-io = { version = "7.0.0", path = "../../../primitives/io" } sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } -sp-version = { version = "5.0.0", path = "../../../primitives/version" } tokio = { version = "1.22.0", features = ["macros", "rt-multi-thread"] } substrate-rpc-client = { path = "../rpc/client" } futures = "0.3" diff --git a/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml b/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml index 3a1b4b8b6c..dd6456f714 100644 --- a/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml +++ b/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml @@ -18,8 +18,6 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = serde = { version = "1", features = ["derive"] } log = { version = "0.4.17", default-features = false } -sp-std = { path = "../../../../primitives/std" } -sp-io = { path = "../../../../primitives/io" } sp-core = { path = "../../../../primitives/core" } sp-state-machine = { path = "../../../../primitives/state-machine" } sp-trie = { path = "../../../../primitives/trie" } diff --git a/utils/frame/rpc/support/Cargo.toml b/utils/frame/rpc/support/Cargo.toml index d098877e73..0660384692 100644 --- a/utils/frame/rpc/support/Cargo.toml +++ b/utils/frame/rpc/support/Cargo.toml @@ -16,7 +16,6 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } -futures = "0.3.21" jsonrpsee = { version = "0.16.2", features = ["jsonrpsee-types"] } serde = "1" frame-support = { version = "4.0.0-dev", path = "../../../../frame/support" } diff --git a/utils/frame/rpc/system/Cargo.toml b/utils/frame/rpc/system/Cargo.toml index 92cf6882a1..55fc4b27db 100644 --- a/utils/frame/rpc/system/Cargo.toml +++ b/utils/frame/rpc/system/Cargo.toml @@ -13,13 +13,11 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde_json = "1" codec = { package = "parity-scale-codec", version = "3.0.0" } jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } futures = "0.3.21" log = "0.4.17" frame-system-rpc-runtime-api = { version = "4.0.0-dev", path = "../../../../frame/system/rpc/runtime-api" } -sc-client-api = { version = "4.0.0-dev", path = "../../../../client/api" } sc-rpc-api = { version = "0.10.0-dev", path = "../../../../client/rpc-api" } sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../../../client/transaction-pool/api" } sp-api = { version = "4.0.0-dev", path = "../../../../primitives/api" } diff --git a/utils/frame/try-runtime/cli/Cargo.toml b/utils/frame/try-runtime/cli/Cargo.toml index d6f211392c..c4cc2e757d 100644 --- a/utils/frame/try-runtime/cli/Cargo.toml +++ b/utils/frame/try-runtime/cli/Cargo.toml @@ -13,7 +13,6 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] remote-externalities = { version = "0.10.0-dev", path = "../../remote-externalities", package = "frame-remote-externalities" } -sc-chain-spec = { version = "4.0.0-dev", path = "../../../../client/chain-spec" } sc-cli = { version = "0.10.0-dev", path = "../../../../client/cli" } sc-executor = { version = "0.10.0-dev", path = "../../../../client/executor" } sc-service = { version = "0.10.0-dev", default-features = false, path = "../../../../client/service" } diff --git a/utils/prometheus/Cargo.toml b/utils/prometheus/Cargo.toml index 1371fe6f40..e84a6f4b30 100644 --- a/utils/prometheus/Cargo.toml +++ b/utils/prometheus/Cargo.toml @@ -13,7 +13,6 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -futures-util = { version = "0.3.19", default-features = false, features = ["io"] } hyper = { version = "0.14.16", default-features = false, features = ["http1", "server", "tcp"] } log = "0.4.17" prometheus = { version = "0.13.0", default-features = false } From 1f0865c4f8cde8589e36f70b142db36219115f23 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Mon, 19 Dec 2022 11:18:32 +0100 Subject: [PATCH 055/127] sc-network-test::Peer: block push methods return hashes vec (#12944) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * sc-network-test::Peer: block push methods return hashes vec This commit reworks the block generation/push methods in sc-network-test::Peer. Now methods are providing the vector of hashes that were built. This allows to get rid of redundant `block_hash_from_id` call, as all hashes are known just after being built. Similar approach was taken in BeefyTestNet::generate_blocks_and_sync method. This PR is part of BlockId::Number refactoring analysis (paritytech/substrate#11292) * fix * Apply suggestions from code review Co-authored-by: Bastian Köcher Co-authored-by: Bastian Köcher --- bin/node/bench/src/import.rs | 9 +- client/beefy/src/tests.rs | 120 +++++++++++----------- client/beefy/src/worker.rs | 8 +- client/finality-grandpa/src/tests.rs | 97 +++++++++--------- client/network/test/src/lib.rs | 32 +++--- client/network/test/src/sync.rs | 143 ++++++++++++++------------- 6 files changed, 212 insertions(+), 197 deletions(-) diff --git a/bin/node/bench/src/import.rs b/bin/node/bench/src/import.rs index 28a3228342..7d59e94a44 100644 --- a/bin/node/bench/src/import.rs +++ b/bin/node/bench/src/import.rs @@ -34,7 +34,7 @@ use std::borrow::Cow; use node_primitives::Block; use node_testing::bench::{BenchDb, BlockType, DatabaseType, KeyTypes, Profile}; -use sc_client_api::{backend::Backend, HeaderBackend}; +use sc_client_api::backend::Backend; use sp_runtime::generic::BlockId; use sp_state_machine::InspectState; @@ -127,15 +127,10 @@ impl core::Benchmark for ImportBenchmark { context.import_block(self.block.clone()); let elapsed = start.elapsed(); - let hash = context - .client - .expect_block_hash_from_id(&BlockId::number(1)) - .expect("Block 1 was imported; qed"); - // Sanity checks. context .client - .state_at(hash) + .state_at(self.block.header.hash()) .expect("state_at failed for block#1") .inspect_state(|| { match self.block_type { diff --git a/client/beefy/src/tests.rs b/client/beefy/src/tests.rs index d2befc979b..055c2669eb 100644 --- a/client/beefy/src/tests.rs +++ b/client/beefy/src/tests.rs @@ -139,14 +139,26 @@ impl BeefyTestNet { }); } + /// Builds the blocks and returns the vector of built block hashes. + /// Returned vector contains the genesis hash which allows for easy indexing (block number is + /// equal to index) pub(crate) async fn generate_blocks_and_sync( &mut self, count: usize, session_length: u64, validator_set: &BeefyValidatorSet, include_mmr_digest: bool, - ) { - self.peer(0).generate_blocks(count, BlockOrigin::File, |builder| { + ) -> Vec { + let mut all_hashes = Vec::with_capacity(count + 1); + + // make sure genesis is the only block in network, so we can insert genesis at he beginning + // of hashes, otherwise indexing would be broken + assert!(self.peer(0).client().as_backend().blockchain().hash(1).unwrap().is_none()); + + // push genesis to make indexing human readable (index equals to block number) + all_hashes.push(self.peer(0).client().info().genesis_hash); + + let built_hashes = self.peer(0).generate_blocks(count, BlockOrigin::File, |builder| { let mut block = builder.build().unwrap().block; if include_mmr_digest { @@ -162,7 +174,10 @@ impl BeefyTestNet { block }); + all_hashes.extend(built_hashes); self.run_until_sync().await; + + all_hashes } } @@ -495,7 +510,7 @@ async fn finalize_block_and_wait_for_beefy( net: &Arc>, // peer index and key peers: impl Iterator + Clone, - finalize_targets: &[u64], + finalize_targets: &[H256], expected_beefy: &[u64], ) { let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers.clone()); @@ -503,8 +518,7 @@ async fn finalize_block_and_wait_for_beefy( for block in finalize_targets { peers.clone().for_each(|(index, _)| { let client = net.lock().peer(index).client().as_client(); - let finalize = client.expect_block_hash_from_id(&BlockId::number(*block)).unwrap(); - client.finalize_block(finalize, None).unwrap(); + client.finalize_block(*block, None).unwrap(); }) } @@ -536,7 +550,7 @@ async fn beefy_finalizing_blocks() { tokio::spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta)); // push 42 blocks including `AuthorityChange` digests every 10 blocks. - net.generate_blocks_and_sync(42, session_len, &validator_set, true).await; + let hashes = net.generate_blocks_and_sync(42, session_len, &validator_set, true).await; let net = Arc::new(Mutex::new(net)); @@ -544,19 +558,19 @@ async fn beefy_finalizing_blocks() { let peers = peers.into_iter().enumerate(); // finalize block #5 -> BEEFY should finalize #1 (mandatory) and #5 from diff-power-of-two rule. - finalize_block_and_wait_for_beefy(&net, peers.clone(), &[1, 5], &[1, 5]).await; + finalize_block_and_wait_for_beefy(&net, peers.clone(), &[hashes[1], hashes[5]], &[1, 5]).await; // GRANDPA finalize #10 -> BEEFY finalize #10 (mandatory) - finalize_block_and_wait_for_beefy(&net, peers.clone(), &[10], &[10]).await; + finalize_block_and_wait_for_beefy(&net, peers.clone(), &[hashes[10]], &[10]).await; // GRANDPA finalize #18 -> BEEFY finalize #14, then #18 (diff-power-of-two rule) - finalize_block_and_wait_for_beefy(&net, peers.clone(), &[18], &[14, 18]).await; + finalize_block_and_wait_for_beefy(&net, peers.clone(), &[hashes[18]], &[14, 18]).await; // GRANDPA finalize #20 -> BEEFY finalize #20 (mandatory) - finalize_block_and_wait_for_beefy(&net, peers.clone(), &[20], &[20]).await; + finalize_block_and_wait_for_beefy(&net, peers.clone(), &[hashes[20]], &[20]).await; // GRANDPA finalize #21 -> BEEFY finalize nothing (yet) because min delta is 4 - finalize_block_and_wait_for_beefy(&net, peers, &[21], &[]).await; + finalize_block_and_wait_for_beefy(&net, peers, &[hashes[21]], &[]).await; } #[tokio::test] @@ -574,23 +588,23 @@ async fn lagging_validators() { tokio::spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta)); // push 62 blocks including `AuthorityChange` digests every 30 blocks. - net.generate_blocks_and_sync(62, session_len, &validator_set, true).await; + let hashes = net.generate_blocks_and_sync(62, session_len, &validator_set, true).await; let net = Arc::new(Mutex::new(net)); let peers = peers.into_iter().enumerate(); // finalize block #15 -> BEEFY should finalize #1 (mandatory) and #9, #13, #14, #15 from // diff-power-of-two rule. - finalize_block_and_wait_for_beefy(&net, peers.clone(), &[1, 15], &[1, 9, 13, 14, 15]).await; + finalize_block_and_wait_for_beefy( + &net, + peers.clone(), + &[hashes[1], hashes[15]], + &[1, 9, 13, 14, 15], + ) + .await; // Alice finalizes #25, Bob lags behind - let finalize = net - .lock() - .peer(0) - .client() - .as_client() - .expect_block_hash_from_id(&BlockId::number(25)) - .unwrap(); + let finalize = hashes[25]; let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers.clone()); net.lock().peer(0).client().as_client().finalize_block(finalize, None).unwrap(); // verify nothing gets finalized by BEEFY @@ -606,7 +620,13 @@ async fn lagging_validators() { wait_for_beefy_signed_commitments(versioned_finality_proof, &net, &[23, 24, 25]).await; // Both finalize #30 (mandatory session) and #32 -> BEEFY finalize #30 (mandatory), #31, #32 - finalize_block_and_wait_for_beefy(&net, peers.clone(), &[30, 32], &[30, 31, 32]).await; + finalize_block_and_wait_for_beefy( + &net, + peers.clone(), + &[hashes[30], hashes[32]], + &[30, 31, 32], + ) + .await; // Verify that session-boundary votes get buffered by client and only processed once // session-boundary block is GRANDPA-finalized (this guarantees authenticity for the new session @@ -614,13 +634,7 @@ async fn lagging_validators() { // Alice finalizes session-boundary mandatory block #60, Bob lags behind let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers.clone()); - let finalize = net - .lock() - .peer(0) - .client() - .as_client() - .expect_block_hash_from_id(&BlockId::number(60)) - .unwrap(); + let finalize = hashes[60]; net.lock().peer(0).client().as_client().finalize_block(finalize, None).unwrap(); // verify nothing gets finalized by BEEFY let timeout = Some(Duration::from_millis(250)); @@ -661,24 +675,18 @@ async fn correct_beefy_payload() { tokio::spawn(initialize_beefy(&mut net, bad_peers, min_block_delta)); // push 12 blocks - net.generate_blocks_and_sync(12, session_len, &validator_set, false).await; + let hashes = net.generate_blocks_and_sync(12, session_len, &validator_set, false).await; let net = Arc::new(Mutex::new(net)); let peers = peers.into_iter().enumerate(); // with 3 good voters and 1 bad one, consensus should happen and best blocks produced. - finalize_block_and_wait_for_beefy(&net, peers, &[1, 10], &[1, 9]).await; + finalize_block_and_wait_for_beefy(&net, peers, &[hashes[1], hashes[10]], &[1, 9]).await; let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), [(0, BeefyKeyring::Alice)].into_iter()); // now 2 good validators and 1 bad one are voting - let hashof11 = net - .lock() - .peer(0) - .client() - .as_client() - .expect_block_hash_from_id(&BlockId::number(11)) - .unwrap(); + let hashof11 = hashes[11]; net.lock().peer(0).client().as_client().finalize_block(hashof11, None).unwrap(); net.lock().peer(1).client().as_client().finalize_block(hashof11, None).unwrap(); net.lock().peer(3).client().as_client().finalize_block(hashof11, None).unwrap(); @@ -863,7 +871,7 @@ async fn voter_initialization() { tokio::spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta)); // push 26 blocks - net.generate_blocks_and_sync(26, session_len, &validator_set, false).await; + let hashes = net.generate_blocks_and_sync(26, session_len, &validator_set, false).await; let net = Arc::new(Mutex::new(net)); // Finalize multiple blocks at once to get a burst of finality notifications right from start. @@ -872,7 +880,7 @@ async fn voter_initialization() { finalize_block_and_wait_for_beefy( &net, peers.into_iter().enumerate(), - &[1, 6, 10, 17, 24, 26], + &[hashes[1], hashes[6], hashes[10], hashes[17], hashes[24], hashes[26]], &[1, 5, 10, 15, 20, 25], ) .await; @@ -905,7 +913,7 @@ async fn on_demand_beefy_justification_sync() { let dave_index = 3; // push 30 blocks - net.generate_blocks_and_sync(30, session_len, &validator_set, false).await; + let hashes = net.generate_blocks_and_sync(30, session_len, &validator_set, false).await; let fast_peers = fast_peers.into_iter().enumerate(); let net = Arc::new(Mutex::new(net)); @@ -914,7 +922,7 @@ async fn on_demand_beefy_justification_sync() { finalize_block_and_wait_for_beefy( &net, fast_peers.clone(), - &[1, 6, 10, 17, 24], + &[hashes[1], hashes[6], hashes[10], hashes[17], hashes[24]], &[1, 5, 10, 15, 20], ) .await; @@ -927,14 +935,13 @@ async fn on_demand_beefy_justification_sync() { let (dave_best_blocks, _) = get_beefy_streams(&mut net.lock(), [(dave_index, BeefyKeyring::Dave)].into_iter()); let client = net.lock().peer(dave_index).client().as_client(); - let hashof1 = client.expect_block_hash_from_id(&BlockId::number(1)).unwrap(); - client.finalize_block(hashof1, None).unwrap(); + client.finalize_block(hashes[1], None).unwrap(); // Give Dave task some cpu cycles to process the finality notification, run_for(Duration::from_millis(100), &net).await; // freshly spun up Dave now needs to listen for gossip to figure out the state of his peers. // Have the other peers do some gossip so Dave finds out about their progress. - finalize_block_and_wait_for_beefy(&net, fast_peers, &[25], &[25]).await; + finalize_block_and_wait_for_beefy(&net, fast_peers, &[hashes[25]], &[25]).await; // Now verify Dave successfully finalized #1 (through on-demand justification request). wait_for_best_beefy_blocks(dave_best_blocks, &net, &[1]).await; @@ -945,14 +952,14 @@ async fn on_demand_beefy_justification_sync() { finalize_block_and_wait_for_beefy( &net, [(dave_index, BeefyKeyring::Dave)].into_iter(), - &[6, 10, 17, 24, 26], + &[hashes[6], hashes[10], hashes[17], hashes[24], hashes[26]], &[5, 10, 15, 20, 25], ) .await; let all_peers = all_peers.into_iter().enumerate(); // Now that Dave has caught up, sanity check voting works for all of them. - finalize_block_and_wait_for_beefy(&net, all_peers, &[30], &[30]).await; + finalize_block_and_wait_for_beefy(&net, all_peers, &[hashes[30]], &[30]).await; } #[tokio::test] @@ -963,13 +970,12 @@ async fn should_initialize_voter_at_genesis() { let backend = net.peer(0).client().as_backend(); // push 15 blocks with `AuthorityChange` digests every 10 blocks - net.generate_blocks_and_sync(15, 10, &validator_set, false).await; + let hashes = net.generate_blocks_and_sync(15, 10, &validator_set, false).await; let mut finality = net.peer(0).client().as_client().finality_notification_stream().fuse(); // finalize 13 without justifications - let hashof13 = backend.blockchain().expect_block_hash_from_id(&BlockId::Number(13)).unwrap(); - net.peer(0).client().as_client().finalize_block(hashof13, None).unwrap(); + net.peer(0).client().as_client().finalize_block(hashes[13], None).unwrap(); // load persistent state - nothing in DB, should init at session boundary let persisted_state = voter_init_setup(&mut net, &mut finality).unwrap(); @@ -1008,13 +1014,12 @@ async fn should_initialize_voter_when_last_final_is_session_boundary() { let backend = net.peer(0).client().as_backend(); // push 15 blocks with `AuthorityChange` digests every 10 blocks - net.generate_blocks_and_sync(15, 10, &validator_set, false).await; + let hashes = net.generate_blocks_and_sync(15, 10, &validator_set, false).await; let mut finality = net.peer(0).client().as_client().finality_notification_stream().fuse(); // finalize 13 without justifications - let hashof13 = backend.blockchain().expect_block_hash_from_id(&BlockId::Number(13)).unwrap(); - net.peer(0).client().as_client().finalize_block(hashof13, None).unwrap(); + net.peer(0).client().as_client().finalize_block(hashes[13], None).unwrap(); // import/append BEEFY justification for session boundary block 10 let commitment = Commitment { @@ -1026,9 +1031,8 @@ async fn should_initialize_voter_when_last_final_is_session_boundary() { commitment, signatures: vec![None], }); - let hashof10 = backend.blockchain().expect_block_hash_from_id(&BlockId::Number(10)).unwrap(); backend - .append_justification(hashof10, (BEEFY_ENGINE_ID, justif.encode())) + .append_justification(hashes[10], (BEEFY_ENGINE_ID, justif.encode())) .unwrap(); // Test corner-case where session boundary == last beefy finalized, @@ -1068,13 +1072,12 @@ async fn should_initialize_voter_at_latest_finalized() { let backend = net.peer(0).client().as_backend(); // push 15 blocks with `AuthorityChange` digests every 10 blocks - net.generate_blocks_and_sync(15, 10, &validator_set, false).await; + let hashes = net.generate_blocks_and_sync(15, 10, &validator_set, false).await; let mut finality = net.peer(0).client().as_client().finality_notification_stream().fuse(); // finalize 13 without justifications - let hashof13 = backend.blockchain().expect_block_hash_from_id(&BlockId::Number(13)).unwrap(); - net.peer(0).client().as_client().finalize_block(hashof13, None).unwrap(); + net.peer(0).client().as_client().finalize_block(hashes[13], None).unwrap(); // import/append BEEFY justification for block 12 let commitment = Commitment { @@ -1086,9 +1089,8 @@ async fn should_initialize_voter_at_latest_finalized() { commitment, signatures: vec![None], }); - let hashof12 = backend.blockchain().expect_block_hash_from_id(&BlockId::Number(12)).unwrap(); backend - .append_justification(hashof12, (BEEFY_ENGINE_ID, justif.encode())) + .append_justification(hashes[12], (BEEFY_ENGINE_ID, justif.encode())) .unwrap(); // Test initialization at last BEEFY finalized. diff --git a/client/beefy/src/worker.rs b/client/beefy/src/worker.rs index df465b2dbf..89d231a6c2 100644 --- a/client/beefy/src/worker.rs +++ b/client/beefy/src/worker.rs @@ -1382,10 +1382,10 @@ pub(crate) mod tests { // generate 2 blocks, try again expect success let (mut best_block_streams, _) = get_beefy_streams(&mut net, keys); let mut best_block_stream = best_block_streams.drain(..).next().unwrap(); - net.peer(0).push_blocks(2, false); - // finalize 1 and 2 without justifications - let hashof1 = backend.blockchain().expect_block_hash_from_id(&BlockId::Number(1)).unwrap(); - let hashof2 = backend.blockchain().expect_block_hash_from_id(&BlockId::Number(2)).unwrap(); + let hashes = net.peer(0).push_blocks(2, false); + // finalize 1 and 2 without justifications (hashes does not contain genesis) + let hashof1 = hashes[0]; + let hashof2 = hashes[1]; backend.finalize_block(hashof1, None).unwrap(); backend.finalize_block(hashof2, None).unwrap(); diff --git a/client/finality-grandpa/src/tests.rs b/client/finality-grandpa/src/tests.rs index 97c0c9fe7c..d5bc7d50c5 100644 --- a/client/finality-grandpa/src/tests.rs +++ b/client/finality-grandpa/src/tests.rs @@ -627,14 +627,18 @@ async fn sync_justifications_on_change_blocks() { net.peer(0).push_blocks(20, false); // at block 21 we do add a transition which is instant - let hashof21 = net.peer(0).generate_blocks(1, BlockOrigin::File, |builder| { - let mut block = builder.build().unwrap().block; - add_scheduled_change( - &mut block, - ScheduledChange { next_authorities: make_ids(peers_b), delay: 0 }, - ); - block - }); + let hashof21 = net + .peer(0) + .generate_blocks(1, BlockOrigin::File, |builder| { + let mut block = builder.build().unwrap().block; + add_scheduled_change( + &mut block, + ScheduledChange { next_authorities: make_ids(peers_b), delay: 0 }, + ); + block + }) + .pop() + .unwrap(); // add more blocks on top of it (until we have 25) net.peer(0).push_blocks(4, false); @@ -1352,7 +1356,7 @@ async fn grandpa_environment_respects_voting_rules() { let link = peer.data.lock().take().unwrap(); // add 21 blocks - peer.push_blocks(21, false); + let hashes = peer.push_blocks(21, false); // create an environment with no voting rule restrictions let unrestricted_env = test_environment(&link, None, network_service.clone(), ()); @@ -1408,12 +1412,7 @@ async fn grandpa_environment_respects_voting_rules() { ); // we finalize block 19 with block 21 being the best block - let hashof19 = peer - .client() - .as_client() - .expect_block_hash_from_id(&BlockId::Number(19)) - .unwrap(); - peer.client().finalize_block(hashof19, None, false).unwrap(); + peer.client().finalize_block(hashes[18], None, false).unwrap(); // the 3/4 environment should propose block 21 for voting assert_eq!( @@ -1439,11 +1438,7 @@ async fn grandpa_environment_respects_voting_rules() { ); // we finalize block 21 with block 21 being the best block - let hashof21 = peer - .client() - .as_client() - .expect_block_hash_from_id(&BlockId::Number(21)) - .unwrap(); + let hashof21 = hashes[20]; peer.client().finalize_block(hashof21, None, false).unwrap(); // even though the default environment will always try to not vote on the @@ -1757,20 +1752,23 @@ async fn revert_prunes_authority_changes() { // Fork before revert point // add more blocks on top of block 23 (until we have 26) - let hash = peer.generate_blocks_at( - BlockId::Number(23), - 3, - BlockOrigin::File, - |builder| { - let mut block = builder.build().unwrap().block; - block.header.digest_mut().push(DigestItem::Other(vec![1])); - block - }, - false, - false, - true, - ForkChoiceStrategy::LongestChain, - ); + let hash = peer + .generate_blocks_at( + BlockId::Number(23), + 3, + BlockOrigin::File, + |builder| { + let mut block = builder.build().unwrap().block; + block.header.digest_mut().push(DigestItem::Other(vec![1])); + block + }, + false, + false, + true, + ForkChoiceStrategy::LongestChain, + ) + .pop() + .unwrap(); // at block 27 of the fork add an authority transition peer.generate_blocks_at( BlockId::Hash(hash), @@ -1786,20 +1784,23 @@ async fn revert_prunes_authority_changes() { // Fork after revert point // add more block on top of block 25 (until we have 28) - let hash = peer.generate_blocks_at( - BlockId::Number(25), - 3, - BlockOrigin::File, - |builder| { - let mut block = builder.build().unwrap().block; - block.header.digest_mut().push(DigestItem::Other(vec![2])); - block - }, - false, - false, - true, - ForkChoiceStrategy::LongestChain, - ); + let hash = peer + .generate_blocks_at( + BlockId::Number(25), + 3, + BlockOrigin::File, + |builder| { + let mut block = builder.build().unwrap().block; + block.header.digest_mut().push(DigestItem::Other(vec![2])); + block + }, + false, + false, + true, + ForkChoiceStrategy::LongestChain, + ) + .pop() + .unwrap(); // at block 29 of the fork add an authority transition peer.generate_blocks_at( BlockId::Hash(hash), diff --git a/client/network/test/src/lib.rs b/client/network/test/src/lib.rs index c3304df629..a7b963555c 100644 --- a/client/network/test/src/lib.rs +++ b/client/network/test/src/lib.rs @@ -297,7 +297,12 @@ where } /// Add blocks to the peer -- edit the block before adding - pub fn generate_blocks(&mut self, count: usize, origin: BlockOrigin, edit_block: F) -> H256 + pub fn generate_blocks( + &mut self, + count: usize, + origin: BlockOrigin, + edit_block: F, + ) -> Vec where F: FnMut( BlockBuilder, @@ -323,7 +328,7 @@ where origin: BlockOrigin, edit_block: F, fork_choice: ForkChoiceStrategy, - ) -> H256 + ) -> Vec where F: FnMut( BlockBuilder, @@ -354,12 +359,13 @@ where inform_sync_about_new_best_block: bool, announce_block: bool, fork_choice: ForkChoiceStrategy, - ) -> H256 + ) -> Vec where F: FnMut( BlockBuilder, ) -> Block, { + let mut hashes = Vec::with_capacity(count); let full_client = self.client.as_client(); let mut at = full_client.header(&at).unwrap().unwrap().hash(); for _ in 0..count { @@ -391,6 +397,7 @@ where if announce_block { self.network.service().announce_block(hash, None); } + hashes.push(hash); at = hash; } @@ -400,24 +407,24 @@ where *full_client.header(&BlockId::Hash(at)).ok().flatten().unwrap().number(), ); } - at + hashes } /// Push blocks to the peer (simplified: with or without a TX) - pub fn push_blocks(&mut self, count: usize, with_tx: bool) -> H256 { + pub fn push_blocks(&mut self, count: usize, with_tx: bool) -> Vec { let best_hash = self.client.info().best_hash; self.push_blocks_at(BlockId::Hash(best_hash), count, with_tx) } /// Push blocks to the peer (simplified: with or without a TX) - pub fn push_headers(&mut self, count: usize) -> H256 { + pub fn push_headers(&mut self, count: usize) -> Vec { let best_hash = self.client.info().best_hash; self.generate_tx_blocks_at(BlockId::Hash(best_hash), count, false, true, true, true) } /// Push blocks to the peer (simplified: with or without a TX) starting from /// given hash. - pub fn push_blocks_at(&mut self, at: BlockId, count: usize, with_tx: bool) -> H256 { + pub fn push_blocks_at(&mut self, at: BlockId, count: usize, with_tx: bool) -> Vec { self.generate_tx_blocks_at(at, count, with_tx, false, true, true) } @@ -429,7 +436,7 @@ where count: usize, with_tx: bool, announce_block: bool, - ) -> H256 { + ) -> Vec { self.generate_tx_blocks_at(at, count, with_tx, false, false, announce_block) } @@ -440,7 +447,7 @@ where at: BlockId, count: usize, with_tx: bool, - ) -> H256 { + ) -> Vec { self.generate_tx_blocks_at(at, count, with_tx, false, true, false) } @@ -454,7 +461,7 @@ where headers_only: bool, inform_sync_about_new_best_block: bool, announce_block: bool, - ) -> H256 { + ) -> Vec { let mut nonce = 0; if with_tx { self.generate_blocks_at( @@ -491,7 +498,10 @@ where } } - pub fn push_authorities_change_block(&mut self, new_authorities: Vec) -> H256 { + pub fn push_authorities_change_block( + &mut self, + new_authorities: Vec, + ) -> Vec { self.generate_blocks(1, BlockOrigin::File, |mut builder| { builder.push(Extrinsic::AuthoritiesChange(new_authorities.clone())).unwrap(); builder.build().unwrap().block diff --git a/client/network/test/src/sync.rs b/client/network/test/src/sync.rs index 9a9b5782dc..c7b651f457 100644 --- a/client/network/test/src/sync.rs +++ b/client/network/test/src/sync.rs @@ -250,13 +250,12 @@ async fn sync_no_common_longer_chain_fails() { async fn sync_justifications() { sp_tracing::try_init_simple(); let mut net = JustificationTestNet::new(3); - net.peer(0).push_blocks(20, false); + let hashes = net.peer(0).push_blocks(20, false); net.run_until_sync().await; - let backend = net.peer(0).client().as_backend(); - let hashof10 = backend.blockchain().expect_block_hash_from_id(&BlockId::Number(10)).unwrap(); - let hashof15 = backend.blockchain().expect_block_hash_from_id(&BlockId::Number(15)).unwrap(); - let hashof20 = backend.blockchain().expect_block_hash_from_id(&BlockId::Number(20)).unwrap(); + let hashof10 = hashes[9]; + let hashof15 = hashes[14]; + let hashof20 = hashes[19]; // there's currently no justification for block #10 assert_eq!(net.peer(0).client().justifications(hashof10).unwrap(), None); @@ -280,13 +279,13 @@ async fn sync_justifications() { futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); - for hash in [hashof10, hashof15, hashof20] { - if net.peer(0).client().justifications(hash).unwrap() != + for height in (10..21).step_by(5) { + if net.peer(0).client().justifications(hashes[height - 1]).unwrap() != Some(Justifications::from((*b"FRNK", Vec::new()))) { return Poll::Pending } - if net.peer(1).client().justifications(hash).unwrap() != + if net.peer(1).client().justifications(hashes[height - 1]).unwrap() != Some(Justifications::from((*b"FRNK", Vec::new()))) { return Poll::Pending @@ -305,8 +304,8 @@ async fn sync_justifications_across_forks() { // we push 5 blocks net.peer(0).push_blocks(5, false); // and then two forks 5 and 6 blocks long - let f1_best = net.peer(0).push_blocks_at(BlockId::Number(5), 5, false); - let f2_best = net.peer(0).push_blocks_at(BlockId::Number(5), 6, false); + let f1_best = net.peer(0).push_blocks_at(BlockId::Number(5), 5, false).pop().unwrap(); + let f2_best = net.peer(0).push_blocks_at(BlockId::Number(5), 6, false).pop().unwrap(); // peer 1 will only see the longer fork. but we'll request justifications // for both and finalize the small fork instead. @@ -364,8 +363,8 @@ async fn syncs_all_forks() { net.peer(0).push_blocks(2, false); net.peer(1).push_blocks(2, false); - let b1 = net.peer(0).push_blocks(2, true); - let b2 = net.peer(1).push_blocks(4, false); + let b1 = net.peer(0).push_blocks(2, true).pop().unwrap(); + let b2 = net.peer(1).push_blocks(4, false).pop().unwrap(); net.run_until_sync().await; // Check that all peers have all of the branches. @@ -446,7 +445,7 @@ async fn can_sync_small_non_best_forks() { .await; net.run_until_sync().await; - let another_fork = net.peer(0).push_blocks_at(BlockId::Number(35), 2, true); + let another_fork = net.peer(0).push_blocks_at(BlockId::Number(35), 2, true).pop().unwrap(); net.peer(0).announce_block(another_fork, None); futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); @@ -467,12 +466,16 @@ async fn can_sync_forks_ahead_of_the_best_chain() { net.run_until_connected().await; // Peer 0 is on 2-block fork which is announced with is_best=false - let fork_hash = net.peer(0).generate_blocks_with_fork_choice( - 2, - BlockOrigin::Own, - |builder| builder.build().unwrap().block, - ForkChoiceStrategy::Custom(false), - ); + let fork_hash = net + .peer(0) + .generate_blocks_with_fork_choice( + 2, + BlockOrigin::Own, + |builder| builder.build().unwrap().block, + ForkChoiceStrategy::Custom(false), + ) + .pop() + .unwrap(); // Peer 1 is on 1-block fork net.peer(1).push_blocks(1, false); assert!(net.peer(0).client().header(&BlockId::Hash(fork_hash)).unwrap().is_some()); @@ -575,8 +578,8 @@ async fn does_not_sync_announced_old_best_block() { sp_tracing::try_init_simple(); let mut net = TestNet::new(3); - let old_hash = net.peer(0).push_blocks(1, false); - let old_hash_with_parent = net.peer(0).push_blocks(1, false); + let old_hash = net.peer(0).push_blocks(1, false).pop().unwrap(); + let old_hash_with_parent = net.peer(0).push_blocks(1, false).pop().unwrap(); net.peer(0).push_blocks(18, true); net.peer(1).push_blocks(20, true); @@ -647,12 +650,12 @@ async fn imports_stale_once() { net.run_until_sync().await; // check that NEW block is imported from announce message - let new_hash = net.peer(0).push_blocks(1, false); + let new_hash = net.peer(0).push_blocks(1, false).pop().unwrap(); import_with_announce(&mut net, new_hash).await; assert_eq!(net.peer(1).num_downloaded_blocks(), 1); // check that KNOWN STALE block is imported from announce message - let known_stale_hash = net.peer(0).push_blocks_at(BlockId::Number(0), 1, true); + let known_stale_hash = net.peer(0).push_blocks_at(BlockId::Number(0), 1, true).pop().unwrap(); import_with_announce(&mut net, known_stale_hash).await; assert_eq!(net.peer(1).num_downloaded_blocks(), 2); } @@ -664,7 +667,7 @@ async fn can_sync_to_peers_with_wrong_common_block() { net.peer(0).push_blocks(2, true); net.peer(1).push_blocks(2, true); - let fork_hash = net.peer(0).push_blocks_at(BlockId::Number(0), 2, false); + let fork_hash = net.peer(0).push_blocks_at(BlockId::Number(0), 2, false).pop().unwrap(); net.peer(1).push_blocks_at(BlockId::Number(0), 2, false); // wait for connection net.run_until_connected().await; @@ -673,7 +676,7 @@ async fn can_sync_to_peers_with_wrong_common_block() { let just = Some((*b"FRNK", Vec::new())); net.peer(0).client().finalize_block(fork_hash, just.clone(), true).unwrap(); net.peer(1).client().finalize_block(fork_hash, just, true).unwrap(); - let final_hash = net.peer(0).push_blocks(1, false); + let final_hash = net.peer(0).push_blocks(1, false).pop().unwrap(); net.run_until_sync().await; @@ -731,12 +734,16 @@ async fn sync_blocks_when_block_announce_validator_says_it_is_new_best() { net.run_until_connected().await; // Add blocks but don't set them as best - let block_hash = net.peer(0).generate_blocks_with_fork_choice( - 1, - BlockOrigin::Own, - |builder| builder.build().unwrap().block, - ForkChoiceStrategy::Custom(false), - ); + let block_hash = net + .peer(0) + .generate_blocks_with_fork_choice( + 1, + BlockOrigin::Own, + |builder| builder.build().unwrap().block, + ForkChoiceStrategy::Custom(false), + ) + .pop() + .unwrap(); while !net.peer(2).has_block(block_hash) { net.run_until_idle().await; @@ -774,12 +781,16 @@ async fn wait_until_deferred_block_announce_validation_is_ready() { net.run_until_connected().await; // Add blocks but don't set them as best - let block_hash = net.peer(0).generate_blocks_with_fork_choice( - 1, - BlockOrigin::Own, - |builder| builder.build().unwrap().block, - ForkChoiceStrategy::Custom(false), - ); + let block_hash = net + .peer(0) + .generate_blocks_with_fork_choice( + 1, + BlockOrigin::Own, + |builder| builder.build().unwrap().block, + ForkChoiceStrategy::Custom(false), + ) + .pop() + .unwrap(); while !net.peer(1).has_block(block_hash) { net.run_until_idle().await; @@ -794,9 +805,11 @@ async fn sync_to_tip_requires_that_sync_protocol_is_informed_about_best_block() let mut net = TestNet::new(1); // Produce some blocks - let block_hash = - net.peer(0) - .push_blocks_at_without_informing_sync(BlockId::Number(0), 3, true, true); + let block_hash = net + .peer(0) + .push_blocks_at_without_informing_sync(BlockId::Number(0), 3, true, true) + .pop() + .unwrap(); // Add a node and wait until they are connected net.add_full_peer_with_config(Default::default()); @@ -838,9 +851,11 @@ async fn sync_to_tip_when_we_sync_together_with_multiple_peers() { let mut net = TestNet::new(3); - let block_hash = - net.peer(0) - .push_blocks_at_without_informing_sync(BlockId::Number(0), 10_000, false, false); + let block_hash = net + .peer(0) + .push_blocks_at_without_informing_sync(BlockId::Number(0), 10_000, false, false) + .pop() + .unwrap(); net.peer(1) .push_blocks_at_without_informing_sync(BlockId::Number(0), 5_000, false, false); @@ -912,7 +927,11 @@ async fn block_announce_data_is_propagated() { }) .await; - let block_hash = net.peer(0).push_blocks_at_without_announcing(BlockId::Number(0), 1, true); + let block_hash = net + .peer(0) + .push_blocks_at_without_announcing(BlockId::Number(0), 1, true) + .pop() + .unwrap(); net.peer(0).announce_block(block_hash, Some(vec![137])); while !net.peer(1).has_block(block_hash) || !net.peer(2).has_block(block_hash) { @@ -956,7 +975,7 @@ async fn continue_to_sync_after_some_block_announcement_verifications_failed() { net.run_until_connected().await; net.run_until_idle().await; - let block_hash = net.peer(0).push_blocks(500, true); + let block_hash = net.peer(0).push_blocks(500, true).pop().unwrap(); net.run_until_sync().await; assert!(net.peer(1).has_block(block_hash)); @@ -970,10 +989,10 @@ async fn continue_to_sync_after_some_block_announcement_verifications_failed() { async fn multiple_requests_are_accepted_as_long_as_they_are_not_fulfilled() { sp_tracing::try_init_simple(); let mut net = JustificationTestNet::new(2); - net.peer(0).push_blocks(10, false); + let hashes = net.peer(0).push_blocks(10, false); net.run_until_sync().await; - let hashof10 = net.peer(1).client().header(&BlockId::Number(10)).unwrap().unwrap().hash(); + let hashof10 = hashes[9]; // there's currently no justification for block #10 assert_eq!(net.peer(0).client().justifications(hashof10).unwrap(), None); @@ -992,14 +1011,8 @@ async fn multiple_requests_are_accepted_as_long_as_they_are_not_fulfilled() { assert_eq!(1, net.peer(0).num_peers()); } - let hashof10 = net - .peer(0) - .client() - .as_backend() - .blockchain() - .expect_block_hash_from_id(&BlockId::Number(10)) - .unwrap(); - // Finalize the block and make the justification available. + let hashof10 = hashes[9]; + // Finalize the 10th block and make the justification available. net.peer(0) .client() .finalize_block(hashof10, Some((*b"FRNK", Vec::new())), true) @@ -1030,7 +1043,7 @@ async fn syncs_all_forks_from_single_peer() { net.run_until_connected().await; // Peer 0 produces new blocks and announces. - let branch1 = net.peer(0).push_blocks_at(BlockId::Number(10), 2, true); + let branch1 = net.peer(0).push_blocks_at(BlockId::Number(10), 2, true).pop().unwrap(); // Wait till peer 1 starts downloading futures::future::poll_fn::<(), _>(|cx| { @@ -1043,7 +1056,7 @@ async fn syncs_all_forks_from_single_peer() { .await; // Peer 0 produces and announces another fork - let branch2 = net.peer(0).push_blocks_at(BlockId::Number(10), 2, false); + let branch2 = net.peer(0).push_blocks_at(BlockId::Number(10), 2, false).pop().unwrap(); net.run_until_sync().await; @@ -1070,7 +1083,7 @@ async fn syncs_after_missing_announcement() { // Peer 0 produces a new block and announces. Peer 1 ignores announcement. net.peer(0).push_blocks_at(BlockId::Number(10), 1, false); // Peer 0 produces another block and announces. - let final_block = net.peer(0).push_blocks_at(BlockId::Number(11), 1, false); + let final_block = net.peer(0).push_blocks_at(BlockId::Number(11), 1, false).pop().unwrap(); net.peer(1).push_blocks_at(BlockId::Number(10), 1, true); net.run_until_sync().await; assert!(net.peer(1).client().header(&BlockId::Hash(final_block)).unwrap().is_some()); @@ -1119,19 +1132,13 @@ async fn syncs_state() { config_two.sync_mode = SyncMode::Fast { skip_proofs: *skip_proofs, storage_chain_mode: false }; net.add_full_peer_with_config(config_two); - net.peer(0).push_blocks(64, false); + let hashes = net.peer(0).push_blocks(64, false); // Wait for peer 1 to sync header chain. net.run_until_sync().await; assert!(!net.peer(1).client().has_state_at(&BlockId::Number(64))); let just = (*b"FRNK", Vec::new()); - let hashof60 = net - .peer(0) - .client() - .as_backend() - .blockchain() - .expect_block_hash_from_id(&BlockId::Number(60)) - .unwrap(); + let hashof60 = hashes[59]; net.peer(1).client().finalize_block(hashof60, Some(just), true).unwrap(); // Wait for state sync. futures::future::poll_fn::<(), _>(|cx| { @@ -1222,8 +1229,8 @@ async fn warp_sync() { sync_mode: SyncMode::Warp, ..Default::default() }); - let gap_end = net.peer(0).push_blocks(63, false); - let target = net.peer(0).push_blocks(1, false); + let gap_end = net.peer(0).push_blocks(63, false).pop().unwrap(); + let target = net.peer(0).push_blocks(1, false).pop().unwrap(); net.peer(1).push_blocks(64, false); net.peer(2).push_blocks(64, false); // Wait for peer 1 to sync state. From cad470b6b68d6d2c79371dd67d2f915b37ab8304 Mon Sep 17 00:00:00 2001 From: Liu-Cheng Xu Date: Mon, 19 Dec 2022 22:26:31 +0800 Subject: [PATCH 056/127] Support custom genesis block (#12291) * Set genesis block data using the built genesis block * Make resolve_state_version_from_wasm a separate function and some small refactorings Useful for the commit following. * Introduce trait BuildGenesisBlock Substrate users can use this trait to implement their custom genesis block when constructing the client. * Make call_executor test compile * cargo +nightly fmt --all * Fix test * Remove unnecessary clone * FMT * Apply review suggestions * Revert changes to new_full_client() and new_full_parts() signature * Remove needless `Block` type in `resolve_state_version_from_wasm` --- bin/node/testing/src/bench.rs | 30 ++++-- client/service/src/builder.rs | 59 +++++++---- client/service/src/client/call_executor.rs | 33 +++--- client/service/src/client/client.rs | 118 +++++++++++---------- client/service/src/client/genesis.rs | 67 +++++++++++- client/service/src/client/mod.rs | 2 +- client/service/src/config.rs | 17 +++ client/service/src/lib.rs | 5 +- client/service/test/src/client/mod.rs | 19 +++- test-utils/client/src/lib.rs | 24 +++-- 10 files changed, 261 insertions(+), 113 deletions(-) diff --git a/bin/node/testing/src/bench.rs b/bin/node/testing/src/bench.rs index 59f1fa94c9..2151a61a6c 100644 --- a/bin/node/testing/src/bench.rs +++ b/bin/node/testing/src/bench.rs @@ -397,24 +397,34 @@ impl BenchDb { let task_executor = TaskExecutor::new(); let backend = sc_service::new_db_backend(db_config).expect("Should not fail"); + let executor = NativeElseWasmExecutor::new( + WasmExecutionMethod::Compiled { + instantiation_strategy: WasmtimeInstantiationStrategy::PoolingCopyOnWrite, + }, + None, + 8, + 2, + ); + let client_config = sc_service::ClientConfig::default(); + let genesis_block_builder = sc_service::GenesisBlockBuilder::new( + &keyring.generate_genesis(), + !client_config.no_genesis, + backend.clone(), + executor.clone(), + ) + .expect("Failed to create genesis block builder"); + let client = sc_service::new_client( backend.clone(), - NativeElseWasmExecutor::new( - WasmExecutionMethod::Compiled { - instantiation_strategy: WasmtimeInstantiationStrategy::PoolingCopyOnWrite, - }, - None, - 8, - 2, - ), - &keyring.generate_genesis(), + executor, + genesis_block_builder, None, None, ExecutionExtensions::new(profile.into_execution_strategies(), None, None), Box::new(task_executor.clone()), None, None, - Default::default(), + client_config, ) .expect("Should not fail"); diff --git a/client/service/src/builder.rs b/client/service/src/builder.rs index 7153672030..1c8f4c7de7 100644 --- a/client/service/src/builder.rs +++ b/client/service/src/builder.rs @@ -22,7 +22,8 @@ use crate::{ config::{Configuration, KeystoreConfig, PrometheusConfig}, error::Error, metrics::MetricsService, - start_rpc_servers, RpcHandlers, SpawnTaskHandle, TaskManager, TransactionPoolAdapter, + start_rpc_servers, BuildGenesisBlock, GenesisBlockBuilder, RpcHandlers, SpawnTaskHandle, + TaskManager, TransactionPoolAdapter, }; use futures::{channel::oneshot, future::ready, FutureExt, StreamExt}; use jsonrpsee::RpcModule; @@ -72,7 +73,6 @@ use sp_keystore::{CryptoStore, SyncCryptoStore, SyncCryptoStorePtr}; use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, BlockIdTo, NumberFor, Zero}, - BuildStorage, }; use std::{str::FromStr, sync::Arc, time::SystemTime}; @@ -182,7 +182,7 @@ where new_full_parts(config, telemetry, executor).map(|parts| parts.0) } -/// Create the initial parts of a full node. +/// Create the initial parts of a full node with the default genesis block builder. pub fn new_full_parts( config: &Configuration, telemetry: Option, @@ -191,6 +191,34 @@ pub fn new_full_parts( where TBl: BlockT, TExec: CodeExecutor + RuntimeVersionOf + Clone, +{ + let backend = new_db_backend(config.db_config())?; + + let genesis_block_builder = GenesisBlockBuilder::new( + config.chain_spec.as_storage_builder(), + !config.no_genesis(), + backend.clone(), + executor.clone(), + )?; + + new_full_parts_with_genesis_builder(config, telemetry, executor, backend, genesis_block_builder) +} + +/// Create the initial parts of a full node. +pub fn new_full_parts_with_genesis_builder( + config: &Configuration, + telemetry: Option, + executor: TExec, + backend: Arc>, + genesis_block_builder: TBuildGenesisBlock, +) -> Result, Error> +where + TBl: BlockT, + TExec: CodeExecutor + RuntimeVersionOf + Clone, + TBuildGenesisBlock: BuildGenesisBlock< + TBl, + BlockImportOperation = as sc_client_api::backend::Backend>::BlockImportOperation + >, { let keystore_container = KeystoreContainer::new(&config.keystore)?; @@ -208,16 +236,7 @@ where .cloned() .unwrap_or_default(); - let (client, backend) = { - let db_config = sc_client_db::DatabaseSettings { - trie_cache_maximum_size: config.trie_cache_maximum_size, - state_pruning: config.state_pruning.clone(), - source: config.database.clone(), - blocks_pruning: config.blocks_pruning, - }; - - let backend = new_db_backend(db_config)?; - + let client = { let extensions = sc_client_api::execution_extensions::ExecutionExtensions::new( config.execution_strategies.clone(), Some(keystore_container.sync_keystore()), @@ -244,7 +263,7 @@ where let client = new_client( backend.clone(), executor, - chain_spec.as_storage_builder(), + genesis_block_builder, fork_blocks, bad_blocks, extensions, @@ -263,7 +282,7 @@ where }, )?; - (client, backend) + client }; Ok((client, backend, keystore_container, task_manager)) @@ -282,10 +301,10 @@ where } /// Create an instance of client backed by given backend. -pub fn new_client( +pub fn new_client( backend: Arc>, executor: E, - genesis_storage: &dyn BuildStorage, + genesis_block_builder: G, fork_blocks: ForkBlocks, bad_blocks: BadBlocks, execution_extensions: ExecutionExtensions, @@ -305,6 +324,10 @@ pub fn new_client( where Block: BlockT, E: CodeExecutor + RuntimeVersionOf, + G: BuildGenesisBlock< + Block, + BlockImportOperation = as sc_client_api::backend::Backend>::BlockImportOperation + >, { let executor = crate::client::LocalCallExecutor::new( backend.clone(), @@ -316,7 +339,7 @@ where crate::client::Client::new( backend, executor, - genesis_storage, + genesis_block_builder, fork_blocks, bad_blocks, prometheus_registry, diff --git a/client/service/src/client/call_executor.rs b/client/service/src/client/call_executor.rs index fcece49b5f..bf7e8b3a65 100644 --- a/client/service/src/client/call_executor.rs +++ b/client/service/src/client/call_executor.rs @@ -377,24 +377,27 @@ mod tests { // LocalCallExecutor directly later on let client_config = ClientConfig::default(); - // client is used for the convenience of creating and inserting the genesis block. - let _client = substrate_test_runtime_client::client::new_with_backend::< - _, - _, - runtime::Block, - _, - runtime::RuntimeApi, - >( + let genesis_block_builder = crate::GenesisBlockBuilder::new( + &substrate_test_runtime_client::GenesisParameters::default().genesis_storage(), + !client_config.no_genesis, backend.clone(), executor.clone(), - &substrate_test_runtime_client::GenesisParameters::default().genesis_storage(), - None, - Box::new(TaskExecutor::new()), - None, - None, - Default::default(), ) - .expect("Creates a client"); + .expect("Creates genesis block builder"); + + // client is used for the convenience of creating and inserting the genesis block. + let _client = + crate::client::new_with_backend::<_, _, runtime::Block, _, runtime::RuntimeApi>( + backend.clone(), + executor.clone(), + genesis_block_builder, + None, + Box::new(TaskExecutor::new()), + None, + None, + Default::default(), + ) + .expect("Creates a client"); let call_executor = LocalCallExecutor { backend: backend.clone(), diff --git a/client/service/src/client/client.rs b/client/service/src/client/client.rs index 8ded5ec95c..f81586d424 100644 --- a/client/service/src/client/client.rs +++ b/client/service/src/client/client.rs @@ -20,7 +20,7 @@ use super::{ block_rules::{BlockRules, LookupResult as BlockLookupResult}, - genesis, + genesis::BuildGenesisBlock, }; use log::{info, trace, warn}; use parking_lot::{Mutex, RwLock}; @@ -70,7 +70,7 @@ use sp_runtime::{ Block as BlockT, BlockIdTo, HashFor, Header as HeaderT, NumberFor, One, SaturatedConversion, Zero, }, - BuildStorage, Digest, Justification, Justifications, StateVersion, + Digest, Justification, Justifications, StateVersion, }; use sp_state_machine::{ prove_child_read, prove_range_read_with_child_with_size, prove_read, @@ -155,9 +155,10 @@ enum PrepareStorageChangesResult, Block: BlockT> { /// Create an instance of in-memory client. #[cfg(feature = "test-helpers")] -pub fn new_in_mem( +pub fn new_in_mem( + backend: Arc>, executor: E, - genesis_storage: &S, + genesis_block_builder: G, keystore: Option, prometheus_registry: Option, telemetry: Option, @@ -168,13 +169,16 @@ pub fn new_in_mem( > where E: CodeExecutor + RuntimeVersionOf, - S: BuildStorage, Block: BlockT, + G: BuildGenesisBlock< + Block, + BlockImportOperation = as backend::Backend>::BlockImportOperation, + >, { new_with_backend( - Arc::new(in_mem::Backend::new()), + backend, executor, - genesis_storage, + genesis_block_builder, keystore, spawn_handle, prometheus_registry, @@ -214,10 +218,10 @@ impl Default for ClientConfig { /// Create a client with the explicitly provided backend. /// This is useful for testing backend implementations. #[cfg(feature = "test-helpers")] -pub fn new_with_backend( +pub fn new_with_backend( backend: Arc, executor: E, - build_genesis_storage: &S, + genesis_block_builder: G, keystore: Option, spawn_handle: Box, prometheus_registry: Option, @@ -226,7 +230,10 @@ pub fn new_with_backend( ) -> sp_blockchain::Result, Block, RA>> where E: CodeExecutor + RuntimeVersionOf, - S: BuildStorage, + G: BuildGenesisBlock< + Block, + BlockImportOperation = >::BlockImportOperation, + >, Block: BlockT, B: backend::LocalBackend + 'static, { @@ -247,7 +254,7 @@ where Client::new( backend, call_executor, - build_genesis_storage, + genesis_block_builder, Default::default(), Default::default(), prometheus_registry, @@ -347,26 +354,25 @@ where Block::Header: Clone, { /// Creates new Substrate Client with given blockchain and code executor. - pub fn new( + pub fn new( backend: Arc, executor: E, - build_genesis_storage: &dyn BuildStorage, + genesis_block_builder: G, fork_blocks: ForkBlocks, bad_blocks: BadBlocks, prometheus_registry: Option, telemetry: Option, config: ClientConfig, - ) -> sp_blockchain::Result { + ) -> sp_blockchain::Result + where + G: BuildGenesisBlock< + Block, + BlockImportOperation = >::BlockImportOperation, + >, + { let info = backend.blockchain().info(); if info.finalized_state.is_none() { - let genesis_storage = - build_genesis_storage.build_storage().map_err(sp_blockchain::Error::Storage)?; - let genesis_state_version = - Self::resolve_state_version_from_wasm(&genesis_storage, &executor)?; - let mut op = backend.begin_operation()?; - let state_root = - op.set_genesis_state(genesis_storage, !config.no_genesis, genesis_state_version)?; - let genesis_block = genesis::construct_genesis_block::(state_root); + let (genesis_block, mut op) = genesis_block_builder.build_genesis_block()?; info!( "🔨 Initializing Genesis block/state (state: {}, header-hash: {})", genesis_block.header().state_root(), @@ -379,13 +385,8 @@ where } else { NewBlockState::Normal }; - op.set_block_data( - genesis_block.deconstruct().0, - Some(vec![]), - None, - None, - block_state, - )?; + let (header, body) = genesis_block.deconstruct(); + op.set_block_data(header, Some(body), None, None, block_state)?; backend.commit_operation(op)?; } @@ -640,7 +641,7 @@ where // This is use by fast sync for runtime version to be resolvable from // changes. let state_version = - Self::resolve_state_version_from_wasm(&storage, &self.executor)?; + resolve_state_version_from_wasm(&storage, &self.executor)?; let state_root = operation.op.reset_storage(storage, state_version)?; if state_root != *import_headers.post().state_root() { // State root mismatch when importing state. This should not happen in @@ -1104,34 +1105,37 @@ where trace!("Collected {} uncles", uncles.len()); Ok(uncles) } +} - fn resolve_state_version_from_wasm( - storage: &Storage, - executor: &E, - ) -> sp_blockchain::Result { - if let Some(wasm) = storage.top.get(well_known_keys::CODE) { - let mut ext = sp_state_machine::BasicExternalities::new_empty(); // just to read runtime version. - - let code_fetcher = sp_core::traits::WrappedRuntimeCode(wasm.as_slice().into()); - let runtime_code = sp_core::traits::RuntimeCode { - code_fetcher: &code_fetcher, - heap_pages: None, - hash: { - use std::hash::{Hash, Hasher}; - let mut state = DefaultHasher::new(); - wasm.hash(&mut state); - state.finish().to_le_bytes().to_vec() - }, - }; - let runtime_version = - RuntimeVersionOf::runtime_version(executor, &mut ext, &runtime_code) - .map_err(|e| sp_blockchain::Error::VersionInvalid(e.to_string()))?; - Ok(runtime_version.state_version()) - } else { - Err(sp_blockchain::Error::VersionInvalid( - "Runtime missing from initial storage, could not read state version.".to_string(), - )) - } +/// Return the genesis state version given the genesis storage and executor. +pub fn resolve_state_version_from_wasm( + storage: &Storage, + executor: &E, +) -> sp_blockchain::Result +where + E: RuntimeVersionOf, +{ + if let Some(wasm) = storage.top.get(well_known_keys::CODE) { + let mut ext = sp_state_machine::BasicExternalities::new_empty(); // just to read runtime version. + + let code_fetcher = sp_core::traits::WrappedRuntimeCode(wasm.as_slice().into()); + let runtime_code = sp_core::traits::RuntimeCode { + code_fetcher: &code_fetcher, + heap_pages: None, + hash: { + use std::hash::{Hash, Hasher}; + let mut state = DefaultHasher::new(); + wasm.hash(&mut state); + state.finish().to_le_bytes().to_vec() + }, + }; + let runtime_version = RuntimeVersionOf::runtime_version(executor, &mut ext, &runtime_code) + .map_err(|e| sp_blockchain::Error::VersionInvalid(e.to_string()))?; + Ok(runtime_version.state_version()) + } else { + Err(sp_blockchain::Error::VersionInvalid( + "Runtime missing from initial storage, could not read state version.".to_string(), + )) } } diff --git a/client/service/src/client/genesis.rs b/client/service/src/client/genesis.rs index 35fb11f049..54ef1182b7 100644 --- a/client/service/src/client/genesis.rs +++ b/client/service/src/client/genesis.rs @@ -18,7 +18,14 @@ //! Tool for creating the genesis block. -use sp_runtime::traits::{Block as BlockT, Hash as HashT, Header as HeaderT, Zero}; +use sc_client_api::{backend::Backend, BlockImportOperation}; +use sc_executor::RuntimeVersionOf; +use sp_core::storage::Storage; +use sp_runtime::{ + traits::{Block as BlockT, Hash as HashT, Header as HeaderT, Zero}, + BuildStorage, +}; +use std::{marker::PhantomData, sync::Arc}; /// Create a genesis block, given the initial storage. pub fn construct_genesis_block(state_root: Block::Hash) -> Block { @@ -38,3 +45,61 @@ pub fn construct_genesis_block(state_root: Block::Hash) -> Block Default::default(), ) } + +/// Trait for building the genesis block. +pub trait BuildGenesisBlock { + /// The import operation used to import the genesis block into the backend. + type BlockImportOperation; + + /// Returns the built genesis block along with the block import operation + /// after setting the genesis storage. + fn build_genesis_block(self) -> sp_blockchain::Result<(Block, Self::BlockImportOperation)>; +} + +/// Default genesis block builder in Substrate. +pub struct GenesisBlockBuilder { + genesis_storage: Storage, + commit_genesis_state: bool, + backend: Arc, + executor: E, + _phantom: PhantomData, +} + +impl, E: RuntimeVersionOf> GenesisBlockBuilder { + /// Constructs a new instance of [`GenesisBlockBuilder`]. + pub fn new( + build_genesis_storage: &dyn BuildStorage, + commit_genesis_state: bool, + backend: Arc, + executor: E, + ) -> sp_blockchain::Result { + let genesis_storage = + build_genesis_storage.build_storage().map_err(sp_blockchain::Error::Storage)?; + Ok(Self { + genesis_storage, + commit_genesis_state, + backend, + executor, + _phantom: PhantomData::, + }) + } +} + +impl, E: RuntimeVersionOf> BuildGenesisBlock + for GenesisBlockBuilder +{ + type BlockImportOperation = >::BlockImportOperation; + + fn build_genesis_block(self) -> sp_blockchain::Result<(Block, Self::BlockImportOperation)> { + let Self { genesis_storage, commit_genesis_state, backend, executor, _phantom } = self; + + let genesis_state_version = + crate::resolve_state_version_from_wasm(&genesis_storage, &executor)?; + let mut op = backend.begin_operation()?; + let state_root = + op.set_genesis_state(genesis_storage, commit_genesis_state, genesis_state_version)?; + let genesis_block = construct_genesis_block::(state_root); + + Ok((genesis_block, op)) + } +} diff --git a/client/service/src/client/mod.rs b/client/service/src/client/mod.rs index eac744923d..dafeb68831 100644 --- a/client/service/src/client/mod.rs +++ b/client/service/src/client/mod.rs @@ -53,7 +53,7 @@ mod wasm_substitutes; pub use self::{ call_executor::LocalCallExecutor, - client::{Client, ClientConfig}, + client::{resolve_state_version_from_wasm, Client, ClientConfig}, }; #[cfg(feature = "test-helpers")] diff --git a/client/service/src/config.rs b/client/service/src/config.rs index e79ff48d6f..efadb8433f 100644 --- a/client/service/src/config.rs +++ b/client/service/src/config.rs @@ -34,6 +34,7 @@ pub use sc_network_common::{ use prometheus_endpoint::Registry; use sc_chain_spec::ChainSpec; +use sc_network::config::SyncMode; pub use sc_telemetry::TelemetryEndpoints; pub use sc_transaction_pool::Options as TransactionPoolOptions; use sp_core::crypto::SecretString; @@ -238,6 +239,22 @@ impl Configuration { }; ProtocolId::from(protocol_id_full) } + + /// Returns true if the genesis state writting will be skipped while initializing the genesis + /// block. + pub fn no_genesis(&self) -> bool { + matches!(self.network.sync_mode, SyncMode::Fast { .. } | SyncMode::Warp { .. }) + } + + /// Returns the database config for creating the backend. + pub fn db_config(&self) -> sc_client_db::DatabaseSettings { + sc_client_db::DatabaseSettings { + trie_cache_maximum_size: self.trie_cache_maximum_size, + state_pruning: self.state_pruning.clone(), + source: self.database.clone(), + blocks_pruning: self.blocks_pruning, + } + } } /// Available RPC methods. diff --git a/client/service/src/lib.rs b/client/service/src/lib.rs index f0e3f72510..8b3a29ba40 100644 --- a/client/service/src/lib.rs +++ b/client/service/src/lib.rs @@ -57,7 +57,10 @@ pub use self::{ new_full_parts, spawn_tasks, BuildNetworkParams, KeystoreContainer, NetworkStarter, SpawnTasksParams, TFullBackend, TFullCallExecutor, TFullClient, }, - client::{ClientConfig, LocalCallExecutor}, + client::{ + genesis::{BuildGenesisBlock, GenesisBlockBuilder}, + resolve_state_version_from_wasm, ClientConfig, LocalCallExecutor, + }, error::Error, }; pub use config::{ diff --git a/client/service/test/src/client/mod.rs b/client/service/test/src/client/mod.rs index be9253d8c7..e0b07b39cd 100644 --- a/client/service/test/src/client/mod.rs +++ b/client/service/test/src/client/mod.rs @@ -1756,17 +1756,30 @@ fn storage_keys_iter_works() { fn cleans_up_closed_notification_sinks_on_block_import() { use substrate_test_runtime_client::GenesisInit; + let backend = Arc::new(sc_client_api::in_mem::Backend::new()); + let executor = substrate_test_runtime_client::new_native_executor(); + let client_config = sc_service::ClientConfig::default(); + + let genesis_block_builder = sc_service::GenesisBlockBuilder::new( + &substrate_test_runtime_client::GenesisParameters::default().genesis_storage(), + !client_config.no_genesis, + backend.clone(), + executor.clone(), + ) + .unwrap(); + // NOTE: we need to build the client here instead of using the client // provided by test_runtime_client otherwise we can't access the private // `import_notification_sinks` and `finality_notification_sinks` fields. let mut client = new_in_mem::<_, Block, _, RuntimeApi>( - substrate_test_runtime_client::new_native_executor(), - &substrate_test_runtime_client::GenesisParameters::default().genesis_storage(), + backend, + executor, + genesis_block_builder, None, None, None, Box::new(TaskExecutor::new()), - Default::default(), + client_config, ) .unwrap(); diff --git a/test-utils/client/src/lib.rs b/test-utils/client/src/lib.rs index 8ee652abe2..5dc93da13f 100644 --- a/test-utils/client/src/lib.rs +++ b/test-utils/client/src/lib.rs @@ -203,7 +203,7 @@ impl ) where ExecutorDispatch: - sc_client_api::CallExecutor + sc_executor::RuntimeVersionOf + 'static, + sc_client_api::CallExecutor + sc_executor::RuntimeVersionOf + Clone + 'static, Backend: sc_client_api::backend::Backend, >::OffchainStorage: 'static, { @@ -223,19 +223,29 @@ impl storage }; + let client_config = ClientConfig { + offchain_indexing_api: self.enable_offchain_indexing_api, + no_genesis: self.no_genesis, + ..Default::default() + }; + + let genesis_block_builder = sc_service::GenesisBlockBuilder::new( + &storage, + !client_config.no_genesis, + self.backend.clone(), + executor.clone(), + ) + .expect("Creates genesis block builder"); + let client = client::Client::new( self.backend.clone(), executor, - &storage, + genesis_block_builder, self.fork_blocks, self.bad_blocks, None, None, - ClientConfig { - offchain_indexing_api: self.enable_offchain_indexing_api, - no_genesis: self.no_genesis, - ..Default::default() - }, + client_config, ) .expect("Creates new client"); From e118df551952d1aa7d85e79f04e1b8cb851b6761 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Date: Mon, 19 Dec 2022 17:38:12 +0200 Subject: [PATCH 057/127] rpc: Implement `chainHead` RPC API (#12544) * rpc/chain_head: Add event structure for serialization Signed-off-by: Alexandru Vasile * rpc/chain_head: Add tests for events Signed-off-by: Alexandru Vasile * rpc/chain_head: Add API trait for `chainHead` Signed-off-by: Alexandru Vasile * rpc/chain_head: Add RPC errors Signed-off-by: Alexandru Vasile * rpc/chain_head: Manage subscription ID tracking for pinned blocks Signed-off-by: Alexandru Vasile * rpc/chain_head: Add tests for subscription management Signed-off-by: Alexandru Vasile * rpc/chain_head: Constructor for the API Signed-off-by: Alexandru Vasile * rpc/chain_head: Placeholders for API implementation Signed-off-by: Alexandru Vasile * rpc/chain_head: Accept RPC subscription sink Signed-off-by: Alexandru Vasile * rpc/chain_head: Generate the runtime API event Signed-off-by: Alexandru Vasile * rpc/chain_head: Implement the `follow` method Signed-off-by: Alexandru Vasile * rpc/chain_head: Implement the `body` method Signed-off-by: Alexandru Vasile * rpc/chain_head: Implement the `header` method Signed-off-by: Alexandru Vasile * rpc/chain_head: Implement the `storage` method Signed-off-by: Alexandru Vasile * rpc/chain_head: Implement the `call` method Signed-off-by: Alexandru Vasile * rpc/chain_head: Implement the `unpin` method Signed-off-by: Alexandru Vasile * Update `Cargo.lock` Signed-off-by: Alexandru Vasile * rpc/chain_head: Implement `getGenesis` method Signed-off-by: Alexandru Vasile * rpc/chain_head: Fix clippy Signed-off-by: Alexandru Vasile * rpc/chain_head: Parse params from hex string Signed-off-by: Alexandru Vasile * rpc/chain_head: Constuct API with genesis hash Signed-off-by: Alexandru Vasile * rpc/chain_head: Add the finalized block to reported tree route Signed-off-by: Alexandru Vasile * rpc/chain_head: Export the API and events for better ergonomics Signed-off-by: Alexandru Vasile * chain_head/tests: Add test module with helper functions Signed-off-by: Alexandru Vasile * chain_head/tests: Test block events from the `follow` pubsub Signed-off-by: Alexandru Vasile * chain_head/tests: Test `genesisHash` getter Signed-off-by: Alexandru Vasile * chain_head/tests: Test `header` method Signed-off-by: Alexandru Vasile * chain_head/tests: Test `body` method Signed-off-by: Alexandru Vasile * chain_head/tests: Test calling into the runtime API Signed-off-by: Alexandru Vasile * chain_head/tests: Test runtime for the `follow` method Signed-off-by: Alexandru Vasile * chain_head/tests: Add runtime code changes for `follow` method Signed-off-by: Alexandru Vasile * rpc/chain_head: Remove space from rustdoc Signed-off-by: Alexandru Vasile * rpc/chain_head: Use the `child_key` for storage queries Signed-off-by: Alexandru Vasile * rpc/chain_head: Test `storage` method Signed-off-by: Alexandru Vasile * rpc/chain_head: Test child trie query for `storage` method Signed-off-by: Alexandru Vasile * rpc/chain_head: Event serialization typo Signed-off-by: Alexandru Vasile * rpc/chain_head: Remove subscription aliases Signed-off-by: Alexandru Vasile * rpc/chain_head: Add `NetworkConfig` parameter Signed-off-by: Alexandru Vasile * rpc/chain_head: Named parameters as camelCase if present Signed-off-by: Alexandru Vasile * rpc/chain_head: Implement From for RuntimeEvents Signed-off-by: Alexandru Vasile * rpc/chain_head: Handle pruning of the best block in finalization window Signed-off-by: Alexandru Vasile * rpc/chain_head: Generate initial block events Signed-off-by: Alexandru Vasile * chain_head/tests: Verify that initial in-memory blocks are reported Signed-off-by: Alexandru Vasile * chain_head/tests: Verify the finalized event with forks and pruned blocks Signed-off-by: Alexandru Vasile * rpc/chain_head: Fix clippy Signed-off-by: Alexandru Vasile * rpc/chain_head: Separate logic for generating initial events Signed-off-by: Alexandru Vasile * rpc/chain_head: Handle stopping a subscription ID Signed-off-by: Alexandru Vasile * rpc/chain_head: Submit events until the "Stop" event is triggered Signed-off-by: Alexandru Vasile * rpc/chain_head: Separate logic for handling new and finalized blocks Signed-off-by: Alexandru Vasile * rpc/chain_head: Extend subscription logic with subId handle Signed-off-by: Alexandru Vasile * rpc/chain_head: Adjust to the new subscription mngmt API Signed-off-by: Alexandru Vasile * rpc/chain_head: Refuse RuntimeAPI calls without the runtime flag Signed-off-by: Alexandru Vasile * chain_head/tests: Verify RuntimeAPI calls without runtime flag Signed-off-by: Alexandru Vasile * rpc/chain_head: Add best block per subscription Signed-off-by: Alexandru Vasile * rpc/chain_head: Check storage keys for prefixes Signed-off-by: Alexandru Vasile * chain_head/tests: Check storage queries with invalid prefixes Signed-off-by: Alexandru Vasile * rpc/chain_head: Allow maximum number of pinned blocks Signed-off-by: Alexandru Vasile * chain_head/tests: Test the maximum number of pinned blocks Signed-off-by: Alexandru Vasile * rpc/chain_head: Adjust to origin/master and apply clippy Signed-off-by: Alexandru Vasile * client/service: Enable the `chainHead` API Signed-off-by: Alexandru Vasile * rpc/chain_head: Stop subscription on client disconnect and add debug logs Signed-off-by: Alexandru Vasile * rpc/chain_head: Fix sending `Stop` on subscription exit Signed-off-by: Alexandru Vasile * rpc/chain_head: Check best block is descendent of latest finalized Signed-off-by: Alexandru Vasile * chain_head/tests: Report events before pruning the best block Signed-off-by: Alexandru Vasile * rpc/chain_head: Nonrecursive initial block generation Signed-off-by: Alexandru Vasile * rpc/chain_head: Generate initial events on subscription executor Signed-off-by: Alexandru Vasile * rpc/chain_head: Reduce dev-dependencies for tokio Signed-off-by: Alexandru Vasile * Apply suggestions from code review Co-authored-by: Sebastian Kunert * rpc/chain_head: Accept empty parameters Signed-off-by: Alexandru Vasile * rpc/chain_head: Use debug of `HexDisplay` for full format Signed-off-by: Alexandru Vasile * rpc/chain_head: Enable subscription ID Signed-off-by: Alexandru Vasile * rpc/chain_head: Use jsonrpsee 16.2 camelCase feature for paramaters Signed-off-by: Alexandru Vasile * rpc/chain_head: Use `NonZeroUsize` for `NetworkConfig` param Signed-off-by: Alexandru Vasile * rpc/chain_head: Rename `runtime_updates` to `has_runtime_updates` Signed-off-by: Alexandru Vasile Signed-off-by: Alexandru Vasile Co-authored-by: Sebastian Kunert --- Cargo.lock | 38 +- client/rpc-spec-v2/Cargo.toml | 13 + client/rpc-spec-v2/src/chain_head/api.rs | 137 +++ .../rpc-spec-v2/src/chain_head/chain_head.rs | 783 +++++++++++++ client/rpc-spec-v2/src/chain_head/error.rs | 74 ++ client/rpc-spec-v2/src/chain_head/event.rs | 480 ++++++++ client/rpc-spec-v2/src/chain_head/mod.rs | 40 + .../src/chain_head/subscription.rs | 284 +++++ client/rpc-spec-v2/src/chain_head/tests.rs | 1022 +++++++++++++++++ client/rpc-spec-v2/src/lib.rs | 1 + client/service/src/builder.rs | 22 +- 11 files changed, 2885 insertions(+), 9 deletions(-) create mode 100644 client/rpc-spec-v2/src/chain_head/api.rs create mode 100644 client/rpc-spec-v2/src/chain_head/chain_head.rs create mode 100644 client/rpc-spec-v2/src/chain_head/error.rs create mode 100644 client/rpc-spec-v2/src/chain_head/event.rs create mode 100644 client/rpc-spec-v2/src/chain_head/mod.rs create mode 100644 client/rpc-spec-v2/src/chain_head/subscription.rs create mode 100644 client/rpc-spec-v2/src/chain_head/tests.rs diff --git a/Cargo.lock b/Cargo.lock index c62d71348f..046e5991ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2503,7 +2503,7 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util", + "tokio-util 0.7.4", "tracing", ] @@ -2928,7 +2928,7 @@ dependencies = [ "thiserror", "tokio", "tokio-rustls", - "tokio-util", + "tokio-util 0.7.4", "tracing", "webpki-roots", ] @@ -2991,7 +2991,7 @@ dependencies = [ "soketto", "tokio", "tokio-stream", - "tokio-util", + "tokio-util 0.7.4", "tower", "tracing", ] @@ -4279,7 +4279,7 @@ dependencies = [ "substrate-rpc-client", "tempfile", "tokio", - "tokio-util", + "tokio-util 0.7.4", "try-runtime-cli", "wait-timeout", ] @@ -7774,7 +7774,7 @@ dependencies = [ "tempfile", "thiserror", "tokio", - "tokio-util", + "tokio-util 0.7.4", "unsigned-varint", "zeroize", ] @@ -8085,20 +8085,33 @@ dependencies = [ name = "sc-rpc-spec-v2" version = "0.10.0-dev" dependencies = [ + "array-bytes", + "assert_matches", "futures", + "futures-util", "hex", "jsonrpsee", + "log", "parity-scale-codec", + "parking_lot 0.12.1", + "sc-block-builder", "sc-chain-spec", + "sc-client-api", "sc-transaction-pool-api", "serde", "serde_json", "sp-api", "sp-blockchain", + "sp-consensus", "sp-core", + "sp-maybe-compressed-blob", "sp-runtime", + "sp-version", + "substrate-test-runtime", + "substrate-test-runtime-client", "thiserror", "tokio", + "tokio-stream", ] [[package]] @@ -10264,6 +10277,7 @@ dependencies = [ "futures-core", "pin-project-lite 0.2.9", "tokio", + "tokio-util 0.6.10", ] [[package]] @@ -10279,6 +10293,20 @@ dependencies = [ "tokio-stream", ] +[[package]] +name = "tokio-util" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite 0.2.9", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.4" diff --git a/client/rpc-spec-v2/Cargo.toml b/client/rpc-spec-v2/Cargo.toml index 930aeb4bd8..f9598b3c1a 100644 --- a/client/rpc-spec-v2/Cargo.toml +++ b/client/rpc-spec-v2/Cargo.toml @@ -22,12 +22,25 @@ sp-core = { version = "7.0.0", path = "../../primitives/core" } sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } +sp-version = { version = "5.0.0", path = "../../primitives/version" } +sc-client-api = { version = "4.0.0-dev", path = "../api" } codec = { package = "parity-scale-codec", version = "3.0.0" } thiserror = "1.0" serde = "1.0" hex = "0.4" futures = "0.3.21" +parking_lot = "0.12.1" +tokio-stream = { version = "0.1", features = ["sync"] } +array-bytes = "4.1" +log = "0.4.17" +futures-util = { version = "0.3.19", default-features = false } [dev-dependencies] serde_json = "1.0" tokio = { version = "1.22.0", features = ["macros"] } +substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } +substrate-test-runtime = { version = "2.0.0", path = "../../test-utils/runtime" } +sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } +sp-maybe-compressed-blob = { version = "4.1.0-dev", path = "../../primitives/maybe-compressed-blob" } +sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } +assert_matches = "1.3.0" diff --git a/client/rpc-spec-v2/src/chain_head/api.rs b/client/rpc-spec-v2/src/chain_head/api.rs new file mode 100644 index 0000000000..7e72d6d774 --- /dev/null +++ b/client/rpc-spec-v2/src/chain_head/api.rs @@ -0,0 +1,137 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 . + +#![allow(non_snake_case)] + +//! API trait of the chain head. +use crate::chain_head::event::{ChainHeadEvent, FollowEvent, NetworkConfig}; +use jsonrpsee::{core::RpcResult, proc_macros::rpc}; + +#[rpc(client, server)] +pub trait ChainHeadApi { + /// Track the state of the head of the chain: the finalized, non-finalized, and best blocks. + /// + /// # Unstable + /// + /// This method is unstable and subject to change in the future. + #[subscription( + name = "chainHead_unstable_follow", + unsubscribe = "chainHead_unstable_unfollow", + item = FollowEvent, + )] + fn chain_head_unstable_follow(&self, runtime_updates: bool); + + /// Retrieves the body (list of transactions) of a pinned block. + /// + /// This method should be seen as a complement to `chainHead_unstable_follow`, + /// allowing the JSON-RPC client to retrieve more information about a block + /// that has been reported. + /// + /// Use `archive_unstable_body` if instead you want to retrieve the body of an arbitrary block. + /// + /// # Unstable + /// + /// This method is unstable and subject to change in the future. + #[subscription( + name = "chainHead_unstable_body", + unsubscribe = "chainHead_unstable_stopBody", + item = ChainHeadEvent, + )] + fn chain_head_unstable_body( + &self, + follow_subscription: String, + hash: Hash, + network_config: Option, + ); + + /// Retrieves the header of a pinned block. + /// + /// This method should be seen as a complement to `chainHead_unstable_follow`, + /// allowing the JSON-RPC client to retrieve more information about a block + /// that has been reported. + /// + /// Use `archive_unstable_header` if instead you want to retrieve the header of an arbitrary + /// block. + /// + /// # Unstable + /// + /// This method is unstable and subject to change in the future. + #[method(name = "chainHead_unstable_header", blocking)] + fn chain_head_unstable_header( + &self, + follow_subscription: String, + hash: Hash, + ) -> RpcResult>; + + /// Get the chain's genesis hash. + /// + /// # Unstable + /// + /// This method is unstable and subject to change in the future. + #[method(name = "chainHead_unstable_genesisHash", blocking)] + fn chain_head_unstable_genesis_hash(&self) -> RpcResult; + + /// Return a storage entry at a specific block's state. + /// + /// # Unstable + /// + /// This method is unstable and subject to change in the future. + #[subscription( + name = "chainHead_unstable_storage", + unsubscribe = "chainHead_unstable_stopStorage", + item = ChainHeadEvent, + )] + fn chain_head_unstable_storage( + &self, + follow_subscription: String, + hash: Hash, + key: String, + child_key: Option, + network_config: Option, + ); + + /// Call into the Runtime API at a specified block's state. + /// + /// # Unstable + /// + /// This method is unstable and subject to change in the future. + #[subscription( + name = "chainHead_unstable_call", + unsubscribe = "chainHead_unstable_stopCall", + item = ChainHeadEvent, + )] + fn chain_head_unstable_call( + &self, + follow_subscription: String, + hash: Hash, + function: String, + call_parameters: String, + network_config: Option, + ); + + /// Unpin a block reported by the `follow` method. + /// + /// Ongoing operations that require the provided block + /// will continue normally. + /// + /// # Unstable + /// + /// This method is unstable and subject to change in the future. + #[method(name = "chainHead_unstable_unpin", blocking)] + fn chain_head_unstable_unpin(&self, follow_subscription: String, hash: Hash) -> RpcResult<()>; +} diff --git a/client/rpc-spec-v2/src/chain_head/chain_head.rs b/client/rpc-spec-v2/src/chain_head/chain_head.rs new file mode 100644 index 0000000000..c55625e99c --- /dev/null +++ b/client/rpc-spec-v2/src/chain_head/chain_head.rs @@ -0,0 +1,783 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 . + +//! API implementation for `chainHead`. + +use crate::{ + chain_head::{ + api::ChainHeadApiServer, + error::Error as ChainHeadRpcError, + event::{ + BestBlockChanged, ChainHeadEvent, ChainHeadResult, ErrorEvent, Finalized, FollowEvent, + Initialized, NetworkConfig, NewBlock, RuntimeEvent, RuntimeVersionEvent, + }, + subscription::{SubscriptionHandle, SubscriptionManagement, SubscriptionManagementError}, + }, + SubscriptionTaskExecutor, +}; +use codec::Encode; +use futures::{ + channel::oneshot, + future::FutureExt, + stream::{self, Stream, StreamExt}, +}; +use futures_util::future::Either; +use jsonrpsee::{ + core::{async_trait, RpcResult}, + types::{SubscriptionEmptyError, SubscriptionId, SubscriptionResult}, + SubscriptionSink, +}; +use log::{debug, error}; +use sc_client_api::{ + Backend, BlockBackend, BlockImportNotification, BlockchainEvents, CallExecutor, ChildInfo, + ExecutorProvider, FinalityNotification, StorageKey, StorageProvider, +}; +use serde::Serialize; +use sp_api::CallApiAt; +use sp_blockchain::{ + Backend as BlockChainBackend, Error as BlockChainError, HeaderBackend, HeaderMetadata, +}; +use sp_core::{hexdisplay::HexDisplay, storage::well_known_keys, Bytes}; +use sp_runtime::{ + generic::BlockId, + traits::{Block as BlockT, Header}, +}; +use std::{marker::PhantomData, sync::Arc}; + +/// An API for chain head RPC calls. +pub struct ChainHead { + /// Substrate client. + client: Arc, + /// Backend of the chain. + backend: Arc, + /// Executor to spawn subscriptions. + executor: SubscriptionTaskExecutor, + /// Keep track of the pinned blocks for each subscription. + subscriptions: Arc>, + /// The hexadecimal encoded hash of the genesis block. + genesis_hash: String, + /// The maximum number of pinned blocks allowed per connection. + max_pinned_blocks: usize, + /// Phantom member to pin the block type. + _phantom: PhantomData, +} + +impl ChainHead { + /// Create a new [`ChainHead`]. + pub fn new>( + client: Arc, + backend: Arc, + executor: SubscriptionTaskExecutor, + genesis_hash: GenesisHash, + max_pinned_blocks: usize, + ) -> Self { + let genesis_hash = format!("0x{:?}", HexDisplay::from(&genesis_hash.as_ref())); + + Self { + client, + backend, + executor, + subscriptions: Arc::new(SubscriptionManagement::new()), + genesis_hash, + max_pinned_blocks, + _phantom: PhantomData, + } + } + + /// Accept the subscription and return the subscription ID on success. + fn accept_subscription( + &self, + sink: &mut SubscriptionSink, + ) -> Result { + // The subscription must be accepted before it can provide a valid subscription ID. + sink.accept()?; + + let Some(sub_id) = sink.subscription_id() else { + // This can only happen if the subscription was not accepted. + return Err(SubscriptionEmptyError) + }; + + // Get the string representation for the subscription. + let sub_id = match sub_id { + SubscriptionId::Num(num) => num.to_string(), + SubscriptionId::Str(id) => id.into_owned().into(), + }; + + Ok(sub_id) + } +} + +/// Generate the initial events reported by the RPC `follow` method. +/// +/// This includes the "Initialized" event followed by the in-memory +/// blocks via "NewBlock" and the "BestBlockChanged". +fn generate_initial_events( + client: &Arc, + backend: &Arc, + handle: &SubscriptionHandle, + runtime_updates: bool, +) -> Result>, SubscriptionManagementError> +where + Block: BlockT + 'static, + Block::Header: Unpin, + BE: Backend + 'static, + Client: HeaderBackend + CallApiAt + 'static, +{ + // The initialized event is the first one sent. + let finalized_block_hash = client.info().finalized_hash; + handle.pin_block(finalized_block_hash)?; + + let finalized_block_runtime = generate_runtime_event( + &client, + runtime_updates, + &BlockId::Hash(finalized_block_hash), + None, + ); + + let initialized_event = FollowEvent::Initialized(Initialized { + finalized_block_hash, + finalized_block_runtime, + runtime_updates, + }); + + let initial_blocks = get_initial_blocks(&backend, finalized_block_hash); + let mut in_memory_blocks = Vec::with_capacity(initial_blocks.len() + 1); + + in_memory_blocks.push(initialized_event); + for (child, parent) in initial_blocks.into_iter() { + handle.pin_block(child)?; + + let new_runtime = generate_runtime_event( + &client, + runtime_updates, + &BlockId::Hash(child), + Some(&BlockId::Hash(parent)), + ); + + let event = FollowEvent::NewBlock(NewBlock { + block_hash: child, + parent_block_hash: parent, + new_runtime, + runtime_updates, + }); + + in_memory_blocks.push(event); + } + + // Generate a new best block event. + let best_block_hash = client.info().best_hash; + if best_block_hash != finalized_block_hash { + let best_block = FollowEvent::BestBlockChanged(BestBlockChanged { best_block_hash }); + in_memory_blocks.push(best_block); + }; + + Ok(in_memory_blocks) +} + +/// Parse hex-encoded string parameter as raw bytes. +/// +/// If the parsing fails, the subscription is rejected. +fn parse_hex_param( + sink: &mut SubscriptionSink, + param: String, +) -> Result, SubscriptionEmptyError> { + // Methods can accept empty parameters. + if param.is_empty() { + return Ok(Default::default()) + } + + match array_bytes::hex2bytes(¶m) { + Ok(bytes) => Ok(bytes), + Err(_) => { + let _ = sink.reject(ChainHeadRpcError::InvalidParam(param)); + Err(SubscriptionEmptyError) + }, + } +} + +/// Conditionally generate the runtime event of the given block. +fn generate_runtime_event( + client: &Arc, + runtime_updates: bool, + block: &BlockId, + parent: Option<&BlockId>, +) -> Option +where + Block: BlockT + 'static, + Client: CallApiAt + 'static, +{ + // No runtime versions should be reported. + if !runtime_updates { + return None + } + + let block_rt = match client.runtime_version_at(block) { + Ok(rt) => rt, + Err(err) => return Some(err.into()), + }; + + let parent = match parent { + Some(parent) => parent, + // Nothing to compare against, always report. + None => return Some(RuntimeEvent::Valid(RuntimeVersionEvent { spec: block_rt })), + }; + + let parent_rt = match client.runtime_version_at(parent) { + Ok(rt) => rt, + Err(err) => return Some(err.into()), + }; + + // Report the runtime version change. + if block_rt != parent_rt { + Some(RuntimeEvent::Valid(RuntimeVersionEvent { spec: block_rt })) + } else { + None + } +} + +/// Get the in-memory blocks of the client, starting from the provided finalized hash. +/// +/// Returns a tuple of block hash with parent hash. +fn get_initial_blocks( + backend: &Arc, + parent_hash: Block::Hash, +) -> Vec<(Block::Hash, Block::Hash)> +where + Block: BlockT + 'static, + BE: Backend + 'static, +{ + let mut result = Vec::new(); + let mut next_hash = Vec::new(); + next_hash.push(parent_hash); + + while let Some(parent_hash) = next_hash.pop() { + let Ok(blocks) = backend.blockchain().children(parent_hash) else { + continue + }; + + for child_hash in blocks { + result.push((child_hash, parent_hash)); + next_hash.push(child_hash); + } + } + + result +} + +/// Submit the events from the provided stream to the RPC client +/// for as long as the `rx_stop` event was not called. +async fn submit_events( + sink: &mut SubscriptionSink, + mut stream: EventStream, + rx_stop: oneshot::Receiver<()>, +) where + EventStream: Stream + Unpin, + T: Serialize, +{ + let mut stream_item = stream.next(); + let mut stop_event = rx_stop; + + while let Either::Left((Some(event), next_stop_event)) = + futures_util::future::select(stream_item, stop_event).await + { + match sink.send(&event) { + Ok(true) => { + stream_item = stream.next(); + stop_event = next_stop_event; + }, + // Client disconnected. + Ok(false) => return, + Err(_) => { + // Failed to submit event. + break + }, + } + } + + let _ = sink.send(&FollowEvent::::Stop); +} + +/// Generate the "NewBlock" event and potentially the "BestBlockChanged" event for +/// every notification. +fn handle_import_blocks( + client: &Arc, + handle: &SubscriptionHandle, + runtime_updates: bool, + notification: BlockImportNotification, +) -> Result<(FollowEvent, Option>), SubscriptionManagementError> +where + Block: BlockT + 'static, + Client: CallApiAt + 'static, +{ + handle.pin_block(notification.hash)?; + + let new_runtime = generate_runtime_event( + &client, + runtime_updates, + &BlockId::Hash(notification.hash), + Some(&BlockId::Hash(*notification.header.parent_hash())), + ); + + // Note: `Block::Hash` will serialize to hexadecimal encoded string. + let new_block = FollowEvent::NewBlock(NewBlock { + block_hash: notification.hash, + parent_block_hash: *notification.header.parent_hash(), + new_runtime, + runtime_updates, + }); + + if !notification.is_new_best { + return Ok((new_block, None)) + } + + // If this is the new best block, then we need to generate two events. + let best_block_event = + FollowEvent::BestBlockChanged(BestBlockChanged { best_block_hash: notification.hash }); + + let mut best_block_cache = handle.best_block_write(); + match *best_block_cache { + Some(block_cache) => { + // The RPC layer has not reported this block as best before. + // Note: This handles the race with the finalized branch. + if block_cache != notification.hash { + *best_block_cache = Some(notification.hash); + Ok((new_block, Some(best_block_event))) + } else { + Ok((new_block, None)) + } + }, + None => { + *best_block_cache = Some(notification.hash); + Ok((new_block, Some(best_block_event))) + }, + } +} + +/// Generate the "Finalized" event and potentially the "BestBlockChanged" for +/// every notification. +fn handle_finalized_blocks( + client: &Arc, + handle: &SubscriptionHandle, + notification: FinalityNotification, +) -> Result<(FollowEvent, Option>), SubscriptionManagementError> +where + Block: BlockT + 'static, + Client: HeaderBackend + HeaderMetadata + 'static, +{ + let last_finalized = notification.hash; + // We might not receive all new blocks reports, also pin the block here. + handle.pin_block(last_finalized)?; + + // The tree route contains the exclusive path from the last finalized block + // to the block reported by the notification. Ensure the finalized block is + // properly reported to that path. + let mut finalized_block_hashes = notification.tree_route.iter().cloned().collect::>(); + finalized_block_hashes.push(last_finalized); + + let pruned_block_hashes: Vec<_> = notification.stale_heads.iter().cloned().collect(); + + let finalized_event = FollowEvent::Finalized(Finalized { + finalized_block_hashes, + pruned_block_hashes: pruned_block_hashes.clone(), + }); + + let mut best_block_cache = handle.best_block_write(); + match *best_block_cache { + Some(block_cache) => { + // Check if the current best block is also reported as pruned. + let reported_pruned = pruned_block_hashes.iter().find(|&&hash| hash == block_cache); + if reported_pruned.is_none() { + return Ok((finalized_event, None)) + } + + // The best block is reported as pruned. Therefore, we need to signal a new + // best block event before submitting the finalized event. + let best_block_hash = client.info().best_hash; + if best_block_hash == block_cache { + // The client doest not have any new information about the best block. + // The information from `.info()` is updated from the DB as the last + // step of the finalization and it should be up to date. Also, the + // displaced nodes (list of nodes reported) should be reported with + // an offset of 32 blocks for substrate. + // If the info is outdated, there is nothing the RPC can do for now. + error!(target: "rpc-spec-v2", "Client does not contain different best block"); + Ok((finalized_event, None)) + } else { + let ancestor = sp_blockchain::lowest_common_ancestor( + &**client, + last_finalized, + best_block_hash, + ) + .map_err(|_| { + SubscriptionManagementError::Custom("Could not find common ancestor".into()) + })?; + + // The client's best block must be a descendent of the last finalized block. + // In other words, the lowest common ancestor must be the last finalized block. + if ancestor.hash != last_finalized { + return Err(SubscriptionManagementError::Custom( + "The finalized block is not an ancestor of the best block".into(), + )) + } + + // The RPC needs to also submit a new best block changed before the + // finalized event. + *best_block_cache = Some(best_block_hash); + let best_block_event = + FollowEvent::BestBlockChanged(BestBlockChanged { best_block_hash }); + Ok((finalized_event, Some(best_block_event))) + } + }, + None => Ok((finalized_event, None)), + } +} + +#[async_trait] +impl ChainHeadApiServer for ChainHead +where + Block: BlockT + 'static, + Block::Header: Unpin, + BE: Backend + 'static, + Client: BlockBackend + + ExecutorProvider + + HeaderBackend + + HeaderMetadata + + BlockchainEvents + + CallApiAt + + StorageProvider + + 'static, +{ + fn chain_head_unstable_follow( + &self, + mut sink: SubscriptionSink, + runtime_updates: bool, + ) -> SubscriptionResult { + let sub_id = match self.accept_subscription(&mut sink) { + Ok(sub_id) => sub_id, + Err(err) => { + sink.close(ChainHeadRpcError::InvalidSubscriptionID); + return Err(err) + }, + }; + + // Keep track of the subscription. + let Some((rx_stop, sub_handle)) = self.subscriptions.insert_subscription(sub_id.clone(), runtime_updates, self.max_pinned_blocks) else { + // Inserting the subscription can only fail if the JsonRPSee + // generated a duplicate subscription ID. + debug!(target: "rpc-spec-v2", "[follow][id={:?}] Subscription already accepted", sub_id); + let _ = sink.send(&FollowEvent::::Stop); + return Ok(()) + }; + debug!(target: "rpc-spec-v2", "[follow][id={:?}] Subscription accepted", sub_id); + + let client = self.client.clone(); + let handle = sub_handle.clone(); + let subscription_id = sub_id.clone(); + + let stream_import = self + .client + .import_notification_stream() + .map(move |notification| { + match handle_import_blocks(&client, &handle, runtime_updates, notification) { + Ok((new_block, None)) => stream::iter(vec![new_block]), + Ok((new_block, Some(best_block))) => stream::iter(vec![new_block, best_block]), + Err(_) => { + debug!(target: "rpc-spec-v2", "[follow][id={:?}] Failed to handle block import notification.", subscription_id); + handle.stop(); + stream::iter(vec![]) + }, + } + }) + .flatten(); + + let client = self.client.clone(); + let handle = sub_handle.clone(); + let subscription_id = sub_id.clone(); + + let stream_finalized = self + .client + .finality_notification_stream() + .map(move |notification| { + match handle_finalized_blocks(&client, &handle, notification) { + Ok((finalized_event, None)) => stream::iter(vec![finalized_event]), + Ok((finalized_event, Some(best_block))) => + stream::iter(vec![best_block, finalized_event]), + Err(_) => { + debug!(target: "rpc-spec-v2", "[follow][id={:?}] Failed to import finalized blocks", subscription_id); + handle.stop(); + stream::iter(vec![]) + }, + } + }) + .flatten(); + + let merged = tokio_stream::StreamExt::merge(stream_import, stream_finalized); + let subscriptions = self.subscriptions.clone(); + let client = self.client.clone(); + let backend = self.backend.clone(); + let fut = async move { + let Ok(initial_events) = generate_initial_events(&client, &backend, &sub_handle, runtime_updates) else { + // Stop the subscription if we exceeded the maximum number of blocks pinned. + debug!(target: "rpc-spec-v2", "[follow][id={:?}] Exceeded max pinned blocks from initial events", sub_id); + let _ = sink.send(&FollowEvent::::Stop); + return + }; + + let stream = stream::iter(initial_events).chain(merged); + + submit_events(&mut sink, stream.boxed(), rx_stop).await; + // The client disconnected or called the unsubscribe method. + subscriptions.remove_subscription(&sub_id); + debug!(target: "rpc-spec-v2", "[follow][id={:?}] Subscription removed", sub_id); + }; + + self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); + Ok(()) + } + + fn chain_head_unstable_body( + &self, + mut sink: SubscriptionSink, + follow_subscription: String, + hash: Block::Hash, + _network_config: Option, + ) -> SubscriptionResult { + let client = self.client.clone(); + let subscriptions = self.subscriptions.clone(); + + let fut = async move { + let Some(handle) = subscriptions.get_subscription(&follow_subscription) else { + // Invalid invalid subscription ID. + let _ = sink.send(&ChainHeadEvent::::Disjoint); + return + }; + + // Block is not part of the subscription. + if !handle.contains_block(&hash) { + let _ = sink.reject(ChainHeadRpcError::InvalidBlock); + return + } + + let event = match client.block(&BlockId::Hash(hash)) { + Ok(Some(signed_block)) => { + let extrinsics = signed_block.block.extrinsics(); + let result = format!("0x{:?}", HexDisplay::from(&extrinsics.encode())); + ChainHeadEvent::Done(ChainHeadResult { result }) + }, + Ok(None) => { + // The block's body was pruned. This subscription ID has become invalid. + debug!(target: "rpc-spec-v2", "[body][id={:?}] Stopping subscription because hash={:?} was pruned", follow_subscription, hash); + handle.stop(); + ChainHeadEvent::::Disjoint + }, + Err(error) => ChainHeadEvent::Error(ErrorEvent { error: error.to_string() }), + }; + let _ = sink.send(&event); + }; + + self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); + Ok(()) + } + + fn chain_head_unstable_header( + &self, + follow_subscription: String, + hash: Block::Hash, + ) -> RpcResult> { + let Some(handle) = self.subscriptions.get_subscription(&follow_subscription) else { + // Invalid invalid subscription ID. + return Ok(None) + }; + + // Block is not part of the subscription. + if !handle.contains_block(&hash) { + return Err(ChainHeadRpcError::InvalidBlock.into()) + } + + self.client + .header(BlockId::Hash(hash)) + .map(|opt_header| opt_header.map(|h| format!("0x{:?}", HexDisplay::from(&h.encode())))) + .map_err(ChainHeadRpcError::FetchBlockHeader) + .map_err(Into::into) + } + + fn chain_head_unstable_genesis_hash(&self) -> RpcResult { + Ok(self.genesis_hash.clone()) + } + + fn chain_head_unstable_storage( + &self, + mut sink: SubscriptionSink, + follow_subscription: String, + hash: Block::Hash, + key: String, + child_key: Option, + _network_config: Option, + ) -> SubscriptionResult { + let key = StorageKey(parse_hex_param(&mut sink, key)?); + + let child_key = child_key + .map(|child_key| parse_hex_param(&mut sink, child_key)) + .transpose()? + .map(ChildInfo::new_default_from_vec); + + let client = self.client.clone(); + let subscriptions = self.subscriptions.clone(); + + let fut = async move { + let Some(handle) = subscriptions.get_subscription(&follow_subscription) else { + // Invalid invalid subscription ID. + let _ = sink.send(&ChainHeadEvent::::Disjoint); + return + }; + + // Block is not part of the subscription. + if !handle.contains_block(&hash) { + let _ = sink.reject(ChainHeadRpcError::InvalidBlock); + return + } + + // The child key is provided, use the key to query the child trie. + if let Some(child_key) = child_key { + // The child key must not be prefixed with ":child_storage:" nor + // ":child_storage:default:". + if well_known_keys::is_default_child_storage_key(child_key.storage_key()) || + well_known_keys::is_child_storage_key(child_key.storage_key()) + { + let _ = sink + .send(&ChainHeadEvent::Done(ChainHeadResult { result: None:: })); + return + } + + let res = client + .child_storage(hash, &child_key, &key) + .map(|result| { + let result = + result.map(|storage| format!("0x{:?}", HexDisplay::from(&storage.0))); + ChainHeadEvent::Done(ChainHeadResult { result }) + }) + .unwrap_or_else(|error| { + ChainHeadEvent::Error(ErrorEvent { error: error.to_string() }) + }); + let _ = sink.send(&res); + return + } + + // The main key must not be prefixed with b":child_storage:" nor + // b":child_storage:default:". + if well_known_keys::is_default_child_storage_key(&key.0) || + well_known_keys::is_child_storage_key(&key.0) + { + let _ = + sink.send(&ChainHeadEvent::Done(ChainHeadResult { result: None:: })); + return + } + + // Main root trie storage query. + let res = client + .storage(hash, &key) + .map(|result| { + let result = + result.map(|storage| format!("0x{:?}", HexDisplay::from(&storage.0))); + ChainHeadEvent::Done(ChainHeadResult { result }) + }) + .unwrap_or_else(|error| { + ChainHeadEvent::Error(ErrorEvent { error: error.to_string() }) + }); + let _ = sink.send(&res); + }; + + self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); + Ok(()) + } + + fn chain_head_unstable_call( + &self, + mut sink: SubscriptionSink, + follow_subscription: String, + hash: Block::Hash, + function: String, + call_parameters: String, + _network_config: Option, + ) -> SubscriptionResult { + let call_parameters = Bytes::from(parse_hex_param(&mut sink, call_parameters)?); + + let client = self.client.clone(); + let subscriptions = self.subscriptions.clone(); + + let fut = async move { + let Some(handle) = subscriptions.get_subscription(&follow_subscription) else { + // Invalid invalid subscription ID. + let _ = sink.send(&ChainHeadEvent::::Disjoint); + return + }; + + // Block is not part of the subscription. + if !handle.contains_block(&hash) { + let _ = sink.reject(ChainHeadRpcError::InvalidBlock); + return + } + + // Reject subscription if runtime_updates is false. + if !handle.has_runtime_updates() { + let _ = sink.reject(ChainHeadRpcError::InvalidParam( + "The runtime updates flag must be set".into(), + )); + return + } + + let res = client + .executor() + .call( + &BlockId::Hash(hash), + &function, + &call_parameters, + client.execution_extensions().strategies().other, + ) + .map(|result| { + let result = format!("0x{:?}", HexDisplay::from(&result)); + ChainHeadEvent::Done(ChainHeadResult { result }) + }) + .unwrap_or_else(|error| { + ChainHeadEvent::Error(ErrorEvent { error: error.to_string() }) + }); + + let _ = sink.send(&res); + }; + + self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); + Ok(()) + } + + fn chain_head_unstable_unpin( + &self, + follow_subscription: String, + hash: Block::Hash, + ) -> RpcResult<()> { + let Some(handle) = self.subscriptions.get_subscription(&follow_subscription) else { + // Invalid invalid subscription ID. + return Ok(()) + }; + + if !handle.unpin_block(&hash) { + return Err(ChainHeadRpcError::InvalidBlock.into()) + } + + Ok(()) + } +} diff --git a/client/rpc-spec-v2/src/chain_head/error.rs b/client/rpc-spec-v2/src/chain_head/error.rs new file mode 100644 index 0000000000..92f336ed4f --- /dev/null +++ b/client/rpc-spec-v2/src/chain_head/error.rs @@ -0,0 +1,74 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 . + +//! Error helpers for `chainHead` RPC module. + +use jsonrpsee::{ + core::Error as RpcError, + types::error::{CallError, ErrorObject}, +}; +use sp_blockchain::Error as BlockchainError; + +/// ChainHead RPC errors. +#[derive(Debug, thiserror::Error)] +pub enum Error { + /// The provided block hash is invalid. + #[error("Invalid block hash")] + InvalidBlock, + /// Fetch block header error. + #[error("Could not fetch block header: {0}")] + FetchBlockHeader(BlockchainError), + /// Invalid parameter provided to the RPC method. + #[error("Invalid parameter: {0}")] + InvalidParam(String), + /// Invalid subscription ID provided by the RPC server. + #[error("Invalid subscription ID")] + InvalidSubscriptionID, +} + +// Base code for all `chainHead` errors. +const BASE_ERROR: i32 = 2000; +/// The provided block hash is invalid. +const INVALID_BLOCK_ERROR: i32 = BASE_ERROR + 1; +/// Fetch block header error. +const FETCH_BLOCK_HEADER_ERROR: i32 = BASE_ERROR + 2; +/// Invalid parameter error. +const INVALID_PARAM_ERROR: i32 = BASE_ERROR + 3; +/// Invalid subscription ID. +const INVALID_SUB_ID: i32 = BASE_ERROR + 4; + +impl From for ErrorObject<'static> { + fn from(e: Error) -> Self { + let msg = e.to_string(); + + match e { + Error::InvalidBlock => ErrorObject::owned(INVALID_BLOCK_ERROR, msg, None::<()>), + Error::FetchBlockHeader(_) => + ErrorObject::owned(FETCH_BLOCK_HEADER_ERROR, msg, None::<()>), + Error::InvalidParam(_) => ErrorObject::owned(INVALID_PARAM_ERROR, msg, None::<()>), + Error::InvalidSubscriptionID => ErrorObject::owned(INVALID_SUB_ID, msg, None::<()>), + } + .into() + } +} + +impl From for RpcError { + fn from(e: Error) -> Self { + CallError::Custom(e.into()).into() + } +} diff --git a/client/rpc-spec-v2/src/chain_head/event.rs b/client/rpc-spec-v2/src/chain_head/event.rs new file mode 100644 index 0000000000..25930cb6f9 --- /dev/null +++ b/client/rpc-spec-v2/src/chain_head/event.rs @@ -0,0 +1,480 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 . + +//! The chain head's event returned as json compatible object. + +use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer}; +use sp_api::ApiError; +use sp_version::RuntimeVersion; +use std::num::NonZeroUsize; + +/// The network config parameter is used when a function +/// needs to request the information from its peers. +/// +/// These values can be tweaked depending on the urgency of the JSON-RPC function call. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct NetworkConfig { + /// The total number of peers from which the information is requested. + total_attempts: u64, + /// The maximum number of requests to perform in parallel. + /// + /// # Note + /// + /// A zero value is illegal. + max_parallel: NonZeroUsize, + /// The time, in milliseconds, after which a single requests towards one peer + /// is considered unsuccessful. + timeout_ms: u64, +} + +/// The operation could not be processed due to an error. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ErrorEvent { + /// Reason of the error. + pub error: String, +} + +/// The runtime specification of the current block. +/// +/// This event is generated for: +/// - the first announced block by the follow subscription +/// - blocks that suffered a change in runtime compared with their parents +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RuntimeVersionEvent { + /// The runtime version. + pub spec: RuntimeVersion, +} + +/// The runtime event generated if the `follow` subscription +/// has set the `runtime_updates` flag. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[serde(tag = "type")] +pub enum RuntimeEvent { + /// The runtime version of this block. + Valid(RuntimeVersionEvent), + /// The runtime could not be obtained due to an error. + Invalid(ErrorEvent), +} + +impl From for RuntimeEvent { + fn from(err: ApiError) -> Self { + RuntimeEvent::Invalid(ErrorEvent { error: format!("Api error: {}", err) }) + } +} + +/// Contain information about the latest finalized block. +/// +/// # Note +/// +/// This is the first event generated by the `follow` subscription +/// and is submitted only once. +/// +/// If the `runtime_updates` flag is set, then this event contains +/// the `RuntimeEvent`, otherwise the `RuntimeEvent` is not present. +#[derive(Debug, Clone, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Initialized { + /// The hash of the latest finalized block. + pub finalized_block_hash: Hash, + /// The runtime version of the finalized block. + /// + /// # Note + /// + /// This is present only if the `runtime_updates` flag is set for + /// the `follow` subscription. + pub finalized_block_runtime: Option, + /// Privately keep track if the `finalized_block_runtime` should be + /// serialized. + #[serde(default)] + pub(crate) runtime_updates: bool, +} + +impl Serialize for Initialized { + /// Custom serialize implementation to include the `RuntimeEvent` depending + /// on the internal `runtime_updates` flag. + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + if self.runtime_updates { + let mut state = serializer.serialize_struct("Initialized", 2)?; + state.serialize_field("finalizedBlockHash", &self.finalized_block_hash)?; + state.serialize_field("finalizedBlockRuntime", &self.finalized_block_runtime)?; + state.end() + } else { + let mut state = serializer.serialize_struct("Initialized", 1)?; + state.serialize_field("finalizedBlockHash", &self.finalized_block_hash)?; + state.end() + } + } +} + +/// Indicate a new non-finalized block. +#[derive(Debug, Clone, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct NewBlock { + /// The hash of the new block. + pub block_hash: Hash, + /// The parent hash of the new block. + pub parent_block_hash: Hash, + /// The runtime version of the new block. + /// + /// # Note + /// + /// This is present only if the `runtime_updates` flag is set for + /// the `follow` subscription. + pub new_runtime: Option, + /// Privately keep track if the `finalized_block_runtime` should be + /// serialized. + #[serde(default)] + pub(crate) runtime_updates: bool, +} + +impl Serialize for NewBlock { + /// Custom serialize implementation to include the `RuntimeEvent` depending + /// on the internal `runtime_updates` flag. + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + if self.runtime_updates { + let mut state = serializer.serialize_struct("NewBlock", 3)?; + state.serialize_field("blockHash", &self.block_hash)?; + state.serialize_field("parentBlockHash", &self.parent_block_hash)?; + state.serialize_field("newRuntime", &self.new_runtime)?; + state.end() + } else { + let mut state = serializer.serialize_struct("NewBlock", 2)?; + state.serialize_field("blockHash", &self.block_hash)?; + state.serialize_field("parentBlockHash", &self.parent_block_hash)?; + state.end() + } + } +} + +/// Indicate the block hash of the new best block. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BestBlockChanged { + /// The block hash of the new best block. + pub best_block_hash: Hash, +} + +/// Indicate the finalized and pruned block hashes. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Finalized { + /// Block hashes that are finalized. + pub finalized_block_hashes: Vec, + /// Block hashes that are pruned (removed). + pub pruned_block_hashes: Vec, +} + +/// The event generated by the `follow` method. +/// +/// The events are generated in the following order: +/// 1. Initialized - generated only once to signal the +/// latest finalized block +/// 2. NewBlock - a new block was added. +/// 3. BestBlockChanged - indicate that the best block +/// is now the one from this event. The block was +/// announced priorly with the `NewBlock` event. +/// 4. Finalized - State the finalized and pruned blocks. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[serde(tag = "event")] +pub enum FollowEvent { + /// The latest finalized block. + /// + /// This event is generated only once. + Initialized(Initialized), + /// A new non-finalized block was added. + NewBlock(NewBlock), + /// The best block of the chain. + BestBlockChanged(BestBlockChanged), + /// A list of finalized and pruned blocks. + Finalized(Finalized), + /// The subscription is dropped and no further events + /// will be generated. + Stop, +} + +/// The result of a chain head method. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ChainHeadResult { + /// Result of the method. + pub result: T, +} + +/// The event generated by the body / call / storage methods. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[serde(tag = "event")] +pub enum ChainHeadEvent { + /// The request completed successfully. + Done(ChainHeadResult), + /// The resources requested are inaccessible. + /// + /// Resubmitting the request later might succeed. + Inaccessible(ErrorEvent), + /// An error occurred. This is definitive. + Error(ErrorEvent), + /// The provided subscription ID is stale or invalid. + Disjoint, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn follow_initialized_event_no_updates() { + // Runtime flag is false. + let event: FollowEvent = FollowEvent::Initialized(Initialized { + finalized_block_hash: "0x1".into(), + finalized_block_runtime: None, + runtime_updates: false, + }); + + let ser = serde_json::to_string(&event).unwrap(); + let exp = r#"{"event":"initialized","finalizedBlockHash":"0x1"}"#; + assert_eq!(ser, exp); + + let event_dec: FollowEvent = serde_json::from_str(exp).unwrap(); + assert_eq!(event_dec, event); + } + + #[test] + fn follow_initialized_event_with_updates() { + // Runtime flag is true, block runtime must always be reported for this event. + let runtime = RuntimeVersion { + spec_name: "ABC".into(), + impl_name: "Impl".into(), + spec_version: 1, + ..Default::default() + }; + + let runtime_event = RuntimeEvent::Valid(RuntimeVersionEvent { spec: runtime }); + let mut initialized = Initialized { + finalized_block_hash: "0x1".into(), + finalized_block_runtime: Some(runtime_event), + runtime_updates: true, + }; + let event: FollowEvent = FollowEvent::Initialized(initialized.clone()); + + let ser = serde_json::to_string(&event).unwrap(); + let exp = concat!( + r#"{"event":"initialized","finalizedBlockHash":"0x1","#, + r#""finalizedBlockRuntime":{"type":"valid","spec":{"specName":"ABC","implName":"Impl","authoringVersion":0,"#, + r#""specVersion":1,"implVersion":0,"apis":[],"transactionVersion":0,"stateVersion":0}}}"#, + ); + assert_eq!(ser, exp); + + let event_dec: FollowEvent = serde_json::from_str(exp).unwrap(); + // The `runtime_updates` field is used for serialization purposes. + initialized.runtime_updates = false; + assert!(matches!( + event_dec, FollowEvent::Initialized(ref dec) if dec == &initialized + )); + } + + #[test] + fn follow_new_block_event_no_updates() { + // Runtime flag is false. + let event: FollowEvent = FollowEvent::NewBlock(NewBlock { + block_hash: "0x1".into(), + parent_block_hash: "0x2".into(), + new_runtime: None, + runtime_updates: false, + }); + + let ser = serde_json::to_string(&event).unwrap(); + let exp = r#"{"event":"newBlock","blockHash":"0x1","parentBlockHash":"0x2"}"#; + assert_eq!(ser, exp); + + let event_dec: FollowEvent = serde_json::from_str(exp).unwrap(); + assert_eq!(event_dec, event); + } + + #[test] + fn follow_new_block_event_with_updates() { + // Runtime flag is true, block runtime must always be reported for this event. + let runtime = RuntimeVersion { + spec_name: "ABC".into(), + impl_name: "Impl".into(), + spec_version: 1, + ..Default::default() + }; + + let runtime_event = RuntimeEvent::Valid(RuntimeVersionEvent { spec: runtime }); + let mut new_block = NewBlock { + block_hash: "0x1".into(), + parent_block_hash: "0x2".into(), + new_runtime: Some(runtime_event), + runtime_updates: true, + }; + + let event: FollowEvent = FollowEvent::NewBlock(new_block.clone()); + + let ser = serde_json::to_string(&event).unwrap(); + let exp = concat!( + r#"{"event":"newBlock","blockHash":"0x1","parentBlockHash":"0x2","#, + r#""newRuntime":{"type":"valid","spec":{"specName":"ABC","implName":"Impl","authoringVersion":0,"#, + r#""specVersion":1,"implVersion":0,"apis":[],"transactionVersion":0,"stateVersion":0}}}"#, + ); + assert_eq!(ser, exp); + + let event_dec: FollowEvent = serde_json::from_str(exp).unwrap(); + // The `runtime_updates` field is used for serialization purposes. + new_block.runtime_updates = false; + assert!(matches!( + event_dec, FollowEvent::NewBlock(ref dec) if dec == &new_block + )); + + // Runtime flag is true, runtime didn't change compared to parent. + let mut new_block = NewBlock { + block_hash: "0x1".into(), + parent_block_hash: "0x2".into(), + new_runtime: None, + runtime_updates: true, + }; + let event: FollowEvent = FollowEvent::NewBlock(new_block.clone()); + + let ser = serde_json::to_string(&event).unwrap(); + let exp = + r#"{"event":"newBlock","blockHash":"0x1","parentBlockHash":"0x2","newRuntime":null}"#; + assert_eq!(ser, exp); + new_block.runtime_updates = false; + let event_dec: FollowEvent = serde_json::from_str(exp).unwrap(); + assert!(matches!( + event_dec, FollowEvent::NewBlock(ref dec) if dec == &new_block + )); + } + + #[test] + fn follow_best_block_changed_event() { + let event: FollowEvent = + FollowEvent::BestBlockChanged(BestBlockChanged { best_block_hash: "0x1".into() }); + + let ser = serde_json::to_string(&event).unwrap(); + let exp = r#"{"event":"bestBlockChanged","bestBlockHash":"0x1"}"#; + assert_eq!(ser, exp); + + let event_dec: FollowEvent = serde_json::from_str(exp).unwrap(); + assert_eq!(event_dec, event); + } + + #[test] + fn follow_finalized_event() { + let event: FollowEvent = FollowEvent::Finalized(Finalized { + finalized_block_hashes: vec!["0x1".into()], + pruned_block_hashes: vec!["0x2".into()], + }); + + let ser = serde_json::to_string(&event).unwrap(); + let exp = + r#"{"event":"finalized","finalizedBlockHashes":["0x1"],"prunedBlockHashes":["0x2"]}"#; + assert_eq!(ser, exp); + + let event_dec: FollowEvent = serde_json::from_str(exp).unwrap(); + assert_eq!(event_dec, event); + } + + #[test] + fn follow_stop_event() { + let event: FollowEvent = FollowEvent::Stop; + + let ser = serde_json::to_string(&event).unwrap(); + let exp = r#"{"event":"stop"}"#; + assert_eq!(ser, exp); + + let event_dec: FollowEvent = serde_json::from_str(exp).unwrap(); + assert_eq!(event_dec, event); + } + + #[test] + fn chain_head_done_event() { + let event: ChainHeadEvent = + ChainHeadEvent::Done(ChainHeadResult { result: "A".into() }); + + let ser = serde_json::to_string(&event).unwrap(); + let exp = r#"{"event":"done","result":"A"}"#; + assert_eq!(ser, exp); + + let event_dec: ChainHeadEvent = serde_json::from_str(exp).unwrap(); + assert_eq!(event_dec, event); + } + + #[test] + fn chain_head_inaccessible_event() { + let event: ChainHeadEvent = + ChainHeadEvent::Inaccessible(ErrorEvent { error: "A".into() }); + + let ser = serde_json::to_string(&event).unwrap(); + let exp = r#"{"event":"inaccessible","error":"A"}"#; + assert_eq!(ser, exp); + + let event_dec: ChainHeadEvent = serde_json::from_str(exp).unwrap(); + assert_eq!(event_dec, event); + } + + #[test] + fn chain_head_error_event() { + let event: ChainHeadEvent = ChainHeadEvent::Error(ErrorEvent { error: "A".into() }); + + let ser = serde_json::to_string(&event).unwrap(); + let exp = r#"{"event":"error","error":"A"}"#; + assert_eq!(ser, exp); + + let event_dec: ChainHeadEvent = serde_json::from_str(exp).unwrap(); + assert_eq!(event_dec, event); + } + + #[test] + fn chain_head_disjoint_event() { + let event: ChainHeadEvent = ChainHeadEvent::Disjoint; + + let ser = serde_json::to_string(&event).unwrap(); + let exp = r#"{"event":"disjoint"}"#; + assert_eq!(ser, exp); + + let event_dec: ChainHeadEvent = serde_json::from_str(exp).unwrap(); + assert_eq!(event_dec, event); + } + + #[test] + fn chain_head_network_config() { + let conf = NetworkConfig { + total_attempts: 1, + max_parallel: NonZeroUsize::new(2).expect("Non zero number; qed"), + timeout_ms: 3, + }; + + let ser = serde_json::to_string(&conf).unwrap(); + let exp = r#"{"totalAttempts":1,"maxParallel":2,"timeoutMs":3}"#; + assert_eq!(ser, exp); + + let conf_dec: NetworkConfig = serde_json::from_str(exp).unwrap(); + assert_eq!(conf_dec, conf); + } +} diff --git a/client/rpc-spec-v2/src/chain_head/mod.rs b/client/rpc-spec-v2/src/chain_head/mod.rs new file mode 100644 index 0000000000..a25933b40f --- /dev/null +++ b/client/rpc-spec-v2/src/chain_head/mod.rs @@ -0,0 +1,40 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 . + +//! Substrate chain head API. +//! +//! # Note +//! +//! Methods are prefixed by `chainHead`. + +#[cfg(test)] +mod tests; + +pub mod api; +pub mod chain_head; +pub mod error; +pub mod event; + +mod subscription; + +pub use api::ChainHeadApiServer; +pub use chain_head::ChainHead; +pub use event::{ + BestBlockChanged, ChainHeadEvent, ChainHeadResult, ErrorEvent, Finalized, FollowEvent, + Initialized, NetworkConfig, NewBlock, RuntimeEvent, RuntimeVersionEvent, +}; diff --git a/client/rpc-spec-v2/src/chain_head/subscription.rs b/client/rpc-spec-v2/src/chain_head/subscription.rs new file mode 100644 index 0000000000..033db45ca7 --- /dev/null +++ b/client/rpc-spec-v2/src/chain_head/subscription.rs @@ -0,0 +1,284 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 . + +//! Subscription management for tracking subscription IDs to pinned blocks. + +use futures::channel::oneshot; +use parking_lot::{RwLock, RwLockWriteGuard}; +use sp_runtime::traits::Block as BlockT; +use std::{ + collections::{hash_map::Entry, HashMap, HashSet}, + sync::Arc, +}; + +/// Subscription management error. +#[derive(Debug)] +pub enum SubscriptionManagementError { + /// The block cannot be pinned into memory because + /// the subscription has exceeded the maximum number + /// of blocks pinned. + ExceededLimits, + /// Custom error. + Custom(String), +} + +/// Inner subscription data structure. +struct SubscriptionInner { + /// The `runtime_updates` parameter flag of the subscription. + runtime_updates: bool, + /// Signals the "Stop" event. + tx_stop: Option>, + /// The blocks pinned. + blocks: HashSet, + /// The maximum number of pinned blocks allowed per subscription. + max_pinned_blocks: usize, +} + +/// Manage the blocks of a specific subscription ID. +#[derive(Clone)] +pub struct SubscriptionHandle { + inner: Arc>>, + /// The best reported block by this subscription. + /// Have this as a separate variable to easily share + /// the write guard with the RPC layer. + best_block: Arc>>, +} + +impl SubscriptionHandle { + /// Construct a new [`SubscriptionHandle`]. + fn new(runtime_updates: bool, tx_stop: oneshot::Sender<()>, max_pinned_blocks: usize) -> Self { + SubscriptionHandle { + inner: Arc::new(RwLock::new(SubscriptionInner { + runtime_updates, + tx_stop: Some(tx_stop), + blocks: HashSet::new(), + max_pinned_blocks, + })), + best_block: Arc::new(RwLock::new(None)), + } + } + + /// Trigger the stop event for the current subscription. + /// + /// This can happen on internal failure (ie, the pruning deleted the block from memory) + /// or if the user exceeded the amount of available pinned blocks. + pub fn stop(&self) { + let mut inner = self.inner.write(); + + if let Some(tx_stop) = inner.tx_stop.take() { + let _ = tx_stop.send(()); + } + } + + /// Pin a new block for the current subscription ID. + /// + /// Returns whether the value was newly inserted if the block can be pinned. + /// Otherwise, returns an error if the maximum number of blocks has been exceeded. + pub fn pin_block(&self, hash: Block::Hash) -> Result { + let mut inner = self.inner.write(); + + if inner.blocks.len() == inner.max_pinned_blocks { + // We have reached the limit. However, the block can be already inserted. + if inner.blocks.contains(&hash) { + return Ok(false) + } else { + return Err(SubscriptionManagementError::ExceededLimits) + } + } + + Ok(inner.blocks.insert(hash)) + } + + /// Unpin a new block for the current subscription ID. + /// + /// Returns whether the value was present in the set. + pub fn unpin_block(&self, hash: &Block::Hash) -> bool { + let mut inner = self.inner.write(); + inner.blocks.remove(hash) + } + + /// Check if the block hash is present for the provided subscription ID. + /// + /// Returns `true` if the set contains the block. + pub fn contains_block(&self, hash: &Block::Hash) -> bool { + let inner = self.inner.read(); + inner.blocks.contains(hash) + } + + /// Get the `runtime_updates` flag of this subscription. + pub fn has_runtime_updates(&self) -> bool { + let inner = self.inner.read(); + inner.runtime_updates + } + + /// Get the write guard of the best reported block. + pub fn best_block_write(&self) -> RwLockWriteGuard<'_, Option> { + self.best_block.write() + } +} + +/// Manage block pinning / unpinning for subscription IDs. +pub struct SubscriptionManagement { + /// Manage subscription by mapping the subscription ID + /// to a set of block hashes. + inner: RwLock>>, +} + +impl SubscriptionManagement { + /// Construct a new [`SubscriptionManagement`]. + pub fn new() -> Self { + SubscriptionManagement { inner: RwLock::new(HashMap::new()) } + } + + /// Insert a new subscription ID. + /// + /// If the subscription was not previously inserted, the method returns a tuple of + /// the receiver that is triggered upon the "Stop" event and the subscription + /// handle. Otherwise, when the subscription ID was already inserted returns none. + pub fn insert_subscription( + &self, + subscription_id: String, + runtime_updates: bool, + max_pinned_blocks: usize, + ) -> Option<(oneshot::Receiver<()>, SubscriptionHandle)> { + let mut subs = self.inner.write(); + + if let Entry::Vacant(entry) = subs.entry(subscription_id) { + let (tx_stop, rx_stop) = oneshot::channel(); + let handle = + SubscriptionHandle::::new(runtime_updates, tx_stop, max_pinned_blocks); + entry.insert(handle.clone()); + Some((rx_stop, handle)) + } else { + None + } + } + + /// Remove the subscription ID with associated pinned blocks. + pub fn remove_subscription(&self, subscription_id: &String) { + let mut subs = self.inner.write(); + subs.remove(subscription_id); + } + + /// Obtain the specific subscription handle. + pub fn get_subscription(&self, subscription_id: &String) -> Option> { + let subs = self.inner.write(); + subs.get(subscription_id).and_then(|handle| Some(handle.clone())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use sp_core::H256; + use substrate_test_runtime_client::runtime::Block; + + #[test] + fn subscription_check_id() { + let subs = SubscriptionManagement::::new(); + + let id = "abc".to_string(); + let hash = H256::random(); + + let handle = subs.get_subscription(&id); + assert!(handle.is_none()); + + let (_, handle) = subs.insert_subscription(id.clone(), false, 10).unwrap(); + assert!(!handle.contains_block(&hash)); + + subs.remove_subscription(&id); + + let handle = subs.get_subscription(&id); + assert!(handle.is_none()); + } + + #[test] + fn subscription_check_block() { + let subs = SubscriptionManagement::::new(); + + let id = "abc".to_string(); + let hash = H256::random(); + + // Check with subscription. + let (_, handle) = subs.insert_subscription(id.clone(), false, 10).unwrap(); + assert!(!handle.contains_block(&hash)); + assert!(!handle.unpin_block(&hash)); + + handle.pin_block(hash).unwrap(); + assert!(handle.contains_block(&hash)); + // Unpin an invalid block. + assert!(!handle.unpin_block(&H256::random())); + + // Unpin the valid block. + assert!(handle.unpin_block(&hash)); + assert!(!handle.contains_block(&hash)); + } + + #[test] + fn subscription_check_stop_event() { + let subs = SubscriptionManagement::::new(); + + let id = "abc".to_string(); + + // Check with subscription. + let (mut rx_stop, handle) = subs.insert_subscription(id.clone(), false, 10).unwrap(); + + // Check the stop signal was not received. + let res = rx_stop.try_recv().unwrap(); + assert!(res.is_none()); + + // Inserting a second time returns None. + let res = subs.insert_subscription(id.clone(), false, 10); + assert!(res.is_none()); + + handle.stop(); + + // Check the signal was received. + let res = rx_stop.try_recv().unwrap(); + assert!(res.is_some()); + } + + #[test] + fn subscription_check_data() { + let subs = SubscriptionManagement::::new(); + + let id = "abc".to_string(); + let (_, handle) = subs.insert_subscription(id.clone(), false, 10).unwrap(); + assert!(!handle.has_runtime_updates()); + + let id2 = "abcd".to_string(); + let (_, handle) = subs.insert_subscription(id2.clone(), true, 10).unwrap(); + assert!(handle.has_runtime_updates()); + } + + #[test] + fn subscription_check_max_pinned() { + let subs = SubscriptionManagement::::new(); + + let id = "abc".to_string(); + let hash = H256::random(); + let hash_2 = H256::random(); + let (_, handle) = subs.insert_subscription(id.clone(), false, 1).unwrap(); + + handle.pin_block(hash).unwrap(); + // The same block can be pinned multiple times. + handle.pin_block(hash).unwrap(); + // Exceeded number of pinned blocks. + handle.pin_block(hash_2).unwrap_err(); + } +} diff --git a/client/rpc-spec-v2/src/chain_head/tests.rs b/client/rpc-spec-v2/src/chain_head/tests.rs new file mode 100644 index 0000000000..4084075f0b --- /dev/null +++ b/client/rpc-spec-v2/src/chain_head/tests.rs @@ -0,0 +1,1022 @@ +use super::*; +use assert_matches::assert_matches; +use codec::{Decode, Encode}; +use jsonrpsee::{ + core::{error::Error, server::rpc_module::Subscription as RpcSubscription}, + types::{error::CallError, EmptyServerParams as EmptyParams}, + RpcModule, +}; +use sc_block_builder::BlockBuilderProvider; +use sc_client_api::ChildInfo; +use sp_api::BlockId; +use sp_blockchain::HeaderBackend; +use sp_consensus::BlockOrigin; +use sp_core::{ + hexdisplay::HexDisplay, + storage::well_known_keys::{self, CODE}, + testing::TaskExecutor, +}; +use sp_version::RuntimeVersion; +use std::sync::Arc; +use substrate_test_runtime::Transfer; +use substrate_test_runtime_client::{ + prelude::*, runtime, Backend, BlockBuilderExt, Client, ClientBlockImportExt, +}; + +type Header = substrate_test_runtime_client::runtime::Header; +type Block = substrate_test_runtime_client::runtime::Block; +const MAX_PINNED_BLOCKS: usize = 32; +const CHAIN_GENESIS: [u8; 32] = [0; 32]; +const INVALID_HASH: [u8; 32] = [1; 32]; +const KEY: &[u8] = b":mock"; +const VALUE: &[u8] = b"hello world"; +const CHILD_STORAGE_KEY: &[u8] = b"child"; +const CHILD_VALUE: &[u8] = b"child value"; + +async fn get_next_event(sub: &mut RpcSubscription) -> T { + let (event, _sub_id) = tokio::time::timeout(std::time::Duration::from_secs(1), sub.next()) + .await + .unwrap() + .unwrap() + .unwrap(); + event +} + +async fn setup_api() -> ( + Arc>, + RpcModule>>, + RpcSubscription, + String, + Block, +) { + let child_info = ChildInfo::new_default(CHILD_STORAGE_KEY); + let builder = TestClientBuilder::new().add_extra_child_storage( + &child_info, + KEY.to_vec(), + CHILD_VALUE.to_vec(), + ); + let backend = builder.backend(); + let mut client = Arc::new(builder.build()); + + let api = ChainHead::new( + client.clone(), + backend, + Arc::new(TaskExecutor::default()), + CHAIN_GENESIS, + MAX_PINNED_BLOCKS, + ) + .into_rpc(); + + let mut sub = api.subscribe("chainHead_unstable_follow", [true]).await.unwrap(); + let sub_id = sub.subscription_id(); + let sub_id = serde_json::to_string(&sub_id).unwrap(); + + let block = client.new_block(Default::default()).unwrap().build().unwrap().block; + client.import(BlockOrigin::Own, block.clone()).await.unwrap(); + + // Ensure the imported block is propagated and pinned for this subscription. + assert_matches!( + get_next_event::>(&mut sub).await, + FollowEvent::Initialized(_) + ); + assert_matches!( + get_next_event::>(&mut sub).await, + FollowEvent::NewBlock(_) + ); + assert_matches!( + get_next_event::>(&mut sub).await, + FollowEvent::BestBlockChanged(_) + ); + + (client, api, sub, sub_id, block) +} + +#[tokio::test] +async fn follow_subscription_produces_blocks() { + let builder = TestClientBuilder::new(); + let backend = builder.backend(); + let mut client = Arc::new(builder.build()); + + let api = ChainHead::new( + client.clone(), + backend, + Arc::new(TaskExecutor::default()), + CHAIN_GENESIS, + MAX_PINNED_BLOCKS, + ) + .into_rpc(); + + let finalized_hash = client.info().finalized_hash; + let mut sub = api.subscribe("chainHead_unstable_follow", [false]).await.unwrap(); + + // Initialized must always be reported first. + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::Initialized(Initialized { + finalized_block_hash: format!("{:?}", finalized_hash), + finalized_block_runtime: None, + runtime_updates: false, + }); + assert_eq!(event, expected); + + let block = client.new_block(Default::default()).unwrap().build().unwrap().block; + + let best_hash = block.header.hash(); + client.import(BlockOrigin::Own, block.clone()).await.unwrap(); + + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::NewBlock(NewBlock { + block_hash: format!("{:?}", best_hash), + parent_block_hash: format!("{:?}", finalized_hash), + new_runtime: None, + runtime_updates: false, + }); + assert_eq!(event, expected); + + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::BestBlockChanged(BestBlockChanged { + best_block_hash: format!("{:?}", best_hash), + }); + assert_eq!(event, expected); + + client.finalize_block(best_hash, None).unwrap(); + + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::Finalized(Finalized { + finalized_block_hashes: vec![format!("{:?}", best_hash)], + pruned_block_hashes: vec![], + }); + assert_eq!(event, expected); +} + +#[tokio::test] +async fn follow_with_runtime() { + let builder = TestClientBuilder::new(); + let backend = builder.backend(); + let mut client = Arc::new(builder.build()); + + let api = ChainHead::new( + client.clone(), + backend, + Arc::new(TaskExecutor::default()), + CHAIN_GENESIS, + MAX_PINNED_BLOCKS, + ) + .into_rpc(); + + let finalized_hash = client.info().finalized_hash; + let mut sub = api.subscribe("chainHead_unstable_follow", [true]).await.unwrap(); + + // Initialized must always be reported first. + let event: FollowEvent = get_next_event(&mut sub).await; + + let runtime_str = "{\"specName\":\"test\",\"implName\":\"parity-test\",\"authoringVersion\":1,\ + \"specVersion\":2,\"implVersion\":2,\"apis\":[[\"0xdf6acb689907609b\",4],\ + [\"0x37e397fc7c91f5e4\",1],[\"0xd2bc9897eed08f15\",3],[\"0x40fe3ad401f8959a\",6],\ + [\"0xc6e9a76309f39b09\",1],[\"0xdd718d5cc53262d4\",1],[\"0xcbca25e39f142387\",2],\ + [\"0xf78b278be53f454c\",2],[\"0xab3c0572291feb8b\",1],[\"0xbc9d89904f5b923f\",1]],\ + \"transactionVersion\":1,\"stateVersion\":1}"; + let runtime: RuntimeVersion = serde_json::from_str(runtime_str).unwrap(); + + let finalized_block_runtime = + Some(RuntimeEvent::Valid(RuntimeVersionEvent { spec: runtime.clone() })); + // Runtime must always be reported with the first event. + let expected = FollowEvent::Initialized(Initialized { + finalized_block_hash: format!("{:?}", finalized_hash), + finalized_block_runtime, + runtime_updates: false, + }); + assert_eq!(event, expected); + + // Import a new block without runtime changes. + // The runtime field must be None in this case. + let block = client.new_block(Default::default()).unwrap().build().unwrap().block; + let best_hash = block.header.hash(); + client.import(BlockOrigin::Own, block.clone()).await.unwrap(); + + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::NewBlock(NewBlock { + block_hash: format!("{:?}", best_hash), + parent_block_hash: format!("{:?}", finalized_hash), + new_runtime: None, + runtime_updates: false, + }); + assert_eq!(event, expected); + + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::BestBlockChanged(BestBlockChanged { + best_block_hash: format!("{:?}", best_hash), + }); + assert_eq!(event, expected); + + client.finalize_block(best_hash, None).unwrap(); + + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::Finalized(Finalized { + finalized_block_hashes: vec![format!("{:?}", best_hash)], + pruned_block_hashes: vec![], + }); + assert_eq!(event, expected); + + let finalized_hash = best_hash; + // The `RuntimeVersion` is embedded into the WASM blob at the `runtime_version` + // section. Modify the `RuntimeVersion` and commit the changes to a new block. + // The RPC must notify the runtime event change. + let wasm = sp_maybe_compressed_blob::decompress( + runtime::wasm_binary_unwrap(), + sp_maybe_compressed_blob::CODE_BLOB_BOMB_LIMIT, + ) + .unwrap(); + // Update the runtime spec version. + let mut runtime = runtime; + runtime.spec_version += 1; + let embedded = sp_version::embed::embed_runtime_version(&wasm, runtime.clone()).unwrap(); + let wasm = sp_maybe_compressed_blob::compress( + &embedded, + sp_maybe_compressed_blob::CODE_BLOB_BOMB_LIMIT, + ) + .unwrap(); + + let mut builder = client.new_block(Default::default()).unwrap(); + builder.push_storage_change(CODE.to_vec(), Some(wasm)).unwrap(); + let block = builder.build().unwrap().block; + let best_hash = block.header.hash(); + client.import(BlockOrigin::Own, block.clone()).await.unwrap(); + + let new_runtime = Some(RuntimeEvent::Valid(RuntimeVersionEvent { spec: runtime.clone() })); + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::NewBlock(NewBlock { + block_hash: format!("{:?}", best_hash), + parent_block_hash: format!("{:?}", finalized_hash), + new_runtime, + runtime_updates: false, + }); + assert_eq!(event, expected); +} + +#[tokio::test] +async fn get_genesis() { + let builder = TestClientBuilder::new(); + let backend = builder.backend(); + let client = Arc::new(builder.build()); + + let api = ChainHead::new( + client.clone(), + backend, + Arc::new(TaskExecutor::default()), + CHAIN_GENESIS, + MAX_PINNED_BLOCKS, + ) + .into_rpc(); + + let genesis: String = + api.call("chainHead_unstable_genesisHash", EmptyParams::new()).await.unwrap(); + assert_eq!(genesis, format!("0x{}", HexDisplay::from(&CHAIN_GENESIS))); +} + +#[tokio::test] +async fn get_header() { + let (_client, api, _sub, sub_id, block) = setup_api().await; + let block_hash = format!("{:?}", block.header.hash()); + let invalid_hash = format!("0x{:?}", HexDisplay::from(&INVALID_HASH)); + + // Invalid subscription ID must produce no results. + let res: Option = api + .call("chainHead_unstable_header", ["invalid_sub_id", &invalid_hash]) + .await + .unwrap(); + assert!(res.is_none()); + + // Valid subscription with invalid block hash will error. + let err = api + .call::<_, serde_json::Value>("chainHead_unstable_header", [&sub_id, &invalid_hash]) + .await + .unwrap_err(); + assert_matches!(err, + Error::Call(CallError::Custom(ref err)) if err.code() == 2001 && err.message() == "Invalid block hash" + ); + + // Obtain the valid header. + let res: String = api.call("chainHead_unstable_header", [&sub_id, &block_hash]).await.unwrap(); + let bytes = array_bytes::hex2bytes(&res).unwrap(); + let header: Header = Decode::decode(&mut &bytes[..]).unwrap(); + assert_eq!(header, block.header); +} + +#[tokio::test] +async fn get_body() { + let (mut client, api, mut block_sub, sub_id, block) = setup_api().await; + let block_hash = format!("{:?}", block.header.hash()); + let invalid_hash = format!("0x{:?}", HexDisplay::from(&INVALID_HASH)); + + // Subscription ID is stale the disjoint event is emitted. + let mut sub = api + .subscribe("chainHead_unstable_body", ["invalid_sub_id", &invalid_hash]) + .await + .unwrap(); + let event: ChainHeadEvent = get_next_event(&mut sub).await; + assert_eq!(event, ChainHeadEvent::::Disjoint); + + // Valid subscription ID with invalid block hash will error. + let err = api + .subscribe("chainHead_unstable_body", [&sub_id, &invalid_hash]) + .await + .unwrap_err(); + assert_matches!(err, + Error::Call(CallError::Custom(ref err)) if err.code() == 2001 && err.message() == "Invalid block hash" + ); + + // Obtain valid the body (list of extrinsics). + let mut sub = api.subscribe("chainHead_unstable_body", [&sub_id, &block_hash]).await.unwrap(); + let event: ChainHeadEvent = get_next_event(&mut sub).await; + // Block contains no extrinsics. + assert_matches!(event, + ChainHeadEvent::Done(done) if done.result == "0x00" + ); + + // Import a block with extrinsics. + let mut builder = client.new_block(Default::default()).unwrap(); + builder + .push_transfer(runtime::Transfer { + from: AccountKeyring::Alice.into(), + to: AccountKeyring::Ferdie.into(), + amount: 42, + nonce: 0, + }) + .unwrap(); + let block = builder.build().unwrap().block; + let block_hash = format!("{:?}", block.header.hash()); + client.import(BlockOrigin::Own, block.clone()).await.unwrap(); + // Ensure the imported block is propagated and pinned for this subscription. + assert_matches!( + get_next_event::>(&mut block_sub).await, + FollowEvent::NewBlock(_) + ); + assert_matches!( + get_next_event::>(&mut block_sub).await, + FollowEvent::BestBlockChanged(_) + ); + + let mut sub = api.subscribe("chainHead_unstable_body", [&sub_id, &block_hash]).await.unwrap(); + let event: ChainHeadEvent = get_next_event(&mut sub).await; + // Hex encoded scale encoded string for the vector of extrinsics. + let expected = format!("0x{:?}", HexDisplay::from(&block.extrinsics.encode())); + assert_matches!(event, + ChainHeadEvent::Done(done) if done.result == expected + ); +} + +#[tokio::test] +async fn call_runtime() { + let (_client, api, _sub, sub_id, block) = setup_api().await; + let block_hash = format!("{:?}", block.header.hash()); + let invalid_hash = format!("0x{:?}", HexDisplay::from(&INVALID_HASH)); + + // Subscription ID is stale the disjoint event is emitted. + let mut sub = api + .subscribe( + "chainHead_unstable_call", + ["invalid_sub_id", &block_hash, "BabeApi_current_epoch", "0x00"], + ) + .await + .unwrap(); + let event: ChainHeadEvent = get_next_event(&mut sub).await; + assert_eq!(event, ChainHeadEvent::::Disjoint); + + // Valid subscription ID with invalid block hash will error. + let err = api + .subscribe( + "chainHead_unstable_call", + [&sub_id, &invalid_hash, "BabeApi_current_epoch", "0x00"], + ) + .await + .unwrap_err(); + assert_matches!(err, + Error::Call(CallError::Custom(ref err)) if err.code() == 2001 && err.message() == "Invalid block hash" + ); + + // Pass an invalid parameters that cannot be decode. + let err = api + .subscribe( + "chainHead_unstable_call", + [&sub_id, &block_hash, "BabeApi_current_epoch", "0x0"], + ) + .await + .unwrap_err(); + assert_matches!(err, + Error::Call(CallError::Custom(ref err)) if err.code() == 2003 && err.message().contains("Invalid parameter") + ); + + let alice_id = AccountKeyring::Alice.to_account_id(); + // Hex encoded scale encoded bytes representing the call parameters. + let call_parameters = format!("0x{:?}", HexDisplay::from(&alice_id.encode())); + let mut sub = api + .subscribe( + "chainHead_unstable_call", + [&sub_id, &block_hash, "AccountNonceApi_account_nonce", &call_parameters], + ) + .await + .unwrap(); + + assert_matches!( + get_next_event::>(&mut sub).await, + ChainHeadEvent::Done(done) if done.result == "0x0000000000000000" + ); + + // The `current_epoch` takes no parameters and not draining the input buffer + // will cause the execution to fail. + let mut sub = api + .subscribe( + "chainHead_unstable_call", + [&sub_id, &block_hash, "BabeApi_current_epoch", "0x00"], + ) + .await + .unwrap(); + assert_matches!( + get_next_event::>(&mut sub).await, + ChainHeadEvent::Error(event) if event.error.contains("Execution failed") + ); +} + +#[tokio::test] +async fn call_runtime_without_flag() { + let builder = TestClientBuilder::new(); + let backend = builder.backend(); + let mut client = Arc::new(builder.build()); + + let api = ChainHead::new( + client.clone(), + backend, + Arc::new(TaskExecutor::default()), + CHAIN_GENESIS, + MAX_PINNED_BLOCKS, + ) + .into_rpc(); + + let mut sub = api.subscribe("chainHead_unstable_follow", [false]).await.unwrap(); + let sub_id = sub.subscription_id(); + let sub_id = serde_json::to_string(&sub_id).unwrap(); + + let block = client.new_block(Default::default()).unwrap().build().unwrap().block; + let block_hash = format!("{:?}", block.header.hash()); + client.import(BlockOrigin::Own, block.clone()).await.unwrap(); + + // Ensure the imported block is propagated and pinned for this subscription. + assert_matches!( + get_next_event::>(&mut sub).await, + FollowEvent::Initialized(_) + ); + assert_matches!( + get_next_event::>(&mut sub).await, + FollowEvent::NewBlock(_) + ); + assert_matches!( + get_next_event::>(&mut sub).await, + FollowEvent::BestBlockChanged(_) + ); + + // Valid runtime call on a subscription started with `runtime_updates` false. + let alice_id = AccountKeyring::Alice.to_account_id(); + let call_parameters = format!("0x{:?}", HexDisplay::from(&alice_id.encode())); + let err = api + .subscribe( + "chainHead_unstable_call", + [&sub_id, &block_hash, "AccountNonceApi_account_nonce", &call_parameters], + ) + .await + .unwrap_err(); + + assert_matches!(err, + Error::Call(CallError::Custom(ref err)) if err.code() == 2003 && err.message().contains("The runtime updates flag must be set") + ); +} + +#[tokio::test] +async fn get_storage() { + let (mut client, api, mut block_sub, sub_id, block) = setup_api().await; + let block_hash = format!("{:?}", block.header.hash()); + let invalid_hash = format!("0x{:?}", HexDisplay::from(&INVALID_HASH)); + let key = format!("0x{:?}", HexDisplay::from(&KEY)); + + // Subscription ID is stale the disjoint event is emitted. + let mut sub = api + .subscribe("chainHead_unstable_storage", ["invalid_sub_id", &invalid_hash, &key]) + .await + .unwrap(); + let event: ChainHeadEvent = get_next_event(&mut sub).await; + assert_eq!(event, ChainHeadEvent::::Disjoint); + + // Valid subscription ID with invalid block hash will error. + let err = api + .subscribe("chainHead_unstable_storage", [&sub_id, &invalid_hash, &key]) + .await + .unwrap_err(); + assert_matches!(err, + Error::Call(CallError::Custom(ref err)) if err.code() == 2001 && err.message() == "Invalid block hash" + ); + + // Valid call without storage at the key. + let mut sub = api + .subscribe("chainHead_unstable_storage", [&sub_id, &block_hash, &key]) + .await + .unwrap(); + let event: ChainHeadEvent> = get_next_event(&mut sub).await; + assert_matches!(event, ChainHeadEvent::>::Done(done) if done.result.is_none()); + + // Import a new block with storage changes. + let mut builder = client.new_block(Default::default()).unwrap(); + builder.push_storage_change(KEY.to_vec(), Some(VALUE.to_vec())).unwrap(); + let block = builder.build().unwrap().block; + let block_hash = format!("{:?}", block.header.hash()); + client.import(BlockOrigin::Own, block.clone()).await.unwrap(); + + // Ensure the imported block is propagated and pinned for this subscription. + assert_matches!( + get_next_event::>(&mut block_sub).await, + FollowEvent::NewBlock(_) + ); + assert_matches!( + get_next_event::>(&mut block_sub).await, + FollowEvent::BestBlockChanged(_) + ); + + // Valid call with storage at the key. + let expected_value = Some(format!("0x{:?}", HexDisplay::from(&VALUE))); + let mut sub = api + .subscribe("chainHead_unstable_storage", [&sub_id, &block_hash, &key]) + .await + .unwrap(); + let event: ChainHeadEvent> = get_next_event(&mut sub).await; + assert_matches!(event, ChainHeadEvent::>::Done(done) if done.result == expected_value); + + // Child value set in `setup_api`. + let child_info = format!("0x{:?}", HexDisplay::from(b"child")); + let genesis_hash = format!("{:?}", client.genesis_hash()); + let expected_value = Some(format!("0x{:?}", HexDisplay::from(&CHILD_VALUE))); + let mut sub = api + .subscribe("chainHead_unstable_storage", [&sub_id, &genesis_hash, &key, &child_info]) + .await + .unwrap(); + let event: ChainHeadEvent> = get_next_event(&mut sub).await; + assert_matches!(event, ChainHeadEvent::>::Done(done) if done.result == expected_value); +} + +#[tokio::test] +async fn get_storage_wrong_key() { + let (mut _client, api, mut _block_sub, sub_id, block) = setup_api().await; + let block_hash = format!("{:?}", block.header.hash()); + let key = format!("0x{:?}", HexDisplay::from(&KEY)); + + // Key is prefixed by CHILD_STORAGE_KEY_PREFIX. + let mut prefixed_key = well_known_keys::CHILD_STORAGE_KEY_PREFIX.to_vec(); + prefixed_key.extend_from_slice(&KEY); + let prefixed_key = format!("0x{:?}", HexDisplay::from(&prefixed_key)); + let mut sub = api + .subscribe("chainHead_unstable_storage", [&sub_id, &block_hash, &prefixed_key]) + .await + .unwrap(); + let event: ChainHeadEvent> = get_next_event(&mut sub).await; + assert_matches!(event, ChainHeadEvent::>::Done(done) if done.result.is_none()); + + // Key is prefixed by DEFAULT_CHILD_STORAGE_KEY_PREFIX. + let mut prefixed_key = well_known_keys::DEFAULT_CHILD_STORAGE_KEY_PREFIX.to_vec(); + prefixed_key.extend_from_slice(&KEY); + let prefixed_key = format!("0x{:?}", HexDisplay::from(&prefixed_key)); + let mut sub = api + .subscribe("chainHead_unstable_storage", [&sub_id, &block_hash, &prefixed_key]) + .await + .unwrap(); + let event: ChainHeadEvent> = get_next_event(&mut sub).await; + assert_matches!(event, ChainHeadEvent::>::Done(done) if done.result.is_none()); + + // Child key is prefixed by CHILD_STORAGE_KEY_PREFIX. + let mut prefixed_key = well_known_keys::CHILD_STORAGE_KEY_PREFIX.to_vec(); + prefixed_key.extend_from_slice(b"child"); + let prefixed_key = format!("0x{:?}", HexDisplay::from(&prefixed_key)); + let mut sub = api + .subscribe("chainHead_unstable_storage", [&sub_id, &block_hash, &key, &prefixed_key]) + .await + .unwrap(); + let event: ChainHeadEvent> = get_next_event(&mut sub).await; + assert_matches!(event, ChainHeadEvent::>::Done(done) if done.result.is_none()); + + // Child key is prefixed by DEFAULT_CHILD_STORAGE_KEY_PREFIX. + let mut prefixed_key = well_known_keys::DEFAULT_CHILD_STORAGE_KEY_PREFIX.to_vec(); + prefixed_key.extend_from_slice(b"child"); + let prefixed_key = format!("0x{:?}", HexDisplay::from(&prefixed_key)); + let mut sub = api + .subscribe("chainHead_unstable_storage", [&sub_id, &block_hash, &key, &prefixed_key]) + .await + .unwrap(); + let event: ChainHeadEvent> = get_next_event(&mut sub).await; + assert_matches!(event, ChainHeadEvent::>::Done(done) if done.result.is_none()); +} + +#[tokio::test] +async fn follow_generates_initial_blocks() { + let builder = TestClientBuilder::new(); + let backend = builder.backend(); + let mut client = Arc::new(builder.build()); + + let api = ChainHead::new( + client.clone(), + backend, + Arc::new(TaskExecutor::default()), + CHAIN_GENESIS, + MAX_PINNED_BLOCKS, + ) + .into_rpc(); + + let finalized_hash = client.info().finalized_hash; + + // Block tree: + // finalized -> block 1 -> block 2 -> block 4 + // -> block 1 -> block 3 + let block_1 = client.new_block(Default::default()).unwrap().build().unwrap().block; + let block_1_hash = block_1.header.hash(); + client.import(BlockOrigin::Own, block_1.clone()).await.unwrap(); + + let block_2 = client.new_block(Default::default()).unwrap().build().unwrap().block; + let block_2_hash = block_2.header.hash(); + client.import(BlockOrigin::Own, block_2.clone()).await.unwrap(); + + let mut block_builder = client + .new_block_at(&BlockId::Hash(block_1.header.hash()), Default::default(), false) + .unwrap(); + // This push is required as otherwise block 3 has the same hash as block 2 and won't get + // imported + block_builder + .push_transfer(Transfer { + from: AccountKeyring::Alice.into(), + to: AccountKeyring::Ferdie.into(), + amount: 41, + nonce: 0, + }) + .unwrap(); + let block_3 = block_builder.build().unwrap().block; + let block_3_hash = block_3.header.hash(); + client.import(BlockOrigin::Own, block_3.clone()).await.unwrap(); + + let mut sub = api.subscribe("chainHead_unstable_follow", [false]).await.unwrap(); + + // Initialized must always be reported first. + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::Initialized(Initialized { + finalized_block_hash: format!("{:?}", finalized_hash), + finalized_block_runtime: None, + runtime_updates: false, + }); + assert_eq!(event, expected); + + // Check block 1. + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::NewBlock(NewBlock { + block_hash: format!("{:?}", block_1_hash), + parent_block_hash: format!("{:?}", finalized_hash), + new_runtime: None, + runtime_updates: false, + }); + assert_eq!(event, expected); + + // Check block 2. + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::NewBlock(NewBlock { + block_hash: format!("{:?}", block_2_hash), + parent_block_hash: format!("{:?}", block_1_hash), + new_runtime: None, + runtime_updates: false, + }); + assert_eq!(event, expected); + // Check block 3. + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::NewBlock(NewBlock { + block_hash: format!("{:?}", block_3_hash), + parent_block_hash: format!("{:?}", block_1_hash), + new_runtime: None, + runtime_updates: false, + }); + assert_eq!(event, expected); + + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::BestBlockChanged(BestBlockChanged { + best_block_hash: format!("{:?}", block_2_hash), + }); + assert_eq!(event, expected); + + // Import block 4. + let block_4 = client.new_block(Default::default()).unwrap().build().unwrap().block; + let block_4_hash = block_4.header.hash(); + client.import(BlockOrigin::Own, block_4.clone()).await.unwrap(); + + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::NewBlock(NewBlock { + block_hash: format!("{:?}", block_4_hash), + parent_block_hash: format!("{:?}", block_2_hash), + new_runtime: None, + runtime_updates: false, + }); + assert_eq!(event, expected); + + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::BestBlockChanged(BestBlockChanged { + best_block_hash: format!("{:?}", block_4_hash), + }); + assert_eq!(event, expected); + + // Check the finalized event: + // - blocks 1, 2, 4 from canonical chain are finalized + // - block 3 from the fork is pruned + client.finalize_block(block_4_hash, None).unwrap(); + + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::Finalized(Finalized { + finalized_block_hashes: vec![ + format!("{:?}", block_1_hash), + format!("{:?}", block_2_hash), + format!("{:?}", block_4_hash), + ], + pruned_block_hashes: vec![format!("{:?}", block_3_hash)], + }); + assert_eq!(event, expected); +} + +#[tokio::test] +async fn follow_exceeding_pinned_blocks() { + let builder = TestClientBuilder::new(); + let backend = builder.backend(); + let mut client = Arc::new(builder.build()); + + let api = ChainHead::new( + client.clone(), + backend, + Arc::new(TaskExecutor::default()), + CHAIN_GENESIS, + 2, + ) + .into_rpc(); + + let mut sub = api.subscribe("chainHead_unstable_follow", [false]).await.unwrap(); + + let block = client.new_block(Default::default()).unwrap().build().unwrap().block; + client.import(BlockOrigin::Own, block.clone()).await.unwrap(); + + // Ensure the imported block is propagated and pinned for this subscription. + assert_matches!( + get_next_event::>(&mut sub).await, + FollowEvent::Initialized(_) + ); + assert_matches!( + get_next_event::>(&mut sub).await, + FollowEvent::NewBlock(_) + ); + assert_matches!( + get_next_event::>(&mut sub).await, + FollowEvent::BestBlockChanged(_) + ); + + // Block tree: + // finalized_block -> block -> block2 + // The first 2 blocks are pinned into the subscription, but the block2 will exceed the limit (2 + // blocks). + let block2 = client.new_block(Default::default()).unwrap().build().unwrap().block; + client.import(BlockOrigin::Own, block2.clone()).await.unwrap(); + + assert_matches!(get_next_event::>(&mut sub).await, FollowEvent::Stop); + + // Subscription will not produce any more event for further blocks. + let block3 = client.new_block(Default::default()).unwrap().build().unwrap().block; + client.import(BlockOrigin::Own, block3.clone()).await.unwrap(); + + assert!(sub.next::>().await.is_none()); +} + +#[tokio::test] +async fn follow_with_unpin() { + let builder = TestClientBuilder::new(); + let backend = builder.backend(); + let mut client = Arc::new(builder.build()); + + let api = ChainHead::new( + client.clone(), + backend, + Arc::new(TaskExecutor::default()), + CHAIN_GENESIS, + 2, + ) + .into_rpc(); + + let mut sub = api.subscribe("chainHead_unstable_follow", [false]).await.unwrap(); + let sub_id = sub.subscription_id(); + let sub_id = serde_json::to_string(&sub_id).unwrap(); + + let block = client.new_block(Default::default()).unwrap().build().unwrap().block; + let block_hash = format!("{:?}", block.header.hash()); + client.import(BlockOrigin::Own, block.clone()).await.unwrap(); + + // Ensure the imported block is propagated and pinned for this subscription. + assert_matches!( + get_next_event::>(&mut sub).await, + FollowEvent::Initialized(_) + ); + assert_matches!( + get_next_event::>(&mut sub).await, + FollowEvent::NewBlock(_) + ); + assert_matches!( + get_next_event::>(&mut sub).await, + FollowEvent::BestBlockChanged(_) + ); + + // Unpin an invalid subscription ID must return Ok(()). + let invalid_hash = format!("0x{:?}", HexDisplay::from(&INVALID_HASH)); + let _res: () = api + .call("chainHead_unstable_unpin", ["invalid_sub_id", &invalid_hash]) + .await + .unwrap(); + + // Valid subscription with invalid block hash. + let invalid_hash = format!("0x{:?}", HexDisplay::from(&INVALID_HASH)); + let err = api + .call::<_, serde_json::Value>("chainHead_unstable_unpin", [&sub_id, &invalid_hash]) + .await + .unwrap_err(); + assert_matches!(err, + Error::Call(CallError::Custom(ref err)) if err.code() == 2001 && err.message() == "Invalid block hash" + ); + + // To not exceed the number of pinned blocks, we need to unpin before the next import. + let _res: () = api.call("chainHead_unstable_unpin", [&sub_id, &block_hash]).await.unwrap(); + + // Block tree: + // finalized_block -> block -> block2 + // ^ has been unpinned + let block2 = client.new_block(Default::default()).unwrap().build().unwrap().block; + client.import(BlockOrigin::Own, block2.clone()).await.unwrap(); + + assert_matches!( + get_next_event::>(&mut sub).await, + FollowEvent::NewBlock(_) + ); + + assert_matches!( + get_next_event::>(&mut sub).await, + FollowEvent::BestBlockChanged(_) + ); + + let block3 = client.new_block(Default::default()).unwrap().build().unwrap().block; + client.import(BlockOrigin::Own, block3.clone()).await.unwrap(); + + assert_matches!(get_next_event::>(&mut sub).await, FollowEvent::Stop); + assert!(sub.next::>().await.is_none()); +} + +#[tokio::test] +async fn follow_prune_best_block() { + let builder = TestClientBuilder::new(); + let backend = builder.backend(); + let mut client = Arc::new(builder.build()); + + let api = ChainHead::new( + client.clone(), + backend, + Arc::new(TaskExecutor::default()), + CHAIN_GENESIS, + MAX_PINNED_BLOCKS, + ) + .into_rpc(); + + let finalized_hash = client.info().finalized_hash; + let mut sub = api.subscribe("chainHead_unstable_follow", [false]).await.unwrap(); + + // Initialized must always be reported first. + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::Initialized(Initialized { + finalized_block_hash: format!("{:?}", finalized_hash), + finalized_block_runtime: None, + runtime_updates: false, + }); + assert_eq!(event, expected); + + // Block tree: + // + // finalized -> block 1 -> block 2 + // ^^^ best block reported + // + // -> block 1 -> block 3 -> block 4 + // ^^^ finalized + // + // The block 4 is needed on the longest chain because we want the + // best block 2 to be reported as pruned. Pruning is happening at + // height (N - 1), where N is the finalized block number. + + let block_1 = client.new_block(Default::default()).unwrap().build().unwrap().block; + let block_1_hash = block_1.header.hash(); + client.import(BlockOrigin::Own, block_1.clone()).await.unwrap(); + + let block_3 = client.new_block(Default::default()).unwrap().build().unwrap().block; + let block_3_hash = block_3.header.hash(); + client.import(BlockOrigin::Own, block_3.clone()).await.unwrap(); + + let block_4 = client.new_block(Default::default()).unwrap().build().unwrap().block; + let block_4_hash = block_4.header.hash(); + client.import(BlockOrigin::Own, block_4.clone()).await.unwrap(); + + // Import block 2 as best on the fork. + let mut block_builder = client + .new_block_at(&BlockId::Hash(block_1.header.hash()), Default::default(), false) + .unwrap(); + // This push is required as otherwise block 3 has the same hash as block 2 and won't get + // imported + block_builder + .push_transfer(Transfer { + from: AccountKeyring::Alice.into(), + to: AccountKeyring::Ferdie.into(), + amount: 41, + nonce: 0, + }) + .unwrap(); + let block_2 = block_builder.build().unwrap().block; + let block_2_hash = block_2.header.hash(); + client.import_as_best(BlockOrigin::Own, block_2.clone()).await.unwrap(); + + // Check block 1. + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::NewBlock(NewBlock { + block_hash: format!("{:?}", block_1_hash), + parent_block_hash: format!("{:?}", finalized_hash), + new_runtime: None, + runtime_updates: false, + }); + assert_eq!(event, expected); + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::BestBlockChanged(BestBlockChanged { + best_block_hash: format!("{:?}", block_1_hash), + }); + assert_eq!(event, expected); + + // Check block 3. + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::NewBlock(NewBlock { + block_hash: format!("{:?}", block_3_hash), + parent_block_hash: format!("{:?}", block_1_hash), + new_runtime: None, + runtime_updates: false, + }); + assert_eq!(event, expected); + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::BestBlockChanged(BestBlockChanged { + best_block_hash: format!("{:?}", block_3_hash), + }); + assert_eq!(event, expected); + + // Check block 4. + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::NewBlock(NewBlock { + block_hash: format!("{:?}", block_4_hash), + parent_block_hash: format!("{:?}", block_3_hash), + new_runtime: None, + runtime_updates: false, + }); + assert_eq!(event, expected); + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::BestBlockChanged(BestBlockChanged { + best_block_hash: format!("{:?}", block_4_hash), + }); + assert_eq!(event, expected); + + // Check block 2, that we imported as custom best. + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::NewBlock(NewBlock { + block_hash: format!("{:?}", block_2_hash), + parent_block_hash: format!("{:?}", block_1_hash), + new_runtime: None, + runtime_updates: false, + }); + assert_eq!(event, expected); + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::BestBlockChanged(BestBlockChanged { + best_block_hash: format!("{:?}", block_2_hash), + }); + assert_eq!(event, expected); + + // Finalize the block 4 from the fork. + client.finalize_block(block_4_hash, None).unwrap(); + + // Expect to report the best block changed before the finalized event. + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::BestBlockChanged(BestBlockChanged { + best_block_hash: format!("{:?}", block_4_hash), + }); + assert_eq!(event, expected); + + // Block 2 must be reported as pruned, even if it was the previous best. + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::Finalized(Finalized { + finalized_block_hashes: vec![ + format!("{:?}", block_1_hash), + format!("{:?}", block_3_hash), + format!("{:?}", block_4_hash), + ], + pruned_block_hashes: vec![format!("{:?}", block_2_hash)], + }); + assert_eq!(event, expected); +} diff --git a/client/rpc-spec-v2/src/lib.rs b/client/rpc-spec-v2/src/lib.rs index f4b9d2f95b..5af7e3be26 100644 --- a/client/rpc-spec-v2/src/lib.rs +++ b/client/rpc-spec-v2/src/lib.rs @@ -23,6 +23,7 @@ #![warn(missing_docs)] #![deny(unused_crate_dependencies)] +pub mod chain_head; pub mod chain_spec; pub mod transaction; diff --git a/client/service/src/builder.rs b/client/service/src/builder.rs index 1c8f4c7de7..64a5f70cc0 100644 --- a/client/service/src/builder.rs +++ b/client/service/src/builder.rs @@ -59,7 +59,7 @@ use sc_rpc::{ system::SystemApiServer, DenyUnsafe, SubscriptionTaskExecutor, }; -use sc_rpc_spec_v2::transaction::TransactionApiServer; +use sc_rpc_spec_v2::{chain_head::ChainHeadApiServer, transaction::TransactionApiServer}; use sc_telemetry::{telemetry, ConnectionMessage, Telemetry, TelemetryHandle, SUBSTRATE_INFO}; use sc_transaction_pool_api::MaintainedTransactionPool; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedSender}; @@ -549,7 +549,7 @@ where keystore.clone(), system_rpc_tx.clone(), &config, - backend.offchain_storage(), + backend.clone(), &*rpc_builder, ) }; @@ -643,7 +643,7 @@ fn gen_rpc_module( keystore: SyncCryptoStorePtr, system_rpc_tx: TracingUnboundedSender>, config: &Configuration, - offchain_storage: Option<>::OffchainStorage>, + backend: Arc, rpc_builder: &(dyn Fn(DenyUnsafe, SubscriptionTaskExecutor) -> Result, Error>), ) -> Result, Error> where @@ -698,6 +698,19 @@ where ) .into_rpc(); + // Maximum pinned blocks per connection. + // This number is large enough to consider immediate blocks, + // but it will change to facilitate adequate limits for the pinning API. + const MAX_PINNED_BLOCKS: usize = 4096; + let chain_head_v2 = sc_rpc_spec_v2::chain_head::ChainHead::new( + client.clone(), + backend.clone(), + task_executor.clone(), + client.info().genesis_hash, + MAX_PINNED_BLOCKS, + ) + .into_rpc(); + let author = sc_rpc::author::Author::new( client.clone(), transaction_pool, @@ -709,7 +722,7 @@ where let system = sc_rpc::system::System::new(system_info, system_rpc_tx, deny_unsafe).into_rpc(); - if let Some(storage) = offchain_storage { + if let Some(storage) = backend.offchain_storage() { let offchain = sc_rpc::offchain::Offchain::new(storage, deny_unsafe).into_rpc(); rpc_api.merge(offchain).map_err(|e| Error::Application(e.into()))?; @@ -717,6 +730,7 @@ where // Part of the RPC v2 spec. rpc_api.merge(transaction_v2).map_err(|e| Error::Application(e.into()))?; + rpc_api.merge(chain_head_v2).map_err(|e| Error::Application(e.into()))?; // Part of the old RPC spec. rpc_api.merge(chain).map_err(|e| Error::Application(e.into()))?; From 8a3b2f2e0d4bcf9614dbf174ad2b24da6f9b0b44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Mon, 19 Dec 2022 19:58:25 +0100 Subject: [PATCH 058/127] ed25519: Don't panic for invalid signature (#12965) * ed25519: Don't panic for invalid signature We should not panic for an invalid signature when the `UseDalekExt` is given. * Update Cargo.toml * Update primitives/io/Cargo.toml --- Cargo.lock | 5 +++-- primitives/io/Cargo.toml | 5 +++++ primitives/io/src/lib.rs | 26 ++++++++++++++++++++++---- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 046e5991ba..8412781c68 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1566,9 +1566,9 @@ dependencies = [ [[package]] name = "ed25519" -version = "1.0.3" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c66a534cbb46ab4ea03477eae19d5c22c01da8258030280b7bd9d8433fb6ef" +checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" dependencies = [ "signature", ] @@ -9181,6 +9181,7 @@ name = "sp-io" version = "7.0.0" dependencies = [ "bytes", + "ed25519", "ed25519-dalek", "futures", "libsecp256k1", diff --git a/primitives/io/Cargo.toml b/primitives/io/Cargo.toml index 57dc3604de..d83c82c022 100644 --- a/primitives/io/Cargo.toml +++ b/primitives/io/Cargo.toml @@ -31,7 +31,11 @@ futures = { version = "0.3.21", features = ["thread-pool"], optional = true } secp256k1 = { version = "0.24.0", features = ["recovery", "global-context"], optional = true } tracing = { version = "0.1.29", default-features = false } tracing-core = { version = "0.1.28", default-features = false} + +# Required for backwards compatibility reason, but only used for verifying when `UseDalekExt` is set. ed25519-dalek = { version = "1.0.1", default-features = false, optional = true } +# Force the usage of ed25519, this is being used in `ed25519-dalek`. +ed25519 = { version = "1.5.2", optional = true } [features] default = ["std"] @@ -53,6 +57,7 @@ std = [ "log", "futures", "ed25519-dalek", + "ed25519", ] with-tracing = [ diff --git a/primitives/io/src/lib.rs b/primitives/io/src/lib.rs index 600d76b3b4..bb06c00ee2 100644 --- a/primitives/io/src/lib.rs +++ b/primitives/io/src/lib.rs @@ -783,13 +783,13 @@ pub trait Crypto { { use ed25519_dalek::Verifier; - let public_key = if let Ok(vk) = ed25519_dalek::PublicKey::from_bytes(&pub_key.0) { - vk - } else { + let Ok(public_key) = ed25519_dalek::PublicKey::from_bytes(&pub_key.0) else { return false }; - let sig = ed25519_dalek::Signature::from(sig.0); + let Ok(sig) = ed25519_dalek::Signature::from_bytes(&sig.0) else { + return false + }; public_key.verify(msg, &sig).is_ok() } else { @@ -1946,4 +1946,22 @@ mod tests { assert!(crypto::ed25519_verify(&zero_ed_sig(), &Vec::new(), &zero_ed_pub())); }) } + + #[test] + fn dalek_should_not_panic_on_invalid_signature() { + let mut ext = BasicExternalities::default(); + ext.register_extension(UseDalekExt::default()); + + ext.execute_with(|| { + let mut bytes = [0u8; 64]; + // Make it invalid + bytes[63] = 0b1110_0000; + + assert!(!crypto::ed25519_verify( + &ed25519::Signature::from_raw(bytes), + &Vec::new(), + &zero_ed_pub() + )); + }); + } } From 657c808eb3d22284359a802d67fdfdbe823fbd17 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Tue, 20 Dec 2022 10:43:31 +0100 Subject: [PATCH 059/127] `BlockId` removal: refactor: `HeaderBackend::header` (#12874) * BlockId removal: refactor: HeaderBackend::header It changes the arguments of: - `HeaderBackend::header`, - `Client::header`, - `PeersClient::header` - `ChainApi::block_header` methods from: `BlockId` to: `Block::Hash` This PR is part of BlockId::Number refactoring analysis (paritytech/substrate#11292) * non-trivial usages of haeder(block_id) refactored This may required introduction of dedicated function: header_for_block_num * fmt * fix * doc fixed * ".git/.scripts/fmt.sh" * BlockId removal: refactor: HeaderBackend::expect_header It changes the arguments of `HeaderBackend::expect_header` method from: `BlockId` to: `Block::Hash` * ".git/.scripts/fmt.sh" * readme updated * ".git/.scripts/fmt.sh" * fix Co-authored-by: parity-processbot <> --- bin/node/bench/src/construct.rs | 2 +- bin/node/cli/src/service.rs | 5 +- bin/node/inspect/src/lib.rs | 7 +- client/api/src/in_mem.rs | 10 +- .../authority-discovery/src/worker/tests.rs | 2 +- client/basic-authorship/README.md | 2 +- .../basic-authorship/src/basic_authorship.rs | 40 ++++---- client/basic-authorship/src/lib.rs | 2 +- client/beefy/src/lib.rs | 2 +- client/beefy/src/tests.rs | 3 +- client/beefy/src/worker.rs | 25 +++-- client/consensus/aura/src/lib.rs | 4 +- client/consensus/babe/src/lib.rs | 4 +- client/consensus/babe/src/tests.rs | 94 +++++++------------ client/consensus/common/src/longest_chain.rs | 7 +- .../manual-seal/src/consensus/babe.rs | 4 +- .../manual-seal/src/consensus/timestamp.rs | 7 +- client/consensus/manual-seal/src/lib.rs | 22 +++-- .../consensus/manual-seal/src/seal_block.rs | 10 +- client/db/src/lib.rs | 92 +++++++++--------- client/finality-grandpa/src/environment.rs | 8 +- client/finality-grandpa/src/finality_proof.rs | 3 +- client/finality-grandpa/src/import.rs | 8 +- client/finality-grandpa/src/justification.rs | 7 +- client/finality-grandpa/src/tests.rs | 13 ++- client/finality-grandpa/src/voting_rule.rs | 28 +++--- client/finality-grandpa/src/warp_proof.rs | 11 ++- .../merkle-mountain-range/src/test_utils.rs | 4 +- client/network/src/protocol.rs | 7 +- .../network/src/service/tests/chain_sync.rs | 2 +- .../network/sync/src/block_request_handler.rs | 11 ++- client/network/sync/src/lib.rs | 2 +- client/network/test/src/block_import.rs | 3 +- client/network/test/src/lib.rs | 26 +++-- client/network/test/src/sync.rs | 47 +++++----- client/offchain/src/lib.rs | 2 +- .../rpc-spec-v2/src/chain_head/chain_head.rs | 2 +- client/rpc/src/chain/chain_full.rs | 4 +- client/rpc/src/chain/mod.rs | 10 +- client/rpc/src/dev/mod.rs | 2 +- client/service/src/builder.rs | 7 +- client/service/src/client/client.rs | 34 ++++--- client/service/test/src/client/mod.rs | 13 ++- client/sync-state-rpc/src/lib.rs | 7 +- client/tracing/src/block/mod.rs | 5 +- client/transaction-pool/benches/basics.rs | 2 +- client/transaction-pool/src/api.rs | 4 +- client/transaction-pool/src/graph/pool.rs | 2 +- client/transaction-pool/src/lib.rs | 2 +- client/transaction-pool/src/tests.rs | 2 +- primitives/blockchain/src/backend.rs | 14 +-- primitives/session/src/lib.rs | 4 +- test-utils/runtime/client/src/trait_tests.rs | 11 --- .../runtime/transaction-pool/src/lib.rs | 9 +- .../benchmarking-cli/src/storage/write.rs | 7 +- 55 files changed, 307 insertions(+), 360 deletions(-) diff --git a/bin/node/bench/src/construct.rs b/bin/node/bench/src/construct.rs index 2e20a7acb1..f994b29b5e 100644 --- a/bin/node/bench/src/construct.rs +++ b/bin/node/bench/src/construct.rs @@ -143,7 +143,7 @@ impl core::Benchmark for ConstructionBenchmark { proposer_factory.init( &context .client - .header(&BlockId::number(0)) + .header(context.client.chain_info().genesis_hash) .expect("Database error querying block #0") .expect("Block #0 should exist"), ), diff --git a/bin/node/cli/src/service.rs b/bin/node/cli/src/service.rs index e7b825a8e5..cdee61af3f 100644 --- a/bin/node/cli/src/service.rs +++ b/bin/node/cli/src/service.rs @@ -640,9 +640,8 @@ mod tests { Ok((node, setup_handles.unwrap())) }, |service, &mut (ref mut block_import, ref babe_link)| { - let parent_id = BlockId::number(service.client().chain_info().best_number); - let parent_header = service.client().header(&parent_id).unwrap().unwrap(); - let parent_hash = parent_header.hash(); + let parent_hash = service.client().chain_info().best_hash; + let parent_header = service.client().header(parent_hash).unwrap().unwrap(); let parent_number = *parent_header.number(); futures::executor::block_on(service.transaction_pool().maintain( diff --git a/bin/node/inspect/src/lib.rs b/bin/node/inspect/src/lib.rs index 528dce14f4..3b9d7b2b1f 100644 --- a/bin/node/inspect/src/lib.rs +++ b/bin/node/inspect/src/lib.rs @@ -147,18 +147,17 @@ impl> Inspector .block_body(hash)? .ok_or_else(|| Error::NotFound(not_found.clone()))?; let header = - self.chain.header(id)?.ok_or_else(|| Error::NotFound(not_found.clone()))?; + self.chain.header(hash)?.ok_or_else(|| Error::NotFound(not_found.clone()))?; TBlock::new(header, body) }, BlockAddress::Hash(hash) => { - let id = BlockId::hash(hash); - let not_found = format!("Could not find block {:?}", id); + let not_found = format!("Could not find block {:?}", BlockId::::Hash(hash)); let body = self .chain .block_body(hash)? .ok_or_else(|| Error::NotFound(not_found.clone()))?; let header = - self.chain.header(id)?.ok_or_else(|| Error::NotFound(not_found.clone()))?; + self.chain.header(hash)?.ok_or_else(|| Error::NotFound(not_found.clone()))?; TBlock::new(header, body) }, }) diff --git a/client/api/src/in_mem.rs b/client/api/src/in_mem.rs index 5a3e25ab59..58add08707 100644 --- a/client/api/src/in_mem.rs +++ b/client/api/src/in_mem.rs @@ -225,7 +225,7 @@ impl Blockchain { /// Set an existing block as head. pub fn set_head(&self, hash: Block::Hash) -> sp_blockchain::Result<()> { let header = self - .header(BlockId::Hash(hash))? + .header(hash)? .ok_or_else(|| sp_blockchain::Error::UnknownBlock(format!("{}", hash)))?; self.apply_head(&header) @@ -336,11 +336,9 @@ impl Blockchain { impl HeaderBackend for Blockchain { fn header( &self, - id: BlockId, + hash: Block::Hash, ) -> sp_blockchain::Result::Header>> { - Ok(self - .id(id) - .and_then(|hash| self.storage.read().blocks.get(&hash).map(|b| b.header().clone()))) + Ok(self.storage.read().blocks.get(&hash).map(|b| b.header().clone())) } fn info(&self) -> blockchain::Info { @@ -387,7 +385,7 @@ impl HeaderMetadata for Blockchain { &self, hash: Block::Hash, ) -> Result, Self::Error> { - self.header(BlockId::hash(hash))? + self.header(hash)? .map(|header| CachedHeaderMetadata::from(&header)) .ok_or_else(|| { sp_blockchain::Error::UnknownBlock(format!("header not found: {}", hash)) diff --git a/client/authority-discovery/src/worker/tests.rs b/client/authority-discovery/src/worker/tests.rs index 8e7a221877..ea0c16eca8 100644 --- a/client/authority-discovery/src/worker/tests.rs +++ b/client/authority-discovery/src/worker/tests.rs @@ -58,7 +58,7 @@ impl ProvideRuntimeApi for TestApi { impl HeaderBackend for TestApi { fn header( &self, - _id: BlockId, + _hash: Block::Hash, ) -> std::result::Result, sp_blockchain::Error> { Ok(None) } diff --git a/client/basic-authorship/README.md b/client/basic-authorship/README.md index d29ce258e5..f2f160b6e2 100644 --- a/client/basic-authorship/README.md +++ b/client/basic-authorship/README.md @@ -8,7 +8,7 @@ let mut proposer_factory = ProposerFactory::new(client.clone(), txpool.clone(), // From this factory, we create a `Proposer`. let proposer = proposer_factory.init( - &client.header(&BlockId::number(0)).unwrap().unwrap(), + &client.header(client.chain_info().genesis_hash).unwrap().unwrap(), ); // The proposer is created asynchronously. diff --git a/client/basic-authorship/src/basic_authorship.rs b/client/basic-authorship/src/basic_authorship.rs index b69294bf6c..c39d07a14f 100644 --- a/client/basic-authorship/src/basic_authorship.rs +++ b/client/basic-authorship/src/basic_authorship.rs @@ -617,8 +617,7 @@ mod tests { block_on( txpool.maintain(chain_event( client - .header(&BlockId::Number(0u64)) - .expect("header get error") + .expect_header(client.info().genesis_hash) .expect("there should be header"), )), ); @@ -628,7 +627,7 @@ mod tests { let cell = Mutex::new((false, time::Instant::now())); let proposer = proposer_factory.init_with_now( - &client.header(&BlockId::number(0)).unwrap().unwrap(), + &client.expect_header(client.info().genesis_hash).unwrap(), Box::new(move || { let mut value = cell.lock(); if !value.0 { @@ -672,7 +671,7 @@ mod tests { let cell = Mutex::new((false, time::Instant::now())); let proposer = proposer_factory.init_with_now( - &client.header(&BlockId::number(0)).unwrap().unwrap(), + &client.expect_header(client.info().genesis_hash).unwrap(), Box::new(move || { let mut value = cell.lock(); if !value.0 { @@ -705,15 +704,13 @@ mod tests { ); let genesis_hash = client.info().best_hash; - let block_id = BlockId::Hash(genesis_hash); block_on(txpool.submit_at(&BlockId::number(0), SOURCE, vec![extrinsic(0)])).unwrap(); block_on( txpool.maintain(chain_event( client - .header(&BlockId::Number(0u64)) - .expect("header get error") + .expect_header(client.info().genesis_hash) .expect("there should be header"), )), ); @@ -722,7 +719,7 @@ mod tests { ProposerFactory::new(spawner.clone(), client.clone(), txpool.clone(), None, None); let proposer = proposer_factory.init_with_now( - &client.header(&block_id).unwrap().unwrap(), + &client.header(genesis_hash).unwrap().unwrap(), Box::new(move || time::Instant::now()), ); @@ -734,7 +731,7 @@ mod tests { assert_eq!(proposal.block.extrinsics().len(), 1); let api = client.runtime_api(); - api.execute_block(&block_id, proposal.block).unwrap(); + api.execute_block(&BlockId::Hash(genesis_hash), proposal.block).unwrap(); let state = backend.state_at(genesis_hash).unwrap(); @@ -790,8 +787,9 @@ mod tests { number, expected_block_extrinsics, expected_pool_transactions| { + let hash = client.expect_block_hash_from_id(&BlockId::Number(number)).unwrap(); let proposer = proposer_factory.init_with_now( - &client.header(&BlockId::number(number)).unwrap().unwrap(), + &client.expect_header(hash).unwrap(), Box::new(move || time::Instant::now()), ); @@ -813,8 +811,7 @@ mod tests { block_on( txpool.maintain(chain_event( client - .header(&BlockId::Number(0u64)) - .expect("header get error") + .expect_header(client.info().genesis_hash) .expect("there should be header"), )), ); @@ -822,14 +819,12 @@ mod tests { // let's create one block and import it let block = propose_block(&client, 0, 2, 7); + let hashof1 = block.hash(); block_on(client.import(BlockOrigin::Own, block)).unwrap(); block_on( txpool.maintain(chain_event( - client - .header(&BlockId::Number(1)) - .expect("header get error") - .expect("there should be header"), + client.expect_header(hashof1).expect("there should be header"), )), ); assert_eq!(txpool.ready().count(), 5); @@ -851,8 +846,7 @@ mod tests { client.clone(), ); let genesis_header = client - .header(&BlockId::Number(0u64)) - .expect("header get error") + .expect_header(client.info().genesis_hash) .expect("there should be header"); let extrinsics_num = 5; @@ -966,8 +960,7 @@ mod tests { block_on( txpool.maintain(chain_event( client - .header(&BlockId::Number(0u64)) - .expect("header get error") + .expect_header(client.info().genesis_hash) .expect("there should be header"), )), ); @@ -978,7 +971,7 @@ mod tests { let cell = Mutex::new(time::Instant::now()); let proposer = proposer_factory.init_with_now( - &client.header(&BlockId::number(0)).unwrap().unwrap(), + &client.expect_header(client.info().genesis_hash).unwrap(), Box::new(move || { let mut value = cell.lock(); let old = *value; @@ -1029,8 +1022,7 @@ mod tests { block_on( txpool.maintain(chain_event( client - .header(&BlockId::Number(0u64)) - .expect("header get error") + .expect_header(client.info().genesis_hash) .expect("there should be header"), )), ); @@ -1043,7 +1035,7 @@ mod tests { let cell = Arc::new(Mutex::new((0, time::Instant::now()))); let cell2 = cell.clone(); let proposer = proposer_factory.init_with_now( - &client.header(&BlockId::number(0)).unwrap().unwrap(), + &client.expect_header(client.info().genesis_hash).unwrap(), Box::new(move || { let mut value = cell.lock(); let (called, old) = *value; diff --git a/client/basic-authorship/src/lib.rs b/client/basic-authorship/src/lib.rs index 4a26ebd9df..e7f0d4496e 100644 --- a/client/basic-authorship/src/lib.rs +++ b/client/basic-authorship/src/lib.rs @@ -50,7 +50,7 @@ //! //! // From this factory, we create a `Proposer`. //! let proposer = proposer_factory.init( -//! &client.header(&BlockId::number(0)).unwrap().unwrap(), +//! &client.header(client.chain_info().genesis_hash).unwrap().unwrap(), //! ); //! //! // The proposer is created asynchronously. diff --git a/client/beefy/src/lib.rs b/client/beefy/src/lib.rs index a057a9fdc5..35f7cac55a 100644 --- a/client/beefy/src/lib.rs +++ b/client/beefy/src/lib.rs @@ -412,7 +412,7 @@ where })?; // Move up the chain. - header = blockchain.expect_header(BlockId::Hash(parent_hash))?; + header = blockchain.expect_header(parent_hash)?; }; aux_schema::write_current_version(backend)?; diff --git a/client/beefy/src/tests.rs b/client/beefy/src/tests.rs index 055c2669eb..ce6f6ae3c9 100644 --- a/client/beefy/src/tests.rs +++ b/client/beefy/src/tests.rs @@ -448,9 +448,8 @@ async fn wait_for_best_beefy_blocks( wait_for.push(Box::pin(stream.take(len).for_each(move |best_beefy_hash| { let expected = expected.next(); async move { - let block_id = BlockId::hash(best_beefy_hash); let header = - net.lock().peer(i).client().as_client().expect_header(block_id).unwrap(); + net.lock().peer(i).client().as_client().expect_header(best_beefy_hash).unwrap(); let best_beefy = *header.number(); assert_eq!(expected, Some(best_beefy).as_ref()); diff --git a/client/beefy/src/worker.rs b/client/beefy/src/worker.rs index 89d231a6c2..b48dfa369e 100644 --- a/client/beefy/src/worker.rs +++ b/client/beefy/src/worker.rs @@ -465,7 +465,7 @@ where .map(|hash| { backend .blockchain() - .expect_header(BlockId::hash(*hash)) + .expect_header(*hash) .expect("just finalized block should be available; qed.") }) .chain(std::iter::once(header.clone())) @@ -718,16 +718,25 @@ where let target_header = if target_number == self.best_grandpa_block() { self.persisted_state.best_grandpa_block_header.clone() } else { - self.backend + let hash = self + .backend .blockchain() - .expect_header(BlockId::Number(target_number)) + .expect_block_hash_from_id(&BlockId::Number(target_number)) .map_err(|err| { let err_msg = format!( - "Couldn't get header for block #{:?} (error: {:?}), skipping vote..", + "Couldn't get hash for block #{:?} (error: {:?}), skipping vote..", target_number, err ); Error::Backend(err_msg) - })? + })?; + + self.backend.blockchain().expect_header(hash).map_err(|err| { + let err_msg = format!( + "Couldn't get header for block #{:?} ({:?}) (error: {:?}), skipping vote..", + target_number, hash, err + ); + Error::Backend(err_msg) + })? }; let target_hash = target_header.hash(); @@ -1050,8 +1059,10 @@ pub(crate) mod tests { "/beefy/justifs/1".into(), known_peers, ); - let at = BlockId::number(Zero::zero()); - let genesis_header = backend.blockchain().expect_header(at).unwrap(); + let genesis_header = backend + .blockchain() + .expect_header(backend.blockchain().info().genesis_hash) + .unwrap(); let persisted_state = PersistedState::checked_new( genesis_header, Zero::zero(), diff --git a/client/consensus/aura/src/lib.rs b/client/consensus/aura/src/lib.rs index 212376dcd0..6ef6bdc2ed 100644 --- a/client/consensus/aura/src/lib.rs +++ b/client/consensus/aura/src/lib.rs @@ -957,7 +957,7 @@ mod tests { compatibility_mode: Default::default(), }; - let head = client.header(&BlockId::Number(0)).unwrap().unwrap(); + let head = client.expect_header(client.info().genesis_hash).unwrap(); let res = worker .on_slot(SlotInfo { @@ -972,6 +972,6 @@ mod tests { .unwrap(); // The returned block should be imported and we should be able to get its header by now. - assert!(client.header(&BlockId::Hash(res.block.hash())).unwrap().is_some()); + assert!(client.header(res.block.hash()).unwrap().is_some()); } } diff --git a/client/consensus/babe/src/lib.rs b/client/consensus/babe/src/lib.rs index b50874ae34..9134da0b83 100644 --- a/client/consensus/babe/src/lib.rs +++ b/client/consensus/babe/src/lib.rs @@ -1425,7 +1425,7 @@ where let parent_hash = *block.header.parent_hash(); let parent_header = self .client - .header(BlockId::Hash(parent_hash)) + .header(parent_hash) .map_err(|e| ConsensusError::ChainLookup(e.to_string()))? .ok_or_else(|| { ConsensusError::ChainLookup( @@ -1664,7 +1664,7 @@ where let finalized_slot = { let finalized_header = client - .header(BlockId::Hash(info.finalized_hash)) + .header(info.finalized_hash) .map_err(|e| ConsensusError::ClientImport(e.to_string()))? .expect( "best finalized hash was given by client; finalized headers must exist in db; qed", diff --git a/client/consensus/babe/src/tests.rs b/client/consensus/babe/src/tests.rs index 6861379ebb..84ac3d7341 100644 --- a/client/consensus/babe/src/tests.rs +++ b/client/consensus/babe/src/tests.rs @@ -668,17 +668,17 @@ async fn propose_and_import_blocks( client: &PeersFullClient, proposer_factory: &mut DummyFactory, block_import: &mut BoxBlockImport, - parent_id: BlockId, + parent_hash: Hash, n: usize, ) -> Vec { let mut hashes = Vec::with_capacity(n); - let mut parent_header = client.header(&parent_id).unwrap().unwrap(); + let mut parent_header = client.header(parent_hash).unwrap().unwrap(); for _ in 0..n { let block_hash = propose_and_import_block(&parent_header, None, proposer_factory, block_import).await; hashes.push(block_hash); - parent_header = client.header(&BlockId::Hash(block_hash)).unwrap().unwrap(); + parent_header = client.header(block_hash).unwrap().unwrap(); } hashes @@ -701,7 +701,7 @@ async fn importing_block_one_sets_genesis_epoch() { let mut block_import = data.block_import.lock().take().expect("import set up during init"); - let genesis_header = client.header(&BlockId::Number(0)).unwrap().unwrap(); + let genesis_header = client.header(client.chain_info().genesis_hash).unwrap().unwrap(); let block_hash = propose_and_import_block( &genesis_header, @@ -759,34 +759,19 @@ async fn revert_prunes_epoch_changes_and_removes_weights() { &client, &mut proposer_factory, &mut block_import, - BlockId::Number(0), + client.chain_info().genesis_hash, 21, ) .await; - let fork1 = propose_and_import_blocks( - &client, - &mut proposer_factory, - &mut block_import, - BlockId::Hash(canon[0]), - 10, - ) - .await; - let fork2 = propose_and_import_blocks( - &client, - &mut proposer_factory, - &mut block_import, - BlockId::Hash(canon[7]), - 10, - ) - .await; - let fork3 = propose_and_import_blocks( - &client, - &mut proposer_factory, - &mut block_import, - BlockId::Hash(canon[11]), - 8, - ) - .await; + let fork1 = + propose_and_import_blocks(&client, &mut proposer_factory, &mut block_import, canon[0], 10) + .await; + let fork2 = + propose_and_import_blocks(&client, &mut proposer_factory, &mut block_import, canon[7], 10) + .await; + let fork3 = + propose_and_import_blocks(&client, &mut proposer_factory, &mut block_import, canon[11], 8) + .await; // We should be tracking a total of 9 epochs in the fork tree assert_eq!(epoch_changes.shared_data().tree().iter().count(), 8); @@ -850,7 +835,7 @@ async fn revert_not_allowed_for_finalized() { &client, &mut proposer_factory, &mut block_import, - BlockId::Number(0), + client.chain_info().genesis_hash, 3, ) .await; @@ -903,36 +888,21 @@ async fn importing_epoch_change_block_prunes_tree() { &client, &mut proposer_factory, &mut block_import, - BlockId::Number(0), + client.chain_info().genesis_hash, 30, ) .await; // Create the forks - let fork_1 = propose_and_import_blocks( - &client, - &mut proposer_factory, - &mut block_import, - BlockId::Hash(canon[0]), - 10, - ) - .await; - let fork_2 = propose_and_import_blocks( - &client, - &mut proposer_factory, - &mut block_import, - BlockId::Hash(canon[12]), - 15, - ) - .await; - let fork_3 = propose_and_import_blocks( - &client, - &mut proposer_factory, - &mut block_import, - BlockId::Hash(canon[18]), - 10, - ) - .await; + let fork_1 = + propose_and_import_blocks(&client, &mut proposer_factory, &mut block_import, canon[0], 10) + .await; + let fork_2 = + propose_and_import_blocks(&client, &mut proposer_factory, &mut block_import, canon[12], 15) + .await; + let fork_3 = + propose_and_import_blocks(&client, &mut proposer_factory, &mut block_import, canon[18], 10) + .await; // We should be tracking a total of 9 epochs in the fork tree assert_eq!(epoch_changes.shared_data().tree().iter().count(), 9); @@ -947,7 +917,7 @@ async fn importing_epoch_change_block_prunes_tree() { &client, &mut proposer_factory, &mut block_import, - BlockId::Hash(client.chain_info().best_hash), + client.chain_info().best_hash, 7, ) .await; @@ -967,7 +937,7 @@ async fn importing_epoch_change_block_prunes_tree() { &client, &mut proposer_factory, &mut block_import, - BlockId::Hash(client.chain_info().best_hash), + client.chain_info().best_hash, 8, ) .await; @@ -1001,7 +971,7 @@ async fn verify_slots_are_strictly_increasing() { mutator: Arc::new(|_, _| ()), }; - let genesis_header = client.header(&BlockId::Number(0)).unwrap().unwrap(); + let genesis_header = client.header(client.chain_info().genesis_hash).unwrap().unwrap(); // we should have no issue importing this block let b1 = propose_and_import_block( @@ -1012,7 +982,7 @@ async fn verify_slots_are_strictly_increasing() { ) .await; - let b1 = client.header(&BlockId::Hash(b1)).unwrap().unwrap(); + let b1 = client.header(b1).unwrap().unwrap(); // we should fail to import this block since the slot number didn't increase. // we will panic due to the `PanickingBlockImport` defined above. @@ -1091,7 +1061,7 @@ async fn obsolete_blocks_aux_data_cleanup() { &client, &mut proposer_factory, &mut block_import, - BlockId::Number(0), + client.chain_info().genesis_hash, 4, ) .await; @@ -1099,7 +1069,7 @@ async fn obsolete_blocks_aux_data_cleanup() { &client, &mut proposer_factory, &mut block_import, - BlockId::Number(0), + client.chain_info().genesis_hash, 2, ) .await; @@ -1107,7 +1077,7 @@ async fn obsolete_blocks_aux_data_cleanup() { &client, &mut proposer_factory, &mut block_import, - BlockId::Number(3), + fork1_hashes[2], 2, ) .await; diff --git a/client/consensus/common/src/longest_chain.rs b/client/consensus/common/src/longest_chain.rs index 941cd4b944..fab2c3a4c0 100644 --- a/client/consensus/common/src/longest_chain.rs +++ b/client/consensus/common/src/longest_chain.rs @@ -21,10 +21,7 @@ use sc_client_api::backend; use sp_blockchain::{Backend, HeaderBackend}; use sp_consensus::{Error as ConsensusError, SelectChain}; -use sp_runtime::{ - generic::BlockId, - traits::{Block as BlockT, NumberFor}, -}; +use sp_runtime::traits::{Block as BlockT, NumberFor}; use std::{marker::PhantomData, sync::Arc}; /// Implement Longest Chain Select implementation @@ -63,7 +60,7 @@ where Ok(self .backend .blockchain() - .header(BlockId::Hash(best_hash))? + .header(best_hash)? .expect("given block hash was fetched from block in db; qed")) } diff --git a/client/consensus/manual-seal/src/consensus/babe.rs b/client/consensus/manual-seal/src/consensus/babe.rs index d2bea3a3a3..c5bb3b78fc 100644 --- a/client/consensus/manual-seal/src/consensus/babe.rs +++ b/client/consensus/manual-seal/src/consensus/babe.rs @@ -44,7 +44,7 @@ use sp_consensus_babe::{ use sp_consensus_slots::Slot; use sp_inherents::InherentData; use sp_runtime::{ - generic::{BlockId, Digest}, + generic::Digest, traits::{Block as BlockT, Header}, DigestItem, }; @@ -108,7 +108,7 @@ where let parent_hash = import_params.header.parent_hash(); let parent = self .client - .header(BlockId::Hash(*parent_hash)) + .header(*parent_hash) .ok() .flatten() .ok_or_else(|| format!("header for block {} not found", parent_hash))?; diff --git a/client/consensus/manual-seal/src/consensus/timestamp.rs b/client/consensus/manual-seal/src/consensus/timestamp.rs index 2cb9887a81..b3c2d780c3 100644 --- a/client/consensus/manual-seal/src/consensus/timestamp.rs +++ b/client/consensus/manual-seal/src/consensus/timestamp.rs @@ -30,10 +30,7 @@ use sp_consensus_aura::{ use sp_consensus_babe::BabeApi; use sp_consensus_slots::{Slot, SlotDuration}; use sp_inherents::{InherentData, InherentDataProvider, InherentIdentifier}; -use sp_runtime::{ - generic::BlockId, - traits::{Block as BlockT, Zero}, -}; +use sp_runtime::traits::{Block as BlockT, Zero}; use sp_timestamp::{InherentType, INHERENT_IDENTIFIER}; use std::{ sync::{atomic, Arc}, @@ -109,7 +106,7 @@ impl SlotTimestampProvider { // otherwise we'd be producing blocks for older slots. let time = if info.best_number != Zero::zero() { let header = client - .header(BlockId::Hash(info.best_hash))? + .header(info.best_hash)? .ok_or_else(|| "best header not found in the db!".to_string())?; let slot = func(header)?; // add the slot duration so there's no collision of slots diff --git a/client/consensus/manual-seal/src/lib.rs b/client/consensus/manual-seal/src/lib.rs index 700b94cf1d..5c86382a3a 100644 --- a/client/consensus/manual-seal/src/lib.rs +++ b/client/consensus/manual-seal/src/lib.rs @@ -360,7 +360,7 @@ mod tests { let (client, select_chain) = builder.build_with_longest_chain(); let client = Arc::new(client); let spawner = sp_core::testing::TaskExecutor::new(); - let genesis_hash = client.header(&BlockId::Number(0)).unwrap().unwrap().hash(); + let genesis_hash = client.info().genesis_hash; let pool = Arc::new(BasicPool::with_revalidation_type( Options::default(), true.into(), @@ -424,7 +424,8 @@ mod tests { } ); // assert that there's a new block in the db. - assert!(client.header(&BlockId::Number(1)).unwrap().is_some()) + assert!(client.header(created_block.hash).unwrap().is_some()); + assert_eq!(client.header(created_block.hash).unwrap().unwrap().number, 1) } #[tokio::test] @@ -433,7 +434,7 @@ mod tests { let (client, select_chain) = builder.build_with_longest_chain(); let client = Arc::new(client); let spawner = sp_core::testing::TaskExecutor::new(); - let genesis_hash = client.header(&BlockId::Number(0)).unwrap().unwrap().hash(); + let genesis_hash = client.info().genesis_hash; let pool = Arc::new(BasicPool::with_revalidation_type( Options::default(), true.into(), @@ -494,7 +495,7 @@ mod tests { } ); // assert that there's a new block in the db. - let header = client.header(&BlockId::Number(1)).unwrap().unwrap(); + let header = client.header(created_block.hash).unwrap().unwrap(); let (tx, rx) = futures::channel::oneshot::channel(); sink.send(EngineCommand::FinalizeBlock { sender: Some(tx), @@ -518,7 +519,7 @@ mod tests { &sp_core::testing::TaskExecutor::new(), )); let spawner = sp_core::testing::TaskExecutor::new(); - let genesis_hash = client.header(&BlockId::Number(0)).unwrap().unwrap().hash(); + let genesis_hash = client.info().genesis_hash; let pool = Arc::new(BasicPool::with_revalidation_type( Options::default(), true.into(), @@ -582,7 +583,8 @@ mod tests { assert!(pool.submit_one(&BlockId::Number(1), SOURCE, uxt(Alice, 1)).await.is_ok()); - let header = client.header(&BlockId::Number(1)).expect("db error").expect("imported above"); + let header = client.header(created_block.hash).expect("db error").expect("imported above"); + assert_eq!(header.number, 1); pool.maintain(sc_transaction_pool_api::ChainEvent::NewBestBlock { hash: header.hash(), tree_route: None, @@ -614,7 +616,7 @@ mod tests { .is_ok()); let imported = rx2.await.unwrap().unwrap(); // assert that fork block is in the db - assert!(client.header(&BlockId::Hash(imported.hash)).unwrap().is_some()) + assert!(client.header(imported.hash).unwrap().is_some()) } #[tokio::test] @@ -623,7 +625,7 @@ mod tests { let (client, select_chain) = builder.build_with_longest_chain(); let client = Arc::new(client); let spawner = sp_core::testing::TaskExecutor::new(); - let genesis_hash = client.header(&BlockId::Number(0)).unwrap().unwrap().hash(); + let genesis_hash = client.header(client.info().genesis_hash).unwrap().unwrap().hash(); let pool = Arc::new(BasicPool::with_revalidation_type( Options::default(), true.into(), @@ -665,7 +667,7 @@ mod tests { let created_block = rx.await.unwrap().unwrap(); // assert that the background task returned the actual header hash - let header = client.header(&BlockId::Number(1)).unwrap().unwrap(); - assert_eq!(header.hash(), created_block.hash); + let header = client.header(created_block.hash).unwrap().unwrap(); + assert_eq!(header.number, 1); } } diff --git a/client/consensus/manual-seal/src/seal_block.rs b/client/consensus/manual-seal/src/seal_block.rs index 25c091bf46..2cafdae503 100644 --- a/client/consensus/manual-seal/src/seal_block.rs +++ b/client/consensus/manual-seal/src/seal_block.rs @@ -26,10 +26,7 @@ use sp_api::{ProvideRuntimeApi, TransactionFor}; use sp_blockchain::HeaderBackend; use sp_consensus::{self, BlockOrigin, Environment, Proposer, SelectChain}; use sp_inherents::{CreateInherentDataProviders, InherentDataProvider}; -use sp_runtime::{ - generic::BlockId, - traits::{Block as BlockT, Header as HeaderT}, -}; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; use std::{collections::HashMap, sync::Arc, time::Duration}; /// max duration for creating a proposal in secs @@ -102,9 +99,8 @@ pub async fn seal_block( // use the parent_hash supplied via `EngineCommand` // or fetch the best_block. let parent = match parent_hash { - Some(hash) => client - .header(BlockId::Hash(hash))? - .ok_or_else(|| Error::BlockNotFound(format!("{}", hash)))?, + Some(hash) => + client.header(hash)?.ok_or_else(|| Error::BlockNotFound(format!("{}", hash)))?, None => select_chain.best_chain().await?, }; diff --git a/client/db/src/lib.rs b/client/db/src/lib.rs index 7c2635f843..b1f5a6edcd 100644 --- a/client/db/src/lib.rs +++ b/client/db/src/lib.rs @@ -524,21 +524,19 @@ impl BlockchainDb { } impl sc_client_api::blockchain::HeaderBackend for BlockchainDb { - fn header(&self, id: BlockId) -> ClientResult> { - match &id { - BlockId::Hash(h) => { - let mut cache = self.header_cache.lock(); - if let Some(result) = cache.get_refresh(h) { - return Ok(result.clone()) - } - let header = - utils::read_header(&*self.db, columns::KEY_LOOKUP, columns::HEADER, id)?; - cache_header(&mut cache, *h, header.clone()); - Ok(header) - }, - BlockId::Number(_) => - utils::read_header(&*self.db, columns::KEY_LOOKUP, columns::HEADER, id), + fn header(&self, hash: Block::Hash) -> ClientResult> { + let mut cache = self.header_cache.lock(); + if let Some(result) = cache.get_refresh(&hash) { + return Ok(result.clone()) } + let header = utils::read_header( + &*self.db, + columns::KEY_LOOKUP, + columns::HEADER, + BlockId::::Hash(hash), + )?; + cache_header(&mut cache, hash, header.clone()); + Ok(header) } fn info(&self) -> sc_client_api::blockchain::Info { @@ -557,7 +555,7 @@ impl sc_client_api::blockchain::HeaderBackend for Blockcha fn status(&self, id: BlockId) -> ClientResult { let exists = match id { - BlockId::Hash(_) => self.header(id)?.is_some(), + BlockId::Hash(hash) => self.header(hash)?.is_some(), BlockId::Number(n) => n <= self.meta.read().best_number, }; match exists { @@ -571,8 +569,13 @@ impl sc_client_api::blockchain::HeaderBackend for Blockcha } fn hash(&self, number: NumberFor) -> ClientResult> { - self.header(BlockId::Number(number)) - .map(|maybe_header| maybe_header.map(|header| header.hash())) + Ok(utils::read_header::( + &*self.db, + columns::KEY_LOOKUP, + columns::HEADER, + BlockId::Number(number), + )? + .map(|header| header.hash())) } } @@ -736,7 +739,7 @@ impl HeaderMetadata for BlockchainDb { ) -> Result, Self::Error> { self.header_metadata_cache.header_metadata(hash).map_or_else( || { - self.header(BlockId::hash(hash))? + self.header(hash)? .map(|header| { let header_metadata = CachedHeaderMetadata::from(&header); self.header_metadata_cache @@ -1369,7 +1372,7 @@ impl Backend { }; for (block_hash, justification) in operation.finalized_blocks { - let block_header = self.blockchain.expect_header(BlockId::Hash(block_hash))?; + let block_header = self.blockchain.expect_header(block_hash)?; meta_updates.push(self.finalize_block_with_transaction( &mut transaction, block_hash, @@ -1394,8 +1397,7 @@ impl Backend { .highest_leaf() .map(|(n, _)| n) .unwrap_or(Zero::zero()); - let existing_header = - number <= highest_leaf && self.blockchain.header(BlockId::hash(hash))?.is_some(); + let existing_header = number <= highest_leaf && self.blockchain.header(hash)?.is_some(); // blocks are keyed by number + hash. let lookup_key = utils::number_and_hash_to_lookup_key(number, hash)?; @@ -1615,10 +1617,7 @@ impl Backend { ); } } else if number > best_num + One::one() && - number > One::one() && self - .blockchain - .header(BlockId::hash(parent_hash))? - .is_none() + number > One::one() && self.blockchain.header(parent_hash)?.is_none() { let gap = (best_num + One::one(), number - One::one()); transaction.set(columns::META, meta_keys::BLOCK_GAP, &gap.encode()); @@ -1640,10 +1639,9 @@ impl Backend { }; if let Some(set_head) = operation.set_head { - if let Some(header) = sc_client_api::blockchain::HeaderBackend::header( - &self.blockchain, - BlockId::Hash(set_head), - )? { + if let Some(header) = + sc_client_api::blockchain::HeaderBackend::header(&self.blockchain, set_head)? + { let number = header.number(); let hash = header.hash(); @@ -1767,10 +1765,9 @@ impl Backend { // that are canonical, but not yet finalized. So we stop deleting as soon as // we reach canonical chain. while self.blockchain.hash(number)? != Some(hash) { - let id = BlockId::::hash(hash); - match self.blockchain.header(id)? { + match self.blockchain.header(hash)? { Some(header) => { - self.prune_block(transaction, id)?; + self.prune_block(transaction, BlockId::::hash(hash))?; number = header.number().saturating_sub(One::one()); hash = *header.parent_hash(); }, @@ -2013,7 +2010,7 @@ impl sc_client_api::backend::Backend for Backend { justification: Option, ) -> ClientResult<()> { let mut transaction = Transaction::new(); - let header = self.blockchain.expect_header(BlockId::Hash(hash))?; + let header = self.blockchain.expect_header(hash)?; let mut displaced = None; let m = self.finalize_block_with_transaction( @@ -2035,7 +2032,7 @@ impl sc_client_api::backend::Backend for Backend { justification: Justification, ) -> ClientResult<()> { let mut transaction: Transaction = Transaction::new(); - let header = self.blockchain.expect_header(BlockId::Hash(hash))?; + let header = self.blockchain.expect_header(hash)?; let number = *header.number(); // Check if the block is finalized first. @@ -2141,13 +2138,12 @@ impl sc_client_api::backend::Backend for Backend { return Ok(c.saturated_into::>()) } let mut transaction = Transaction::new(); - let removed = - self.blockchain.header(BlockId::Hash(hash_to_revert))?.ok_or_else(|| { - sp_blockchain::Error::UnknownBlock(format!( - "Error reverting to {}. Block header not found.", - hash_to_revert, - )) - })?; + let removed = self.blockchain.header(hash_to_revert)?.ok_or_else(|| { + sp_blockchain::Error::UnknownBlock(format!( + "Error reverting to {}. Block header not found.", + hash_to_revert, + )) + })?; let removed_hash = removed.hash(); let prev_number = number_to_revert.saturating_sub(One::one()); @@ -3196,7 +3192,7 @@ pub(crate) mod tests { }; { - let header = backend.blockchain().header(BlockId::Hash(hash1)).unwrap().unwrap(); + let header = backend.blockchain().header(hash1).unwrap().unwrap(); let mut op = backend.begin_operation().unwrap(); op.set_block_data(header, None, None, None, NewBlockState::Best).unwrap(); backend.commit_operation(op).unwrap(); @@ -3603,26 +3599,26 @@ pub(crate) mod tests { assert_eq!(backend.blockchain().children(blocks[1]).unwrap(), vec![blocks[2], blocks[3]]); assert!(backend.have_state_at(blocks[3], 2)); - assert!(backend.blockchain().header(BlockId::hash(blocks[3])).unwrap().is_some()); + assert!(backend.blockchain().header(blocks[3]).unwrap().is_some()); backend.remove_leaf_block(blocks[3]).unwrap(); assert!(!backend.have_state_at(blocks[3], 2)); - assert!(backend.blockchain().header(BlockId::hash(blocks[3])).unwrap().is_none()); + assert!(backend.blockchain().header(blocks[3]).unwrap().is_none()); assert_eq!(backend.blockchain().leaves().unwrap(), vec![blocks[2], best_hash]); assert_eq!(backend.blockchain().children(blocks[1]).unwrap(), vec![blocks[2]]); assert!(backend.have_state_at(blocks[2], 2)); - assert!(backend.blockchain().header(BlockId::hash(blocks[2])).unwrap().is_some()); + assert!(backend.blockchain().header(blocks[2]).unwrap().is_some()); backend.remove_leaf_block(blocks[2]).unwrap(); assert!(!backend.have_state_at(blocks[2], 2)); - assert!(backend.blockchain().header(BlockId::hash(blocks[2])).unwrap().is_none()); + assert!(backend.blockchain().header(blocks[2]).unwrap().is_none()); assert_eq!(backend.blockchain().leaves().unwrap(), vec![best_hash, blocks[1]]); assert_eq!(backend.blockchain().children(blocks[1]).unwrap(), vec![]); assert!(backend.have_state_at(blocks[1], 1)); - assert!(backend.blockchain().header(BlockId::hash(blocks[1])).unwrap().is_some()); + assert!(backend.blockchain().header(blocks[1]).unwrap().is_some()); backend.remove_leaf_block(blocks[1]).unwrap(); assert!(!backend.have_state_at(blocks[1], 1)); - assert!(backend.blockchain().header(BlockId::hash(blocks[1])).unwrap().is_none()); + assert!(backend.blockchain().header(blocks[1]).unwrap().is_none()); assert_eq!(backend.blockchain().leaves().unwrap(), vec![best_hash]); assert_eq!(backend.blockchain().children(blocks[0]).unwrap(), vec![best_hash]); } diff --git a/client/finality-grandpa/src/environment.rs b/client/finality-grandpa/src/environment.rs index 110d0eb2c9..9e63bef78a 100644 --- a/client/finality-grandpa/src/environment.rs +++ b/client/finality-grandpa/src/environment.rs @@ -525,7 +525,7 @@ where Some((_, n)) if n > best_block_number => best_block_hash, Some((h, _)) => { // this is the header at which the new set will start - let header = self.client.header(BlockId::Hash(h))?.expect( + let header = self.client.header(h)?.expect( "got block hash from registered pending change; \ pending changes are only registered on block import; qed.", ); @@ -1171,7 +1171,7 @@ where SelectChain: SelectChainT + 'static, VotingRule: VotingRuleT, { - let base_header = match client.header(BlockId::Hash(block))? { + let base_header = match client.header(block)? { Some(h) => h, None => { debug!( @@ -1197,7 +1197,7 @@ where let result = match select_chain.finality_target(block, None).await { Ok(best_hash) => { let best_header = client - .header(BlockId::Hash(best_hash))? + .header(best_hash)? .expect("Header known to exist after `finality_target` call; qed"); // check if our vote is currently being limited due to a pending change @@ -1221,7 +1221,7 @@ where } target_header = client - .header(BlockId::Hash(*target_header.parent_hash()))? + .header(*target_header.parent_hash())? .expect("Header known to exist after `finality_target` call; qed"); } diff --git a/client/finality-grandpa/src/finality_proof.rs b/client/finality-grandpa/src/finality_proof.rs index 43ed0ed319..416e71478a 100644 --- a/client/finality-grandpa/src/finality_proof.rs +++ b/client/finality-grandpa/src/finality_proof.rs @@ -222,7 +222,8 @@ where if current > just_block || headers.len() >= MAX_UNKNOWN_HEADERS { break } - headers.push(backend.blockchain().expect_header(BlockId::Number(current))?); + let hash = backend.blockchain().expect_block_hash_from_id(&BlockId::Number(current))?; + headers.push(backend.blockchain().expect_header(hash)?); current += One::one(); } headers diff --git a/client/finality-grandpa/src/import.rs b/client/finality-grandpa/src/import.rs index e061c105ee..1359110132 100644 --- a/client/finality-grandpa/src/import.rs +++ b/client/finality-grandpa/src/import.rs @@ -122,7 +122,7 @@ where }; if let Ok(hash) = effective_block_hash { - if let Ok(Some(header)) = self.inner.header(BlockId::Hash(hash)) { + if let Ok(Some(header)) = self.inner.header(hash) { if *header.number() == pending_change.effective_number() { out.push((header.hash(), *header.number())); } @@ -364,14 +364,12 @@ where // best finalized block. let best_finalized_number = self.inner.info().finalized_number; let canon_number = best_finalized_number.min(median_last_finalized_number); - let canon_hash = - self.inner.header(BlockId::Number(canon_number)) + let canon_hash = self.inner.hash(canon_number) .map_err(|e| ConsensusError::ClientImport(e.to_string()))? .expect( "the given block number is less or equal than the current best finalized number; \ current best finalized number must exist in chain; qed." - ) - .hash(); + ); NewAuthoritySet { canon_number, diff --git a/client/finality-grandpa/src/justification.rs b/client/finality-grandpa/src/justification.rs index 56b26c964c..96c31d4c0c 100644 --- a/client/finality-grandpa/src/justification.rs +++ b/client/finality-grandpa/src/justification.rs @@ -26,10 +26,7 @@ use finality_grandpa::{voter_set::VoterSet, Error as GrandpaError}; use parity_scale_codec::{Decode, Encode}; use sp_blockchain::{Error as ClientError, HeaderBackend}; use sp_finality_grandpa::AuthorityId; -use sp_runtime::{ - generic::BlockId, - traits::{Block as BlockT, Header as HeaderT, NumberFor}, -}; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor}; use crate::{AuthorityList, Commit, Error}; @@ -104,7 +101,7 @@ impl GrandpaJustification { break } - match client.header(BlockId::Hash(current_hash))? { + match client.header(current_hash)? { Some(current_header) => { // NOTE: this should never happen as we pick the lowest block // as base and only traverse backwards from the other blocks diff --git a/client/finality-grandpa/src/tests.rs b/client/finality-grandpa/src/tests.rs index d5bc7d50c5..4abcb05406 100644 --- a/client/finality-grandpa/src/tests.rs +++ b/client/finality-grandpa/src/tests.rs @@ -1530,10 +1530,13 @@ async fn justification_with_equivocation() { // we create a basic chain with 3 blocks (no forks) net.peer(0).push_blocks(3, false); - let client = net.peer(0).client().clone(); - let block1 = client.header(&BlockId::Number(1)).ok().flatten().unwrap(); - let block2 = client.header(&BlockId::Number(2)).ok().flatten().unwrap(); - let block3 = client.header(&BlockId::Number(3)).ok().flatten().unwrap(); + let client = net.peer(0).client().as_client().clone(); + let hashof1 = client.expect_block_hash_from_id(&BlockId::Number(1)).unwrap(); + let hashof2 = client.expect_block_hash_from_id(&BlockId::Number(2)).unwrap(); + let hashof3 = client.expect_block_hash_from_id(&BlockId::Number(3)).unwrap(); + let block1 = client.expect_header(hashof1).unwrap(); + let block2 = client.expect_header(hashof2).unwrap(); + let block3 = client.expect_header(hashof3).unwrap(); let set_id = 0; let justification = { @@ -1576,7 +1579,7 @@ async fn justification_with_equivocation() { precommits, }; - GrandpaJustification::from_commit(&client.as_client(), round, commit).unwrap() + GrandpaJustification::from_commit(&client, round, commit).unwrap() }; // the justification should include the minimal necessary vote ancestry and diff --git a/client/finality-grandpa/src/voting_rule.rs b/client/finality-grandpa/src/voting_rule.rs index fb7754fc01..6f15a9cdd3 100644 --- a/client/finality-grandpa/src/voting_rule.rs +++ b/client/finality-grandpa/src/voting_rule.rs @@ -27,10 +27,7 @@ use std::{future::Future, pin::Pin, sync::Arc}; use dyn_clone::DynClone; use sc_client_api::blockchain::HeaderBackend; -use sp_runtime::{ - generic::BlockId, - traits::{Block as BlockT, Header, NumberFor, One, Zero}, -}; +use sp_runtime::traits::{Block as BlockT, Header, NumberFor, One, Zero}; /// A future returned by a `VotingRule` to restrict a given vote, if any restriction is necessary. pub type VotingRuleResult = @@ -197,7 +194,7 @@ where target_hash = *target_header.parent_hash(); target_header = backend - .header(BlockId::Hash(target_hash)) + .header(target_hash) .ok()? .expect("Header known to exist due to the existence of one of its descendents; qed"); } @@ -242,7 +239,7 @@ where restricted_number >= base.number() && restricted_number < restricted_target.number() }) - .and_then(|(hash, _)| backend.header(BlockId::Hash(hash)).ok()) + .and_then(|(hash, _)| backend.header(hash).ok()) .and_then(std::convert::identity) { restricted_target = header; @@ -371,16 +368,18 @@ mod tests { let rule = VotingRulesBuilder::new().add(Subtract(50)).add(Subtract(50)).build(); let mut client = Arc::new(TestClientBuilder::new().build()); + let mut hashes = Vec::with_capacity(200); for _ in 0..200 { let block = client.new_block(Default::default()).unwrap().build().unwrap().block; + hashes.push(block.hash()); futures::executor::block_on(client.import(BlockOrigin::Own, block)).unwrap(); } - let genesis = client.header(&BlockId::Number(0u32.into())).unwrap().unwrap(); + let genesis = client.header(client.info().genesis_hash).unwrap().unwrap(); - let best = client.header(&BlockId::Hash(client.info().best_hash)).unwrap().unwrap(); + let best = client.header(client.info().best_hash).unwrap().unwrap(); let (_, number) = futures::executor::block_on(rule.restrict_vote(client.clone(), &genesis, &best, &best)) @@ -390,7 +389,7 @@ mod tests { // which means that we should be voting for block #100 assert_eq!(number, 100); - let block110 = client.header(&BlockId::Number(110u32.into())).unwrap().unwrap(); + let block110 = client.header(hashes[109]).unwrap().unwrap(); let (_, number) = futures::executor::block_on(rule.restrict_vote( client.clone(), @@ -412,17 +411,20 @@ mod tests { let mut client = Arc::new(TestClientBuilder::new().build()); - for _ in 0..5 { + let n = 5; + let mut hashes = Vec::with_capacity(n); + for _ in 0..n { let block = client.new_block(Default::default()).unwrap().build().unwrap().block; + hashes.push(block.hash()); futures::executor::block_on(client.import(BlockOrigin::Own, block)).unwrap(); } - let best = client.header(&BlockId::Hash(client.info().best_hash)).unwrap().unwrap(); + let best = client.header(client.info().best_hash).unwrap().unwrap(); let best_number = *best.number(); - for i in 0u32..5 { - let base = client.header(&BlockId::Number(i.into())).unwrap().unwrap(); + for i in 0..n { + let base = client.header(hashes[i]).unwrap().unwrap(); let (_, number) = futures::executor::block_on(rule.restrict_vote( client.clone(), &base, diff --git a/client/finality-grandpa/src/warp_proof.rs b/client/finality-grandpa/src/warp_proof.rs index c9f762fc7d..c65a705d3e 100644 --- a/client/finality-grandpa/src/warp_proof.rs +++ b/client/finality-grandpa/src/warp_proof.rs @@ -116,9 +116,12 @@ impl WarpSyncProof { let set_changes = set_changes.iter_from(begin_number).ok_or(Error::MissingData)?; for (_, last_block) in set_changes { - let header = blockchain.header(BlockId::Number(*last_block))?.expect( - "header number comes from previously applied set changes; must exist in db; qed.", - ); + let hash = blockchain.block_hash_from_id(&BlockId::Number(*last_block))? + .expect("header number comes from previously applied set changes; corresponding hash must exist in db; qed."); + + let header = blockchain + .header(hash)? + .expect("header hash obtained from header number exists in db; corresponding header must exist in db too; qed."); // the last block in a set is the one that triggers a change to the next set, // therefore the block must have a digest that signals the authority set change @@ -172,7 +175,7 @@ impl WarpSyncProof { }); if let Some(latest_justification) = latest_justification { - let header = blockchain.header(BlockId::Hash(latest_justification.target().1))? + let header = blockchain.header(latest_justification.target().1)? .expect("header hash corresponds to a justification in db; must exist in db as well; qed."); proofs.push(WarpSyncFragment { header, justification: latest_justification }) diff --git a/client/merkle-mountain-range/src/test_utils.rs b/client/merkle-mountain-range/src/test_utils.rs index f345fb5257..1bc7a9bd5b 100644 --- a/client/merkle-mountain-range/src/test_utils.rs +++ b/client/merkle-mountain-range/src/test_utils.rs @@ -226,8 +226,8 @@ impl HeaderMetadata for MockClient { } impl HeaderBackend for MockClient { - fn header(&self, id: BlockId) -> sc_client_api::blockchain::Result> { - self.client.lock().header(&id) + fn header(&self, hash: Hash) -> sc_client_api::blockchain::Result> { + self.client.lock().header(hash) } fn info(&self) -> Info { diff --git a/client/network/src/protocol.rs b/client/network/src/protocol.rs index 10eb31b595..1f0ec9c0d1 100644 --- a/client/network/src/protocol.rs +++ b/client/network/src/protocol.rs @@ -46,10 +46,7 @@ use sc_network_common::{ utils::{interval, LruHashSet}, }; use sp_arithmetic::traits::SaturatedConversion; -use sp_runtime::{ - generic::BlockId, - traits::{Block as BlockT, CheckedSub, Header as HeaderT, NumberFor, Zero}, -}; +use sp_runtime::traits::{Block as BlockT, CheckedSub, Header as HeaderT, NumberFor, Zero}; use std::{ collections::{HashMap, HashSet, VecDeque}, io, iter, @@ -627,7 +624,7 @@ where /// In chain-based consensus, we often need to make sure non-best forks are /// at least temporarily synced. pub fn announce_block(&mut self, hash: B::Hash, data: Option>) { - let header = match self.chain.header(BlockId::Hash(hash)) { + let header = match self.chain.header(hash) { Ok(Some(header)) => header, Ok(None) => { warn!("Trying to announce unknown block: {}", hash); diff --git a/client/network/src/service/tests/chain_sync.rs b/client/network/src/service/tests/chain_sync.rs index ce4430dd87..9d8463ff19 100644 --- a/client/network/src/service/tests/chain_sync.rs +++ b/client/network/src/service/tests/chain_sync.rs @@ -199,7 +199,7 @@ async fn on_block_finalized() { let mut chain_sync = Box::new(MockChainSync::::new()); - let at = client.header(&BlockId::Hash(client.info().best_hash)).unwrap().unwrap().hash(); + let at = client.header(client.info().best_hash).unwrap().unwrap().hash(); let block = client .new_block_at(&BlockId::Hash(at), Default::default(), false) .unwrap() diff --git a/client/network/sync/src/block_request_handler.rs b/client/network/sync/src/block_request_handler.rs index b5f8b6b73b..84755453fb 100644 --- a/client/network/sync/src/block_request_handler.rs +++ b/client/network/sync/src/block_request_handler.rs @@ -330,7 +330,16 @@ where let mut blocks = Vec::new(); let mut total_size: usize = 0; - while let Some(header) = self.client.header(block_id).unwrap_or_default() { + + let client_header_from_block_id = + |block_id: BlockId| -> Result, HandleRequestError> { + if let Some(hash) = self.client.block_hash_from_id(&block_id)? { + return self.client.header(hash).map_err(Into::into) + } + Ok(None) + }; + + while let Some(header) = client_header_from_block_id(block_id).unwrap_or_default() { let number = *header.number(); let hash = header.hash(); let parent_hash = *header.parent_hash(); diff --git a/client/network/sync/src/lib.rs b/client/network/sync/src/lib.rs index 75eda91219..7dd818d4c1 100644 --- a/client/network/sync/src/lib.rs +++ b/client/network/sync/src/lib.rs @@ -1082,7 +1082,7 @@ where 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(BlockId::hash(*hash)) { + if let Ok(Some(header)) = self.client.header(*hash) { log::debug!( target: "sync", "Starting state sync for #{} ({})", diff --git a/client/network/test/src/block_import.rs b/client/network/test/src/block_import.rs index b86f6787f3..250f5f5fd1 100644 --- a/client/network/test/src/block_import.rs +++ b/client/network/test/src/block_import.rs @@ -26,7 +26,6 @@ use sc_consensus::{ IncomingBlock, }; use sp_consensus::BlockOrigin; -use sp_runtime::generic::BlockId; use substrate_test_runtime_client::{ self, prelude::*, @@ -39,7 +38,7 @@ fn prepare_good_block() -> (TestClient, Hash, u64, PeerId, IncomingBlock) block_on(client.import(BlockOrigin::File, block)).unwrap(); let (hash, number) = (client.block_hash(1).unwrap().unwrap(), 1); - let header = client.header(&BlockId::Number(1)).unwrap(); + let header = client.header(hash).unwrap(); let justifications = client.justifications(hash).unwrap(); let peer_id = PeerId::random(); ( diff --git a/client/network/test/src/lib.rs b/client/network/test/src/lib.rs index a7b963555c..b3653ac7c0 100644 --- a/client/network/test/src/lib.rs +++ b/client/network/test/src/lib.rs @@ -163,17 +163,23 @@ impl PeersClient { pub fn header( &self, - block: &BlockId, + hash: ::Hash, ) -> ClientResult::Header>> { - self.client.header(block) + self.client.header(hash) } pub fn has_state_at(&self, block: &BlockId) -> bool { - let header = match self.header(block).unwrap() { - Some(header) => header, - None => return false, + let (number, hash) = match *block { + BlockId::Hash(h) => match self.as_client().number(h) { + Ok(Some(n)) => (n, h), + _ => return false, + }, + BlockId::Number(n) => match self.as_client().hash(n) { + Ok(Some(h)) => (n, h), + _ => return false, + }, }; - self.backend.have_state_at(header.hash(), *header.number()) + self.backend.have_state_at(hash, number) } pub fn justifications( @@ -367,7 +373,7 @@ where { let mut hashes = Vec::with_capacity(count); let full_client = self.client.as_client(); - let mut at = full_client.header(&at).unwrap().unwrap().hash(); + let mut at = full_client.block_hash_from_id(&at).unwrap().unwrap(); for _ in 0..count { let builder = full_client.new_block_at(&BlockId::Hash(at), Default::default(), false).unwrap(); @@ -404,7 +410,7 @@ where if inform_sync_about_new_best_block { self.network.new_best_block_imported( at, - *full_client.header(&BlockId::Hash(at)).ok().flatten().unwrap().number(), + *full_client.header(at).ok().flatten().unwrap().number(), ); } hashes @@ -549,7 +555,7 @@ where pub fn has_block(&self, hash: H256) -> bool { self.backend .as_ref() - .map(|backend| backend.blockchain().header(BlockId::hash(hash)).unwrap().is_some()) + .map(|backend| backend.blockchain().header(hash).unwrap().is_some()) .unwrap_or(false) } @@ -673,7 +679,7 @@ impl WarpSyncProvider for TestWarpSyncProvider { _start: B::Hash, ) -> Result> { let info = self.0.info(); - let best_header = self.0.header(BlockId::hash(info.best_hash)).unwrap().unwrap(); + let best_header = self.0.header(info.best_hash).unwrap().unwrap(); Ok(EncodedProof(best_header.encode())) } fn verify( diff --git a/client/network/test/src/sync.rs b/client/network/test/src/sync.rs index c7b651f457..b629574fe9 100644 --- a/client/network/test/src/sync.rs +++ b/client/network/test/src/sync.rs @@ -267,9 +267,9 @@ async fn sync_justifications() { net.peer(0).client().finalize_block(hashof15, Some(just.clone()), true).unwrap(); net.peer(0).client().finalize_block(hashof20, Some(just.clone()), true).unwrap(); - let hashof10 = net.peer(1).client().header(&BlockId::Number(10)).unwrap().unwrap().hash(); - let hashof15 = net.peer(1).client().header(&BlockId::Number(15)).unwrap().unwrap().hash(); - let hashof20 = net.peer(1).client().header(&BlockId::Number(20)).unwrap().unwrap().hash(); + let hashof10 = net.peer(1).client().as_client().hash(10).unwrap().unwrap(); + let hashof15 = net.peer(1).client().as_client().hash(15).unwrap().unwrap(); + let hashof20 = net.peer(1).client().as_client().hash(20).unwrap().unwrap(); // peer 1 should get the justifications from the network net.peer(1).request_justification(&hashof10, 10); @@ -408,8 +408,8 @@ async fn can_sync_small_non_best_forks() { net.peer(1).push_blocks(10, false); assert_eq!(net.peer(1).client().info().best_number, 40); - assert!(net.peer(0).client().header(&BlockId::Hash(small_hash)).unwrap().is_some()); - assert!(net.peer(1).client().header(&BlockId::Hash(small_hash)).unwrap().is_none()); + assert!(net.peer(0).client().header(small_hash).unwrap().is_some()); + assert!(net.peer(1).client().header(small_hash).unwrap().is_none()); // poll until the two nodes connect, otherwise announcing the block will not work futures::future::poll_fn::<(), _>(|cx| { @@ -426,8 +426,8 @@ async fn can_sync_small_non_best_forks() { assert_eq!(net.peer(0).client().info().best_number, 40); - assert!(net.peer(0).client().header(&BlockId::Hash(small_hash)).unwrap().is_some()); - assert!(!net.peer(1).client().header(&BlockId::Hash(small_hash)).unwrap().is_some()); + assert!(net.peer(0).client().header(small_hash).unwrap().is_some()); + assert!(!net.peer(1).client().header(small_hash).unwrap().is_some()); net.peer(0).announce_block(small_hash, None); @@ -436,8 +436,8 @@ async fn can_sync_small_non_best_forks() { futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); - assert!(net.peer(0).client().header(&BlockId::Hash(small_hash)).unwrap().is_some()); - if net.peer(1).client().header(&BlockId::Hash(small_hash)).unwrap().is_none() { + assert!(net.peer(0).client().header(small_hash).unwrap().is_some()); + if net.peer(1).client().header(small_hash).unwrap().is_none() { return Poll::Pending } Poll::Ready(()) @@ -449,7 +449,7 @@ async fn can_sync_small_non_best_forks() { net.peer(0).announce_block(another_fork, None); futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); - if net.peer(1).client().header(&BlockId::Hash(another_fork)).unwrap().is_none() { + if net.peer(1).client().header(another_fork).unwrap().is_none() { return Poll::Pending } Poll::Ready(()) @@ -478,7 +478,7 @@ async fn can_sync_forks_ahead_of_the_best_chain() { .unwrap(); // Peer 1 is on 1-block fork net.peer(1).push_blocks(1, false); - assert!(net.peer(0).client().header(&BlockId::Hash(fork_hash)).unwrap().is_some()); + assert!(net.peer(0).client().header(fork_hash).unwrap().is_some()); assert_eq!(net.peer(0).client().info().best_number, 1); assert_eq!(net.peer(1).client().info().best_number, 2); @@ -486,7 +486,7 @@ async fn can_sync_forks_ahead_of_the_best_chain() { futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); - if net.peer(1).client().header(&BlockId::Hash(fork_hash)).unwrap().is_none() { + if net.peer(1).client().header(fork_hash).unwrap().is_none() { return Poll::Pending } Poll::Ready(()) @@ -512,8 +512,8 @@ async fn can_sync_explicit_forks() { net.peer(1).push_blocks(10, false); assert_eq!(net.peer(1).client().info().best_number, 40); - assert!(net.peer(0).client().header(&BlockId::Hash(small_hash)).unwrap().is_some()); - assert!(net.peer(1).client().header(&BlockId::Hash(small_hash)).unwrap().is_none()); + assert!(net.peer(0).client().header(small_hash).unwrap().is_some()); + assert!(net.peer(1).client().header(small_hash).unwrap().is_none()); // poll until the two nodes connect, otherwise announcing the block will not work futures::future::poll_fn::<(), _>(|cx| { @@ -530,8 +530,8 @@ async fn can_sync_explicit_forks() { assert_eq!(net.peer(0).client().info().best_number, 40); - assert!(net.peer(0).client().header(&BlockId::Hash(small_hash)).unwrap().is_some()); - assert!(!net.peer(1).client().header(&BlockId::Hash(small_hash)).unwrap().is_some()); + assert!(net.peer(0).client().header(small_hash).unwrap().is_some()); + assert!(!net.peer(1).client().header(small_hash).unwrap().is_some()); // request explicit sync let first_peer_id = net.peer(0).id(); @@ -541,8 +541,8 @@ async fn can_sync_explicit_forks() { futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); - assert!(net.peer(0).client().header(&BlockId::Hash(small_hash)).unwrap().is_some()); - if net.peer(1).client().header(&BlockId::Hash(small_hash)).unwrap().is_none() { + assert!(net.peer(0).client().header(small_hash).unwrap().is_some()); + if net.peer(1).client().header(small_hash).unwrap().is_none() { return Poll::Pending } Poll::Ready(()) @@ -634,7 +634,7 @@ async fn imports_stale_once() { futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); - if net.peer(1).client().header(&BlockId::Hash(hash)).unwrap().is_some() { + if net.peer(1).client().header(hash).unwrap().is_some() { Poll::Ready(()) } else { Poll::Pending @@ -992,9 +992,8 @@ async fn multiple_requests_are_accepted_as_long_as_they_are_not_fulfilled() { let hashes = net.peer(0).push_blocks(10, false); net.run_until_sync().await; - let hashof10 = hashes[9]; - // there's currently no justification for block #10 + let hashof10 = hashes[9]; assert_eq!(net.peer(0).client().justifications(hashof10).unwrap(), None); assert_eq!(net.peer(1).client().justifications(hashof10).unwrap(), None); @@ -1061,8 +1060,8 @@ async fn syncs_all_forks_from_single_peer() { net.run_until_sync().await; // Peer 1 should have both branches, - assert!(net.peer(1).client().header(&BlockId::Hash(branch1)).unwrap().is_some()); - assert!(net.peer(1).client().header(&BlockId::Hash(branch2)).unwrap().is_some()); + assert!(net.peer(1).client().header(branch1).unwrap().is_some()); + assert!(net.peer(1).client().header(branch2).unwrap().is_some()); } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] @@ -1086,7 +1085,7 @@ async fn syncs_after_missing_announcement() { let final_block = net.peer(0).push_blocks_at(BlockId::Number(11), 1, false).pop().unwrap(); net.peer(1).push_blocks_at(BlockId::Number(10), 1, true); net.run_until_sync().await; - assert!(net.peer(1).client().header(&BlockId::Hash(final_block)).unwrap().is_some()); + assert!(net.peer(1).client().header(final_block).unwrap().is_some()); } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] diff --git a/client/offchain/src/lib.rs b/client/offchain/src/lib.rs index 87e79833b9..6b28d3f8a4 100644 --- a/client/offchain/src/lib.rs +++ b/client/offchain/src/lib.rs @@ -375,7 +375,7 @@ mod tests { client.clone(), )); let network = Arc::new(TestNetwork()); - let header = client.header(&BlockId::number(0)).unwrap().unwrap(); + let header = client.header(client.chain_info().genesis_hash).unwrap().unwrap(); // when let offchain = OffchainWorkers::new(client); diff --git a/client/rpc-spec-v2/src/chain_head/chain_head.rs b/client/rpc-spec-v2/src/chain_head/chain_head.rs index c55625e99c..c0ba9e165d 100644 --- a/client/rpc-spec-v2/src/chain_head/chain_head.rs +++ b/client/rpc-spec-v2/src/chain_head/chain_head.rs @@ -610,7 +610,7 @@ where } self.client - .header(BlockId::Hash(hash)) + .header(hash) .map(|opt_header| opt_header.map(|h| format!("0x{:?}", HexDisplay::from(&h.encode())))) .map_err(ChainHeadRpcError::FetchBlockHeader) .map_err(Into::into) diff --git a/client/rpc/src/chain/chain_full.rs b/client/rpc/src/chain/chain_full.rs index 375e724a33..ee7870e062 100644 --- a/client/rpc/src/chain/chain_full.rs +++ b/client/rpc/src/chain/chain_full.rs @@ -62,7 +62,7 @@ where } fn header(&self, hash: Option) -> Result, Error> { - self.client.header(BlockId::Hash(self.unwrap_or_best(hash))).map_err(client_err) + self.client.header(self.unwrap_or_best(hash)).map_err(client_err) } fn block(&self, hash: Option) -> Result>, Error> { @@ -130,7 +130,7 @@ fn subscribe_headers( { // send current head right at the start. let maybe_header = client - .header(BlockId::Hash(best_block_hash())) + .header(best_block_hash()) .map_err(client_err) .and_then(|header| header.ok_or_else(|| Error::Other("Best header missing.".into()))) .map_err(|e| log::warn!("Best header error {:?}", e)) diff --git a/client/rpc/src/chain/mod.rs b/client/rpc/src/chain/mod.rs index be06b91ca7..16e69c0f63 100644 --- a/client/rpc/src/chain/mod.rs +++ b/client/rpc/src/chain/mod.rs @@ -31,8 +31,8 @@ use jsonrpsee::{core::RpcResult, types::SubscriptionResult, SubscriptionSink}; use sc_client_api::BlockchainEvents; use sp_rpc::{list::ListOrValue, number::NumberOrHex}; use sp_runtime::{ - generic::{BlockId, SignedBlock}, - traits::{Block as BlockT, Header, NumberFor}, + generic::SignedBlock, + traits::{Block as BlockT, NumberFor}, }; use self::error::Error; @@ -80,11 +80,7 @@ where )) })?; let block_num = >::from(block_num); - Ok(self - .client() - .header(BlockId::number(block_num)) - .map_err(client_err)? - .map(|h| h.hash())) + self.client().hash(block_num).map_err(client_err) }, } } diff --git a/client/rpc/src/dev/mod.rs b/client/rpc/src/dev/mod.rs index 7f4b68f56f..ec9e61678f 100644 --- a/client/rpc/src/dev/mod.rs +++ b/client/rpc/src/dev/mod.rs @@ -87,7 +87,7 @@ where let parent_hash = *block.header().parent_hash(); let parent_header = self .client - .header(BlockId::Hash(parent_hash)) + .header(parent_hash) .map_err(|e| Error::BlockQueryError(Box::new(e)))?; if let Some(header) = parent_header { header diff --git a/client/service/src/builder.rs b/client/service/src/builder.rs index 64a5f70cc0..0a26c00485 100644 --- a/client/service/src/builder.rs +++ b/client/service/src/builder.rs @@ -70,10 +70,7 @@ use sp_consensus::block_validation::{ }; use sp_core::traits::{CodeExecutor, SpawnNamed}; use sp_keystore::{CryptoStore, SyncCryptoStore, SyncCryptoStorePtr}; -use sp_runtime::{ - generic::BlockId, - traits::{Block as BlockT, BlockIdTo, NumberFor, Zero}, -}; +use sp_runtime::traits::{Block as BlockT, BlockIdTo, NumberFor, Zero}; use std::{str::FromStr, sync::Arc, time::SystemTime}; /// Full client type. @@ -479,7 +476,7 @@ where sp_session::generate_initial_session_keys( client.clone(), - &BlockId::Hash(chain_info.best_hash), + chain_info.best_hash, config.dev_key_seed.clone().map(|s| vec![s]).unwrap_or_default(), ) .map_err(|e| Error::Application(Box::new(e)))?; diff --git a/client/service/src/client/client.rs b/client/service/src/client/client.rs index f81586d424..bf25e803f5 100644 --- a/client/service/src/client/client.rs +++ b/client/service/src/client/client.rs @@ -914,7 +914,7 @@ where let header = self .backend .blockchain() - .header(BlockId::Hash(block))? + .header(block)? .expect("Block to finalize expected to be onchain; qed"); operation.notify_finalized = Some(FinalizeSummary { header, finalized, stale_heads }); @@ -1050,9 +1050,9 @@ where /// Get block header by id. pub fn header( &self, - id: &BlockId, + hash: Block::Hash, ) -> sp_blockchain::Result::Header>> { - self.backend.blockchain().header(*id) + self.backend.blockchain().header(hash) } /// Get block body by id. @@ -1069,11 +1069,11 @@ where target_hash: Block::Hash, max_generation: NumberFor, ) -> sp_blockchain::Result> { - let load_header = |id: Block::Hash| -> sp_blockchain::Result { + let load_header = |hash: Block::Hash| -> sp_blockchain::Result { self.backend .blockchain() - .header(BlockId::Hash(id))? - .ok_or_else(|| Error::UnknownBlock(format!("{:?}", id))) + .header(hash)? + .ok_or_else(|| Error::UnknownBlock(format!("{:?}", hash))) }; let genesis_hash = self.backend.blockchain().info().genesis_hash; @@ -1552,7 +1552,7 @@ where ) -> sp_blockchain::Result> { Ok(Client::uncles(self, target_hash, max_generation)? .into_iter() - .filter_map(|hash| Client::header(self, &BlockId::Hash(hash)).unwrap_or(None)) + .filter_map(|hash| Client::header(self, hash).unwrap_or(None)) .collect()) } } @@ -1564,8 +1564,8 @@ where Block: BlockT, RA: Send + Sync, { - fn header(&self, id: BlockId) -> sp_blockchain::Result> { - self.backend.blockchain().header(id) + fn header(&self, hash: Block::Hash) -> sp_blockchain::Result> { + self.backend.blockchain().header(hash) } fn info(&self) -> blockchain::Info { @@ -1616,8 +1616,8 @@ where Block: BlockT, RA: Send + Sync, { - fn header(&self, id: BlockId) -> sp_blockchain::Result> { - self.backend.blockchain().header(id) + fn header(&self, hash: Block::Hash) -> sp_blockchain::Result> { + self.backend.blockchain().header(hash) } fn info(&self) -> blockchain::Info { @@ -1948,15 +1948,13 @@ where } fn block(&self, id: &BlockId) -> sp_blockchain::Result>> { - Ok(match self.header(id)? { - Some(header) => { - let hash = header.hash(); - match (self.body(hash)?, self.justifications(hash)?) { - (Some(extrinsics), justifications) => + Ok(match self.backend.blockchain().block_hash_from_id(id)? { + Some(hash) => + match (self.header(hash)?, self.body(hash)?, self.justifications(hash)?) { + (Some(header), Some(extrinsics), justifications) => Some(SignedBlock { block: Block::new(header, extrinsics), justifications }), _ => None, - } - }, + }, None => None, }) } diff --git a/client/service/test/src/client/mod.rs b/client/service/test/src/client/mod.rs index e0b07b39cd..661cf83fc4 100644 --- a/client/service/test/src/client/mod.rs +++ b/client/service/test/src/client/mod.rs @@ -1141,13 +1141,22 @@ fn get_block_by_bad_block_hash_returns_none() { } #[test] -fn get_header_by_block_number_doesnt_panic() { +fn expect_block_hash_by_block_number_doesnt_panic() { let client = substrate_test_runtime_client::new(); // backend uses u32 for block numbers, make sure we don't panic when // trying to convert let id = BlockId::::Number(72340207214430721); - client.header(&id).expect_err("invalid block number overflows u32"); + client.block_hash_from_id(&id).expect_err("invalid block number overflows u32"); +} + +#[test] +fn get_hash_by_block_number_doesnt_panic() { + let client = substrate_test_runtime_client::new(); + + // backend uses u32 for block numbers, make sure we don't panic when + // trying to convert + client.hash(72340207214430721).expect_err("invalid block number overflows u32"); } #[test] diff --git a/client/sync-state-rpc/src/lib.rs b/client/sync-state-rpc/src/lib.rs index 9540d94c57..9edc2ceecb 100644 --- a/client/sync-state-rpc/src/lib.rs +++ b/client/sync-state-rpc/src/lib.rs @@ -48,10 +48,7 @@ use jsonrpsee::{ }; use sc_client_api::StorageData; use sp_blockchain::HeaderBackend; -use sp_runtime::{ - generic::BlockId, - traits::{Block as BlockT, NumberFor}, -}; +use sp_runtime::traits::{Block as BlockT, NumberFor}; use std::sync::Arc; type SharedAuthoritySet = @@ -164,7 +161,7 @@ where let finalized_hash = self.client.info().finalized_hash; let finalized_header = self .client - .header(BlockId::Hash(finalized_hash))? + .header(finalized_hash)? .ok_or_else(|| sp_blockchain::Error::MissingHeader(finalized_hash.to_string()))?; let finalized_block_weight = diff --git a/client/tracing/src/block/mod.rs b/client/tracing/src/block/mod.rs index 63fd1de374..89c0945e0e 100644 --- a/client/tracing/src/block/mod.rs +++ b/client/tracing/src/block/mod.rs @@ -217,10 +217,9 @@ where pub fn trace_block(&self) -> TraceBlockResult { tracing::debug!(target: "state_tracing", "Tracing block: {}", self.block); // Prepare the block - let id = BlockId::Hash(self.block); let mut header = self .client - .header(id) + .header(self.block) .map_err(Error::InvalidBlockId)? .ok_or_else(|| Error::MissingBlockComponent("Header not found".to_string()))?; let extrinsics = self @@ -298,7 +297,7 @@ where }) } else { TraceBlockResponse::BlockTrace(BlockTrace { - block_hash: block_id_as_string(id), + block_hash: block_id_as_string(BlockId::::Hash(self.block)), parent_hash: block_id_as_string(parent_id), tracing_targets: targets.to_string(), storage_keys: self.storage_keys.clone().unwrap_or_default(), diff --git a/client/transaction-pool/benches/basics.rs b/client/transaction-pool/benches/basics.rs index 602e84b477..97d8edddff 100644 --- a/client/transaction-pool/benches/basics.rs +++ b/client/transaction-pool/benches/basics.rs @@ -117,7 +117,7 @@ impl ChainApi for TestApi { fn block_header( &self, - _: &BlockId, + _: ::Hash, ) -> Result::Header>, Self::Error> { Ok(None) } diff --git a/client/transaction-pool/src/api.rs b/client/transaction-pool/src/api.rs index c3f9b50f94..a2b317cfb3 100644 --- a/client/transaction-pool/src/api.rs +++ b/client/transaction-pool/src/api.rs @@ -190,9 +190,9 @@ where fn block_header( &self, - at: &BlockId, + hash: ::Hash, ) -> Result::Header>, Self::Error> { - self.client.header(*at).map_err(Into::into) + self.client.header(hash).map_err(Into::into) } fn tree_route( diff --git a/client/transaction-pool/src/graph/pool.rs b/client/transaction-pool/src/graph/pool.rs index 9c747ade12..96cbeb9b80 100644 --- a/client/transaction-pool/src/graph/pool.rs +++ b/client/transaction-pool/src/graph/pool.rs @@ -96,7 +96,7 @@ pub trait ChainApi: Send + Sync { /// Returns a block header given the block id. fn block_header( &self, - at: &BlockId, + at: ::Hash, ) -> Result::Header>, Self::Error>; /// Compute a tree-route between two blocks. See [`TreeRoute`] for more details. diff --git a/client/transaction-pool/src/lib.rs b/client/transaction-pool/src/lib.rs index 0124038b75..f3797b180f 100644 --- a/client/transaction-pool/src/lib.rs +++ b/client/transaction-pool/src/lib.rs @@ -556,7 +556,7 @@ async fn prune_known_txs_for_block h, Ok(None) => { log::debug!(target: "txpool", "Could not find header for {:?}.", block_hash); diff --git a/client/transaction-pool/src/tests.rs b/client/transaction-pool/src/tests.rs index d8732077eb..d399367eae 100644 --- a/client/transaction-pool/src/tests.rs +++ b/client/transaction-pool/src/tests.rs @@ -170,7 +170,7 @@ impl ChainApi for TestApi { fn block_header( &self, - _: &BlockId, + _: ::Hash, ) -> Result::Header>, Self::Error> { Ok(None) } diff --git a/primitives/blockchain/src/backend.rs b/primitives/blockchain/src/backend.rs index 33edc56d4b..6199fd25d0 100644 --- a/primitives/blockchain/src/backend.rs +++ b/primitives/blockchain/src/backend.rs @@ -33,7 +33,7 @@ use crate::error::{Error, Result}; /// Blockchain database header backend. Does not perform any validation. pub trait HeaderBackend: Send + Sync { /// Get block header. Returns `None` if block is not found. - fn header(&self, id: BlockId) -> Result>; + fn header(&self, hash: Block::Hash) -> Result>; /// Get blockchain info. fn info(&self) -> Info; /// Get block status. @@ -63,9 +63,9 @@ pub trait HeaderBackend: Send + Sync { } /// Get block header. Returns `UnknownBlock` error if block is not found. - fn expect_header(&self, id: BlockId) -> Result { - self.header(id)? - .ok_or_else(|| Error::UnknownBlock(format!("Expect header: {}", id))) + fn expect_header(&self, hash: Block::Hash) -> Result { + self.header(hash)? + .ok_or_else(|| Error::UnknownBlock(format!("Expect header: {}", hash))) } /// Convert an arbitrary block ID into a block number. Returns `UnknownBlock` error if block is @@ -201,7 +201,7 @@ pub trait Backend: import_lock: &RwLock<()>, ) -> Result> { let target_header = { - match self.header(BlockId::Hash(target_hash))? { + match self.header(target_hash)? { Some(x) => x, // target not in blockchain None => return Ok(None), @@ -256,7 +256,7 @@ pub trait Backend: if let Some(max_number) = maybe_max_number { loop { let current_header = self - .header(BlockId::Hash(current_hash))? + .header(current_hash)? .ok_or_else(|| Error::MissingHeader(current_hash.to_string()))?; if current_header.number() <= &max_number { @@ -276,7 +276,7 @@ pub trait Backend: } let current_header = self - .header(BlockId::Hash(current_hash))? + .header(current_hash)? .ok_or_else(|| Error::MissingHeader(current_hash.to_string()))?; // stop search in this chain once we go below the target's block number diff --git a/primitives/session/src/lib.rs b/primitives/session/src/lib.rs index dde262738a..9b403409c9 100644 --- a/primitives/session/src/lib.rs +++ b/primitives/session/src/lib.rs @@ -110,7 +110,7 @@ impl GetValidatorCount for MembershipProof { #[cfg(feature = "std")] pub fn generate_initial_session_keys( client: std::sync::Arc, - at: &BlockId, + at: Block::Hash, seeds: Vec, ) -> Result<(), sp_api::ApiError> where @@ -125,7 +125,7 @@ where let runtime_api = client.runtime_api(); for seed in seeds { - runtime_api.generate_session_keys(at, Some(seed.as_bytes().to_vec()))?; + runtime_api.generate_session_keys(&BlockId::Hash(at), Some(seed.as_bytes().to_vec()))?; } Ok(()) diff --git a/test-utils/runtime/client/src/trait_tests.rs b/test-utils/runtime/client/src/trait_tests.rs index 65aa3e65e6..19772c8c94 100644 --- a/test-utils/runtime/client/src/trait_tests.rs +++ b/test-utils/runtime/client/src/trait_tests.rs @@ -430,21 +430,10 @@ where let genesis_hash = client.chain_info().genesis_hash; - assert_eq!(blockchain.header(BlockId::Number(0)).unwrap().unwrap().hash(), genesis_hash); assert_eq!(blockchain.hash(0).unwrap().unwrap(), genesis_hash); - - assert_eq!(blockchain.header(BlockId::Number(1)).unwrap().unwrap().hash(), a1.hash()); assert_eq!(blockchain.hash(1).unwrap().unwrap(), a1.hash()); - - assert_eq!(blockchain.header(BlockId::Number(2)).unwrap().unwrap().hash(), a2.hash()); assert_eq!(blockchain.hash(2).unwrap().unwrap(), a2.hash()); - - assert_eq!(blockchain.header(BlockId::Number(3)).unwrap().unwrap().hash(), a3.hash()); assert_eq!(blockchain.hash(3).unwrap().unwrap(), a3.hash()); - - assert_eq!(blockchain.header(BlockId::Number(4)).unwrap().unwrap().hash(), a4.hash()); assert_eq!(blockchain.hash(4).unwrap().unwrap(), a4.hash()); - - assert_eq!(blockchain.header(BlockId::Number(5)).unwrap().unwrap().hash(), a5.hash()); assert_eq!(blockchain.hash(5).unwrap().unwrap(), a5.hash()); } diff --git a/test-utils/runtime/transaction-pool/src/lib.rs b/test-utils/runtime/transaction-pool/src/lib.rs index f8d551a6fa..aadd83c00e 100644 --- a/test-utils/runtime/transaction-pool/src/lib.rs +++ b/test-utils/runtime/transaction-pool/src/lib.rs @@ -326,14 +326,9 @@ impl sc_transaction_pool::ChainApi for TestApi { fn block_header( &self, - at: &BlockId, + hash: ::Hash, ) -> Result::Header>, Self::Error> { - Ok(match at { - BlockId::Number(num) => - self.chain.read().block_by_number.get(num).map(|b| b[0].0.header().clone()), - BlockId::Hash(hash) => - self.chain.read().block_by_hash.get(hash).map(|b| b.header().clone()), - }) + Ok(self.chain.read().block_by_hash.get(&hash).map(|b| b.header().clone())) } fn tree_route( diff --git a/utils/frame/benchmarking-cli/src/storage/write.rs b/utils/frame/benchmarking-cli/src/storage/write.rs index 55a7b60d55..1917bc5f8f 100644 --- a/utils/frame/benchmarking-cli/src/storage/write.rs +++ b/utils/frame/benchmarking-cli/src/storage/write.rs @@ -21,10 +21,7 @@ use sc_client_db::{DbHash, DbState, DbStateBuilder}; use sp_api::StateBackend; use sp_blockchain::HeaderBackend; use sp_database::{ColumnId, Transaction}; -use sp_runtime::{ - generic::BlockId, - traits::{Block as BlockT, HashFor, Header as HeaderT}, -}; +use sp_runtime::traits::{Block as BlockT, HashFor, Header as HeaderT}; use sp_trie::PrefixedMemoryDB; use log::{info, trace}; @@ -58,7 +55,7 @@ impl StorageCmd { let mut record = BenchRecord::default(); let best_hash = client.usage_info().chain.best_hash; - let header = client.header(BlockId::Hash(best_hash))?.ok_or("Header not found")?; + let header = client.header(best_hash)?.ok_or("Header not found")?; let original_root = *header.state_root(); let trie = DbStateBuilder::::new(storage.clone(), original_root).build(); From a8b46dacd77e7beffeb0cf91c61fde9ea0c18d2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulo=20Silva=20de=20Souza?= <77391175+joao-paulo-parity@users.noreply.github.com> Date: Tue, 20 Dec 2022 16:56:27 -0300 Subject: [PATCH 060/127] remove hardcoded crate versions from tests (#12950) --- frame/support/test/tests/construct_runtime.rs | 31 +++++----- frame/support/test/tests/pallet_instance.rs | 57 +++++++------------ 2 files changed, 35 insertions(+), 53 deletions(-) diff --git a/frame/support/test/tests/construct_runtime.rs b/frame/support/test/tests/construct_runtime.rs index b1ace12936..18e4af1ac3 100644 --- a/frame/support/test/tests/construct_runtime.rs +++ b/frame/support/test/tests/construct_runtime.rs @@ -22,10 +22,7 @@ #![recursion_limit = "128"] use codec::MaxEncodedLen; -use frame_support::{ - parameter_types, - traits::{CrateVersion, PalletInfo as _}, -}; +use frame_support::{parameter_types, traits::PalletInfo as _}; use scale_info::TypeInfo; use sp_core::{sr25519, H256}; use sp_runtime::{ @@ -733,65 +730,65 @@ fn pallet_in_runtime_is_correct() { assert_eq!(PalletInfo::index::().unwrap(), 30); assert_eq!(PalletInfo::name::().unwrap(), "System"); assert_eq!(PalletInfo::module_name::().unwrap(), "system"); - assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); + assert!(PalletInfo::crate_version::().is_some()); assert_eq!(PalletInfo::index::().unwrap(), 31); assert_eq!(PalletInfo::name::().unwrap(), "Module1_1"); assert_eq!(PalletInfo::module_name::().unwrap(), "module1"); - assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); + assert!(PalletInfo::crate_version::().is_some()); assert_eq!(PalletInfo::index::().unwrap(), 32); assert_eq!(PalletInfo::name::().unwrap(), "Module2"); assert_eq!(PalletInfo::module_name::().unwrap(), "module2"); - assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); + assert!(PalletInfo::crate_version::().is_some()); assert_eq!(PalletInfo::index::().unwrap(), 33); assert_eq!(PalletInfo::name::().unwrap(), "Module1_2"); assert_eq!(PalletInfo::module_name::().unwrap(), "module1"); - assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); + assert!(PalletInfo::crate_version::().is_some()); assert_eq!(PalletInfo::index::().unwrap(), 34); assert_eq!(PalletInfo::name::().unwrap(), "NestedModule3"); assert_eq!(PalletInfo::module_name::().unwrap(), "nested::module3"); - assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); + assert!(PalletInfo::crate_version::().is_some()); assert_eq!(PalletInfo::index::().unwrap(), 35); assert_eq!(PalletInfo::name::().unwrap(), "Module3"); assert_eq!(PalletInfo::module_name::().unwrap(), "self::module3"); - assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); + assert!(PalletInfo::crate_version::().is_some()); assert_eq!(PalletInfo::index::().unwrap(), 6); assert_eq!(PalletInfo::name::().unwrap(), "Module1_3"); assert_eq!(PalletInfo::module_name::().unwrap(), "module1"); - assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); + assert!(PalletInfo::crate_version::().is_some()); assert_eq!(PalletInfo::index::().unwrap(), 3); assert_eq!(PalletInfo::name::().unwrap(), "Module1_4"); assert_eq!(PalletInfo::module_name::().unwrap(), "module1"); - assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); + assert!(PalletInfo::crate_version::().is_some()); assert_eq!(PalletInfo::index::().unwrap(), 4); assert_eq!(PalletInfo::name::().unwrap(), "Module1_5"); assert_eq!(PalletInfo::module_name::().unwrap(), "module1"); - assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); + assert!(PalletInfo::crate_version::().is_some()); assert_eq!(PalletInfo::index::().unwrap(), 1); assert_eq!(PalletInfo::name::().unwrap(), "Module1_6"); assert_eq!(PalletInfo::module_name::().unwrap(), "module1"); - assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); + assert!(PalletInfo::crate_version::().is_some()); assert_eq!(PalletInfo::index::().unwrap(), 2); assert_eq!(PalletInfo::name::().unwrap(), "Module1_7"); assert_eq!(PalletInfo::module_name::().unwrap(), "module1"); - assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); + assert!(PalletInfo::crate_version::().is_some()); assert_eq!(PalletInfo::index::().unwrap(), 12); assert_eq!(PalletInfo::name::().unwrap(), "Module1_8"); assert_eq!(PalletInfo::module_name::().unwrap(), "module1"); - assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); + assert!(PalletInfo::crate_version::().is_some()); assert_eq!(PalletInfo::index::().unwrap(), 13); assert_eq!(PalletInfo::name::().unwrap(), "Module1_9"); assert_eq!(PalletInfo::module_name::().unwrap(), "module1"); - assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); + assert!(PalletInfo::crate_version::().is_some()); } diff --git a/frame/support/test/tests/pallet_instance.rs b/frame/support/test/tests/pallet_instance.rs index d8ad13ceda..7fec0aa9d0 100644 --- a/frame/support/test/tests/pallet_instance.rs +++ b/frame/support/test/tests/pallet_instance.rs @@ -619,44 +619,29 @@ fn storage_expand() { #[test] fn pallet_metadata_expands() { - use frame_support::traits::{CrateVersion, PalletInfoData, PalletsInfoAccess}; + use frame_support::traits::PalletsInfoAccess; let mut infos = AllPalletsWithSystem::infos(); infos.sort_by_key(|x| x.index); - assert_eq!( - infos, - vec![ - PalletInfoData { - index: 0, - name: "System", - module_name: "frame_system", - crate_version: CrateVersion { major: 4, minor: 0, patch: 0 }, - }, - PalletInfoData { - index: 1, - name: "Example", - module_name: "pallet", - crate_version: CrateVersion { major: 3, minor: 0, patch: 0 }, - }, - PalletInfoData { - index: 2, - name: "Instance1Example", - module_name: "pallet", - crate_version: CrateVersion { major: 3, minor: 0, patch: 0 }, - }, - PalletInfoData { - index: 3, - name: "Example2", - module_name: "pallet2", - crate_version: CrateVersion { major: 3, minor: 0, patch: 0 }, - }, - PalletInfoData { - index: 4, - name: "Instance1Example2", - module_name: "pallet2", - crate_version: CrateVersion { major: 3, minor: 0, patch: 0 }, - }, - ] - ); + + assert_eq!(infos[0].index, 0); + assert_eq!(infos[0].name, "System"); + assert_eq!(infos[0].module_name, "frame_system"); + + assert_eq!(infos[1].index, 1); + assert_eq!(infos[1].name, "Example"); + assert_eq!(infos[1].module_name, "pallet"); + + assert_eq!(infos[2].index, 2); + assert_eq!(infos[2].name, "Instance1Example"); + assert_eq!(infos[2].module_name, "pallet"); + + assert_eq!(infos[3].index, 3); + assert_eq!(infos[3].name, "Example2"); + assert_eq!(infos[3].module_name, "pallet2"); + + assert_eq!(infos[4].index, 4); + assert_eq!(infos[4].name, "Instance1Example2"); + assert_eq!(infos[4].module_name, "pallet2"); } #[test] From ce025f3a989d165225c0f2f7ab497cf53319dd6d Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Wed, 21 Dec 2022 11:25:16 +0100 Subject: [PATCH 061/127] BlockId removal: refactor: HeaderBackend::status (#12981) It changes the arguments of `HeaderBackend::status` method from: `BlockId` to: `Block::Hash` This PR is part of BlockId::Number refactoring analysis (paritytech/substrate#11292) Co-authored-by: parity-processbot <> --- client/api/src/in_mem.rs | 4 ++-- client/authority-discovery/src/worker/tests.rs | 2 +- client/consensus/babe/src/lib.rs | 2 +- client/db/src/lib.rs | 10 +++------- client/finality-grandpa/src/import.rs | 2 +- client/merkle-mountain-range/src/test_utils.rs | 4 ++-- client/service/src/client/client.rs | 14 +++++++------- primitives/blockchain/src/backend.rs | 2 +- 8 files changed, 18 insertions(+), 22 deletions(-) diff --git a/client/api/src/in_mem.rs b/client/api/src/in_mem.rs index 58add08707..144aa352f5 100644 --- a/client/api/src/in_mem.rs +++ b/client/api/src/in_mem.rs @@ -359,8 +359,8 @@ impl HeaderBackend for Blockchain { } } - fn status(&self, id: BlockId) -> sp_blockchain::Result { - match self.id(id).map_or(false, |hash| self.storage.read().blocks.contains_key(&hash)) { + fn status(&self, hash: Block::Hash) -> sp_blockchain::Result { + match self.storage.read().blocks.contains_key(&hash) { true => Ok(BlockStatus::InChain), false => Ok(BlockStatus::Unknown), } diff --git a/client/authority-discovery/src/worker/tests.rs b/client/authority-discovery/src/worker/tests.rs index ea0c16eca8..ce55728a1b 100644 --- a/client/authority-discovery/src/worker/tests.rs +++ b/client/authority-discovery/src/worker/tests.rs @@ -78,7 +78,7 @@ impl HeaderBackend for TestApi { fn status( &self, - _id: BlockId, + _hash: Block::Hash, ) -> std::result::Result { Ok(sc_client_api::blockchain::BlockStatus::Unknown) } diff --git a/client/consensus/babe/src/lib.rs b/client/consensus/babe/src/lib.rs index 9134da0b83..1d5d4b5fe5 100644 --- a/client/consensus/babe/src/lib.rs +++ b/client/consensus/babe/src/lib.rs @@ -1402,7 +1402,7 @@ where // early exit if block already in chain, otherwise the check for // epoch changes will error when trying to re-import an epoch change - match self.client.status(BlockId::Hash(hash)) { + match self.client.status(hash) { Ok(sp_blockchain::BlockStatus::InChain) => { // When re-importing existing block strip away intermediates. let _ = block.remove_intermediate::>(INTERMEDIATE_KEY); diff --git a/client/db/src/lib.rs b/client/db/src/lib.rs index b1f5a6edcd..3e6332732d 100644 --- a/client/db/src/lib.rs +++ b/client/db/src/lib.rs @@ -553,12 +553,8 @@ impl sc_client_api::blockchain::HeaderBackend for Blockcha } } - fn status(&self, id: BlockId) -> ClientResult { - let exists = match id { - BlockId::Hash(hash) => self.header(hash)?.is_some(), - BlockId::Number(n) => n <= self.meta.read().best_number, - }; - match exists { + fn status(&self, hash: Block::Hash) -> ClientResult { + match self.header(hash)?.is_some() { true => Ok(sc_client_api::blockchain::BlockStatus::InChain), false => Ok(sc_client_api::blockchain::BlockStatus::Unknown), } @@ -1223,7 +1219,7 @@ impl Backend { } let parent_exists = - self.blockchain.status(BlockId::Hash(route_to))? == sp_blockchain::BlockStatus::InChain; + self.blockchain.status(route_to)? == sp_blockchain::BlockStatus::InChain; // Cannot find tree route with empty DB or when imported a detached block. if meta.best_hash != Default::default() && parent_exists { diff --git a/client/finality-grandpa/src/import.rs b/client/finality-grandpa/src/import.rs index 1359110132..ed6e4a0fa2 100644 --- a/client/finality-grandpa/src/import.rs +++ b/client/finality-grandpa/src/import.rs @@ -534,7 +534,7 @@ where // early exit if block already in chain, otherwise the check for // authority changes will error when trying to re-import a change block - match self.inner.status(BlockId::Hash(hash)) { + match self.inner.status(hash) { Ok(BlockStatus::InChain) => { // Strip justifications when re-importing an existing block. let _justifications = block.justifications.take(); diff --git a/client/merkle-mountain-range/src/test_utils.rs b/client/merkle-mountain-range/src/test_utils.rs index 1bc7a9bd5b..39570e0d23 100644 --- a/client/merkle-mountain-range/src/test_utils.rs +++ b/client/merkle-mountain-range/src/test_utils.rs @@ -234,8 +234,8 @@ impl HeaderBackend for MockClient { self.client.lock().info() } - fn status(&self, id: BlockId) -> sc_client_api::blockchain::Result { - self.client.lock().status(id) + fn status(&self, hash: Hash) -> sc_client_api::blockchain::Result { + self.client.lock().status(hash) } fn number(&self, hash: Hash) -> sc_client_api::blockchain::Result> { diff --git a/client/service/src/client/client.rs b/client/service/src/client/client.rs index bf25e803f5..493fd320b7 100644 --- a/client/service/src/client/client.rs +++ b/client/service/src/client/client.rs @@ -552,9 +552,9 @@ where CoreApi + ApiExt, { let parent_hash = *import_headers.post().parent_hash(); - let status = self.backend.blockchain().status(BlockId::Hash(hash))?; - let parent_exists = self.backend.blockchain().status(BlockId::Hash(parent_hash))? == - blockchain::BlockStatus::InChain; + let status = self.backend.blockchain().status(hash)?; + let parent_exists = + self.backend.blockchain().status(parent_hash)? == blockchain::BlockStatus::InChain; match (import_existing, status) { (false, blockchain::BlockStatus::InChain) => return Ok(ImportResult::AlreadyInChain), (false, blockchain::BlockStatus::Unknown) => {}, @@ -1572,8 +1572,8 @@ where self.backend.blockchain().info() } - fn status(&self, id: BlockId) -> sp_blockchain::Result { - self.backend.blockchain().status(id) + fn status(&self, hash: Block::Hash) -> sp_blockchain::Result { + self.backend.blockchain().status(hash) } fn number( @@ -1624,8 +1624,8 @@ where self.backend.blockchain().info() } - fn status(&self, id: BlockId) -> sp_blockchain::Result { - (**self).status(id) + fn status(&self, hash: Block::Hash) -> sp_blockchain::Result { + (**self).status(hash) } fn number( diff --git a/primitives/blockchain/src/backend.rs b/primitives/blockchain/src/backend.rs index 6199fd25d0..ec9c8ac0d5 100644 --- a/primitives/blockchain/src/backend.rs +++ b/primitives/blockchain/src/backend.rs @@ -37,7 +37,7 @@ pub trait HeaderBackend: Send + Sync { /// Get blockchain info. fn info(&self) -> Info; /// Get block status. - fn status(&self, id: BlockId) -> Result; + fn status(&self, hash: Block::Hash) -> Result; /// Get block number by hash. Returns `None` if the header is not in the chain. fn number( &self, From 8e76398fa1f070a1361a77b04269c2c59c08d76a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dino=20Pa=C4=8Dandi?= <3002868+Dinonard@users.noreply.github.com> Date: Wed, 21 Dec 2022 14:03:49 +0100 Subject: [PATCH 062/127] Add CallbackHandle to pallet-assets (#12307) * Add CallbackHandle to pallet-assets * Address review comments * Add use for sp_io::storage * Rebase & review comments * Fix UT Co-authored-by: Hoon Kim --- bin/node/runtime/src/lib.rs | 1 + frame/assets/src/functions.rs | 4 ++- frame/assets/src/lib.rs | 29 +++++++++++++++++- frame/assets/src/mock.rs | 19 +++++++++++- frame/assets/src/tests.rs | 30 +++++++++++++++++++ .../asset-tx-payment/src/tests.rs | 1 + 6 files changed, 81 insertions(+), 3 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 0e3bee8821..e5776e3fd6 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1478,6 +1478,7 @@ impl pallet_assets::Config for Runtime { type StringLimit = StringLimit; type Freezer = (); type Extra = (); + type CallbackHandle = (); type WeightInfo = pallet_assets::weights::SubstrateWeight; type RemoveItemsLimit = ConstU32<1000>; #[cfg(feature = "runtime-benchmarks")] diff --git a/frame/assets/src/functions.rs b/frame/assets/src/functions.rs index f7f11cafec..2cdf2d7c4c 100644 --- a/frame/assets/src/functions.rs +++ b/frame/assets/src/functions.rs @@ -664,7 +664,8 @@ impl, I: 'static> Pallet { status: AssetStatus::Live, }, ); - Self::deposit_event(Event::ForceCreated { asset_id: id, owner }); + Self::deposit_event(Event::ForceCreated { asset_id: id, owner: owner.clone() }); + T::CallbackHandle::created(&id, &owner); Ok(()) } @@ -754,6 +755,7 @@ impl, I: 'static> Pallet { approvals_destroyed: removed_approvals as u32, approvals_remaining: details.approvals as u32, }); + T::CallbackHandle::destroyed(&id); Ok(()) })?; Ok(removed_approvals) diff --git a/frame/assets/src/lib.rs b/frame/assets/src/lib.rs index ab589c0eef..1853ef75f5 100644 --- a/frame/assets/src/lib.rs +++ b/frame/assets/src/lib.rs @@ -116,6 +116,11 @@ //! //! Please refer to the [`Pallet`] struct for details on publicly available functions. //! +//! ### Callbacks +//! +//! Using `CallbackHandle` associated type, user can configure custom callback functions which are +//! executed when new asset is created or an existing asset is destroyed. +//! //! ## Related Modules //! //! * [`System`](../frame_system/index.html) @@ -171,6 +176,18 @@ pub use weights::WeightInfo; type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; +/// Trait with callbacks that are executed after successfull asset creation or destruction. +pub trait AssetsCallback { + /// Indicates that asset with `id` was successfully created by the `owner` + fn created(_id: &AssetId, _owner: &AccountId) {} + + /// Indicates that asset with `id` has just been destroyed + fn destroyed(_id: &AssetId) {} +} + +/// Empty implementation in case no callbacks are required. +impl AssetsCallback for () {} + #[frame_support::pallet] pub mod pallet { use super::*; @@ -283,6 +300,9 @@ pub mod pallet { /// Additional data to be stored with an account's asset balance. type Extra: Member + Parameter + Default + MaxEncodedLen; + /// Callback methods for asset state change (e.g. asset created or destroyed) + type CallbackHandle: AssetsCallback; + /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; @@ -598,7 +618,14 @@ pub mod pallet { status: AssetStatus::Live, }, ); - Self::deposit_event(Event::Created { asset_id: id, creator: owner, owner: admin }); + + Self::deposit_event(Event::Created { + asset_id: id, + creator: owner.clone(), + owner: admin, + }); + T::CallbackHandle::created(&id, &owner); + Ok(()) } diff --git a/frame/assets/src/mock.rs b/frame/assets/src/mock.rs index 06b6ccf06c..93a87ee671 100644 --- a/frame/assets/src/mock.rs +++ b/frame/assets/src/mock.rs @@ -20,11 +20,13 @@ use super::*; use crate as pallet_assets; +use codec::Encode; use frame_support::{ construct_runtime, parameter_types, traits::{AsEnsureOriginWithArg, ConstU32, ConstU64, GenesisBuild}, }; use sp_core::H256; +use sp_io::storage; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup}, @@ -45,6 +47,9 @@ construct_runtime!( } ); +type AccountId = u64; +type AssetId = u32; + impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); @@ -55,7 +60,7 @@ impl frame_system::Config for Test { type BlockNumber = u64; type Hash = H256; type Hashing = BlakeTwo256; - type AccountId = u64; + type AccountId = AccountId; type Lookup = IdentityLookup; type Header = Header; type RuntimeEvent = RuntimeEvent; @@ -84,6 +89,17 @@ impl pallet_balances::Config for Test { type ReserveIdentifier = [u8; 8]; } +pub struct AssetsCallbackHandle; +impl AssetsCallback for AssetsCallbackHandle { + fn created(_id: &AssetId, _owner: &AccountId) { + storage::set(b"asset_created", &().encode()); + } + + fn destroyed(_id: &AssetId) { + storage::set(b"asset_destroyed", &().encode()); + } +} + impl Config for Test { type RuntimeEvent = RuntimeEvent; type Balance = u64; @@ -100,6 +116,7 @@ impl Config for Test { type StringLimit = ConstU32<50>; type Freezer = TestFreezer; type WeightInfo = (); + type CallbackHandle = AssetsCallbackHandle; type Extra = (); type RemoveItemsLimit = ConstU32<5>; #[cfg(feature = "runtime-benchmarks")] diff --git a/frame/assets/src/tests.rs b/frame/assets/src/tests.rs index d5fcece0e9..f7007ff3cc 100644 --- a/frame/assets/src/tests.rs +++ b/frame/assets/src/tests.rs @@ -24,6 +24,7 @@ use frame_support::{ traits::{fungibles::InspectEnumerable, Currency}, }; use pallet_balances::Error as BalancesError; +use sp_io::storage; use sp_runtime::{traits::ConvertInto, TokenError}; fn asset_ids() -> Vec { @@ -1194,3 +1195,32 @@ fn querying_roles_should_work() { assert_eq!(Assets::freezer(0), Some(4)); }); } + +#[test] +fn normal_asset_create_and_destroy_callbacks_should_work() { + new_test_ext().execute_with(|| { + assert!(storage::get(b"asset_created").is_none()); + assert!(storage::get(b"asset_destroyed").is_none()); + + Balances::make_free_balance_be(&1, 100); + assert_ok!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 1)); + assert!(storage::get(b"asset_created").is_some()); + assert!(storage::get(b"asset_destroyed").is_none()); + + assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0)); + assert!(storage::get(b"asset_destroyed").is_some()); + }); +} + +#[test] +fn root_asset_create_should_work() { + new_test_ext().execute_with(|| { + assert!(storage::get(b"asset_created").is_none()); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert!(storage::get(b"asset_created").is_some()); + assert!(storage::get(b"asset_destroyed").is_none()); + }); +} diff --git a/frame/transaction-payment/asset-tx-payment/src/tests.rs b/frame/transaction-payment/asset-tx-payment/src/tests.rs index b70a88d02c..b79b87a93c 100644 --- a/frame/transaction-payment/asset-tx-payment/src/tests.rs +++ b/frame/transaction-payment/asset-tx-payment/src/tests.rs @@ -171,6 +171,7 @@ impl pallet_assets::Config for Runtime { type StringLimit = ConstU32<20>; type Freezer = (); type Extra = (); + type CallbackHandle = (); type WeightInfo = (); type RemoveItemsLimit = ConstU32<1000>; pallet_assets::runtime_benchmarks_enabled! { From 5aaf5f42a7850f00b15a14f635b67061d831ac2d Mon Sep 17 00:00:00 2001 From: Muharem Ismailov Date: Wed, 21 Dec 2022 16:25:46 +0100 Subject: [PATCH 063/127] Scheduler: remove empty agenda on cancel (#12989) * Scheduler: remove empty agenda on cancel * use iter any * fix benches * remove trailing None * Add CleanupAgendas migration Signed-off-by: Oliver Tale-Yazdi * fix ci Signed-off-by: Oliver Tale-Yazdi * Count non-empty agendas in migration Signed-off-by: Oliver Tale-Yazdi Signed-off-by: Oliver Tale-Yazdi Co-authored-by: Oliver Tale-Yazdi --- frame/scheduler/src/benchmarking.rs | 24 ++- frame/scheduler/src/lib.rs | 20 +++ frame/scheduler/src/migration.rs | 171 ++++++++++++++++++++++ frame/scheduler/src/tests.rs | 219 ++++++++++++++++++---------- 4 files changed, 353 insertions(+), 81 deletions(-) diff --git a/frame/scheduler/src/benchmarking.rs b/frame/scheduler/src/benchmarking.rs index e621c913b2..ca98cabc3c 100644 --- a/frame/scheduler/src/benchmarking.rs +++ b/frame/scheduler/src/benchmarking.rs @@ -244,13 +244,17 @@ benchmarks! { }: _>(schedule_origin, when, 0) verify { ensure!( - Lookup::::get(u32_to_name(0)).is_none(), - "didn't remove from lookup" + s == 1 || Lookup::::get(u32_to_name(0)).is_none(), + "didn't remove from lookup if more than 1 task scheduled for `when`" ); // Removed schedule is NONE ensure!( - Agenda::::get(when)[0].is_none(), - "didn't remove from schedule" + s == 1 || Agenda::::get(when)[0].is_none(), + "didn't remove from schedule if more than 1 task scheduled for `when`" + ); + ensure!( + s > 1 || Agenda::::get(when).len() == 0, + "remove from schedule if only 1 task scheduled for `when`" ); } @@ -280,13 +284,17 @@ benchmarks! { }: _(RawOrigin::Root, u32_to_name(0)) verify { ensure!( - Lookup::::get(u32_to_name(0)).is_none(), - "didn't remove from lookup" + s == 1 || Lookup::::get(u32_to_name(0)).is_none(), + "didn't remove from lookup if more than 1 task scheduled for `when`" ); // Removed schedule is NONE ensure!( - Agenda::::get(when)[0].is_none(), - "didn't remove from schedule" + s == 1 || Agenda::::get(when)[0].is_none(), + "didn't remove from schedule if more than 1 task scheduled for `when`" + ); + ensure!( + s > 1 || Agenda::::get(when).len() == 0, + "remove from schedule if only 1 task scheduled for `when`" ); } diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index d6a66c5e2c..20e1420676 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -752,6 +752,22 @@ impl Pallet { Ok(index) } + /// Remove trailing `None` items of an agenda at `when`. If all items are `None` remove the + /// agenda record entirely. + fn cleanup_agenda(when: T::BlockNumber) { + let mut agenda = Agenda::::get(when); + match agenda.iter().rposition(|i| i.is_some()) { + Some(i) if agenda.len() > i + 1 => { + agenda.truncate(i + 1); + Agenda::::insert(when, agenda); + }, + Some(_) => {}, + None => { + Agenda::::remove(when); + }, + } + } + fn do_schedule( when: DispatchTime, maybe_periodic: Option>, @@ -802,6 +818,7 @@ impl Pallet { if let Some(id) = s.maybe_id { Lookup::::remove(id); } + Self::cleanup_agenda(when); Self::deposit_event(Event::Canceled { when, index }); Ok(()) } else { @@ -824,6 +841,7 @@ impl Pallet { ensure!(!matches!(task, Some(Scheduled { maybe_id: Some(_), .. })), Error::::Named); task.take().ok_or(Error::::NotFound) })?; + Self::cleanup_agenda(when); Self::deposit_event(Event::Canceled { when, index }); Self::place_task(new_time, task).map_err(|x| x.0) @@ -880,6 +898,7 @@ impl Pallet { } Ok(()) })?; + Self::cleanup_agenda(when); Self::deposit_event(Event::Canceled { when, index }); Ok(()) } else { @@ -905,6 +924,7 @@ impl Pallet { let task = agenda.get_mut(index as usize).ok_or(Error::::NotFound)?; task.take().ok_or(Error::::NotFound) })?; + Self::cleanup_agenda(when); Self::deposit_event(Event::Canceled { when, index }); Self::place_task(new_time, task).map_err(|x| x.0) } diff --git a/frame/scheduler/src/migration.rs b/frame/scheduler/src/migration.rs index 6769d20023..97a5ef4c3e 100644 --- a/frame/scheduler/src/migration.rs +++ b/frame/scheduler/src/migration.rs @@ -198,6 +198,119 @@ pub mod v3 { } } +mod v4 { + use super::*; + use frame_support::pallet_prelude::*; + + /// This migration cleans up empty agendas of the V4 scheduler. + /// + /// This should be run on a scheduler that does not have + /// since it piles up `None`-only agendas. This does not modify the pallet version. + pub struct CleanupAgendas(sp_std::marker::PhantomData); + + impl OnRuntimeUpgrade for CleanupAgendas { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, &'static str> { + assert_eq!( + StorageVersion::get::>(), + 4, + "Can only cleanup agendas of the V4 scheduler" + ); + + let agendas = Agenda::::iter_keys().count(); + let non_empty_agendas = + Agenda::::iter_values().filter(|a| a.iter().any(|s| s.is_some())).count(); + log::info!( + target: TARGET, + "There are {} total and {} non-empty agendas", + agendas, + non_empty_agendas + ); + + Ok((agendas as u32, non_empty_agendas as u32).encode()) + } + + fn on_runtime_upgrade() -> Weight { + let version = StorageVersion::get::>(); + if version != 4 { + log::warn!(target: TARGET, "Skipping CleanupAgendas migration since it was run on the wrong version: {:?} != 4", version); + return T::DbWeight::get().reads(1) + } + + let keys = Agenda::::iter_keys().collect::>(); + let mut writes = 0; + for k in &keys { + let mut schedules = Agenda::::get(k); + let all_schedules = schedules.len(); + let suffix_none_schedules = + schedules.iter().rev().take_while(|s| s.is_none()).count(); + + match all_schedules.checked_sub(suffix_none_schedules) { + Some(0) => { + log::info!( + "Deleting None-only agenda {:?} with {} entries", + k, + all_schedules + ); + Agenda::::remove(k); + writes.saturating_inc(); + }, + Some(ne) if ne > 0 => { + log::info!( + "Removing {} schedules of {} from agenda {:?}, now {:?}", + suffix_none_schedules, + all_schedules, + ne, + k + ); + schedules.truncate(ne); + Agenda::::insert(k, schedules); + writes.saturating_inc(); + }, + Some(_) => { + frame_support::defensive!( + // Bad but let's not panic. + "Cannot have more None suffix schedules that schedules in total" + ); + }, + None => { + log::info!("Agenda {:?} does not have any None suffix schedules", k); + }, + } + } + + // We don't modify the pallet version. + + T::DbWeight::get().reads_writes(1 + keys.len().saturating_mul(2) as u64, writes) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(state: Vec) -> Result<(), &'static str> { + assert_eq!(StorageVersion::get::>(), 4, "Version must not change"); + + let (old_agendas, non_empty_agendas): (u32, u32) = + Decode::decode(&mut state.as_ref()).expect("Must decode pre_upgrade state"); + let new_agendas = Agenda::::iter_keys().count() as u32; + + match old_agendas.checked_sub(new_agendas) { + Some(0) => log::warn!( + target: TARGET, + "Did not clean up any agendas. v4::CleanupAgendas can be removed." + ), + Some(n) => + log::info!(target: TARGET, "Cleaned up {} agendas, now {}", n, new_agendas), + None => unreachable!( + "Number of agendas cannot increase, old {} new {}", + old_agendas, new_agendas + ), + } + assert_eq!(new_agendas, non_empty_agendas, "Expected to keep all non-empty agendas"); + + Ok(()) + } + } +} + #[cfg(test)] #[cfg(feature = "try-runtime")] mod test { @@ -396,6 +509,64 @@ mod test { }); } + #[test] + fn cleanup_agendas_works() { + use sp_core::bounded_vec; + new_test_ext().execute_with(|| { + StorageVersion::new(4).put::(); + + let call = RuntimeCall::System(frame_system::Call::remark { remark: vec![] }); + let bounded_call = Preimage::bound(call).unwrap(); + let some = Some(ScheduledOf:: { + maybe_id: None, + priority: 1, + call: bounded_call, + maybe_periodic: None, + origin: root(), + _phantom: Default::default(), + }); + + // Put some empty, and some non-empty agendas in there. + let test_data: Vec<( + BoundedVec>, ::MaxScheduledPerBlock>, + Option< + BoundedVec>, ::MaxScheduledPerBlock>, + >, + )> = vec![ + (bounded_vec![some.clone()], Some(bounded_vec![some.clone()])), + (bounded_vec![None, some.clone()], Some(bounded_vec![None, some.clone()])), + (bounded_vec![None, some.clone(), None], Some(bounded_vec![None, some.clone()])), + (bounded_vec![some.clone(), None, None], Some(bounded_vec![some.clone()])), + (bounded_vec![None, None], None), + (bounded_vec![None, None, None], None), + (bounded_vec![], None), + ]; + + // Insert all the agendas. + for (i, test) in test_data.iter().enumerate() { + Agenda::::insert(i as u64, test.0.clone()); + } + + // Run the migration. + let data = v4::CleanupAgendas::::pre_upgrade().unwrap(); + let _w = v4::CleanupAgendas::::on_runtime_upgrade(); + v4::CleanupAgendas::::post_upgrade(data).unwrap(); + + // Check that the post-state is correct. + for (i, test) in test_data.iter().enumerate() { + match test.1.clone() { + None => assert!( + !Agenda::::contains_key(i as u64), + "Agenda {} should be removed", + i + ), + Some(new) => + assert_eq!(Agenda::::get(i as u64), new, "Agenda wrong {}", i), + } + } + }); + } + fn signed(i: u64) -> OriginCaller { system::RawOrigin::Signed(i).into() } diff --git a/frame/scheduler/src/tests.rs b/frame/scheduler/src/tests.rs index 033d787946..7c261fdd74 100644 --- a/frame/scheduler/src/tests.rs +++ b/frame/scheduler/src/tests.rs @@ -1291,40 +1291,6 @@ fn scheduler_v3_anon_reschedule_works() { }); } -/// Cancelling a call and then scheduling a second call for the same -/// block results in different addresses. -#[test] -fn scheduler_v3_anon_schedule_does_not_resuse_addr() { - use frame_support::traits::schedule::v3::Anon; - new_test_ext().execute_with(|| { - let call = - RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); - - // Schedule both calls. - let addr_1 = >::schedule( - DispatchTime::At(4), - None, - 127, - root(), - Preimage::bound(call.clone()).unwrap(), - ) - .unwrap(); - // Cancel the call. - assert_ok!(>::cancel(addr_1)); - let addr_2 = >::schedule( - DispatchTime::At(4), - None, - 127, - root(), - Preimage::bound(call).unwrap(), - ) - .unwrap(); - - // Should not re-use the address. - assert!(addr_1 != addr_2); - }); -} - #[test] fn scheduler_v3_anon_next_schedule_time_works() { use frame_support::traits::schedule::v3::Anon; @@ -1531,45 +1497,6 @@ fn scheduler_v3_anon_reschedule_fills_holes() { }); } -/// Re-scheduling into the same block produces a different address -/// if there is still space in the agenda. -#[test] -fn scheduler_v3_anon_reschedule_does_not_resuse_addr_if_agenda_not_full() { - use frame_support::traits::schedule::v3::Anon; - let max: u32 = ::MaxScheduledPerBlock::get(); - assert!(max > 1, "This test only makes sense for MaxScheduledPerBlock > 1"); - - new_test_ext().execute_with(|| { - let call = - RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); - - // Schedule both calls. - let addr_1 = >::schedule( - DispatchTime::At(4), - None, - 127, - root(), - Preimage::bound(call.clone()).unwrap(), - ) - .unwrap(); - // Cancel the call. - assert_ok!(>::cancel(addr_1)); - let addr_2 = >::schedule( - DispatchTime::At(5), - None, - 127, - root(), - Preimage::bound(call).unwrap(), - ) - .unwrap(); - // Re-schedule `call` to block 4. - let addr_3 = >::reschedule(addr_2, DispatchTime::At(4)).unwrap(); - - // Should not re-use the address. - assert!(addr_1 != addr_3); - }); -} - /// The scheduler can be used as `v3::Named` trait. #[test] fn scheduler_v3_named_basic_works() { @@ -1767,3 +1694,149 @@ fn scheduler_v3_named_next_schedule_time_works() { ); }); } + +#[test] +fn cancel_last_task_removes_agenda() { + new_test_ext().execute_with(|| { + let when = 4; + let call = + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + let address = Scheduler::do_schedule( + DispatchTime::At(when), + None, + 127, + root(), + Preimage::bound(call.clone()).unwrap(), + ) + .unwrap(); + let address2 = Scheduler::do_schedule( + DispatchTime::At(when), + None, + 127, + root(), + Preimage::bound(call).unwrap(), + ) + .unwrap(); + // two tasks at agenda. + assert!(Agenda::::get(when).len() == 2); + assert_ok!(Scheduler::do_cancel(None, address)); + // still two tasks at agenda, `None` and `Some`. + assert!(Agenda::::get(when).len() == 2); + // cancel last task from `when` agenda. + assert_ok!(Scheduler::do_cancel(None, address2)); + // if all tasks `None`, agenda fully removed. + assert!(Agenda::::get(when).len() == 0); + }); +} + +#[test] +fn cancel_named_last_task_removes_agenda() { + new_test_ext().execute_with(|| { + let when = 4; + let call = + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + Scheduler::do_schedule_named( + [1u8; 32], + DispatchTime::At(when), + None, + 127, + root(), + Preimage::bound(call.clone()).unwrap(), + ) + .unwrap(); + Scheduler::do_schedule_named( + [2u8; 32], + DispatchTime::At(when), + None, + 127, + root(), + Preimage::bound(call).unwrap(), + ) + .unwrap(); + // two tasks at agenda. + assert!(Agenda::::get(when).len() == 2); + assert_ok!(Scheduler::do_cancel_named(None, [2u8; 32])); + // removes trailing `None` and leaves one task. + assert!(Agenda::::get(when).len() == 1); + // cancel last task from `when` agenda. + assert_ok!(Scheduler::do_cancel_named(None, [1u8; 32])); + // if all tasks `None`, agenda fully removed. + assert!(Agenda::::get(when).len() == 0); + }); +} + +#[test] +fn reschedule_last_task_removes_agenda() { + new_test_ext().execute_with(|| { + let when = 4; + let call = + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + let address = Scheduler::do_schedule( + DispatchTime::At(when), + None, + 127, + root(), + Preimage::bound(call.clone()).unwrap(), + ) + .unwrap(); + let address2 = Scheduler::do_schedule( + DispatchTime::At(when), + None, + 127, + root(), + Preimage::bound(call).unwrap(), + ) + .unwrap(); + // two tasks at agenda. + assert!(Agenda::::get(when).len() == 2); + assert_ok!(Scheduler::do_cancel(None, address)); + // still two tasks at agenda, `None` and `Some`. + assert!(Agenda::::get(when).len() == 2); + // reschedule last task from `when` agenda. + assert_eq!( + Scheduler::do_reschedule(address2, DispatchTime::At(when + 1)).unwrap(), + (when + 1, 0) + ); + // if all tasks `None`, agenda fully removed. + assert!(Agenda::::get(when).len() == 0); + }); +} + +#[test] +fn reschedule_named_last_task_removes_agenda() { + new_test_ext().execute_with(|| { + let when = 4; + let call = + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + Scheduler::do_schedule_named( + [1u8; 32], + DispatchTime::At(when), + None, + 127, + root(), + Preimage::bound(call.clone()).unwrap(), + ) + .unwrap(); + Scheduler::do_schedule_named( + [2u8; 32], + DispatchTime::At(when), + None, + 127, + root(), + Preimage::bound(call).unwrap(), + ) + .unwrap(); + // two tasks at agenda. + assert!(Agenda::::get(when).len() == 2); + assert_ok!(Scheduler::do_cancel_named(None, [1u8; 32])); + // still two tasks at agenda, `None` and `Some`. + assert!(Agenda::::get(when).len() == 2); + // reschedule last task from `when` agenda. + assert_eq!( + Scheduler::do_reschedule_named([2u8; 32], DispatchTime::At(when + 1)).unwrap(), + (when + 1, 0) + ); + // if all tasks `None`, agenda fully removed. + assert!(Agenda::::get(when).len() == 0); + }); +} From 1bcb3ce58418e72c151db797af5df87dcc20130f Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Wed, 21 Dec 2022 17:36:08 +0100 Subject: [PATCH 064/127] txpool: TestApi best_block fix (#12992) This is a minor clean up. There was a minor bug in TestApi::add_block: the best block flag for given number was not cleared when the new best block with the same number was added. The usage of is_best_block argument in add_block method in txpool tests were aligned with the BestBlock event. --- client/transaction-pool/tests/pool.rs | 25 +++++++++++-------- .../runtime/transaction-pool/src/lib.rs | 12 +++++++++ 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/client/transaction-pool/tests/pool.rs b/client/transaction-pool/tests/pool.rs index 7ba61e58b4..27d62c3250 100644 --- a/client/transaction-pool/tests/pool.rs +++ b/client/transaction-pool/tests/pool.rs @@ -211,14 +211,18 @@ fn block_event(header: Header) -> ChainEvent { } fn block_event_with_retracted( - header: Header, + new_best_block_header: Header, retracted_start: Hash, api: &TestApi, ) -> ChainEvent { - let tree_route = - api.tree_route(retracted_start, header.parent_hash).expect("Tree route exists"); + let tree_route = api + .tree_route(retracted_start, new_best_block_header.parent_hash) + .expect("Tree route exists"); - ChainEvent::NewBestBlock { hash: header.hash(), tree_route: Some(Arc::new(tree_route)) } + ChainEvent::NewBestBlock { + hash: new_best_block_header.hash(), + tree_route: Some(Arc::new(tree_route)), + } } #[test] @@ -272,7 +276,7 @@ fn should_resubmit_from_retracted_during_maintenance() { assert_eq!(pool.status().ready, 1); let header = api.push_block(1, vec![], true); - let fork_header = api.push_block(1, vec![], false); + let fork_header = api.push_block(1, vec![], true); let event = block_event_with_retracted(header, fork_header.hash(), pool.api()); @@ -290,7 +294,7 @@ fn should_not_resubmit_from_retracted_during_maintenance_if_tx_is_also_in_enacte assert_eq!(pool.status().ready, 1); let header = api.push_block(1, vec![xt.clone()], true); - let fork_header = api.push_block(1, vec![xt], false); + let fork_header = api.push_block(1, vec![xt], true); let event = block_event_with_retracted(header, fork_header.hash(), pool.api()); @@ -309,7 +313,7 @@ fn should_not_retain_invalid_hashes_from_retracted() { assert_eq!(pool.status().ready, 1); let header = api.push_block(1, vec![], true); - let fork_header = api.push_block(1, vec![xt.clone()], false); + let fork_header = api.push_block(1, vec![xt.clone()], true); api.add_invalid(&xt); let event = block_event_with_retracted(header, fork_header.hash(), pool.api()); @@ -649,7 +653,7 @@ fn prune_and_retract_tx_at_same_time() { // Block B2 let b2 = { - let header = pool.api().push_block(2, vec![from_alice.clone()], false); + let header = pool.api().push_block(2, vec![from_alice.clone()], true); assert_eq!(pool.status().ready, 0); let event = block_event_with_retracted(header.clone(), b1, pool.api()); @@ -726,7 +730,8 @@ fn resubmit_tx_of_fork_that_is_not_part_of_retracted() { // Block D2 { - let header = pool.api().push_block(2, vec![], false); + //push new best block + let header = pool.api().push_block(2, vec![], true); let event = block_event_with_retracted(header, d0, pool.api()); block_on(pool.maintain(event)); assert_eq!(pool.status().ready, 2); @@ -1036,7 +1041,7 @@ fn finalized_only_handled_correctly() { .expect("1. Imported"); assert_eq!(pool.status().ready, 1); - let header = api.push_block(1, vec![xt], false); + let header = api.push_block(1, vec![xt], true); let event = ChainEvent::Finalized { hash: header.clone().hash(), tree_route: Arc::from(vec![]) }; diff --git a/test-utils/runtime/transaction-pool/src/lib.rs b/test-utils/runtime/transaction-pool/src/lib.rs index aadd83c00e..8a648cd003 100644 --- a/test-utils/runtime/transaction-pool/src/lib.rs +++ b/test-utils/runtime/transaction-pool/src/lib.rs @@ -178,6 +178,18 @@ impl TestApi { let mut chain = self.chain.write(); chain.block_by_hash.insert(hash, block.clone()); + + if is_best_block { + chain + .block_by_number + .entry(*block_number) + .or_default() + .iter_mut() + .for_each(|x| { + x.1 = IsBestBlock::No; + }); + } + chain .block_by_number .entry(*block_number) From ee316317b85b2f65fc022b27bbfefcd42b6560ae Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Wed, 21 Dec 2022 23:00:27 +0100 Subject: [PATCH 065/127] Migration should be pub (#12996) Signed-off-by: Oliver Tale-Yazdi Signed-off-by: Oliver Tale-Yazdi --- frame/scheduler/src/migration.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/scheduler/src/migration.rs b/frame/scheduler/src/migration.rs index 97a5ef4c3e..f71b9457f5 100644 --- a/frame/scheduler/src/migration.rs +++ b/frame/scheduler/src/migration.rs @@ -198,7 +198,7 @@ pub mod v3 { } } -mod v4 { +pub mod v4 { use super::*; use frame_support::pallet_prelude::*; From 708626fdfac84909b8de6692415fac9413f97f33 Mon Sep 17 00:00:00 2001 From: Serban Iorga Date: Thu, 22 Dec 2022 13:00:12 +0200 Subject: [PATCH 066/127] Mmr client gadget - support pallet reset (#12999) * Remove unneeded code * Moving some code * Support pallet-mmr reset * Rename update_first_mmr_block Co-authored-by: Adrian Catangiu * Renamings Co-authored-by: Adrian Catangiu --- .../merkle-mountain-range/src/aux_schema.rs | 62 +++--- client/merkle-mountain-range/src/lib.rs | 137 ++++++------ .../merkle-mountain-range/src/offchain_mmr.rs | 199 +++++++++++++----- .../merkle-mountain-range/src/test_utils.rs | 12 ++ .../merkle-mountain-range/src/mmr/storage.rs | 8 - 5 files changed, 270 insertions(+), 148 deletions(-) diff --git a/client/merkle-mountain-range/src/aux_schema.rs b/client/merkle-mountain-range/src/aux_schema.rs index 907deb0bde..e6c53d4067 100644 --- a/client/merkle-mountain-range/src/aux_schema.rs +++ b/client/merkle-mountain-range/src/aux_schema.rs @@ -54,8 +54,8 @@ fn load_decode(backend: &B, key: &[u8]) -> ClientResult< } } -/// Load or initialize persistent data from backend. -pub(crate) fn load_persistent(backend: &BE) -> ClientResult>> +/// Load persistent data from backend. +pub(crate) fn load_state(backend: &BE) -> ClientResult>> where B: Block, BE: AuxStore, @@ -73,15 +73,36 @@ where Ok(None) } +/// Load or initialize persistent data from backend. +pub(crate) fn load_or_init_state( + backend: &BE, + default: NumberFor, +) -> sp_blockchain::Result> +where + B: Block, + BE: AuxStore, +{ + // Initialize gadget best_canon from AUX DB or from pallet genesis. + if let Some(best) = load_state::(backend)? { + info!(target: LOG_TARGET, "Loading MMR best canonicalized state from db: {:?}.", best); + Ok(best) + } else { + info!( + target: LOG_TARGET, + "Loading MMR from pallet genesis on what appears to be the first startup: {:?}.", + default + ); + write_current_version(backend)?; + write_gadget_state::(backend, &default)?; + Ok(default) + } +} + #[cfg(test)] pub(crate) mod tests { use super::*; - use crate::test_utils::{ - run_test_with_mmr_gadget_pre_post_using_client, MmrBlock, MockClient, OffchainKeyType, - }; + use crate::test_utils::{run_test_with_mmr_gadget_pre_post_using_client, MmrBlock, MockClient}; use parking_lot::Mutex; - use sp_core::offchain::{DbExternalities, StorageKind}; - use sp_mmr_primitives::utils::NodesUtils; use sp_runtime::generic::BlockId; use std::{sync::Arc, time::Duration}; use substrate_test_runtime_client::{runtime::Block, Backend}; @@ -92,7 +113,7 @@ pub(crate) mod tests { let backend = &*client.backend; // version not available in db -> None - assert_eq!(load_persistent::(backend).unwrap(), None); + assert_eq!(load_state::(backend).unwrap(), None); // populate version in db write_current_version(backend).unwrap(); @@ -100,7 +121,7 @@ pub(crate) mod tests { assert_eq!(load_decode(backend, VERSION_KEY).unwrap(), Some(CURRENT_VERSION)); // version is available in db but state isn't -> None - assert_eq!(load_persistent::(backend).unwrap(), None); + assert_eq!(load_state::(backend).unwrap(), None); } #[test] @@ -113,7 +134,7 @@ pub(crate) mod tests { // version not available in db -> None assert_eq!(load_decode::>(&*backend, VERSION_KEY).unwrap(), None); // state not available in db -> None - assert_eq!(load_persistent::(&*backend).unwrap(), None); + assert_eq!(load_state::(&*backend).unwrap(), None); // run the gadget while importing and finalizing 3 blocks run_test_with_mmr_gadget_pre_post_using_client( client.clone(), @@ -136,7 +157,7 @@ pub(crate) mod tests { let backend = &*client.backend; // check there is both version and best canon available in db before running gadget assert_eq!(load_decode(backend, VERSION_KEY).unwrap(), Some(CURRENT_VERSION)); - assert_eq!(load_persistent::(backend).unwrap(), Some(3)); + assert_eq!(load_state::(backend).unwrap(), Some(3)); }, |client| async move { let a4 = client.import_block(&BlockId::Number(3), b"a4", Some(3)).await; @@ -148,7 +169,7 @@ pub(crate) mod tests { // a4, a5, a6 were canonicalized client.assert_canonicalized(&[&a4, &a5, &a6]); // check persisted best canon was updated - assert_eq!(load_persistent::(&*client.backend).unwrap(), Some(6)); + assert_eq!(load_state::(&*client.backend).unwrap(), Some(6)); }, ); } @@ -177,19 +198,8 @@ pub(crate) mod tests { client.assert_canonicalized(&slice); // now manually move them back to non-canon/temp location - let mut offchain_db = client.offchain_db(); for mmr_block in slice { - for node in NodesUtils::right_branch_ending_in_leaf(mmr_block.leaf_idx.unwrap()) - { - let canon_key = mmr_block.get_offchain_key(node, OffchainKeyType::Canon); - let val = offchain_db - .local_storage_get(StorageKind::PERSISTENT, &canon_key) - .unwrap(); - offchain_db.local_storage_clear(StorageKind::PERSISTENT, &canon_key); - - let temp_key = mmr_block.get_offchain_key(node, OffchainKeyType::Temp); - offchain_db.local_storage_set(StorageKind::PERSISTENT, &temp_key, &val); - } + client.undo_block_canonicalization(mmr_block) } }, ); @@ -203,7 +213,7 @@ pub(crate) mod tests { let slice: Vec<&MmrBlock> = blocks.iter().collect(); // verify persisted state says a1, a2, a3 were canonicalized, - assert_eq!(load_persistent::(&*client.backend).unwrap(), Some(3)); + assert_eq!(load_state::(&*client.backend).unwrap(), Some(3)); // but actually they are NOT canon (we manually reverted them earlier). client.assert_not_canonicalized(&slice); }, @@ -221,7 +231,7 @@ pub(crate) mod tests { // but a4, a5, a6 were canonicalized client.assert_canonicalized(&[&a4, &a5, &a6]); // check persisted best canon was updated - assert_eq!(load_persistent::(&*client.backend).unwrap(), Some(6)); + assert_eq!(load_state::(&*client.backend).unwrap(), Some(6)); }, ); } diff --git a/client/merkle-mountain-range/src/lib.rs b/client/merkle-mountain-range/src/lib.rs index 401a5d5d4d..568c4b94c1 100644 --- a/client/merkle-mountain-range/src/lib.rs +++ b/client/merkle-mountain-range/src/lib.rs @@ -46,7 +46,7 @@ use crate::offchain_mmr::OffchainMmr; use beefy_primitives::MmrRootHash; use futures::StreamExt; use log::{debug, error, trace, warn}; -use sc_client_api::{Backend, BlockchainEvents, FinalityNotifications}; +use sc_client_api::{Backend, BlockchainEvents, FinalityNotification, FinalityNotifications}; use sc_offchain::OffchainDb; use sp_api::ProvideRuntimeApi; use sp_blockchain::{HeaderBackend, HeaderMetadata}; @@ -60,6 +60,62 @@ use std::{marker::PhantomData, sync::Arc}; /// Logging target for the mmr gadget. pub const LOG_TARGET: &str = "mmr"; +/// A convenience MMR client trait that defines all the type bounds a MMR client +/// has to satisfy and defines some helper methods. +pub trait MmrClient: + BlockchainEvents + HeaderBackend + HeaderMetadata + ProvideRuntimeApi +where + B: Block, + BE: Backend, + Self::Api: MmrApi>, +{ + /// Get the block number where the mmr pallet was added to the runtime. + fn first_mmr_block_num(&self, notification: &FinalityNotification) -> Option> { + let best_block = *notification.header.number(); + match self.runtime_api().mmr_leaf_count(&BlockId::number(best_block)) { + Ok(Ok(mmr_leaf_count)) => { + match utils::first_mmr_block_num::(best_block, mmr_leaf_count) { + Ok(first_mmr_block) => { + debug!( + target: LOG_TARGET, + "pallet-mmr detected at block {:?} with genesis at block {:?}", + best_block, + first_mmr_block + ); + Some(first_mmr_block) + }, + Err(e) => { + error!( + target: LOG_TARGET, + "Error calculating the first mmr block: {:?}", e + ); + None + }, + } + }, + _ => { + trace!( + target: LOG_TARGET, + "pallet-mmr not detected at block {:?} ... (best finalized {:?})", + best_block, + notification.header.number() + ); + None + }, + } + } +} + +impl MmrClient for T +where + B: Block, + BE: Backend, + T: BlockchainEvents + HeaderBackend + HeaderMetadata + ProvideRuntimeApi, + T::Api: MmrApi>, +{ + // empty +} + struct OffchainMmrBuilder, C> { backend: Arc, client: Arc, @@ -73,7 +129,7 @@ impl OffchainMmrBuilder where B: Block, BE: Backend, - C: ProvideRuntimeApi + HeaderBackend + HeaderMetadata, + C: MmrClient, C::Api: MmrApi>, { async fn try_build( @@ -81,66 +137,21 @@ where finality_notifications: &mut FinalityNotifications, ) -> Option> { while let Some(notification) = finality_notifications.next().await { - let best_block = *notification.header.number(); - match self.client.runtime_api().mmr_leaf_count(&BlockId::number(best_block)) { - Ok(Ok(mmr_leaf_count)) => { - debug!( - target: LOG_TARGET, - "pallet-mmr detected at block {:?} with mmr size {:?}", - best_block, - mmr_leaf_count - ); - match utils::first_mmr_block_num::(best_block, mmr_leaf_count) { - Ok(first_mmr_block) => { - debug!( - target: LOG_TARGET, - "pallet-mmr genesis computed at block {:?}", first_mmr_block, - ); - let best_canonicalized = - match offchain_mmr::load_or_init_best_canonicalized::( - &*self.backend, - first_mmr_block, - ) { - Ok(best) => best, - Err(e) => { - error!( - target: LOG_TARGET, - "Error loading state from aux db: {:?}", e - ); - return None - }, - }; - let mut offchain_mmr = OffchainMmr { - backend: self.backend, - client: self.client, - offchain_db: self.offchain_db, - indexing_prefix: self.indexing_prefix, - first_mmr_block, - best_canonicalized, - }; - // We need to make sure all blocks leading up to current notification - // have also been canonicalized. - offchain_mmr.canonicalize_catch_up(¬ification); - // We have to canonicalize and prune the blocks in the finality - // notification that lead to building the offchain-mmr as well. - offchain_mmr.canonicalize_and_prune(notification); - return Some(offchain_mmr) - }, - Err(e) => { - error!( - target: LOG_TARGET, - "Error calculating the first mmr block: {:?}", e - ); - }, - } - }, - _ => { - trace!( - target: LOG_TARGET, - "Waiting for MMR pallet to become available... (best finalized {:?})", - notification.header.number() - ); - }, + if let Some(first_mmr_block_num) = self.client.first_mmr_block_num(¬ification) { + let mut offchain_mmr = OffchainMmr::new( + self.backend, + self.client, + self.offchain_db, + self.indexing_prefix, + first_mmr_block_num, + )?; + // We need to make sure all blocks leading up to current notification + // have also been canonicalized. + offchain_mmr.canonicalize_catch_up(¬ification); + // We have to canonicalize and prune the blocks in the finality + // notification that lead to building the offchain-mmr as well. + offchain_mmr.canonicalize_and_prune(notification); + return Some(offchain_mmr) } } @@ -165,7 +176,7 @@ where B: Block, ::Number: Into, BE: Backend, - C: BlockchainEvents + HeaderBackend + HeaderMetadata + ProvideRuntimeApi, + C: MmrClient, C::Api: MmrApi>, { async fn run(mut self, builder: OffchainMmrBuilder) { diff --git a/client/merkle-mountain-range/src/offchain_mmr.rs b/client/merkle-mountain-range/src/offchain_mmr.rs index 988b3ffef8..bbb53cf3a9 100644 --- a/client/merkle-mountain-range/src/offchain_mmr.rs +++ b/client/merkle-mountain-range/src/offchain_mmr.rs @@ -21,59 +21,59 @@ #![warn(missing_docs)] -use crate::{aux_schema, LOG_TARGET}; +use crate::{aux_schema, MmrClient, LOG_TARGET}; +use beefy_primitives::MmrRootHash; use log::{debug, error, info, warn}; -use sc_client_api::{AuxStore, Backend, FinalityNotification}; +use sc_client_api::{Backend, FinalityNotification}; use sc_offchain::OffchainDb; -use sp_blockchain::{CachedHeaderMetadata, ForkBackend, HeaderBackend, HeaderMetadata}; +use sp_blockchain::{CachedHeaderMetadata, ForkBackend}; use sp_core::offchain::{DbExternalities, StorageKind}; -use sp_mmr_primitives::{utils, utils::NodesUtils, NodeIndex}; +use sp_mmr_primitives::{utils, utils::NodesUtils, MmrApi, NodeIndex}; use sp_runtime::{ - traits::{Block, NumberFor, One}, + traits::{Block, Header, NumberFor, One}, Saturating, }; use std::{collections::VecDeque, sync::Arc}; -pub(crate) fn load_or_init_best_canonicalized( - backend: &BE, - first_mmr_block: NumberFor, -) -> sp_blockchain::Result> -where - BE: AuxStore, - B: Block, -{ - // Initialize gadget best_canon from AUX DB or from pallet genesis. - if let Some(best) = aux_schema::load_persistent::(backend)? { - info!(target: LOG_TARGET, "Loading MMR best canonicalized state from db: {:?}.", best); - Ok(best) - } else { - let best = first_mmr_block.saturating_sub(One::one()); - info!( - target: LOG_TARGET, - "Loading MMR from pallet genesis on what appears to be the first startup: {:?}.", best - ); - aux_schema::write_current_version(backend)?; - aux_schema::write_gadget_state::(backend, &best)?; - Ok(best) - } -} - /// `OffchainMMR` exposes MMR offchain canonicalization and pruning logic. pub struct OffchainMmr, C> { - pub backend: Arc, - pub client: Arc, - pub offchain_db: OffchainDb, - pub indexing_prefix: Vec, - pub first_mmr_block: NumberFor, - pub best_canonicalized: NumberFor, + backend: Arc, + client: Arc, + offchain_db: OffchainDb, + indexing_prefix: Vec, + first_mmr_block: NumberFor, + best_canonicalized: NumberFor, } impl OffchainMmr where - C: HeaderBackend + HeaderMetadata, BE: Backend, B: Block, + C: MmrClient, + C::Api: MmrApi>, { + pub fn new( + backend: Arc, + client: Arc, + offchain_db: OffchainDb, + indexing_prefix: Vec, + first_mmr_block: NumberFor, + ) -> Option { + let mut best_canonicalized = first_mmr_block.saturating_sub(One::one()); + best_canonicalized = aux_schema::load_or_init_state::(&*backend, best_canonicalized) + .map_err(|e| error!(target: LOG_TARGET, "Error loading state from aux db: {:?}", e)) + .ok()?; + + Some(Self { + backend, + client, + offchain_db, + indexing_prefix, + first_mmr_block, + best_canonicalized, + }) + } + fn node_temp_offchain_key(&self, pos: NodeIndex, parent_hash: B::Hash) -> Vec { NodesUtils::node_temp_offchain_key::(&self.indexing_prefix, pos, parent_hash) } @@ -82,6 +82,14 @@ where NodesUtils::node_canon_offchain_key(&self.indexing_prefix, pos) } + fn write_gadget_state_or_log(&self) { + if let Err(e) = + aux_schema::write_gadget_state::(&*self.backend, &self.best_canonicalized) + { + debug!(target: LOG_TARGET, "error saving state: {:?}", e); + } + } + fn header_metadata_or_log( &self, hash: B::Hash, @@ -231,10 +239,22 @@ where for hash in to_canon.drain(..) { self.canonicalize_branch(hash); } - if let Err(e) = - aux_schema::write_gadget_state::(&*self.backend, &self.best_canonicalized) - { - debug!(target: LOG_TARGET, "error saving state: {:?}", e); + self.write_gadget_state_or_log(); + } + } + + fn handle_potential_pallet_reset(&mut self, notification: &FinalityNotification) { + if let Some(first_mmr_block_num) = self.client.first_mmr_block_num(¬ification) { + if first_mmr_block_num != self.first_mmr_block { + info!( + target: LOG_TARGET, + "pallet-mmr reset detected at block {:?} with new genesis at block {:?}", + notification.header.number(), + first_mmr_block_num + ); + self.first_mmr_block = first_mmr_block_num; + self.best_canonicalized = first_mmr_block_num.saturating_sub(One::one()); + self.write_gadget_state_or_log(); } } } @@ -243,15 +263,14 @@ where /// _canonical key_. /// Prune leafs and nodes added by stale blocks in offchain db from _fork-aware key_. pub fn canonicalize_and_prune(&mut self, notification: FinalityNotification) { + // Update the first MMR block in case of a pallet reset. + self.handle_potential_pallet_reset(¬ification); + // Move offchain MMR nodes for finalized blocks to canonical keys. for hash in notification.tree_route.iter().chain(std::iter::once(¬ification.hash)) { self.canonicalize_branch(*hash); } - if let Err(e) = - aux_schema::write_gadget_state::(&*self.backend, &self.best_canonicalized) - { - debug!(target: LOG_TARGET, "error saving state: {:?}", e); - } + self.write_gadget_state_or_log(); // Remove offchain MMR nodes for stale forks. let stale_forks = self.client.expand_forks(¬ification.stale_heads).unwrap_or_else( @@ -303,7 +322,7 @@ mod tests { // expected pruned heads because of temp key collision: b1 client.assert_pruned(&[&c1, &b1]); - client.finalize_block(d5.hash(), None); + client.finalize_block(d5.hash(), Some(5)); tokio::time::sleep(Duration::from_millis(200)).await; // expected finalized heads: d4, d5, client.assert_canonicalized(&[&d4, &d5]); @@ -312,6 +331,36 @@ mod tests { }) } + #[test] + fn canonicalize_and_prune_handles_pallet_reset() { + run_test_with_mmr_gadget(|client| async move { + // G -> A1 -> A2 -> A3 -> A4 -> A5 + // | | + // | | -> pallet reset + // | + // | -> first finality notification + + let a1 = client.import_block(&BlockId::Number(0), b"a1", Some(0)).await; + let a2 = client.import_block(&BlockId::Hash(a1.hash()), b"a2", Some(1)).await; + let a3 = client.import_block(&BlockId::Hash(a2.hash()), b"a3", Some(0)).await; + let a4 = client.import_block(&BlockId::Hash(a3.hash()), b"a4", Some(1)).await; + let a5 = client.import_block(&BlockId::Hash(a4.hash()), b"a5", Some(2)).await; + + client.finalize_block(a1.hash(), Some(1)); + tokio::time::sleep(Duration::from_millis(200)).await; + // expected finalized heads: a1 + client.assert_canonicalized(&[&a1]); + // a2 shouldn't be either canonicalized or pruned. It should be handled as part of the + // reset process. + client.assert_not_canonicalized(&[&a2]); + + client.finalize_block(a5.hash(), Some(3)); + tokio::time::sleep(Duration::from_millis(200)).await; + //expected finalized heads: a3, a4, a5, + client.assert_canonicalized(&[&a3, &a4, &a5]); + }) + } + #[test] fn canonicalize_catchup_works_correctly() { let mmr_blocks = Arc::new(Mutex::new(vec![])); @@ -329,11 +378,9 @@ mod tests { client.finalize_block(a2.hash(), Some(2)); - { - let mut mmr_blocks = mmr_blocks_ref.lock(); - mmr_blocks.push(a1); - mmr_blocks.push(a2); - } + let mut mmr_blocks = mmr_blocks_ref.lock(); + mmr_blocks.push(a1); + mmr_blocks.push(a2); }, |client| async move { // G -> A1 -> A2 -> A3 -> A4 @@ -358,4 +405,54 @@ mod tests { }, ) } + + #[test] + fn canonicalize_catchup_works_correctly_with_pallet_reset() { + let mmr_blocks = Arc::new(Mutex::new(vec![])); + let mmr_blocks_ref = mmr_blocks.clone(); + run_test_with_mmr_gadget_pre_post( + |client| async move { + // G -> A1 -> A2 + // | | + // | | -> finalized without gadget (missed notification) + // | + // | -> first mmr block + + let a1 = client.import_block(&BlockId::Number(0), b"a1", Some(0)).await; + let a2 = client.import_block(&BlockId::Hash(a1.hash()), b"a2", Some(0)).await; + + client.finalize_block(a2.hash(), Some(1)); + + let mut mmr_blocks = mmr_blocks_ref.lock(); + mmr_blocks.push(a1); + mmr_blocks.push(a2); + }, + |client| async move { + // G -> A1 -> A2 -> A3 -> A4 + // | | | | + // | | | | -> finalized after starting gadget + // | | | + // | | | -> gadget start + // | | + // | | -> finalized before gadget start (missed notification) + // | | + pallet reset + // | + // | -> first mmr block + let blocks = mmr_blocks.lock(); + let a1 = blocks[0].clone(); + let a2 = blocks[1].clone(); + let a3 = client.import_block(&BlockId::Hash(a2.hash()), b"a3", Some(1)).await; + let a4 = client.import_block(&BlockId::Hash(a3.hash()), b"a4", Some(2)).await; + + client.finalize_block(a4.hash(), Some(3)); + tokio::time::sleep(Duration::from_millis(200)).await; + // a1 shouldn't be either canonicalized or pruned. It should be handled as part of + // the reset process. Checking only that it wasn't pruned. Because of temp key + // collision with a2 we can't check that it wasn't canonicalized. + client.assert_not_pruned(&[&a1]); + // expected finalized heads: a4, a5. + client.assert_canonicalized(&[&a2, &a3, &a4]); + }, + ) + } } diff --git a/client/merkle-mountain-range/src/test_utils.rs b/client/merkle-mountain-range/src/test_utils.rs index 39570e0d23..e5a6673483 100644 --- a/client/merkle-mountain-range/src/test_utils.rs +++ b/client/merkle-mountain-range/src/test_utils.rs @@ -162,6 +162,18 @@ impl MockClient { client.finalize_block(hash, None).unwrap(); } + pub fn undo_block_canonicalization(&self, mmr_block: &MmrBlock) { + let mut offchain_db = self.offchain_db(); + for node in NodesUtils::right_branch_ending_in_leaf(mmr_block.leaf_idx.unwrap()) { + let canon_key = mmr_block.get_offchain_key(node, OffchainKeyType::Canon); + let val = offchain_db.local_storage_get(StorageKind::PERSISTENT, &canon_key).unwrap(); + offchain_db.local_storage_clear(StorageKind::PERSISTENT, &canon_key); + + let temp_key = mmr_block.get_offchain_key(node, OffchainKeyType::Temp); + offchain_db.local_storage_set(StorageKind::PERSISTENT, &temp_key, &val); + } + } + pub fn check_offchain_storage( &self, key_type: OffchainKeyType, diff --git a/frame/merkle-mountain-range/src/mmr/storage.rs b/frame/merkle-mountain-range/src/mmr/storage.rs index 1f5d01f87e..2e3b174f39 100644 --- a/frame/merkle-mountain-range/src/mmr/storage.rs +++ b/frame/merkle-mountain-range/src/mmr/storage.rs @@ -60,14 +60,6 @@ impl Default for Storage { } } -impl Storage -where - T: Config, - I: 'static, - L: primitives::FullLeaf, -{ -} - impl mmr_lib::MMRStore> for Storage where T: Config, From be510f203049b3b644c5004c036c8bd2b28ef40c Mon Sep 17 00:00:00 2001 From: JP <77391175+joao-paulo-parity@users.noreply.github.com> Date: Thu, 22 Dec 2022 09:21:30 -0300 Subject: [PATCH 067/127] change crates publishing to be scheduled (#13002) --- .gitlab-ci.yml | 4 ++++ scripts/ci/gitlab/pipeline/publish.yml | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 145a25c809..4d17b70028 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -213,6 +213,10 @@ default: # this job runs only on nightly pipeline with the mentioned variable, against `master` branch - if: $CI_COMMIT_REF_NAME == "master" && $CI_PIPELINE_SOURCE == "schedule" && $PIPELINE == "nightly" +.scheduled-crate-publishing-pipeline: + rules: + - if: $CI_COMMIT_REF_NAME == "master" && $CI_PIPELINE_SOURCE == "schedule" && $PIPELINE == "automatic-crate-publishing" + .crates-publishing-template: stage: test extends: .docker-env diff --git a/scripts/ci/gitlab/pipeline/publish.yml b/scripts/ci/gitlab/pipeline/publish.yml index f2fd64bc34..cc7451a9fb 100644 --- a/scripts/ci/gitlab/pipeline/publish.yml +++ b/scripts/ci/gitlab/pipeline/publish.yml @@ -225,12 +225,12 @@ update-node-template: - rusty-cachier cache upload publish-crates: - extends: .publish-crates-template + extends: + - .publish-crates-template + - .scheduled-crate-publishing-pipeline needs: - job: publish-crates-locally artifacts: false - rules: - - if: $CI_COMMIT_REF_NAME == "master" publish-crates-manual: extends: .publish-crates-template From d57777ae1658544f0f9bca98ac6d9832bd32e4f6 Mon Sep 17 00:00:00 2001 From: Sasha Gryaznov Date: Thu, 22 Dec 2022 16:48:39 +0200 Subject: [PATCH 068/127] stabilize `[seal0] take_storage` (#13007) --- frame/contracts/src/wasm/runtime.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index 50ad9996e6..066c1d71ea 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -1230,7 +1230,6 @@ pub mod env { /// # Errors /// /// `ReturnCode::KeyNotFound` - #[unstable] #[prefixed_alias] fn take_storage( ctx: _, From 96e71b531cebf63211771c721169f78eb20eae54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Thu, 22 Dec 2022 15:52:48 +0100 Subject: [PATCH 069/127] contracts: Improve contract address derivation (#12883) * Add prefix to address derivation * Extend benchmark * Fix node test * ".git/.scripts/bench-bot.sh" pallet dev pallet_contracts * Adapt to new benchmark * Update dispatchable benchmarks * ".git/.scripts/bench-bot.sh" pallet dev pallet_contracts * Use benchmark results * Apply suggestions from code review Co-authored-by: Sasha Gryaznov * Don't use T::AdressGenerator directly * Rename constructor_args to input_data Co-authored-by: command-bot <> Co-authored-by: Sasha Gryaznov --- bin/node/executor/tests/basic.rs | 3 +- frame/contracts/proc-macro/src/lib.rs | 6 +- frame/contracts/src/benchmarking/code.rs | 25 +- frame/contracts/src/benchmarking/mod.rs | 31 +- frame/contracts/src/benchmarking/sandbox.rs | 7 +- frame/contracts/src/chain_extension.rs | 27 +- frame/contracts/src/exec.rs | 18 +- frame/contracts/src/gas.rs | 6 +- frame/contracts/src/lib.rs | 61 +- frame/contracts/src/schedule.rs | 15 +- frame/contracts/src/storage.rs | 14 +- frame/contracts/src/storage/meter.rs | 10 +- frame/contracts/src/tests.rs | 852 +++++---- frame/contracts/src/wasm/code_cache.rs | 21 +- frame/contracts/src/wasm/mod.rs | 14 +- frame/contracts/src/wasm/prepare.rs | 4 - frame/contracts/src/wasm/runtime.rs | 22 +- frame/contracts/src/weights.rs | 1902 ++++++++++--------- 18 files changed, 1559 insertions(+), 1479 deletions(-) diff --git a/bin/node/executor/tests/basic.rs b/bin/node/executor/tests/basic.rs index fc4e138faa..c88703b929 100644 --- a/bin/node/executor/tests/basic.rs +++ b/bin/node/executor/tests/basic.rs @@ -654,7 +654,8 @@ fn deploying_wasm_contract_should_work() { let transfer_code = wat::parse_str(CODE_TRANSFER).unwrap(); let transfer_ch = ::Hashing::hash(&transfer_code); - let addr = pallet_contracts::Pallet::::contract_address(&charlie(), &transfer_ch, &[]); + let addr = + pallet_contracts::Pallet::::contract_address(&charlie(), &transfer_ch, &[], &[]); let time = 42 * 1000; let b = construct_block( diff --git a/frame/contracts/proc-macro/src/lib.rs b/frame/contracts/proc-macro/src/lib.rs index a8f95bd10c..ded5880a2d 100644 --- a/frame/contracts/proc-macro/src/lib.rs +++ b/frame/contracts/proc-macro/src/lib.rs @@ -404,11 +404,7 @@ fn expand_impls(def: &mut EnvDef) -> TokenStream2 { let dummy_impls = expand_functions(def, false, quote! { () }); quote! { - impl<'a, E> crate::wasm::Environment> for Env - where - E: Ext, - ::AccountId: - ::sp_core::crypto::UncheckedFrom<::Hash> + ::core::convert::AsRef<[::core::primitive::u8]>, + impl<'a, E: Ext> crate::wasm::Environment> for Env { fn define(store: &mut ::wasmi::Store>, linker: &mut ::wasmi::Linker>, allow_unstable: bool) -> Result<(), ::wasmi::errors::LinkerError> { #impls diff --git a/frame/contracts/src/benchmarking/code.rs b/frame/contracts/src/benchmarking/code.rs index 2074e1867d..0b09726214 100644 --- a/frame/contracts/src/benchmarking/code.rs +++ b/frame/contracts/src/benchmarking/code.rs @@ -26,7 +26,6 @@ use crate::{Config, Determinism}; use frame_support::traits::Get; -use sp_core::crypto::UncheckedFrom; use sp_runtime::traits::Hash; use sp_std::{borrow::ToOwned, prelude::*}; use wasm_instrument::{ @@ -105,11 +104,7 @@ pub struct ImportedMemory { } impl ImportedMemory { - pub fn max() -> Self - where - T: Config, - T::AccountId: UncheckedFrom + AsRef<[u8]>, - { + pub fn max() -> Self { let pages = max_pages::(); Self { min_pages: pages, max_pages: pages } } @@ -130,11 +125,7 @@ pub struct WasmModule { pub memory: Option, } -impl From for WasmModule -where - T: Config, - T::AccountId: UncheckedFrom + AsRef<[u8]>, -{ +impl From for WasmModule { fn from(def: ModuleDefinition) -> Self { // internal functions start at that offset. let func_offset = u32::try_from(def.imported_functions.len()).unwrap(); @@ -259,11 +250,7 @@ where } } -impl WasmModule -where - T: Config, - T::AccountId: UncheckedFrom + AsRef<[u8]>, -{ +impl WasmModule { /// Uses the supplied wasm module and instruments it when requested. pub fn instrumented(code: &[u8], inject_gas: bool, inject_stack: bool) -> Self { let module = { @@ -533,11 +520,7 @@ pub mod body { } /// The maximum amount of pages any contract is allowed to have according to the current `Schedule`. -pub fn max_pages() -> u32 -where - T: Config, - T::AccountId: UncheckedFrom + AsRef<[u8]>, -{ +pub fn max_pages() -> u32 { T::Schedule::get().limits.memory_pages } diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs index 6b8701ac84..da9b53495d 100644 --- a/frame/contracts/src/benchmarking/mod.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -63,8 +63,6 @@ struct Contract { impl Contract where - T: Config, - T::AccountId: UncheckedFrom + AsRef<[u8]>, as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, { /// Create new contract and use a default account id as instantiator. @@ -90,7 +88,7 @@ where let value = Pallet::::min_balance(); T::Currency::make_free_balance_be(&caller, caller_funding::()); let salt = vec![0xff]; - let addr = Contracts::::contract_address(&caller, &module.hash, &salt); + let addr = Contracts::::contract_address(&caller, &module.hash, &data, &salt); Contracts::::store_code_raw(module.code, caller.clone())?; Contracts::::instantiate( @@ -203,8 +201,6 @@ macro_rules! load_benchmark { benchmarks! { where_clause { where - T::AccountId: UncheckedFrom, - T::AccountId: AsRef<[u8]>, as codec::HasCompact>::Type: Clone + Eq + PartialEq + sp_std::fmt::Debug + scale_info::TypeInfo + codec::Encode, } @@ -270,6 +266,7 @@ benchmarks! { // a code of that size into the sandbox. // // `c`: Size of the code in kilobytes. + // `i`: Size of the input in kilobytes. // `s`: Size of the salt in kilobytes. // // # Note @@ -278,15 +275,17 @@ benchmarks! { // to be larger than the maximum size **after instrumentation**. instantiate_with_code { let c in 0 .. Perbill::from_percent(49).mul_ceil(T::MaxCodeLen::get()); + let i in 0 .. code::max_pages::() * 64 * 1024; let s in 0 .. code::max_pages::() * 64 * 1024; + let input = vec![42u8; i as usize]; let salt = vec![42u8; s as usize]; let value = Pallet::::min_balance(); let caller = whitelisted_caller(); T::Currency::make_free_balance_be(&caller, caller_funding::()); let WasmModule { code, hash, .. } = WasmModule::::sized(c, Location::Call); let origin = RawOrigin::Signed(caller.clone()); - let addr = Contracts::::contract_address(&caller, &hash, &salt); - }: _(origin, value, Weight::MAX, None, code, vec![], salt) + let addr = Contracts::::contract_address(&caller, &hash, &input, &salt); + }: _(origin, value, Weight::MAX, None, code, input, salt) verify { // the contract itself does not trigger any reserves let deposit = T::Currency::reserved_balance(&addr); @@ -303,18 +302,21 @@ benchmarks! { } // Instantiate uses a dummy contract constructor to measure the overhead of the instantiate. + // `i`: Size of the input in kilobytes. // `s`: Size of the salt in kilobytes. instantiate { + let i in 0 .. code::max_pages::() * 64 * 1024; let s in 0 .. code::max_pages::() * 64 * 1024; + let input = vec![42u8; i as usize]; let salt = vec![42u8; s as usize]; let value = Pallet::::min_balance(); let caller = whitelisted_caller(); T::Currency::make_free_balance_be(&caller, caller_funding::()); let WasmModule { code, hash, .. } = WasmModule::::dummy(); let origin = RawOrigin::Signed(caller.clone()); - let addr = Contracts::::contract_address(&caller, &hash, &salt); + let addr = Contracts::::contract_address(&caller, &hash, &input, &salt); Contracts::::store_code_raw(code, caller.clone())?; - }: _(origin, value, Weight::MAX, None, hash, vec![], salt) + }: _(origin, value, Weight::MAX, None, hash, input, salt) verify { // the contract itself does not trigger any reserves let deposit = T::Currency::reserved_balance(&addr); @@ -1779,7 +1781,7 @@ benchmarks! { let addresses = hashes .iter() .map(|hash| Contracts::::contract_address( - &instance.account_id, hash, &[], + &instance.account_id, hash, &[], &[], )) .collect::>(); @@ -1796,8 +1798,9 @@ benchmarks! { } } - seal_instantiate_per_transfer_salt_kb { + seal_instantiate_per_transfer_input_salt_kb { let t in 0 .. 1; + let i in 0 .. (code::max_pages::() - 1) * 64; let s in 0 .. (code::max_pages::() - 1) * 64; let callee_code = WasmModule::::dummy(); let hash = callee_code.hash; @@ -1865,14 +1868,14 @@ benchmarks! { Regular(Instruction::I64Const(0)), // gas Regular(Instruction::I32Const(value_offset as i32)), // value_ptr Regular(Instruction::I32Const(value_len as i32)), // value_len - Regular(Instruction::I32Const(0)), // input_data_ptr - Regular(Instruction::I32Const(0)), // input_data_len + Counter(salt_offset as u32, salt_len as u32), // input_data_ptr + Regular(Instruction::I32Const((i * 1024) as i32)), // input_data_len Regular(Instruction::I32Const((addr_len_offset + addr_len) as i32)), // address_ptr Regular(Instruction::I32Const(addr_len_offset as i32)), // address_len_ptr Regular(Instruction::I32Const(SENTINEL as i32)), // output_ptr Regular(Instruction::I32Const(0)), // output_len_ptr Counter(salt_offset as u32, salt_len as u32), // salt_ptr - Regular(Instruction::I32Const((s * 1024).max(salt_len as u32) as i32)), // salt_len + Regular(Instruction::I32Const((s * 1024) as i32)), // salt_len Regular(Instruction::Call(0)), Regular(Instruction::I32Eqz), Regular(Instruction::If(BlockType::NoResult)), diff --git a/frame/contracts/src/benchmarking/sandbox.rs b/frame/contracts/src/benchmarking/sandbox.rs index a35aad3500..5d080bad92 100644 --- a/frame/contracts/src/benchmarking/sandbox.rs +++ b/frame/contracts/src/benchmarking/sandbox.rs @@ -20,7 +20,6 @@ /// ! environment that provides the seal interface as imported functions. use super::{code::WasmModule, Config}; use crate::wasm::{Environment, PrefabWasmModule}; -use sp_core::crypto::UncheckedFrom; use wasmi::{errors::LinkerError, Func, Linker, StackLimits, Store}; /// Minimal execution environment without any imported functions. @@ -36,11 +35,7 @@ impl Sandbox { } } -impl From<&WasmModule> for Sandbox -where - T: Config, - T::AccountId: UncheckedFrom + AsRef<[u8]>, -{ +impl From<&WasmModule> for Sandbox { /// Creates an instance from the supplied module and supplies as much memory /// to the instance as the module declares as imported. fn from(module: &WasmModule) -> Self { diff --git a/frame/contracts/src/chain_extension.rs b/frame/contracts/src/chain_extension.rs index 3c3e9b1ef0..dfa9c7c2de 100644 --- a/frame/contracts/src/chain_extension.rs +++ b/frame/contracts/src/chain_extension.rs @@ -83,7 +83,6 @@ use sp_std::{marker::PhantomData, vec::Vec}; pub use crate::{exec::Ext, Config}; pub use frame_system::Config as SysConfig; pub use pallet_contracts_primitives::ReturnFlags; -pub use sp_core::crypto::UncheckedFrom; /// Result that returns a [`DispatchError`] on error. pub type Result = sp_std::result::Result; @@ -114,10 +113,7 @@ pub trait ChainExtension { /// In case of `Err` the contract execution is immediately suspended and the passed error /// is returned to the caller. Otherwise the value of [`RetVal`] determines the exit /// behaviour. - fn call(&mut self, env: Environment) -> Result - where - E: Ext, - ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>; + fn call>(&mut self, env: Environment) -> Result; /// Determines whether chain extensions are enabled for this chain. /// @@ -153,11 +149,7 @@ pub trait RegisteredChainExtension: ChainExtension { #[impl_trait_for_tuples::impl_for_tuples(10)] #[tuple_types_custom_trait_bound(RegisteredChainExtension)] impl ChainExtension for Tuple { - fn call(&mut self, mut env: Environment) -> Result - where - E: Ext, - ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, - { + fn call>(&mut self, mut env: Environment) -> Result { for_tuples!( #( if (Tuple::ID == env.ext_id()) && Tuple::enabled() { @@ -205,10 +197,7 @@ pub struct Environment<'a, 'b, E: Ext, S: State> { } /// Functions that are available in every state of this type. -impl<'a, 'b, E: Ext, S: State> Environment<'a, 'b, E, S> -where - ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, -{ +impl<'a, 'b, E: Ext, S: State> Environment<'a, 'b, E, S> { /// The function id within the `id` passed by a contract. /// /// It returns the two least significant bytes of the `id` passed by a contract as the other @@ -326,10 +315,7 @@ impl<'a, 'b, E: Ext, S: PrimOut> Environment<'a, 'b, E, S> { } /// Functions to use the input arguments as pointer to a buffer. -impl<'a, 'b, E: Ext, S: BufIn> Environment<'a, 'b, E, S> -where - ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, -{ +impl<'a, 'b, E: Ext, S: BufIn> Environment<'a, 'b, E, S> { /// Reads `min(max_len, in_len)` from contract memory. /// /// This does **not** charge any weight. The caller must make sure that the an @@ -401,10 +387,7 @@ where } /// Functions to use the output arguments as pointer to a buffer. -impl<'a, 'b, E: Ext, S: BufOut> Environment<'a, 'b, E, S> -where - ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, -{ +impl<'a, 'b, E: Ext, S: BufOut> Environment<'a, 'b, E, S> { /// Write the supplied buffer to contract memory. /// /// If the contract supplied buffer is smaller than the passed `buffer` an `Err` is returned. diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index 945095dc20..f4dc3a0ec0 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -32,7 +32,7 @@ use frame_support::{ use frame_system::RawOrigin; use pallet_contracts_primitives::ExecReturnValue; use smallvec::{Array, SmallVec}; -use sp_core::{crypto::UncheckedFrom, ecdsa::Public as ECDSAPublic}; +use sp_core::ecdsa::Public as ECDSAPublic; use sp_io::{crypto::secp256k1_ecdsa_recover_compressed, hashing::blake2_256}; use sp_runtime::traits::{Convert, Hash}; use sp_std::{marker::PhantomData, mem, prelude::*}; @@ -475,6 +475,8 @@ enum FrameArgs<'a, T: Config, E> { executable: E, /// A salt used in the contract address deriviation of the new contract. salt: &'a [u8], + /// The input data is used in the contract address deriviation of the new contract. + input_data: &'a [u8], }, } @@ -596,7 +598,6 @@ impl CachedContract { impl<'a, T, E> Stack<'a, T, E> where T: Config, - T::AccountId: UncheckedFrom + AsRef<[u8]>, E: Executable, { /// Create and run a new call stack by calling into `dest`. @@ -660,6 +661,7 @@ where nonce: >::get().wrapping_add(1), executable, salt, + input_data: input_data.as_ref(), }, origin, gas_meter, @@ -742,9 +744,13 @@ where (dest, contract, executable, delegate_caller, ExportedFunction::Call, None) }, - FrameArgs::Instantiate { sender, nonce, executable, salt } => { - let account_id = - >::contract_address(&sender, executable.code_hash(), salt); + FrameArgs::Instantiate { sender, nonce, executable, salt, input_data } => { + let account_id = Contracts::::contract_address( + &sender, + executable.code_hash(), + input_data, + salt, + ); let trie_id = Storage::::generate_trie_id(&account_id, nonce); let contract = Storage::::new_contract(&account_id, trie_id, *executable.code_hash())?; @@ -1080,7 +1086,6 @@ where impl<'a, T, E> Ext for Stack<'a, T, E> where T: Config, - T::AccountId: UncheckedFrom + AsRef<[u8]>, E: Executable, { type T = T; @@ -1167,6 +1172,7 @@ where nonce, executable, salt, + input_data: input_data.as_ref(), }, value, gas_limit, diff --git a/frame/contracts/src/gas.rs b/frame/contracts/src/gas.rs index c0cc2db2aa..ccbe680b2f 100644 --- a/frame/contracts/src/gas.rs +++ b/frame/contracts/src/gas.rs @@ -23,7 +23,6 @@ use frame_support::{ weights::Weight, DefaultNoBound, }; -use sp_core::crypto::UncheckedFrom; use sp_runtime::traits::Zero; use sp_std::marker::PhantomData; @@ -86,10 +85,7 @@ pub struct GasMeter { tokens: Vec, } -impl GasMeter -where - T::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, -{ +impl GasMeter { pub fn new(gas_limit: Weight) -> Self { GasMeter { gas_limit, diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index b76acf9d1d..39626e43f4 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -105,7 +105,7 @@ use crate::{ wasm::{OwnerInfo, PrefabWasmModule, TryInstantiate}, weights::WeightInfo, }; -use codec::{Codec, Encode, HasCompact}; +use codec::{Codec, Decode, Encode, HasCompact}; use frame_support::{ dispatch::{Dispatchable, GetDispatchInfo, Pays, PostDispatchInfo}, ensure, @@ -123,8 +123,7 @@ use pallet_contracts_primitives::{ StorageDeposit, }; use scale_info::TypeInfo; -use sp_core::crypto::UncheckedFrom; -use sp_runtime::traits::{Convert, Hash, Saturating, StaticLookup}; +use sp_runtime::traits::{Convert, Hash, Saturating, StaticLookup, TrailingZeroInput}; use sp_std::{fmt::Debug, marker::PhantomData, prelude::*}; pub use crate::{ @@ -155,7 +154,7 @@ const SENTINEL: u32 = u32::MAX; /// Provides the contract address generation method. /// /// See [`DefaultAddressGenerator`] for the default implementation. -pub trait AddressGenerator { +pub trait AddressGenerator { /// Generate the address of a contract based on the given instantiate parameters. /// /// # Note for implementors @@ -166,6 +165,7 @@ pub trait AddressGenerator { fn generate_address( deploying_address: &T::AccountId, code_hash: &CodeHash, + input_data: &[u8], salt: &[u8], ) -> T::AccountId; } @@ -176,28 +176,21 @@ pub trait AddressGenerator { /// is only dependant on its inputs. It can therefore be used to reliably predict the /// address of a contract. This is akin to the formula of eth's CREATE2 opcode. There /// is no CREATE equivalent because CREATE2 is strictly more powerful. -/// -/// Formula: `hash(deploying_address ++ code_hash ++ salt)` +/// Formula: +/// `hash("contract_addr_v1" ++ deploying_address ++ code_hash ++ input_data ++ salt)` pub struct DefaultAddressGenerator; -impl AddressGenerator for DefaultAddressGenerator -where - T: frame_system::Config, - T::AccountId: UncheckedFrom + AsRef<[u8]>, -{ +impl AddressGenerator for DefaultAddressGenerator { fn generate_address( deploying_address: &T::AccountId, code_hash: &CodeHash, + input_data: &[u8], salt: &[u8], ) -> T::AccountId { - let buf: Vec<_> = deploying_address - .as_ref() - .iter() - .chain(code_hash.as_ref()) - .chain(salt) - .cloned() - .collect(); - UncheckedFrom::unchecked_from(T::Hashing::hash(&buf)) + let entropy = (b"contract_addr_v1", deploying_address, code_hash, input_data, salt) + .using_encoded(T::Hashing::hash); + Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref())) + .expect("infinite length input; no invalid inputs for type; qed") } } @@ -352,11 +345,7 @@ pub mod pallet { } #[pallet::hooks] - impl Hooks> for Pallet - where - T::AccountId: UncheckedFrom, - T::AccountId: AsRef<[u8]>, - { + impl Hooks> for Pallet { fn on_idle(_block: T::BlockNumber, remaining_weight: Weight) -> Weight { Storage::::process_deletion_queue_batch(remaining_weight) .saturating_add(T::WeightInfo::on_process_deletion_queue_batch()) @@ -385,8 +374,6 @@ pub mod pallet { #[pallet::call] impl Pallet where - T::AccountId: UncheckedFrom, - T::AccountId: AsRef<[u8]>, as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, { /// Deprecated version if [`Self::call`] for use in an in-storage `Call`. @@ -415,7 +402,7 @@ pub mod pallet { /// Deprecated version if [`Self::instantiate_with_code`] for use in an in-storage `Call`. #[pallet::call_index(1)] #[pallet::weight( - T::WeightInfo::instantiate_with_code(code.len() as u32, salt.len() as u32) + T::WeightInfo::instantiate_with_code(code.len() as u32, data.len() as u32, salt.len() as u32) .saturating_add(>::compat_weight(*gas_limit)) )] #[allow(deprecated)] @@ -445,7 +432,7 @@ pub mod pallet { /// Deprecated version if [`Self::instantiate`] for use in an in-storage `Call`. #[pallet::call_index(2)] #[pallet::weight( - T::WeightInfo::instantiate(salt.len() as u32).saturating_add(>::compat_weight(*gas_limit)) + T::WeightInfo::instantiate(data.len() as u32, salt.len() as u32).saturating_add(>::compat_weight(*gas_limit)) )] #[allow(deprecated)] #[deprecated(note = "1D weight is used in this extrinsic, please migrate to `instantiate`")] @@ -633,7 +620,7 @@ pub mod pallet { /// - The `deploy` function is executed in the context of the newly-created account. #[pallet::call_index(7)] #[pallet::weight( - T::WeightInfo::instantiate_with_code(code.len() as u32, salt.len() as u32) + T::WeightInfo::instantiate_with_code(code.len() as u32, data.len() as u32, salt.len() as u32) .saturating_add(*gas_limit) )] pub fn instantiate_with_code( @@ -647,6 +634,7 @@ pub mod pallet { ) -> DispatchResultWithPostInfo { let origin = ensure_signed(origin)?; let code_len = code.len() as u32; + let data_len = data.len() as u32; let salt_len = salt.len() as u32; let mut output = Self::internal_instantiate( origin, @@ -665,7 +653,7 @@ pub mod pallet { } output.gas_meter.into_dispatch_result( output.result.map(|(_address, result)| result), - T::WeightInfo::instantiate_with_code(code_len, salt_len), + T::WeightInfo::instantiate_with_code(code_len, data_len, salt_len), ) } @@ -676,7 +664,7 @@ pub mod pallet { /// must be supplied. #[pallet::call_index(8)] #[pallet::weight( - T::WeightInfo::instantiate(salt.len() as u32).saturating_add(*gas_limit) + T::WeightInfo::instantiate(data.len() as u32, salt.len() as u32).saturating_add(*gas_limit) )] pub fn instantiate( origin: OriginFor, @@ -688,6 +676,7 @@ pub mod pallet { salt: Vec, ) -> DispatchResultWithPostInfo { let origin = ensure_signed(origin)?; + let data_len = data.len() as u32; let salt_len = salt.len() as u32; let mut output = Self::internal_instantiate( origin, @@ -706,7 +695,7 @@ pub mod pallet { } output.gas_meter.into_dispatch_result( output.result.map(|(_address, output)| output), - T::WeightInfo::instantiate(salt_len), + T::WeightInfo::instantiate(data_len, salt_len), ) } } @@ -943,10 +932,7 @@ struct InternalOutput { result: Result, } -impl Pallet -where - T::AccountId: UncheckedFrom + AsRef<[u8]>, -{ +impl Pallet { /// Perform a call to a specified contract. /// /// This function is similar to [`Self::call`], but doesn't perform any address lookups @@ -1081,9 +1067,10 @@ where pub fn contract_address( deploying_address: &T::AccountId, code_hash: &CodeHash, + input_data: &[u8], salt: &[u8], ) -> T::AccountId { - T::AddressGenerator::generate_address(deploying_address, code_hash, salt) + T::AddressGenerator::generate_address(deploying_address, code_hash, input_data, salt) } /// Returns the code hash of the contract specified by `account` ID. diff --git a/frame/contracts/src/schedule.rs b/frame/contracts/src/schedule.rs index 4f2d3b61d0..e7a6b16e23 100644 --- a/frame/contracts/src/schedule.rs +++ b/frame/contracts/src/schedule.rs @@ -397,6 +397,9 @@ pub struct HostFnWeights { /// Weight surcharge that is claimed if `seal_instantiate` does a balance transfer. pub instantiate_transfer_surcharge: u64, + /// Weight per input byte supplied to `seal_instantiate`. + pub instantiate_per_input_byte: u64, + /// Weight per salt byte supplied to `seal_instantiate`. pub instantiate_per_salt_byte: u64, @@ -658,12 +661,20 @@ impl Default for HostFnWeights { call_per_cloned_byte: cost_batched_args!(seal_call_per_transfer_clone_kb, 0, 1), instantiate: cost_batched!(seal_instantiate), instantiate_transfer_surcharge: cost_byte_batched_args!( - seal_instantiate_per_transfer_salt_kb, + seal_instantiate_per_transfer_input_salt_kb, + 1, + 0, + 0 + ), + instantiate_per_input_byte: cost_byte_batched_args!( + seal_instantiate_per_transfer_input_salt_kb, + 0, 1, 0 ), instantiate_per_salt_byte: cost_byte_batched_args!( - seal_instantiate_per_transfer_salt_kb, + seal_instantiate_per_transfer_input_salt_kb, + 0, 0, 1 ), diff --git a/frame/contracts/src/storage.rs b/frame/contracts/src/storage.rs index c7644e6961..d40cc755c3 100644 --- a/frame/contracts/src/storage.rs +++ b/frame/contracts/src/storage.rs @@ -31,7 +31,6 @@ use frame_support::{ weights::Weight, }; use scale_info::TypeInfo; -use sp_core::crypto::UncheckedFrom; use sp_io::KillStorageResult; use sp_runtime::{ traits::{Hash, Saturating, Zero}, @@ -135,11 +134,7 @@ impl WriteOutcome { pub struct Storage(PhantomData); -impl Storage -where - T: Config, - T::AccountId: UncheckedFrom + AsRef<[u8]>, -{ +impl Storage { /// Reads a storage kv pair of a contract. /// /// The read is performed from the `trie_id` only. The `address` is not necessary. If the @@ -317,11 +312,10 @@ where Weight::from_ref_time(ref_time_weight) } - /// Generates a unique trie id by returning `hash(account_id ++ nonce)`. + /// Generates a unique trie id by returning `hash(account_id ++ nonce)`. pub fn generate_trie_id(account_id: &AccountIdOf, nonce: u64) -> TrieId { - let buf: Vec<_> = account_id.as_ref().iter().chain(&nonce.to_le_bytes()).cloned().collect(); - T::Hashing::hash(&buf) - .as_ref() + let buf = (account_id, nonce).using_encoded(T::Hashing::hash); + buf.as_ref() .to_vec() .try_into() .expect("Runtime uses a reasonable hash size. Hence sizeof(T::Hash) <= 128; qed") diff --git a/frame/contracts/src/storage/meter.rs b/frame/contracts/src/storage/meter.rs index 0a63eb42b8..94bca741af 100644 --- a/frame/contracts/src/storage/meter.rs +++ b/frame/contracts/src/storage/meter.rs @@ -29,7 +29,6 @@ use frame_support::{ DefaultNoBound, RuntimeDebugNoBound, }; use pallet_contracts_primitives::StorageDeposit as Deposit; -use sp_core::crypto::UncheckedFrom; use sp_runtime::{ traits::{Saturating, Zero}, FixedPointNumber, FixedU128, @@ -255,7 +254,6 @@ impl Default for Contribution { impl RawMeter where T: Config, - T::AccountId: UncheckedFrom + AsRef<[u8]>, E: Ext, S: State, { @@ -325,7 +323,6 @@ where impl RawMeter where T: Config, - T::AccountId: UncheckedFrom + AsRef<[u8]>, E: Ext, { /// Create new storage meter for the specified `origin` and `limit`. @@ -361,7 +358,6 @@ where impl RawMeter where T: Config, - T::AccountId: UncheckedFrom + AsRef<[u8]>, E: Ext, { /// Try to charge the `diff` from the meter. Fails if this would exceed the original limit. @@ -441,11 +437,7 @@ where } } -impl Ext for ReservingExt -where - T: Config, - T::AccountId: UncheckedFrom + AsRef<[u8]>, -{ +impl Ext for ReservingExt { fn check_limit( origin: &T::AccountId, limit: Option>, diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index 6121d880ca..85fe22ab9a 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -19,7 +19,7 @@ use self::test_utils::hash; use crate::{ chain_extension::{ ChainExtension, Environment, Ext, InitState, RegisteredChainExtension, - Result as ExtensionResult, RetVal, ReturnFlags, SysConfig, UncheckedFrom, + Result as ExtensionResult, RetVal, ReturnFlags, SysConfig, }, exec::{FixSizedKey, Frame}, storage::Storage, @@ -174,7 +174,6 @@ impl ChainExtension for TestExtension { fn call(&mut self, env: Environment) -> ExtensionResult where E: Ext, - ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, { let func_id = env.func_id(); let id = env.ext_id() as u32 | func_id as u32; @@ -219,7 +218,6 @@ impl ChainExtension for RevertingExtension { fn call(&mut self, _env: Environment) -> ExtensionResult where E: Ext, - ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, { Ok(RetVal::Diverging { flags: ReturnFlags::REVERT, data: vec![0x4B, 0x1D] }) } @@ -237,7 +235,6 @@ impl ChainExtension for DisabledExtension { fn call(&mut self, _env: Environment) -> ExtensionResult where E: Ext, - ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, { panic!("Disabled chain extensions are never called") } @@ -255,7 +252,6 @@ impl ChainExtension for TempStorageExtension { fn call(&mut self, env: Environment) -> ExtensionResult where E: Ext, - ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, { let func_id = env.func_id(); match func_id { @@ -543,16 +539,19 @@ fn instantiate_and_call_and_deposit_event() { initialize_block(2); // Check at the end to get hash on error easily - assert_ok!(Contracts::instantiate( - RuntimeOrigin::signed(ALICE), + let addr = Contracts::bare_instantiate( + ALICE, value, GAS_LIMIT, None, - code_hash, + Code::Existing(code_hash), vec![], vec![], - )); - let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + false, + ) + .result + .unwrap() + .account_id; assert!(ContractInfoOf::::contains_key(&addr)); assert_eq!( @@ -622,21 +621,24 @@ fn instantiate_and_call_and_deposit_event() { #[test] fn deposit_event_max_value_limit() { - let (wasm, code_hash) = compile_module::("event_size").unwrap(); + let (wasm, _code_hash) = compile_module::("event_size").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { // Create let _ = Balances::deposit_creating(&ALICE, 1_000_000); - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr = Contracts::bare_instantiate( + ALICE, 30_000, GAS_LIMIT, None, - wasm, + Code::Upload(wasm), vec![], vec![], - )); - let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + false, + ) + .result + .unwrap() + .account_id; // Call contract with allowed storage value. assert_ok!(Contracts::call( @@ -665,21 +667,24 @@ fn deposit_event_max_value_limit() { #[test] fn run_out_of_gas() { - let (wasm, code_hash) = compile_module::("run_out_of_gas").unwrap(); + let (wasm, _code_hash) = compile_module::("run_out_of_gas").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); let _ = Balances::deposit_creating(&ALICE, 1_000_000); - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr = Contracts::bare_instantiate( + ALICE, 100 * min_balance, GAS_LIMIT, None, - wasm, + Code::Upload(wasm), vec![], vec![], - )); - let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + false, + ) + .result + .unwrap() + .account_id; // Call the contract with a fixed gas limit. It must run out of gas because it just // loops forever. @@ -712,18 +717,21 @@ fn instantiate_unique_trie_id() { Determinism::Deterministic, ) .unwrap(); - let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); // Instantiate the contract and store its trie id for later comparison. - assert_ok!(Contracts::instantiate( - RuntimeOrigin::signed(ALICE), + let addr = Contracts::bare_instantiate( + ALICE, 0, GAS_LIMIT, None, - code_hash, + Code::Existing(code_hash), vec![], vec![], - )); + false, + ) + .result + .unwrap() + .account_id; let trie_id = get_contract(&addr).trie_id; // Try to instantiate it again without termination should yield an error. @@ -768,21 +776,24 @@ fn instantiate_unique_trie_id() { #[test] fn storage_max_value_limit() { - let (wasm, code_hash) = compile_module::("storage_size").unwrap(); + let (wasm, _code_hash) = compile_module::("storage_size").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { // Create let _ = Balances::deposit_creating(&ALICE, 1_000_000); - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr = Contracts::bare_instantiate( + ALICE, 30_000, GAS_LIMIT, None, - wasm, + Code::Upload(wasm), vec![], vec![], - )); - let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + false, + ) + .result + .unwrap() + .account_id; get_contract(&addr); // Call contract with allowed storage value. @@ -812,34 +823,46 @@ fn storage_max_value_limit() { #[test] fn deploy_and_call_other_contract() { - let (caller_wasm, caller_code_hash) = compile_module::("caller_contract").unwrap(); + let (caller_wasm, _caller_code_hash) = compile_module::("caller_contract").unwrap(); let (callee_wasm, callee_code_hash) = compile_module::("return_with_data").unwrap(); - let caller_addr = Contracts::contract_address(&ALICE, &caller_code_hash, &[]); - let callee_addr = Contracts::contract_address(&caller_addr, &callee_code_hash, &[]); ExtBuilder::default().existential_deposit(500).build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); // Create let _ = Balances::deposit_creating(&ALICE, 1_000_000); - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let caller_addr = Contracts::bare_instantiate( + ALICE, 100_000, GAS_LIMIT, None, - caller_wasm, + Code::Upload(caller_wasm), vec![], vec![], - )); - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + false, + ) + .result + .unwrap() + .account_id; + Contracts::bare_instantiate( + ALICE, 100_000, GAS_LIMIT, None, - callee_wasm, + Code::Upload(callee_wasm), 0u32.to_le_bytes().encode(), vec![42], - )); + false, + ) + .result + .unwrap(); + + let callee_addr = Contracts::contract_address( + &caller_addr, + &callee_code_hash, + &[0, 1, 34, 51, 68, 85, 102, 119], // hard coded in wasm + &[], + ); // Drop previous events initialize_block(2); @@ -939,23 +962,26 @@ fn deploy_and_call_other_contract() { #[test] fn delegate_call() { - let (caller_wasm, caller_code_hash) = compile_module::("delegate_call").unwrap(); + let (caller_wasm, _caller_code_hash) = compile_module::("delegate_call").unwrap(); let (callee_wasm, callee_code_hash) = compile_module::("delegate_call_lib").unwrap(); - let caller_addr = Contracts::contract_address(&ALICE, &caller_code_hash, &[]); ExtBuilder::default().existential_deposit(500).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); // Instantiate the 'caller' - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let caller_addr = Contracts::bare_instantiate( + ALICE, 300_000, GAS_LIMIT, None, - caller_wasm, + Code::Upload(caller_wasm), vec![], vec![], - )); + false, + ) + .result + .unwrap() + .account_id; // Only upload 'callee' code assert_ok!(Contracts::upload_code( RuntimeOrigin::signed(ALICE), @@ -977,21 +1003,24 @@ fn delegate_call() { #[test] fn cannot_self_destruct_through_draning() { - let (wasm, code_hash) = compile_module::("drain").unwrap(); + let (wasm, _code_hash) = compile_module::("drain").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); // Instantiate the BOB contract. - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr = Contracts::bare_instantiate( + ALICE, 1_000, GAS_LIMIT, None, - wasm, + Code::Upload(wasm), vec![], vec![], - )); - let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + false, + ) + .result + .unwrap() + .account_id; // Check that the BOB contract has been instantiated. get_contract(&addr); @@ -1017,22 +1046,25 @@ fn cannot_self_destruct_through_draning() { #[test] fn cannot_self_destruct_through_storage_refund_after_price_change() { - let (wasm, code_hash) = compile_module::("store").unwrap(); + let (wasm, _code_hash) = compile_module::("store").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); let min_balance = ::Currency::minimum_balance(); // Instantiate the BOB contract. - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr = Contracts::bare_instantiate( + ALICE, 0, GAS_LIMIT, None, - wasm, + Code::Upload(wasm), vec![], vec![], - )); - let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + false, + ) + .result + .unwrap() + .account_id; // Check that the BOB contract has been instantiated and has the minimum balance assert_eq!(get_contract(&addr).total_deposit(), min_balance); @@ -1073,21 +1105,24 @@ fn cannot_self_destruct_through_storage_refund_after_price_change() { #[test] fn cannot_self_destruct_by_refund_after_slash() { - let (wasm, code_hash) = compile_module::("store").unwrap(); + let (wasm, _code_hash) = compile_module::("store").unwrap(); ExtBuilder::default().existential_deposit(500).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); let min_balance = ::Currency::minimum_balance(); - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr = Contracts::bare_instantiate( + ALICE, 0, GAS_LIMIT, None, - wasm, + Code::Upload(wasm), vec![], vec![], - )); - let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + false, + ) + .result + .unwrap() + .account_id; // create 100 more reserved balance assert_ok!(Contracts::call( @@ -1156,21 +1191,24 @@ fn cannot_self_destruct_by_refund_after_slash() { #[test] fn cannot_self_destruct_while_live() { - let (wasm, code_hash) = compile_module::("self_destruct").unwrap(); + let (wasm, _code_hash) = compile_module::("self_destruct").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); // Instantiate the BOB contract. - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr = Contracts::bare_instantiate( + ALICE, 100_000, GAS_LIMIT, None, - wasm, + Code::Upload(wasm), vec![], vec![], - )); - let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + false, + ) + .result + .unwrap() + .account_id; // Check that the BOB contract has been instantiated. get_contract(&addr); @@ -1202,16 +1240,19 @@ fn self_destruct_works() { let _ = Balances::deposit_creating(&DJANGO, 1_000_000); // Instantiate the BOB contract. - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr = Contracts::bare_instantiate( + ALICE, 100_000, GAS_LIMIT, None, - wasm, + Code::Upload(wasm), vec![], vec![], - )); - let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + false, + ) + .result + .unwrap() + .account_id; // Check that the BOB contract has been instantiated. get_contract(&addr); @@ -1290,36 +1331,32 @@ fn self_destruct_works() { #[test] fn destroy_contract_and_transfer_funds() { let (callee_wasm, callee_code_hash) = compile_module::("self_destruct").unwrap(); - let (caller_wasm, caller_code_hash) = compile_module::("destroy_and_transfer").unwrap(); + let (caller_wasm, _caller_code_hash) = compile_module::("destroy_and_transfer").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - // Create + // Create code hash for bob to instantiate let _ = Balances::deposit_creating(&ALICE, 1_000_000); - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), - 200_000, - GAS_LIMIT, - None, - callee_wasm, - vec![], - vec![42] - )); + Contracts::bare_upload_code(ALICE, callee_wasm, None, Determinism::Deterministic).unwrap(); // This deploys the BOB contract, which in turn deploys the CHARLIE contract during // construction. - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr_bob = Contracts::bare_instantiate( + ALICE, 200_000, GAS_LIMIT, None, - caller_wasm, + Code::Upload(caller_wasm), callee_code_hash.as_ref().to_vec(), vec![], - )); - let addr_bob = Contracts::contract_address(&ALICE, &caller_code_hash, &[]); - let addr_charlie = Contracts::contract_address(&addr_bob, &callee_code_hash, &[0x47, 0x11]); + false, + ) + .result + .unwrap() + .account_id; // Check that the CHARLIE contract has been instantiated. + let addr_charlie = + Contracts::contract_address(&addr_bob, &callee_code_hash, &[], &[0x47, 0x11]); get_contract(&addr_charlie); // Call BOB, which calls CHARLIE, forcing CHARLIE to self-destruct. @@ -1361,22 +1398,25 @@ fn cannot_self_destruct_in_constructor() { #[test] fn crypto_hashes() { - let (wasm, code_hash) = compile_module::("crypto_hashes").unwrap(); + let (wasm, _code_hash) = compile_module::("crypto_hashes").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); // Instantiate the CRYPTO_HASHES contract. - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr = Contracts::bare_instantiate( + ALICE, 100_000, GAS_LIMIT, None, - wasm, + Code::Upload(wasm), vec![], vec![], - )); - let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + false, + ) + .result + .unwrap() + .account_id; // Perform the call. let input = b"_DEAD_BEEF"; use sp_io::hashing::*; @@ -1419,21 +1459,24 @@ fn crypto_hashes() { #[test] fn transfer_return_code() { - let (wasm, code_hash) = compile_module::("transfer_return_code").unwrap(); + let (wasm, _code_hash) = compile_module::("transfer_return_code").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr = Contracts::bare_instantiate( + ALICE, min_balance * 100, GAS_LIMIT, None, - wasm, + Code::Upload(wasm), vec![], vec![], - ),); - let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + false, + ) + .result + .unwrap() + .account_id; // Contract has only the minimal balance so any transfer will fail. Balances::make_free_balance_be(&addr, min_balance); @@ -1474,23 +1517,26 @@ fn transfer_return_code() { #[test] fn call_return_code() { - let (caller_code, caller_hash) = compile_module::("call_return_code").unwrap(); - let (callee_code, callee_hash) = compile_module::("ok_trap_revert").unwrap(); + let (caller_code, _caller_hash) = compile_module::("call_return_code").unwrap(); + let (callee_code, _callee_hash) = compile_module::("ok_trap_revert").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); let _ = Balances::deposit_creating(&CHARLIE, 1000 * min_balance); - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr_bob = Contracts::bare_instantiate( + ALICE, min_balance * 100, GAS_LIMIT, None, - caller_code, + Code::Upload(caller_code), vec![0], vec![], - ),); - let addr_bob = Contracts::contract_address(&ALICE, &caller_hash, &[]); + false, + ) + .result + .unwrap() + .account_id; Balances::make_free_balance_be(&addr_bob, min_balance); // Contract calls into Django which is no valid contract @@ -1508,16 +1554,19 @@ fn call_return_code() { .unwrap(); assert_return_code!(result, RuntimeReturnCode::NotCallable); - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(CHARLIE), + let addr_django = Contracts::bare_instantiate( + CHARLIE, min_balance * 100, GAS_LIMIT, None, - callee_code, + Code::Upload(callee_code), vec![0], vec![], - ),); - let addr_django = Contracts::contract_address(&CHARLIE, &callee_hash, &[]); + false, + ) + .result + .unwrap() + .account_id; Balances::make_free_balance_be(&addr_django, min_balance); // Contract has only the minimal balance so any transfer will fail. @@ -1605,7 +1654,7 @@ fn call_return_code() { #[test] fn instantiate_return_code() { - let (caller_code, caller_hash) = compile_module::("instantiate_return_code").unwrap(); + let (caller_code, _caller_hash) = compile_module::("instantiate_return_code").unwrap(); let (callee_code, callee_hash) = compile_module::("ok_trap_revert").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); @@ -1621,18 +1670,21 @@ fn instantiate_return_code() { callee_code, vec![], vec![], - ),); + )); - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr = Contracts::bare_instantiate( + ALICE, min_balance * 100, GAS_LIMIT, None, - caller_code, + Code::Upload(caller_code), vec![], vec![], - ),); - let addr = Contracts::contract_address(&ALICE, &caller_hash, &[]); + false, + ) + .result + .unwrap() + .account_id; // Contract has only the minimal balance so any transfer will fail. Balances::make_free_balance_be(&addr, min_balance); @@ -1741,20 +1793,23 @@ fn disabled_chain_extension_wont_deploy() { #[test] fn disabled_chain_extension_errors_on_call() { - let (code, hash) = compile_module::("chain_extension").unwrap(); + let (code, _hash) = compile_module::("chain_extension").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr = Contracts::bare_instantiate( + ALICE, min_balance * 100, GAS_LIMIT, None, - code, + Code::Upload(code), vec![], vec![], - ),); - let addr = Contracts::contract_address(&ALICE, &hash, &[]); + false, + ) + .result + .unwrap() + .account_id; TestExtension::disable(); assert_err_ignore_postinfo!( Contracts::call(RuntimeOrigin::signed(ALICE), addr.clone(), 0, GAS_LIMIT, None, vec![],), @@ -1765,20 +1820,23 @@ fn disabled_chain_extension_errors_on_call() { #[test] fn chain_extension_works() { - let (code, hash) = compile_module::("chain_extension").unwrap(); + let (code, _hash) = compile_module::("chain_extension").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr = Contracts::bare_instantiate( + ALICE, min_balance * 100, GAS_LIMIT, None, - code, + Code::Upload(code), vec![], vec![], - ),); - let addr = Contracts::contract_address(&ALICE, &hash, &[]); + false, + ) + .result + .unwrap() + .account_id; // 0 = read input buffer and pass it through as output let input: Vec = ExtensionInput { extension_id: 0, func_id: 0, extra: &[99] }.into(); @@ -1901,20 +1959,23 @@ fn chain_extension_works() { #[test] fn chain_extension_temp_storage_works() { - let (code, hash) = compile_module::("chain_extension_temp_storage").unwrap(); + let (code, _hash) = compile_module::("chain_extension_temp_storage").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr = Contracts::bare_instantiate( + ALICE, min_balance * 100, GAS_LIMIT, None, - code, + Code::Upload(code), vec![], vec![], - ),); - let addr = Contracts::contract_address(&ALICE, &hash, &[]); + false, + ) + .result + .unwrap() + .account_id; // Call func 0 and func 1 back to back. let stop_recursion = 0u8; @@ -1943,22 +2004,25 @@ fn chain_extension_temp_storage_works() { #[test] fn lazy_removal_works() { - let (code, hash) = compile_module::("self_destruct").unwrap(); + let (code, _hash) = compile_module::("self_destruct").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr = Contracts::bare_instantiate( + ALICE, min_balance * 100, GAS_LIMIT, None, - code, + Code::Upload(code), vec![], vec![], - ),); + false, + ) + .result + .unwrap() + .account_id; - let addr = Contracts::contract_address(&ALICE, &hash, &[]); let info = get_contract(&addr); let trie = &info.child_trie_info(); @@ -2010,24 +2074,27 @@ fn lazy_removal_on_full_queue_works_on_initialize() { #[test] fn lazy_batch_removal_works() { - let (code, hash) = compile_module::("self_destruct").unwrap(); + let (code, _hash) = compile_module::("self_destruct").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); let mut tries: Vec = vec![]; for i in 0..3u8 { - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr = Contracts::bare_instantiate( + ALICE, min_balance * 100, GAS_LIMIT, None, - code.clone(), + Code::Upload(code.clone()), vec![], vec![i], - ),); + false, + ) + .result + .unwrap() + .account_id; - let addr = Contracts::contract_address(&ALICE, &hash, &[i]); let info = get_contract(&addr); let trie = &info.child_trie_info(); @@ -2063,7 +2130,7 @@ fn lazy_batch_removal_works() { #[test] fn lazy_removal_partial_remove_works() { - let (code, hash) = compile_module::("self_destruct").unwrap(); + let (code, _hash) = compile_module::("self_destruct").unwrap(); // We create a contract with some extra keys above the weight limit let extra_keys = 7u32; @@ -2079,17 +2146,20 @@ fn lazy_removal_partial_remove_works() { let min_balance = ::Currency::minimum_balance(); let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr = Contracts::bare_instantiate( + ALICE, min_balance * 100, GAS_LIMIT, None, - code, + Code::Upload(code), vec![], vec![], - ),); + false, + ) + .result + .unwrap() + .account_id; - let addr = Contracts::contract_address(&ALICE, &hash, &[]); let info = get_contract(&addr); // Put value into the contracts child trie @@ -2186,22 +2256,25 @@ fn lazy_removal_does_no_run_on_full_queue_and_full_block() { #[test] fn lazy_removal_does_no_run_on_low_remaining_weight() { - let (code, hash) = compile_module::("self_destruct").unwrap(); + let (code, _hash) = compile_module::("self_destruct").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr = Contracts::bare_instantiate( + ALICE, min_balance * 100, GAS_LIMIT, None, - code, + Code::Upload(code), vec![], vec![], - ),); + false, + ) + .result + .unwrap() + .account_id; - let addr = Contracts::contract_address(&ALICE, &hash, &[]); let info = get_contract(&addr); let trie = &info.child_trie_info(); @@ -2250,7 +2323,7 @@ fn lazy_removal_does_no_run_on_low_remaining_weight() { #[test] fn lazy_removal_does_not_use_all_weight() { - let (code, hash) = compile_module::("self_destruct").unwrap(); + let (code, _hash) = compile_module::("self_destruct").unwrap(); let weight_limit = Weight::from_ref_time(5_000_000_000); let mut ext = ExtBuilder::default().existential_deposit(50).build(); @@ -2259,17 +2332,20 @@ fn lazy_removal_does_not_use_all_weight() { let min_balance = ::Currency::minimum_balance(); let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr = Contracts::bare_instantiate( + ALICE, min_balance * 100, GAS_LIMIT, None, - code, + Code::Upload(code), vec![], vec![], - ),); + false, + ) + .result + .unwrap() + .account_id; - let addr = Contracts::contract_address(&ALICE, &hash, &[]); let info = get_contract(&addr); let (weight_per_key, max_keys) = Storage::::deletion_budget(1, weight_limit); @@ -2334,22 +2410,24 @@ fn lazy_removal_does_not_use_all_weight() { #[test] fn deletion_queue_full() { - let (code, hash) = compile_module::("self_destruct").unwrap(); + let (code, _hash) = compile_module::("self_destruct").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr = Contracts::bare_instantiate( + ALICE, min_balance * 100, GAS_LIMIT, None, - code, + Code::Upload(code), vec![], vec![], - ),); - - let addr = Contracts::contract_address(&ALICE, &hash, &[]); + false, + ) + .result + .unwrap() + .account_id; // fill the deletion queue up until its limit Storage::::fill_queue_with_dummies(); @@ -2373,43 +2451,50 @@ fn refcounter() { let min_balance = ::Currency::minimum_balance(); // Create two contracts with the same code and check that they do in fact share it. - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr0 = Contracts::bare_instantiate( + ALICE, min_balance * 100, GAS_LIMIT, None, - wasm.clone(), + Code::Upload(wasm.clone()), vec![], vec![0], - )); - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + false, + ) + .result + .unwrap() + .account_id; + let addr1 = Contracts::bare_instantiate( + ALICE, min_balance * 100, GAS_LIMIT, None, - wasm.clone(), + Code::Upload(wasm.clone()), vec![], vec![1], - )); + false, + ) + .result + .unwrap() + .account_id; assert_refcount!(code_hash, 2); // Sharing should also work with the usual instantiate call - assert_ok!(Contracts::instantiate( - RuntimeOrigin::signed(ALICE), + let addr2 = Contracts::bare_instantiate( + ALICE, min_balance * 100, GAS_LIMIT, None, - code_hash, + Code::Existing(code_hash), vec![], vec![2], - )); + false, + ) + .result + .unwrap() + .account_id; assert_refcount!(code_hash, 3); - // addresses of all three existing contracts - let addr0 = Contracts::contract_address(&ALICE, &code_hash, &[0]); - let addr1 = Contracts::contract_address(&ALICE, &code_hash, &[1]); - let addr2 = Contracts::contract_address(&ALICE, &code_hash, &[2]); - // Terminating one contract should decrement the refcount assert_ok!(Contracts::call( RuntimeOrigin::signed(ALICE), @@ -2461,17 +2546,19 @@ fn reinstrument_does_charge() { let zero = 0u32.to_le_bytes().encode(); let code_len = wasm.len() as u32; - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr = Contracts::bare_instantiate( + ALICE, min_balance * 100, GAS_LIMIT, None, - wasm, + Code::Upload(wasm), zero.clone(), vec![], - )); - - let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + false, + ) + .result + .unwrap() + .account_id; // Call the contract two times without reinstrument @@ -2531,20 +2618,23 @@ fn reinstrument_does_charge() { #[test] fn debug_message_works() { - let (wasm, code_hash) = compile_module::("debug_message_works").unwrap(); + let (wasm, _code_hash) = compile_module::("debug_message_works").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr = Contracts::bare_instantiate( + ALICE, 30_000, GAS_LIMIT, None, - wasm, + Code::Upload(wasm), vec![], vec![], - ),); - let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + false, + ) + .result + .unwrap() + .account_id; let result = Contracts::bare_call( ALICE, addr, @@ -2563,20 +2653,23 @@ fn debug_message_works() { #[test] fn debug_message_logging_disabled() { - let (wasm, code_hash) = compile_module::("debug_message_logging_disabled").unwrap(); + let (wasm, _code_hash) = compile_module::("debug_message_logging_disabled").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr = Contracts::bare_instantiate( + ALICE, 30_000, GAS_LIMIT, None, - wasm, + Code::Upload(wasm), vec![], vec![], - ),); - let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + false, + ) + .result + .unwrap() + .account_id; // disable logging by passing `false` let result = Contracts::bare_call( ALICE, @@ -2597,20 +2690,23 @@ fn debug_message_logging_disabled() { #[test] fn debug_message_invalid_utf8() { - let (wasm, code_hash) = compile_module::("debug_message_invalid_utf8").unwrap(); + let (wasm, _code_hash) = compile_module::("debug_message_invalid_utf8").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr = Contracts::bare_instantiate( + ALICE, 30_000, GAS_LIMIT, None, - wasm, + Code::Upload(wasm), vec![], vec![], - ),); - let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + false, + ) + .result + .unwrap() + .account_id; let result = Contracts::bare_call( ALICE, addr, @@ -2627,34 +2723,40 @@ fn debug_message_invalid_utf8() { #[test] fn gas_estimation_nested_call_fixed_limit() { - let (caller_code, caller_hash) = compile_module::("call_with_limit").unwrap(); - let (callee_code, callee_hash) = compile_module::("dummy").unwrap(); + let (caller_code, _caller_hash) = compile_module::("call_with_limit").unwrap(); + let (callee_code, _callee_hash) = compile_module::("dummy").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); let _ = Balances::deposit_creating(&CHARLIE, 1000 * min_balance); - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr_caller = Contracts::bare_instantiate( + ALICE, min_balance * 100, GAS_LIMIT, None, - caller_code, + Code::Upload(caller_code), vec![], vec![0], - ),); - let addr_caller = Contracts::contract_address(&ALICE, &caller_hash, &[0]); + false, + ) + .result + .unwrap() + .account_id; - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr_callee = Contracts::bare_instantiate( + ALICE, min_balance * 100, GAS_LIMIT, None, - callee_code, + Code::Upload(callee_code), vec![], vec![1], - ),); - let addr_callee = Contracts::contract_address(&ALICE, &callee_hash, &[1]); + false, + ) + .result + .unwrap() + .account_id; let input: Vec = AsRef::<[u8]>::as_ref(&addr_callee) .iter() @@ -2698,34 +2800,40 @@ fn gas_estimation_nested_call_fixed_limit() { #[test] fn gas_estimation_call_runtime() { use codec::Decode; - let (caller_code, caller_hash) = compile_module::("call_runtime").unwrap(); - let (callee_code, callee_hash) = compile_module::("dummy").unwrap(); + let (caller_code, _caller_hash) = compile_module::("call_runtime").unwrap(); + let (callee_code, _callee_hash) = compile_module::("dummy").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); let _ = Balances::deposit_creating(&CHARLIE, 1000 * min_balance); - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr_caller = Contracts::bare_instantiate( + ALICE, min_balance * 100, GAS_LIMIT, None, - caller_code, + Code::Upload(caller_code), vec![], vec![0], - ),); - let addr_caller = Contracts::contract_address(&ALICE, &caller_hash, &[0]); + false, + ) + .result + .unwrap() + .account_id; - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr_callee = Contracts::bare_instantiate( + ALICE, min_balance * 100, GAS_LIMIT, None, - callee_code, + Code::Upload(callee_code), vec![], vec![1], - ),); - let addr_callee = Contracts::contract_address(&ALICE, &callee_hash, &[1]); + false, + ) + .result + .unwrap() + .account_id; // Call something trivial with a huge gas limit so that we can observe the effects // of pre-charging. This should create a difference between consumed and required. @@ -2770,22 +2878,25 @@ fn gas_estimation_call_runtime() { #[test] fn ecdsa_recover() { - let (wasm, code_hash) = compile_module::("ecdsa_recover").unwrap(); + let (wasm, _code_hash) = compile_module::("ecdsa_recover").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); // Instantiate the ecdsa_recover contract. - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr = Contracts::bare_instantiate( + ALICE, 100_000, GAS_LIMIT, None, - wasm, + Code::Upload(wasm), vec![], vec![], - )); - let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + false, + ) + .result + .unwrap() + .account_id; #[rustfmt::skip] let signature: [u8; 65] = [ @@ -3072,16 +3183,19 @@ fn instantiate_with_zero_balance_works() { initialize_block(2); // Instantiate the BOB contract. - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr = Contracts::bare_instantiate( + ALICE, 0, GAS_LIMIT, None, - wasm, + Code::Upload(wasm), vec![], vec![], - )); - let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + false, + ) + .result + .unwrap() + .account_id; // Check that the BOB contract has been instantiated. get_contract(&addr); @@ -3165,16 +3279,19 @@ fn instantiate_with_below_existential_deposit_works() { initialize_block(2); // Instantiate the BOB contract. - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr = Contracts::bare_instantiate( + ALICE, 50, GAS_LIMIT, None, - wasm, + Code::Upload(wasm), vec![], vec![], - )); - let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + false, + ) + .result + .unwrap() + .account_id; // Check that the BOB contract has been instantiated. get_contract(&addr); @@ -3258,21 +3375,24 @@ fn instantiate_with_below_existential_deposit_works() { #[test] fn storage_deposit_works() { - let (wasm, code_hash) = compile_module::("multi_store").unwrap(); + let (wasm, _code_hash) = compile_module::("multi_store").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); let mut deposit = ::Currency::minimum_balance(); - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr = Contracts::bare_instantiate( + ALICE, 0, GAS_LIMIT, None, - wasm, + Code::Upload(wasm), vec![], vec![], - )); - let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + false, + ) + .result + .unwrap() + .account_id; // Drop previous events initialize_block(2); @@ -3413,16 +3533,19 @@ fn set_code_extrinsic() { ExtBuilder::default().existential_deposit(100).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr = Contracts::bare_instantiate( + ALICE, 0, GAS_LIMIT, None, - wasm, + Code::Upload(wasm), vec![], vec![], - )); - let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + false, + ) + .result + .unwrap() + .account_id; assert_ok!(Contracts::upload_code( RuntimeOrigin::signed(ALICE), @@ -3490,21 +3613,24 @@ fn set_code_extrinsic() { #[test] fn call_after_killed_account_needs_funding() { - let (wasm, code_hash) = compile_module::("dummy").unwrap(); + let (wasm, _code_hash) = compile_module::("dummy").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); let min_balance = ::Currency::minimum_balance(); - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr = Contracts::bare_instantiate( + ALICE, 700, GAS_LIMIT, None, - wasm, + Code::Upload(wasm), vec![], vec![], - )); - let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + false, + ) + .result + .unwrap() + .account_id; // Drop previous events initialize_block(2); @@ -3791,21 +3917,23 @@ fn set_code_hash() { let (wasm, code_hash) = compile_module::("set_code_hash").unwrap(); let (new_wasm, new_code_hash) = compile_module::("new_set_code_hash_contract").unwrap(); - let contract_addr = Contracts::contract_address(&ALICE, &code_hash, &[]); - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); // Instantiate the 'caller' - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let contract_addr = Contracts::bare_instantiate( + ALICE, 300_000, GAS_LIMIT, None, - wasm, + Code::Upload(wasm), vec![], vec![], - )); + false, + ) + .result + .unwrap() + .account_id; // upload new code assert_ok!(Contracts::upload_code( RuntimeOrigin::signed(ALICE), @@ -3882,22 +4010,25 @@ fn set_code_hash() { #[test] fn storage_deposit_limit_is_enforced() { - let (wasm, code_hash) = compile_module::("store").unwrap(); + let (wasm, _code_hash) = compile_module::("store").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); let min_balance = ::Currency::minimum_balance(); // Instantiate the BOB contract. - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr = Contracts::bare_instantiate( + ALICE, 0, GAS_LIMIT, None, - wasm, + Code::Upload(wasm), vec![], vec![], - )); - let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + false, + ) + .result + .unwrap() + .account_id; // Check that the BOB contract has been instantiated and has the minimum balance assert_eq!(get_contract(&addr).total_deposit(), min_balance); @@ -3920,33 +4051,39 @@ fn storage_deposit_limit_is_enforced() { #[test] fn storage_deposit_limit_is_enforced_late() { - let (wasm_caller, code_hash_caller) = + let (wasm_caller, _code_hash_caller) = compile_module::("create_storage_and_call").unwrap(); - let (wasm_callee, code_hash_callee) = compile_module::("store").unwrap(); + let (wasm_callee, _code_hash_callee) = compile_module::("store").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); // Create both contracts: Constructors do nothing. - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr_caller = Contracts::bare_instantiate( + ALICE, 0, GAS_LIMIT, None, - wasm_caller, + Code::Upload(wasm_caller), vec![], vec![], - )); - let addr_caller = Contracts::contract_address(&ALICE, &code_hash_caller, &[]); - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + false, + ) + .result + .unwrap() + .account_id; + let addr_callee = Contracts::bare_instantiate( + ALICE, 0, GAS_LIMIT, None, - wasm_callee, + Code::Upload(wasm_callee), vec![], vec![], - )); - let addr_callee = Contracts::contract_address(&ALICE, &code_hash_callee, &[]); + false, + ) + .result + .unwrap() + .account_id; // Create 100 bytes of storage with a price of per byte // This is 100 Balance + 2 Balance for the item @@ -4059,23 +4196,26 @@ fn storage_deposit_limit_is_enforced_late() { #[test] fn deposit_limit_honors_liquidity_restrictions() { - let (wasm, code_hash) = compile_module::("store").unwrap(); + let (wasm, _code_hash) = compile_module::("store").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); let _ = Balances::deposit_creating(&BOB, 1_000); let min_balance = ::Currency::minimum_balance(); // Instantiate the BOB contract. - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr = Contracts::bare_instantiate( + ALICE, 0, GAS_LIMIT, None, - wasm, + Code::Upload(wasm), vec![], vec![], - )); - let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + false, + ) + .result + .unwrap() + .account_id; // Check that the contract has been instantiated and has the minimum balance assert_eq!(get_contract(&addr).total_deposit(), min_balance); @@ -4100,23 +4240,26 @@ fn deposit_limit_honors_liquidity_restrictions() { #[test] fn deposit_limit_honors_existential_deposit() { - let (wasm, code_hash) = compile_module::("store").unwrap(); + let (wasm, _code_hash) = compile_module::("store").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); let _ = Balances::deposit_creating(&BOB, 1_000); let min_balance = ::Currency::minimum_balance(); // Instantiate the BOB contract. - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr = Contracts::bare_instantiate( + ALICE, 0, GAS_LIMIT, None, - wasm, + Code::Upload(wasm), vec![], vec![], - )); - let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + false, + ) + .result + .unwrap() + .account_id; // Check that the contract has been instantiated and has the minimum balance assert_eq!(get_contract(&addr).total_deposit(), min_balance); @@ -4140,23 +4283,26 @@ fn deposit_limit_honors_existential_deposit() { #[test] fn deposit_limit_honors_min_leftover() { - let (wasm, code_hash) = compile_module::("store").unwrap(); + let (wasm, _code_hash) = compile_module::("store").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); let _ = Balances::deposit_creating(&BOB, 1_000); let min_balance = ::Currency::minimum_balance(); // Instantiate the BOB contract. - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let addr = Contracts::bare_instantiate( + ALICE, 0, GAS_LIMIT, None, - wasm, + Code::Upload(wasm), vec![], vec![], - )); - let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + false, + ) + .result + .unwrap() + .account_id; // Check that the contract has been instantiated and has the minimum balance assert_eq!(get_contract(&addr).total_deposit(), min_balance); @@ -4420,21 +4566,24 @@ fn delegate_call_indeterministic_code() { #[test] fn reentrance_count_works_with_call() { - let (wasm, code_hash) = compile_module::("reentrance_count_call").unwrap(); - let contract_addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + let (wasm, _code_hash) = compile_module::("reentrance_count_call").unwrap(); ExtBuilder::default().existential_deposit(100).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let contract_addr = Contracts::bare_instantiate( + ALICE, 300_000, GAS_LIMIT, None, - wasm, + Code::Upload(wasm), vec![], vec![], - )); + false, + ) + .result + .unwrap() + .account_id; // passing reentrant count to the input let input = 0.encode(); @@ -4457,20 +4606,23 @@ fn reentrance_count_works_with_call() { #[test] fn reentrance_count_works_with_delegated_call() { let (wasm, code_hash) = compile_module::("reentrance_count_delegated_call").unwrap(); - let contract_addr = Contracts::contract_address(&ALICE, &code_hash, &[]); ExtBuilder::default().existential_deposit(100).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let contract_addr = Contracts::bare_instantiate( + ALICE, 300_000, GAS_LIMIT, None, - wasm, + Code::Upload(wasm), vec![], vec![], - )); + false, + ) + .result + .unwrap() + .account_id; // adding a callstack height to the input let input = (code_hash, 1).encode(); @@ -4492,36 +4644,40 @@ fn reentrance_count_works_with_delegated_call() { #[test] fn account_reentrance_count_works() { - let (wasm, code_hash) = compile_module::("account_reentrance_count_call").unwrap(); - let (wasm_reentrance_count, code_hash_reentrance_count) = + let (wasm, _code_hash) = compile_module::("account_reentrance_count_call").unwrap(); + let (wasm_reentrance_count, _code_hash_reentrance_count) = compile_module::("reentrance_count_call").unwrap(); ExtBuilder::default().existential_deposit(100).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let contract_addr = Contracts::bare_instantiate( + ALICE, 300_000, GAS_LIMIT, None, - wasm, + Code::Upload(wasm), vec![], vec![], - )); + false, + ) + .result + .unwrap() + .account_id; - assert_ok!(Contracts::instantiate_with_code( - RuntimeOrigin::signed(ALICE), + let another_contract_addr = Contracts::bare_instantiate( + ALICE, 300_000, GAS_LIMIT, None, - wasm_reentrance_count, + Code::Upload(wasm_reentrance_count), vec![], - vec![] - )); - - let contract_addr = Contracts::contract_address(&ALICE, &code_hash, &[]); - let another_contract_addr = - Contracts::contract_address(&ALICE, &code_hash_reentrance_count, &[]); + vec![], + false, + ) + .result + .unwrap() + .account_id; let result1 = Contracts::bare_call( ALICE, diff --git a/frame/contracts/src/wasm/code_cache.rs b/frame/contracts/src/wasm/code_cache.rs index eb337ac682..410d269215 100644 --- a/frame/contracts/src/wasm/code_cache.rs +++ b/frame/contracts/src/wasm/code_cache.rs @@ -41,7 +41,6 @@ use frame_support::{ traits::{Get, ReservableCurrency}, WeakBoundedVec, }; -use sp_core::crypto::UncheckedFrom; use sp_runtime::traits::BadOrigin; use sp_std::vec; @@ -49,10 +48,7 @@ use sp_std::vec; /// /// Increments the refcount of the in-storage `prefab_module` if it already exists in storage /// under the specified `code_hash`. -pub fn store(mut module: PrefabWasmModule, instantiated: bool) -> DispatchResult -where - T::AccountId: UncheckedFrom + AsRef<[u8]>, -{ +pub fn store(mut module: PrefabWasmModule, instantiated: bool) -> DispatchResult { let code_hash = sp_std::mem::take(&mut module.code_hash); >::mutate(&code_hash, |existing| match existing { Some(existing) => { @@ -135,10 +131,7 @@ pub fn increment_refcount(code_hash: CodeHash) -> Result<(), Dispa } /// Try to remove code together with all associated information. -pub fn try_remove(origin: &T::AccountId, code_hash: CodeHash) -> DispatchResult -where - T::AccountId: UncheckedFrom + AsRef<[u8]>, -{ +pub fn try_remove(origin: &T::AccountId, code_hash: CodeHash) -> DispatchResult { >::try_mutate_exists(&code_hash, |existing| { if let Some(owner_info) = existing { ensure!(owner_info.refcount == 0, >::CodeInUse); @@ -164,10 +157,7 @@ pub fn load( code_hash: CodeHash, schedule: &Schedule, gas_meter: &mut GasMeter, -) -> Result, DispatchError> -where - T::AccountId: UncheckedFrom + AsRef<[u8]>, -{ +) -> Result, DispatchError> { let max_code_len = T::MaxCodeLen::get(); let charged = gas_meter.charge(CodeToken::Load(max_code_len))?; @@ -192,10 +182,7 @@ where pub fn reinstrument( prefab_module: &mut PrefabWasmModule, schedule: &Schedule, -) -> Result -where - T::AccountId: UncheckedFrom + AsRef<[u8]>, -{ +) -> Result { let original_code = >::get(&prefab_module.code_hash).ok_or(Error::::CodeNotFound)?; let original_code_len = original_code.len(); diff --git a/frame/contracts/src/wasm/mod.rs b/frame/contracts/src/wasm/mod.rs index d85dac95cc..903ca0abb5 100644 --- a/frame/contracts/src/wasm/mod.rs +++ b/frame/contracts/src/wasm/mod.rs @@ -36,7 +36,7 @@ use crate::{ }; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::dispatch::{DispatchError, DispatchResult}; -use sp_core::{crypto::UncheckedFrom, Get}; +use sp_core::Get; use sp_runtime::RuntimeDebug; use sp_std::prelude::*; #[cfg(test)] @@ -140,10 +140,7 @@ impl ExportedFunction { } } -impl PrefabWasmModule -where - T::AccountId: UncheckedFrom + AsRef<[u8]>, -{ +impl PrefabWasmModule { /// Create the module by checking and instrumenting `original_code`. /// /// This does **not** store the module. For this one need to either call [`Self::store`] @@ -263,10 +260,7 @@ impl OwnerInfo { } } -impl Executable for PrefabWasmModule -where - T::AccountId: UncheckedFrom + AsRef<[u8]>, -{ +impl Executable for PrefabWasmModule { fn from_storage( code_hash: CodeHash, schedule: &Schedule, @@ -476,7 +470,7 @@ mod tests { salt: salt.to_vec(), }); Ok(( - Contracts::::contract_address(&ALICE, &code_hash, salt), + Contracts::::contract_address(&ALICE, &code_hash, &data, salt), ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }, )) } diff --git a/frame/contracts/src/wasm/prepare.rs b/frame/contracts/src/wasm/prepare.rs index c63a5b1e13..9bb58bb02b 100644 --- a/frame/contracts/src/wasm/prepare.rs +++ b/frame/contracts/src/wasm/prepare.rs @@ -26,7 +26,6 @@ use crate::{ AccountIdOf, CodeVec, Config, Error, Schedule, }; use codec::{Encode, MaxEncodedLen}; -use sp_core::crypto::UncheckedFrom; use sp_runtime::{traits::Hash, DispatchError}; use sp_std::prelude::*; use wasm_instrument::{ @@ -396,7 +395,6 @@ fn instrument( where E: Environment<()>, T: Config, - T::AccountId: UncheckedFrom + AsRef<[u8]>, { // Do not enable any features here. Any additional feature needs to be carefully // checked for potential security issues. For example, enabling multi value could lead @@ -500,7 +498,6 @@ pub fn prepare( where E: Environment<()>, T: Config, - T::AccountId: UncheckedFrom + AsRef<[u8]>, { let (code, (initial, maximum)) = instrument::(original_code.as_ref(), schedule, determinism, try_instantiate)?; @@ -547,7 +544,6 @@ pub fn reinstrument( where E: Environment<()>, T: Config, - T::AccountId: UncheckedFrom + AsRef<[u8]>, { instrument::(original_code, schedule, determinism, TryInstantiate::Skip) .map_err(|(err, msg)| { diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index 066c1d71ea..aaacb9e5f8 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -29,7 +29,6 @@ use codec::{Decode, DecodeLimit, Encode, MaxEncodedLen}; use frame_support::{dispatch::DispatchError, ensure, traits::Get, weights::Weight, RuntimeDebug}; use pallet_contracts_primitives::{ExecReturnValue, ReturnFlags}; use pallet_contracts_proc_macro::define_env; -use sp_core::crypto::UncheckedFrom; use sp_io::hashing::{blake2_128, blake2_256, keccak_256, sha2_256}; use sp_runtime::traits::{Bounded, Zero}; use sp_std::{fmt, prelude::*}; @@ -270,11 +269,7 @@ pub enum RuntimeCosts { } impl RuntimeCosts { - fn token(&self, s: &HostFnWeights) -> RuntimeToken - where - T: Config, - T::AccountId: UncheckedFrom + AsRef<[u8]>, - { + fn token(&self, s: &HostFnWeights) -> RuntimeToken { use self::RuntimeCosts::*; let weight = match *self { MeteringBlock(amount) => s.gas.saturating_add(amount), @@ -324,7 +319,7 @@ impl RuntimeCosts { CallInputCloned(len) => s.call_per_cloned_byte.saturating_mul(len.into()), InstantiateBase { input_data_len, salt_len } => s .instantiate - .saturating_add(s.return_per_byte.saturating_mul(input_data_len.into())) + .saturating_add(s.instantiate_per_input_byte.saturating_mul(input_data_len.into())) .saturating_add(s.instantiate_per_salt_byte.saturating_mul(salt_len.into())), InstantiateSurchargeTransfer => s.instantiate_transfer_surcharge, HashSha256(len) => s @@ -375,11 +370,7 @@ struct RuntimeToken { weight: Weight, } -impl Token for RuntimeToken -where - T: Config, - T::AccountId: UncheckedFrom + AsRef<[u8]>, -{ +impl Token for RuntimeToken { fn weight(&self) -> Weight { self.weight } @@ -463,12 +454,7 @@ pub struct Runtime<'a, E: Ext + 'a> { chain_extension: Option::ChainExtension>>, } -impl<'a, E> Runtime<'a, E> -where - E: Ext + 'a, - ::AccountId: - UncheckedFrom<::Hash> + AsRef<[u8]>, -{ +impl<'a, E: Ext + 'a> Runtime<'a, E> { pub fn new(ext: &'a mut E, input_data: Vec) -> Self { Runtime { ext, diff --git a/frame/contracts/src/weights.rs b/frame/contracts/src/weights.rs index c3f3b50097..69fcbd6eec 100644 --- a/frame/contracts/src/weights.rs +++ b/frame/contracts/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_contracts //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-12-01, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-12-09, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 @@ -53,8 +53,8 @@ pub trait WeightInfo { fn on_initialize_per_queue_item(q: u32, ) -> Weight; fn reinstrument(c: u32, ) -> Weight; fn call_with_code_per_byte(c: u32, ) -> Weight; - fn instantiate_with_code(c: u32, s: u32, ) -> Weight; - fn instantiate(s: u32, ) -> Weight; + fn instantiate_with_code(c: u32, i: u32, s: u32, ) -> Weight; + fn instantiate(i: u32, s: u32, ) -> Weight; fn call() -> Weight; fn upload_code(c: u32, ) -> Weight; fn remove_code() -> Weight; @@ -98,7 +98,7 @@ pub trait WeightInfo { fn seal_delegate_call(r: u32, ) -> Weight; fn seal_call_per_transfer_clone_kb(t: u32, c: u32, ) -> Weight; fn seal_instantiate(r: u32, ) -> Weight; - fn seal_instantiate_per_transfer_salt_kb(t: u32, s: u32, ) -> Weight; + fn seal_instantiate_per_transfer_input_salt_kb(t: u32, i: u32, s: u32, ) -> Weight; fn seal_hash_sha2_256(r: u32, ) -> Weight; fn seal_hash_sha2_256_per_kb(n: u32, ) -> Weight; fn seal_hash_keccak_256(r: u32, ) -> Weight; @@ -172,17 +172,17 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: Contracts DeletionQueue (r:1 w:0) fn on_process_deletion_queue_batch() -> Weight { - // Minimum execution time: 3_148 nanoseconds. - Weight::from_ref_time(3_326_000) + // Minimum execution time: 3_196 nanoseconds. + Weight::from_ref_time(3_293_000) .saturating_add(T::DbWeight::get().reads(1)) } // Storage: Skipped Metadata (r:0 w:0) /// The range of component `k` is `[0, 1024]`. fn on_initialize_per_trie_key(k: u32, ) -> Weight { - // Minimum execution time: 15_318 nanoseconds. - Weight::from_ref_time(14_905_070) - // Standard Error: 1_055 - .saturating_add(Weight::from_ref_time(941_176).saturating_mul(k.into())) + // Minimum execution time: 14_857 nanoseconds. + Weight::from_ref_time(14_957_593) + // Standard Error: 1_015 + .saturating_add(Weight::from_ref_time(935_359).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) @@ -190,10 +190,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts DeletionQueue (r:1 w:0) /// The range of component `q` is `[0, 128]`. fn on_initialize_per_queue_item(q: u32, ) -> Weight { - // Minimum execution time: 3_182 nanoseconds. - Weight::from_ref_time(14_837_078) - // Standard Error: 3_423 - .saturating_add(Weight::from_ref_time(1_203_909).saturating_mul(q.into())) + // Minimum execution time: 3_231 nanoseconds. + Weight::from_ref_time(14_859_580) + // Standard Error: 3_479 + .saturating_add(Weight::from_ref_time(1_185_600).saturating_mul(q.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -201,10 +201,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:0 w:1) /// The range of component `c` is `[0, 64226]`. fn reinstrument(c: u32, ) -> Weight { - // Minimum execution time: 34_272 nanoseconds. - Weight::from_ref_time(33_159_915) - // Standard Error: 60 - .saturating_add(Weight::from_ref_time(46_967).saturating_mul(c.into())) + // Minimum execution time: 34_565 nanoseconds. + Weight::from_ref_time(29_199_016) + // Standard Error: 70 + .saturating_add(Weight::from_ref_time(47_107).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -215,10 +215,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `c` is `[0, 131072]`. fn call_with_code_per_byte(c: u32, ) -> Weight { - // Minimum execution time: 395_141 nanoseconds. - Weight::from_ref_time(413_672_628) - // Standard Error: 25 - .saturating_add(Weight::from_ref_time(30_570).saturating_mul(c.into())) + // Minimum execution time: 392_074 nanoseconds. + Weight::from_ref_time(404_090_909) + // Standard Error: 24 + .saturating_add(Weight::from_ref_time(30_454).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -231,14 +231,17 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts PristineCode (r:0 w:1) // Storage: Contracts OwnerInfoOf (r:0 w:1) /// The range of component `c` is `[0, 64226]`. + /// The range of component `i` is `[0, 1048576]`. /// The range of component `s` is `[0, 1048576]`. - fn instantiate_with_code(c: u32, s: u32, ) -> Weight { - // Minimum execution time: 2_259_439 nanoseconds. - Weight::from_ref_time(407_506_250) - // Standard Error: 79 - .saturating_add(Weight::from_ref_time(89_557).saturating_mul(c.into())) - // Standard Error: 4 - .saturating_add(Weight::from_ref_time(1_804).saturating_mul(s.into())) + fn instantiate_with_code(c: u32, i: u32, s: u32, ) -> Weight { + // Minimum execution time: 3_785_934 nanoseconds. + Weight::from_ref_time(683_143_843) + // Standard Error: 272 + .saturating_add(Weight::from_ref_time(87_620).saturating_mul(c.into())) + // Standard Error: 16 + .saturating_add(Weight::from_ref_time(1_363).saturating_mul(i.into())) + // Standard Error: 16 + .saturating_add(Weight::from_ref_time(1_778).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(9)) } @@ -249,12 +252,15 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:1 w:1) // Storage: System EventTopics (r:2 w:2) + /// The range of component `i` is `[0, 1048576]`. /// The range of component `s` is `[0, 1048576]`. - fn instantiate(s: u32, ) -> Weight { - // Minimum execution time: 185_950 nanoseconds. - Weight::from_ref_time(173_152_122) - // Standard Error: 4 - .saturating_add(Weight::from_ref_time(1_559).saturating_mul(s.into())) + fn instantiate(i: u32, s: u32, ) -> Weight { + // Minimum execution time: 1_935_310 nanoseconds. + Weight::from_ref_time(203_531_122) + // Standard Error: 8 + .saturating_add(Weight::from_ref_time(1_674).saturating_mul(i.into())) + // Standard Error: 8 + .saturating_add(Weight::from_ref_time(1_789).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(7)) } @@ -264,8 +270,8 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) // Storage: System EventTopics (r:2 w:2) fn call() -> Weight { - // Minimum execution time: 154_738 nanoseconds. - Weight::from_ref_time(159_212_000) + // Minimum execution time: 151_999 nanoseconds. + Weight::from_ref_time(153_527_000) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -275,10 +281,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts OwnerInfoOf (r:0 w:1) /// The range of component `c` is `[0, 64226]`. fn upload_code(c: u32, ) -> Weight { - // Minimum execution time: 392_302 nanoseconds. - Weight::from_ref_time(402_889_681) - // Standard Error: 84 - .saturating_add(Weight::from_ref_time(89_393).saturating_mul(c.into())) + // Minimum execution time: 391_165 nanoseconds. + Weight::from_ref_time(394_519_487) + // Standard Error: 75 + .saturating_add(Weight::from_ref_time(89_736).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -287,8 +293,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:0 w:1) // Storage: Contracts PristineCode (r:0 w:1) fn remove_code() -> Weight { - // Minimum execution time: 39_892 nanoseconds. - Weight::from_ref_time(40_258_000) + // Minimum execution time: 39_354 nanoseconds. + Weight::from_ref_time(39_855_000) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -296,8 +302,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts OwnerInfoOf (r:2 w:2) // Storage: System EventTopics (r:3 w:3) fn set_code() -> Weight { - // Minimum execution time: 41_441 nanoseconds. - Weight::from_ref_time(42_011_000) + // Minimum execution time: 40_909 nanoseconds. + Weight::from_ref_time(41_275_000) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(6)) } @@ -308,10 +314,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_caller(r: u32, ) -> Weight { - // Minimum execution time: 383_919 nanoseconds. - Weight::from_ref_time(387_844_956) - // Standard Error: 38_460 - .saturating_add(Weight::from_ref_time(16_014_536).saturating_mul(r.into())) + // Minimum execution time: 380_150 nanoseconds. + Weight::from_ref_time(384_429_035) + // Standard Error: 34_740 + .saturating_add(Weight::from_ref_time(15_582_218).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -322,10 +328,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_is_contract(r: u32, ) -> Weight { - // Minimum execution time: 384_047 nanoseconds. - Weight::from_ref_time(316_828_665) - // Standard Error: 571_260 - .saturating_add(Weight::from_ref_time(197_635_022).saturating_mul(r.into())) + // Minimum execution time: 380_608 nanoseconds. + Weight::from_ref_time(326_544_805) + // Standard Error: 475_381 + .saturating_add(Weight::from_ref_time(190_717_772).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -337,10 +343,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_code_hash(r: u32, ) -> Weight { - // Minimum execution time: 385_259 nanoseconds. - Weight::from_ref_time(338_849_650) - // Standard Error: 447_004 - .saturating_add(Weight::from_ref_time(235_734_380).saturating_mul(r.into())) + // Minimum execution time: 382_780 nanoseconds. + Weight::from_ref_time(333_775_113) + // Standard Error: 446_086 + .saturating_add(Weight::from_ref_time(232_531_042).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -352,10 +358,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_own_code_hash(r: u32, ) -> Weight { - // Minimum execution time: 385_528 nanoseconds. - Weight::from_ref_time(388_332_749) - // Standard Error: 43_017 - .saturating_add(Weight::from_ref_time(20_406_602).saturating_mul(r.into())) + // Minimum execution time: 381_815 nanoseconds. + Weight::from_ref_time(390_931_793) + // Standard Error: 61_918 + .saturating_add(Weight::from_ref_time(18_798_438).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -366,10 +372,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_caller_is_origin(r: u32, ) -> Weight { - // Minimum execution time: 382_243 nanoseconds. - Weight::from_ref_time(387_692_764) - // Standard Error: 32_726 - .saturating_add(Weight::from_ref_time(10_753_573).saturating_mul(r.into())) + // Minimum execution time: 379_162 nanoseconds. + Weight::from_ref_time(383_796_363) + // Standard Error: 25_555 + .saturating_add(Weight::from_ref_time(10_843_721).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -380,10 +386,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_address(r: u32, ) -> Weight { - // Minimum execution time: 383_993 nanoseconds. - Weight::from_ref_time(389_189_394) - // Standard Error: 55_885 - .saturating_add(Weight::from_ref_time(15_943_739).saturating_mul(r.into())) + // Minimum execution time: 380_307 nanoseconds. + Weight::from_ref_time(390_211_779) + // Standard Error: 53_326 + .saturating_add(Weight::from_ref_time(15_311_171).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -394,10 +400,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_gas_left(r: u32, ) -> Weight { - // Minimum execution time: 383_057 nanoseconds. - Weight::from_ref_time(387_466_678) - // Standard Error: 38_570 - .saturating_add(Weight::from_ref_time(15_739_675).saturating_mul(r.into())) + // Minimum execution time: 380_230 nanoseconds. + Weight::from_ref_time(383_470_453) + // Standard Error: 45_007 + .saturating_add(Weight::from_ref_time(15_582_472).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -408,10 +414,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_balance(r: u32, ) -> Weight { - // Minimum execution time: 383_688 nanoseconds. - Weight::from_ref_time(394_708_428) - // Standard Error: 101_035 - .saturating_add(Weight::from_ref_time(89_822_613).saturating_mul(r.into())) + // Minimum execution time: 380_244 nanoseconds. + Weight::from_ref_time(388_092_461) + // Standard Error: 98_199 + .saturating_add(Weight::from_ref_time(97_339_528).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -422,10 +428,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_value_transferred(r: u32, ) -> Weight { - // Minimum execution time: 383_511 nanoseconds. - Weight::from_ref_time(387_270_075) - // Standard Error: 33_383 - .saturating_add(Weight::from_ref_time(15_672_963).saturating_mul(r.into())) + // Minimum execution time: 380_242 nanoseconds. + Weight::from_ref_time(384_078_258) + // Standard Error: 28_510 + .saturating_add(Weight::from_ref_time(15_423_359).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -436,10 +442,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_minimum_balance(r: u32, ) -> Weight { - // Minimum execution time: 383_483 nanoseconds. - Weight::from_ref_time(386_995_457) - // Standard Error: 28_781 - .saturating_add(Weight::from_ref_time(15_632_597).saturating_mul(r.into())) + // Minimum execution time: 379_890 nanoseconds. + Weight::from_ref_time(383_658_430) + // Standard Error: 44_976 + .saturating_add(Weight::from_ref_time(15_451_905).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -450,10 +456,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_block_number(r: u32, ) -> Weight { - // Minimum execution time: 383_630 nanoseconds. - Weight::from_ref_time(387_801_190) - // Standard Error: 41_234 - .saturating_add(Weight::from_ref_time(15_531_236).saturating_mul(r.into())) + // Minimum execution time: 380_269 nanoseconds. + Weight::from_ref_time(383_580_481) + // Standard Error: 31_862 + .saturating_add(Weight::from_ref_time(15_230_473).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -464,10 +470,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_now(r: u32, ) -> Weight { - // Minimum execution time: 383_668 nanoseconds. - Weight::from_ref_time(387_490_160) - // Standard Error: 28_070 - .saturating_add(Weight::from_ref_time(15_639_764).saturating_mul(r.into())) + // Minimum execution time: 380_308 nanoseconds. + Weight::from_ref_time(383_732_372) + // Standard Error: 38_359 + .saturating_add(Weight::from_ref_time(15_358_775).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -479,10 +485,10 @@ impl WeightInfo for SubstrateWeight { // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) /// The range of component `r` is `[0, 20]`. fn seal_weight_to_fee(r: u32, ) -> Weight { - // Minimum execution time: 383_401 nanoseconds. - Weight::from_ref_time(393_140_360) - // Standard Error: 95_092 - .saturating_add(Weight::from_ref_time(83_864_160).saturating_mul(r.into())) + // Minimum execution time: 380_834 nanoseconds. + Weight::from_ref_time(388_999_459) + // Standard Error: 96_447 + .saturating_add(Weight::from_ref_time(86_714_414).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -493,10 +499,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_gas(r: u32, ) -> Weight { - // Minimum execution time: 142_684 nanoseconds. - Weight::from_ref_time(145_540_019) - // Standard Error: 18_177 - .saturating_add(Weight::from_ref_time(8_061_360).saturating_mul(r.into())) + // Minimum execution time: 140_955 nanoseconds. + Weight::from_ref_time(144_716_423) + // Standard Error: 11_370 + .saturating_add(Weight::from_ref_time(7_858_807).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -507,10 +513,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_input(r: u32, ) -> Weight { - // Minimum execution time: 383_472 nanoseconds. - Weight::from_ref_time(386_518_915) - // Standard Error: 46_174 - .saturating_add(Weight::from_ref_time(14_052_552).saturating_mul(r.into())) + // Minimum execution time: 380_210 nanoseconds. + Weight::from_ref_time(384_185_776) + // Standard Error: 49_038 + .saturating_add(Weight::from_ref_time(13_649_793).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -521,10 +527,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `n` is `[0, 1024]`. fn seal_input_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 398_912 nanoseconds. - Weight::from_ref_time(426_793_015) - // Standard Error: 5_524 - .saturating_add(Weight::from_ref_time(9_645_931).saturating_mul(n.into())) + // Minimum execution time: 396_209 nanoseconds. + Weight::from_ref_time(424_522_611) + // Standard Error: 3_917 + .saturating_add(Weight::from_ref_time(9_627_216).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -535,10 +541,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 1]`. fn seal_return(r: u32, ) -> Weight { - // Minimum execution time: 380_288 nanoseconds. - Weight::from_ref_time(382_064_302) - // Standard Error: 274_854 - .saturating_add(Weight::from_ref_time(5_341_497).saturating_mul(r.into())) + // Minimum execution time: 378_412 nanoseconds. + Weight::from_ref_time(380_502_085) + // Standard Error: 201_552 + .saturating_add(Weight::from_ref_time(1_434_214).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -549,10 +555,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `n` is `[0, 1024]`. fn seal_return_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 382_104 nanoseconds. - Weight::from_ref_time(383_966_376) - // Standard Error: 668 - .saturating_add(Weight::from_ref_time(230_898).saturating_mul(n.into())) + // Minimum execution time: 381_463 nanoseconds. + Weight::from_ref_time(383_955_553) + // Standard Error: 998 + .saturating_add(Weight::from_ref_time(230_512).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -565,10 +571,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts OwnerInfoOf (r:1 w:1) /// The range of component `r` is `[0, 1]`. fn seal_terminate(r: u32, ) -> Weight { - // Minimum execution time: 382_423 nanoseconds. - Weight::from_ref_time(384_162_748) - // Standard Error: 403_637 - .saturating_add(Weight::from_ref_time(57_465_451).saturating_mul(r.into())) + // Minimum execution time: 379_877 nanoseconds. + Weight::from_ref_time(381_729_546) + // Standard Error: 214_004 + .saturating_add(Weight::from_ref_time(52_528_353).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -582,10 +588,10 @@ impl WeightInfo for SubstrateWeight { // Storage: RandomnessCollectiveFlip RandomMaterial (r:1 w:0) /// The range of component `r` is `[0, 20]`. fn seal_random(r: u32, ) -> Weight { - // Minimum execution time: 382_853 nanoseconds. - Weight::from_ref_time(391_962_017) - // Standard Error: 102_169 - .saturating_add(Weight::from_ref_time(108_325_188).saturating_mul(r.into())) + // Minimum execution time: 380_275 nanoseconds. + Weight::from_ref_time(386_495_777) + // Standard Error: 94_674 + .saturating_add(Weight::from_ref_time(108_432_929).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -596,10 +602,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_deposit_event(r: u32, ) -> Weight { - // Minimum execution time: 380_999 nanoseconds. - Weight::from_ref_time(392_336_632) - // Standard Error: 168_846 - .saturating_add(Weight::from_ref_time(218_950_403).saturating_mul(r.into())) + // Minimum execution time: 379_095 nanoseconds. + Weight::from_ref_time(393_997_924) + // Standard Error: 141_916 + .saturating_add(Weight::from_ref_time(212_937_170).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -611,12 +617,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `t` is `[0, 4]`. /// The range of component `n` is `[0, 16]`. fn seal_deposit_event_per_topic_and_kb(t: u32, n: u32, ) -> Weight { - // Minimum execution time: 1_276_841 nanoseconds. - Weight::from_ref_time(587_558_952) - // Standard Error: 504_583 - .saturating_add(Weight::from_ref_time(178_141_140).saturating_mul(t.into())) - // Standard Error: 138_583 - .saturating_add(Weight::from_ref_time(71_194_319).saturating_mul(n.into())) + // Minimum execution time: 1_241_001 nanoseconds. + Weight::from_ref_time(548_526_917) + // Standard Error: 496_807 + .saturating_add(Weight::from_ref_time(177_087_769).saturating_mul(t.into())) + // Standard Error: 136_447 + .saturating_add(Weight::from_ref_time(71_558_402).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(t.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -629,20 +635,20 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_debug_message(r: u32, ) -> Weight { - // Minimum execution time: 161_230 nanoseconds. - Weight::from_ref_time(168_508_241) - // Standard Error: 31_112 - .saturating_add(Weight::from_ref_time(12_496_531).saturating_mul(r.into())) + // Minimum execution time: 159_158 nanoseconds. + Weight::from_ref_time(163_427_712) + // Standard Error: 22_442 + .saturating_add(Weight::from_ref_time(12_647_838).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } // Storage: Skipped Metadata (r:0 w:0) /// The range of component `r` is `[0, 10]`. fn seal_set_storage(r: u32, ) -> Weight { - // Minimum execution time: 383_055 nanoseconds. - Weight::from_ref_time(339_358_786) - // Standard Error: 486_941 - .saturating_add(Weight::from_ref_time(412_066_056).saturating_mul(r.into())) + // Minimum execution time: 381_068 nanoseconds. + Weight::from_ref_time(341_041_230) + // Standard Error: 464_053 + .saturating_add(Weight::from_ref_time(402_677_314).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -651,10 +657,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Skipped Metadata (r:0 w:0) /// The range of component `n` is `[0, 8]`. fn seal_set_storage_per_new_kb(n: u32, ) -> Weight { - // Minimum execution time: 512_301 nanoseconds. - Weight::from_ref_time(670_220_816) - // Standard Error: 1_460_983 - .saturating_add(Weight::from_ref_time(97_308_816).saturating_mul(n.into())) + // Minimum execution time: 508_695 nanoseconds. + Weight::from_ref_time(663_159_695) + // Standard Error: 1_419_342 + .saturating_add(Weight::from_ref_time(96_558_570).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(52)) .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(50)) @@ -663,10 +669,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Skipped Metadata (r:0 w:0) /// The range of component `n` is `[0, 8]`. fn seal_set_storage_per_old_kb(n: u32, ) -> Weight { - // Minimum execution time: 510_820 nanoseconds. - Weight::from_ref_time(638_537_372) - // Standard Error: 1_195_632 - .saturating_add(Weight::from_ref_time(65_979_491).saturating_mul(n.into())) + // Minimum execution time: 508_542 nanoseconds. + Weight::from_ref_time(634_146_978) + // Standard Error: 1_168_252 + .saturating_add(Weight::from_ref_time(64_231_947).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(51)) .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(49)) @@ -675,10 +681,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Skipped Metadata (r:0 w:0) /// The range of component `r` is `[0, 10]`. fn seal_clear_storage(r: u32, ) -> Weight { - // Minimum execution time: 383_596 nanoseconds. - Weight::from_ref_time(341_575_167) - // Standard Error: 478_894 - .saturating_add(Weight::from_ref_time(407_044_103).saturating_mul(r.into())) + // Minimum execution time: 381_743 nanoseconds. + Weight::from_ref_time(337_309_674) + // Standard Error: 527_407 + .saturating_add(Weight::from_ref_time(395_767_068).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -687,10 +693,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Skipped Metadata (r:0 w:0) /// The range of component `n` is `[0, 8]`. fn seal_clear_storage_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 481_757 nanoseconds. - Weight::from_ref_time(628_126_550) - // Standard Error: 1_363_017 - .saturating_add(Weight::from_ref_time(67_242_851).saturating_mul(n.into())) + // Minimum execution time: 478_283 nanoseconds. + Weight::from_ref_time(616_673_245) + // Standard Error: 1_290_784 + .saturating_add(Weight::from_ref_time(66_534_552).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(51)) .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(48)) @@ -699,10 +705,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Skipped Metadata (r:0 w:0) /// The range of component `r` is `[0, 10]`. fn seal_get_storage(r: u32, ) -> Weight { - // Minimum execution time: 383_868 nanoseconds. - Weight::from_ref_time(359_800_153) - // Standard Error: 338_998 - .saturating_add(Weight::from_ref_time(323_351_220).saturating_mul(r.into())) + // Minimum execution time: 381_916 nanoseconds. + Weight::from_ref_time(349_150_912) + // Standard Error: 443_388 + .saturating_add(Weight::from_ref_time(316_975_558).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -710,10 +716,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Skipped Metadata (r:0 w:0) /// The range of component `n` is `[0, 8]`. fn seal_get_storage_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 462_237 nanoseconds. - Weight::from_ref_time(585_809_405) - // Standard Error: 1_181_517 - .saturating_add(Weight::from_ref_time(160_905_409).saturating_mul(n.into())) + // Minimum execution time: 459_294 nanoseconds. + Weight::from_ref_time(579_698_524) + // Standard Error: 1_111_681 + .saturating_add(Weight::from_ref_time(159_276_087).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(51)) .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -721,10 +727,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Skipped Metadata (r:0 w:0) /// The range of component `r` is `[0, 10]`. fn seal_contains_storage(r: u32, ) -> Weight { - // Minimum execution time: 383_794 nanoseconds. - Weight::from_ref_time(355_233_888) - // Standard Error: 416_492 - .saturating_add(Weight::from_ref_time(317_857_887).saturating_mul(r.into())) + // Minimum execution time: 381_700 nanoseconds. + Weight::from_ref_time(352_544_675) + // Standard Error: 401_504 + .saturating_add(Weight::from_ref_time(304_380_106).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -732,10 +738,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Skipped Metadata (r:0 w:0) /// The range of component `n` is `[0, 8]`. fn seal_contains_storage_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 462_530 nanoseconds. - Weight::from_ref_time(571_276_165) - // Standard Error: 1_035_339 - .saturating_add(Weight::from_ref_time(63_481_618).saturating_mul(n.into())) + // Minimum execution time: 455_595 nanoseconds. + Weight::from_ref_time(560_428_166) + // Standard Error: 991_088 + .saturating_add(Weight::from_ref_time(61_810_610).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(51)) .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -743,10 +749,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Skipped Metadata (r:0 w:0) /// The range of component `r` is `[0, 10]`. fn seal_take_storage(r: u32, ) -> Weight { - // Minimum execution time: 385_343 nanoseconds. - Weight::from_ref_time(341_897_876) - // Standard Error: 451_948 - .saturating_add(Weight::from_ref_time(417_987_655).saturating_mul(r.into())) + // Minimum execution time: 382_000 nanoseconds. + Weight::from_ref_time(336_164_219) + // Standard Error: 601_744 + .saturating_add(Weight::from_ref_time(406_198_079).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -755,10 +761,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Skipped Metadata (r:0 w:0) /// The range of component `n` is `[0, 8]`. fn seal_take_storage_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 485_507 nanoseconds. - Weight::from_ref_time(646_265_325) - // Standard Error: 1_495_172 - .saturating_add(Weight::from_ref_time(166_973_554).saturating_mul(n.into())) + // Minimum execution time: 482_335 nanoseconds. + Weight::from_ref_time(634_245_177) + // Standard Error: 1_418_845 + .saturating_add(Weight::from_ref_time(164_352_113).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(51)) .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(48)) @@ -771,10 +777,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_transfer(r: u32, ) -> Weight { - // Minimum execution time: 384_834 nanoseconds. - Weight::from_ref_time(348_341_375) - // Standard Error: 792_708 - .saturating_add(Weight::from_ref_time(1_336_691_822).saturating_mul(r.into())) + // Minimum execution time: 382_142 nanoseconds. + Weight::from_ref_time(317_581_708) + // Standard Error: 682_156 + .saturating_add(Weight::from_ref_time(1_305_289_569).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(4)) @@ -787,10 +793,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_call(r: u32, ) -> Weight { - // Minimum execution time: 386_112 nanoseconds. - Weight::from_ref_time(386_971_000) - // Standard Error: 5_920_386 - .saturating_add(Weight::from_ref_time(28_051_657_660).saturating_mul(r.into())) + // Minimum execution time: 383_580 nanoseconds. + Weight::from_ref_time(384_176_000) + // Standard Error: 6_483_482 + .saturating_add(Weight::from_ref_time(28_047_685_517).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().reads((160_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -803,10 +809,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_delegate_call(r: u32, ) -> Weight { - // Minimum execution time: 385_776 nanoseconds. - Weight::from_ref_time(387_017_000) - // Standard Error: 6_680_801 - .saturating_add(Weight::from_ref_time(27_761_537_154).saturating_mul(r.into())) + // Minimum execution time: 384_523 nanoseconds. + Weight::from_ref_time(385_105_000) + // Standard Error: 6_156_142 + .saturating_add(Weight::from_ref_time(27_780_652_513).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((150_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -820,12 +826,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `t` is `[0, 1]`. /// The range of component `c` is `[0, 1024]`. fn seal_call_per_transfer_clone_kb(t: u32, c: u32, ) -> Weight { - // Minimum execution time: 9_623_952 nanoseconds. - Weight::from_ref_time(8_552_923_566) - // Standard Error: 6_582_866 - .saturating_add(Weight::from_ref_time(1_283_786_003).saturating_mul(t.into())) - // Standard Error: 9_870 - .saturating_add(Weight::from_ref_time(9_833_844).saturating_mul(c.into())) + // Minimum execution time: 9_509_961 nanoseconds. + Weight::from_ref_time(8_509_991_348) + // Standard Error: 6_614_757 + .saturating_add(Weight::from_ref_time(1_244_514_376).saturating_mul(t.into())) + // Standard Error: 9_918 + .saturating_add(Weight::from_ref_time(9_856_517).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(167)) .saturating_add(T::DbWeight::get().reads((81_u64).saturating_mul(t.into()))) .saturating_add(T::DbWeight::get().writes(163)) @@ -840,10 +846,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts OwnerInfoOf (r:80 w:80) /// The range of component `r` is `[0, 20]`. fn seal_instantiate(r: u32, ) -> Weight { - // Minimum execution time: 386_667 nanoseconds. - Weight::from_ref_time(387_559_000) - // Standard Error: 18_953_118 - .saturating_add(Weight::from_ref_time(33_188_342_429).saturating_mul(r.into())) + // Minimum execution time: 384_604 nanoseconds. + Weight::from_ref_time(385_278_000) + // Standard Error: 21_140_468 + .saturating_add(Weight::from_ref_time(33_100_726_150).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().reads((400_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(5)) @@ -857,14 +863,15 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts OwnerInfoOf (r:1 w:1) // Storage: System EventTopics (r:82 w:82) /// The range of component `t` is `[0, 1]`. + /// The range of component `i` is `[0, 960]`. /// The range of component `s` is `[0, 960]`. - fn seal_instantiate_per_transfer_salt_kb(t: u32, s: u32, ) -> Weight { - // Minimum execution time: 11_664_478 nanoseconds. - Weight::from_ref_time(11_359_540_086) - // Standard Error: 45_626_277 - .saturating_add(Weight::from_ref_time(19_120_579).saturating_mul(t.into())) - // Standard Error: 72_976 - .saturating_add(Weight::from_ref_time(125_731_953).saturating_mul(s.into())) + fn seal_instantiate_per_transfer_input_salt_kb(t: u32, i: u32, s: u32, ) -> Weight { + // Minimum execution time: 129_699_480 nanoseconds. + Weight::from_ref_time(10_187_699_005) + // Standard Error: 155_040 + .saturating_add(Weight::from_ref_time(125_284_310).saturating_mul(i.into())) + // Standard Error: 155_040 + .saturating_add(Weight::from_ref_time(125_850_564).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(249)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(t.into()))) .saturating_add(T::DbWeight::get().writes(247)) @@ -877,10 +884,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 1]`. fn seal_hash_sha2_256(r: u32, ) -> Weight { - // Minimum execution time: 384_826 nanoseconds. - Weight::from_ref_time(387_293_630) - // Standard Error: 437_875 - .saturating_add(Weight::from_ref_time(48_464_369).saturating_mul(r.into())) + // Minimum execution time: 380_397 nanoseconds. + Weight::from_ref_time(382_881_855) + // Standard Error: 290_948 + .saturating_add(Weight::from_ref_time(41_017_644).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -891,10 +898,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `n` is `[0, 1024]`. fn seal_hash_sha2_256_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 426_531 nanoseconds. - Weight::from_ref_time(427_315_000) - // Standard Error: 48_058 - .saturating_add(Weight::from_ref_time(327_318_884).saturating_mul(n.into())) + // Minimum execution time: 422_439 nanoseconds. + Weight::from_ref_time(423_480_000) + // Standard Error: 56_072 + .saturating_add(Weight::from_ref_time(329_103_825).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -905,10 +912,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 1]`. fn seal_hash_keccak_256(r: u32, ) -> Weight { - // Minimum execution time: 384_084 nanoseconds. - Weight::from_ref_time(386_354_628) - // Standard Error: 195_951 - .saturating_add(Weight::from_ref_time(52_991_271).saturating_mul(r.into())) + // Minimum execution time: 380_433 nanoseconds. + Weight::from_ref_time(382_325_624) + // Standard Error: 139_248 + .saturating_add(Weight::from_ref_time(53_159_175).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -919,10 +926,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `n` is `[0, 1024]`. fn seal_hash_keccak_256_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 438_319 nanoseconds. - Weight::from_ref_time(439_001_000) - // Standard Error: 53_445 - .saturating_add(Weight::from_ref_time(251_353_803).saturating_mul(n.into())) + // Minimum execution time: 434_834 nanoseconds. + Weight::from_ref_time(435_383_000) + // Standard Error: 59_824 + .saturating_add(Weight::from_ref_time(251_297_967).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -933,10 +940,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 1]`. fn seal_hash_blake2_256(r: u32, ) -> Weight { - // Minimum execution time: 384_397 nanoseconds. - Weight::from_ref_time(386_532_859) - // Standard Error: 141_195 - .saturating_add(Weight::from_ref_time(31_116_440).saturating_mul(r.into())) + // Minimum execution time: 379_088 nanoseconds. + Weight::from_ref_time(381_627_077) + // Standard Error: 152_561 + .saturating_add(Weight::from_ref_time(31_696_922).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -947,10 +954,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `n` is `[0, 1024]`. fn seal_hash_blake2_256_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 416_504 nanoseconds. - Weight::from_ref_time(417_686_000) - // Standard Error: 47_003 - .saturating_add(Weight::from_ref_time(103_095_636).saturating_mul(n.into())) + // Minimum execution time: 411_630 nanoseconds. + Weight::from_ref_time(412_354_000) + // Standard Error: 50_788 + .saturating_add(Weight::from_ref_time(103_105_715).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -961,10 +968,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 1]`. fn seal_hash_blake2_128(r: u32, ) -> Weight { - // Minimum execution time: 382_479 nanoseconds. - Weight::from_ref_time(384_623_057) - // Standard Error: 243_054 - .saturating_add(Weight::from_ref_time(37_025_542).saturating_mul(r.into())) + // Minimum execution time: 379_549 nanoseconds. + Weight::from_ref_time(382_406_346) + // Standard Error: 701_449 + .saturating_add(Weight::from_ref_time(31_066_353).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -975,10 +982,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `n` is `[0, 1024]`. fn seal_hash_blake2_128_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 414_863 nanoseconds. - Weight::from_ref_time(415_728_000) - // Standard Error: 48_764 - .saturating_add(Weight::from_ref_time(103_050_672).saturating_mul(n.into())) + // Minimum execution time: 412_112 nanoseconds. + Weight::from_ref_time(412_710_000) + // Standard Error: 54_875 + .saturating_add(Weight::from_ref_time(103_169_035).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -989,10 +996,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 1]`. fn seal_ecdsa_recover(r: u32, ) -> Weight { - // Minimum execution time: 384_418 nanoseconds. - Weight::from_ref_time(387_283_069) - // Standard Error: 526_301 - .saturating_add(Weight::from_ref_time(2_993_987_030).saturating_mul(r.into())) + // Minimum execution time: 381_994 nanoseconds. + Weight::from_ref_time(383_832_551) + // Standard Error: 676_656 + .saturating_add(Weight::from_ref_time(3_020_035_748).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1003,10 +1010,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 1]`. fn seal_ecdsa_to_eth_address(r: u32, ) -> Weight { - // Minimum execution time: 383_686 nanoseconds. - Weight::from_ref_time(385_812_638) - // Standard Error: 539_029 - .saturating_add(Weight::from_ref_time(2_098_063_561).saturating_mul(r.into())) + // Minimum execution time: 381_472 nanoseconds. + Weight::from_ref_time(383_590_834) + // Standard Error: 538_574 + .saturating_add(Weight::from_ref_time(2_077_926_265).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1018,10 +1025,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts OwnerInfoOf (r:16 w:16) /// The range of component `r` is `[0, 20]`. fn seal_set_code_hash(r: u32, ) -> Weight { - // Minimum execution time: 384_399 nanoseconds. - Weight::from_ref_time(385_337_000) - // Standard Error: 2_827_655 - .saturating_add(Weight::from_ref_time(1_383_659_432).saturating_mul(r.into())) + // Minimum execution time: 381_527 nanoseconds. + Weight::from_ref_time(382_291_000) + // Standard Error: 2_760_840 + .saturating_add(Weight::from_ref_time(1_356_115_009).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((225_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -1034,10 +1041,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_reentrance_count(r: u32, ) -> Weight { - // Minimum execution time: 385_165 nanoseconds. - Weight::from_ref_time(389_255_026) - // Standard Error: 25_918 - .saturating_add(Weight::from_ref_time(10_716_905).saturating_mul(r.into())) + // Minimum execution time: 382_722 nanoseconds. + Weight::from_ref_time(387_231_409) + // Standard Error: 28_817 + .saturating_add(Weight::from_ref_time(11_349_809).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1048,10 +1055,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_account_reentrance_count(r: u32, ) -> Weight { - // Minimum execution time: 386_959 nanoseconds. - Weight::from_ref_time(423_364_524) - // Standard Error: 127_096 - .saturating_add(Weight::from_ref_time(25_552_186).saturating_mul(r.into())) + // Minimum execution time: 383_568 nanoseconds. + Weight::from_ref_time(419_835_108) + // Standard Error: 125_982 + .saturating_add(Weight::from_ref_time(25_241_800).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1063,376 +1070,376 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts Nonce (r:1 w:1) /// The range of component `r` is `[0, 20]`. fn seal_instantiation_nonce(r: u32, ) -> Weight { - // Minimum execution time: 293_987 nanoseconds. - Weight::from_ref_time(307_154_849) - // Standard Error: 27_486 - .saturating_add(Weight::from_ref_time(8_759_333).saturating_mul(r.into())) + // Minimum execution time: 382_084 nanoseconds. + Weight::from_ref_time(388_155_568) + // Standard Error: 29_161 + .saturating_add(Weight::from_ref_time(9_015_217).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(4)) } /// The range of component `r` is `[0, 50]`. fn instr_i64const(r: u32, ) -> Weight { - // Minimum execution time: 688 nanoseconds. - Weight::from_ref_time(914_830) - // Standard Error: 222 - .saturating_add(Weight::from_ref_time(343_835).saturating_mul(r.into())) + // Minimum execution time: 593 nanoseconds. + Weight::from_ref_time(816_706) + // Standard Error: 173 + .saturating_add(Weight::from_ref_time(344_732).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64load(r: u32, ) -> Weight { - // Minimum execution time: 775 nanoseconds. - Weight::from_ref_time(1_286_008) - // Standard Error: 391 - .saturating_add(Weight::from_ref_time(984_759).saturating_mul(r.into())) + // Minimum execution time: 705 nanoseconds. + Weight::from_ref_time(1_191_205) + // Standard Error: 600 + .saturating_add(Weight::from_ref_time(986_102).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64store(r: u32, ) -> Weight { - // Minimum execution time: 779 nanoseconds. - Weight::from_ref_time(1_162_588) - // Standard Error: 694 - .saturating_add(Weight::from_ref_time(883_828).saturating_mul(r.into())) + // Minimum execution time: 717 nanoseconds. + Weight::from_ref_time(1_019_448) + // Standard Error: 421 + .saturating_add(Weight::from_ref_time(882_531).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_select(r: u32, ) -> Weight { - // Minimum execution time: 687 nanoseconds. - Weight::from_ref_time(965_966) - // Standard Error: 290 - .saturating_add(Weight::from_ref_time(955_392).saturating_mul(r.into())) + // Minimum execution time: 642 nanoseconds. + Weight::from_ref_time(917_880) + // Standard Error: 362 + .saturating_add(Weight::from_ref_time(957_235).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_if(r: u32, ) -> Weight { - // Minimum execution time: 681 nanoseconds. - Weight::from_ref_time(778_970) - // Standard Error: 670 - .saturating_add(Weight::from_ref_time(1_265_116).saturating_mul(r.into())) + // Minimum execution time: 630 nanoseconds. + Weight::from_ref_time(694_427) + // Standard Error: 1_506 + .saturating_add(Weight::from_ref_time(1_298_411).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_br(r: u32, ) -> Weight { - // Minimum execution time: 673 nanoseconds. - Weight::from_ref_time(1_125_562) - // Standard Error: 818 - .saturating_add(Weight::from_ref_time(528_126).saturating_mul(r.into())) + // Minimum execution time: 634 nanoseconds. + Weight::from_ref_time(1_101_754) + // Standard Error: 840 + .saturating_add(Weight::from_ref_time(526_433).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_br_if(r: u32, ) -> Weight { - // Minimum execution time: 649 nanoseconds. - Weight::from_ref_time(780_802) - // Standard Error: 943 - .saturating_add(Weight::from_ref_time(800_988).saturating_mul(r.into())) + // Minimum execution time: 651 nanoseconds. + Weight::from_ref_time(790_908) + // Standard Error: 849 + .saturating_add(Weight::from_ref_time(800_188).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_br_table(r: u32, ) -> Weight { - // Minimum execution time: 662 nanoseconds. - Weight::from_ref_time(555_078) - // Standard Error: 1_540 - .saturating_add(Weight::from_ref_time(1_078_705).saturating_mul(r.into())) + // Minimum execution time: 622 nanoseconds. + Weight::from_ref_time(416_266) + // Standard Error: 1_574 + .saturating_add(Weight::from_ref_time(1_080_225).saturating_mul(r.into())) } /// The range of component `e` is `[1, 256]`. fn instr_br_table_per_entry(e: u32, ) -> Weight { - // Minimum execution time: 2_177 nanoseconds. - Weight::from_ref_time(2_581_121) - // Standard Error: 67 - .saturating_add(Weight::from_ref_time(5_001).saturating_mul(e.into())) + // Minimum execution time: 2_131 nanoseconds. + Weight::from_ref_time(2_540_446) + // Standard Error: 75 + .saturating_add(Weight::from_ref_time(4_997).saturating_mul(e.into())) } /// The range of component `r` is `[0, 50]`. fn instr_call(r: u32, ) -> Weight { - // Minimum execution time: 702 nanoseconds. - Weight::from_ref_time(1_570_695) - // Standard Error: 1_524 - .saturating_add(Weight::from_ref_time(2_192_040).saturating_mul(r.into())) + // Minimum execution time: 650 nanoseconds. + Weight::from_ref_time(1_577_978) + // Standard Error: 2_696 + .saturating_add(Weight::from_ref_time(2_204_044).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_call_indirect(r: u32, ) -> Weight { - // Minimum execution time: 745 nanoseconds. - Weight::from_ref_time(1_694_451) - // Standard Error: 4_170 - .saturating_add(Weight::from_ref_time(2_813_621).saturating_mul(r.into())) + // Minimum execution time: 742 nanoseconds. + Weight::from_ref_time(1_947_575) + // Standard Error: 1_651 + .saturating_add(Weight::from_ref_time(2_799_445).saturating_mul(r.into())) } /// The range of component `p` is `[0, 128]`. fn instr_call_indirect_per_param(p: u32, ) -> Weight { - // Minimum execution time: 4_255 nanoseconds. - Weight::from_ref_time(5_064_243) - // Standard Error: 289 - .saturating_add(Weight::from_ref_time(178_868).saturating_mul(p.into())) + // Minimum execution time: 4_230 nanoseconds. + Weight::from_ref_time(5_079_432) + // Standard Error: 315 + .saturating_add(Weight::from_ref_time(179_278).saturating_mul(p.into())) } /// The range of component `l` is `[0, 1024]`. fn instr_call_per_local(l: u32, ) -> Weight { - // Minimum execution time: 2_802 nanoseconds. - Weight::from_ref_time(3_474_642) - // Standard Error: 28 - .saturating_add(Weight::from_ref_time(92_517).saturating_mul(l.into())) + // Minimum execution time: 2_830 nanoseconds. + Weight::from_ref_time(3_601_633) + // Standard Error: 31 + .saturating_add(Weight::from_ref_time(92_499).saturating_mul(l.into())) } /// The range of component `r` is `[0, 50]`. fn instr_local_get(r: u32, ) -> Weight { - // Minimum execution time: 2_973 nanoseconds. - Weight::from_ref_time(3_218_977) - // Standard Error: 183 - .saturating_add(Weight::from_ref_time(364_688).saturating_mul(r.into())) + // Minimum execution time: 2_896 nanoseconds. + Weight::from_ref_time(3_137_247) + // Standard Error: 245 + .saturating_add(Weight::from_ref_time(364_431).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_local_set(r: u32, ) -> Weight { - // Minimum execution time: 2_912 nanoseconds. - Weight::from_ref_time(3_173_203) - // Standard Error: 260 - .saturating_add(Weight::from_ref_time(381_853).saturating_mul(r.into())) + // Minimum execution time: 2_795 nanoseconds. + Weight::from_ref_time(3_199_878) + // Standard Error: 549 + .saturating_add(Weight::from_ref_time(380_524).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_local_tee(r: u32, ) -> Weight { - // Minimum execution time: 2_916 nanoseconds. - Weight::from_ref_time(3_228_548) - // Standard Error: 311 - .saturating_add(Weight::from_ref_time(526_008).saturating_mul(r.into())) + // Minimum execution time: 2_807 nanoseconds. + Weight::from_ref_time(3_130_120) + // Standard Error: 319 + .saturating_add(Weight::from_ref_time(525_857).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_global_get(r: u32, ) -> Weight { - // Minimum execution time: 739 nanoseconds. - Weight::from_ref_time(1_128_919) - // Standard Error: 479 - .saturating_add(Weight::from_ref_time(810_765).saturating_mul(r.into())) + // Minimum execution time: 678 nanoseconds. + Weight::from_ref_time(1_013_348) + // Standard Error: 362 + .saturating_add(Weight::from_ref_time(810_232).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_global_set(r: u32, ) -> Weight { - // Minimum execution time: 724 nanoseconds. - Weight::from_ref_time(1_044_382) - // Standard Error: 371 - .saturating_add(Weight::from_ref_time(828_530).saturating_mul(r.into())) + // Minimum execution time: 624 nanoseconds. + Weight::from_ref_time(973_583) + // Standard Error: 373 + .saturating_add(Weight::from_ref_time(829_360).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_memory_current(r: u32, ) -> Weight { - // Minimum execution time: 770 nanoseconds. - Weight::from_ref_time(988_307) - // Standard Error: 587 - .saturating_add(Weight::from_ref_time(699_091).saturating_mul(r.into())) + // Minimum execution time: 698 nanoseconds. + Weight::from_ref_time(904_862) + // Standard Error: 360 + .saturating_add(Weight::from_ref_time(694_614).saturating_mul(r.into())) } /// The range of component `r` is `[0, 1]`. fn instr_memory_grow(r: u32, ) -> Weight { - // Minimum execution time: 688 nanoseconds. - Weight::from_ref_time(747_995) - // Standard Error: 3_894 - .saturating_add(Weight::from_ref_time(234_512_504).saturating_mul(r.into())) + // Minimum execution time: 642 nanoseconds. + Weight::from_ref_time(735_085) + // Standard Error: 105_815 + .saturating_add(Weight::from_ref_time(233_816_514).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64clz(r: u32, ) -> Weight { - // Minimum execution time: 643 nanoseconds. - Weight::from_ref_time(946_893) - // Standard Error: 188 - .saturating_add(Weight::from_ref_time(505_004).saturating_mul(r.into())) + // Minimum execution time: 606 nanoseconds. + Weight::from_ref_time(850_590) + // Standard Error: 262 + .saturating_add(Weight::from_ref_time(507_721).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64ctz(r: u32, ) -> Weight { // Minimum execution time: 663 nanoseconds. - Weight::from_ref_time(965_194) - // Standard Error: 343 - .saturating_add(Weight::from_ref_time(505_213).saturating_mul(r.into())) + Weight::from_ref_time(853_060) + // Standard Error: 250 + .saturating_add(Weight::from_ref_time(514_225).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64popcnt(r: u32, ) -> Weight { - // Minimum execution time: 675 nanoseconds. - Weight::from_ref_time(903_705) - // Standard Error: 425 - .saturating_add(Weight::from_ref_time(507_749).saturating_mul(r.into())) + // Minimum execution time: 605 nanoseconds. + Weight::from_ref_time(849_563) + // Standard Error: 275 + .saturating_add(Weight::from_ref_time(511_494).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64eqz(r: u32, ) -> Weight { - // Minimum execution time: 637 nanoseconds. - Weight::from_ref_time(946_419) - // Standard Error: 301 - .saturating_add(Weight::from_ref_time(522_387).saturating_mul(r.into())) + // Minimum execution time: 618 nanoseconds. + Weight::from_ref_time(839_855) + // Standard Error: 228 + .saturating_add(Weight::from_ref_time(524_614).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64extendsi32(r: u32, ) -> Weight { - // Minimum execution time: 674 nanoseconds. - Weight::from_ref_time(963_566) - // Standard Error: 952 - .saturating_add(Weight::from_ref_time(504_528).saturating_mul(r.into())) + // Minimum execution time: 637 nanoseconds. + Weight::from_ref_time(860_326) + // Standard Error: 240 + .saturating_add(Weight::from_ref_time(504_847).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64extendui32(r: u32, ) -> Weight { - // Minimum execution time: 663 nanoseconds. - Weight::from_ref_time(927_099) - // Standard Error: 336 - .saturating_add(Weight::from_ref_time(505_200).saturating_mul(r.into())) + // Minimum execution time: 623 nanoseconds. + Weight::from_ref_time(844_585) + // Standard Error: 235 + .saturating_add(Weight::from_ref_time(505_821).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i32wrapi64(r: u32, ) -> Weight { - // Minimum execution time: 660 nanoseconds. - Weight::from_ref_time(901_114) - // Standard Error: 255 - .saturating_add(Weight::from_ref_time(503_803).saturating_mul(r.into())) + // Minimum execution time: 672 nanoseconds. + Weight::from_ref_time(826_784) + // Standard Error: 225 + .saturating_add(Weight::from_ref_time(504_632).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64eq(r: u32, ) -> Weight { - // Minimum execution time: 636 nanoseconds. - Weight::from_ref_time(906_526) - // Standard Error: 246 - .saturating_add(Weight::from_ref_time(730_299).saturating_mul(r.into())) + // Minimum execution time: 642 nanoseconds. + Weight::from_ref_time(867_080) + // Standard Error: 231 + .saturating_add(Weight::from_ref_time(732_430).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64ne(r: u32, ) -> Weight { - // Minimum execution time: 659 nanoseconds. - Weight::from_ref_time(947_772) - // Standard Error: 312 - .saturating_add(Weight::from_ref_time(729_463).saturating_mul(r.into())) + // Minimum execution time: 639 nanoseconds. + Weight::from_ref_time(866_094) + // Standard Error: 272 + .saturating_add(Weight::from_ref_time(732_560).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64lts(r: u32, ) -> Weight { - // Minimum execution time: 646 nanoseconds. - Weight::from_ref_time(923_694) - // Standard Error: 243 - .saturating_add(Weight::from_ref_time(738_995).saturating_mul(r.into())) + // Minimum execution time: 619 nanoseconds. + Weight::from_ref_time(928_672) + // Standard Error: 484 + .saturating_add(Weight::from_ref_time(739_523).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64ltu(r: u32, ) -> Weight { - // Minimum execution time: 652 nanoseconds. - Weight::from_ref_time(955_453) - // Standard Error: 1_430 - .saturating_add(Weight::from_ref_time(741_624).saturating_mul(r.into())) + // Minimum execution time: 612 nanoseconds. + Weight::from_ref_time(863_312) + // Standard Error: 328 + .saturating_add(Weight::from_ref_time(743_687).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64gts(r: u32, ) -> Weight { - // Minimum execution time: 642 nanoseconds. - Weight::from_ref_time(900_107) - // Standard Error: 376 - .saturating_add(Weight::from_ref_time(740_016).saturating_mul(r.into())) + // Minimum execution time: 648 nanoseconds. + Weight::from_ref_time(931_331) + // Standard Error: 612 + .saturating_add(Weight::from_ref_time(747_653).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64gtu(r: u32, ) -> Weight { - // Minimum execution time: 625 nanoseconds. - Weight::from_ref_time(946_744) - // Standard Error: 252 - .saturating_add(Weight::from_ref_time(743_532).saturating_mul(r.into())) + // Minimum execution time: 632 nanoseconds. + Weight::from_ref_time(868_901) + // Standard Error: 276 + .saturating_add(Weight::from_ref_time(744_778).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64les(r: u32, ) -> Weight { - // Minimum execution time: 652 nanoseconds. - Weight::from_ref_time(918_551) - // Standard Error: 313 - .saturating_add(Weight::from_ref_time(731_451).saturating_mul(r.into())) + // Minimum execution time: 629 nanoseconds. + Weight::from_ref_time(898_516) + // Standard Error: 288 + .saturating_add(Weight::from_ref_time(734_393).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64leu(r: u32, ) -> Weight { - // Minimum execution time: 651 nanoseconds. - Weight::from_ref_time(923_475) - // Standard Error: 341 - .saturating_add(Weight::from_ref_time(738_353).saturating_mul(r.into())) + // Minimum execution time: 603 nanoseconds. + Weight::from_ref_time(853_744) + // Standard Error: 204 + .saturating_add(Weight::from_ref_time(739_499).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64ges(r: u32, ) -> Weight { - // Minimum execution time: 666 nanoseconds. - Weight::from_ref_time(1_136_987) - // Standard Error: 1_482 - .saturating_add(Weight::from_ref_time(727_254).saturating_mul(r.into())) + // Minimum execution time: 625 nanoseconds. + Weight::from_ref_time(885_174) + // Standard Error: 238 + .saturating_add(Weight::from_ref_time(734_010).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64geu(r: u32, ) -> Weight { - // Minimum execution time: 633 nanoseconds. - Weight::from_ref_time(934_201) - // Standard Error: 332 - .saturating_add(Weight::from_ref_time(731_804).saturating_mul(r.into())) + // Minimum execution time: 602 nanoseconds. + Weight::from_ref_time(915_329) + // Standard Error: 273 + .saturating_add(Weight::from_ref_time(738_209).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64add(r: u32, ) -> Weight { - // Minimum execution time: 673 nanoseconds. - Weight::from_ref_time(983_317) - // Standard Error: 492 - .saturating_add(Weight::from_ref_time(718_126).saturating_mul(r.into())) + // Minimum execution time: 585 nanoseconds. + Weight::from_ref_time(862_239) + // Standard Error: 294 + .saturating_add(Weight::from_ref_time(719_000).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64sub(r: u32, ) -> Weight { - // Minimum execution time: 647 nanoseconds. - Weight::from_ref_time(925_490) - // Standard Error: 1_799 - .saturating_add(Weight::from_ref_time(711_178).saturating_mul(r.into())) + // Minimum execution time: 633 nanoseconds. + Weight::from_ref_time(881_696) + // Standard Error: 195 + .saturating_add(Weight::from_ref_time(713_153).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64mul(r: u32, ) -> Weight { - // Minimum execution time: 652 nanoseconds. - Weight::from_ref_time(955_546) - // Standard Error: 283 - .saturating_add(Weight::from_ref_time(710_844).saturating_mul(r.into())) + // Minimum execution time: 629 nanoseconds. + Weight::from_ref_time(856_238) + // Standard Error: 269 + .saturating_add(Weight::from_ref_time(715_451).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64divs(r: u32, ) -> Weight { - // Minimum execution time: 653 nanoseconds. - Weight::from_ref_time(982_314) - // Standard Error: 267 - .saturating_add(Weight::from_ref_time(1_344_080).saturating_mul(r.into())) + // Minimum execution time: 633 nanoseconds. + Weight::from_ref_time(880_804) + // Standard Error: 725 + .saturating_add(Weight::from_ref_time(1_349_577).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64divu(r: u32, ) -> Weight { - // Minimum execution time: 637 nanoseconds. - Weight::from_ref_time(913_421) - // Standard Error: 737 - .saturating_add(Weight::from_ref_time(1_285_749).saturating_mul(r.into())) + // Minimum execution time: 639 nanoseconds. + Weight::from_ref_time(867_528) + // Standard Error: 1_940 + .saturating_add(Weight::from_ref_time(1_287_959).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64rems(r: u32, ) -> Weight { - // Minimum execution time: 653 nanoseconds. - Weight::from_ref_time(939_041) - // Standard Error: 354 - .saturating_add(Weight::from_ref_time(1_391_470).saturating_mul(r.into())) + // Minimum execution time: 595 nanoseconds. + Weight::from_ref_time(851_253) + // Standard Error: 503 + .saturating_add(Weight::from_ref_time(1_398_668).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64remu(r: u32, ) -> Weight { - // Minimum execution time: 656 nanoseconds. - Weight::from_ref_time(951_030) - // Standard Error: 342 - .saturating_add(Weight::from_ref_time(1_287_904).saturating_mul(r.into())) + // Minimum execution time: 604 nanoseconds. + Weight::from_ref_time(804_977) + // Standard Error: 812 + .saturating_add(Weight::from_ref_time(1_288_816).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64and(r: u32, ) -> Weight { - // Minimum execution time: 682 nanoseconds. - Weight::from_ref_time(940_975) - // Standard Error: 195 - .saturating_add(Weight::from_ref_time(717_132).saturating_mul(r.into())) + // Minimum execution time: 609 nanoseconds. + Weight::from_ref_time(890_945) + // Standard Error: 1_645 + .saturating_add(Weight::from_ref_time(719_770).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64or(r: u32, ) -> Weight { - // Minimum execution time: 704 nanoseconds. - Weight::from_ref_time(941_860) - // Standard Error: 200 - .saturating_add(Weight::from_ref_time(717_696).saturating_mul(r.into())) + // Minimum execution time: 628 nanoseconds. + Weight::from_ref_time(897_973) + // Standard Error: 1_641 + .saturating_add(Weight::from_ref_time(718_838).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64xor(r: u32, ) -> Weight { - // Minimum execution time: 648 nanoseconds. - Weight::from_ref_time(917_135) - // Standard Error: 237 - .saturating_add(Weight::from_ref_time(717_979).saturating_mul(r.into())) + // Minimum execution time: 634 nanoseconds. + Weight::from_ref_time(848_440) + // Standard Error: 249 + .saturating_add(Weight::from_ref_time(718_206).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64shl(r: u32, ) -> Weight { - // Minimum execution time: 653 nanoseconds. - Weight::from_ref_time(1_031_822) - // Standard Error: 937 - .saturating_add(Weight::from_ref_time(730_965).saturating_mul(r.into())) + // Minimum execution time: 625 nanoseconds. + Weight::from_ref_time(881_579) + // Standard Error: 273 + .saturating_add(Weight::from_ref_time(736_934).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64shrs(r: u32, ) -> Weight { - // Minimum execution time: 671 nanoseconds. - Weight::from_ref_time(935_833) - // Standard Error: 184 - .saturating_add(Weight::from_ref_time(732_227).saturating_mul(r.into())) + // Minimum execution time: 630 nanoseconds. + Weight::from_ref_time(897_813) + // Standard Error: 249 + .saturating_add(Weight::from_ref_time(734_657).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64shru(r: u32, ) -> Weight { - // Minimum execution time: 637 nanoseconds. - Weight::from_ref_time(962_491) - // Standard Error: 488 - .saturating_add(Weight::from_ref_time(733_218).saturating_mul(r.into())) + // Minimum execution time: 607 nanoseconds. + Weight::from_ref_time(871_660) + // Standard Error: 312 + .saturating_add(Weight::from_ref_time(735_377).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64rotl(r: u32, ) -> Weight { - // Minimum execution time: 643 nanoseconds. - Weight::from_ref_time(951_949) - // Standard Error: 321 - .saturating_add(Weight::from_ref_time(732_212).saturating_mul(r.into())) + // Minimum execution time: 610 nanoseconds. + Weight::from_ref_time(861_640) + // Standard Error: 293 + .saturating_add(Weight::from_ref_time(735_524).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64rotr(r: u32, ) -> Weight { - // Minimum execution time: 665 nanoseconds. - Weight::from_ref_time(951_447) - // Standard Error: 346 - .saturating_add(Weight::from_ref_time(732_549).saturating_mul(r.into())) + // Minimum execution time: 625 nanoseconds. + Weight::from_ref_time(880_203) + // Standard Error: 373 + .saturating_add(Weight::from_ref_time(737_354).saturating_mul(r.into())) } } @@ -1440,17 +1447,17 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { // Storage: Contracts DeletionQueue (r:1 w:0) fn on_process_deletion_queue_batch() -> Weight { - // Minimum execution time: 3_148 nanoseconds. - Weight::from_ref_time(3_326_000) + // Minimum execution time: 3_196 nanoseconds. + Weight::from_ref_time(3_293_000) .saturating_add(RocksDbWeight::get().reads(1)) } // Storage: Skipped Metadata (r:0 w:0) /// The range of component `k` is `[0, 1024]`. fn on_initialize_per_trie_key(k: u32, ) -> Weight { - // Minimum execution time: 15_318 nanoseconds. - Weight::from_ref_time(14_905_070) - // Standard Error: 1_055 - .saturating_add(Weight::from_ref_time(941_176).saturating_mul(k.into())) + // Minimum execution time: 14_857 nanoseconds. + Weight::from_ref_time(14_957_593) + // Standard Error: 1_015 + .saturating_add(Weight::from_ref_time(935_359).saturating_mul(k.into())) .saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().writes(1)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(k.into()))) @@ -1458,10 +1465,10 @@ impl WeightInfo for () { // Storage: Contracts DeletionQueue (r:1 w:0) /// The range of component `q` is `[0, 128]`. fn on_initialize_per_queue_item(q: u32, ) -> Weight { - // Minimum execution time: 3_182 nanoseconds. - Weight::from_ref_time(14_837_078) - // Standard Error: 3_423 - .saturating_add(Weight::from_ref_time(1_203_909).saturating_mul(q.into())) + // Minimum execution time: 3_231 nanoseconds. + Weight::from_ref_time(14_859_580) + // Standard Error: 3_479 + .saturating_add(Weight::from_ref_time(1_185_600).saturating_mul(q.into())) .saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().writes(1)) } @@ -1469,10 +1476,10 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:0 w:1) /// The range of component `c` is `[0, 64226]`. fn reinstrument(c: u32, ) -> Weight { - // Minimum execution time: 34_272 nanoseconds. - Weight::from_ref_time(33_159_915) - // Standard Error: 60 - .saturating_add(Weight::from_ref_time(46_967).saturating_mul(c.into())) + // Minimum execution time: 34_565 nanoseconds. + Weight::from_ref_time(29_199_016) + // Standard Error: 70 + .saturating_add(Weight::from_ref_time(47_107).saturating_mul(c.into())) .saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().writes(1)) } @@ -1483,10 +1490,10 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `c` is `[0, 131072]`. fn call_with_code_per_byte(c: u32, ) -> Weight { - // Minimum execution time: 395_141 nanoseconds. - Weight::from_ref_time(413_672_628) - // Standard Error: 25 - .saturating_add(Weight::from_ref_time(30_570).saturating_mul(c.into())) + // Minimum execution time: 392_074 nanoseconds. + Weight::from_ref_time(404_090_909) + // Standard Error: 24 + .saturating_add(Weight::from_ref_time(30_454).saturating_mul(c.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().writes(4)) } @@ -1499,14 +1506,17 @@ impl WeightInfo for () { // Storage: Contracts PristineCode (r:0 w:1) // Storage: Contracts OwnerInfoOf (r:0 w:1) /// The range of component `c` is `[0, 64226]`. + /// The range of component `i` is `[0, 1048576]`. /// The range of component `s` is `[0, 1048576]`. - fn instantiate_with_code(c: u32, s: u32, ) -> Weight { - // Minimum execution time: 2_259_439 nanoseconds. - Weight::from_ref_time(407_506_250) - // Standard Error: 79 - .saturating_add(Weight::from_ref_time(89_557).saturating_mul(c.into())) - // Standard Error: 4 - .saturating_add(Weight::from_ref_time(1_804).saturating_mul(s.into())) + fn instantiate_with_code(c: u32, i: u32, s: u32, ) -> Weight { + // Minimum execution time: 3_785_934 nanoseconds. + Weight::from_ref_time(683_143_843) + // Standard Error: 272 + .saturating_add(Weight::from_ref_time(87_620).saturating_mul(c.into())) + // Standard Error: 16 + .saturating_add(Weight::from_ref_time(1_363).saturating_mul(i.into())) + // Standard Error: 16 + .saturating_add(Weight::from_ref_time(1_778).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(8)) .saturating_add(RocksDbWeight::get().writes(9)) } @@ -1517,12 +1527,15 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:1 w:1) // Storage: System EventTopics (r:2 w:2) + /// The range of component `i` is `[0, 1048576]`. /// The range of component `s` is `[0, 1048576]`. - fn instantiate(s: u32, ) -> Weight { - // Minimum execution time: 185_950 nanoseconds. - Weight::from_ref_time(173_152_122) - // Standard Error: 4 - .saturating_add(Weight::from_ref_time(1_559).saturating_mul(s.into())) + fn instantiate(i: u32, s: u32, ) -> Weight { + // Minimum execution time: 1_935_310 nanoseconds. + Weight::from_ref_time(203_531_122) + // Standard Error: 8 + .saturating_add(Weight::from_ref_time(1_674).saturating_mul(i.into())) + // Standard Error: 8 + .saturating_add(Weight::from_ref_time(1_789).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(8)) .saturating_add(RocksDbWeight::get().writes(7)) } @@ -1532,8 +1545,8 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) // Storage: System EventTopics (r:2 w:2) fn call() -> Weight { - // Minimum execution time: 154_738 nanoseconds. - Weight::from_ref_time(159_212_000) + // Minimum execution time: 151_999 nanoseconds. + Weight::from_ref_time(153_527_000) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().writes(4)) } @@ -1543,10 +1556,10 @@ impl WeightInfo for () { // Storage: Contracts OwnerInfoOf (r:0 w:1) /// The range of component `c` is `[0, 64226]`. fn upload_code(c: u32, ) -> Weight { - // Minimum execution time: 392_302 nanoseconds. - Weight::from_ref_time(402_889_681) - // Standard Error: 84 - .saturating_add(Weight::from_ref_time(89_393).saturating_mul(c.into())) + // Minimum execution time: 391_165 nanoseconds. + Weight::from_ref_time(394_519_487) + // Standard Error: 75 + .saturating_add(Weight::from_ref_time(89_736).saturating_mul(c.into())) .saturating_add(RocksDbWeight::get().reads(2)) .saturating_add(RocksDbWeight::get().writes(4)) } @@ -1555,8 +1568,8 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:0 w:1) // Storage: Contracts PristineCode (r:0 w:1) fn remove_code() -> Weight { - // Minimum execution time: 39_892 nanoseconds. - Weight::from_ref_time(40_258_000) + // Minimum execution time: 39_354 nanoseconds. + Weight::from_ref_time(39_855_000) .saturating_add(RocksDbWeight::get().reads(2)) .saturating_add(RocksDbWeight::get().writes(4)) } @@ -1564,8 +1577,8 @@ impl WeightInfo for () { // Storage: Contracts OwnerInfoOf (r:2 w:2) // Storage: System EventTopics (r:3 w:3) fn set_code() -> Weight { - // Minimum execution time: 41_441 nanoseconds. - Weight::from_ref_time(42_011_000) + // Minimum execution time: 40_909 nanoseconds. + Weight::from_ref_time(41_275_000) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().writes(6)) } @@ -1576,10 +1589,10 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_caller(r: u32, ) -> Weight { - // Minimum execution time: 383_919 nanoseconds. - Weight::from_ref_time(387_844_956) - // Standard Error: 38_460 - .saturating_add(Weight::from_ref_time(16_014_536).saturating_mul(r.into())) + // Minimum execution time: 380_150 nanoseconds. + Weight::from_ref_time(384_429_035) + // Standard Error: 34_740 + .saturating_add(Weight::from_ref_time(15_582_218).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().writes(3)) } @@ -1590,10 +1603,10 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_is_contract(r: u32, ) -> Weight { - // Minimum execution time: 384_047 nanoseconds. - Weight::from_ref_time(316_828_665) - // Standard Error: 571_260 - .saturating_add(Weight::from_ref_time(197_635_022).saturating_mul(r.into())) + // Minimum execution time: 380_608 nanoseconds. + Weight::from_ref_time(326_544_805) + // Standard Error: 475_381 + .saturating_add(Weight::from_ref_time(190_717_772).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().reads((80_u64).saturating_mul(r.into()))) .saturating_add(RocksDbWeight::get().writes(3)) @@ -1605,10 +1618,10 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_code_hash(r: u32, ) -> Weight { - // Minimum execution time: 385_259 nanoseconds. - Weight::from_ref_time(338_849_650) - // Standard Error: 447_004 - .saturating_add(Weight::from_ref_time(235_734_380).saturating_mul(r.into())) + // Minimum execution time: 382_780 nanoseconds. + Weight::from_ref_time(333_775_113) + // Standard Error: 446_086 + .saturating_add(Weight::from_ref_time(232_531_042).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().reads((80_u64).saturating_mul(r.into()))) .saturating_add(RocksDbWeight::get().writes(3)) @@ -1620,10 +1633,10 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_own_code_hash(r: u32, ) -> Weight { - // Minimum execution time: 385_528 nanoseconds. - Weight::from_ref_time(388_332_749) - // Standard Error: 43_017 - .saturating_add(Weight::from_ref_time(20_406_602).saturating_mul(r.into())) + // Minimum execution time: 381_815 nanoseconds. + Weight::from_ref_time(390_931_793) + // Standard Error: 61_918 + .saturating_add(Weight::from_ref_time(18_798_438).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().writes(3)) } @@ -1634,10 +1647,10 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_caller_is_origin(r: u32, ) -> Weight { - // Minimum execution time: 382_243 nanoseconds. - Weight::from_ref_time(387_692_764) - // Standard Error: 32_726 - .saturating_add(Weight::from_ref_time(10_753_573).saturating_mul(r.into())) + // Minimum execution time: 379_162 nanoseconds. + Weight::from_ref_time(383_796_363) + // Standard Error: 25_555 + .saturating_add(Weight::from_ref_time(10_843_721).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().writes(3)) } @@ -1648,10 +1661,10 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_address(r: u32, ) -> Weight { - // Minimum execution time: 383_993 nanoseconds. - Weight::from_ref_time(389_189_394) - // Standard Error: 55_885 - .saturating_add(Weight::from_ref_time(15_943_739).saturating_mul(r.into())) + // Minimum execution time: 380_307 nanoseconds. + Weight::from_ref_time(390_211_779) + // Standard Error: 53_326 + .saturating_add(Weight::from_ref_time(15_311_171).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().writes(3)) } @@ -1662,10 +1675,10 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_gas_left(r: u32, ) -> Weight { - // Minimum execution time: 383_057 nanoseconds. - Weight::from_ref_time(387_466_678) - // Standard Error: 38_570 - .saturating_add(Weight::from_ref_time(15_739_675).saturating_mul(r.into())) + // Minimum execution time: 380_230 nanoseconds. + Weight::from_ref_time(383_470_453) + // Standard Error: 45_007 + .saturating_add(Weight::from_ref_time(15_582_472).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().writes(3)) } @@ -1676,10 +1689,10 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_balance(r: u32, ) -> Weight { - // Minimum execution time: 383_688 nanoseconds. - Weight::from_ref_time(394_708_428) - // Standard Error: 101_035 - .saturating_add(Weight::from_ref_time(89_822_613).saturating_mul(r.into())) + // Minimum execution time: 380_244 nanoseconds. + Weight::from_ref_time(388_092_461) + // Standard Error: 98_199 + .saturating_add(Weight::from_ref_time(97_339_528).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(7)) .saturating_add(RocksDbWeight::get().writes(3)) } @@ -1690,10 +1703,10 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_value_transferred(r: u32, ) -> Weight { - // Minimum execution time: 383_511 nanoseconds. - Weight::from_ref_time(387_270_075) - // Standard Error: 33_383 - .saturating_add(Weight::from_ref_time(15_672_963).saturating_mul(r.into())) + // Minimum execution time: 380_242 nanoseconds. + Weight::from_ref_time(384_078_258) + // Standard Error: 28_510 + .saturating_add(Weight::from_ref_time(15_423_359).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().writes(3)) } @@ -1704,10 +1717,10 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_minimum_balance(r: u32, ) -> Weight { - // Minimum execution time: 383_483 nanoseconds. - Weight::from_ref_time(386_995_457) - // Standard Error: 28_781 - .saturating_add(Weight::from_ref_time(15_632_597).saturating_mul(r.into())) + // Minimum execution time: 379_890 nanoseconds. + Weight::from_ref_time(383_658_430) + // Standard Error: 44_976 + .saturating_add(Weight::from_ref_time(15_451_905).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().writes(3)) } @@ -1718,10 +1731,10 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_block_number(r: u32, ) -> Weight { - // Minimum execution time: 383_630 nanoseconds. - Weight::from_ref_time(387_801_190) - // Standard Error: 41_234 - .saturating_add(Weight::from_ref_time(15_531_236).saturating_mul(r.into())) + // Minimum execution time: 380_269 nanoseconds. + Weight::from_ref_time(383_580_481) + // Standard Error: 31_862 + .saturating_add(Weight::from_ref_time(15_230_473).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().writes(3)) } @@ -1732,10 +1745,10 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_now(r: u32, ) -> Weight { - // Minimum execution time: 383_668 nanoseconds. - Weight::from_ref_time(387_490_160) - // Standard Error: 28_070 - .saturating_add(Weight::from_ref_time(15_639_764).saturating_mul(r.into())) + // Minimum execution time: 380_308 nanoseconds. + Weight::from_ref_time(383_732_372) + // Standard Error: 38_359 + .saturating_add(Weight::from_ref_time(15_358_775).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().writes(3)) } @@ -1747,10 +1760,10 @@ impl WeightInfo for () { // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) /// The range of component `r` is `[0, 20]`. fn seal_weight_to_fee(r: u32, ) -> Weight { - // Minimum execution time: 383_401 nanoseconds. - Weight::from_ref_time(393_140_360) - // Standard Error: 95_092 - .saturating_add(Weight::from_ref_time(83_864_160).saturating_mul(r.into())) + // Minimum execution time: 380_834 nanoseconds. + Weight::from_ref_time(388_999_459) + // Standard Error: 96_447 + .saturating_add(Weight::from_ref_time(86_714_414).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(7)) .saturating_add(RocksDbWeight::get().writes(3)) } @@ -1761,10 +1774,10 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_gas(r: u32, ) -> Weight { - // Minimum execution time: 142_684 nanoseconds. - Weight::from_ref_time(145_540_019) - // Standard Error: 18_177 - .saturating_add(Weight::from_ref_time(8_061_360).saturating_mul(r.into())) + // Minimum execution time: 140_955 nanoseconds. + Weight::from_ref_time(144_716_423) + // Standard Error: 11_370 + .saturating_add(Weight::from_ref_time(7_858_807).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().writes(3)) } @@ -1775,10 +1788,10 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_input(r: u32, ) -> Weight { - // Minimum execution time: 383_472 nanoseconds. - Weight::from_ref_time(386_518_915) - // Standard Error: 46_174 - .saturating_add(Weight::from_ref_time(14_052_552).saturating_mul(r.into())) + // Minimum execution time: 380_210 nanoseconds. + Weight::from_ref_time(384_185_776) + // Standard Error: 49_038 + .saturating_add(Weight::from_ref_time(13_649_793).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().writes(3)) } @@ -1789,10 +1802,10 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `n` is `[0, 1024]`. fn seal_input_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 398_912 nanoseconds. - Weight::from_ref_time(426_793_015) - // Standard Error: 5_524 - .saturating_add(Weight::from_ref_time(9_645_931).saturating_mul(n.into())) + // Minimum execution time: 396_209 nanoseconds. + Weight::from_ref_time(424_522_611) + // Standard Error: 3_917 + .saturating_add(Weight::from_ref_time(9_627_216).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().writes(3)) } @@ -1803,10 +1816,10 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 1]`. fn seal_return(r: u32, ) -> Weight { - // Minimum execution time: 380_288 nanoseconds. - Weight::from_ref_time(382_064_302) - // Standard Error: 274_854 - .saturating_add(Weight::from_ref_time(5_341_497).saturating_mul(r.into())) + // Minimum execution time: 378_412 nanoseconds. + Weight::from_ref_time(380_502_085) + // Standard Error: 201_552 + .saturating_add(Weight::from_ref_time(1_434_214).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().writes(3)) } @@ -1817,10 +1830,10 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `n` is `[0, 1024]`. fn seal_return_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 382_104 nanoseconds. - Weight::from_ref_time(383_966_376) - // Standard Error: 668 - .saturating_add(Weight::from_ref_time(230_898).saturating_mul(n.into())) + // Minimum execution time: 381_463 nanoseconds. + Weight::from_ref_time(383_955_553) + // Standard Error: 998 + .saturating_add(Weight::from_ref_time(230_512).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().writes(3)) } @@ -1833,10 +1846,10 @@ impl WeightInfo for () { // Storage: Contracts OwnerInfoOf (r:1 w:1) /// The range of component `r` is `[0, 1]`. fn seal_terminate(r: u32, ) -> Weight { - // Minimum execution time: 382_423 nanoseconds. - Weight::from_ref_time(384_162_748) - // Standard Error: 403_637 - .saturating_add(Weight::from_ref_time(57_465_451).saturating_mul(r.into())) + // Minimum execution time: 379_877 nanoseconds. + Weight::from_ref_time(381_729_546) + // Standard Error: 214_004 + .saturating_add(Weight::from_ref_time(52_528_353).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().reads((5_u64).saturating_mul(r.into()))) .saturating_add(RocksDbWeight::get().writes(3)) @@ -1850,10 +1863,10 @@ impl WeightInfo for () { // Storage: RandomnessCollectiveFlip RandomMaterial (r:1 w:0) /// The range of component `r` is `[0, 20]`. fn seal_random(r: u32, ) -> Weight { - // Minimum execution time: 382_853 nanoseconds. - Weight::from_ref_time(391_962_017) - // Standard Error: 102_169 - .saturating_add(Weight::from_ref_time(108_325_188).saturating_mul(r.into())) + // Minimum execution time: 380_275 nanoseconds. + Weight::from_ref_time(386_495_777) + // Standard Error: 94_674 + .saturating_add(Weight::from_ref_time(108_432_929).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(7)) .saturating_add(RocksDbWeight::get().writes(3)) } @@ -1864,10 +1877,10 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_deposit_event(r: u32, ) -> Weight { - // Minimum execution time: 380_999 nanoseconds. - Weight::from_ref_time(392_336_632) - // Standard Error: 168_846 - .saturating_add(Weight::from_ref_time(218_950_403).saturating_mul(r.into())) + // Minimum execution time: 379_095 nanoseconds. + Weight::from_ref_time(393_997_924) + // Standard Error: 141_916 + .saturating_add(Weight::from_ref_time(212_937_170).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().writes(3)) } @@ -1879,12 +1892,12 @@ impl WeightInfo for () { /// The range of component `t` is `[0, 4]`. /// The range of component `n` is `[0, 16]`. fn seal_deposit_event_per_topic_and_kb(t: u32, n: u32, ) -> Weight { - // Minimum execution time: 1_276_841 nanoseconds. - Weight::from_ref_time(587_558_952) - // Standard Error: 504_583 - .saturating_add(Weight::from_ref_time(178_141_140).saturating_mul(t.into())) - // Standard Error: 138_583 - .saturating_add(Weight::from_ref_time(71_194_319).saturating_mul(n.into())) + // Minimum execution time: 1_241_001 nanoseconds. + Weight::from_ref_time(548_526_917) + // Standard Error: 496_807 + .saturating_add(Weight::from_ref_time(177_087_769).saturating_mul(t.into())) + // Standard Error: 136_447 + .saturating_add(Weight::from_ref_time(71_558_402).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().reads((80_u64).saturating_mul(t.into()))) .saturating_add(RocksDbWeight::get().writes(3)) @@ -1897,20 +1910,20 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_debug_message(r: u32, ) -> Weight { - // Minimum execution time: 161_230 nanoseconds. - Weight::from_ref_time(168_508_241) - // Standard Error: 31_112 - .saturating_add(Weight::from_ref_time(12_496_531).saturating_mul(r.into())) + // Minimum execution time: 159_158 nanoseconds. + Weight::from_ref_time(163_427_712) + // Standard Error: 22_442 + .saturating_add(Weight::from_ref_time(12_647_838).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().writes(3)) } // Storage: Skipped Metadata (r:0 w:0) /// The range of component `r` is `[0, 10]`. fn seal_set_storage(r: u32, ) -> Weight { - // Minimum execution time: 383_055 nanoseconds. - Weight::from_ref_time(339_358_786) - // Standard Error: 486_941 - .saturating_add(Weight::from_ref_time(412_066_056).saturating_mul(r.into())) + // Minimum execution time: 381_068 nanoseconds. + Weight::from_ref_time(341_041_230) + // Standard Error: 464_053 + .saturating_add(Weight::from_ref_time(402_677_314).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().reads((80_u64).saturating_mul(r.into()))) .saturating_add(RocksDbWeight::get().writes(3)) @@ -1919,10 +1932,10 @@ impl WeightInfo for () { // Storage: Skipped Metadata (r:0 w:0) /// The range of component `n` is `[0, 8]`. fn seal_set_storage_per_new_kb(n: u32, ) -> Weight { - // Minimum execution time: 512_301 nanoseconds. - Weight::from_ref_time(670_220_816) - // Standard Error: 1_460_983 - .saturating_add(Weight::from_ref_time(97_308_816).saturating_mul(n.into())) + // Minimum execution time: 508_695 nanoseconds. + Weight::from_ref_time(663_159_695) + // Standard Error: 1_419_342 + .saturating_add(Weight::from_ref_time(96_558_570).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(52)) .saturating_add(RocksDbWeight::get().reads((7_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(50)) @@ -1931,10 +1944,10 @@ impl WeightInfo for () { // Storage: Skipped Metadata (r:0 w:0) /// The range of component `n` is `[0, 8]`. fn seal_set_storage_per_old_kb(n: u32, ) -> Weight { - // Minimum execution time: 510_820 nanoseconds. - Weight::from_ref_time(638_537_372) - // Standard Error: 1_195_632 - .saturating_add(Weight::from_ref_time(65_979_491).saturating_mul(n.into())) + // Minimum execution time: 508_542 nanoseconds. + Weight::from_ref_time(634_146_978) + // Standard Error: 1_168_252 + .saturating_add(Weight::from_ref_time(64_231_947).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(51)) .saturating_add(RocksDbWeight::get().reads((7_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(49)) @@ -1943,10 +1956,10 @@ impl WeightInfo for () { // Storage: Skipped Metadata (r:0 w:0) /// The range of component `r` is `[0, 10]`. fn seal_clear_storage(r: u32, ) -> Weight { - // Minimum execution time: 383_596 nanoseconds. - Weight::from_ref_time(341_575_167) - // Standard Error: 478_894 - .saturating_add(Weight::from_ref_time(407_044_103).saturating_mul(r.into())) + // Minimum execution time: 381_743 nanoseconds. + Weight::from_ref_time(337_309_674) + // Standard Error: 527_407 + .saturating_add(Weight::from_ref_time(395_767_068).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().reads((80_u64).saturating_mul(r.into()))) .saturating_add(RocksDbWeight::get().writes(3)) @@ -1955,10 +1968,10 @@ impl WeightInfo for () { // Storage: Skipped Metadata (r:0 w:0) /// The range of component `n` is `[0, 8]`. fn seal_clear_storage_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 481_757 nanoseconds. - Weight::from_ref_time(628_126_550) - // Standard Error: 1_363_017 - .saturating_add(Weight::from_ref_time(67_242_851).saturating_mul(n.into())) + // Minimum execution time: 478_283 nanoseconds. + Weight::from_ref_time(616_673_245) + // Standard Error: 1_290_784 + .saturating_add(Weight::from_ref_time(66_534_552).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(51)) .saturating_add(RocksDbWeight::get().reads((7_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(48)) @@ -1967,10 +1980,10 @@ impl WeightInfo for () { // Storage: Skipped Metadata (r:0 w:0) /// The range of component `r` is `[0, 10]`. fn seal_get_storage(r: u32, ) -> Weight { - // Minimum execution time: 383_868 nanoseconds. - Weight::from_ref_time(359_800_153) - // Standard Error: 338_998 - .saturating_add(Weight::from_ref_time(323_351_220).saturating_mul(r.into())) + // Minimum execution time: 381_916 nanoseconds. + Weight::from_ref_time(349_150_912) + // Standard Error: 443_388 + .saturating_add(Weight::from_ref_time(316_975_558).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().reads((80_u64).saturating_mul(r.into()))) .saturating_add(RocksDbWeight::get().writes(3)) @@ -1978,10 +1991,10 @@ impl WeightInfo for () { // Storage: Skipped Metadata (r:0 w:0) /// The range of component `n` is `[0, 8]`. fn seal_get_storage_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 462_237 nanoseconds. - Weight::from_ref_time(585_809_405) - // Standard Error: 1_181_517 - .saturating_add(Weight::from_ref_time(160_905_409).saturating_mul(n.into())) + // Minimum execution time: 459_294 nanoseconds. + Weight::from_ref_time(579_698_524) + // Standard Error: 1_111_681 + .saturating_add(Weight::from_ref_time(159_276_087).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(51)) .saturating_add(RocksDbWeight::get().reads((7_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(3)) @@ -1989,10 +2002,10 @@ impl WeightInfo for () { // Storage: Skipped Metadata (r:0 w:0) /// The range of component `r` is `[0, 10]`. fn seal_contains_storage(r: u32, ) -> Weight { - // Minimum execution time: 383_794 nanoseconds. - Weight::from_ref_time(355_233_888) - // Standard Error: 416_492 - .saturating_add(Weight::from_ref_time(317_857_887).saturating_mul(r.into())) + // Minimum execution time: 381_700 nanoseconds. + Weight::from_ref_time(352_544_675) + // Standard Error: 401_504 + .saturating_add(Weight::from_ref_time(304_380_106).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().reads((80_u64).saturating_mul(r.into()))) .saturating_add(RocksDbWeight::get().writes(3)) @@ -2000,10 +2013,10 @@ impl WeightInfo for () { // Storage: Skipped Metadata (r:0 w:0) /// The range of component `n` is `[0, 8]`. fn seal_contains_storage_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 462_530 nanoseconds. - Weight::from_ref_time(571_276_165) - // Standard Error: 1_035_339 - .saturating_add(Weight::from_ref_time(63_481_618).saturating_mul(n.into())) + // Minimum execution time: 455_595 nanoseconds. + Weight::from_ref_time(560_428_166) + // Standard Error: 991_088 + .saturating_add(Weight::from_ref_time(61_810_610).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(51)) .saturating_add(RocksDbWeight::get().reads((7_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(3)) @@ -2011,10 +2024,10 @@ impl WeightInfo for () { // Storage: Skipped Metadata (r:0 w:0) /// The range of component `r` is `[0, 10]`. fn seal_take_storage(r: u32, ) -> Weight { - // Minimum execution time: 385_343 nanoseconds. - Weight::from_ref_time(341_897_876) - // Standard Error: 451_948 - .saturating_add(Weight::from_ref_time(417_987_655).saturating_mul(r.into())) + // Minimum execution time: 382_000 nanoseconds. + Weight::from_ref_time(336_164_219) + // Standard Error: 601_744 + .saturating_add(Weight::from_ref_time(406_198_079).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().reads((80_u64).saturating_mul(r.into()))) .saturating_add(RocksDbWeight::get().writes(3)) @@ -2023,10 +2036,10 @@ impl WeightInfo for () { // Storage: Skipped Metadata (r:0 w:0) /// The range of component `n` is `[0, 8]`. fn seal_take_storage_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 485_507 nanoseconds. - Weight::from_ref_time(646_265_325) - // Standard Error: 1_495_172 - .saturating_add(Weight::from_ref_time(166_973_554).saturating_mul(n.into())) + // Minimum execution time: 482_335 nanoseconds. + Weight::from_ref_time(634_245_177) + // Standard Error: 1_418_845 + .saturating_add(Weight::from_ref_time(164_352_113).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(51)) .saturating_add(RocksDbWeight::get().reads((7_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(48)) @@ -2039,10 +2052,10 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_transfer(r: u32, ) -> Weight { - // Minimum execution time: 384_834 nanoseconds. - Weight::from_ref_time(348_341_375) - // Standard Error: 792_708 - .saturating_add(Weight::from_ref_time(1_336_691_822).saturating_mul(r.into())) + // Minimum execution time: 382_142 nanoseconds. + Weight::from_ref_time(317_581_708) + // Standard Error: 682_156 + .saturating_add(Weight::from_ref_time(1_305_289_569).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(7)) .saturating_add(RocksDbWeight::get().reads((80_u64).saturating_mul(r.into()))) .saturating_add(RocksDbWeight::get().writes(4)) @@ -2055,10 +2068,10 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_call(r: u32, ) -> Weight { - // Minimum execution time: 386_112 nanoseconds. - Weight::from_ref_time(386_971_000) - // Standard Error: 5_920_386 - .saturating_add(Weight::from_ref_time(28_051_657_660).saturating_mul(r.into())) + // Minimum execution time: 383_580 nanoseconds. + Weight::from_ref_time(384_176_000) + // Standard Error: 6_483_482 + .saturating_add(Weight::from_ref_time(28_047_685_517).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(7)) .saturating_add(RocksDbWeight::get().reads((160_u64).saturating_mul(r.into()))) .saturating_add(RocksDbWeight::get().writes(3)) @@ -2071,10 +2084,10 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_delegate_call(r: u32, ) -> Weight { - // Minimum execution time: 385_776 nanoseconds. - Weight::from_ref_time(387_017_000) - // Standard Error: 6_680_801 - .saturating_add(Weight::from_ref_time(27_761_537_154).saturating_mul(r.into())) + // Minimum execution time: 384_523 nanoseconds. + Weight::from_ref_time(385_105_000) + // Standard Error: 6_156_142 + .saturating_add(Weight::from_ref_time(27_780_652_513).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().reads((150_u64).saturating_mul(r.into()))) .saturating_add(RocksDbWeight::get().writes(3)) @@ -2088,12 +2101,12 @@ impl WeightInfo for () { /// The range of component `t` is `[0, 1]`. /// The range of component `c` is `[0, 1024]`. fn seal_call_per_transfer_clone_kb(t: u32, c: u32, ) -> Weight { - // Minimum execution time: 9_623_952 nanoseconds. - Weight::from_ref_time(8_552_923_566) - // Standard Error: 6_582_866 - .saturating_add(Weight::from_ref_time(1_283_786_003).saturating_mul(t.into())) - // Standard Error: 9_870 - .saturating_add(Weight::from_ref_time(9_833_844).saturating_mul(c.into())) + // Minimum execution time: 9_509_961 nanoseconds. + Weight::from_ref_time(8_509_991_348) + // Standard Error: 6_614_757 + .saturating_add(Weight::from_ref_time(1_244_514_376).saturating_mul(t.into())) + // Standard Error: 9_918 + .saturating_add(Weight::from_ref_time(9_856_517).saturating_mul(c.into())) .saturating_add(RocksDbWeight::get().reads(167)) .saturating_add(RocksDbWeight::get().reads((81_u64).saturating_mul(t.into()))) .saturating_add(RocksDbWeight::get().writes(163)) @@ -2108,10 +2121,10 @@ impl WeightInfo for () { // Storage: Contracts OwnerInfoOf (r:80 w:80) /// The range of component `r` is `[0, 20]`. fn seal_instantiate(r: u32, ) -> Weight { - // Minimum execution time: 386_667 nanoseconds. - Weight::from_ref_time(387_559_000) - // Standard Error: 18_953_118 - .saturating_add(Weight::from_ref_time(33_188_342_429).saturating_mul(r.into())) + // Minimum execution time: 384_604 nanoseconds. + Weight::from_ref_time(385_278_000) + // Standard Error: 21_140_468 + .saturating_add(Weight::from_ref_time(33_100_726_150).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(8)) .saturating_add(RocksDbWeight::get().reads((400_u64).saturating_mul(r.into()))) .saturating_add(RocksDbWeight::get().writes(5)) @@ -2125,14 +2138,15 @@ impl WeightInfo for () { // Storage: Contracts OwnerInfoOf (r:1 w:1) // Storage: System EventTopics (r:82 w:82) /// The range of component `t` is `[0, 1]`. + /// The range of component `i` is `[0, 960]`. /// The range of component `s` is `[0, 960]`. - fn seal_instantiate_per_transfer_salt_kb(t: u32, s: u32, ) -> Weight { - // Minimum execution time: 11_664_478 nanoseconds. - Weight::from_ref_time(11_359_540_086) - // Standard Error: 45_626_277 - .saturating_add(Weight::from_ref_time(19_120_579).saturating_mul(t.into())) - // Standard Error: 72_976 - .saturating_add(Weight::from_ref_time(125_731_953).saturating_mul(s.into())) + fn seal_instantiate_per_transfer_input_salt_kb(t: u32, i: u32, s: u32, ) -> Weight { + // Minimum execution time: 129_699_480 nanoseconds. + Weight::from_ref_time(10_187_699_005) + // Standard Error: 155_040 + .saturating_add(Weight::from_ref_time(125_284_310).saturating_mul(i.into())) + // Standard Error: 155_040 + .saturating_add(Weight::from_ref_time(125_850_564).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(249)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(t.into()))) .saturating_add(RocksDbWeight::get().writes(247)) @@ -2145,10 +2159,10 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 1]`. fn seal_hash_sha2_256(r: u32, ) -> Weight { - // Minimum execution time: 384_826 nanoseconds. - Weight::from_ref_time(387_293_630) - // Standard Error: 437_875 - .saturating_add(Weight::from_ref_time(48_464_369).saturating_mul(r.into())) + // Minimum execution time: 380_397 nanoseconds. + Weight::from_ref_time(382_881_855) + // Standard Error: 290_948 + .saturating_add(Weight::from_ref_time(41_017_644).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().writes(3)) } @@ -2159,10 +2173,10 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `n` is `[0, 1024]`. fn seal_hash_sha2_256_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 426_531 nanoseconds. - Weight::from_ref_time(427_315_000) - // Standard Error: 48_058 - .saturating_add(Weight::from_ref_time(327_318_884).saturating_mul(n.into())) + // Minimum execution time: 422_439 nanoseconds. + Weight::from_ref_time(423_480_000) + // Standard Error: 56_072 + .saturating_add(Weight::from_ref_time(329_103_825).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().writes(3)) } @@ -2173,10 +2187,10 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 1]`. fn seal_hash_keccak_256(r: u32, ) -> Weight { - // Minimum execution time: 384_084 nanoseconds. - Weight::from_ref_time(386_354_628) - // Standard Error: 195_951 - .saturating_add(Weight::from_ref_time(52_991_271).saturating_mul(r.into())) + // Minimum execution time: 380_433 nanoseconds. + Weight::from_ref_time(382_325_624) + // Standard Error: 139_248 + .saturating_add(Weight::from_ref_time(53_159_175).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().writes(3)) } @@ -2187,10 +2201,10 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `n` is `[0, 1024]`. fn seal_hash_keccak_256_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 438_319 nanoseconds. - Weight::from_ref_time(439_001_000) - // Standard Error: 53_445 - .saturating_add(Weight::from_ref_time(251_353_803).saturating_mul(n.into())) + // Minimum execution time: 434_834 nanoseconds. + Weight::from_ref_time(435_383_000) + // Standard Error: 59_824 + .saturating_add(Weight::from_ref_time(251_297_967).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().writes(3)) } @@ -2201,10 +2215,10 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 1]`. fn seal_hash_blake2_256(r: u32, ) -> Weight { - // Minimum execution time: 384_397 nanoseconds. - Weight::from_ref_time(386_532_859) - // Standard Error: 141_195 - .saturating_add(Weight::from_ref_time(31_116_440).saturating_mul(r.into())) + // Minimum execution time: 379_088 nanoseconds. + Weight::from_ref_time(381_627_077) + // Standard Error: 152_561 + .saturating_add(Weight::from_ref_time(31_696_922).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().writes(3)) } @@ -2215,10 +2229,10 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `n` is `[0, 1024]`. fn seal_hash_blake2_256_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 416_504 nanoseconds. - Weight::from_ref_time(417_686_000) - // Standard Error: 47_003 - .saturating_add(Weight::from_ref_time(103_095_636).saturating_mul(n.into())) + // Minimum execution time: 411_630 nanoseconds. + Weight::from_ref_time(412_354_000) + // Standard Error: 50_788 + .saturating_add(Weight::from_ref_time(103_105_715).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().writes(3)) } @@ -2229,10 +2243,10 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 1]`. fn seal_hash_blake2_128(r: u32, ) -> Weight { - // Minimum execution time: 382_479 nanoseconds. - Weight::from_ref_time(384_623_057) - // Standard Error: 243_054 - .saturating_add(Weight::from_ref_time(37_025_542).saturating_mul(r.into())) + // Minimum execution time: 379_549 nanoseconds. + Weight::from_ref_time(382_406_346) + // Standard Error: 701_449 + .saturating_add(Weight::from_ref_time(31_066_353).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().writes(3)) } @@ -2243,10 +2257,10 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `n` is `[0, 1024]`. fn seal_hash_blake2_128_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 414_863 nanoseconds. - Weight::from_ref_time(415_728_000) - // Standard Error: 48_764 - .saturating_add(Weight::from_ref_time(103_050_672).saturating_mul(n.into())) + // Minimum execution time: 412_112 nanoseconds. + Weight::from_ref_time(412_710_000) + // Standard Error: 54_875 + .saturating_add(Weight::from_ref_time(103_169_035).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().writes(3)) } @@ -2257,10 +2271,10 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 1]`. fn seal_ecdsa_recover(r: u32, ) -> Weight { - // Minimum execution time: 384_418 nanoseconds. - Weight::from_ref_time(387_283_069) - // Standard Error: 526_301 - .saturating_add(Weight::from_ref_time(2_993_987_030).saturating_mul(r.into())) + // Minimum execution time: 381_994 nanoseconds. + Weight::from_ref_time(383_832_551) + // Standard Error: 676_656 + .saturating_add(Weight::from_ref_time(3_020_035_748).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().writes(3)) } @@ -2271,10 +2285,10 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 1]`. fn seal_ecdsa_to_eth_address(r: u32, ) -> Weight { - // Minimum execution time: 383_686 nanoseconds. - Weight::from_ref_time(385_812_638) - // Standard Error: 539_029 - .saturating_add(Weight::from_ref_time(2_098_063_561).saturating_mul(r.into())) + // Minimum execution time: 381_472 nanoseconds. + Weight::from_ref_time(383_590_834) + // Standard Error: 538_574 + .saturating_add(Weight::from_ref_time(2_077_926_265).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().writes(3)) } @@ -2286,10 +2300,10 @@ impl WeightInfo for () { // Storage: Contracts OwnerInfoOf (r:16 w:16) /// The range of component `r` is `[0, 20]`. fn seal_set_code_hash(r: u32, ) -> Weight { - // Minimum execution time: 384_399 nanoseconds. - Weight::from_ref_time(385_337_000) - // Standard Error: 2_827_655 - .saturating_add(Weight::from_ref_time(1_383_659_432).saturating_mul(r.into())) + // Minimum execution time: 381_527 nanoseconds. + Weight::from_ref_time(382_291_000) + // Standard Error: 2_760_840 + .saturating_add(Weight::from_ref_time(1_356_115_009).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().reads((225_u64).saturating_mul(r.into()))) .saturating_add(RocksDbWeight::get().writes(3)) @@ -2302,10 +2316,10 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_reentrance_count(r: u32, ) -> Weight { - // Minimum execution time: 385_165 nanoseconds. - Weight::from_ref_time(389_255_026) - // Standard Error: 25_918 - .saturating_add(Weight::from_ref_time(10_716_905).saturating_mul(r.into())) + // Minimum execution time: 382_722 nanoseconds. + Weight::from_ref_time(387_231_409) + // Standard Error: 28_817 + .saturating_add(Weight::from_ref_time(11_349_809).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().writes(3)) } @@ -2316,10 +2330,10 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_account_reentrance_count(r: u32, ) -> Weight { - // Minimum execution time: 386_959 nanoseconds. - Weight::from_ref_time(423_364_524) - // Standard Error: 127_096 - .saturating_add(Weight::from_ref_time(25_552_186).saturating_mul(r.into())) + // Minimum execution time: 383_568 nanoseconds. + Weight::from_ref_time(419_835_108) + // Standard Error: 125_982 + .saturating_add(Weight::from_ref_time(25_241_800).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(6)) .saturating_add(RocksDbWeight::get().writes(3)) } @@ -2331,375 +2345,375 @@ impl WeightInfo for () { // Storage: Contracts Nonce (r:1 w:1) /// The range of component `r` is `[0, 20]`. fn seal_instantiation_nonce(r: u32, ) -> Weight { - // Minimum execution time: 293_987 nanoseconds. - Weight::from_ref_time(307_154_849) - // Standard Error: 27_486 - .saturating_add(Weight::from_ref_time(8_759_333).saturating_mul(r.into())) + // Minimum execution time: 382_084 nanoseconds. + Weight::from_ref_time(388_155_568) + // Standard Error: 29_161 + .saturating_add(Weight::from_ref_time(9_015_217).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(7)) .saturating_add(RocksDbWeight::get().writes(4)) } /// The range of component `r` is `[0, 50]`. fn instr_i64const(r: u32, ) -> Weight { - // Minimum execution time: 688 nanoseconds. - Weight::from_ref_time(914_830) - // Standard Error: 222 - .saturating_add(Weight::from_ref_time(343_835).saturating_mul(r.into())) + // Minimum execution time: 593 nanoseconds. + Weight::from_ref_time(816_706) + // Standard Error: 173 + .saturating_add(Weight::from_ref_time(344_732).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64load(r: u32, ) -> Weight { - // Minimum execution time: 775 nanoseconds. - Weight::from_ref_time(1_286_008) - // Standard Error: 391 - .saturating_add(Weight::from_ref_time(984_759).saturating_mul(r.into())) + // Minimum execution time: 705 nanoseconds. + Weight::from_ref_time(1_191_205) + // Standard Error: 600 + .saturating_add(Weight::from_ref_time(986_102).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64store(r: u32, ) -> Weight { - // Minimum execution time: 779 nanoseconds. - Weight::from_ref_time(1_162_588) - // Standard Error: 694 - .saturating_add(Weight::from_ref_time(883_828).saturating_mul(r.into())) + // Minimum execution time: 717 nanoseconds. + Weight::from_ref_time(1_019_448) + // Standard Error: 421 + .saturating_add(Weight::from_ref_time(882_531).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_select(r: u32, ) -> Weight { - // Minimum execution time: 687 nanoseconds. - Weight::from_ref_time(965_966) - // Standard Error: 290 - .saturating_add(Weight::from_ref_time(955_392).saturating_mul(r.into())) + // Minimum execution time: 642 nanoseconds. + Weight::from_ref_time(917_880) + // Standard Error: 362 + .saturating_add(Weight::from_ref_time(957_235).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_if(r: u32, ) -> Weight { - // Minimum execution time: 681 nanoseconds. - Weight::from_ref_time(778_970) - // Standard Error: 670 - .saturating_add(Weight::from_ref_time(1_265_116).saturating_mul(r.into())) + // Minimum execution time: 630 nanoseconds. + Weight::from_ref_time(694_427) + // Standard Error: 1_506 + .saturating_add(Weight::from_ref_time(1_298_411).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_br(r: u32, ) -> Weight { - // Minimum execution time: 673 nanoseconds. - Weight::from_ref_time(1_125_562) - // Standard Error: 818 - .saturating_add(Weight::from_ref_time(528_126).saturating_mul(r.into())) + // Minimum execution time: 634 nanoseconds. + Weight::from_ref_time(1_101_754) + // Standard Error: 840 + .saturating_add(Weight::from_ref_time(526_433).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_br_if(r: u32, ) -> Weight { - // Minimum execution time: 649 nanoseconds. - Weight::from_ref_time(780_802) - // Standard Error: 943 - .saturating_add(Weight::from_ref_time(800_988).saturating_mul(r.into())) + // Minimum execution time: 651 nanoseconds. + Weight::from_ref_time(790_908) + // Standard Error: 849 + .saturating_add(Weight::from_ref_time(800_188).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_br_table(r: u32, ) -> Weight { - // Minimum execution time: 662 nanoseconds. - Weight::from_ref_time(555_078) - // Standard Error: 1_540 - .saturating_add(Weight::from_ref_time(1_078_705).saturating_mul(r.into())) + // Minimum execution time: 622 nanoseconds. + Weight::from_ref_time(416_266) + // Standard Error: 1_574 + .saturating_add(Weight::from_ref_time(1_080_225).saturating_mul(r.into())) } /// The range of component `e` is `[1, 256]`. fn instr_br_table_per_entry(e: u32, ) -> Weight { - // Minimum execution time: 2_177 nanoseconds. - Weight::from_ref_time(2_581_121) - // Standard Error: 67 - .saturating_add(Weight::from_ref_time(5_001).saturating_mul(e.into())) + // Minimum execution time: 2_131 nanoseconds. + Weight::from_ref_time(2_540_446) + // Standard Error: 75 + .saturating_add(Weight::from_ref_time(4_997).saturating_mul(e.into())) } /// The range of component `r` is `[0, 50]`. fn instr_call(r: u32, ) -> Weight { - // Minimum execution time: 702 nanoseconds. - Weight::from_ref_time(1_570_695) - // Standard Error: 1_524 - .saturating_add(Weight::from_ref_time(2_192_040).saturating_mul(r.into())) + // Minimum execution time: 650 nanoseconds. + Weight::from_ref_time(1_577_978) + // Standard Error: 2_696 + .saturating_add(Weight::from_ref_time(2_204_044).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_call_indirect(r: u32, ) -> Weight { - // Minimum execution time: 745 nanoseconds. - Weight::from_ref_time(1_694_451) - // Standard Error: 4_170 - .saturating_add(Weight::from_ref_time(2_813_621).saturating_mul(r.into())) + // Minimum execution time: 742 nanoseconds. + Weight::from_ref_time(1_947_575) + // Standard Error: 1_651 + .saturating_add(Weight::from_ref_time(2_799_445).saturating_mul(r.into())) } /// The range of component `p` is `[0, 128]`. fn instr_call_indirect_per_param(p: u32, ) -> Weight { - // Minimum execution time: 4_255 nanoseconds. - Weight::from_ref_time(5_064_243) - // Standard Error: 289 - .saturating_add(Weight::from_ref_time(178_868).saturating_mul(p.into())) + // Minimum execution time: 4_230 nanoseconds. + Weight::from_ref_time(5_079_432) + // Standard Error: 315 + .saturating_add(Weight::from_ref_time(179_278).saturating_mul(p.into())) } /// The range of component `l` is `[0, 1024]`. fn instr_call_per_local(l: u32, ) -> Weight { - // Minimum execution time: 2_802 nanoseconds. - Weight::from_ref_time(3_474_642) - // Standard Error: 28 - .saturating_add(Weight::from_ref_time(92_517).saturating_mul(l.into())) + // Minimum execution time: 2_830 nanoseconds. + Weight::from_ref_time(3_601_633) + // Standard Error: 31 + .saturating_add(Weight::from_ref_time(92_499).saturating_mul(l.into())) } /// The range of component `r` is `[0, 50]`. fn instr_local_get(r: u32, ) -> Weight { - // Minimum execution time: 2_973 nanoseconds. - Weight::from_ref_time(3_218_977) - // Standard Error: 183 - .saturating_add(Weight::from_ref_time(364_688).saturating_mul(r.into())) + // Minimum execution time: 2_896 nanoseconds. + Weight::from_ref_time(3_137_247) + // Standard Error: 245 + .saturating_add(Weight::from_ref_time(364_431).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_local_set(r: u32, ) -> Weight { - // Minimum execution time: 2_912 nanoseconds. - Weight::from_ref_time(3_173_203) - // Standard Error: 260 - .saturating_add(Weight::from_ref_time(381_853).saturating_mul(r.into())) + // Minimum execution time: 2_795 nanoseconds. + Weight::from_ref_time(3_199_878) + // Standard Error: 549 + .saturating_add(Weight::from_ref_time(380_524).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_local_tee(r: u32, ) -> Weight { - // Minimum execution time: 2_916 nanoseconds. - Weight::from_ref_time(3_228_548) - // Standard Error: 311 - .saturating_add(Weight::from_ref_time(526_008).saturating_mul(r.into())) + // Minimum execution time: 2_807 nanoseconds. + Weight::from_ref_time(3_130_120) + // Standard Error: 319 + .saturating_add(Weight::from_ref_time(525_857).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_global_get(r: u32, ) -> Weight { - // Minimum execution time: 739 nanoseconds. - Weight::from_ref_time(1_128_919) - // Standard Error: 479 - .saturating_add(Weight::from_ref_time(810_765).saturating_mul(r.into())) + // Minimum execution time: 678 nanoseconds. + Weight::from_ref_time(1_013_348) + // Standard Error: 362 + .saturating_add(Weight::from_ref_time(810_232).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_global_set(r: u32, ) -> Weight { - // Minimum execution time: 724 nanoseconds. - Weight::from_ref_time(1_044_382) - // Standard Error: 371 - .saturating_add(Weight::from_ref_time(828_530).saturating_mul(r.into())) + // Minimum execution time: 624 nanoseconds. + Weight::from_ref_time(973_583) + // Standard Error: 373 + .saturating_add(Weight::from_ref_time(829_360).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_memory_current(r: u32, ) -> Weight { - // Minimum execution time: 770 nanoseconds. - Weight::from_ref_time(988_307) - // Standard Error: 587 - .saturating_add(Weight::from_ref_time(699_091).saturating_mul(r.into())) + // Minimum execution time: 698 nanoseconds. + Weight::from_ref_time(904_862) + // Standard Error: 360 + .saturating_add(Weight::from_ref_time(694_614).saturating_mul(r.into())) } /// The range of component `r` is `[0, 1]`. fn instr_memory_grow(r: u32, ) -> Weight { - // Minimum execution time: 688 nanoseconds. - Weight::from_ref_time(747_995) - // Standard Error: 3_894 - .saturating_add(Weight::from_ref_time(234_512_504).saturating_mul(r.into())) + // Minimum execution time: 642 nanoseconds. + Weight::from_ref_time(735_085) + // Standard Error: 105_815 + .saturating_add(Weight::from_ref_time(233_816_514).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64clz(r: u32, ) -> Weight { - // Minimum execution time: 643 nanoseconds. - Weight::from_ref_time(946_893) - // Standard Error: 188 - .saturating_add(Weight::from_ref_time(505_004).saturating_mul(r.into())) + // Minimum execution time: 606 nanoseconds. + Weight::from_ref_time(850_590) + // Standard Error: 262 + .saturating_add(Weight::from_ref_time(507_721).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64ctz(r: u32, ) -> Weight { // Minimum execution time: 663 nanoseconds. - Weight::from_ref_time(965_194) - // Standard Error: 343 - .saturating_add(Weight::from_ref_time(505_213).saturating_mul(r.into())) + Weight::from_ref_time(853_060) + // Standard Error: 250 + .saturating_add(Weight::from_ref_time(514_225).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64popcnt(r: u32, ) -> Weight { - // Minimum execution time: 675 nanoseconds. - Weight::from_ref_time(903_705) - // Standard Error: 425 - .saturating_add(Weight::from_ref_time(507_749).saturating_mul(r.into())) + // Minimum execution time: 605 nanoseconds. + Weight::from_ref_time(849_563) + // Standard Error: 275 + .saturating_add(Weight::from_ref_time(511_494).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64eqz(r: u32, ) -> Weight { - // Minimum execution time: 637 nanoseconds. - Weight::from_ref_time(946_419) - // Standard Error: 301 - .saturating_add(Weight::from_ref_time(522_387).saturating_mul(r.into())) + // Minimum execution time: 618 nanoseconds. + Weight::from_ref_time(839_855) + // Standard Error: 228 + .saturating_add(Weight::from_ref_time(524_614).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64extendsi32(r: u32, ) -> Weight { - // Minimum execution time: 674 nanoseconds. - Weight::from_ref_time(963_566) - // Standard Error: 952 - .saturating_add(Weight::from_ref_time(504_528).saturating_mul(r.into())) + // Minimum execution time: 637 nanoseconds. + Weight::from_ref_time(860_326) + // Standard Error: 240 + .saturating_add(Weight::from_ref_time(504_847).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64extendui32(r: u32, ) -> Weight { - // Minimum execution time: 663 nanoseconds. - Weight::from_ref_time(927_099) - // Standard Error: 336 - .saturating_add(Weight::from_ref_time(505_200).saturating_mul(r.into())) + // Minimum execution time: 623 nanoseconds. + Weight::from_ref_time(844_585) + // Standard Error: 235 + .saturating_add(Weight::from_ref_time(505_821).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i32wrapi64(r: u32, ) -> Weight { - // Minimum execution time: 660 nanoseconds. - Weight::from_ref_time(901_114) - // Standard Error: 255 - .saturating_add(Weight::from_ref_time(503_803).saturating_mul(r.into())) + // Minimum execution time: 672 nanoseconds. + Weight::from_ref_time(826_784) + // Standard Error: 225 + .saturating_add(Weight::from_ref_time(504_632).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64eq(r: u32, ) -> Weight { - // Minimum execution time: 636 nanoseconds. - Weight::from_ref_time(906_526) - // Standard Error: 246 - .saturating_add(Weight::from_ref_time(730_299).saturating_mul(r.into())) + // Minimum execution time: 642 nanoseconds. + Weight::from_ref_time(867_080) + // Standard Error: 231 + .saturating_add(Weight::from_ref_time(732_430).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64ne(r: u32, ) -> Weight { - // Minimum execution time: 659 nanoseconds. - Weight::from_ref_time(947_772) - // Standard Error: 312 - .saturating_add(Weight::from_ref_time(729_463).saturating_mul(r.into())) + // Minimum execution time: 639 nanoseconds. + Weight::from_ref_time(866_094) + // Standard Error: 272 + .saturating_add(Weight::from_ref_time(732_560).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64lts(r: u32, ) -> Weight { - // Minimum execution time: 646 nanoseconds. - Weight::from_ref_time(923_694) - // Standard Error: 243 - .saturating_add(Weight::from_ref_time(738_995).saturating_mul(r.into())) + // Minimum execution time: 619 nanoseconds. + Weight::from_ref_time(928_672) + // Standard Error: 484 + .saturating_add(Weight::from_ref_time(739_523).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64ltu(r: u32, ) -> Weight { - // Minimum execution time: 652 nanoseconds. - Weight::from_ref_time(955_453) - // Standard Error: 1_430 - .saturating_add(Weight::from_ref_time(741_624).saturating_mul(r.into())) + // Minimum execution time: 612 nanoseconds. + Weight::from_ref_time(863_312) + // Standard Error: 328 + .saturating_add(Weight::from_ref_time(743_687).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64gts(r: u32, ) -> Weight { - // Minimum execution time: 642 nanoseconds. - Weight::from_ref_time(900_107) - // Standard Error: 376 - .saturating_add(Weight::from_ref_time(740_016).saturating_mul(r.into())) + // Minimum execution time: 648 nanoseconds. + Weight::from_ref_time(931_331) + // Standard Error: 612 + .saturating_add(Weight::from_ref_time(747_653).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64gtu(r: u32, ) -> Weight { - // Minimum execution time: 625 nanoseconds. - Weight::from_ref_time(946_744) - // Standard Error: 252 - .saturating_add(Weight::from_ref_time(743_532).saturating_mul(r.into())) + // Minimum execution time: 632 nanoseconds. + Weight::from_ref_time(868_901) + // Standard Error: 276 + .saturating_add(Weight::from_ref_time(744_778).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64les(r: u32, ) -> Weight { - // Minimum execution time: 652 nanoseconds. - Weight::from_ref_time(918_551) - // Standard Error: 313 - .saturating_add(Weight::from_ref_time(731_451).saturating_mul(r.into())) + // Minimum execution time: 629 nanoseconds. + Weight::from_ref_time(898_516) + // Standard Error: 288 + .saturating_add(Weight::from_ref_time(734_393).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64leu(r: u32, ) -> Weight { - // Minimum execution time: 651 nanoseconds. - Weight::from_ref_time(923_475) - // Standard Error: 341 - .saturating_add(Weight::from_ref_time(738_353).saturating_mul(r.into())) + // Minimum execution time: 603 nanoseconds. + Weight::from_ref_time(853_744) + // Standard Error: 204 + .saturating_add(Weight::from_ref_time(739_499).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64ges(r: u32, ) -> Weight { - // Minimum execution time: 666 nanoseconds. - Weight::from_ref_time(1_136_987) - // Standard Error: 1_482 - .saturating_add(Weight::from_ref_time(727_254).saturating_mul(r.into())) + // Minimum execution time: 625 nanoseconds. + Weight::from_ref_time(885_174) + // Standard Error: 238 + .saturating_add(Weight::from_ref_time(734_010).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64geu(r: u32, ) -> Weight { - // Minimum execution time: 633 nanoseconds. - Weight::from_ref_time(934_201) - // Standard Error: 332 - .saturating_add(Weight::from_ref_time(731_804).saturating_mul(r.into())) + // Minimum execution time: 602 nanoseconds. + Weight::from_ref_time(915_329) + // Standard Error: 273 + .saturating_add(Weight::from_ref_time(738_209).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64add(r: u32, ) -> Weight { - // Minimum execution time: 673 nanoseconds. - Weight::from_ref_time(983_317) - // Standard Error: 492 - .saturating_add(Weight::from_ref_time(718_126).saturating_mul(r.into())) + // Minimum execution time: 585 nanoseconds. + Weight::from_ref_time(862_239) + // Standard Error: 294 + .saturating_add(Weight::from_ref_time(719_000).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64sub(r: u32, ) -> Weight { - // Minimum execution time: 647 nanoseconds. - Weight::from_ref_time(925_490) - // Standard Error: 1_799 - .saturating_add(Weight::from_ref_time(711_178).saturating_mul(r.into())) + // Minimum execution time: 633 nanoseconds. + Weight::from_ref_time(881_696) + // Standard Error: 195 + .saturating_add(Weight::from_ref_time(713_153).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64mul(r: u32, ) -> Weight { - // Minimum execution time: 652 nanoseconds. - Weight::from_ref_time(955_546) - // Standard Error: 283 - .saturating_add(Weight::from_ref_time(710_844).saturating_mul(r.into())) + // Minimum execution time: 629 nanoseconds. + Weight::from_ref_time(856_238) + // Standard Error: 269 + .saturating_add(Weight::from_ref_time(715_451).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64divs(r: u32, ) -> Weight { - // Minimum execution time: 653 nanoseconds. - Weight::from_ref_time(982_314) - // Standard Error: 267 - .saturating_add(Weight::from_ref_time(1_344_080).saturating_mul(r.into())) + // Minimum execution time: 633 nanoseconds. + Weight::from_ref_time(880_804) + // Standard Error: 725 + .saturating_add(Weight::from_ref_time(1_349_577).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64divu(r: u32, ) -> Weight { - // Minimum execution time: 637 nanoseconds. - Weight::from_ref_time(913_421) - // Standard Error: 737 - .saturating_add(Weight::from_ref_time(1_285_749).saturating_mul(r.into())) + // Minimum execution time: 639 nanoseconds. + Weight::from_ref_time(867_528) + // Standard Error: 1_940 + .saturating_add(Weight::from_ref_time(1_287_959).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64rems(r: u32, ) -> Weight { - // Minimum execution time: 653 nanoseconds. - Weight::from_ref_time(939_041) - // Standard Error: 354 - .saturating_add(Weight::from_ref_time(1_391_470).saturating_mul(r.into())) + // Minimum execution time: 595 nanoseconds. + Weight::from_ref_time(851_253) + // Standard Error: 503 + .saturating_add(Weight::from_ref_time(1_398_668).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64remu(r: u32, ) -> Weight { - // Minimum execution time: 656 nanoseconds. - Weight::from_ref_time(951_030) - // Standard Error: 342 - .saturating_add(Weight::from_ref_time(1_287_904).saturating_mul(r.into())) + // Minimum execution time: 604 nanoseconds. + Weight::from_ref_time(804_977) + // Standard Error: 812 + .saturating_add(Weight::from_ref_time(1_288_816).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64and(r: u32, ) -> Weight { - // Minimum execution time: 682 nanoseconds. - Weight::from_ref_time(940_975) - // Standard Error: 195 - .saturating_add(Weight::from_ref_time(717_132).saturating_mul(r.into())) + // Minimum execution time: 609 nanoseconds. + Weight::from_ref_time(890_945) + // Standard Error: 1_645 + .saturating_add(Weight::from_ref_time(719_770).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64or(r: u32, ) -> Weight { - // Minimum execution time: 704 nanoseconds. - Weight::from_ref_time(941_860) - // Standard Error: 200 - .saturating_add(Weight::from_ref_time(717_696).saturating_mul(r.into())) + // Minimum execution time: 628 nanoseconds. + Weight::from_ref_time(897_973) + // Standard Error: 1_641 + .saturating_add(Weight::from_ref_time(718_838).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64xor(r: u32, ) -> Weight { - // Minimum execution time: 648 nanoseconds. - Weight::from_ref_time(917_135) - // Standard Error: 237 - .saturating_add(Weight::from_ref_time(717_979).saturating_mul(r.into())) + // Minimum execution time: 634 nanoseconds. + Weight::from_ref_time(848_440) + // Standard Error: 249 + .saturating_add(Weight::from_ref_time(718_206).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64shl(r: u32, ) -> Weight { - // Minimum execution time: 653 nanoseconds. - Weight::from_ref_time(1_031_822) - // Standard Error: 937 - .saturating_add(Weight::from_ref_time(730_965).saturating_mul(r.into())) + // Minimum execution time: 625 nanoseconds. + Weight::from_ref_time(881_579) + // Standard Error: 273 + .saturating_add(Weight::from_ref_time(736_934).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64shrs(r: u32, ) -> Weight { - // Minimum execution time: 671 nanoseconds. - Weight::from_ref_time(935_833) - // Standard Error: 184 - .saturating_add(Weight::from_ref_time(732_227).saturating_mul(r.into())) + // Minimum execution time: 630 nanoseconds. + Weight::from_ref_time(897_813) + // Standard Error: 249 + .saturating_add(Weight::from_ref_time(734_657).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64shru(r: u32, ) -> Weight { - // Minimum execution time: 637 nanoseconds. - Weight::from_ref_time(962_491) - // Standard Error: 488 - .saturating_add(Weight::from_ref_time(733_218).saturating_mul(r.into())) + // Minimum execution time: 607 nanoseconds. + Weight::from_ref_time(871_660) + // Standard Error: 312 + .saturating_add(Weight::from_ref_time(735_377).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64rotl(r: u32, ) -> Weight { - // Minimum execution time: 643 nanoseconds. - Weight::from_ref_time(951_949) - // Standard Error: 321 - .saturating_add(Weight::from_ref_time(732_212).saturating_mul(r.into())) + // Minimum execution time: 610 nanoseconds. + Weight::from_ref_time(861_640) + // Standard Error: 293 + .saturating_add(Weight::from_ref_time(735_524).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64rotr(r: u32, ) -> Weight { - // Minimum execution time: 665 nanoseconds. - Weight::from_ref_time(951_447) - // Standard Error: 346 - .saturating_add(Weight::from_ref_time(732_549).saturating_mul(r.into())) + // Minimum execution time: 625 nanoseconds. + Weight::from_ref_time(880_203) + // Standard Error: 373 + .saturating_add(Weight::from_ref_time(737_354).saturating_mul(r.into())) } } From acbb91b0c6168eed052cf68467c5532c97403bba Mon Sep 17 00:00:00 2001 From: Sasha Gryaznov Date: Thu, 22 Dec 2022 19:22:57 +0200 Subject: [PATCH 070/127] [contracts] Remove stack height limiter wasm instrumentation (#12957) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove stack height limiter from uploaded wasm * fix benchmarks * ".git/.scripts/bench-bot.sh" pallet dev pallet_contracts Co-authored-by: command-bot <> Co-authored-by: Alexander Theißen --- frame/contracts/src/benchmarking/code.rs | 26 ++---------------------- frame/contracts/src/benchmarking/mod.rs | 7 ++----- frame/contracts/src/schedule.rs | 18 +--------------- frame/contracts/src/tests.rs | 15 ++++++-------- frame/contracts/src/wasm/prepare.rs | 15 +------------- 5 files changed, 12 insertions(+), 69 deletions(-) diff --git a/frame/contracts/src/benchmarking/code.rs b/frame/contracts/src/benchmarking/code.rs index 0b09726214..156b9e2f99 100644 --- a/frame/contracts/src/benchmarking/code.rs +++ b/frame/contracts/src/benchmarking/code.rs @@ -72,11 +72,6 @@ pub struct ModuleDefinition { pub aux_body: Option, /// The amount of I64 arguments the aux function should have. pub aux_arg_num: u32, - /// If set to true the stack height limiter is injected into the the module. This is - /// needed for instruction debugging because the cost of executing the stack height - /// instrumentation should be included in the costs for the individual instructions - /// that cause more metering code (only call). - pub inject_stack_metering: bool, /// Create a table containing function pointers. pub table: Option, /// Create a section named "dummy" of the specified size. This is useful in order to @@ -238,13 +233,7 @@ impl From for WasmModule { ))); } - let mut code = contract.build(); - - if def.inject_stack_metering { - code = inject_stack_metering::(code); - } - - let code = code.into_bytes().unwrap(); + let code = contract.build().into_bytes().unwrap(); let hash = T::Hashing::hash(&code); Self { code: code.into(), hash, memory: def.memory } } @@ -252,15 +241,12 @@ impl From for WasmModule { impl WasmModule { /// Uses the supplied wasm module and instruments it when requested. - pub fn instrumented(code: &[u8], inject_gas: bool, inject_stack: bool) -> Self { + pub fn instrumented(code: &[u8], inject_gas: bool) -> Self { let module = { let mut module = Module::from_bytes(code).unwrap(); if inject_gas { module = inject_gas_metering::(module); } - if inject_stack { - module = inject_stack_metering::(module); - } module }; let limits = *module @@ -530,11 +516,3 @@ fn inject_gas_metering(module: Module) -> Module { let backend = gas_metering::host_function::Injector::new("seal0", "gas"); gas_metering::inject(module, backend, &gas_rules).unwrap() } - -fn inject_stack_metering(module: Module) -> Module { - if let Some(height) = T::Schedule::get().limits.stack_height { - wasm_instrument::inject_stack_limiter(module, height).unwrap() - } else { - module - } -} diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs index da9b53495d..d46da8469e 100644 --- a/frame/contracts/src/benchmarking/mod.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -2384,7 +2384,6 @@ benchmarks! { call_body: Some(body::repeated(r * INSTR_BENCHMARK_BATCH_SIZE, &[ Instruction::Call(2), // call aux ])), - inject_stack_metering: true, .. Default::default() })); }: { @@ -2408,7 +2407,6 @@ benchmarks! { RandomI32(0, num_elements as i32), Regular(Instruction::CallIndirect(0, 0)), // we only have one sig: 0 ])), - inject_stack_metering: true, table: Some(TableSegment { num_elements, function_index: 2, // aux @@ -2441,7 +2439,6 @@ benchmarks! { RandomI32(0, num_elements as i32), Regular(Instruction::CallIndirect(p.min(1), 0)), // aux signature: 1 or 0 ])), - inject_stack_metering: true, table: Some(TableSegment { num_elements, function_index: 2, // aux @@ -2968,7 +2965,7 @@ benchmarks! { new.encode() }; let instance = Contract::::new( - WasmModule::instrumented(code, gas_metering, true), data, + WasmModule::instrumented(code, gas_metering), data, )?; let data = { let transfer: ([u8; 4], AccountIdOf, BalanceOf) = ( @@ -3015,7 +3012,7 @@ benchmarks! { new.encode() }; let instance = Contract::::with_caller( - caller, WasmModule::instrumented(code, gas_metering, true), data, + caller, WasmModule::instrumented(code, gas_metering), data, )?; balance[0] = 1; let data = { diff --git a/frame/contracts/src/schedule.rs b/frame/contracts/src/schedule.rs index e7a6b16e23..912e58b048 100644 --- a/frame/contracts/src/schedule.rs +++ b/frame/contracts/src/schedule.rs @@ -99,23 +99,9 @@ pub struct Limits { /// The maximum number of topics supported by an event. pub event_topics: u32, - /// Maximum allowed stack height in number of elements. - /// - /// See to find out - /// how the stack frame cost is calculated. Each element can be of one of the - /// wasm value types. This means the maximum size per element is 64bit. - /// - /// # Note - /// - /// It is safe to disable (pass `None`) the `stack_height` when the execution engine - /// is part of the runtime and hence there can be no indeterminism between different - /// client resident execution engines. - pub stack_height: Option, - /// Maximum number of globals a module is allowed to declare. /// - /// Globals are not limited through the `stack_height` as locals are. Neither does - /// the linear memory limit `memory_pages` applies to them. + /// Globals are not limited through the linear memory limit `memory_pages`. pub globals: u32, /// Maximum number of locals a function can have. @@ -532,8 +518,6 @@ impl Default for Limits { fn default() -> Self { Self { event_topics: 4, - // No stack limit required because we use a runtime resident execution engine. - stack_height: None, globals: 256, locals: 1024, parameters: 128, diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index 85fe22ab9a..7a0736d1ed 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -339,9 +339,6 @@ impl pallet_utility::Config for Test { parameter_types! { pub MySchedule: Schedule = { let mut schedule = >::default(); - // We want stack height to be always enabled for tests so that this - // instrumentation path is always tested implicitly. - schedule.limits.stack_height = Some(512); schedule.instruction_weights.fallback = 1; schedule }; @@ -2964,7 +2961,7 @@ fn upload_code_works() { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Reserved { who: ALICE, - amount: 241, + amount: 173, }), topics: vec![], }, @@ -3054,7 +3051,7 @@ fn remove_code_works() { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Reserved { who: ALICE, - amount: 241, + amount: 173, }), topics: vec![], }, @@ -3067,7 +3064,7 @@ fn remove_code_works() { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Unreserved { who: ALICE, - amount: 241, + amount: 173, }), topics: vec![], }, @@ -3110,7 +3107,7 @@ fn remove_code_wrong_origin() { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Reserved { who: ALICE, - amount: 241, + amount: 173, }), topics: vec![], }, @@ -3246,7 +3243,7 @@ fn instantiate_with_zero_balance_works() { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Reserved { who: ALICE, - amount: 241, + amount: 173, }), topics: vec![], }, @@ -3351,7 +3348,7 @@ fn instantiate_with_below_existential_deposit_works() { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Reserved { who: ALICE, - amount: 241, + amount: 173, }), topics: vec![], }, diff --git a/frame/contracts/src/wasm/prepare.rs b/frame/contracts/src/wasm/prepare.rs index 9bb58bb02b..dfe25bc3af 100644 --- a/frame/contracts/src/wasm/prepare.rs +++ b/frame/contracts/src/wasm/prepare.rs @@ -216,16 +216,6 @@ impl<'a, T: Config> ContractModule<'a, T> { Ok(ContractModule { module: contract_module, schedule: self.schedule }) } - fn inject_stack_height_metering(self) -> Result { - if let Some(limit) = self.schedule.limits.stack_height { - let contract_module = wasm_instrument::inject_stack_limiter(self.module, limit) - .map_err(|_| "stack height instrumentation failed")?; - Ok(ContractModule { module: contract_module, schedule: self.schedule }) - } else { - Ok(ContractModule { module: self.module, schedule: self.schedule }) - } - } - /// Check that the module has required exported functions. For now /// these are just entrypoints: /// @@ -447,10 +437,7 @@ where let memory_limits = get_memory_limits(contract_module.scan_imports(&disallowed_imports)?, schedule)?; - let code = contract_module - .inject_gas_metering(determinism)? - .inject_stack_height_metering()? - .into_wasm_code()?; + let code = contract_module.inject_gas_metering(determinism)?.into_wasm_code()?; Ok((code, memory_limits)) })() From 91d91d167d37630de663ddc78e2dab38aabe573d Mon Sep 17 00:00:00 2001 From: Muharem Ismailov Date: Thu, 22 Dec 2022 21:12:01 +0100 Subject: [PATCH 071/127] Alliance: remove unused func from weight trait (#13001) * Alliance: remove unused weight func * ".git/.scripts/bench-bot.sh" pallet dev pallet_alliance Co-authored-by: command-bot <> --- frame/alliance/src/weights.rs | 569 +++++++++++++++++----------------- 1 file changed, 281 insertions(+), 288 deletions(-) diff --git a/frame/alliance/src/weights.rs b/frame/alliance/src/weights.rs index 58d28b28f2..1cfc83dfc4 100644 --- a/frame/alliance/src/weights.rs +++ b/frame/alliance/src/weights.rs @@ -1,22 +1,42 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + //! Autogenerated weights for pallet_alliance //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `cob`, CPU: `` -//! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 +//! DATE: 2022-12-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// ./target/release/substrate +// /home/benchbot/cargo_target_dir/production/substrate // benchmark // pallet -// --chain=dev // --steps=50 // --repeat=20 -// --pallet=pallet-alliance // --extrinsic=* +// --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 -// --output=./frame/alliance/src/._weights.rs +// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/substrate/.git/.artifacts/bench.json +// --pallet=pallet_alliance +// --chain=dev +// --header=./HEADER-APACHE2 +// --output=./frame/alliance/src/weights.rs // --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] @@ -30,7 +50,6 @@ use sp_std::marker::PhantomData; pub trait WeightInfo { fn propose_proposed(b: u32, m: u32, p: u32, ) -> Weight; fn vote(m: u32, ) -> Weight; - fn veto(p: u32, ) -> Weight; fn close_early_disapproved(m: u32, p: u32, ) -> Weight; fn close_early_approved(b: u32, m: u32, p: u32, ) -> Weight; fn close_disapproved(m: u32, p: u32, ) -> Weight; @@ -62,41 +81,26 @@ impl WeightInfo for SubstrateWeight { /// The range of component `b` is `[1, 1024]`. /// The range of component `m` is `[2, 100]`. /// The range of component `p` is `[1, 100]`. - fn propose_proposed(b: u32, m: u32, p: u32, ) -> Weight { - // Minimum execution time: 23_000 nanoseconds. - Weight::from_ref_time(24_357_172 as u64) - // Standard Error: 428 - .saturating_add(Weight::from_ref_time(233 as u64).saturating_mul(b as u64)) - // Standard Error: 4_474 - .saturating_add(Weight::from_ref_time(48_024 as u64).saturating_mul(m as u64)) - // Standard Error: 4_418 - .saturating_add(Weight::from_ref_time(97_604 as u64).saturating_mul(p as u64)) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + fn propose_proposed(_b: u32, m: u32, p: u32, ) -> Weight { + // Minimum execution time: 45_945 nanoseconds. + Weight::from_ref_time(44_195_340) + // Standard Error: 2_212 + .saturating_add(Weight::from_ref_time(73_715).saturating_mul(m.into())) + // Standard Error: 2_184 + .saturating_add(Weight::from_ref_time(172_373).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(4)) } // Storage: Alliance Members (r:1 w:0) // Storage: AllianceMotion Voting (r:1 w:1) /// The range of component `m` is `[5, 100]`. fn vote(m: u32, ) -> Weight { - // Minimum execution time: 24_000 nanoseconds. - Weight::from_ref_time(26_617_201 as u64) - // Standard Error: 4_280 - .saturating_add(Weight::from_ref_time(43_152 as u64).saturating_mul(m as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Alliance Members (r:1 w:0) - // Storage: AllianceMotion ProposalOf (r:1 w:1) - // Storage: AllianceMotion Proposals (r:1 w:1) - // Storage: AllianceMotion Voting (r:0 w:1) - /// The range of component `p` is `[1, 100]`. - fn veto(p: u32, ) -> Weight { - // Minimum execution time: 23_000 nanoseconds. - Weight::from_ref_time(26_045_752 as u64) - // Standard Error: 2_154 - .saturating_add(Weight::from_ref_time(61_220 as u64).saturating_mul(p as u64)) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 41_578 nanoseconds. + Weight::from_ref_time(44_299_385) + // Standard Error: 3_727 + .saturating_add(Weight::from_ref_time(138_681).saturating_mul(m.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Alliance Members (r:1 w:0) // Storage: AllianceMotion Voting (r:1 w:1) @@ -106,14 +110,14 @@ impl WeightInfo for SubstrateWeight { /// The range of component `m` is `[4, 100]`. /// The range of component `p` is `[1, 100]`. fn close_early_disapproved(m: u32, p: u32, ) -> Weight { - // Minimum execution time: 30_000 nanoseconds. - Weight::from_ref_time(25_697_866 as u64) - // Standard Error: 3_827 - .saturating_add(Weight::from_ref_time(48_360 as u64).saturating_mul(m as u64)) - // Standard Error: 3_731 - .saturating_add(Weight::from_ref_time(116_922 as u64).saturating_mul(p as u64)) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 53_033 nanoseconds. + Weight::from_ref_time(44_608_804) + // Standard Error: 2_306 + .saturating_add(Weight::from_ref_time(102_842).saturating_mul(m.into())) + // Standard Error: 2_248 + .saturating_add(Weight::from_ref_time(172_018).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(3)) } // Storage: Alliance Members (r:1 w:0) // Storage: AllianceMotion Voting (r:1 w:1) @@ -124,16 +128,16 @@ impl WeightInfo for SubstrateWeight { /// The range of component `m` is `[4, 100]`. /// The range of component `p` is `[1, 100]`. fn close_early_approved(b: u32, m: u32, p: u32, ) -> Weight { - // Minimum execution time: 34_000 nanoseconds. - Weight::from_ref_time(30_725_464 as u64) - // Standard Error: 370 - .saturating_add(Weight::from_ref_time(2_367 as u64).saturating_mul(b as u64)) - // Standard Error: 3_920 - .saturating_add(Weight::from_ref_time(40_710 as u64).saturating_mul(m as u64)) - // Standard Error: 3_822 - .saturating_add(Weight::from_ref_time(111_866 as u64).saturating_mul(p as u64)) - .saturating_add(T::DbWeight::get().reads(5 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 61_157 nanoseconds. + Weight::from_ref_time(53_647_510) + // Standard Error: 235 + .saturating_add(Weight::from_ref_time(88).saturating_mul(b.into())) + // Standard Error: 2_486 + .saturating_add(Weight::from_ref_time(103_912).saturating_mul(m.into())) + // Standard Error: 2_424 + .saturating_add(Weight::from_ref_time(197_471).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(3)) } // Storage: Alliance Members (r:1 w:0) // Storage: AllianceMotion Voting (r:1 w:1) @@ -144,14 +148,14 @@ impl WeightInfo for SubstrateWeight { /// The range of component `m` is `[2, 100]`. /// The range of component `p` is `[1, 100]`. fn close_disapproved(m: u32, p: u32, ) -> Weight { - // Minimum execution time: 31_000 nanoseconds. - Weight::from_ref_time(29_444_599 as u64) - // Standard Error: 4_043 - .saturating_add(Weight::from_ref_time(48_928 as u64).saturating_mul(m as u64)) - // Standard Error: 3_994 - .saturating_add(Weight::from_ref_time(76_527 as u64).saturating_mul(p as u64)) - .saturating_add(T::DbWeight::get().reads(5 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 53_830 nanoseconds. + Weight::from_ref_time(43_926_698) + // Standard Error: 2_495 + .saturating_add(Weight::from_ref_time(112_814).saturating_mul(m.into())) + // Standard Error: 2_465 + .saturating_add(Weight::from_ref_time(186_422).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(3)) } // Storage: Alliance Members (r:1 w:0) // Storage: AllianceMotion Voting (r:1 w:1) @@ -163,28 +167,28 @@ impl WeightInfo for SubstrateWeight { /// The range of component `m` is `[5, 100]`. /// The range of component `p` is `[1, 100]`. fn close_approved(_b: u32, m: u32, p: u32, ) -> Weight { - // Minimum execution time: 29_000 nanoseconds. - Weight::from_ref_time(32_315_075 as u64) - // Standard Error: 4_502 - .saturating_add(Weight::from_ref_time(43_205 as u64).saturating_mul(m as u64)) - // Standard Error: 4_340 - .saturating_add(Weight::from_ref_time(101_872 as u64).saturating_mul(p as u64)) - .saturating_add(T::DbWeight::get().reads(5 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 53_665 nanoseconds. + Weight::from_ref_time(44_914_913) + // Standard Error: 2_244 + .saturating_add(Weight::from_ref_time(105_848).saturating_mul(m.into())) + // Standard Error: 2_164 + .saturating_add(Weight::from_ref_time(190_830).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(3)) } // Storage: Alliance Members (r:2 w:2) // Storage: AllianceMotion Members (r:1 w:1) /// The range of component `m` is `[1, 100]`. /// The range of component `z` is `[0, 100]`. fn init_members(m: u32, z: u32, ) -> Weight { - // Minimum execution time: 22_000 nanoseconds. - Weight::from_ref_time(13_523_882 as u64) - // Standard Error: 1_823 - .saturating_add(Weight::from_ref_time(91_625 as u64).saturating_mul(m as u64)) - // Standard Error: 1_802 - .saturating_add(Weight::from_ref_time(98_184 as u64).saturating_mul(z as u64)) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 46_486 nanoseconds. + Weight::from_ref_time(34_596_922) + // Standard Error: 1_807 + .saturating_add(Weight::from_ref_time(143_225).saturating_mul(m.into())) + // Standard Error: 1_786 + .saturating_add(Weight::from_ref_time(125_729).saturating_mul(z.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) } // Storage: Alliance Members (r:2 w:2) // Storage: AllianceMotion Proposals (r:1 w:0) @@ -196,68 +200,68 @@ impl WeightInfo for SubstrateWeight { /// The range of component `y` is `[0, 100]`. /// The range of component `z` is `[0, 50]`. fn disband(x: u32, y: u32, z: u32, ) -> Weight { - // Minimum execution time: 145_000 nanoseconds. - Weight::from_ref_time(147_000_000 as u64) - // Standard Error: 13_290 - .saturating_add(Weight::from_ref_time(304_371 as u64).saturating_mul(x as u64)) - // Standard Error: 13_226 - .saturating_add(Weight::from_ref_time(330_798 as u64).saturating_mul(y as u64)) - // Standard Error: 26_428 - .saturating_add(Weight::from_ref_time(7_207_748 as u64).saturating_mul(z as u64)) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(x as u64))) - .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(y as u64))) - .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(z as u64))) - .saturating_add(T::DbWeight::get().writes(4 as u64)) - .saturating_add(T::DbWeight::get().writes((2 as u64).saturating_mul(z as u64))) + // Minimum execution time: 251_640 nanoseconds. + Weight::from_ref_time(252_446_000) + // Standard Error: 20_419 + .saturating_add(Weight::from_ref_time(486_667).saturating_mul(x.into())) + // Standard Error: 20_320 + .saturating_add(Weight::from_ref_time(498_890).saturating_mul(y.into())) + // Standard Error: 40_604 + .saturating_add(Weight::from_ref_time(9_411_242).saturating_mul(z.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(x.into()))) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(y.into()))) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(z.into()))) + .saturating_add(T::DbWeight::get().writes(4)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(z.into()))) } // Storage: Alliance Rule (r:0 w:1) fn set_rule() -> Weight { - // Minimum execution time: 11_000 nanoseconds. - Weight::from_ref_time(12_000_000 as u64) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 18_867 nanoseconds. + Weight::from_ref_time(19_155_000) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Alliance Announcements (r:1 w:1) fn announce() -> Weight { - // Minimum execution time: 13_000 nanoseconds. - Weight::from_ref_time(14_000_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 21_942 nanoseconds. + Weight::from_ref_time(22_319_000) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Alliance Announcements (r:1 w:1) fn remove_announcement() -> Weight { - // Minimum execution time: 14_000 nanoseconds. - Weight::from_ref_time(14_000_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 23_419 nanoseconds. + Weight::from_ref_time(23_854_000) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Alliance Members (r:3 w:1) // Storage: Alliance UnscrupulousAccounts (r:1 w:0) // Storage: System Account (r:1 w:1) // Storage: Alliance DepositOf (r:0 w:1) fn join_alliance() -> Weight { - // Minimum execution time: 39_000 nanoseconds. - Weight::from_ref_time(41_000_000 as u64) - .saturating_add(T::DbWeight::get().reads(5 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 53_016 nanoseconds. + Weight::from_ref_time(53_768_000) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(3)) } // Storage: Alliance Members (r:3 w:1) // Storage: Alliance UnscrupulousAccounts (r:1 w:0) fn nominate_ally() -> Weight { - // Minimum execution time: 29_000 nanoseconds. - Weight::from_ref_time(30_000_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 40_019 nanoseconds. + Weight::from_ref_time(41_087_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Alliance Members (r:2 w:2) // Storage: AllianceMotion Proposals (r:1 w:0) // Storage: AllianceMotion Members (r:0 w:1) // Storage: AllianceMotion Prime (r:0 w:1) fn elevate_ally() -> Weight { - // Minimum execution time: 23_000 nanoseconds. - Weight::from_ref_time(24_000_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Minimum execution time: 35_973 nanoseconds. + Weight::from_ref_time(36_365_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(4)) } // Storage: Alliance Members (r:4 w:2) // Storage: AllianceMotion Proposals (r:1 w:0) @@ -265,20 +269,20 @@ impl WeightInfo for SubstrateWeight { // Storage: AllianceMotion Prime (r:0 w:1) // Storage: Alliance RetiringMembers (r:0 w:1) fn give_retirement_notice() -> Weight { - // Minimum execution time: 31_000 nanoseconds. - Weight::from_ref_time(32_000_000 as u64) - .saturating_add(T::DbWeight::get().reads(5 as u64)) - .saturating_add(T::DbWeight::get().writes(5 as u64)) + // Minimum execution time: 44_949 nanoseconds. + Weight::from_ref_time(46_284_000) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(5)) } // Storage: Alliance RetiringMembers (r:1 w:1) // Storage: Alliance Members (r:1 w:1) // Storage: Alliance DepositOf (r:1 w:1) // Storage: System Account (r:1 w:1) fn retire() -> Weight { - // Minimum execution time: 29_000 nanoseconds. - Weight::from_ref_time(30_000_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Minimum execution time: 45_989 nanoseconds. + Weight::from_ref_time(46_586_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(4)) } // Storage: Alliance Members (r:3 w:1) // Storage: AllianceMotion Proposals (r:1 w:0) @@ -287,46 +291,48 @@ impl WeightInfo for SubstrateWeight { // Storage: AllianceMotion Members (r:0 w:1) // Storage: AllianceMotion Prime (r:0 w:1) fn kick_member() -> Weight { - // Minimum execution time: 45_000 nanoseconds. - Weight::from_ref_time(47_000_000 as u64) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(5 as u64)) + // Minimum execution time: 67_333 nanoseconds. + Weight::from_ref_time(68_302_000) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(5)) } // Storage: Alliance UnscrupulousAccounts (r:1 w:1) // Storage: Alliance UnscrupulousWebsites (r:1 w:1) /// The range of component `n` is `[0, 100]`. /// The range of component `l` is `[0, 255]`. fn add_unscrupulous_items(n: u32, l: u32, ) -> Weight { - // Minimum execution time: 9_000 nanoseconds. - Weight::from_ref_time(9_512_816 as u64) - // Standard Error: 2_976 - .saturating_add(Weight::from_ref_time(560_175 as u64).saturating_mul(n as u64)) - // Standard Error: 1_169 - .saturating_add(Weight::from_ref_time(24_530 as u64).saturating_mul(l as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Minimum execution time: 16_911 nanoseconds. + Weight::from_ref_time(17_204_000) + // Standard Error: 2_688 + .saturating_add(Weight::from_ref_time(1_324_527).saturating_mul(n.into())) + // Standard Error: 1_052 + .saturating_add(Weight::from_ref_time(73_828).saturating_mul(l.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) } // Storage: Alliance UnscrupulousAccounts (r:1 w:1) // Storage: Alliance UnscrupulousWebsites (r:1 w:1) /// The range of component `n` is `[0, 100]`. /// The range of component `l` is `[0, 255]`. - fn remove_unscrupulous_items(n: u32, _l: u32, ) -> Weight { - // Minimum execution time: 10_000 nanoseconds. - Weight::from_ref_time(10_000_000 as u64) - // Standard Error: 94_049 - .saturating_add(Weight::from_ref_time(10_887_604 as u64).saturating_mul(n as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + fn remove_unscrupulous_items(n: u32, l: u32, ) -> Weight { + // Minimum execution time: 17_117 nanoseconds. + Weight::from_ref_time(17_291_000) + // Standard Error: 166_132 + .saturating_add(Weight::from_ref_time(13_173_211).saturating_mul(n.into())) + // Standard Error: 65_064 + .saturating_add(Weight::from_ref_time(478_626).saturating_mul(l.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) } // Storage: Alliance Members (r:3 w:2) // Storage: AllianceMotion Proposals (r:1 w:0) // Storage: AllianceMotion Members (r:0 w:1) // Storage: AllianceMotion Prime (r:0 w:1) fn abdicate_fellow_status() -> Weight { - // Minimum execution time: 29_000 nanoseconds. - Weight::from_ref_time(30_000_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Minimum execution time: 43_347 nanoseconds. + Weight::from_ref_time(43_986_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(4)) } } @@ -340,41 +346,26 @@ impl WeightInfo for () { /// The range of component `b` is `[1, 1024]`. /// The range of component `m` is `[2, 100]`. /// The range of component `p` is `[1, 100]`. - fn propose_proposed(b: u32, m: u32, p: u32, ) -> Weight { - // Minimum execution time: 23_000 nanoseconds. - Weight::from_ref_time(24_357_172 as u64) - // Standard Error: 428 - .saturating_add(Weight::from_ref_time(233 as u64).saturating_mul(b as u64)) - // Standard Error: 4_474 - .saturating_add(Weight::from_ref_time(48_024 as u64).saturating_mul(m as u64)) - // Standard Error: 4_418 - .saturating_add(Weight::from_ref_time(97_604 as u64).saturating_mul(p as u64)) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + fn propose_proposed(_b: u32, m: u32, p: u32, ) -> Weight { + // Minimum execution time: 45_945 nanoseconds. + Weight::from_ref_time(44_195_340) + // Standard Error: 2_212 + .saturating_add(Weight::from_ref_time(73_715).saturating_mul(m.into())) + // Standard Error: 2_184 + .saturating_add(Weight::from_ref_time(172_373).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().writes(4)) } // Storage: Alliance Members (r:1 w:0) // Storage: AllianceMotion Voting (r:1 w:1) /// The range of component `m` is `[5, 100]`. fn vote(m: u32, ) -> Weight { - // Minimum execution time: 24_000 nanoseconds. - Weight::from_ref_time(26_617_201 as u64) - // Standard Error: 4_280 - .saturating_add(Weight::from_ref_time(43_152 as u64).saturating_mul(m as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Alliance Members (r:1 w:0) - // Storage: AllianceMotion ProposalOf (r:1 w:1) - // Storage: AllianceMotion Proposals (r:1 w:1) - // Storage: AllianceMotion Voting (r:0 w:1) - /// The range of component `p` is `[1, 100]`. - fn veto(p: u32, ) -> Weight { - // Minimum execution time: 23_000 nanoseconds. - Weight::from_ref_time(26_045_752 as u64) - // Standard Error: 2_154 - .saturating_add(Weight::from_ref_time(61_220 as u64).saturating_mul(p as u64)) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 41_578 nanoseconds. + Weight::from_ref_time(44_299_385) + // Standard Error: 3_727 + .saturating_add(Weight::from_ref_time(138_681).saturating_mul(m.into())) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Alliance Members (r:1 w:0) // Storage: AllianceMotion Voting (r:1 w:1) @@ -384,14 +375,14 @@ impl WeightInfo for () { /// The range of component `m` is `[4, 100]`. /// The range of component `p` is `[1, 100]`. fn close_early_disapproved(m: u32, p: u32, ) -> Weight { - // Minimum execution time: 30_000 nanoseconds. - Weight::from_ref_time(25_697_866 as u64) - // Standard Error: 3_827 - .saturating_add(Weight::from_ref_time(48_360 as u64).saturating_mul(m as u64)) - // Standard Error: 3_731 - .saturating_add(Weight::from_ref_time(116_922 as u64).saturating_mul(p as u64)) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 53_033 nanoseconds. + Weight::from_ref_time(44_608_804) + // Standard Error: 2_306 + .saturating_add(Weight::from_ref_time(102_842).saturating_mul(m.into())) + // Standard Error: 2_248 + .saturating_add(Weight::from_ref_time(172_018).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().writes(3)) } // Storage: Alliance Members (r:1 w:0) // Storage: AllianceMotion Voting (r:1 w:1) @@ -402,16 +393,16 @@ impl WeightInfo for () { /// The range of component `m` is `[4, 100]`. /// The range of component `p` is `[1, 100]`. fn close_early_approved(b: u32, m: u32, p: u32, ) -> Weight { - // Minimum execution time: 34_000 nanoseconds. - Weight::from_ref_time(30_725_464 as u64) - // Standard Error: 370 - .saturating_add(Weight::from_ref_time(2_367 as u64).saturating_mul(b as u64)) - // Standard Error: 3_920 - .saturating_add(Weight::from_ref_time(40_710 as u64).saturating_mul(m as u64)) - // Standard Error: 3_822 - .saturating_add(Weight::from_ref_time(111_866 as u64).saturating_mul(p as u64)) - .saturating_add(RocksDbWeight::get().reads(5 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 61_157 nanoseconds. + Weight::from_ref_time(53_647_510) + // Standard Error: 235 + .saturating_add(Weight::from_ref_time(88).saturating_mul(b.into())) + // Standard Error: 2_486 + .saturating_add(Weight::from_ref_time(103_912).saturating_mul(m.into())) + // Standard Error: 2_424 + .saturating_add(Weight::from_ref_time(197_471).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(5)) + .saturating_add(RocksDbWeight::get().writes(3)) } // Storage: Alliance Members (r:1 w:0) // Storage: AllianceMotion Voting (r:1 w:1) @@ -422,14 +413,14 @@ impl WeightInfo for () { /// The range of component `m` is `[2, 100]`. /// The range of component `p` is `[1, 100]`. fn close_disapproved(m: u32, p: u32, ) -> Weight { - // Minimum execution time: 31_000 nanoseconds. - Weight::from_ref_time(29_444_599 as u64) - // Standard Error: 4_043 - .saturating_add(Weight::from_ref_time(48_928 as u64).saturating_mul(m as u64)) - // Standard Error: 3_994 - .saturating_add(Weight::from_ref_time(76_527 as u64).saturating_mul(p as u64)) - .saturating_add(RocksDbWeight::get().reads(5 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 53_830 nanoseconds. + Weight::from_ref_time(43_926_698) + // Standard Error: 2_495 + .saturating_add(Weight::from_ref_time(112_814).saturating_mul(m.into())) + // Standard Error: 2_465 + .saturating_add(Weight::from_ref_time(186_422).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(5)) + .saturating_add(RocksDbWeight::get().writes(3)) } // Storage: Alliance Members (r:1 w:0) // Storage: AllianceMotion Voting (r:1 w:1) @@ -441,28 +432,28 @@ impl WeightInfo for () { /// The range of component `m` is `[5, 100]`. /// The range of component `p` is `[1, 100]`. fn close_approved(_b: u32, m: u32, p: u32, ) -> Weight { - // Minimum execution time: 29_000 nanoseconds. - Weight::from_ref_time(32_315_075 as u64) - // Standard Error: 4_502 - .saturating_add(Weight::from_ref_time(43_205 as u64).saturating_mul(m as u64)) - // Standard Error: 4_340 - .saturating_add(Weight::from_ref_time(101_872 as u64).saturating_mul(p as u64)) - .saturating_add(RocksDbWeight::get().reads(5 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 53_665 nanoseconds. + Weight::from_ref_time(44_914_913) + // Standard Error: 2_244 + .saturating_add(Weight::from_ref_time(105_848).saturating_mul(m.into())) + // Standard Error: 2_164 + .saturating_add(Weight::from_ref_time(190_830).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(5)) + .saturating_add(RocksDbWeight::get().writes(3)) } // Storage: Alliance Members (r:2 w:2) // Storage: AllianceMotion Members (r:1 w:1) /// The range of component `m` is `[1, 100]`. /// The range of component `z` is `[0, 100]`. fn init_members(m: u32, z: u32, ) -> Weight { - // Minimum execution time: 22_000 nanoseconds. - Weight::from_ref_time(13_523_882 as u64) - // Standard Error: 1_823 - .saturating_add(Weight::from_ref_time(91_625 as u64).saturating_mul(m as u64)) - // Standard Error: 1_802 - .saturating_add(Weight::from_ref_time(98_184 as u64).saturating_mul(z as u64)) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 46_486 nanoseconds. + Weight::from_ref_time(34_596_922) + // Standard Error: 1_807 + .saturating_add(Weight::from_ref_time(143_225).saturating_mul(m.into())) + // Standard Error: 1_786 + .saturating_add(Weight::from_ref_time(125_729).saturating_mul(z.into())) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(3)) } // Storage: Alliance Members (r:2 w:2) // Storage: AllianceMotion Proposals (r:1 w:0) @@ -474,68 +465,68 @@ impl WeightInfo for () { /// The range of component `y` is `[0, 100]`. /// The range of component `z` is `[0, 50]`. fn disband(x: u32, y: u32, z: u32, ) -> Weight { - // Minimum execution time: 145_000 nanoseconds. - Weight::from_ref_time(147_000_000 as u64) - // Standard Error: 13_290 - .saturating_add(Weight::from_ref_time(304_371 as u64).saturating_mul(x as u64)) - // Standard Error: 13_226 - .saturating_add(Weight::from_ref_time(330_798 as u64).saturating_mul(y as u64)) - // Standard Error: 26_428 - .saturating_add(Weight::from_ref_time(7_207_748 as u64).saturating_mul(z as u64)) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(x as u64))) - .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(y as u64))) - .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(z as u64))) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) - .saturating_add(RocksDbWeight::get().writes((2 as u64).saturating_mul(z as u64))) + // Minimum execution time: 251_640 nanoseconds. + Weight::from_ref_time(252_446_000) + // Standard Error: 20_419 + .saturating_add(Weight::from_ref_time(486_667).saturating_mul(x.into())) + // Standard Error: 20_320 + .saturating_add(Weight::from_ref_time(498_890).saturating_mul(y.into())) + // Standard Error: 40_604 + .saturating_add(Weight::from_ref_time(9_411_242).saturating_mul(z.into())) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(x.into()))) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(y.into()))) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(z.into()))) + .saturating_add(RocksDbWeight::get().writes(4)) + .saturating_add(RocksDbWeight::get().writes((2_u64).saturating_mul(z.into()))) } // Storage: Alliance Rule (r:0 w:1) fn set_rule() -> Weight { - // Minimum execution time: 11_000 nanoseconds. - Weight::from_ref_time(12_000_000 as u64) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 18_867 nanoseconds. + Weight::from_ref_time(19_155_000) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Alliance Announcements (r:1 w:1) fn announce() -> Weight { - // Minimum execution time: 13_000 nanoseconds. - Weight::from_ref_time(14_000_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 21_942 nanoseconds. + Weight::from_ref_time(22_319_000) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Alliance Announcements (r:1 w:1) fn remove_announcement() -> Weight { - // Minimum execution time: 14_000 nanoseconds. - Weight::from_ref_time(14_000_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 23_419 nanoseconds. + Weight::from_ref_time(23_854_000) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Alliance Members (r:3 w:1) // Storage: Alliance UnscrupulousAccounts (r:1 w:0) // Storage: System Account (r:1 w:1) // Storage: Alliance DepositOf (r:0 w:1) fn join_alliance() -> Weight { - // Minimum execution time: 39_000 nanoseconds. - Weight::from_ref_time(41_000_000 as u64) - .saturating_add(RocksDbWeight::get().reads(5 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 53_016 nanoseconds. + Weight::from_ref_time(53_768_000) + .saturating_add(RocksDbWeight::get().reads(5)) + .saturating_add(RocksDbWeight::get().writes(3)) } // Storage: Alliance Members (r:3 w:1) // Storage: Alliance UnscrupulousAccounts (r:1 w:0) fn nominate_ally() -> Weight { - // Minimum execution time: 29_000 nanoseconds. - Weight::from_ref_time(30_000_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 40_019 nanoseconds. + Weight::from_ref_time(41_087_000) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Alliance Members (r:2 w:2) // Storage: AllianceMotion Proposals (r:1 w:0) // Storage: AllianceMotion Members (r:0 w:1) // Storage: AllianceMotion Prime (r:0 w:1) fn elevate_ally() -> Weight { - // Minimum execution time: 23_000 nanoseconds. - Weight::from_ref_time(24_000_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // Minimum execution time: 35_973 nanoseconds. + Weight::from_ref_time(36_365_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(4)) } // Storage: Alliance Members (r:4 w:2) // Storage: AllianceMotion Proposals (r:1 w:0) @@ -543,20 +534,20 @@ impl WeightInfo for () { // Storage: AllianceMotion Prime (r:0 w:1) // Storage: Alliance RetiringMembers (r:0 w:1) fn give_retirement_notice() -> Weight { - // Minimum execution time: 31_000 nanoseconds. - Weight::from_ref_time(32_000_000 as u64) - .saturating_add(RocksDbWeight::get().reads(5 as u64)) - .saturating_add(RocksDbWeight::get().writes(5 as u64)) + // Minimum execution time: 44_949 nanoseconds. + Weight::from_ref_time(46_284_000) + .saturating_add(RocksDbWeight::get().reads(5)) + .saturating_add(RocksDbWeight::get().writes(5)) } // Storage: Alliance RetiringMembers (r:1 w:1) // Storage: Alliance Members (r:1 w:1) // Storage: Alliance DepositOf (r:1 w:1) // Storage: System Account (r:1 w:1) fn retire() -> Weight { - // Minimum execution time: 29_000 nanoseconds. - Weight::from_ref_time(30_000_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // Minimum execution time: 45_989 nanoseconds. + Weight::from_ref_time(46_586_000) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().writes(4)) } // Storage: Alliance Members (r:3 w:1) // Storage: AllianceMotion Proposals (r:1 w:0) @@ -565,45 +556,47 @@ impl WeightInfo for () { // Storage: AllianceMotion Members (r:0 w:1) // Storage: AllianceMotion Prime (r:0 w:1) fn kick_member() -> Weight { - // Minimum execution time: 45_000 nanoseconds. - Weight::from_ref_time(47_000_000 as u64) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(5 as u64)) + // Minimum execution time: 67_333 nanoseconds. + Weight::from_ref_time(68_302_000) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(5)) } // Storage: Alliance UnscrupulousAccounts (r:1 w:1) // Storage: Alliance UnscrupulousWebsites (r:1 w:1) /// The range of component `n` is `[0, 100]`. /// The range of component `l` is `[0, 255]`. fn add_unscrupulous_items(n: u32, l: u32, ) -> Weight { - // Minimum execution time: 9_000 nanoseconds. - Weight::from_ref_time(9_512_816 as u64) - // Standard Error: 2_976 - .saturating_add(Weight::from_ref_time(560_175 as u64).saturating_mul(n as u64)) - // Standard Error: 1_169 - .saturating_add(Weight::from_ref_time(24_530 as u64).saturating_mul(l as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Minimum execution time: 16_911 nanoseconds. + Weight::from_ref_time(17_204_000) + // Standard Error: 2_688 + .saturating_add(Weight::from_ref_time(1_324_527).saturating_mul(n.into())) + // Standard Error: 1_052 + .saturating_add(Weight::from_ref_time(73_828).saturating_mul(l.into())) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) } // Storage: Alliance UnscrupulousAccounts (r:1 w:1) // Storage: Alliance UnscrupulousWebsites (r:1 w:1) /// The range of component `n` is `[0, 100]`. /// The range of component `l` is `[0, 255]`. - fn remove_unscrupulous_items(n: u32, _l: u32, ) -> Weight { - // Minimum execution time: 10_000 nanoseconds. - Weight::from_ref_time(10_000_000 as u64) - // Standard Error: 94_049 - .saturating_add(Weight::from_ref_time(10_887_604 as u64).saturating_mul(n as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + fn remove_unscrupulous_items(n: u32, l: u32, ) -> Weight { + // Minimum execution time: 17_117 nanoseconds. + Weight::from_ref_time(17_291_000) + // Standard Error: 166_132 + .saturating_add(Weight::from_ref_time(13_173_211).saturating_mul(n.into())) + // Standard Error: 65_064 + .saturating_add(Weight::from_ref_time(478_626).saturating_mul(l.into())) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) } // Storage: Alliance Members (r:3 w:2) // Storage: AllianceMotion Proposals (r:1 w:0) // Storage: AllianceMotion Members (r:0 w:1) // Storage: AllianceMotion Prime (r:0 w:1) fn abdicate_fellow_status() -> Weight { - // Minimum execution time: 29_000 nanoseconds. - Weight::from_ref_time(30_000_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // Minimum execution time: 43_347 nanoseconds. + Weight::from_ref_time(43_986_000) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().writes(4)) } } From 9443052a2b7e0c05e8cdc2bb5c972a7a677ab9bc Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Thu, 22 Dec 2022 23:33:01 +0100 Subject: [PATCH 072/127] txpool: don't validate block transactions if the pool is empty (#12973) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * txpool: don't validate block transactions if the pool is empty Fix shall prevent from wasting the CPU during the major sync. Block transaction don't need to be re-validated when the txpool is empty. Fixes: #12903 * Apply suggestions from code review Co-authored-by: Bastian Köcher --- client/transaction-pool/src/graph/pool.rs | 25 +++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/client/transaction-pool/src/graph/pool.rs b/client/transaction-pool/src/graph/pool.rs index 96cbeb9b80..8e3570d1db 100644 --- a/client/transaction-pool/src/graph/pool.rs +++ b/client/transaction-pool/src/graph/pool.rs @@ -271,14 +271,23 @@ impl Pool { // if it's not found in the pool query the runtime at parent block // to get validity info and tags that the extrinsic provides. None => { - let validity = self - .validated_pool - .api() - .validate_transaction(parent, TransactionSource::InBlock, extrinsic.clone()) - .await; - - if let Ok(Ok(validity)) = validity { - future_tags.extend(validity.provides); + // Avoid validating block txs if the pool is empty + if !self.validated_pool.status().is_empty() { + let validity = self + .validated_pool + .api() + .validate_transaction( + parent, + TransactionSource::InBlock, + extrinsic.clone(), + ) + .await; + + if let Ok(Ok(validity)) = validity { + future_tags.extend(validity.provides); + } + } else { + log::trace!(target: "txpool", "txpool is empty, skipping validation for block {at:?}"); } }, } From 6c65424579c04a9d366cb08579bd4f66ddd4f1ad Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Fri, 23 Dec 2022 08:04:47 +0100 Subject: [PATCH 073/127] Fix fast-unstake for accounts with slashing (#12963) * Fix fast-unstake for accounts with slashing * ".git/.scripts/fmt.sh" 1 * fmt * fix * fix weight tracking * Adds tests for withdraw_unbonded with slashing * Removes tests for withdraw_unbonded with slashing * ".git/.scripts/fmt.sh" * Adds slash spans calculation test for withdraw_unbonded Co-authored-by: command-bot <> Co-authored-by: gpestana --- frame/fast-unstake/src/lib.rs | 14 +++++---- frame/staking/src/pallet/impls.rs | 2 +- frame/staking/src/pallet/mod.rs | 3 +- frame/staking/src/tests.rs | 51 +++++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 8 deletions(-) diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index 7f226826cb..f2faeebc13 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -91,7 +91,7 @@ pub mod pallet { DispatchResult, }; use sp_staking::{EraIndex, StakingInterface}; - use sp_std::{prelude::*, vec::Vec}; + use sp_std::{collections::btree_set::BTreeSet, prelude::*, vec::Vec}; pub use weights::WeightInfo; #[derive(scale_info::TypeInfo, codec::Encode, codec::Decode, codec::MaxEncodedLen)] @@ -429,9 +429,9 @@ pub mod pallet { } }; - let check_stash = |stash, deposit, eras_checked: &mut u32| { + let check_stash = |stash, deposit, eras_checked: &mut BTreeSet| { let is_exposed = unchecked_eras_to_check.iter().any(|e| { - eras_checked.saturating_inc(); + eras_checked.insert(*e); T::Staking::is_exposed_in_era(&stash, e) }); @@ -452,7 +452,7 @@ pub mod pallet { ::WeightInfo::on_idle_unstake() } else { // eras checked so far. - let mut eras_checked = 0u32; + let mut eras_checked = BTreeSet::::new(); let pre_length = stashes.len(); let stashes: BoundedVec<(T::AccountId, BalanceOf), T::BatchSize> = stashes @@ -468,7 +468,7 @@ pub mod pallet { log!( debug, "checked {:?} eras, pre stashes: {:?}, post: {:?}", - eras_checked, + eras_checked.len(), pre_length, post_length, ); @@ -489,7 +489,9 @@ pub mod pallet { }, } - ::WeightInfo::on_idle_check(validator_count * eras_checked) + ::WeightInfo::on_idle_check( + validator_count * eras_checked.len() as u32, + ) } } } diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index a7190d70c7..db9aeba6fb 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -1569,7 +1569,7 @@ impl StakingInterface for Pallet { } fn force_unstake(who: Self::AccountId) -> sp_runtime::DispatchResult { - let num_slashing_spans = Self::slashing_spans(&who).iter().count() as u32; + let num_slashing_spans = Self::slashing_spans(&who).map_or(0, |s| s.iter().count() as u32); Self::force_unstake(RawOrigin::Root.into(), who.clone(), num_slashing_spans) } diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 1d5babe7ff..f04d13d05f 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -982,7 +982,8 @@ pub mod pallet { // `BondingDuration` to proceed with the unbonding. let maybe_withdraw_weight = { if unlocking == T::MaxUnlockingChunks::get() as usize { - let real_num_slashing_spans = Self::slashing_spans(&controller).iter().count(); + let real_num_slashing_spans = + Self::slashing_spans(&controller).map_or(0, |s| s.iter().count()); Some(Self::do_withdraw_unbonded(&controller, real_num_slashing_spans as u32)?) } else { None diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index fc6fc68e66..5c2766deee 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -5725,3 +5725,54 @@ fn scale_validator_count_errors() { ); }) } + +mod staking_interface { + use frame_support::storage::with_storage_layer; + use sp_staking::StakingInterface; + + use super::*; + + #[test] + fn force_unstake_with_slash_works() { + ExtBuilder::default().build_and_execute(|| { + // without slash + let _ = with_storage_layer::<(), _, _>(|| { + // bond an account, can unstake + assert_eq!(Staking::bonded(&11), Some(10)); + assert_ok!(::force_unstake(11)); + Err(DispatchError::from("revert")) + }); + + // bond again and add a slash, still can unstake. + assert_eq!(Staking::bonded(&11), Some(10)); + add_slash(&11); + assert_ok!(::force_unstake(11)); + }); + } + + #[test] + fn do_withdraw_unbonded_with_wrong_slash_spans_works_as_expected() { + ExtBuilder::default().build_and_execute(|| { + on_offence_now( + &[OffenceDetails { + offender: (11, Staking::eras_stakers(active_era(), 11)), + reporters: vec![], + }], + &[Perbill::from_percent(100)], + ); + + assert_eq!(Staking::bonded(&11), Some(10)); + + assert_noop!( + Staking::withdraw_unbonded(RuntimeOrigin::signed(10), 0), + Error::::IncorrectSlashingSpans + ); + + let num_slashing_spans = Staking::slashing_spans(&11).map_or(0, |s| s.iter().count()); + assert_ok!(Staking::withdraw_unbonded( + RuntimeOrigin::signed(10), + num_slashing_spans as u32 + )); + }); + } +} From 40cb4318102adc806c8f7f17da20d1b6be6dce34 Mon Sep 17 00:00:00 2001 From: Dmitry Markin Date: Fri, 23 Dec 2022 16:03:08 +0300 Subject: [PATCH 074/127] Runtime diagnostics for leaked messages in unbounded channels (#12971) --- Cargo.lock | 2 + client/api/src/notifications.rs | 4 +- client/beefy/rpc/src/lib.rs | 4 +- client/beefy/src/lib.rs | 2 +- client/beefy/src/tests.rs | 10 +- .../common/src/import_queue/basic_queue.rs | 8 +- .../common/src/import_queue/buffered_link.rs | 12 +- client/finality-grandpa/rpc/src/lib.rs | 2 +- .../src/communication/gossip.rs | 2 +- .../src/communication/periodic.rs | 1 + .../src/communication/tests.rs | 4 +- client/finality-grandpa/src/lib.rs | 3 +- client/finality-grandpa/src/observer.rs | 2 +- client/finality-grandpa/src/until_imported.rs | 8 +- client/network/src/service.rs | 4 +- client/network/src/service/out_events.rs | 91 +++++++++---- client/network/sync/src/lib.rs | 2 +- client/network/sync/src/service/network.rs | 2 +- client/network/transactions/Cargo.toml | 1 + client/network/transactions/src/lib.rs | 9 +- client/offchain/src/api/http.rs | 4 +- client/peerset/src/lib.rs | 2 +- client/rpc/src/system/tests.rs | 2 +- client/service/src/builder.rs | 2 +- client/service/src/client/client.rs | 4 +- client/service/src/task_manager/mod.rs | 3 +- client/telemetry/Cargo.toml | 1 + client/telemetry/src/lib.rs | 14 +- client/transaction-pool/src/graph/watcher.rs | 2 +- client/transaction-pool/src/revalidation.rs | 2 +- client/utils/src/mpsc.rs | 121 ++++++++++++++---- client/utils/src/notification.rs | 4 +- client/utils/src/notification/tests.rs | 2 +- client/utils/src/pubsub.rs | 4 +- .../src/pubsub/tests/normal_operation.rs | 10 +- .../src/pubsub/tests/panicking_registry.rs | 37 +++--- client/utils/src/status_sinks.rs | 4 +- 37 files changed, 257 insertions(+), 134 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8412781c68..c4bd2532e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7948,6 +7948,7 @@ dependencies = [ "pin-project", "sc-network-common", "sc-peerset", + "sc-utils", "sp-consensus", "sp-runtime", "substrate-prometheus-endpoint", @@ -8284,6 +8285,7 @@ dependencies = [ "parking_lot 0.12.1", "pin-project", "rand 0.8.5", + "sc-utils", "serde", "serde_json", "thiserror", diff --git a/client/api/src/notifications.rs b/client/api/src/notifications.rs index 9fcc381f96..4dd23581d2 100644 --- a/client/api/src/notifications.rs +++ b/client/api/src/notifications.rs @@ -144,7 +144,9 @@ impl StorageNotifications { filter_keys: Option<&[StorageKey]>, filter_child_keys: Option<&[(StorageKey, Option>)]>, ) -> StorageEventStream { - let receiver = self.0.subscribe(registry::SubscribeOp { filter_keys, filter_child_keys }); + let receiver = self + .0 + .subscribe(registry::SubscribeOp { filter_keys, filter_child_keys }, 100_000); StorageEventStream(receiver) } diff --git a/client/beefy/rpc/src/lib.rs b/client/beefy/rpc/src/lib.rs index 59a133b862..4fea98c6eb 100644 --- a/client/beefy/rpc/src/lib.rs +++ b/client/beefy/rpc/src/lib.rs @@ -120,7 +120,7 @@ where ) -> Result { let beefy_best_block = Arc::new(RwLock::new(None)); - let stream = best_block_stream.subscribe(); + let stream = best_block_stream.subscribe(100_000); let closure_clone = beefy_best_block.clone(); let future = stream.for_each(move |best_beefy| { let async_clone = closure_clone.clone(); @@ -141,7 +141,7 @@ where fn subscribe_justifications(&self, mut sink: SubscriptionSink) -> SubscriptionResult { let stream = self .finality_proof_stream - .subscribe() + .subscribe(100_000) .map(|vfp| notification::EncodedVersionedFinalityProof::new::(vfp)); let fut = async move { diff --git a/client/beefy/src/lib.rs b/client/beefy/src/lib.rs index 35f7cac55a..5b6531822a 100644 --- a/client/beefy/src/lib.rs +++ b/client/beefy/src/lib.rs @@ -265,7 +265,7 @@ where // Subscribe to finality notifications and justifications before waiting for runtime pallet and // reuse the streams, so we don't miss notifications while waiting for pallet to be available. let mut finality_notifications = client.finality_notification_stream().fuse(); - let block_import_justif = links.from_block_import_justif_stream.subscribe().fuse(); + let block_import_justif = links.from_block_import_justif_stream.subscribe(100_000).fuse(); // Wait for BEEFY pallet to be active before starting voter. let persisted_state = diff --git a/client/beefy/src/tests.rs b/client/beefy/src/tests.rs index ce6f6ae3c9..66d3a210d7 100644 --- a/client/beefy/src/tests.rs +++ b/client/beefy/src/tests.rs @@ -430,8 +430,8 @@ pub(crate) fn get_beefy_streams( let beefy_rpc_links = net.peer(index).data.beefy_rpc_links.lock().clone().unwrap(); let BeefyRPCLinks { from_voter_justif_stream, from_voter_best_beefy_stream } = beefy_rpc_links; - best_block_streams.push(from_voter_best_beefy_stream.subscribe()); - versioned_finality_proof_streams.push(from_voter_justif_stream.subscribe()); + best_block_streams.push(from_voter_best_beefy_stream.subscribe(100_000)); + versioned_finality_proof_streams.push(from_voter_justif_stream.subscribe(100_000)); }); (best_block_streams, versioned_finality_proof_streams) } @@ -736,7 +736,7 @@ async fn beefy_importing_blocks() { let hashof1 = block.header.hash(); // Import without justifications. - let mut justif_recv = justif_stream.subscribe(); + let mut justif_recv = justif_stream.subscribe(100_000); assert_eq!( block_import .import_block(params(block.clone(), None), HashMap::new()) @@ -779,7 +779,7 @@ async fn beefy_importing_blocks() { let builder = full_client.new_block_at(&parent_id, Default::default(), false).unwrap(); let block = builder.build().unwrap().block; let hashof2 = block.header.hash(); - let mut justif_recv = justif_stream.subscribe(); + let mut justif_recv = justif_stream.subscribe(100_000); assert_eq!( block_import.import_block(params(block, justif), HashMap::new()).await.unwrap(), ImportResult::Imported(ImportedAux { @@ -823,7 +823,7 @@ async fn beefy_importing_blocks() { let builder = full_client.new_block_at(&parent_id, Default::default(), false).unwrap(); let block = builder.build().unwrap().block; let hashof3 = block.header.hash(); - let mut justif_recv = justif_stream.subscribe(); + let mut justif_recv = justif_stream.subscribe(100_000); assert_eq!( block_import.import_block(params(block, justif), HashMap::new()).await.unwrap(), ImportResult::Imported(ImportedAux { diff --git a/client/consensus/common/src/import_queue/basic_queue.rs b/client/consensus/common/src/import_queue/basic_queue.rs index b63bc192b2..a96b0a47e5 100644 --- a/client/consensus/common/src/import_queue/basic_queue.rs +++ b/client/consensus/common/src/import_queue/basic_queue.rs @@ -69,7 +69,7 @@ impl BasicQueue { spawner: &impl sp_core::traits::SpawnEssentialNamed, prometheus_registry: Option<&Registry>, ) -> Self { - let (result_sender, result_port) = buffered_link::buffered_link(); + let (result_sender, result_port) = buffered_link::buffered_link(100_000); let metrics = prometheus_registry.and_then(|r| { Metrics::register(r) @@ -276,10 +276,10 @@ impl BlockImportWorker { use worker_messages::*; let (justification_sender, mut justification_port) = - tracing_unbounded("mpsc_import_queue_worker_justification"); + tracing_unbounded("mpsc_import_queue_worker_justification", 100_000); let (block_import_sender, block_import_port) = - tracing_unbounded("mpsc_import_queue_worker_blocks"); + tracing_unbounded("mpsc_import_queue_worker_blocks", 100_000); let mut worker = BlockImportWorker { result_sender, justification_import, metrics }; @@ -595,7 +595,7 @@ mod tests { #[test] fn prioritizes_finality_work_over_block_import() { - let (result_sender, mut result_port) = buffered_link::buffered_link(); + let (result_sender, mut result_port) = buffered_link::buffered_link(100_000); let (worker, mut finality_sender, mut block_import_sender) = BlockImportWorker::new(result_sender, (), Box::new(()), Some(Box::new(())), None); diff --git a/client/consensus/common/src/import_queue/buffered_link.rs b/client/consensus/common/src/import_queue/buffered_link.rs index e6d3b212fd..71adcf2dc2 100644 --- a/client/consensus/common/src/import_queue/buffered_link.rs +++ b/client/consensus/common/src/import_queue/buffered_link.rs @@ -28,7 +28,7 @@ //! # use sp_test_primitives::Block; //! # struct DummyLink; impl Link for DummyLink {} //! # let mut my_link = DummyLink; -//! let (mut tx, mut rx) = buffered_link::(); +//! let (mut tx, mut rx) = buffered_link::(100_000); //! tx.blocks_processed(0, 0, vec![]); //! //! // Calls `my_link.blocks_processed(0, 0, vec![])` when polled. @@ -51,9 +51,11 @@ use super::BlockImportResult; /// Wraps around an unbounded channel from the `futures` crate. The sender implements `Link` and /// can be used to buffer commands, and the receiver can be used to poll said commands and transfer -/// them to another link. -pub fn buffered_link() -> (BufferedLinkSender, BufferedLinkReceiver) { - let (tx, rx) = tracing_unbounded("mpsc_buffered_link"); +/// them to another link. `queue_size_warning` sets the warning threshold of the channel queue size. +pub fn buffered_link( + queue_size_warning: i64, +) -> (BufferedLinkSender, BufferedLinkReceiver) { + let (tx, rx) = tracing_unbounded("mpsc_buffered_link", queue_size_warning); let tx = BufferedLinkSender { tx }; let rx = BufferedLinkReceiver { rx: rx.fuse() }; (tx, rx) @@ -175,7 +177,7 @@ mod tests { #[test] fn is_closed() { - let (tx, rx) = super::buffered_link::(); + let (tx, rx) = super::buffered_link::(1); assert!(!tx.is_closed()); drop(rx); assert!(tx.is_closed()); diff --git a/client/finality-grandpa/rpc/src/lib.rs b/client/finality-grandpa/rpc/src/lib.rs index dfdad666ba..70ff7ed176 100644 --- a/client/finality-grandpa/rpc/src/lib.rs +++ b/client/finality-grandpa/rpc/src/lib.rs @@ -104,7 +104,7 @@ where } fn subscribe_justifications(&self, mut sink: SubscriptionSink) -> SubscriptionResult { - let stream = self.justification_stream.subscribe().map( + let stream = self.justification_stream.subscribe(100_000).map( |x: sc_finality_grandpa::GrandpaJustification| { JustificationNotification::from(x) }, diff --git a/client/finality-grandpa/src/communication/gossip.rs b/client/finality-grandpa/src/communication/gossip.rs index 408cbda745..cbcafc727d 100644 --- a/client/finality-grandpa/src/communication/gossip.rs +++ b/client/finality-grandpa/src/communication/gossip.rs @@ -1364,7 +1364,7 @@ impl GossipValidator { None => None, }; - let (tx, rx) = tracing_unbounded("mpsc_grandpa_gossip_validator"); + let (tx, rx) = tracing_unbounded("mpsc_grandpa_gossip_validator", 100_000); let val = GossipValidator { inner: parking_lot::RwLock::new(Inner::new(config)), set_state, diff --git a/client/finality-grandpa/src/communication/periodic.rs b/client/finality-grandpa/src/communication/periodic.rs index 7e50abb96e..c00fed1296 100644 --- a/client/finality-grandpa/src/communication/periodic.rs +++ b/client/finality-grandpa/src/communication/periodic.rs @@ -70,6 +70,7 @@ impl NeighborPacketWorker { pub(super) fn new(rebroadcast_period: Duration) -> (Self, NeighborPacketSender) { let (tx, rx) = tracing_unbounded::<(Vec, NeighborPacket>)>( "mpsc_grandpa_neighbor_packet_worker", + 100_000, ); let delay = Delay::new(rebroadcast_period); diff --git a/client/finality-grandpa/src/communication/tests.rs b/client/finality-grandpa/src/communication/tests.rs index eab7bb2df5..839b2d52be 100644 --- a/client/finality-grandpa/src/communication/tests.rs +++ b/client/finality-grandpa/src/communication/tests.rs @@ -135,7 +135,7 @@ impl NetworkEventStream for TestNetwork { &self, _name: &'static str, ) -> Pin + Send>> { - let (tx, rx) = tracing_unbounded("test"); + let (tx, rx) = tracing_unbounded("test", 100_000); let _ = self.sender.unbounded_send(Event::EventStream(tx)); Box::pin(rx) } @@ -253,7 +253,7 @@ fn voter_set_state() -> SharedVoterSetState { // needs to run in a tokio runtime. pub(crate) fn make_test_network() -> (impl Future, TestNetwork) { - let (tx, rx) = tracing_unbounded("test"); + let (tx, rx) = tracing_unbounded("test", 100_000); let net = TestNetwork { sender: tx }; #[derive(Clone)] diff --git a/client/finality-grandpa/src/lib.rs b/client/finality-grandpa/src/lib.rs index efc46d8f93..1597e60bd6 100644 --- a/client/finality-grandpa/src/lib.rs +++ b/client/finality-grandpa/src/lib.rs @@ -566,7 +566,8 @@ where } })?; - let (voter_commands_tx, voter_commands_rx) = tracing_unbounded("mpsc_grandpa_voter_command"); + let (voter_commands_tx, voter_commands_rx) = + tracing_unbounded("mpsc_grandpa_voter_command", 100_000); let (justification_sender, justification_stream) = GrandpaJustificationStream::channel(); diff --git a/client/finality-grandpa/src/observer.rs b/client/finality-grandpa/src/observer.rs index 1efb71e590..96101a8eda 100644 --- a/client/finality-grandpa/src/observer.rs +++ b/client/finality-grandpa/src/observer.rs @@ -437,7 +437,7 @@ mod tests { aux_schema::load_persistent(&*backend, client.info().genesis_hash, 0, || Ok(voters)) .unwrap(); - let (_tx, voter_command_rx) = tracing_unbounded(""); + let (_tx, voter_command_rx) = tracing_unbounded("test_mpsc_voter_command", 100_000); let observer = ObserverWork::new( client, diff --git a/client/finality-grandpa/src/until_imported.rs b/client/finality-grandpa/src/until_imported.rs index 95b658e922..776411f8fb 100644 --- a/client/finality-grandpa/src/until_imported.rs +++ b/client/finality-grandpa/src/until_imported.rs @@ -579,7 +579,7 @@ mod tests { impl TestChainState { fn new() -> (Self, ImportNotifications) { - let (tx, rx) = tracing_unbounded("test"); + let (tx, rx) = tracing_unbounded("test", 100_000); let state = TestChainState { sender: tx, known_blocks: Arc::new(Mutex::new(HashMap::new())) }; @@ -680,7 +680,7 @@ mod tests { // enact all dependencies before importing the message enact_dependencies(&chain_state); - let (global_tx, global_rx) = tracing_unbounded("test"); + let (global_tx, global_rx) = tracing_unbounded("test", 100_000); let until_imported = UntilGlobalMessageBlocksImported::new( import_notifications, @@ -708,7 +708,7 @@ mod tests { let (chain_state, import_notifications) = TestChainState::new(); let block_status = chain_state.block_status(); - let (global_tx, global_rx) = tracing_unbounded("test"); + let (global_tx, global_rx) = tracing_unbounded("test", 100_000); let until_imported = UntilGlobalMessageBlocksImported::new( import_notifications, @@ -896,7 +896,7 @@ mod tests { let (chain_state, import_notifications) = TestChainState::new(); let block_status = chain_state.block_status(); - let (global_tx, global_rx) = tracing_unbounded("test"); + let (global_tx, global_rx) = tracing_unbounded("test", 100_000); let block_sync_requester = TestBlockSyncRequester::default(); diff --git a/client/network/src/service.rs b/client/network/src/service.rs index 08e498299a..66de263a19 100644 --- a/client/network/src/service.rs +++ b/client/network/src/service.rs @@ -208,7 +208,7 @@ where ¶ms.network_config.transport, )?; - let (to_worker, from_service) = tracing_unbounded("mpsc_network_worker"); + let (to_worker, from_service) = tracing_unbounded("mpsc_network_worker", 100_000); if let Some(path) = ¶ms.network_config.net_config_path { fs::create_dir_all(path)?; @@ -1003,7 +1003,7 @@ where H: ExHashT, { fn event_stream(&self, name: &'static str) -> Pin + Send>> { - let (tx, rx) = out_events::channel(name); + let (tx, rx) = out_events::channel(name, 100_000); let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::EventStream(tx)); Box::pin(rx) } diff --git a/client/network/src/service/out_events.rs b/client/network/src/service/out_events.rs index 4144d7f195..8febdd4726 100644 --- a/client/network/src/service/out_events.rs +++ b/client/network/src/service/out_events.rs @@ -32,25 +32,40 @@ //! collection. use futures::{channel::mpsc, prelude::*, ready, stream::FusedStream}; +use log::error; use parking_lot::Mutex; use prometheus_endpoint::{register, CounterVec, GaugeVec, Opts, PrometheusError, Registry, U64}; use sc_network_common::protocol::event::Event; use std::{ + backtrace::{Backtrace, BacktraceStatus}, cell::RefCell, fmt, pin::Pin, - sync::Arc, + sync::{ + atomic::{AtomicI64, Ordering}, + Arc, + }, task::{Context, Poll}, }; /// Creates a new channel that can be associated to a [`OutChannels`]. /// -/// The name is used in Prometheus reports. -pub fn channel(name: &'static str) -> (Sender, Receiver) { +/// The name is used in Prometheus reports, the queue size threshold is used +/// to warn if there are too many unprocessed events in the channel. +pub fn channel(name: &'static str, queue_size_warning: i64) -> (Sender, Receiver) { let (tx, rx) = mpsc::unbounded(); let metrics = Arc::new(Mutex::new(None)); - let tx = Sender { inner: tx, name, metrics: metrics.clone() }; - let rx = Receiver { inner: rx, name, metrics }; + let queue_size = Arc::new(AtomicI64::new(0)); + let tx = Sender { + inner: tx, + name, + queue_size: queue_size.clone(), + queue_size_warning, + warning_fired: false, + creation_backtrace: Backtrace::capture(), + metrics: metrics.clone(), + }; + let rx = Receiver { inner: rx, name, queue_size, metrics }; (tx, rx) } @@ -63,7 +78,19 @@ pub fn channel(name: &'static str) -> (Sender, Receiver) { /// sync on drop. If someone adds a `#[derive(Clone)]` below, it is **wrong**. pub struct Sender { inner: mpsc::UnboundedSender, + /// Name to identify the channel (e.g., in Prometheus and logs). name: &'static str, + /// Number of events in the queue. Clone of [`Receiver::in_transit`]. + // To not bother with ordering and possible underflow errors of the unsigned counter + // we just use `i64` and `Ordering::Relaxed`, and perceive `queue_size` as approximate. + // It can turn < 0 though. + queue_size: Arc, + /// Threshold queue size to generate an error message in the logs. + queue_size_warning: i64, + /// We generate the error message only once to not spam the logs. + warning_fired: bool, + /// Backtrace of a place where the channel was created. + creation_backtrace: Backtrace, /// Clone of [`Receiver::metrics`]. metrics: Arc>>>>, } @@ -87,6 +114,7 @@ impl Drop for Sender { pub struct Receiver { inner: mpsc::UnboundedReceiver, name: &'static str, + queue_size: Arc, /// Initially contains `None`, and will be set to a value once the corresponding [`Sender`] /// is assigned to an instance of [`OutChannels`]. metrics: Arc>>>>, @@ -97,6 +125,7 @@ impl Stream for Receiver { fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { if let Some(ev) = ready!(Pin::new(&mut self.inner).poll_next(cx)) { + let _ = self.queue_size.fetch_sub(1, Ordering::Relaxed); let metrics = self.metrics.lock().clone(); match metrics.as_ref().map(|m| m.as_ref()) { Some(Some(metrics)) => metrics.event_out(&ev, self.name), @@ -160,12 +189,28 @@ impl OutChannels { /// Sends an event. pub fn send(&mut self, event: Event) { - self.event_streams - .retain(|sender| sender.inner.unbounded_send(event.clone()).is_ok()); + self.event_streams.retain_mut(|sender| { + let queue_size = sender.queue_size.fetch_add(1, Ordering::Relaxed); + if queue_size == sender.queue_size_warning && !sender.warning_fired { + sender.warning_fired = true; + match sender.creation_backtrace.status() { + BacktraceStatus::Captured => error!( + "The number of unprocessed events in channel `{}` reached {}.\n\ + The channel was created at:\n{}", + sender.name, sender.queue_size_warning, sender.creation_backtrace, + ), + _ => error!( + "The number of unprocessed events in channel `{}` reached {}.", + sender.name, sender.queue_size_warning, + ), + } + } + sender.inner.unbounded_send(event.clone()).is_ok() + }); if let Some(metrics) = &*self.metrics { for ev in &self.event_streams { - metrics.event_in(&event, 1, ev.name); + metrics.event_in(&event, ev.name); } } } @@ -232,45 +277,35 @@ impl Metrics { }) } - fn event_in(&self, event: &Event, num: u64, name: &str) { + fn event_in(&self, event: &Event, name: &str) { match event { Event::Dht(_) => { - self.events_total.with_label_values(&["dht", "sent", name]).inc_by(num); + self.events_total.with_label_values(&["dht", "sent", name]).inc(); }, Event::SyncConnected { .. } => { - self.events_total - .with_label_values(&["sync-connected", "sent", name]) - .inc_by(num); + self.events_total.with_label_values(&["sync-connected", "sent", name]).inc(); }, Event::SyncDisconnected { .. } => { - self.events_total - .with_label_values(&["sync-disconnected", "sent", name]) - .inc_by(num); + self.events_total.with_label_values(&["sync-disconnected", "sent", name]).inc(); }, Event::NotificationStreamOpened { protocol, .. } => { format_label("notif-open-", protocol, |protocol_label| { - self.events_total - .with_label_values(&[protocol_label, "sent", name]) - .inc_by(num); + self.events_total.with_label_values(&[protocol_label, "sent", name]).inc(); }); }, Event::NotificationStreamClosed { protocol, .. } => { format_label("notif-closed-", protocol, |protocol_label| { - self.events_total - .with_label_values(&[protocol_label, "sent", name]) - .inc_by(num); + self.events_total.with_label_values(&[protocol_label, "sent", name]).inc(); }); }, Event::NotificationsReceived { messages, .. } => for (protocol, message) in messages { format_label("notif-", protocol, |protocol_label| { - self.events_total - .with_label_values(&[protocol_label, "sent", name]) - .inc_by(num); + self.events_total.with_label_values(&[protocol_label, "sent", name]).inc(); }); - self.notifications_sizes.with_label_values(&[protocol, "sent", name]).inc_by( - num.saturating_mul(u64::try_from(message.len()).unwrap_or(u64::MAX)), - ); + self.notifications_sizes + .with_label_values(&[protocol, "sent", name]) + .inc_by(u64::try_from(message.len()).unwrap_or(u64::MAX)); }, } } diff --git a/client/network/sync/src/lib.rs b/client/network/sync/src/lib.rs index 7dd818d4c1..76f14a721a 100644 --- a/client/network/sync/src/lib.rs +++ b/client/network/sync/src/lib.rs @@ -1436,7 +1436,7 @@ where state_request_protocol_name: ProtocolName, warp_sync_protocol_name: Option, ) -> Result<(Self, ChainSyncInterfaceHandle, NonDefaultSetConfig), ClientError> { - let (tx, service_rx) = tracing_unbounded("mpsc_chain_sync"); + let (tx, service_rx) = tracing_unbounded("mpsc_chain_sync", 100_000); let block_announce_config = Self::get_block_announce_proto_config( protocol_id, fork_id, diff --git a/client/network/sync/src/service/network.rs b/client/network/sync/src/service/network.rs index c44398b0f1..b81a65ae73 100644 --- a/client/network/sync/src/service/network.rs +++ b/client/network/sync/src/service/network.rs @@ -99,7 +99,7 @@ impl NetworkServiceHandle { impl NetworkServiceProvider { /// Create new `NetworkServiceProvider` pub fn new() -> (Self, NetworkServiceHandle) { - let (tx, rx) = tracing_unbounded("mpsc_network_service_provider"); + let (tx, rx) = tracing_unbounded("mpsc_network_service_provider", 100_000); (Self { rx }, NetworkServiceHandle::new(tx)) } diff --git a/client/network/transactions/Cargo.toml b/client/network/transactions/Cargo.toml index f91651d150..1255dededc 100644 --- a/client/network/transactions/Cargo.toml +++ b/client/network/transactions/Cargo.toml @@ -22,5 +22,6 @@ pin-project = "1.0.12" prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../../utils/prometheus" } sc-network-common = { version = "0.10.0-dev", path = "../common" } sc-peerset = { version = "4.0.0-dev", path = "../../peerset" } +sc-utils = { version = "4.0.0-dev", path = "../../utils" } sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } diff --git a/client/network/transactions/src/lib.rs b/client/network/transactions/src/lib.rs index 4cc76507c6..a5adb274d2 100644 --- a/client/network/transactions/src/lib.rs +++ b/client/network/transactions/src/lib.rs @@ -28,7 +28,7 @@ use crate::config::*; use codec::{Decode, Encode}; -use futures::{channel::mpsc, prelude::*, stream::FuturesUnordered}; +use futures::{prelude::*, stream::FuturesUnordered}; use libp2p::{multiaddr, PeerId}; use log::{debug, trace, warn}; use prometheus_endpoint::{register, Counter, PrometheusError, Registry, U64}; @@ -40,6 +40,7 @@ use sc_network_common::{ utils::{interval, LruHashSet}, ExHashT, }; +use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; use sp_runtime::traits::Block as BlockT; use std::{ collections::{hash_map::Entry, HashMap}, @@ -168,7 +169,7 @@ impl TransactionsHandlerPrototype { metrics_registry: Option<&Registry>, ) -> error::Result<(TransactionsHandler, TransactionsHandlerController)> { let event_stream = service.event_stream("transactions-handler"); - let (to_handler, from_controller) = mpsc::unbounded(); + let (to_handler, from_controller) = tracing_unbounded("mpsc_transactions_handler", 100_000); let handler = TransactionsHandler { protocol_name: self.protocol_name, @@ -197,7 +198,7 @@ impl TransactionsHandlerPrototype { /// Controls the behaviour of a [`TransactionsHandler`] it is connected to. pub struct TransactionsHandlerController { - to_handler: mpsc::UnboundedSender>, + to_handler: TracingUnboundedSender>, } impl TransactionsHandlerController { @@ -246,7 +247,7 @@ pub struct TransactionsHandler< // All connected peers peers: HashMap>, transaction_pool: Arc>, - from_controller: mpsc::UnboundedReceiver>, + from_controller: TracingUnboundedReceiver>, /// Prometheus metrics. metrics: Option, } diff --git a/client/offchain/src/api/http.rs b/client/offchain/src/api/http.rs index 4c97e5a470..a47adb3e80 100644 --- a/client/offchain/src/api/http.rs +++ b/client/offchain/src/api/http.rs @@ -66,8 +66,8 @@ impl SharedClient { /// Creates a pair of [`HttpApi`] and [`HttpWorker`]. pub fn http(shared_client: SharedClient) -> (HttpApi, HttpWorker) { - let (to_worker, from_api) = tracing_unbounded("mpsc_ocw_to_worker"); - let (to_api, from_worker) = tracing_unbounded("mpsc_ocw_to_api"); + let (to_worker, from_api) = tracing_unbounded("mpsc_ocw_to_worker", 100_000); + let (to_api, from_worker) = tracing_unbounded("mpsc_ocw_to_api", 100_000); let api = HttpApi { to_worker, diff --git a/client/peerset/src/lib.rs b/client/peerset/src/lib.rs index ec09835c48..9b1dc6a2d0 100644 --- a/client/peerset/src/lib.rs +++ b/client/peerset/src/lib.rs @@ -275,7 +275,7 @@ pub struct Peerset { impl Peerset { /// Builds a new peerset from the given configuration. pub fn from_config(config: PeersetConfig) -> (Self, PeersetHandle) { - let (tx, rx) = tracing_unbounded("mpsc_peerset_messages"); + let (tx, rx) = tracing_unbounded("mpsc_peerset_messages", 10_000); let handle = PeersetHandle { tx: tx.clone() }; diff --git a/client/rpc/src/system/tests.rs b/client/rpc/src/system/tests.rs index 00ab9c4686..4da49cdd1a 100644 --- a/client/rpc/src/system/tests.rs +++ b/client/rpc/src/system/tests.rs @@ -52,7 +52,7 @@ impl Default for Status { fn api>>(sync: T) -> RpcModule> { let status = sync.into().unwrap_or_default(); let should_have_peers = !status.is_dev; - let (tx, rx) = tracing_unbounded("rpc_system_tests"); + let (tx, rx) = tracing_unbounded("rpc_system_tests", 10_000); thread::spawn(move || { futures::executor::block_on(rx.for_each(move |request| { match request { diff --git a/client/service/src/builder.rs b/client/service/src/builder.rs index 0a26c00485..1f94f96fae 100644 --- a/client/service/src/builder.rs +++ b/client/service/src/builder.rs @@ -962,7 +962,7 @@ where ); spawn_handle.spawn("import-queue", None, import_queue.run(Box::new(chain_sync_service))); - let (system_rpc_tx, system_rpc_rx) = tracing_unbounded("mpsc_system_rpc"); + let (system_rpc_tx, system_rpc_rx) = tracing_unbounded("mpsc_system_rpc", 10_000); let future = build_network_future( config.role.clone(), diff --git a/client/service/src/client/client.rs b/client/service/src/client/client.rs index 493fd320b7..78f2174b89 100644 --- a/client/service/src/client/client.rs +++ b/client/service/src/client/client.rs @@ -1913,13 +1913,13 @@ where { /// Get block import event stream. fn import_notification_stream(&self) -> ImportNotifications { - let (sink, stream) = tracing_unbounded("mpsc_import_notification_stream"); + let (sink, stream) = tracing_unbounded("mpsc_import_notification_stream", 100_000); self.import_notification_sinks.lock().push(sink); stream } fn finality_notification_stream(&self) -> FinalityNotifications { - let (sink, stream) = tracing_unbounded("mpsc_finality_notification_stream"); + let (sink, stream) = tracing_unbounded("mpsc_finality_notification_stream", 100_000); self.finality_notification_sinks.lock().push(sink); stream } diff --git a/client/service/src/task_manager/mod.rs b/client/service/src/task_manager/mod.rs index 49189dc21c..23265f9672 100644 --- a/client/service/src/task_manager/mod.rs +++ b/client/service/src/task_manager/mod.rs @@ -310,7 +310,8 @@ impl TaskManager { let (signal, on_exit) = exit_future::signal(); // A side-channel for essential tasks to communicate shutdown. - let (essential_failed_tx, essential_failed_rx) = tracing_unbounded("mpsc_essential_tasks"); + let (essential_failed_tx, essential_failed_rx) = + tracing_unbounded("mpsc_essential_tasks", 100); let metrics = prometheus_registry.map(Metrics::register).transpose()?; diff --git a/client/telemetry/Cargo.toml b/client/telemetry/Cargo.toml index 1fc89980e2..6ef69d1d97 100644 --- a/client/telemetry/Cargo.toml +++ b/client/telemetry/Cargo.toml @@ -20,6 +20,7 @@ libp2p = { version = "0.49.0", default-features = false, features = ["dns", "tcp log = "0.4.17" parking_lot = "0.12.1" pin-project = "1.0.12" +sc-utils = { version = "4.0.0-dev", path = "../utils" } rand = "0.8.5" serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0.85" diff --git a/client/telemetry/src/lib.rs b/client/telemetry/src/lib.rs index 503a326f76..aa6b841b79 100644 --- a/client/telemetry/src/lib.rs +++ b/client/telemetry/src/lib.rs @@ -40,6 +40,7 @@ use futures::{channel::mpsc, prelude::*}; use libp2p::Multiaddr; use log::{error, warn}; use parking_lot::Mutex; +use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; use serde::Serialize; use std::{ collections::{ @@ -147,8 +148,8 @@ pub struct SysInfo { pub struct TelemetryWorker { message_receiver: mpsc::Receiver, message_sender: mpsc::Sender, - register_receiver: mpsc::UnboundedReceiver, - register_sender: mpsc::UnboundedSender, + register_receiver: TracingUnboundedReceiver, + register_sender: TracingUnboundedSender, id_counter: Arc, } @@ -163,7 +164,8 @@ impl TelemetryWorker { // error as early as possible. let _transport = initialize_transport()?; let (message_sender, message_receiver) = mpsc::channel(buffer_size); - let (register_sender, register_receiver) = mpsc::unbounded(); + let (register_sender, register_receiver) = + tracing_unbounded("mpsc_telemetry_register", 10_000); Ok(Self { message_receiver, @@ -360,7 +362,7 @@ impl TelemetryWorker { #[derive(Debug, Clone)] pub struct TelemetryWorkerHandle { message_sender: mpsc::Sender, - register_sender: mpsc::UnboundedSender, + register_sender: TracingUnboundedSender, id_counter: Arc, } @@ -386,7 +388,7 @@ impl TelemetryWorkerHandle { #[derive(Debug)] pub struct Telemetry { message_sender: mpsc::Sender, - register_sender: mpsc::UnboundedSender, + register_sender: TracingUnboundedSender, id: Id, connection_notifier: TelemetryConnectionNotifier, endpoints: Option, @@ -460,7 +462,7 @@ impl TelemetryHandle { /// (re-)establishes. #[derive(Clone, Debug)] pub struct TelemetryConnectionNotifier { - register_sender: mpsc::UnboundedSender, + register_sender: TracingUnboundedSender, addresses: Vec, } diff --git a/client/transaction-pool/src/graph/watcher.rs b/client/transaction-pool/src/graph/watcher.rs index 0613300c86..df5bb94edf 100644 --- a/client/transaction-pool/src/graph/watcher.rs +++ b/client/transaction-pool/src/graph/watcher.rs @@ -62,7 +62,7 @@ impl Default for Sender { impl Sender { /// Add a new watcher to this sender object. pub fn new_watcher(&mut self, hash: H) -> Watcher { - let (tx, receiver) = tracing_unbounded("mpsc_txpool_watcher"); + let (tx, receiver) = tracing_unbounded("mpsc_txpool_watcher", 100_000); self.receivers.push(tx); Watcher { receiver, hash } } diff --git a/client/transaction-pool/src/revalidation.rs b/client/transaction-pool/src/revalidation.rs index b4b4299240..d8c8bea625 100644 --- a/client/transaction-pool/src/revalidation.rs +++ b/client/transaction-pool/src/revalidation.rs @@ -291,7 +291,7 @@ where pool: Arc>, interval: Duration, ) -> (Self, Pin + Send>>) { - let (to_worker, from_queue) = tracing_unbounded("mpsc_revalidation_queue"); + let (to_worker, from_queue) = tracing_unbounded("mpsc_revalidation_queue", 100_000); let worker = RevalidationWorker::new(api.clone(), pool.clone()); diff --git a/client/utils/src/mpsc.rs b/client/utils/src/mpsc.rs index ee3fba4a5e..7db5e49f5b 100644 --- a/client/utils/src/mpsc.rs +++ b/client/utils/src/mpsc.rs @@ -45,71 +45,136 @@ mod inner { stream::{FusedStream, Stream}, task::{Context, Poll}, }; - use std::pin::Pin; + use log::error; + use std::{ + backtrace::{Backtrace, BacktraceStatus}, + pin::Pin, + sync::{ + atomic::{AtomicBool, AtomicI64, Ordering}, + Arc, + }, + }; /// Wrapper Type around `UnboundedSender` that increases the global /// measure when a message is added #[derive(Debug)] - pub struct TracingUnboundedSender(&'static str, UnboundedSender); + pub struct TracingUnboundedSender { + inner: UnboundedSender, + name: &'static str, + // To not bother with ordering and possible underflow errors of the unsigned counter + // we just use `i64` and `Ordering::Relaxed`, and perceive `queue_size` as approximate. + // It can turn < 0 though. + queue_size: Arc, + queue_size_warning: i64, + warning_fired: Arc, + creation_backtrace: Arc, + } // Strangely, deriving `Clone` requires that `T` is also `Clone`. impl Clone for TracingUnboundedSender { fn clone(&self) -> Self { - Self(self.0, self.1.clone()) + Self { + inner: self.inner.clone(), + name: self.name, + queue_size: self.queue_size.clone(), + queue_size_warning: self.queue_size_warning, + warning_fired: self.warning_fired.clone(), + creation_backtrace: self.creation_backtrace.clone(), + } } } /// Wrapper Type around `UnboundedReceiver` that decreases the global /// measure when a message is polled #[derive(Debug)] - pub struct TracingUnboundedReceiver(&'static str, UnboundedReceiver); + pub struct TracingUnboundedReceiver { + inner: UnboundedReceiver, + name: &'static str, + queue_size: Arc, + } /// Wrapper around `mpsc::unbounded` that tracks the in- and outflow via - /// `UNBOUNDED_CHANNELS_COUNTER` + /// `UNBOUNDED_CHANNELS_COUNTER` and warns if the message queue grows + /// above the warning threshold. pub fn tracing_unbounded( - key: &'static str, + name: &'static str, + queue_size_warning: i64, ) -> (TracingUnboundedSender, TracingUnboundedReceiver) { let (s, r) = mpsc::unbounded(); - (TracingUnboundedSender(key, s), TracingUnboundedReceiver(key, r)) + let queue_size = Arc::new(AtomicI64::new(0)); + let sender = TracingUnboundedSender { + inner: s, + name, + queue_size: queue_size.clone(), + queue_size_warning, + warning_fired: Arc::new(AtomicBool::new(false)), + creation_backtrace: Arc::new(Backtrace::capture()), + }; + let receiver = TracingUnboundedReceiver { inner: r, name, queue_size }; + (sender, receiver) } impl TracingUnboundedSender { /// Proxy function to mpsc::UnboundedSender pub fn poll_ready(&self, ctx: &mut Context) -> Poll> { - self.1.poll_ready(ctx) + self.inner.poll_ready(ctx) } /// Proxy function to mpsc::UnboundedSender pub fn is_closed(&self) -> bool { - self.1.is_closed() + self.inner.is_closed() } /// Proxy function to mpsc::UnboundedSender pub fn close_channel(&self) { - self.1.close_channel() + self.inner.close_channel() } /// Proxy function to mpsc::UnboundedSender pub fn disconnect(&mut self) { - self.1.disconnect() + self.inner.disconnect() } - /// Proxy function to mpsc::UnboundedSender pub fn start_send(&mut self, msg: T) -> Result<(), SendError> { - self.1.start_send(msg) + // The underlying implementation of [`UnboundedSender::start_send`] is the same as + // [`UnboundedSender::unbounded_send`], so we just reuse the message counting and + // error reporting code from `unbounded_send`. + self.unbounded_send(msg).map_err(TrySendError::into_send_error) } /// Proxy function to mpsc::UnboundedSender pub fn unbounded_send(&self, msg: T) -> Result<(), TrySendError> { - self.1.unbounded_send(msg).map(|s| { - UNBOUNDED_CHANNELS_COUNTER.with_label_values(&[self.0, "send"]).inc(); + self.inner.unbounded_send(msg).map(|s| { + UNBOUNDED_CHANNELS_COUNTER.with_label_values(&[self.name, "send"]).inc(); + + let queue_size = self.queue_size.fetch_add(1, Ordering::Relaxed); + if queue_size == self.queue_size_warning && + !self.warning_fired.load(Ordering::Relaxed) + { + // `warning_fired` and `queue_size` are not synchronized, so it's possible + // that the warning is fired few times before the `warning_fired` is seen + // by all threads. This seems better than introducing a mutex guarding them. + self.warning_fired.store(true, Ordering::Relaxed); + match self.creation_backtrace.status() { + BacktraceStatus::Captured => error!( + "The number of unprocessed messages in channel `{}` reached {}.\n\ + The channel was created at:\n{}", + self.name, self.queue_size_warning, self.creation_backtrace, + ), + _ => error!( + "The number of unprocessed messages in channel `{}` reached {}.", + self.name, self.queue_size_warning, + ), + } + } + s }) } /// Proxy function to mpsc::UnboundedSender pub fn same_receiver(&self, other: &UnboundedSender) -> bool { - self.1.same_receiver(other) + self.inner.same_receiver(other) } } @@ -118,7 +183,7 @@ mod inner { // consume all items, make sure to reflect the updated count let mut count = 0; loop { - if self.1.is_terminated() { + if self.inner.is_terminated() { break } @@ -129,7 +194,9 @@ mod inner { } // and discount the messages if count > 0 { - UNBOUNDED_CHANNELS_COUNTER.with_label_values(&[self.0, "dropped"]).inc_by(count); + UNBOUNDED_CHANNELS_COUNTER + .with_label_values(&[self.name, "dropped"]) + .inc_by(count); } } @@ -137,15 +204,16 @@ mod inner { /// that consumes all messages first and updates the counter pub fn close(&mut self) { self.consume(); - self.1.close() + self.inner.close() } /// Proxy function to mpsc::UnboundedReceiver /// that discounts the messages taken out pub fn try_next(&mut self) -> Result, TryRecvError> { - self.1.try_next().map(|s| { + self.inner.try_next().map(|s| { if s.is_some() { - UNBOUNDED_CHANNELS_COUNTER.with_label_values(&[self.0, "received"]).inc(); + let _ = self.queue_size.fetch_sub(1, Ordering::Relaxed); + UNBOUNDED_CHANNELS_COUNTER.with_label_values(&[self.name, "received"]).inc(); } s }) @@ -165,10 +233,11 @@ mod inner { fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let s = self.get_mut(); - match Pin::new(&mut s.1).poll_next(cx) { + match Pin::new(&mut s.inner).poll_next(cx) { Poll::Ready(msg) => { if msg.is_some() { - UNBOUNDED_CHANNELS_COUNTER.with_label_values(&[s.0, "received"]).inc(); + let _ = s.queue_size.fetch_sub(1, Ordering::Relaxed); + UNBOUNDED_CHANNELS_COUNTER.with_label_values(&[s.name, "received"]).inc(); } Poll::Ready(msg) }, @@ -179,7 +248,7 @@ mod inner { impl FusedStream for TracingUnboundedReceiver { fn is_terminated(&self) -> bool { - self.1.is_terminated() + self.inner.is_terminated() } } @@ -223,6 +292,10 @@ mod inner { } fn poll_close(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { + // The difference with `TracingUnboundedSender` is intentional. The underlying + // implementation differs for `UnboundedSender` and `&UnboundedSender`: + // the latter closes the channel completely with `close_channel()`, while the former + // only closes this specific sender with `disconnect()`. self.close_channel(); Poll::Ready(Ok(())) } diff --git a/client/utils/src/notification.rs b/client/utils/src/notification.rs index ff527c343f..4917a43265 100644 --- a/client/utils/src/notification.rs +++ b/client/utils/src/notification.rs @@ -79,8 +79,8 @@ impl NotificationStream { } /// Subscribe to a channel through which the generic payload can be received. - pub fn subscribe(&self) -> NotificationReceiver { - let receiver = self.hub.subscribe(()); + pub fn subscribe(&self, queue_size_warning: i64) -> NotificationReceiver { + let receiver = self.hub.subscribe((), queue_size_warning); NotificationReceiver { receiver } } } diff --git a/client/utils/src/notification/tests.rs b/client/utils/src/notification/tests.rs index a001fa7e89..f813f37d29 100644 --- a/client/utils/src/notification/tests.rs +++ b/client/utils/src/notification/tests.rs @@ -36,7 +36,7 @@ fn notification_channel_simple() { // Create a future to receive a single notification // from the stream and verify its payload. - let future = stream.subscribe().take(1).for_each(move |payload| { + let future = stream.subscribe(100_000).take(1).for_each(move |payload| { let test_payload = closure_payload.clone(); async move { assert_eq!(payload, test_payload); diff --git a/client/utils/src/pubsub.rs b/client/utils/src/pubsub.rs index ba6e9ddc6c..f85f44b498 100644 --- a/client/utils/src/pubsub.rs +++ b/client/utils/src/pubsub.rs @@ -164,7 +164,7 @@ impl Hub { /// Subscribe to this Hub using the `subs_key: K`. /// /// A subscription with a key `K` is possible if the Registry implements `Subscribe`. - pub fn subscribe(&self, subs_key: K) -> Receiver + pub fn subscribe(&self, subs_key: K, queue_size_warning: i64) -> Receiver where R: Subscribe + Unsubscribe, { @@ -178,7 +178,7 @@ impl Hub { // have the sink disposed. shared_borrowed.registry.subscribe(subs_key, subs_id); - let (tx, rx) = crate::mpsc::tracing_unbounded(self.tracing_key); + let (tx, rx) = crate::mpsc::tracing_unbounded(self.tracing_key, queue_size_warning); assert!(shared_borrowed.sinks.insert(subs_id, tx).is_none(), "Used IDSequence to create another ID. Should be unique until u64 is overflowed. Should be unique."); Receiver { shared: Arc::downgrade(&self.shared), subs_id, rx } diff --git a/client/utils/src/pubsub/tests/normal_operation.rs b/client/utils/src/pubsub/tests/normal_operation.rs index a13c718d74..830388de32 100644 --- a/client/utils/src/pubsub/tests/normal_operation.rs +++ b/client/utils/src/pubsub/tests/normal_operation.rs @@ -27,7 +27,7 @@ fn positive_rx_receives_relevant_messages_and_terminates_upon_hub_drop() { // No subscribers yet. That message is not supposed to get to anyone. hub.send(0); - let mut rx_01 = hub.subscribe(SubsKey::new()); + let mut rx_01 = hub.subscribe(SubsKey::new(), 100_000); assert_eq!(hub.subs_count(), 1); // That message is sent after subscription. Should be delivered into rx_01. @@ -49,9 +49,9 @@ fn positive_subs_count_is_correct_upon_drop_of_rxs() { let hub = TestHub::new(TK); assert_eq!(hub.subs_count(), 0); - let rx_01 = hub.subscribe(SubsKey::new()); + let rx_01 = hub.subscribe(SubsKey::new(), 100_000); assert_eq!(hub.subs_count(), 1); - let rx_02 = hub.subscribe(SubsKey::new()); + let rx_02 = hub.subscribe(SubsKey::new(), 100_000); assert_eq!(hub.subs_count(), 2); std::mem::drop(rx_01); @@ -69,11 +69,11 @@ fn positive_subs_count_is_correct_upon_drop_of_rxs_on_cloned_hubs() { assert_eq!(hub_01.subs_count(), 0); assert_eq!(hub_02.subs_count(), 0); - let rx_01 = hub_02.subscribe(SubsKey::new()); + let rx_01 = hub_02.subscribe(SubsKey::new(), 100_000); assert_eq!(hub_01.subs_count(), 1); assert_eq!(hub_02.subs_count(), 1); - let rx_02 = hub_02.subscribe(SubsKey::new()); + let rx_02 = hub_02.subscribe(SubsKey::new(), 100_000); assert_eq!(hub_01.subs_count(), 2); assert_eq!(hub_02.subs_count(), 2); diff --git a/client/utils/src/pubsub/tests/panicking_registry.rs b/client/utils/src/pubsub/tests/panicking_registry.rs index 26ce63bd51..cfe8168d80 100644 --- a/client/utils/src/pubsub/tests/panicking_registry.rs +++ b/client/utils/src/pubsub/tests/panicking_registry.rs @@ -30,7 +30,7 @@ fn t01() { let hub = TestHub::new(TK); assert_hub_props(&hub, 0, 0); - let rx_01 = hub.subscribe(SubsKey::new()); + let rx_01 = hub.subscribe(SubsKey::new(), 100_000); assert_hub_props(&hub, 1, 1); std::mem::drop(rx_01); @@ -45,17 +45,17 @@ fn t02() { assert_hub_props(&hub, 0, 0); // Subscribe rx-01 - let rx_01 = hub.subscribe(SubsKey::new()); + let rx_01 = hub.subscribe(SubsKey::new(), 100_000); assert_hub_props(&hub, 1, 1); // Subscribe rx-02 so that its unsubscription will lead to an attempt to drop rx-01 in the // middle of unsubscription of rx-02 - let rx_02 = hub.subscribe(SubsKey::new().with_receiver(rx_01)); + let rx_02 = hub.subscribe(SubsKey::new().with_receiver(rx_01), 100_000); assert_hub_props(&hub, 2, 2); // Subscribe rx-03 in order to see that it will receive messages after the unclean // unsubscription - let mut rx_03 = hub.subscribe(SubsKey::new()); + let mut rx_03 = hub.subscribe(SubsKey::new(), 100_000); assert_hub_props(&hub, 3, 3); // drop rx-02 leads to an attempt to unsubscribe rx-01 @@ -69,7 +69,7 @@ fn t02() { // Subscribe rx-04 in order to see that it will receive messages after the unclean // unsubscription - let mut rx_04 = hub.subscribe(SubsKey::new()); + let mut rx_04 = hub.subscribe(SubsKey::new(), 100_000); assert_hub_props(&hub, 3, 3); hub.send(2); @@ -96,8 +96,8 @@ fn t02() { } async fn add_some_subscribers_see_that_messages_are_delivered_and_unsubscribe(hub: &TestHub) { - let rx_01 = hub.subscribe(SubsKey::new()); - let rx_02 = hub.subscribe(SubsKey::new()); + let rx_01 = hub.subscribe(SubsKey::new(), 100_000); + let rx_02 = hub.subscribe(SubsKey::new(), 100_000); hub.send(1); hub.send(2); @@ -121,9 +121,8 @@ fn t03() { add_some_subscribers_see_that_messages_are_delivered_and_unsubscribe(&hub).await; assert_hub_props(&hub, 0, 0); - assert!(catch_unwind(AssertUnwindSafe( - || hub.subscribe(SubsKey::new().with_panic(SubsKeyPanic::OnSubscribePanicBefore)) - )) + assert!(catch_unwind(AssertUnwindSafe(|| hub + .subscribe(SubsKey::new().with_panic(SubsKeyPanic::OnSubscribePanicBefore), 100_000))) .is_err()); assert_hub_props(&hub, 0, 0); @@ -141,9 +140,8 @@ fn t04() { add_some_subscribers_see_that_messages_are_delivered_and_unsubscribe(&hub).await; assert_hub_props(&hub, 0, 0); - assert!(catch_unwind(AssertUnwindSafe( - || hub.subscribe(SubsKey::new().with_panic(SubsKeyPanic::OnSubscribePanicAfter)) - )) + assert!(catch_unwind(AssertUnwindSafe(|| hub + .subscribe(SubsKey::new().with_panic(SubsKeyPanic::OnSubscribePanicAfter), 100_000))) .is_err()); // the registry has panicked after it has added a subs-id into its internal storage — the @@ -163,8 +161,8 @@ fn t05() { add_some_subscribers_see_that_messages_are_delivered_and_unsubscribe(&hub).await; assert_hub_props(&hub, 0, 0); - let rx_01 = - hub.subscribe(SubsKey::new().with_panic(SubsKeyPanic::OnUnsubscribePanicBefore)); + let rx_01 = hub + .subscribe(SubsKey::new().with_panic(SubsKeyPanic::OnUnsubscribePanicBefore), 100_000); assert_hub_props(&hub, 1, 1); add_some_subscribers_see_that_messages_are_delivered_and_unsubscribe(&hub).await; @@ -189,7 +187,8 @@ fn t06() { add_some_subscribers_see_that_messages_are_delivered_and_unsubscribe(&hub).await; assert_hub_props(&hub, 0, 0); - let rx_01 = hub.subscribe(SubsKey::new().with_panic(SubsKeyPanic::OnUnsubscribePanicAfter)); + let rx_01 = hub + .subscribe(SubsKey::new().with_panic(SubsKeyPanic::OnUnsubscribePanicAfter), 100_000); assert_hub_props(&hub, 1, 1); add_some_subscribers_see_that_messages_are_delivered_and_unsubscribe(&hub).await; @@ -214,7 +213,8 @@ fn t07() { add_some_subscribers_see_that_messages_are_delivered_and_unsubscribe(&hub).await; assert_hub_props(&hub, 0, 0); - let rx_01 = hub.subscribe(SubsKey::new().with_panic(SubsKeyPanic::OnDispatchPanicBefore)); + let rx_01 = + hub.subscribe(SubsKey::new().with_panic(SubsKeyPanic::OnDispatchPanicBefore), 100_000); assert_hub_props(&hub, 1, 1); assert!(catch_unwind(AssertUnwindSafe(|| hub.send(1))).is_err()); assert_hub_props(&hub, 1, 1); @@ -235,7 +235,8 @@ fn t08() { add_some_subscribers_see_that_messages_are_delivered_and_unsubscribe(&hub).await; assert_hub_props(&hub, 0, 0); - let rx_01 = hub.subscribe(SubsKey::new().with_panic(SubsKeyPanic::OnDispatchPanicAfter)); + let rx_01 = + hub.subscribe(SubsKey::new().with_panic(SubsKeyPanic::OnDispatchPanicAfter), 100_000); assert_hub_props(&hub, 1, 1); assert!(catch_unwind(AssertUnwindSafe(|| hub.send(1))).is_err()); assert_hub_props(&hub, 1, 1); diff --git a/client/utils/src/status_sinks.rs b/client/utils/src/status_sinks.rs index a1d965d080..c536e2c18c 100644 --- a/client/utils/src/status_sinks.rs +++ b/client/utils/src/status_sinks.rs @@ -58,7 +58,7 @@ impl Default for StatusSinks { impl StatusSinks { /// Builds a new empty collection. pub fn new() -> StatusSinks { - let (entries_tx, entries_rx) = tracing_unbounded("status-sinks-entries"); + let (entries_tx, entries_rx) = tracing_unbounded("status-sinks-entries", 100_000); StatusSinks { inner: Mutex::new(Inner { entries: stream::FuturesUnordered::new(), entries_rx }), @@ -196,7 +196,7 @@ mod tests { let status_sinks = StatusSinks::new(); - let (tx, rx) = tracing_unbounded("test"); + let (tx, rx) = tracing_unbounded("test", 100_000); status_sinks.push(Duration::from_millis(100), tx); let mut val_order = 5; From c7f5df2c160e8d836285127a60bbf7793b7067ba Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Fri, 23 Dec 2022 18:07:27 +0200 Subject: [PATCH 075/127] NFTs 2.0 (#12765) * Copy Uniques into Nfts * Connect new pallet * Update weights * Nfts: Multiple approvals (#12178) * multiple approvals * clear * tests & clean up * fix in logic & fmt * fix benchmarks * deadline * test deadline * current_block + deadline * update ApprovedTransfer event * benchmark * docs * Update frame/nfts/src/lib.rs Co-authored-by: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> * fmt fix * Update frame/nfts/src/lib.rs Co-authored-by: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> * update tests * anyone can cancel * Update frame/nfts/src/tests.rs Co-authored-by: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> * fmt * fix logic * unnecessary line * ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts * Update frame/nfts/src/lib.rs * Update lib.rs * fmt * Update frame/nfts/src/lib.rs Co-authored-by: Squirrel * Update frame/nfts/src/lib.rs Co-authored-by: Squirrel * fmt * Update frame/nfts/src/lib.rs Co-authored-by: Squirrel * suggestion * new line * ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts Co-authored-by: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Co-authored-by: command-bot <> Co-authored-by: Squirrel * Fixes * cargo fmt * Fixes * Fixes * Fix CI * Nfts: Fix Auto-Increment (#12223) * commit * passing benchmarks * clean up * sync * runtime implementation * fix * fmt * fix benchmark * cfg * remove try-increment-id * remove unused error * impl Incrementable for unsigned types * clean up * fix in tests * not needed anymore * Use OptionQuery Co-authored-by: Keith Yeung * Rename Origin to RuntimeOrigin * [Uniques V2] Tips (#12168) * Allow to add tips when buying an NFT * Chore * Rework tips feature * Add weights + benchmarks * Convert tuple to struct * Fix benchmark * ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts * Update frame/nfts/src/benchmarking.rs Co-authored-by: Oliver Tale-Yazdi * Fix benchmarks * Revert the bounded_vec![] approach * ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts Co-authored-by: command-bot <> Co-authored-by: Oliver Tale-Yazdi * [Uniques V2] Atomic NFTs swap (#12285) * Atomic NFTs swap * Fmt * Fix benchmark * Rename swap -> atomic_swap * Update target balance * Rollback * Fix * ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts * Make desired item optional * Apply suggestions * Update frame/nfts/src/features/atomic_swap.rs Co-authored-by: Squirrel * Rename fields * Optimisation * Add a comment * deadline -> maybe_deadline * Add docs * Change comments * Add price direction field * ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts * Wrap price and direction * Fix benchmarks * Use ensure! instead of if {} * Make duration param mandatory and limit it to MaxDeadlineDuration * Make the code safer * Fix clippy * Chore * Remove unused vars * try * try 2 * try 3 Co-authored-by: command-bot <> Co-authored-by: Squirrel * [Uniques V2] Feature flags (#12367) * Basics * WIP: change the data format * Refactor * Remove redundant new() method * Rename settings * Enable tests * Chore * Change params order * Delete the config on collection removal * Chore * Remove redundant system features * Rename force_item_status to force_collection_status * Update node runtime * Chore * Remove thaw_collection * Chore * Connect collection.is_frozen to config * Allow to lock the collection in a new way * Move free_holding into settings * Connect collection's metadata locker to feature flags * DRY * Chore * Connect pallet level feature flags * Prepare tests for the new changes * Implement Item settings * Allow to lock the metadata or attributes of an item * Common -> Settings * Extract settings related code to a separate file * Move feature flag checks inside the do_* methods * Split settings.rs into parts * Extract repeated code into macro * Extract macros into their own file * Chore * Fix traits * Fix traits * Test SystemFeatures * Fix benchmarks * Add missing benchmark * Fix node/runtime/lib.rs * ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts * Keep item's config on burn if it's not empty * Fix the merge artifacts * Fmt * Add SystemFeature::NoSwaps check * Rename SystemFeatures to PalletFeatures * Rename errors * Add docs * Change error message * Rework pallet features * Move macros * Change comments * Fmt * Refactor Incrementable * Use pub(crate) for do_* functions * Update comments * Refactor freeze and lock functions * Rework Collection config and Item confg api * Chore * Make clippy happy * Chore * Update comment * RequiredDeposit => DepositRequired * Address comments Co-authored-by: command-bot <> * [Uniques V2] Refactor roles (#12437) * Basics * WIP: change the data format * Refactor * Remove redundant new() method * Rename settings * Enable tests * Chore * Change params order * Delete the config on collection removal * Chore * Remove redundant system features * Rename force_item_status to force_collection_status * Update node runtime * Chore * Remove thaw_collection * Chore * Connect collection.is_frozen to config * Allow to lock the collection in a new way * Move free_holding into settings * Connect collection's metadata locker to feature flags * DRY * Chore * Connect pallet level feature flags * Prepare tests for the new changes * Implement Item settings * Allow to lock the metadata or attributes of an item * Common -> Settings * Extract settings related code to a separate file * Move feature flag checks inside the do_* methods * Split settings.rs into parts * Extract repeated code into macro * Extract macros into their own file * Chore * Fix traits * Fix traits * Test SystemFeatures * Fix benchmarks * Add missing benchmark * Fix node/runtime/lib.rs * ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts * Keep item's config on burn if it's not empty * Fix the merge artifacts * Fmt * Add SystemFeature::NoSwaps check * Refactor roles structure * Rename SystemFeatures to PalletFeatures * Rename errors * Add docs * Change error message * Rework pallet features * Move macros * Change comments * Fmt * Refactor Incrementable * Use pub(crate) for do_* functions * Update comments * Refactor freeze and lock functions * Rework Collection config and Item confg api * Chore * Make clippy happy * Chore * Fix artifacts * Address comments * Further refactoring * Add comments * Add tests for group_roles_by_account() * Update frame/nfts/src/impl_nonfungibles.rs * Add test * Replace Itertools group_by with a custom implementation * ItemsNotTransferable => ItemsNonTransferable * Update frame/nfts/src/features/roles.rs Co-authored-by: Muharem Ismailov * Address PR comments * Add missed comment Co-authored-by: command-bot <> Co-authored-by: Muharem Ismailov * Fix copy * Remove storage_prefix * Remove transactional * Update comment * [Uniques V2] Minting options (#12483) * Basics * WIP: change the data format * Refactor * Remove redundant new() method * Rename settings * Enable tests * Chore * Change params order * Delete the config on collection removal * Chore * Remove redundant system features * Rename force_item_status to force_collection_status * Update node runtime * Chore * Remove thaw_collection * Chore * Connect collection.is_frozen to config * Allow to lock the collection in a new way * Move free_holding into settings * Connect collection's metadata locker to feature flags * DRY * Chore * Connect pallet level feature flags * Prepare tests for the new changes * Implement Item settings * Allow to lock the metadata or attributes of an item * Common -> Settings * Extract settings related code to a separate file * Move feature flag checks inside the do_* methods * Split settings.rs into parts * Extract repeated code into macro * Extract macros into their own file * Chore * Fix traits * Fix traits * Test SystemFeatures * Fix benchmarks * Add missing benchmark * Fix node/runtime/lib.rs * ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts * Keep item's config on burn if it's not empty * Fix the merge artifacts * Fmt * Add SystemFeature::NoSwaps check * Rename SystemFeatures to PalletFeatures * Rename errors * Add docs * Change error message * Change the format of CollectionConfig to store more data * Move max supply to the CollectionConfig and allow to change it * Remove ItemConfig from the mint() function and use the one set in mint settings * Add different mint options * Allow to change the mint settings * Add a force_mint() method * Check mint params * Some optimisations * Cover with tests * Remove merge artifacts * Chore * Use the new has_role() method * Rework item deposits * More tests * Refactoring * Address comments * Refactor lock_collection() * Update frame/nfts/src/types.rs Co-authored-by: Squirrel * Update frame/nfts/src/types.rs Co-authored-by: Squirrel * Update frame/nfts/src/lib.rs Co-authored-by: Squirrel * Update frame/nfts/src/lib.rs Co-authored-by: Squirrel * Private => Issuer * Add more tests * Fix benchmarks * Add benchmarks for new methods * [Uniques v2] Refactoring (#12570) * Move do_set_price() and do_buy_item() to buy_sell.rs * Move approvals to feature file * Move metadata to feature files * Move the rest of methods to feature files * Remove artifacts * Split force_collection_status into 2 methods * Fix benchmarks * Fix benchmarks * Update deps Co-authored-by: command-bot <> Co-authored-by: Squirrel * [Uniques V2] Smart attributes (#12702) * Basics * WIP: change the data format * Refactor * Remove redundant new() method * Rename settings * Enable tests * Chore * Change params order * Delete the config on collection removal * Chore * Remove redundant system features * Rename force_item_status to force_collection_status * Update node runtime * Chore * Remove thaw_collection * Chore * Connect collection.is_frozen to config * Allow to lock the collection in a new way * Move free_holding into settings * Connect collection's metadata locker to feature flags * DRY * Chore * Connect pallet level feature flags * Prepare tests for the new changes * Implement Item settings * Allow to lock the metadata or attributes of an item * Common -> Settings * Extract settings related code to a separate file * Move feature flag checks inside the do_* methods * Split settings.rs into parts * Extract repeated code into macro * Extract macros into their own file * Chore * Fix traits * Fix traits * Test SystemFeatures * Fix benchmarks * Add missing benchmark * Fix node/runtime/lib.rs * ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts * Keep item's config on burn if it's not empty * Fix the merge artifacts * Fmt * Add SystemFeature::NoSwaps check * Rename SystemFeatures to PalletFeatures * Rename errors * Add docs * Change error message * Change the format of CollectionConfig to store more data * Move max supply to the CollectionConfig and allow to change it * Remove ItemConfig from the mint() function and use the one set in mint settings * Add different mint options * Allow to change the mint settings * Add a force_mint() method * Check mint params * Some optimisations * Cover with tests * Remove merge artifacts * Chore * Use the new has_role() method * Rework item deposits * More tests * Refactoring * Address comments * Refactor lock_collection() * Update frame/nfts/src/types.rs Co-authored-by: Squirrel * Update frame/nfts/src/types.rs Co-authored-by: Squirrel * Update frame/nfts/src/lib.rs Co-authored-by: Squirrel * Update frame/nfts/src/lib.rs Co-authored-by: Squirrel * Private => Issuer * Add more tests * Fix benchmarks * Add benchmarks for new methods * [Uniques v2] Refactoring (#12570) * Move do_set_price() and do_buy_item() to buy_sell.rs * Move approvals to feature file * Move metadata to feature files * Move the rest of methods to feature files * Remove artifacts * Smart attributes * Split force_collection_status into 2 methods * Fix benchmarks * Fix benchmarks * Update deps * Fix merge artifact * Weights + benchmarks + docs * Change params order * Chore * Update frame/nfts/src/lib.rs Co-authored-by: Squirrel * Update frame/nfts/src/lib.rs Co-authored-by: Squirrel * Update docs * Update frame/nfts/src/lib.rs Co-authored-by: Squirrel * Add PalletId * Chore * Add tests * More tests * Add doc * Update errors snapshots * Ensure we track the owner_deposit field correctly Co-authored-by: command-bot <> Co-authored-by: Squirrel * [Uniques V2] Final improvements (#12736) * Use KeyPrefixIterator instead of Box * Change create_collection() * Restrict from claiming NFTs twice * Update Readme * Remove dead code * Refactoring * Update readme * Fix clippy * Update frame/nfts/src/lib.rs Co-authored-by: Squirrel * ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts * Update docs * Typo * Fix benchmarks * Add more docs * DepositRequired setting should affect only the attributes within the CollectionOwner namespace * [NFTs] Implement missed methods to set the attributes from other pallets (#12919) * Implement missed methods to set the attributes from other pallets * Revert snapshots * Update snapshot * Update snapshot * Revert snapshot changes * Update snapshots * Yet another snapshot update.. * Update frame/support/src/traits/tokens/nonfungible_v2.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Update frame/support/src/traits/tokens/nonfungible_v2.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Update frame/support/src/traits/tokens/nonfungible_v2.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Update frame/support/src/traits/tokens/nonfungibles_v2.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Update frame/nfts/src/lib.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Update frame/nfts/src/lib.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Update frame/nfts/src/lib.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Update frame/nfts/src/lib.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Update frame/nfts/src/lib.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Update frame/support/src/traits/tokens/nonfungible_v2.rs * Update frame/nfts/src/lib.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Update frame/support/src/traits/tokens/nonfungibles_v2.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Update frame/nfts/src/lib.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Update frame/nfts/src/lib.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Update frame/nfts/src/lib.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Update frame/nfts/src/lib.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Address comments * [NFTs] Add the new `owner` param to mint() method (#12997) * Add the new `owner` param to mint() method * Fmt * Address comments * ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts * Update frame/nfts/src/common_functions.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Update frame/nfts/src/types.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Update frame/nfts/src/types.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Update frame/nfts/src/types.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Update frame/nfts/src/types.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Update frame/nfts/src/types.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Add call indexes * Update snapshots Co-authored-by: Sergej Sakac <73715684+Szegoo@users.noreply.github.com> Co-authored-by: Squirrel Co-authored-by: Keith Yeung Co-authored-by: Oliver Tale-Yazdi Co-authored-by: Muharem Ismailov Co-authored-by: command-bot <> Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> --- Cargo.lock | 23 +- Cargo.toml | 1 + bin/node/runtime/Cargo.toml | 4 + bin/node/runtime/src/lib.rs | 38 + frame/nfts/Cargo.toml | 49 + frame/nfts/README.md | 106 + frame/nfts/src/benchmarking.rs | 718 +++++ frame/nfts/src/common_functions.rs | 42 + frame/nfts/src/features/approvals.rs | 132 + frame/nfts/src/features/atomic_swap.rs | 184 ++ frame/nfts/src/features/attributes.rs | 323 +++ frame/nfts/src/features/buy_sell.rs | 130 + .../src/features/create_delete_collection.rs | 118 + frame/nfts/src/features/create_delete_item.rs | 126 + frame/nfts/src/features/lock.rs | 120 + frame/nfts/src/features/metadata.rs | 173 ++ frame/nfts/src/features/mod.rs | 28 + frame/nfts/src/features/roles.rs | 99 + frame/nfts/src/features/settings.rs | 103 + frame/nfts/src/features/transfer.rs | 166 ++ frame/nfts/src/impl_nonfungibles.rs | 289 ++ frame/nfts/src/lib.rs | 1769 ++++++++++++ frame/nfts/src/macros.rs | 74 + frame/nfts/src/mock.rs | 123 + frame/nfts/src/tests.rs | 2484 +++++++++++++++++ frame/nfts/src/types.rs | 465 +++ frame/nfts/src/weights.rs | 851 ++++++ frame/support/src/traits/tokens.rs | 6 +- frame/support/src/traits/tokens/misc.rs | 15 + .../src/traits/tokens/nonfungible_v2.rs | 248 ++ .../src/traits/tokens/nonfungibles_v2.rs | 257 ++ ...ev_mode_without_arg_max_encoded_len.stderr | 2 +- ...age_ensure_span_are_ok_on_wrong_gen.stderr | 6 +- ...re_span_are_ok_on_wrong_gen_unnamed.stderr | 6 +- .../pallet_ui/storage_info_unsatisfied.stderr | 2 +- .../storage_info_unsatisfied_nmap.stderr | 2 +- 36 files changed, 9269 insertions(+), 13 deletions(-) create mode 100644 frame/nfts/Cargo.toml create mode 100644 frame/nfts/README.md create mode 100644 frame/nfts/src/benchmarking.rs create mode 100644 frame/nfts/src/common_functions.rs create mode 100644 frame/nfts/src/features/approvals.rs create mode 100644 frame/nfts/src/features/atomic_swap.rs create mode 100644 frame/nfts/src/features/attributes.rs create mode 100644 frame/nfts/src/features/buy_sell.rs create mode 100644 frame/nfts/src/features/create_delete_collection.rs create mode 100644 frame/nfts/src/features/create_delete_item.rs create mode 100644 frame/nfts/src/features/lock.rs create mode 100644 frame/nfts/src/features/metadata.rs create mode 100644 frame/nfts/src/features/mod.rs create mode 100644 frame/nfts/src/features/roles.rs create mode 100644 frame/nfts/src/features/settings.rs create mode 100644 frame/nfts/src/features/transfer.rs create mode 100644 frame/nfts/src/impl_nonfungibles.rs create mode 100644 frame/nfts/src/lib.rs create mode 100644 frame/nfts/src/macros.rs create mode 100644 frame/nfts/src/mock.rs create mode 100644 frame/nfts/src/tests.rs create mode 100644 frame/nfts/src/types.rs create mode 100644 frame/nfts/src/weights.rs create mode 100644 frame/support/src/traits/tokens/nonfungible_v2.rs create mode 100644 frame/support/src/traits/tokens/nonfungibles_v2.rs diff --git a/Cargo.lock b/Cargo.lock index c4bd2532e8..19abb44e5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1640,9 +1640,9 @@ dependencies = [ [[package]] name = "enumflags2" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b3ab37dc79652c9d85f1f7b6070d77d321d2467f5fe7b00d6b7a86c57b092ae" +checksum = "e75d4cd21b95383444831539909fbb14b9dc3fdceb2a6f5d36577329a1f55ccb" dependencies = [ "enumflags2_derive", ] @@ -3093,6 +3093,7 @@ dependencies = [ "pallet-message-queue", "pallet-mmr", "pallet-multisig", + "pallet-nfts", "pallet-nis", "pallet-nomination-pools", "pallet-nomination-pools-benchmarking", @@ -5378,6 +5379,24 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-nfts" +version = "4.0.0-dev" +dependencies = [ + "enumflags2", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-nicks" version = "4.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index eb78d5e104..8f55d8e527 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -122,6 +122,7 @@ members = [ "frame/preimage", "frame/proxy", "frame/message-queue", + "frame/nfts", "frame/nomination-pools", "frame/nomination-pools/fuzzer", "frame/nomination-pools/benchmarking", diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 477545c9ac..201e3a85f8 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -78,6 +78,7 @@ pallet-membership = { version = "4.0.0-dev", default-features = false, path = ". pallet-message-queue = { version = "7.0.0-dev", default-features = false, path = "../../../frame/message-queue" } pallet-mmr = { version = "4.0.0-dev", default-features = false, path = "../../../frame/merkle-mountain-range" } pallet-multisig = { version = "4.0.0-dev", default-features = false, path = "../../../frame/multisig" } +pallet-nfts = { version = "4.0.0-dev", default-features = false, path = "../../../frame/nfts" } pallet-nomination-pools = { version = "1.0.0", default-features = false, path = "../../../frame/nomination-pools"} pallet-nomination-pools-benchmarking = { version = "1.0.0", default-features = false, optional = true, path = "../../../frame/nomination-pools/benchmarking" } pallet-nomination-pools-runtime-api = { version = "1.0.0-dev", default-features = false, path = "../../../frame/nomination-pools/runtime-api" } @@ -197,6 +198,7 @@ std = [ "pallet-root-testing/std", "pallet-recovery/std", "pallet-uniques/std", + "pallet-nfts/std", "pallet-vesting/std", "log/std", "frame-try-runtime?/std", @@ -253,6 +255,7 @@ runtime-benchmarks = [ "pallet-treasury/runtime-benchmarks", "pallet-utility/runtime-benchmarks", "pallet-uniques/runtime-benchmarks", + "pallet-nfts/runtime-benchmarks", "pallet-vesting/runtime-benchmarks", "pallet-whitelist/runtime-benchmarks", "frame-system-benchmarking/runtime-benchmarks", @@ -312,6 +315,7 @@ try-runtime = [ "pallet-asset-tx-payment/try-runtime", "pallet-transaction-storage/try-runtime", "pallet-uniques/try-runtime", + "pallet-nfts/try-runtime", "pallet-vesting/try-runtime", "pallet-whitelist/try-runtime", ] diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index e5776e3fd6..3e2701b227 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -56,6 +56,7 @@ use pallet_grandpa::{ fg_primitives, AuthorityId as GrandpaId, AuthorityList as GrandpaAuthorityList, }; use pallet_im_online::sr25519::AuthorityId as ImOnlineId; +use pallet_nfts::PalletFeatures; use pallet_nis::WithMaximumOf; use pallet_session::historical::{self as pallet_session_historical}; pub use pallet_transaction_payment::{CurrencyAdapter, Multiplier, TargetedFeeAdjustment}; @@ -301,6 +302,7 @@ impl InstanceFilter for ProxyType { RuntimeCall::Balances(..) | RuntimeCall::Assets(..) | RuntimeCall::Uniques(..) | + RuntimeCall::Nfts(..) | RuntimeCall::Vesting(pallet_vesting::Call::vested_transfer { .. }) | RuntimeCall::Indices(pallet_indices::Call::transfer { .. }) ), @@ -1528,6 +1530,10 @@ parameter_types! { pub const ItemDeposit: Balance = 1 * DOLLARS; pub const KeyLimit: u32 = 32; pub const ValueLimit: u32 = 256; + pub const ApprovalsLimit: u32 = 20; + pub const ItemAttributesApprovalsLimit: u32 = 20; + pub const MaxTips: u32 = 10; + pub const MaxDeadlineDuration: BlockNumber = 12 * 30 * DAYS; } impl pallet_uniques::Config for Runtime { @@ -1551,6 +1557,36 @@ impl pallet_uniques::Config for Runtime { type Locker = (); } +parameter_types! { + pub Features: PalletFeatures = PalletFeatures::all_enabled(); +} + +impl pallet_nfts::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type CollectionId = u32; + type ItemId = u32; + type Currency = Balances; + type ForceOrigin = frame_system::EnsureRoot; + type CollectionDeposit = CollectionDeposit; + type ItemDeposit = ItemDeposit; + type MetadataDepositBase = MetadataDepositBase; + type AttributeDepositBase = MetadataDepositBase; + type DepositPerByte = MetadataDepositPerByte; + type StringLimit = StringLimit; + type KeyLimit = KeyLimit; + type ValueLimit = ValueLimit; + type ApprovalsLimit = ApprovalsLimit; + type ItemAttributesApprovalsLimit = ItemAttributesApprovalsLimit; + type MaxTips = MaxTips; + type MaxDeadlineDuration = MaxDeadlineDuration; + type Features = Features; + type WeightInfo = pallet_nfts::weights::SubstrateWeight; + #[cfg(feature = "runtime-benchmarks")] + type Helper = (); + type CreateOrigin = AsEnsureOriginWithArg>; + type Locker = (); +} + impl pallet_transaction_storage::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Currency = Balances; @@ -1705,6 +1741,7 @@ construct_runtime!( Lottery: pallet_lottery, Nis: pallet_nis, Uniques: pallet_uniques, + Nfts: pallet_nfts, TransactionStorage: pallet_transaction_storage, VoterList: pallet_bags_list::, StateTrieMigration: pallet_state_trie_migration, @@ -1836,6 +1873,7 @@ mod benches { [pallet_transaction_storage, TransactionStorage] [pallet_treasury, Treasury] [pallet_uniques, Uniques] + [pallet_nfts, Nfts] [pallet_utility, Utility] [pallet_vesting, Vesting] [pallet_whitelist, Whitelist] diff --git a/frame/nfts/Cargo.toml b/frame/nfts/Cargo.toml new file mode 100644 index 0000000000..109dffdd10 --- /dev/null +++ b/frame/nfts/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "pallet-nfts" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME NFTs pallet" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +enumflags2 = { version = "0.7.5" } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } + +[dev-dependencies] +pallet-balances = { version = "4.0.0-dev", path = "../balances" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } +sp-std = { version = "5.0.0", path = "../../primitives/std" } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "log/std", + "scale-info/std", + "sp-runtime/std", + "sp-std/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/nfts/README.md b/frame/nfts/README.md new file mode 100644 index 0000000000..7de4b9440e --- /dev/null +++ b/frame/nfts/README.md @@ -0,0 +1,106 @@ +# NFTs pallet + +A pallet for dealing with non-fungible assets. + +## Overview + +The NFTs pallet provides functionality for non-fungible tokens' management, including: + +* Collection Creation +* NFT Minting +* NFT Transfers and Atomic Swaps +* NFT Trading methods +* Attributes Management +* NFT Burning + +To use it in your runtime, you need to implement [`nfts::Config`](https://paritytech.github.io/substrate/master/pallet_nfts/pallet/trait.Config.html). + +The supported dispatchable functions are documented in the [`nfts::Call`](https://paritytech.github.io/substrate/master/pallet_nfts/pallet/enum.Call.html) enum. + +### Terminology + +* **Collection creation:** The creation of a new collection. +* **NFT minting:** The action of creating a new item within a collection. +* **NFT transfer:** The action of sending an item from one account to another. +* **Atomic swap:** The action of exchanging items between accounts without needing a 3rd party service. +* **NFT burning:** The destruction of an item. +* **Non-fungible token (NFT):** An item for which each unit has unique characteristics. There is exactly + one instance of such an item in existence and there is exactly one owning account (though that owning account could be a proxy account or multi-sig account). +* **Soul Bound NFT:** An item that is non-transferable from the account which it is minted into. + +### Goals + +The NFTs pallet in Substrate is designed to make the following possible: + +* Allow accounts to permissionlessly create nft collections. +* Allow a named (permissioned) account to mint and burn unique items within a collection. +* Move items between accounts permissionlessly. +* Allow a named (permissioned) account to freeze and unfreeze items within a + collection or the entire collection. +* Allow the owner of an item to delegate the ability to transfer the item to some + named third-party. +* Allow third-parties to store information in an NFT _without_ owning it (Eg. save game state). + +## Interface + +### Permissionless dispatchables + +* `create`: Create a new collection by placing a deposit. +* `mint`: Mint a new item within a collection (when the minting is public). +* `transfer`: Send an item to a new owner. +* `redeposit`: Update the deposit amount of an item, potentially freeing funds. +* `approve_transfer`: Name a delegate who may authorize a transfer. +* `cancel_approval`: Revert the effects of a previous `approve_transfer`. +* `approve_item_attributes`: Name a delegate who may change item's attributes within a namespace. +* `cancel_item_attributes_approval`: Revert the effects of a previous `approve_item_attributes`. +* `set_price`: Set the price for an item. +* `buy_item`: Buy an item. +* `pay_tips`: Pay tips, could be used for paying the creator royalties. +* `create_swap`: Create an offer to swap an NFT for another NFT and optionally some fungibles. +* `cancel_swap`: Cancel previously created swap offer. +* `claim_swap`: Swap items in an atomic way. + + +### Permissioned dispatchables + +* `destroy`: Destroy a collection. This destroys all the items inside the collection and refunds the deposit. +* `force_mint`: Mint a new item within a collection. +* `burn`: Destroy an item within a collection. +* `lock_item_transfer`: Prevent an individual item from being transferred. +* `unlock_item_transfer`: Revert the effects of a previous `lock_item_transfer`. +* `clear_all_transfer_approvals`: Clears all transfer approvals set by calling the `approve_transfer`. +* `lock_collection`: Prevent all items within a collection from being transferred (making them all `soul bound`). +* `lock_item_properties`: Lock item's metadata or attributes. +* `transfer_ownership`: Alter the owner of a collection, moving all associated deposits. (Ownership of individual items will not be affected.) +* `set_team`: Alter the permissioned accounts of a collection. +* `set_collection_max_supply`: Change the max supply of a collection. +* `update_mint_settings`: Update the minting settings for collection. + + +### Metadata (permissioned) dispatchables + +* `set_attribute`: Set a metadata attribute of an item or collection. +* `clear_attribute`: Remove a metadata attribute of an item or collection. +* `set_metadata`: Set general metadata of an item (E.g. an IPFS address of an image url). +* `clear_metadata`: Remove general metadata of an item. +* `set_collection_metadata`: Set general metadata of a collection. +* `clear_collection_metadata`: Remove general metadata of a collection. + + +### Force (i.e. governance) dispatchables + +* `force_create`: Create a new collection (the collection id can not be chosen). +* `force_collection_owner`: Change collection's owner. +* `force_collection_config`: Change collection's config. +* `force_set_attribute`: Set an attribute. + +Please refer to the [`Call`](https://paritytech.github.io/substrate/master/pallet_nfts/pallet/enum.Call.html) enum +and its associated variants for documentation on each function. + +## Related Modules + +* [`System`](https://docs.rs/frame-system/latest/frame_system/) +* [`Support`](https://docs.rs/frame-support/latest/frame_support/) +* [`Assets`](https://docs.rs/pallet-assets/latest/pallet_assets/) + +License: Apache-2.0 diff --git a/frame/nfts/src/benchmarking.rs b/frame/nfts/src/benchmarking.rs new file mode 100644 index 0000000000..6517445da6 --- /dev/null +++ b/frame/nfts/src/benchmarking.rs @@ -0,0 +1,718 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 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. + +//! Nfts pallet benchmarking. + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; +use enumflags2::{BitFlag, BitFlags}; +use frame_benchmarking::{ + account, benchmarks_instance_pallet, whitelist_account, whitelisted_caller, +}; +use frame_support::{ + assert_ok, + dispatch::UnfilteredDispatchable, + traits::{EnsureOrigin, Get}, + BoundedVec, +}; +use frame_system::RawOrigin as SystemOrigin; +use sp_runtime::traits::{Bounded, One}; +use sp_std::prelude::*; + +use crate::Pallet as Nfts; + +const SEED: u32 = 0; + +fn create_collection, I: 'static>( +) -> (T::CollectionId, T::AccountId, AccountIdLookupOf) { + let caller: T::AccountId = whitelisted_caller(); + let caller_lookup = T::Lookup::unlookup(caller.clone()); + let collection = T::Helper::collection(0); + T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); + assert_ok!(Nfts::::force_create( + SystemOrigin::Root.into(), + caller_lookup.clone(), + default_collection_config::() + )); + (collection, caller, caller_lookup) +} + +fn add_collection_metadata, I: 'static>() -> (T::AccountId, AccountIdLookupOf) { + let caller = Collection::::get(T::Helper::collection(0)).unwrap().owner; + if caller != whitelisted_caller() { + whitelist_account!(caller); + } + let caller_lookup = T::Lookup::unlookup(caller.clone()); + assert_ok!(Nfts::::set_collection_metadata( + SystemOrigin::Signed(caller.clone()).into(), + T::Helper::collection(0), + vec![0; T::StringLimit::get() as usize].try_into().unwrap(), + )); + (caller, caller_lookup) +} + +fn mint_item, I: 'static>( + index: u16, +) -> (T::ItemId, T::AccountId, AccountIdLookupOf) { + let caller = Collection::::get(T::Helper::collection(0)).unwrap().owner; + if caller != whitelisted_caller() { + whitelist_account!(caller); + } + let caller_lookup = T::Lookup::unlookup(caller.clone()); + let item = T::Helper::item(index); + assert_ok!(Nfts::::mint( + SystemOrigin::Signed(caller.clone()).into(), + T::Helper::collection(0), + item, + caller_lookup.clone(), + None, + )); + (item, caller, caller_lookup) +} + +fn add_item_metadata, I: 'static>( + item: T::ItemId, +) -> (T::AccountId, AccountIdLookupOf) { + let caller = Collection::::get(T::Helper::collection(0)).unwrap().owner; + if caller != whitelisted_caller() { + whitelist_account!(caller); + } + let caller_lookup = T::Lookup::unlookup(caller.clone()); + assert_ok!(Nfts::::set_metadata( + SystemOrigin::Signed(caller.clone()).into(), + T::Helper::collection(0), + item, + vec![0; T::StringLimit::get() as usize].try_into().unwrap(), + )); + (caller, caller_lookup) +} + +fn add_item_attribute, I: 'static>( + item: T::ItemId, +) -> (BoundedVec, T::AccountId, AccountIdLookupOf) { + let caller = Collection::::get(T::Helper::collection(0)).unwrap().owner; + if caller != whitelisted_caller() { + whitelist_account!(caller); + } + let caller_lookup = T::Lookup::unlookup(caller.clone()); + let key: BoundedVec<_, _> = vec![0; T::KeyLimit::get() as usize].try_into().unwrap(); + assert_ok!(Nfts::::set_attribute( + SystemOrigin::Signed(caller.clone()).into(), + T::Helper::collection(0), + Some(item), + AttributeNamespace::CollectionOwner, + key.clone(), + vec![0; T::ValueLimit::get() as usize].try_into().unwrap(), + )); + (key, caller, caller_lookup) +} + +fn assert_last_event, I: 'static>(generic_event: >::RuntimeEvent) { + let events = frame_system::Pallet::::events(); + let system_event: ::RuntimeEvent = generic_event.into(); + // compare to the last event record + let frame_system::EventRecord { event, .. } = &events[events.len() - 1]; + assert_eq!(event, &system_event); +} + +fn make_collection_config, I: 'static>( + disable_settings: BitFlags, +) -> CollectionConfigFor { + CollectionConfig { + settings: CollectionSettings::from_disabled(disable_settings), + max_supply: None, + mint_settings: MintSettings::default(), + } +} + +fn default_collection_config, I: 'static>() -> CollectionConfigFor { + make_collection_config::(CollectionSetting::empty()) +} + +fn default_item_config() -> ItemConfig { + ItemConfig { settings: ItemSettings::all_enabled() } +} + +benchmarks_instance_pallet! { + create { + let collection = T::Helper::collection(0); + let origin = T::CreateOrigin::successful_origin(&collection); + let caller = T::CreateOrigin::ensure_origin(origin.clone(), &collection).unwrap(); + whitelist_account!(caller); + let admin = T::Lookup::unlookup(caller.clone()); + T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); + let call = Call::::create { admin, config: default_collection_config::() }; + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert_last_event::(Event::Created { collection: T::Helper::collection(0), creator: caller.clone(), owner: caller }.into()); + } + + force_create { + let caller: T::AccountId = whitelisted_caller(); + let caller_lookup = T::Lookup::unlookup(caller.clone()); + }: _(SystemOrigin::Root, caller_lookup, default_collection_config::()) + verify { + assert_last_event::(Event::ForceCreated { collection: T::Helper::collection(0), owner: caller }.into()); + } + + destroy { + let n in 0 .. 1_000; + let m in 0 .. 1_000; + let a in 0 .. 1_000; + + let (collection, caller, _) = create_collection::(); + add_collection_metadata::(); + for i in 0..n { + mint_item::(i as u16); + } + for i in 0..m { + if !Item::::contains_key(collection, T::Helper::item(i as u16)) { + mint_item::(i as u16); + } + add_item_metadata::(T::Helper::item(i as u16)); + } + for i in 0..a { + if !Item::::contains_key(collection, T::Helper::item(i as u16)) { + mint_item::(i as u16); + } + add_item_attribute::(T::Helper::item(i as u16)); + } + let witness = Collection::::get(collection).unwrap().destroy_witness(); + }: _(SystemOrigin::Signed(caller), collection, witness) + verify { + assert_last_event::(Event::Destroyed { collection }.into()); + } + + mint { + let (collection, caller, caller_lookup) = create_collection::(); + let item = T::Helper::item(0); + }: _(SystemOrigin::Signed(caller.clone()), collection, item, caller_lookup, None) + verify { + assert_last_event::(Event::Issued { collection, item, owner: caller }.into()); + } + + force_mint { + let (collection, caller, caller_lookup) = create_collection::(); + let item = T::Helper::item(0); + }: _(SystemOrigin::Signed(caller.clone()), collection, item, caller_lookup, default_item_config()) + verify { + assert_last_event::(Event::Issued { collection, item, owner: caller }.into()); + } + + burn { + let (collection, caller, caller_lookup) = create_collection::(); + let (item, ..) = mint_item::(0); + }: _(SystemOrigin::Signed(caller.clone()), collection, item, Some(caller_lookup)) + verify { + assert_last_event::(Event::Burned { collection, item, owner: caller }.into()); + } + + transfer { + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + + let target: T::AccountId = account("target", 0, SEED); + let target_lookup = T::Lookup::unlookup(target.clone()); + T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance()); + }: _(SystemOrigin::Signed(caller.clone()), collection, item, target_lookup) + verify { + assert_last_event::(Event::Transferred { collection, item, from: caller, to: target }.into()); + } + + redeposit { + let i in 0 .. 5_000; + let (collection, caller, _) = create_collection::(); + let items = (0..i).map(|x| mint_item::(x as u16).0).collect::>(); + Nfts::::force_collection_config( + SystemOrigin::Root.into(), + collection, + make_collection_config::(CollectionSetting::DepositRequired.into()), + )?; + }: _(SystemOrigin::Signed(caller.clone()), collection, items.clone()) + verify { + assert_last_event::(Event::Redeposited { collection, successful_items: items }.into()); + } + + lock_item_transfer { + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + }: _(SystemOrigin::Signed(caller.clone()), T::Helper::collection(0), T::Helper::item(0)) + verify { + assert_last_event::(Event::ItemTransferLocked { collection: T::Helper::collection(0), item: T::Helper::item(0) }.into()); + } + + unlock_item_transfer { + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + Nfts::::lock_item_transfer( + SystemOrigin::Signed(caller.clone()).into(), + collection, + item, + )?; + }: _(SystemOrigin::Signed(caller.clone()), collection, item) + verify { + assert_last_event::(Event::ItemTransferUnlocked { collection, item }.into()); + } + + lock_collection { + let (collection, caller, _) = create_collection::(); + let lock_settings = CollectionSettings::from_disabled( + CollectionSetting::TransferableItems | + CollectionSetting::UnlockedMetadata | + CollectionSetting::UnlockedAttributes | + CollectionSetting::UnlockedMaxSupply, + ); + }: _(SystemOrigin::Signed(caller.clone()), collection, lock_settings) + verify { + assert_last_event::(Event::CollectionLocked { collection }.into()); + } + + transfer_ownership { + let (collection, caller, _) = create_collection::(); + let target: T::AccountId = account("target", 0, SEED); + let target_lookup = T::Lookup::unlookup(target.clone()); + T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance()); + let origin = SystemOrigin::Signed(target.clone()).into(); + Nfts::::set_accept_ownership(origin, Some(collection))?; + }: _(SystemOrigin::Signed(caller), collection, target_lookup) + verify { + assert_last_event::(Event::OwnerChanged { collection, new_owner: target }.into()); + } + + set_team { + let (collection, caller, _) = create_collection::(); + let target0 = T::Lookup::unlookup(account("target", 0, SEED)); + let target1 = T::Lookup::unlookup(account("target", 1, SEED)); + let target2 = T::Lookup::unlookup(account("target", 2, SEED)); + }: _(SystemOrigin::Signed(caller), collection, target0, target1, target2) + verify { + assert_last_event::(Event::TeamChanged{ + collection, + issuer: account("target", 0, SEED), + admin: account("target", 1, SEED), + freezer: account("target", 2, SEED), + }.into()); + } + + force_collection_owner { + let (collection, _, _) = create_collection::(); + let origin = T::ForceOrigin::successful_origin(); + let target: T::AccountId = account("target", 0, SEED); + let target_lookup = T::Lookup::unlookup(target.clone()); + T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance()); + let call = Call::::force_collection_owner { + collection, + owner: target_lookup, + }; + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert_last_event::(Event::OwnerChanged { collection, new_owner: target }.into()); + } + + force_collection_config { + let (collection, caller, _) = create_collection::(); + let origin = T::ForceOrigin::successful_origin(); + let call = Call::::force_collection_config { + collection, + config: make_collection_config::(CollectionSetting::DepositRequired.into()), + }; + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert_last_event::(Event::CollectionConfigChanged { collection }.into()); + } + + lock_item_properties { + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + let lock_metadata = true; + let lock_attributes = true; + }: _(SystemOrigin::Signed(caller), collection, item, lock_metadata, lock_attributes) + verify { + assert_last_event::(Event::ItemPropertiesLocked { collection, item, lock_metadata, lock_attributes }.into()); + } + + set_attribute { + let key: BoundedVec<_, _> = vec![0u8; T::KeyLimit::get() as usize].try_into().unwrap(); + let value: BoundedVec<_, _> = vec![0u8; T::ValueLimit::get() as usize].try_into().unwrap(); + + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + }: _(SystemOrigin::Signed(caller), collection, Some(item), AttributeNamespace::CollectionOwner, key.clone(), value.clone()) + verify { + assert_last_event::( + Event::AttributeSet { + collection, + maybe_item: Some(item), + namespace: AttributeNamespace::CollectionOwner, + key, + value, + } + .into(), + ); + } + + force_set_attribute { + let key: BoundedVec<_, _> = vec![0u8; T::KeyLimit::get() as usize].try_into().unwrap(); + let value: BoundedVec<_, _> = vec![0u8; T::ValueLimit::get() as usize].try_into().unwrap(); + + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + }: _(SystemOrigin::Root, Some(caller), collection, Some(item), AttributeNamespace::CollectionOwner, key.clone(), value.clone()) + verify { + assert_last_event::( + Event::AttributeSet { + collection, + maybe_item: Some(item), + namespace: AttributeNamespace::CollectionOwner, + key, + value, + } + .into(), + ); + } + + clear_attribute { + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + add_item_metadata::(item); + let (key, ..) = add_item_attribute::(item); + }: _(SystemOrigin::Signed(caller), collection, Some(item), AttributeNamespace::CollectionOwner, key.clone()) + verify { + assert_last_event::( + Event::AttributeCleared { + collection, + maybe_item: Some(item), + namespace: AttributeNamespace::CollectionOwner, + key, + }.into(), + ); + } + + approve_item_attributes { + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + let target: T::AccountId = account("target", 0, SEED); + let target_lookup = T::Lookup::unlookup(target.clone()); + }: _(SystemOrigin::Signed(caller), collection, item, target_lookup) + verify { + assert_last_event::( + Event::ItemAttributesApprovalAdded { + collection, + item, + delegate: target, + } + .into(), + ); + } + + cancel_item_attributes_approval { + let n in 0 .. 1_000; + + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + let target: T::AccountId = account("target", 0, SEED); + let target_lookup = T::Lookup::unlookup(target.clone()); + Nfts::::approve_item_attributes( + SystemOrigin::Signed(caller.clone()).into(), + collection, + item, + target_lookup.clone(), + )?; + T::Currency::make_free_balance_be(&target, DepositBalanceOf::::max_value()); + let value: BoundedVec<_, _> = vec![0u8; T::ValueLimit::get() as usize].try_into().unwrap(); + for i in 0..n { + let mut key = vec![0u8; T::KeyLimit::get() as usize]; + let mut s = Vec::from((i as u16).to_be_bytes()); + key.truncate(s.len()); + key.append(&mut s); + + Nfts::::set_attribute( + SystemOrigin::Signed(target.clone()).into(), + T::Helper::collection(0), + Some(item), + AttributeNamespace::Account(target.clone()), + key.try_into().unwrap(), + value.clone(), + )?; + } + let witness = CancelAttributesApprovalWitness { account_attributes: n }; + }: _(SystemOrigin::Signed(caller), collection, item, target_lookup, witness) + verify { + assert_last_event::( + Event::ItemAttributesApprovalRemoved { + collection, + item, + delegate: target, + } + .into(), + ); + } + + set_metadata { + let data: BoundedVec<_, _> = vec![0u8; T::StringLimit::get() as usize].try_into().unwrap(); + + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + }: _(SystemOrigin::Signed(caller), collection, item, data.clone()) + verify { + assert_last_event::(Event::ItemMetadataSet { collection, item, data }.into()); + } + + clear_metadata { + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + add_item_metadata::(item); + }: _(SystemOrigin::Signed(caller), collection, item) + verify { + assert_last_event::(Event::ItemMetadataCleared { collection, item }.into()); + } + + set_collection_metadata { + let data: BoundedVec<_, _> = vec![0u8; T::StringLimit::get() as usize].try_into().unwrap(); + + let (collection, caller, _) = create_collection::(); + }: _(SystemOrigin::Signed(caller), collection, data.clone()) + verify { + assert_last_event::(Event::CollectionMetadataSet { collection, data }.into()); + } + + clear_collection_metadata { + let (collection, caller, _) = create_collection::(); + add_collection_metadata::(); + }: _(SystemOrigin::Signed(caller), collection) + verify { + assert_last_event::(Event::CollectionMetadataCleared { collection }.into()); + } + + approve_transfer { + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + let delegate: T::AccountId = account("delegate", 0, SEED); + let delegate_lookup = T::Lookup::unlookup(delegate.clone()); + let deadline = T::BlockNumber::max_value(); + }: _(SystemOrigin::Signed(caller.clone()), collection, item, delegate_lookup, Some(deadline)) + verify { + assert_last_event::(Event::TransferApproved { collection, item, owner: caller, delegate, deadline: Some(deadline) }.into()); + } + + cancel_approval { + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + let delegate: T::AccountId = account("delegate", 0, SEED); + let delegate_lookup = T::Lookup::unlookup(delegate.clone()); + let origin = SystemOrigin::Signed(caller.clone()).into(); + let deadline = T::BlockNumber::max_value(); + Nfts::::approve_transfer(origin, collection, item, delegate_lookup.clone(), Some(deadline))?; + }: _(SystemOrigin::Signed(caller.clone()), collection, item, delegate_lookup) + verify { + assert_last_event::(Event::ApprovalCancelled { collection, item, owner: caller, delegate }.into()); + } + + clear_all_transfer_approvals { + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + let delegate: T::AccountId = account("delegate", 0, SEED); + let delegate_lookup = T::Lookup::unlookup(delegate.clone()); + let origin = SystemOrigin::Signed(caller.clone()).into(); + let deadline = T::BlockNumber::max_value(); + Nfts::::approve_transfer(origin, collection, item, delegate_lookup.clone(), Some(deadline))?; + }: _(SystemOrigin::Signed(caller.clone()), collection, item) + verify { + assert_last_event::(Event::AllApprovalsCancelled {collection, item, owner: caller}.into()); + } + + set_accept_ownership { + let caller: T::AccountId = whitelisted_caller(); + T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); + let collection = T::Helper::collection(0); + }: _(SystemOrigin::Signed(caller.clone()), Some(collection)) + verify { + assert_last_event::(Event::OwnershipAcceptanceChanged { + who: caller, + maybe_collection: Some(collection), + }.into()); + } + + set_collection_max_supply { + let (collection, caller, _) = create_collection::(); + }: _(SystemOrigin::Signed(caller.clone()), collection, u32::MAX) + verify { + assert_last_event::(Event::CollectionMaxSupplySet { + collection, + max_supply: u32::MAX, + }.into()); + } + + update_mint_settings { + let (collection, caller, _) = create_collection::(); + let mint_settings = MintSettings { + mint_type: MintType::HolderOf(T::Helper::collection(0)), + start_block: Some(One::one()), + end_block: Some(One::one()), + price: Some(ItemPrice::::from(1u32)), + default_item_settings: ItemSettings::all_enabled(), + }; + }: _(SystemOrigin::Signed(caller.clone()), collection, mint_settings) + verify { + assert_last_event::(Event::CollectionMintSettingsUpdated { collection }.into()); + } + + set_price { + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + let delegate: T::AccountId = account("delegate", 0, SEED); + let delegate_lookup = T::Lookup::unlookup(delegate.clone()); + let price = ItemPrice::::from(100u32); + }: _(SystemOrigin::Signed(caller.clone()), collection, item, Some(price), Some(delegate_lookup)) + verify { + assert_last_event::(Event::ItemPriceSet { + collection, + item, + price, + whitelisted_buyer: Some(delegate), + }.into()); + } + + buy_item { + let (collection, seller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + let buyer: T::AccountId = account("buyer", 0, SEED); + let buyer_lookup = T::Lookup::unlookup(buyer.clone()); + let price = ItemPrice::::from(0u32); + let origin = SystemOrigin::Signed(seller.clone()).into(); + Nfts::::set_price(origin, collection, item, Some(price.clone()), Some(buyer_lookup))?; + T::Currency::make_free_balance_be(&buyer, DepositBalanceOf::::max_value()); + }: _(SystemOrigin::Signed(buyer.clone()), collection, item, price.clone()) + verify { + assert_last_event::(Event::ItemBought { + collection, + item, + price, + seller, + buyer, + }.into()); + } + + pay_tips { + let n in 0 .. T::MaxTips::get() as u32; + let amount = BalanceOf::::from(100u32); + let caller: T::AccountId = whitelisted_caller(); + let collection = T::Helper::collection(0); + let item = T::Helper::item(0); + let tips: BoundedVec<_, _> = vec![ + ItemTip + { collection, item, receiver: caller.clone(), amount }; n as usize + ].try_into().unwrap(); + }: _(SystemOrigin::Signed(caller.clone()), tips) + verify { + if !n.is_zero() { + assert_last_event::(Event::TipSent { + collection, + item, + sender: caller.clone(), + receiver: caller.clone(), + amount, + }.into()); + } + } + + create_swap { + let (collection, caller, _) = create_collection::(); + let (item1, ..) = mint_item::(0); + let (item2, ..) = mint_item::(1); + let price = ItemPrice::::from(100u32); + let price_direction = PriceDirection::Receive; + let price_with_direction = PriceWithDirection { amount: price, direction: price_direction }; + let duration = T::MaxDeadlineDuration::get(); + frame_system::Pallet::::set_block_number(One::one()); + }: _(SystemOrigin::Signed(caller.clone()), collection, item1, collection, Some(item2), Some(price_with_direction.clone()), duration) + verify { + let current_block = frame_system::Pallet::::block_number(); + assert_last_event::(Event::SwapCreated { + offered_collection: collection, + offered_item: item1, + desired_collection: collection, + desired_item: Some(item2), + price: Some(price_with_direction), + deadline: current_block.saturating_add(duration), + }.into()); + } + + cancel_swap { + let (collection, caller, _) = create_collection::(); + let (item1, ..) = mint_item::(0); + let (item2, ..) = mint_item::(1); + let price = ItemPrice::::from(100u32); + let origin = SystemOrigin::Signed(caller.clone()).into(); + let duration = T::MaxDeadlineDuration::get(); + let price_direction = PriceDirection::Receive; + let price_with_direction = PriceWithDirection { amount: price, direction: price_direction }; + frame_system::Pallet::::set_block_number(One::one()); + Nfts::::create_swap(origin, collection, item1, collection, Some(item2), Some(price_with_direction.clone()), duration)?; + }: _(SystemOrigin::Signed(caller.clone()), collection, item1) + verify { + assert_last_event::(Event::SwapCancelled { + offered_collection: collection, + offered_item: item1, + desired_collection: collection, + desired_item: Some(item2), + price: Some(price_with_direction), + deadline: duration.saturating_add(One::one()), + }.into()); + } + + claim_swap { + let (collection, caller, _) = create_collection::(); + let (item1, ..) = mint_item::(0); + let (item2, ..) = mint_item::(1); + let price = ItemPrice::::from(0u32); + let price_direction = PriceDirection::Receive; + let price_with_direction = PriceWithDirection { amount: price, direction: price_direction }; + let duration = T::MaxDeadlineDuration::get(); + let target: T::AccountId = account("target", 0, SEED); + let target_lookup = T::Lookup::unlookup(target.clone()); + T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance()); + let origin = SystemOrigin::Signed(caller.clone()); + frame_system::Pallet::::set_block_number(One::one()); + Nfts::::transfer(origin.clone().into(), collection, item2, target_lookup)?; + Nfts::::create_swap( + origin.clone().into(), + collection, + item1, + collection, + Some(item2), + Some(price_with_direction.clone()), + duration, + )?; + }: _(SystemOrigin::Signed(target.clone()), collection, item2, collection, item1, Some(price_with_direction.clone())) + verify { + let current_block = frame_system::Pallet::::block_number(); + assert_last_event::(Event::SwapClaimed { + sent_collection: collection, + sent_item: item2, + sent_item_owner: target, + received_collection: collection, + received_item: item1, + received_item_owner: caller, + price: Some(price_with_direction), + deadline: duration.saturating_add(One::one()), + }.into()); + } + + impl_benchmark_test_suite!(Nfts, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/frame/nfts/src/common_functions.rs b/frame/nfts/src/common_functions.rs new file mode 100644 index 0000000000..9c0faeb6b7 --- /dev/null +++ b/frame/nfts/src/common_functions.rs @@ -0,0 +1,42 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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. + +//! Various pieces of common functionality. + +use super::*; + +impl, I: 'static> Pallet { + /// Get the owner of the item, if the item exists. + pub fn owner(collection: T::CollectionId, item: T::ItemId) -> Option { + Item::::get(collection, item).map(|i| i.owner) + } + + /// Get the owner of the collection, if the collection exists. + pub fn collection_owner(collection: T::CollectionId) -> Option { + Collection::::get(collection).map(|i| i.owner) + } + + #[cfg(any(test, feature = "runtime-benchmarks"))] + pub fn set_next_id(id: T::CollectionId) { + NextCollectionId::::set(Some(id)); + } + + #[cfg(test)] + pub fn get_next_id() -> T::CollectionId { + NextCollectionId::::get().unwrap_or(T::CollectionId::initial_value()) + } +} diff --git a/frame/nfts/src/features/approvals.rs b/frame/nfts/src/features/approvals.rs new file mode 100644 index 0000000000..cb5279fd94 --- /dev/null +++ b/frame/nfts/src/features/approvals.rs @@ -0,0 +1,132 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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::*; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + pub(crate) fn do_approve_transfer( + maybe_check_origin: Option, + collection: T::CollectionId, + item: T::ItemId, + delegate: T::AccountId, + maybe_deadline: Option<::BlockNumber>, + ) -> DispatchResult { + ensure!( + Self::is_pallet_feature_enabled(PalletFeature::Approvals), + Error::::MethodDisabled + ); + let mut details = + Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + collection_config.is_setting_enabled(CollectionSetting::TransferableItems), + Error::::ItemsNonTransferable + ); + + if let Some(check_origin) = maybe_check_origin { + let is_admin = Self::has_role(&collection, &check_origin, CollectionRole::Admin); + let permitted = is_admin || check_origin == details.owner; + ensure!(permitted, Error::::NoPermission); + } + + let now = frame_system::Pallet::::block_number(); + let deadline = maybe_deadline.map(|d| d.saturating_add(now)); + + details + .approvals + .try_insert(delegate.clone(), deadline) + .map_err(|_| Error::::ReachedApprovalLimit)?; + Item::::insert(&collection, &item, &details); + + Self::deposit_event(Event::TransferApproved { + collection, + item, + owner: details.owner, + delegate, + deadline, + }); + + Ok(()) + } + + pub(crate) fn do_cancel_approval( + maybe_check_origin: Option, + collection: T::CollectionId, + item: T::ItemId, + delegate: T::AccountId, + ) -> DispatchResult { + let mut details = + Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + + let maybe_deadline = details.approvals.get(&delegate).ok_or(Error::::NotDelegate)?; + + let is_past_deadline = if let Some(deadline) = maybe_deadline { + let now = frame_system::Pallet::::block_number(); + now > *deadline + } else { + false + }; + + if !is_past_deadline { + if let Some(check_origin) = maybe_check_origin { + let is_admin = Self::has_role(&collection, &check_origin, CollectionRole::Admin); + let permitted = is_admin || check_origin == details.owner; + ensure!(permitted, Error::::NoPermission); + } + } + + details.approvals.remove(&delegate); + Item::::insert(&collection, &item, &details); + + Self::deposit_event(Event::ApprovalCancelled { + collection, + item, + owner: details.owner, + delegate, + }); + + Ok(()) + } + + pub(crate) fn do_clear_all_transfer_approvals( + maybe_check_origin: Option, + collection: T::CollectionId, + item: T::ItemId, + ) -> DispatchResult { + let mut details = + Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; + + if let Some(check_origin) = maybe_check_origin { + let is_admin = Self::has_role(&collection, &check_origin, CollectionRole::Admin); + let permitted = is_admin || check_origin == details.owner; + ensure!(permitted, Error::::NoPermission); + } + + details.approvals.clear(); + Item::::insert(&collection, &item, &details); + + Self::deposit_event(Event::AllApprovalsCancelled { + collection, + item, + owner: details.owner, + }); + + Ok(()) + } +} diff --git a/frame/nfts/src/features/atomic_swap.rs b/frame/nfts/src/features/atomic_swap.rs new file mode 100644 index 0000000000..bacaccdaed --- /dev/null +++ b/frame/nfts/src/features/atomic_swap.rs @@ -0,0 +1,184 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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::*; +use frame_support::{ + pallet_prelude::*, + traits::{Currency, ExistenceRequirement::KeepAlive}, +}; + +impl, I: 'static> Pallet { + pub(crate) fn do_create_swap( + caller: T::AccountId, + offered_collection_id: T::CollectionId, + offered_item_id: T::ItemId, + desired_collection_id: T::CollectionId, + maybe_desired_item_id: Option, + maybe_price: Option>>, + duration: ::BlockNumber, + ) -> DispatchResult { + ensure!( + Self::is_pallet_feature_enabled(PalletFeature::Swaps), + Error::::MethodDisabled + ); + ensure!(duration <= T::MaxDeadlineDuration::get(), Error::::WrongDuration); + + let item = Item::::get(&offered_collection_id, &offered_item_id) + .ok_or(Error::::UnknownItem)?; + ensure!(item.owner == caller, Error::::NoPermission); + + match maybe_desired_item_id { + Some(desired_item_id) => ensure!( + Item::::contains_key(&desired_collection_id, &desired_item_id), + Error::::UnknownItem + ), + None => ensure!( + Collection::::contains_key(&desired_collection_id), + Error::::UnknownCollection + ), + }; + + let now = frame_system::Pallet::::block_number(); + let deadline = duration.saturating_add(now); + + PendingSwapOf::::insert( + &offered_collection_id, + &offered_item_id, + PendingSwap { + desired_collection: desired_collection_id, + desired_item: maybe_desired_item_id, + price: maybe_price.clone(), + deadline, + }, + ); + + Self::deposit_event(Event::SwapCreated { + offered_collection: offered_collection_id, + offered_item: offered_item_id, + desired_collection: desired_collection_id, + desired_item: maybe_desired_item_id, + price: maybe_price, + deadline, + }); + + Ok(()) + } + + pub(crate) fn do_cancel_swap( + caller: T::AccountId, + offered_collection_id: T::CollectionId, + offered_item_id: T::ItemId, + ) -> DispatchResult { + let swap = PendingSwapOf::::get(&offered_collection_id, &offered_item_id) + .ok_or(Error::::UnknownSwap)?; + + let now = frame_system::Pallet::::block_number(); + if swap.deadline > now { + let item = Item::::get(&offered_collection_id, &offered_item_id) + .ok_or(Error::::UnknownItem)?; + ensure!(item.owner == caller, Error::::NoPermission); + } + + PendingSwapOf::::remove(&offered_collection_id, &offered_item_id); + + Self::deposit_event(Event::SwapCancelled { + offered_collection: offered_collection_id, + offered_item: offered_item_id, + desired_collection: swap.desired_collection, + desired_item: swap.desired_item, + price: swap.price, + deadline: swap.deadline, + }); + + Ok(()) + } + + pub(crate) fn do_claim_swap( + caller: T::AccountId, + send_collection_id: T::CollectionId, + send_item_id: T::ItemId, + receive_collection_id: T::CollectionId, + receive_item_id: T::ItemId, + witness_price: Option>>, + ) -> DispatchResult { + ensure!( + Self::is_pallet_feature_enabled(PalletFeature::Swaps), + Error::::MethodDisabled + ); + + let send_item = Item::::get(&send_collection_id, &send_item_id) + .ok_or(Error::::UnknownItem)?; + let receive_item = Item::::get(&receive_collection_id, &receive_item_id) + .ok_or(Error::::UnknownItem)?; + let swap = PendingSwapOf::::get(&receive_collection_id, &receive_item_id) + .ok_or(Error::::UnknownSwap)?; + + ensure!(send_item.owner == caller, Error::::NoPermission); + ensure!( + swap.desired_collection == send_collection_id && swap.price == witness_price, + Error::::UnknownSwap + ); + + if let Some(desired_item) = swap.desired_item { + ensure!(desired_item == send_item_id, Error::::UnknownSwap); + } + + let now = frame_system::Pallet::::block_number(); + ensure!(now <= swap.deadline, Error::::DeadlineExpired); + + if let Some(ref price) = swap.price { + match price.direction { + PriceDirection::Send => T::Currency::transfer( + &receive_item.owner, + &send_item.owner, + price.amount, + KeepAlive, + )?, + PriceDirection::Receive => T::Currency::transfer( + &send_item.owner, + &receive_item.owner, + price.amount, + KeepAlive, + )?, + }; + } + + // This also removes the swap. + Self::do_transfer(send_collection_id, send_item_id, receive_item.owner.clone(), |_, _| { + Ok(()) + })?; + Self::do_transfer( + receive_collection_id, + receive_item_id, + send_item.owner.clone(), + |_, _| Ok(()), + )?; + + Self::deposit_event(Event::SwapClaimed { + sent_collection: send_collection_id, + sent_item: send_item_id, + sent_item_owner: send_item.owner, + received_collection: receive_collection_id, + received_item: receive_item_id, + received_item_owner: receive_item.owner, + price: swap.price, + deadline: swap.deadline, + }); + + Ok(()) + } +} diff --git a/frame/nfts/src/features/attributes.rs b/frame/nfts/src/features/attributes.rs new file mode 100644 index 0000000000..da663d39a4 --- /dev/null +++ b/frame/nfts/src/features/attributes.rs @@ -0,0 +1,323 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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::*; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + pub(crate) fn do_set_attribute( + origin: T::AccountId, + collection: T::CollectionId, + maybe_item: Option, + namespace: AttributeNamespace, + key: BoundedVec, + value: BoundedVec, + ) -> DispatchResult { + ensure!( + Self::is_pallet_feature_enabled(PalletFeature::Attributes), + Error::::MethodDisabled + ); + + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + + ensure!( + Self::is_valid_namespace( + &origin, + &namespace, + &collection, + &collection_details.owner, + &maybe_item, + )?, + Error::::NoPermission + ); + + let collection_config = Self::get_collection_config(&collection)?; + // for the `CollectionOwner` namespace we need to check if the collection/item is not locked + match namespace { + AttributeNamespace::CollectionOwner => match maybe_item { + None => { + ensure!( + collection_config.is_setting_enabled(CollectionSetting::UnlockedAttributes), + Error::::LockedCollectionAttributes + ) + }, + Some(item) => { + let maybe_is_locked = Self::get_item_config(&collection, &item) + .map(|c| c.has_disabled_setting(ItemSetting::UnlockedAttributes))?; + ensure!(!maybe_is_locked, Error::::LockedItemAttributes); + }, + }, + _ => (), + } + + let attribute = Attribute::::get((collection, maybe_item, &namespace, &key)); + if attribute.is_none() { + collection_details.attributes.saturating_inc(); + } + + let old_deposit = + attribute.map_or(AttributeDeposit { account: None, amount: Zero::zero() }, |m| m.1); + + let mut deposit = Zero::zero(); + if collection_config.is_setting_enabled(CollectionSetting::DepositRequired) || + namespace != AttributeNamespace::CollectionOwner + { + deposit = T::DepositPerByte::get() + .saturating_mul(((key.len() + value.len()) as u32).into()) + .saturating_add(T::AttributeDepositBase::get()); + } + + // NOTE: when we transfer an item, we don't move attributes in the ItemOwner namespace. + // When the new owner updates the same attribute, we will update the depositor record + // and return the deposit to the previous owner. + if old_deposit.account.is_some() && old_deposit.account != Some(origin.clone()) { + T::Currency::unreserve(&old_deposit.account.unwrap(), old_deposit.amount); + T::Currency::reserve(&origin, deposit)?; + } else if deposit > old_deposit.amount { + T::Currency::reserve(&origin, deposit - old_deposit.amount)?; + } else if deposit < old_deposit.amount { + T::Currency::unreserve(&origin, old_deposit.amount - deposit); + } + + // NOTE: we don't track the depositor in the CollectionOwner namespace as it's always a + // collection's owner. This simplifies the collection's transfer to another owner. + let deposit_owner = match namespace { + AttributeNamespace::CollectionOwner => { + collection_details.owner_deposit.saturating_accrue(deposit); + collection_details.owner_deposit.saturating_reduce(old_deposit.amount); + None + }, + _ => Some(origin), + }; + + Attribute::::insert( + (&collection, maybe_item, &namespace, &key), + (&value, AttributeDeposit { account: deposit_owner, amount: deposit }), + ); + Collection::::insert(collection, &collection_details); + Self::deposit_event(Event::AttributeSet { collection, maybe_item, key, value, namespace }); + Ok(()) + } + + pub(crate) fn do_force_set_attribute( + set_as: Option, + collection: T::CollectionId, + maybe_item: Option, + namespace: AttributeNamespace, + key: BoundedVec, + value: BoundedVec, + ) -> DispatchResult { + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + + let attribute = Attribute::::get((collection, maybe_item, &namespace, &key)); + if let Some((_, deposit)) = attribute { + if deposit.account != set_as && deposit.amount != Zero::zero() { + if let Some(deposit_account) = deposit.account { + T::Currency::unreserve(&deposit_account, deposit.amount); + } + } + } else { + collection_details.attributes.saturating_inc(); + } + + Attribute::::insert( + (&collection, maybe_item, &namespace, &key), + (&value, AttributeDeposit { account: set_as, amount: Zero::zero() }), + ); + Collection::::insert(collection, &collection_details); + Self::deposit_event(Event::AttributeSet { collection, maybe_item, key, value, namespace }); + Ok(()) + } + + pub(crate) fn do_clear_attribute( + maybe_check_owner: Option, + collection: T::CollectionId, + maybe_item: Option, + namespace: AttributeNamespace, + key: BoundedVec, + ) -> DispatchResult { + if let Some((_, deposit)) = + Attribute::::take((collection, maybe_item, &namespace, &key)) + { + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + + if let Some(check_owner) = &maybe_check_owner { + if deposit.account != maybe_check_owner { + ensure!( + Self::is_valid_namespace( + &check_owner, + &namespace, + &collection, + &collection_details.owner, + &maybe_item, + )?, + Error::::NoPermission + ); + } + + // can't clear `CollectionOwner` type attributes if the collection/item is locked + match namespace { + AttributeNamespace::CollectionOwner => match maybe_item { + None => { + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + collection_config + .is_setting_enabled(CollectionSetting::UnlockedAttributes), + Error::::LockedCollectionAttributes + ) + }, + Some(item) => { + // NOTE: if the item was previously burned, the ItemConfigOf record + // might not exist. In that case, we allow to clear the attribute. + let maybe_is_locked = Self::get_item_config(&collection, &item) + .map_or(false, |c| { + c.has_disabled_setting(ItemSetting::UnlockedAttributes) + }); + ensure!(!maybe_is_locked, Error::::LockedItemAttributes); + }, + }, + _ => (), + }; + } + + collection_details.attributes.saturating_dec(); + match namespace { + AttributeNamespace::CollectionOwner => { + collection_details.owner_deposit.saturating_reduce(deposit.amount); + T::Currency::unreserve(&collection_details.owner, deposit.amount); + }, + _ => (), + }; + if let Some(deposit_account) = deposit.account { + T::Currency::unreserve(&deposit_account, deposit.amount); + } + Collection::::insert(collection, &collection_details); + Self::deposit_event(Event::AttributeCleared { collection, maybe_item, key, namespace }); + } + Ok(()) + } + + pub(crate) fn do_approve_item_attributes( + check_origin: T::AccountId, + collection: T::CollectionId, + item: T::ItemId, + delegate: T::AccountId, + ) -> DispatchResult { + ensure!( + Self::is_pallet_feature_enabled(PalletFeature::Attributes), + Error::::MethodDisabled + ); + + let details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + ensure!(check_origin == details.owner, Error::::NoPermission); + + ItemAttributesApprovalsOf::::try_mutate(collection, item, |approvals| { + approvals + .try_insert(delegate.clone()) + .map_err(|_| Error::::ReachedApprovalLimit)?; + + Self::deposit_event(Event::ItemAttributesApprovalAdded { collection, item, delegate }); + Ok(()) + }) + } + + pub(crate) fn do_cancel_item_attributes_approval( + check_origin: T::AccountId, + collection: T::CollectionId, + item: T::ItemId, + delegate: T::AccountId, + witness: CancelAttributesApprovalWitness, + ) -> DispatchResult { + ensure!( + Self::is_pallet_feature_enabled(PalletFeature::Attributes), + Error::::MethodDisabled + ); + + let details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + ensure!(check_origin == details.owner, Error::::NoPermission); + + ItemAttributesApprovalsOf::::try_mutate(collection, item, |approvals| { + approvals.remove(&delegate); + + let mut attributes: u32 = 0; + let mut deposited: DepositBalanceOf = Zero::zero(); + for (_, (_, deposit)) in Attribute::::drain_prefix(( + &collection, + Some(item), + AttributeNamespace::Account(delegate.clone()), + )) { + attributes.saturating_inc(); + deposited = deposited.saturating_add(deposit.amount); + } + ensure!(attributes <= witness.account_attributes, Error::::BadWitness); + + if !deposited.is_zero() { + T::Currency::unreserve(&delegate, deposited); + } + + Self::deposit_event(Event::ItemAttributesApprovalRemoved { + collection, + item, + delegate, + }); + Ok(()) + }) + } + + fn is_valid_namespace( + origin: &T::AccountId, + namespace: &AttributeNamespace, + collection: &T::CollectionId, + collection_owner: &T::AccountId, + maybe_item: &Option, + ) -> Result { + let mut result = false; + match namespace { + AttributeNamespace::CollectionOwner => result = origin == collection_owner, + AttributeNamespace::ItemOwner => + if let Some(item) = maybe_item { + let item_details = + Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + result = origin == &item_details.owner + }, + AttributeNamespace::Account(account_id) => + if let Some(item) = maybe_item { + let approvals = ItemAttributesApprovalsOf::::get(&collection, &item); + result = account_id == origin && approvals.contains(&origin) + }, + _ => (), + }; + Ok(result) + } + + /// A helper method to construct attribute's key. + pub fn construct_attribute_key( + key: Vec, + ) -> Result, DispatchError> { + Ok(BoundedVec::try_from(key).map_err(|_| Error::::IncorrectData)?) + } + + /// A helper method to construct attribute's value. + pub fn construct_attribute_value( + value: Vec, + ) -> Result, DispatchError> { + Ok(BoundedVec::try_from(value).map_err(|_| Error::::IncorrectData)?) + } +} diff --git a/frame/nfts/src/features/buy_sell.rs b/frame/nfts/src/features/buy_sell.rs new file mode 100644 index 0000000000..8ba5171f8d --- /dev/null +++ b/frame/nfts/src/features/buy_sell.rs @@ -0,0 +1,130 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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::*; +use frame_support::{ + pallet_prelude::*, + traits::{Currency, ExistenceRequirement, ExistenceRequirement::KeepAlive}, +}; + +impl, I: 'static> Pallet { + pub(crate) fn do_pay_tips( + sender: T::AccountId, + tips: BoundedVec, T::MaxTips>, + ) -> DispatchResult { + for tip in tips { + let ItemTip { collection, item, receiver, amount } = tip; + T::Currency::transfer(&sender, &receiver, amount, KeepAlive)?; + Self::deposit_event(Event::TipSent { + collection, + item, + sender: sender.clone(), + receiver, + amount, + }); + } + Ok(()) + } + + pub(crate) fn do_set_price( + collection: T::CollectionId, + item: T::ItemId, + sender: T::AccountId, + price: Option>, + whitelisted_buyer: Option, + ) -> DispatchResult { + ensure!( + Self::is_pallet_feature_enabled(PalletFeature::Trading), + Error::::MethodDisabled + ); + + let details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + ensure!(details.owner == sender, Error::::NoPermission); + + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + collection_config.is_setting_enabled(CollectionSetting::TransferableItems), + Error::::ItemsNonTransferable + ); + + let item_config = Self::get_item_config(&collection, &item)?; + ensure!( + item_config.is_setting_enabled(ItemSetting::Transferable), + Error::::ItemLocked + ); + + if let Some(ref price) = price { + ItemPriceOf::::insert(&collection, &item, (price, whitelisted_buyer.clone())); + Self::deposit_event(Event::ItemPriceSet { + collection, + item, + price: *price, + whitelisted_buyer, + }); + } else { + ItemPriceOf::::remove(&collection, &item); + Self::deposit_event(Event::ItemPriceRemoved { collection, item }); + } + + Ok(()) + } + + pub(crate) fn do_buy_item( + collection: T::CollectionId, + item: T::ItemId, + buyer: T::AccountId, + bid_price: ItemPrice, + ) -> DispatchResult { + ensure!( + Self::is_pallet_feature_enabled(PalletFeature::Trading), + Error::::MethodDisabled + ); + + let details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + ensure!(details.owner != buyer, Error::::NoPermission); + + let price_info = + ItemPriceOf::::get(&collection, &item).ok_or(Error::::NotForSale)?; + + ensure!(bid_price >= price_info.0, Error::::BidTooLow); + + if let Some(only_buyer) = price_info.1 { + ensure!(only_buyer == buyer, Error::::NoPermission); + } + + T::Currency::transfer( + &buyer, + &details.owner, + price_info.0, + ExistenceRequirement::KeepAlive, + )?; + + let old_owner = details.owner.clone(); + + Self::do_transfer(collection, item, buyer.clone(), |_, _| Ok(()))?; + + Self::deposit_event(Event::ItemBought { + collection, + item, + price: price_info.0, + seller: old_owner, + buyer, + }); + + Ok(()) + } +} diff --git a/frame/nfts/src/features/create_delete_collection.rs b/frame/nfts/src/features/create_delete_collection.rs new file mode 100644 index 0000000000..86625bf49e --- /dev/null +++ b/frame/nfts/src/features/create_delete_collection.rs @@ -0,0 +1,118 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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::*; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + pub fn do_create_collection( + collection: T::CollectionId, + owner: T::AccountId, + admin: T::AccountId, + config: CollectionConfigFor, + deposit: DepositBalanceOf, + event: Event, + ) -> DispatchResult { + ensure!(!Collection::::contains_key(collection), Error::::CollectionIdInUse); + + T::Currency::reserve(&owner, deposit)?; + + Collection::::insert( + collection, + CollectionDetails { + owner: owner.clone(), + owner_deposit: deposit, + items: 0, + item_metadatas: 0, + attributes: 0, + }, + ); + CollectionRoleOf::::insert( + collection, + admin, + CollectionRoles( + CollectionRole::Admin | CollectionRole::Freezer | CollectionRole::Issuer, + ), + ); + + let next_id = collection.increment(); + + CollectionConfigOf::::insert(&collection, config); + CollectionAccount::::insert(&owner, &collection, ()); + NextCollectionId::::set(Some(next_id)); + + Self::deposit_event(Event::NextCollectionIdIncremented { next_id }); + Self::deposit_event(event); + Ok(()) + } + + pub fn do_destroy_collection( + collection: T::CollectionId, + witness: DestroyWitness, + maybe_check_owner: Option, + ) -> Result { + Collection::::try_mutate_exists(collection, |maybe_details| { + let collection_details = + maybe_details.take().ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = maybe_check_owner { + ensure!(collection_details.owner == check_owner, Error::::NoPermission); + } + ensure!(collection_details.items == witness.items, Error::::BadWitness); + ensure!( + collection_details.item_metadatas == witness.item_metadatas, + Error::::BadWitness + ); + ensure!(collection_details.attributes == witness.attributes, Error::::BadWitness); + + for (item, details) in Item::::drain_prefix(&collection) { + Account::::remove((&details.owner, &collection, &item)); + T::Currency::unreserve(&details.deposit.account, details.deposit.amount); + } + #[allow(deprecated)] + ItemMetadataOf::::remove_prefix(&collection, None); + #[allow(deprecated)] + ItemPriceOf::::remove_prefix(&collection, None); + #[allow(deprecated)] + PendingSwapOf::::remove_prefix(&collection, None); + CollectionMetadataOf::::remove(&collection); + Self::clear_roles(&collection)?; + + for (_, (_, deposit)) in Attribute::::drain_prefix((&collection,)) { + if !deposit.amount.is_zero() { + if let Some(account) = deposit.account { + T::Currency::unreserve(&account, deposit.amount); + } + } + } + + CollectionAccount::::remove(&collection_details.owner, &collection); + T::Currency::unreserve(&collection_details.owner, collection_details.owner_deposit); + CollectionConfigOf::::remove(&collection); + let _ = ItemConfigOf::::clear_prefix(&collection, witness.items, None); + let _ = + ItemAttributesApprovalsOf::::clear_prefix(&collection, witness.items, None); + + Self::deposit_event(Event::Destroyed { collection }); + + Ok(DestroyWitness { + items: collection_details.items, + item_metadatas: collection_details.item_metadatas, + attributes: collection_details.attributes, + }) + }) + } +} diff --git a/frame/nfts/src/features/create_delete_item.rs b/frame/nfts/src/features/create_delete_item.rs new file mode 100644 index 0000000000..7fd745b2bf --- /dev/null +++ b/frame/nfts/src/features/create_delete_item.rs @@ -0,0 +1,126 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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::*; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + pub fn do_mint( + collection: T::CollectionId, + item: T::ItemId, + depositor: T::AccountId, + mint_to: T::AccountId, + item_config: ItemConfig, + deposit_collection_owner: bool, + with_details_and_config: impl FnOnce( + &CollectionDetailsFor, + &CollectionConfigFor, + ) -> DispatchResult, + ) -> DispatchResult { + ensure!(!Item::::contains_key(collection, item), Error::::AlreadyExists); + + Collection::::try_mutate( + &collection, + |maybe_collection_details| -> DispatchResult { + let collection_details = + maybe_collection_details.as_mut().ok_or(Error::::UnknownCollection)?; + + let collection_config = Self::get_collection_config(&collection)?; + with_details_and_config(collection_details, &collection_config)?; + + if let Some(max_supply) = collection_config.max_supply { + ensure!(collection_details.items < max_supply, Error::::MaxSupplyReached); + } + + collection_details.items.saturating_inc(); + + let collection_config = Self::get_collection_config(&collection)?; + let deposit_amount = match collection_config + .is_setting_enabled(CollectionSetting::DepositRequired) + { + true => T::ItemDeposit::get(), + false => Zero::zero(), + }; + let deposit_account = match deposit_collection_owner { + true => collection_details.owner.clone(), + false => depositor, + }; + + let item_owner = mint_to.clone(); + Account::::insert((&item_owner, &collection, &item), ()); + + if let Ok(existing_config) = ItemConfigOf::::try_get(&collection, &item) { + ensure!(existing_config == item_config, Error::::InconsistentItemConfig); + } else { + ItemConfigOf::::insert(&collection, &item, item_config); + } + + T::Currency::reserve(&deposit_account, deposit_amount)?; + + let deposit = ItemDeposit { account: deposit_account, amount: deposit_amount }; + let details = ItemDetails { + owner: item_owner, + approvals: ApprovalsOf::::default(), + deposit, + }; + Item::::insert(&collection, &item, details); + Ok(()) + }, + )?; + + Self::deposit_event(Event::Issued { collection, item, owner: mint_to }); + Ok(()) + } + + pub fn do_burn( + collection: T::CollectionId, + item: T::ItemId, + with_details: impl FnOnce(&ItemDetailsFor) -> DispatchResult, + ) -> DispatchResult { + let owner = Collection::::try_mutate( + &collection, + |maybe_collection_details| -> Result { + let collection_details = + maybe_collection_details.as_mut().ok_or(Error::::UnknownCollection)?; + let details = Item::::get(&collection, &item) + .ok_or(Error::::UnknownCollection)?; + with_details(&details)?; + + // Return the deposit. + T::Currency::unreserve(&details.deposit.account, details.deposit.amount); + collection_details.items.saturating_dec(); + Ok(details.owner) + }, + )?; + + Item::::remove(&collection, &item); + Account::::remove((&owner, &collection, &item)); + ItemPriceOf::::remove(&collection, &item); + PendingSwapOf::::remove(&collection, &item); + ItemAttributesApprovalsOf::::remove(&collection, &item); + + // NOTE: if item's settings are not empty (e.g. item's metadata is locked) + // then we keep the record and don't remove it + let config = Self::get_item_config(&collection, &item)?; + if !config.has_disabled_settings() { + ItemConfigOf::::remove(&collection, &item); + } + + Self::deposit_event(Event::Burned { collection, item, owner }); + Ok(()) + } +} diff --git a/frame/nfts/src/features/lock.rs b/frame/nfts/src/features/lock.rs new file mode 100644 index 0000000000..e96a30dfd2 --- /dev/null +++ b/frame/nfts/src/features/lock.rs @@ -0,0 +1,120 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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::*; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + pub(crate) fn do_lock_collection( + origin: T::AccountId, + collection: T::CollectionId, + lock_settings: CollectionSettings, + ) -> DispatchResult { + ensure!( + Self::has_role(&collection, &origin, CollectionRole::Freezer), + Error::::NoPermission + ); + ensure!( + !lock_settings.is_disabled(CollectionSetting::DepositRequired), + Error::::WrongSetting + ); + CollectionConfigOf::::try_mutate(collection, |maybe_config| { + let config = maybe_config.as_mut().ok_or(Error::::NoConfig)?; + + for setting in lock_settings.get_disabled() { + config.disable_setting(setting); + } + + Self::deposit_event(Event::::CollectionLocked { collection }); + Ok(()) + }) + } + + pub(crate) fn do_lock_item_transfer( + origin: T::AccountId, + collection: T::CollectionId, + item: T::ItemId, + ) -> DispatchResult { + ensure!( + Self::has_role(&collection, &origin, CollectionRole::Freezer), + Error::::NoPermission + ); + + let mut config = Self::get_item_config(&collection, &item)?; + if !config.has_disabled_setting(ItemSetting::Transferable) { + config.disable_setting(ItemSetting::Transferable); + } + ItemConfigOf::::insert(&collection, &item, config); + + Self::deposit_event(Event::::ItemTransferLocked { collection, item }); + Ok(()) + } + + pub(crate) fn do_unlock_item_transfer( + origin: T::AccountId, + collection: T::CollectionId, + item: T::ItemId, + ) -> DispatchResult { + ensure!( + Self::has_role(&collection, &origin, CollectionRole::Freezer), + Error::::NoPermission + ); + + let mut config = Self::get_item_config(&collection, &item)?; + if config.has_disabled_setting(ItemSetting::Transferable) { + config.enable_setting(ItemSetting::Transferable); + } + ItemConfigOf::::insert(&collection, &item, config); + + Self::deposit_event(Event::::ItemTransferUnlocked { collection, item }); + Ok(()) + } + + pub(crate) fn do_lock_item_properties( + maybe_check_owner: Option, + collection: T::CollectionId, + item: T::ItemId, + lock_metadata: bool, + lock_attributes: bool, + ) -> DispatchResult { + let collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &collection_details.owner, Error::::NoPermission); + } + + ItemConfigOf::::try_mutate(collection, item, |maybe_config| { + let config = maybe_config.as_mut().ok_or(Error::::UnknownItem)?; + + if lock_metadata { + config.disable_setting(ItemSetting::UnlockedMetadata); + } + if lock_attributes { + config.disable_setting(ItemSetting::UnlockedAttributes); + } + + Self::deposit_event(Event::::ItemPropertiesLocked { + collection, + item, + lock_metadata, + lock_attributes, + }); + Ok(()) + }) + } +} diff --git a/frame/nfts/src/features/metadata.rs b/frame/nfts/src/features/metadata.rs new file mode 100644 index 0000000000..942f377141 --- /dev/null +++ b/frame/nfts/src/features/metadata.rs @@ -0,0 +1,173 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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::*; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + pub(crate) fn do_set_item_metadata( + maybe_check_owner: Option, + collection: T::CollectionId, + item: T::ItemId, + data: BoundedVec, + ) -> DispatchResult { + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + + let item_config = Self::get_item_config(&collection, &item)?; + ensure!( + maybe_check_owner.is_none() || + item_config.is_setting_enabled(ItemSetting::UnlockedMetadata), + Error::::LockedItemMetadata + ); + + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &collection_details.owner, Error::::NoPermission); + } + + let collection_config = Self::get_collection_config(&collection)?; + + ItemMetadataOf::::try_mutate_exists(collection, item, |metadata| { + if metadata.is_none() { + collection_details.item_metadatas.saturating_inc(); + } + let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); + collection_details.owner_deposit.saturating_reduce(old_deposit); + let mut deposit = Zero::zero(); + if collection_config.is_setting_enabled(CollectionSetting::DepositRequired) && + maybe_check_owner.is_some() + { + deposit = T::DepositPerByte::get() + .saturating_mul(((data.len()) as u32).into()) + .saturating_add(T::MetadataDepositBase::get()); + } + if deposit > old_deposit { + T::Currency::reserve(&collection_details.owner, deposit - old_deposit)?; + } else if deposit < old_deposit { + T::Currency::unreserve(&collection_details.owner, old_deposit - deposit); + } + collection_details.owner_deposit.saturating_accrue(deposit); + + *metadata = Some(ItemMetadata { deposit, data: data.clone() }); + + Collection::::insert(&collection, &collection_details); + Self::deposit_event(Event::ItemMetadataSet { collection, item, data }); + Ok(()) + }) + } + + pub(crate) fn do_clear_item_metadata( + maybe_check_owner: Option, + collection: T::CollectionId, + item: T::ItemId, + ) -> DispatchResult { + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &collection_details.owner, Error::::NoPermission); + } + + // NOTE: if the item was previously burned, the ItemConfigOf record might not exist + let is_locked = Self::get_item_config(&collection, &item) + .map_or(false, |c| c.has_disabled_setting(ItemSetting::UnlockedMetadata)); + + ensure!(maybe_check_owner.is_none() || !is_locked, Error::::LockedItemMetadata); + + ItemMetadataOf::::try_mutate_exists(collection, item, |metadata| { + if metadata.is_some() { + collection_details.item_metadatas.saturating_dec(); + } + let deposit = metadata.take().ok_or(Error::::UnknownItem)?.deposit; + T::Currency::unreserve(&collection_details.owner, deposit); + collection_details.owner_deposit.saturating_reduce(deposit); + + Collection::::insert(&collection, &collection_details); + Self::deposit_event(Event::ItemMetadataCleared { collection, item }); + Ok(()) + }) + } + + pub(crate) fn do_set_collection_metadata( + maybe_check_owner: Option, + collection: T::CollectionId, + data: BoundedVec, + ) -> DispatchResult { + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + maybe_check_owner.is_none() || + collection_config.is_setting_enabled(CollectionSetting::UnlockedMetadata), + Error::::LockedCollectionMetadata + ); + + let mut details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &details.owner, Error::::NoPermission); + } + + CollectionMetadataOf::::try_mutate_exists(collection, |metadata| { + let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); + details.owner_deposit.saturating_reduce(old_deposit); + let mut deposit = Zero::zero(); + if maybe_check_owner.is_some() && + collection_config.is_setting_enabled(CollectionSetting::DepositRequired) + { + deposit = T::DepositPerByte::get() + .saturating_mul(((data.len()) as u32).into()) + .saturating_add(T::MetadataDepositBase::get()); + } + if deposit > old_deposit { + T::Currency::reserve(&details.owner, deposit - old_deposit)?; + } else if deposit < old_deposit { + T::Currency::unreserve(&details.owner, old_deposit - deposit); + } + details.owner_deposit.saturating_accrue(deposit); + + Collection::::insert(&collection, details); + + *metadata = Some(CollectionMetadata { deposit, data: data.clone() }); + + Self::deposit_event(Event::CollectionMetadataSet { collection, data }); + Ok(()) + }) + } + + pub(crate) fn do_clear_collection_metadata( + maybe_check_owner: Option, + collection: T::CollectionId, + ) -> DispatchResult { + let details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &details.owner, Error::::NoPermission); + } + + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + maybe_check_owner.is_none() || + collection_config.is_setting_enabled(CollectionSetting::UnlockedMetadata), + Error::::LockedCollectionMetadata + ); + + CollectionMetadataOf::::try_mutate_exists(collection, |metadata| { + let deposit = metadata.take().ok_or(Error::::UnknownCollection)?.deposit; + T::Currency::unreserve(&details.owner, deposit); + Self::deposit_event(Event::CollectionMetadataCleared { collection }); + Ok(()) + }) + } +} diff --git a/frame/nfts/src/features/mod.rs b/frame/nfts/src/features/mod.rs new file mode 100644 index 0000000000..b77ee9bf24 --- /dev/null +++ b/frame/nfts/src/features/mod.rs @@ -0,0 +1,28 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +pub mod approvals; +pub mod atomic_swap; +pub mod attributes; +pub mod buy_sell; +pub mod create_delete_collection; +pub mod create_delete_item; +pub mod lock; +pub mod metadata; +pub mod roles; +pub mod settings; +pub mod transfer; diff --git a/frame/nfts/src/features/roles.rs b/frame/nfts/src/features/roles.rs new file mode 100644 index 0000000000..d6be9965a5 --- /dev/null +++ b/frame/nfts/src/features/roles.rs @@ -0,0 +1,99 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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::*; +use frame_support::pallet_prelude::*; +use sp_std::collections::btree_map::BTreeMap; + +impl, I: 'static> Pallet { + pub(crate) fn do_set_team( + maybe_check_owner: Option, + collection: T::CollectionId, + issuer: T::AccountId, + admin: T::AccountId, + freezer: T::AccountId, + ) -> DispatchResult { + Collection::::try_mutate(collection, |maybe_details| { + let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; + if let Some(check_origin) = maybe_check_owner { + ensure!(check_origin == details.owner, Error::::NoPermission); + } + + // delete previous values + Self::clear_roles(&collection)?; + + let account_to_role = Self::group_roles_by_account(vec![ + (issuer.clone(), CollectionRole::Issuer), + (admin.clone(), CollectionRole::Admin), + (freezer.clone(), CollectionRole::Freezer), + ]); + for (account, roles) in account_to_role { + CollectionRoleOf::::insert(&collection, &account, roles); + } + + Self::deposit_event(Event::TeamChanged { collection, issuer, admin, freezer }); + Ok(()) + }) + } + + /// Clears all the roles in a specified collection. + /// + /// - `collection_id`: A collection to clear the roles in. + /// + /// Throws an error if some of the roles were left in storage. + /// This means the `CollectionRoles::max_roles()` needs to be adjusted. + pub(crate) fn clear_roles(collection_id: &T::CollectionId) -> Result<(), DispatchError> { + let res = CollectionRoleOf::::clear_prefix( + &collection_id, + CollectionRoles::max_roles() as u32, + None, + ); + ensure!(res.maybe_cursor.is_none(), Error::::RolesNotCleared); + Ok(()) + } + + /// Returns true if a specified account has a provided role within that collection. + /// + /// - `collection_id`: A collection to check the role in. + /// - `account_id`: An account to check the role for. + /// - `role`: A role to validate. + /// + /// Returns boolean. + pub(crate) fn has_role( + collection_id: &T::CollectionId, + account_id: &T::AccountId, + role: CollectionRole, + ) -> bool { + CollectionRoleOf::::get(&collection_id, &account_id) + .map_or(false, |roles| roles.has_role(role)) + } + + /// Groups provided roles by account, given one account could have multiple roles. + /// + /// - `input`: A vector of (Account, Role) tuples. + /// + /// Returns a grouped vector. + pub fn group_roles_by_account( + input: Vec<(T::AccountId, CollectionRole)>, + ) -> Vec<(T::AccountId, CollectionRoles)> { + let mut result = BTreeMap::new(); + for (account, role) in input.into_iter() { + result.entry(account).or_insert(CollectionRoles::none()).add_role(role); + } + result.into_iter().collect() + } +} diff --git a/frame/nfts/src/features/settings.rs b/frame/nfts/src/features/settings.rs new file mode 100644 index 0000000000..5f408ed183 --- /dev/null +++ b/frame/nfts/src/features/settings.rs @@ -0,0 +1,103 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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::*; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + pub(crate) fn do_force_collection_config( + collection: T::CollectionId, + config: CollectionConfigFor, + ) -> DispatchResult { + ensure!(Collection::::contains_key(&collection), Error::::UnknownCollection); + CollectionConfigOf::::insert(&collection, config); + Self::deposit_event(Event::CollectionConfigChanged { collection }); + Ok(()) + } + + pub(crate) fn do_set_collection_max_supply( + maybe_check_owner: Option, + collection: T::CollectionId, + max_supply: u32, + ) -> DispatchResult { + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + collection_config.is_setting_enabled(CollectionSetting::UnlockedMaxSupply), + Error::::MaxSupplyLocked + ); + + let details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &details.owner, Error::::NoPermission); + } + + ensure!(details.items <= max_supply, Error::::MaxSupplyTooSmall); + + CollectionConfigOf::::try_mutate(collection, |maybe_config| { + let config = maybe_config.as_mut().ok_or(Error::::NoConfig)?; + config.max_supply = Some(max_supply); + Self::deposit_event(Event::CollectionMaxSupplySet { collection, max_supply }); + Ok(()) + }) + } + + pub(crate) fn do_update_mint_settings( + maybe_check_owner: Option, + collection: T::CollectionId, + mint_settings: MintSettings< + BalanceOf, + ::BlockNumber, + T::CollectionId, + >, + ) -> DispatchResult { + let details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &details.owner, Error::::NoPermission); + } + + CollectionConfigOf::::try_mutate(collection, |maybe_config| { + let config = maybe_config.as_mut().ok_or(Error::::NoConfig)?; + config.mint_settings = mint_settings; + Self::deposit_event(Event::CollectionMintSettingsUpdated { collection }); + Ok(()) + }) + } + + pub(crate) fn get_collection_config( + collection_id: &T::CollectionId, + ) -> Result, DispatchError> { + let config = + CollectionConfigOf::::get(&collection_id).ok_or(Error::::NoConfig)?; + Ok(config) + } + + pub(crate) fn get_item_config( + collection_id: &T::CollectionId, + item_id: &T::ItemId, + ) -> Result { + let config = ItemConfigOf::::get(&collection_id, &item_id) + .ok_or(Error::::UnknownItem)?; + Ok(config) + } + + pub(crate) fn is_pallet_feature_enabled(feature: PalletFeature) -> bool { + let features = T::Features::get(); + return features.is_enabled(feature) + } +} diff --git a/frame/nfts/src/features/transfer.rs b/frame/nfts/src/features/transfer.rs new file mode 100644 index 0000000000..7d6ae3553a --- /dev/null +++ b/frame/nfts/src/features/transfer.rs @@ -0,0 +1,166 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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::*; +use frame_support::pallet_prelude::*; + +impl, I: 'static> Pallet { + pub fn do_transfer( + collection: T::CollectionId, + item: T::ItemId, + dest: T::AccountId, + with_details: impl FnOnce( + &CollectionDetailsFor, + &mut ItemDetailsFor, + ) -> DispatchResult, + ) -> DispatchResult { + let collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + ensure!(!T::Locker::is_locked(collection, item), Error::::ItemLocked); + + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + collection_config.is_setting_enabled(CollectionSetting::TransferableItems), + Error::::ItemsNonTransferable + ); + + let item_config = Self::get_item_config(&collection, &item)?; + ensure!( + item_config.is_setting_enabled(ItemSetting::Transferable), + Error::::ItemLocked + ); + + let mut details = + Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + with_details(&collection_details, &mut details)?; + + if details.deposit.account == details.owner { + // Move the deposit to the new owner. + T::Currency::repatriate_reserved( + &details.owner, + &dest, + details.deposit.amount, + Reserved, + )?; + } + + Account::::remove((&details.owner, &collection, &item)); + Account::::insert((&dest, &collection, &item), ()); + let origin = details.owner; + details.owner = dest; + + // The approved accounts have to be reset to None, because otherwise pre-approve attack + // would be possible, where the owner can approve his second account before making the + // transaction and then claiming the item back. + details.approvals.clear(); + + Item::::insert(&collection, &item, &details); + ItemPriceOf::::remove(&collection, &item); + PendingSwapOf::::remove(&collection, &item); + + Self::deposit_event(Event::Transferred { + collection, + item, + from: origin, + to: details.owner, + }); + Ok(()) + } + + pub(crate) fn do_transfer_ownership( + origin: T::AccountId, + collection: T::CollectionId, + owner: T::AccountId, + ) -> DispatchResult { + let acceptable_collection = OwnershipAcceptance::::get(&owner); + ensure!(acceptable_collection.as_ref() == Some(&collection), Error::::Unaccepted); + + Collection::::try_mutate(collection, |maybe_details| { + let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; + ensure!(origin == details.owner, Error::::NoPermission); + if details.owner == owner { + return Ok(()) + } + + // Move the deposit to the new owner. + T::Currency::repatriate_reserved( + &details.owner, + &owner, + details.owner_deposit, + Reserved, + )?; + CollectionAccount::::remove(&details.owner, &collection); + CollectionAccount::::insert(&owner, &collection, ()); + + details.owner = owner.clone(); + OwnershipAcceptance::::remove(&owner); + + Self::deposit_event(Event::OwnerChanged { collection, new_owner: owner }); + Ok(()) + }) + } + + pub(crate) fn do_set_accept_ownership( + who: T::AccountId, + maybe_collection: Option, + ) -> DispatchResult { + let old = OwnershipAcceptance::::get(&who); + match (old.is_some(), maybe_collection.is_some()) { + (false, true) => { + frame_system::Pallet::::inc_consumers(&who)?; + }, + (true, false) => { + frame_system::Pallet::::dec_consumers(&who); + }, + _ => {}, + } + if let Some(collection) = maybe_collection.as_ref() { + OwnershipAcceptance::::insert(&who, collection); + } else { + OwnershipAcceptance::::remove(&who); + } + Self::deposit_event(Event::OwnershipAcceptanceChanged { who, maybe_collection }); + Ok(()) + } + + pub(crate) fn do_force_collection_owner( + collection: T::CollectionId, + owner: T::AccountId, + ) -> DispatchResult { + Collection::::try_mutate(collection, |maybe_details| { + let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; + if details.owner == owner { + return Ok(()) + } + + // Move the deposit to the new owner. + T::Currency::repatriate_reserved( + &details.owner, + &owner, + details.owner_deposit, + Reserved, + )?; + + CollectionAccount::::remove(&details.owner, &collection); + CollectionAccount::::insert(&owner, &collection, ()); + details.owner = owner.clone(); + + Self::deposit_event(Event::OwnerChanged { collection, new_owner: owner }); + Ok(()) + }) + } +} diff --git a/frame/nfts/src/impl_nonfungibles.rs b/frame/nfts/src/impl_nonfungibles.rs new file mode 100644 index 0000000000..edfc29710b --- /dev/null +++ b/frame/nfts/src/impl_nonfungibles.rs @@ -0,0 +1,289 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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. + +//! Implementations for `nonfungibles` traits. + +use super::*; +use frame_support::{ + ensure, + storage::KeyPrefixIterator, + traits::{tokens::nonfungibles_v2::*, Get}, + BoundedSlice, +}; +use sp_runtime::{DispatchError, DispatchResult}; +use sp_std::prelude::*; + +impl, I: 'static> Inspect<::AccountId> for Pallet { + type ItemId = T::ItemId; + type CollectionId = T::CollectionId; + + fn owner( + collection: &Self::CollectionId, + item: &Self::ItemId, + ) -> Option<::AccountId> { + Item::::get(collection, item).map(|a| a.owner) + } + + fn collection_owner(collection: &Self::CollectionId) -> Option<::AccountId> { + Collection::::get(collection).map(|a| a.owner) + } + + /// Returns the attribute value of `item` of `collection` corresponding to `key`. + /// + /// When `key` is empty, we return the item metadata value. + /// + /// By default this is `None`; no attributes are defined. + fn attribute( + collection: &Self::CollectionId, + item: &Self::ItemId, + namespace: &AttributeNamespace<::AccountId>, + key: &[u8], + ) -> Option> { + if key.is_empty() { + // We make the empty key map to the item metadata value. + ItemMetadataOf::::get(collection, item).map(|m| m.data.into()) + } else { + let key = BoundedSlice::<_, _>::try_from(key).ok()?; + Attribute::::get((collection, Some(item), namespace, key)).map(|a| a.0.into()) + } + } + + /// Returns the attribute value of `item` of `collection` corresponding to `key`. + /// + /// When `key` is empty, we return the item metadata value. + /// + /// By default this is `None`; no attributes are defined. + fn collection_attribute(collection: &Self::CollectionId, key: &[u8]) -> Option> { + if key.is_empty() { + // We make the empty key map to the item metadata value. + CollectionMetadataOf::::get(collection).map(|m| m.data.into()) + } else { + let key = BoundedSlice::<_, _>::try_from(key).ok()?; + Attribute::::get(( + collection, + Option::::None, + AttributeNamespace::CollectionOwner, + key, + )) + .map(|a| a.0.into()) + } + } + + /// Returns `true` if the `item` of `collection` may be transferred. + /// + /// Default implementation is that all items are transferable. + fn can_transfer(collection: &Self::CollectionId, item: &Self::ItemId) -> bool { + match ( + CollectionConfigOf::::get(collection), + ItemConfigOf::::get(collection, item), + ) { + (Some(cc), Some(ic)) + if cc.is_setting_enabled(CollectionSetting::TransferableItems) && + ic.is_setting_enabled(ItemSetting::Transferable) => + true, + _ => false, + } + } +} + +impl, I: 'static> Create<::AccountId, CollectionConfigFor> + for Pallet +{ + /// Create a `collection` of nonfungible items to be owned by `who` and managed by `admin`. + fn create_collection( + who: &T::AccountId, + admin: &T::AccountId, + config: &CollectionConfigFor, + ) -> Result { + // DepositRequired can be disabled by calling the force_create() only + ensure!( + !config.has_disabled_setting(CollectionSetting::DepositRequired), + Error::::WrongSetting + ); + + let collection = + NextCollectionId::::get().unwrap_or(T::CollectionId::initial_value()); + + Self::do_create_collection( + collection, + who.clone(), + admin.clone(), + *config, + T::CollectionDeposit::get(), + Event::Created { collection, creator: who.clone(), owner: admin.clone() }, + )?; + Ok(collection) + } +} + +impl, I: 'static> Destroy<::AccountId> for Pallet { + type DestroyWitness = DestroyWitness; + + fn get_destroy_witness(collection: &Self::CollectionId) -> Option { + Collection::::get(collection).map(|a| a.destroy_witness()) + } + + fn destroy( + collection: Self::CollectionId, + witness: Self::DestroyWitness, + maybe_check_owner: Option, + ) -> Result { + Self::do_destroy_collection(collection, witness, maybe_check_owner) + } +} + +impl, I: 'static> Mutate<::AccountId, ItemConfig> for Pallet { + fn mint_into( + collection: &Self::CollectionId, + item: &Self::ItemId, + who: &T::AccountId, + item_config: &ItemConfig, + deposit_collection_owner: bool, + ) -> DispatchResult { + Self::do_mint( + *collection, + *item, + who.clone(), + who.clone(), + *item_config, + deposit_collection_owner, + |_, _| Ok(()), + ) + } + + fn burn( + collection: &Self::CollectionId, + item: &Self::ItemId, + maybe_check_owner: Option<&T::AccountId>, + ) -> DispatchResult { + Self::do_burn(*collection, *item, |d| { + if let Some(check_owner) = maybe_check_owner { + if &d.owner != check_owner { + return Err(Error::::NoPermission.into()) + } + } + Ok(()) + }) + } + + fn set_attribute( + collection: &Self::CollectionId, + item: &Self::ItemId, + key: &[u8], + value: &[u8], + ) -> DispatchResult { + Self::do_force_set_attribute( + None, + *collection, + Some(*item), + AttributeNamespace::Pallet, + Self::construct_attribute_key(key.to_vec())?, + Self::construct_attribute_value(value.to_vec())?, + ) + } + + fn set_typed_attribute( + collection: &Self::CollectionId, + item: &Self::ItemId, + key: &K, + value: &V, + ) -> DispatchResult { + key.using_encoded(|k| { + value.using_encoded(|v| { + >::set_attribute(collection, item, k, v) + }) + }) + } + + fn set_collection_attribute( + collection: &Self::CollectionId, + key: &[u8], + value: &[u8], + ) -> DispatchResult { + Self::do_force_set_attribute( + None, + *collection, + None, + AttributeNamespace::Pallet, + Self::construct_attribute_key(key.to_vec())?, + Self::construct_attribute_value(value.to_vec())?, + ) + } + + fn set_typed_collection_attribute( + collection: &Self::CollectionId, + key: &K, + value: &V, + ) -> DispatchResult { + key.using_encoded(|k| { + value.using_encoded(|v| { + >::set_collection_attribute( + collection, k, v, + ) + }) + }) + } +} + +impl, I: 'static> Transfer for Pallet { + fn transfer( + collection: &Self::CollectionId, + item: &Self::ItemId, + destination: &T::AccountId, + ) -> DispatchResult { + Self::do_transfer(*collection, *item, destination.clone(), |_, _| Ok(())) + } +} + +impl, I: 'static> InspectEnumerable for Pallet { + type CollectionsIterator = KeyPrefixIterator<>::CollectionId>; + type ItemsIterator = KeyPrefixIterator<>::ItemId>; + type OwnedIterator = + KeyPrefixIterator<(>::CollectionId, >::ItemId)>; + type OwnedInCollectionIterator = KeyPrefixIterator<>::ItemId>; + + /// Returns an iterator of the collections in existence. + /// + /// NOTE: iterating this list invokes a storage read per item. + fn collections() -> Self::CollectionsIterator { + Collection::::iter_keys() + } + + /// Returns an iterator of the items of a `collection` in existence. + /// + /// NOTE: iterating this list invokes a storage read per item. + fn items(collection: &Self::CollectionId) -> Self::ItemsIterator { + Item::::iter_key_prefix(collection) + } + + /// Returns an iterator of the items of all collections owned by `who`. + /// + /// NOTE: iterating this list invokes a storage read per item. + fn owned(who: &T::AccountId) -> Self::OwnedIterator { + Account::::iter_key_prefix((who,)) + } + + /// Returns an iterator of the items of `collection` owned by `who`. + /// + /// NOTE: iterating this list invokes a storage read per item. + fn owned_in_collection( + collection: &Self::CollectionId, + who: &T::AccountId, + ) -> Self::OwnedInCollectionIterator { + Account::::iter_key_prefix((who, collection)) + } +} diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs new file mode 100644 index 0000000000..2006d78959 --- /dev/null +++ b/frame/nfts/src/lib.rs @@ -0,0 +1,1769 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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. + +//! # Nfts Module +//! +//! A simple, secure module for dealing with non-fungible items. +//! +//! ## Related Modules +//! +//! * [`System`](../frame_system/index.html) +//! * [`Support`](../frame_support/index.html) + +#![recursion_limit = "256"] +// Ensure we're `no_std` when compiling for Wasm. +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +#[cfg(test)] +pub mod mock; +#[cfg(test)] +mod tests; + +mod common_functions; +mod features; +mod impl_nonfungibles; +mod types; + +pub mod macros; +pub mod weights; + +use codec::{Decode, Encode}; +use frame_support::traits::{ + tokens::{AttributeNamespace, Locker}, + BalanceStatus::Reserved, + Currency, EnsureOriginWithArg, ReservableCurrency, +}; +use frame_system::Config as SystemConfig; +use sp_runtime::{ + traits::{Saturating, StaticLookup, Zero}, + RuntimeDebug, +}; +use sp_std::prelude::*; + +pub use pallet::*; +pub use types::*; +pub use weights::WeightInfo; + +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::{pallet_prelude::*, traits::ExistenceRequirement}; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[cfg(feature = "runtime-benchmarks")] + pub trait BenchmarkHelper { + fn collection(i: u16) -> CollectionId; + fn item(i: u16) -> ItemId; + } + #[cfg(feature = "runtime-benchmarks")] + impl, ItemId: From> BenchmarkHelper for () { + fn collection(i: u16) -> CollectionId { + i.into() + } + fn item(i: u16) -> ItemId { + i.into() + } + } + + #[pallet::config] + /// The module configuration trait. + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; + + /// Identifier for the collection of item. + type CollectionId: Member + Parameter + MaxEncodedLen + Copy + Incrementable; + + /// The type used to identify a unique item within a collection. + type ItemId: Member + Parameter + MaxEncodedLen + Copy; + + /// The currency mechanism, used for paying for reserves. + type Currency: ReservableCurrency; + + /// The origin which may forcibly create or destroy an item or otherwise alter privileged + /// attributes. + type ForceOrigin: EnsureOrigin; + + /// Standard collection creation is only allowed if the origin attempting it and the + /// collection are in this set. + type CreateOrigin: EnsureOriginWithArg< + Self::RuntimeOrigin, + Self::CollectionId, + Success = Self::AccountId, + >; + + /// Locker trait to enable Locking mechanism downstream. + type Locker: Locker; + + /// The basic amount of funds that must be reserved for collection. + #[pallet::constant] + type CollectionDeposit: Get>; + + /// The basic amount of funds that must be reserved for an item. + #[pallet::constant] + type ItemDeposit: Get>; + + /// The basic amount of funds that must be reserved when adding metadata to your item. + #[pallet::constant] + type MetadataDepositBase: Get>; + + /// The basic amount of funds that must be reserved when adding an attribute to an item. + #[pallet::constant] + type AttributeDepositBase: Get>; + + /// The additional funds that must be reserved for the number of bytes store in metadata, + /// either "normal" metadata or attribute metadata. + #[pallet::constant] + type DepositPerByte: Get>; + + /// The maximum length of data stored on-chain. + #[pallet::constant] + type StringLimit: Get; + + /// The maximum length of an attribute key. + #[pallet::constant] + type KeyLimit: Get; + + /// The maximum length of an attribute value. + #[pallet::constant] + type ValueLimit: Get; + + /// The maximum approvals an item could have. + #[pallet::constant] + type ApprovalsLimit: Get; + + /// The maximum attributes approvals an item could have. + #[pallet::constant] + type ItemAttributesApprovalsLimit: Get; + + /// The max number of tips a user could send. + #[pallet::constant] + type MaxTips: Get; + + /// The max duration in blocks for deadlines. + #[pallet::constant] + type MaxDeadlineDuration: Get<::BlockNumber>; + + /// Disables some of pallet's features. + #[pallet::constant] + type Features: Get; + + #[cfg(feature = "runtime-benchmarks")] + /// A set of helper functions for benchmarking. + type Helper: BenchmarkHelper; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + } + + /// Details of a collection. + #[pallet::storage] + pub(super) type Collection, I: 'static = ()> = StorageMap< + _, + Blake2_128Concat, + T::CollectionId, + CollectionDetails>, + >; + + /// The collection, if any, of which an account is willing to take ownership. + #[pallet::storage] + pub(super) type OwnershipAcceptance, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, T::AccountId, T::CollectionId>; + + /// The items held by any given account; set out this way so that items owned by a single + /// account can be enumerated. + #[pallet::storage] + pub(super) type Account, I: 'static = ()> = StorageNMap< + _, + ( + NMapKey, // owner + NMapKey, + NMapKey, + ), + (), + OptionQuery, + >; + + /// The collections owned by any given account; set out this way so that collections owned by + /// a single account can be enumerated. + #[pallet::storage] + pub(super) type CollectionAccount, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + T::AccountId, + Blake2_128Concat, + T::CollectionId, + (), + OptionQuery, + >; + + /// The items in existence and their ownership details. + #[pallet::storage] + /// Stores collection roles as per account. + pub(super) type CollectionRoleOf, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + T::CollectionId, + Blake2_128Concat, + T::AccountId, + CollectionRoles, + OptionQuery, + >; + + /// The items in existence and their ownership details. + #[pallet::storage] + pub(super) type Item, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + T::CollectionId, + Blake2_128Concat, + T::ItemId, + ItemDetails, ApprovalsOf>, + OptionQuery, + >; + + /// Metadata of a collection. + #[pallet::storage] + pub(super) type CollectionMetadataOf, I: 'static = ()> = StorageMap< + _, + Blake2_128Concat, + T::CollectionId, + CollectionMetadata, T::StringLimit>, + OptionQuery, + >; + + /// Metadata of an item. + #[pallet::storage] + pub(super) type ItemMetadataOf, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + T::CollectionId, + Blake2_128Concat, + T::ItemId, + ItemMetadata, T::StringLimit>, + OptionQuery, + >; + + /// Attributes of a collection. + #[pallet::storage] + pub(super) type Attribute, I: 'static = ()> = StorageNMap< + _, + ( + NMapKey, + NMapKey>, + NMapKey>, + NMapKey>, + ), + (BoundedVec, AttributeDepositOf), + OptionQuery, + >; + + /// A price of an item. + #[pallet::storage] + pub(super) type ItemPriceOf, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + T::CollectionId, + Blake2_128Concat, + T::ItemId, + (ItemPrice, Option), + OptionQuery, + >; + + /// Item attribute approvals. + #[pallet::storage] + pub(super) type ItemAttributesApprovalsOf, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + T::CollectionId, + Blake2_128Concat, + T::ItemId, + ItemAttributesApprovals, + ValueQuery, + >; + + /// Stores the `CollectionId` that is going to be used for the next collection. + /// This gets incremented whenever a new collection is created. + #[pallet::storage] + pub(super) type NextCollectionId, I: 'static = ()> = + StorageValue<_, T::CollectionId, OptionQuery>; + + /// Handles all the pending swaps. + #[pallet::storage] + pub(super) type PendingSwapOf, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + T::CollectionId, + Blake2_128Concat, + T::ItemId, + PendingSwap< + T::CollectionId, + T::ItemId, + PriceWithDirection>, + ::BlockNumber, + >, + OptionQuery, + >; + + /// Config of a collection. + #[pallet::storage] + pub(super) type CollectionConfigOf, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, T::CollectionId, CollectionConfigFor, OptionQuery>; + + /// Config of an item. + #[pallet::storage] + pub(super) type ItemConfigOf, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + T::CollectionId, + Blake2_128Concat, + T::ItemId, + ItemConfig, + OptionQuery, + >; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event, I: 'static = ()> { + /// A `collection` was created. + Created { collection: T::CollectionId, creator: T::AccountId, owner: T::AccountId }, + /// A `collection` was force-created. + ForceCreated { collection: T::CollectionId, owner: T::AccountId }, + /// A `collection` was destroyed. + Destroyed { collection: T::CollectionId }, + /// An `item` was issued. + Issued { collection: T::CollectionId, item: T::ItemId, owner: T::AccountId }, + /// An `item` was transferred. + Transferred { + collection: T::CollectionId, + item: T::ItemId, + from: T::AccountId, + to: T::AccountId, + }, + /// An `item` was destroyed. + Burned { collection: T::CollectionId, item: T::ItemId, owner: T::AccountId }, + /// An `item` became non-transferable. + ItemTransferLocked { collection: T::CollectionId, item: T::ItemId }, + /// An `item` became transferable. + ItemTransferUnlocked { collection: T::CollectionId, item: T::ItemId }, + /// `item` metadata or attributes were locked. + ItemPropertiesLocked { + collection: T::CollectionId, + item: T::ItemId, + lock_metadata: bool, + lock_attributes: bool, + }, + /// Some `collection` was locked. + CollectionLocked { collection: T::CollectionId }, + /// The owner changed. + OwnerChanged { collection: T::CollectionId, new_owner: T::AccountId }, + /// The management team changed. + TeamChanged { + collection: T::CollectionId, + issuer: T::AccountId, + admin: T::AccountId, + freezer: T::AccountId, + }, + /// An `item` of a `collection` has been approved by the `owner` for transfer by + /// a `delegate`. + TransferApproved { + collection: T::CollectionId, + item: T::ItemId, + owner: T::AccountId, + delegate: T::AccountId, + deadline: Option<::BlockNumber>, + }, + /// An approval for a `delegate` account to transfer the `item` of an item + /// `collection` was cancelled by its `owner`. + ApprovalCancelled { + collection: T::CollectionId, + item: T::ItemId, + owner: T::AccountId, + delegate: T::AccountId, + }, + /// All approvals of an item got cancelled. + AllApprovalsCancelled { collection: T::CollectionId, item: T::ItemId, owner: T::AccountId }, + /// A `collection` has had its config changed by the `Force` origin. + CollectionConfigChanged { collection: T::CollectionId }, + /// New metadata has been set for a `collection`. + CollectionMetadataSet { collection: T::CollectionId, data: BoundedVec }, + /// Metadata has been cleared for a `collection`. + CollectionMetadataCleared { collection: T::CollectionId }, + /// New metadata has been set for an item. + ItemMetadataSet { + collection: T::CollectionId, + item: T::ItemId, + data: BoundedVec, + }, + /// Metadata has been cleared for an item. + ItemMetadataCleared { collection: T::CollectionId, item: T::ItemId }, + /// The deposit for a set of `item`s within a `collection` has been updated. + Redeposited { collection: T::CollectionId, successful_items: Vec }, + /// New attribute metadata has been set for a `collection` or `item`. + AttributeSet { + collection: T::CollectionId, + maybe_item: Option, + key: BoundedVec, + value: BoundedVec, + namespace: AttributeNamespace, + }, + /// Attribute metadata has been cleared for a `collection` or `item`. + AttributeCleared { + collection: T::CollectionId, + maybe_item: Option, + key: BoundedVec, + namespace: AttributeNamespace, + }, + /// A new approval to modify item attributes was added. + ItemAttributesApprovalAdded { + collection: T::CollectionId, + item: T::ItemId, + delegate: T::AccountId, + }, + /// A new approval to modify item attributes was removed. + ItemAttributesApprovalRemoved { + collection: T::CollectionId, + item: T::ItemId, + delegate: T::AccountId, + }, + /// Ownership acceptance has changed for an account. + OwnershipAcceptanceChanged { who: T::AccountId, maybe_collection: Option }, + /// Max supply has been set for a collection. + CollectionMaxSupplySet { collection: T::CollectionId, max_supply: u32 }, + /// Mint settings for a collection had changed. + CollectionMintSettingsUpdated { collection: T::CollectionId }, + /// Event gets emitted when the `NextCollectionId` gets incremented. + NextCollectionIdIncremented { next_id: T::CollectionId }, + /// The price was set for the item. + ItemPriceSet { + collection: T::CollectionId, + item: T::ItemId, + price: ItemPrice, + whitelisted_buyer: Option, + }, + /// The price for the item was removed. + ItemPriceRemoved { collection: T::CollectionId, item: T::ItemId }, + /// An item was bought. + ItemBought { + collection: T::CollectionId, + item: T::ItemId, + price: ItemPrice, + seller: T::AccountId, + buyer: T::AccountId, + }, + /// A tip was sent. + TipSent { + collection: T::CollectionId, + item: T::ItemId, + sender: T::AccountId, + receiver: T::AccountId, + amount: DepositBalanceOf, + }, + /// An `item` swap intent was created. + SwapCreated { + offered_collection: T::CollectionId, + offered_item: T::ItemId, + desired_collection: T::CollectionId, + desired_item: Option, + price: Option>>, + deadline: ::BlockNumber, + }, + /// The swap was cancelled. + SwapCancelled { + offered_collection: T::CollectionId, + offered_item: T::ItemId, + desired_collection: T::CollectionId, + desired_item: Option, + price: Option>>, + deadline: ::BlockNumber, + }, + /// The swap has been claimed. + SwapClaimed { + sent_collection: T::CollectionId, + sent_item: T::ItemId, + sent_item_owner: T::AccountId, + received_collection: T::CollectionId, + received_item: T::ItemId, + received_item_owner: T::AccountId, + price: Option>>, + deadline: ::BlockNumber, + }, + } + + #[pallet::error] + pub enum Error { + /// The signing account has no permission to do the operation. + NoPermission, + /// The given item ID is unknown. + UnknownCollection, + /// The item ID has already been used for an item. + AlreadyExists, + /// The approval had a deadline that expired, so the approval isn't valid anymore. + ApprovalExpired, + /// The owner turned out to be different to what was expected. + WrongOwner, + /// The witness data given does not match the current state of the chain. + BadWitness, + /// Collection ID is already taken. + CollectionIdInUse, + /// Items within that collection are non-transferable. + ItemsNonTransferable, + /// The provided account is not a delegate. + NotDelegate, + /// The delegate turned out to be different to what was expected. + WrongDelegate, + /// No approval exists that would allow the transfer. + Unapproved, + /// The named owner has not signed ownership acceptance of the collection. + Unaccepted, + /// The item is locked (non-transferable). + ItemLocked, + /// Item's attributes are locked. + LockedItemAttributes, + /// Collection's attributes are locked. + LockedCollectionAttributes, + /// Item's metadata is locked. + LockedItemMetadata, + /// Collection's metadata is locked. + LockedCollectionMetadata, + /// All items have been minted. + MaxSupplyReached, + /// The max supply is locked and can't be changed. + MaxSupplyLocked, + /// The provided max supply is less than the number of items a collection already has. + MaxSupplyTooSmall, + /// The given item ID is unknown. + UnknownItem, + /// Swap doesn't exist. + UnknownSwap, + /// Item is not for sale. + NotForSale, + /// The provided bid is too low. + BidTooLow, + /// The item has reached its approval limit. + ReachedApprovalLimit, + /// The deadline has already expired. + DeadlineExpired, + /// The duration provided should be less than or equal to `MaxDeadlineDuration`. + WrongDuration, + /// The method is disabled by system settings. + MethodDisabled, + /// The provided setting can't be set. + WrongSetting, + /// Item's config already exists and should be equal to the provided one. + InconsistentItemConfig, + /// Config for a collection or an item can't be found. + NoConfig, + /// Some roles were not cleared. + RolesNotCleared, + /// Mint has not started yet. + MintNotStarted, + /// Mint has already ended. + MintEnded, + /// The provided Item was already used for claiming. + AlreadyClaimed, + /// The provided data is incorrect. + IncorrectData, + } + + #[pallet::call] + impl, I: 'static> Pallet { + /// Issue a new collection of non-fungible items from a public origin. + /// + /// This new collection has no items initially and its owner is the origin. + /// + /// The origin must be Signed and the sender must have sufficient funds free. + /// + /// `ItemDeposit` funds of sender are reserved. + /// + /// Parameters: + /// - `admin`: The admin of this collection. The admin is the initial address of each + /// member of the collection's admin team. + /// + /// Emits `Created` event when successful. + /// + /// Weight: `O(1)` + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::create())] + pub fn create( + origin: OriginFor, + admin: AccountIdLookupOf, + config: CollectionConfigFor, + ) -> DispatchResult { + let collection = + NextCollectionId::::get().unwrap_or(T::CollectionId::initial_value()); + + let owner = T::CreateOrigin::ensure_origin(origin, &collection)?; + let admin = T::Lookup::lookup(admin)?; + + // DepositRequired can be disabled by calling the force_create() only + ensure!( + !config.has_disabled_setting(CollectionSetting::DepositRequired), + Error::::WrongSetting + ); + + Self::do_create_collection( + collection, + owner.clone(), + admin.clone(), + config, + T::CollectionDeposit::get(), + Event::Created { collection, creator: owner, owner: admin }, + ) + } + + /// Issue a new collection of non-fungible items from a privileged origin. + /// + /// This new collection has no items initially. + /// + /// The origin must conform to `ForceOrigin`. + /// + /// Unlike `create`, no funds are reserved. + /// + /// - `owner`: The owner of this collection of items. The owner has full superuser + /// permissions over this item, but may later change and configure the permissions using + /// `transfer_ownership` and `set_team`. + /// + /// Emits `ForceCreated` event when successful. + /// + /// Weight: `O(1)` + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::force_create())] + pub fn force_create( + origin: OriginFor, + owner: AccountIdLookupOf, + config: CollectionConfigFor, + ) -> DispatchResult { + T::ForceOrigin::ensure_origin(origin)?; + let owner = T::Lookup::lookup(owner)?; + + let collection = + NextCollectionId::::get().unwrap_or(T::CollectionId::initial_value()); + + Self::do_create_collection( + collection, + owner.clone(), + owner.clone(), + config, + Zero::zero(), + Event::ForceCreated { collection, owner }, + ) + } + + /// Destroy a collection of fungible items. + /// + /// The origin must conform to `ForceOrigin` or must be `Signed` and the sender must be the + /// owner of the `collection`. + /// + /// - `collection`: The identifier of the collection to be destroyed. + /// - `witness`: Information on the items minted in the collection. This must be + /// correct. + /// + /// Emits `Destroyed` event when successful. + /// + /// Weight: `O(n + m)` where: + /// - `n = witness.items` + /// - `m = witness.item_metadatas` + /// - `a = witness.attributes` + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::destroy( + witness.items, + witness.item_metadatas, + witness.attributes, + ))] + pub fn destroy( + origin: OriginFor, + collection: T::CollectionId, + witness: DestroyWitness, + ) -> DispatchResultWithPostInfo { + let maybe_check_owner = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; + let details = Self::do_destroy_collection(collection, witness, maybe_check_owner)?; + + Ok(Some(T::WeightInfo::destroy( + details.items, + details.item_metadatas, + details.attributes, + )) + .into()) + } + + /// Mint an item of a particular collection. + /// + /// The origin must be Signed and the sender must be the Issuer of the `collection`. + /// + /// - `collection`: The collection of the item to be minted. + /// - `item`: An identifier of the new item. + /// - `mint_to`: Account into which the item will be minted. + /// - `witness_data`: When the mint type is `HolderOf(collection_id)`, then the owned + /// item_id from that collection needs to be provided within the witness data object. + /// + /// Note: the deposit will be taken from the `origin` and not the `owner` of the `item`. + /// + /// Emits `Issued` event when successful. + /// + /// Weight: `O(1)` + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::mint())] + pub fn mint( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + mint_to: AccountIdLookupOf, + witness_data: Option>, + ) -> DispatchResult { + let caller = ensure_signed(origin)?; + let mint_to = T::Lookup::lookup(mint_to)?; + + let collection_config = Self::get_collection_config(&collection)?; + let item_settings = collection_config.mint_settings.default_item_settings; + let item_config = ItemConfig { settings: item_settings }; + + Self::do_mint( + collection, + item, + caller.clone(), + mint_to.clone(), + item_config, + false, + |collection_details, collection_config| { + // Issuer can mint regardless of mint settings + if Self::has_role(&collection, &caller, CollectionRole::Issuer) { + return Ok(()) + } + + let mint_settings = collection_config.mint_settings; + let now = frame_system::Pallet::::block_number(); + + if let Some(start_block) = mint_settings.start_block { + ensure!(start_block <= now, Error::::MintNotStarted); + } + if let Some(end_block) = mint_settings.end_block { + ensure!(end_block >= now, Error::::MintEnded); + } + + match mint_settings.mint_type { + MintType::Issuer => return Err(Error::::NoPermission.into()), + MintType::HolderOf(collection_id) => { + let MintWitness { owner_of_item } = + witness_data.ok_or(Error::::BadWitness)?; + + let has_item = Account::::contains_key(( + &caller, + &collection_id, + &owner_of_item, + )); + ensure!(has_item, Error::::BadWitness); + + let attribute_key = Self::construct_attribute_key( + PalletAttributes::::UsedToClaim(collection) + .encode(), + )?; + + let key = ( + &collection_id, + Some(owner_of_item), + AttributeNamespace::Pallet, + &attribute_key, + ); + let already_claimed = Attribute::::contains_key(key.clone()); + ensure!(!already_claimed, Error::::AlreadyClaimed); + + let value = Self::construct_attribute_value(vec![0])?; + Attribute::::insert( + key, + (value, AttributeDeposit { account: None, amount: Zero::zero() }), + ); + }, + _ => {}, + } + + if let Some(price) = mint_settings.price { + T::Currency::transfer( + &caller, + &collection_details.owner, + price, + ExistenceRequirement::KeepAlive, + )?; + } + + Ok(()) + }, + ) + } + + /// Mint an item of a particular collection from a privileged origin. + /// + /// The origin must conform to `ForceOrigin` or must be `Signed` and the sender must be the + /// Issuer of the `collection`. + /// + /// - `collection`: The collection of the item to be minted. + /// - `item`: An identifier of the new item. + /// - `mint_to`: Account into which the item will be minted. + /// - `item_config`: A config of the new item. + /// + /// Emits `Issued` event when successful. + /// + /// Weight: `O(1)` + #[pallet::call_index(4)] + #[pallet::weight(T::WeightInfo::force_mint())] + pub fn force_mint( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + mint_to: AccountIdLookupOf, + item_config: ItemConfig, + ) -> DispatchResult { + let maybe_check_origin = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; + let mint_to = T::Lookup::lookup(mint_to)?; + + if let Some(check_origin) = maybe_check_origin { + ensure!( + Self::has_role(&collection, &check_origin, CollectionRole::Issuer), + Error::::NoPermission + ); + } + Self::do_mint(collection, item, mint_to.clone(), mint_to, item_config, true, |_, _| { + Ok(()) + }) + } + + /// Destroy a single item. + /// + /// Origin must be Signed and the sender should be the Admin of the `collection`. + /// + /// - `collection`: The collection of the item to be burned. + /// - `item`: The item to be burned. + /// - `check_owner`: If `Some` then the operation will fail with `WrongOwner` unless the + /// item is owned by this value. + /// + /// Emits `Burned` with the actual amount burned. + /// + /// Weight: `O(1)` + /// Modes: `check_owner.is_some()`. + #[pallet::call_index(5)] + #[pallet::weight(T::WeightInfo::burn())] + pub fn burn( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + check_owner: Option>, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + let check_owner = check_owner.map(T::Lookup::lookup).transpose()?; + + Self::do_burn(collection, item, |details| { + let is_admin = Self::has_role(&collection, &origin, CollectionRole::Admin); + let is_permitted = is_admin || details.owner == origin; + ensure!(is_permitted, Error::::NoPermission); + ensure!( + check_owner.map_or(true, |o| o == details.owner), + Error::::WrongOwner + ); + Ok(()) + }) + } + + /// Move an item from the sender account to another. + /// + /// Origin must be Signed and the signing account must be either: + /// - the Admin of the `collection`; + /// - the Owner of the `item`; + /// - the approved delegate for the `item` (in this case, the approval is reset). + /// + /// Arguments: + /// - `collection`: The collection of the item to be transferred. + /// - `item`: The item to be transferred. + /// - `dest`: The account to receive ownership of the item. + /// + /// Emits `Transferred`. + /// + /// Weight: `O(1)` + #[pallet::call_index(6)] + #[pallet::weight(T::WeightInfo::transfer())] + pub fn transfer( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + dest: AccountIdLookupOf, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + let dest = T::Lookup::lookup(dest)?; + + Self::do_transfer(collection, item, dest, |_, details| { + let is_admin = Self::has_role(&collection, &origin, CollectionRole::Admin); + if details.owner != origin && !is_admin { + let deadline = + details.approvals.get(&origin).ok_or(Error::::NoPermission)?; + if let Some(d) = deadline { + let block_number = frame_system::Pallet::::block_number(); + ensure!(block_number <= *d, Error::::ApprovalExpired); + } + } + Ok(()) + }) + } + + /// Re-evaluate the deposits on some items. + /// + /// Origin must be Signed and the sender should be the Owner of the `collection`. + /// + /// - `collection`: The collection of the items to be reevaluated. + /// - `items`: The items of the collection whose deposits will be reevaluated. + /// + /// NOTE: This exists as a best-effort function. Any items which are unknown or + /// in the case that the owner account does not have reservable funds to pay for a + /// deposit increase are ignored. Generally the owner isn't going to call this on items + /// whose existing deposit is less than the refreshed deposit as it would only cost them, + /// so it's of little consequence. + /// + /// It will still return an error in the case that the collection is unknown or the signer + /// is not permitted to call it. + /// + /// Weight: `O(items.len())` + #[pallet::call_index(7)] + #[pallet::weight(T::WeightInfo::redeposit(items.len() as u32))] + pub fn redeposit( + origin: OriginFor, + collection: T::CollectionId, + items: Vec, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + + let collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + ensure!(collection_details.owner == origin, Error::::NoPermission); + + let config = Self::get_collection_config(&collection)?; + let deposit = match config.is_setting_enabled(CollectionSetting::DepositRequired) { + true => T::ItemDeposit::get(), + false => Zero::zero(), + }; + + let mut successful = Vec::with_capacity(items.len()); + for item in items.into_iter() { + let mut details = match Item::::get(&collection, &item) { + Some(x) => x, + None => continue, + }; + let old = details.deposit.amount; + if old > deposit { + T::Currency::unreserve(&details.deposit.account, old - deposit); + } else if deposit > old { + if T::Currency::reserve(&details.deposit.account, deposit - old).is_err() { + // NOTE: No alterations made to collection_details in this iteration so far, + // so this is OK to do. + continue + } + } else { + continue + } + details.deposit.amount = deposit; + Item::::insert(&collection, &item, &details); + successful.push(item); + } + + Self::deposit_event(Event::::Redeposited { + collection, + successful_items: successful, + }); + + Ok(()) + } + + /// Disallow further unprivileged transfer of an item. + /// + /// Origin must be Signed and the sender should be the Freezer of the `collection`. + /// + /// - `collection`: The collection of the item to be changed. + /// - `item`: The item to become non-transferable. + /// + /// Emits `ItemTransferLocked`. + /// + /// Weight: `O(1)` + #[pallet::call_index(8)] + #[pallet::weight(T::WeightInfo::lock_item_transfer())] + pub fn lock_item_transfer( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + Self::do_lock_item_transfer(origin, collection, item) + } + + /// Re-allow unprivileged transfer of an item. + /// + /// Origin must be Signed and the sender should be the Freezer of the `collection`. + /// + /// - `collection`: The collection of the item to be changed. + /// - `item`: The item to become transferable. + /// + /// Emits `ItemTransferUnlocked`. + /// + /// Weight: `O(1)` + #[pallet::call_index(9)] + #[pallet::weight(T::WeightInfo::unlock_item_transfer())] + pub fn unlock_item_transfer( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + Self::do_unlock_item_transfer(origin, collection, item) + } + + /// Disallows specified settings for the whole collection. + /// + /// Origin must be Signed and the sender should be the Freezer of the `collection`. + /// + /// - `collection`: The collection to be locked. + /// - `lock_settings`: The settings to be locked. + /// + /// Note: it's possible to only lock(set) the setting, but not to unset it. + /// Emits `CollectionLocked`. + /// + /// Weight: `O(1)` + #[pallet::call_index(10)] + #[pallet::weight(T::WeightInfo::lock_collection())] + pub fn lock_collection( + origin: OriginFor, + collection: T::CollectionId, + lock_settings: CollectionSettings, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + Self::do_lock_collection(origin, collection, lock_settings) + } + + /// Change the Owner of a collection. + /// + /// Origin must be Signed and the sender should be the Owner of the `collection`. + /// + /// - `collection`: The collection whose owner should be changed. + /// - `owner`: The new Owner of this collection. They must have called + /// `set_accept_ownership` with `collection` in order for this operation to succeed. + /// + /// Emits `OwnerChanged`. + /// + /// Weight: `O(1)` + #[pallet::call_index(11)] + #[pallet::weight(T::WeightInfo::transfer_ownership())] + pub fn transfer_ownership( + origin: OriginFor, + collection: T::CollectionId, + owner: AccountIdLookupOf, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + let owner = T::Lookup::lookup(owner)?; + Self::do_transfer_ownership(origin, collection, owner) + } + + /// Change the Issuer, Admin and Freezer of a collection. + /// + /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the + /// `collection`. + /// + /// - `collection`: The collection whose team should be changed. + /// - `issuer`: The new Issuer of this collection. + /// - `admin`: The new Admin of this collection. + /// - `freezer`: The new Freezer of this collection. + /// + /// Emits `TeamChanged`. + /// + /// Weight: `O(1)` + #[pallet::call_index(12)] + #[pallet::weight(T::WeightInfo::set_team())] + pub fn set_team( + origin: OriginFor, + collection: T::CollectionId, + issuer: AccountIdLookupOf, + admin: AccountIdLookupOf, + freezer: AccountIdLookupOf, + ) -> DispatchResult { + let maybe_check_owner = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; + let issuer = T::Lookup::lookup(issuer)?; + let admin = T::Lookup::lookup(admin)?; + let freezer = T::Lookup::lookup(freezer)?; + Self::do_set_team(maybe_check_owner, collection, issuer, admin, freezer) + } + + /// Change the Owner of a collection. + /// + /// Origin must be `ForceOrigin`. + /// + /// - `collection`: The identifier of the collection. + /// - `owner`: The new Owner of this collection. + /// + /// Emits `OwnerChanged`. + /// + /// Weight: `O(1)` + #[pallet::call_index(13)] + #[pallet::weight(T::WeightInfo::force_collection_owner())] + pub fn force_collection_owner( + origin: OriginFor, + collection: T::CollectionId, + owner: AccountIdLookupOf, + ) -> DispatchResult { + T::ForceOrigin::ensure_origin(origin)?; + let new_owner = T::Lookup::lookup(owner)?; + Self::do_force_collection_owner(collection, new_owner) + } + + /// Change the config of a collection. + /// + /// Origin must be `ForceOrigin`. + /// + /// - `collection`: The identifier of the collection. + /// - `config`: The new config of this collection. + /// + /// Emits `CollectionConfigChanged`. + /// + /// Weight: `O(1)` + #[pallet::call_index(14)] + #[pallet::weight(T::WeightInfo::force_collection_config())] + pub fn force_collection_config( + origin: OriginFor, + collection: T::CollectionId, + config: CollectionConfigFor, + ) -> DispatchResult { + T::ForceOrigin::ensure_origin(origin)?; + Self::do_force_collection_config(collection, config) + } + + /// Approve an item to be transferred by a delegated third-party account. + /// + /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the + /// `item`. + /// + /// - `collection`: The collection of the item to be approved for delegated transfer. + /// - `item`: The item to be approved for delegated transfer. + /// - `delegate`: The account to delegate permission to transfer the item. + /// - `maybe_deadline`: Optional deadline for the approval. Specified by providing the + /// number of blocks after which the approval will expire + /// + /// Emits `TransferApproved` on success. + /// + /// Weight: `O(1)` + #[pallet::call_index(15)] + #[pallet::weight(T::WeightInfo::approve_transfer())] + pub fn approve_transfer( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + delegate: AccountIdLookupOf, + maybe_deadline: Option<::BlockNumber>, + ) -> DispatchResult { + let maybe_check_origin = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; + let delegate = T::Lookup::lookup(delegate)?; + Self::do_approve_transfer( + maybe_check_origin, + collection, + item, + delegate, + maybe_deadline, + ) + } + + /// Cancel one of the transfer approvals for a specific item. + /// + /// Origin must be either: + /// - the `Force` origin; + /// - `Signed` with the signer being the Admin of the `collection`; + /// - `Signed` with the signer being the Owner of the `item`; + /// + /// Arguments: + /// - `collection`: The collection of the item of whose approval will be cancelled. + /// - `item`: The item of the collection of whose approval will be cancelled. + /// - `delegate`: The account that is going to loose their approval. + /// + /// Emits `ApprovalCancelled` on success. + /// + /// Weight: `O(1)` + #[pallet::call_index(16)] + #[pallet::weight(T::WeightInfo::cancel_approval())] + pub fn cancel_approval( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + delegate: AccountIdLookupOf, + ) -> DispatchResult { + let maybe_check_origin = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; + let delegate = T::Lookup::lookup(delegate)?; + Self::do_cancel_approval(maybe_check_origin, collection, item, delegate) + } + + /// Cancel all the approvals of a specific item. + /// + /// Origin must be either: + /// - the `Force` origin; + /// - `Signed` with the signer being the Admin of the `collection`; + /// - `Signed` with the signer being the Owner of the `item`; + /// + /// Arguments: + /// - `collection`: The collection of the item of whose approvals will be cleared. + /// - `item`: The item of the collection of whose approvals will be cleared. + /// + /// Emits `AllApprovalsCancelled` on success. + /// + /// Weight: `O(1)` + #[pallet::call_index(17)] + #[pallet::weight(T::WeightInfo::clear_all_transfer_approvals())] + pub fn clear_all_transfer_approvals( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + ) -> DispatchResult { + let maybe_check_origin = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; + Self::do_clear_all_transfer_approvals(maybe_check_origin, collection, item) + } + + /// Disallows changing the metadata or attributes of the item. + /// + /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the + /// `collection`. + /// + /// - `collection`: The collection if the `item`. + /// - `item`: An item to be locked. + /// - `lock_metadata`: Specifies whether the metadata should be locked. + /// - `lock_attributes`: Specifies whether the attributes in the `CollectionOwner` namespace + /// should be locked. + /// + /// Note: `lock_attributes` affects the attributes in the `CollectionOwner` namespace + /// only. When the metadata or attributes are locked, it won't be possible the unlock them. + /// + /// Emits `ItemPropertiesLocked`. + /// + /// Weight: `O(1)` + #[pallet::call_index(18)] + #[pallet::weight(T::WeightInfo::lock_item_properties())] + pub fn lock_item_properties( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + lock_metadata: bool, + lock_attributes: bool, + ) -> DispatchResult { + let maybe_check_owner = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; + Self::do_lock_item_properties( + maybe_check_owner, + collection, + item, + lock_metadata, + lock_attributes, + ) + } + + /// Set an attribute for a collection or item. + /// + /// Origin must be Signed and must conform to the namespace ruleset: + /// - `CollectionOwner` namespace could be modified by the `collection` owner only; + /// - `ItemOwner` namespace could be modified by the `maybe_item` owner only. `maybe_item` + /// should be set in that case; + /// - `Account(AccountId)` namespace could be modified only when the `origin` was given a + /// permission to do so; + /// + /// The funds of `origin` are reserved according to the formula: + /// `AttributeDepositBase + DepositPerByte * (key.len + value.len)` taking into + /// account any already reserved funds. + /// + /// - `collection`: The identifier of the collection whose item's metadata to set. + /// - `maybe_item`: The identifier of the item whose metadata to set. + /// - `namespace`: Attribute's namespace. + /// - `key`: The key of the attribute. + /// - `value`: The value to which to set the attribute. + /// + /// Emits `AttributeSet`. + /// + /// Weight: `O(1)` + #[pallet::call_index(19)] + #[pallet::weight(T::WeightInfo::set_attribute())] + pub fn set_attribute( + origin: OriginFor, + collection: T::CollectionId, + maybe_item: Option, + namespace: AttributeNamespace, + key: BoundedVec, + value: BoundedVec, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + Self::do_set_attribute(origin, collection, maybe_item, namespace, key, value) + } + + /// Force-set an attribute for a collection or item. + /// + /// Origin must be `ForceOrigin`. + /// + /// If the attribute already exists and it was set by another account, the deposit + /// will be returned to the previous owner. + /// + /// - `set_as`: An optional owner of the attribute. + /// - `collection`: The identifier of the collection whose item's metadata to set. + /// - `maybe_item`: The identifier of the item whose metadata to set. + /// - `namespace`: Attribute's namespace. + /// - `key`: The key of the attribute. + /// - `value`: The value to which to set the attribute. + /// + /// Emits `AttributeSet`. + /// + /// Weight: `O(1)` + #[pallet::call_index(20)] + #[pallet::weight(T::WeightInfo::force_set_attribute())] + pub fn force_set_attribute( + origin: OriginFor, + set_as: Option, + collection: T::CollectionId, + maybe_item: Option, + namespace: AttributeNamespace, + key: BoundedVec, + value: BoundedVec, + ) -> DispatchResult { + T::ForceOrigin::ensure_origin(origin)?; + Self::do_force_set_attribute(set_as, collection, maybe_item, namespace, key, value) + } + + /// Clear an attribute for a collection or item. + /// + /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the + /// `collection`. + /// + /// Any deposit is freed for the collection's owner. + /// + /// - `collection`: The identifier of the collection whose item's metadata to clear. + /// - `maybe_item`: The identifier of the item whose metadata to clear. + /// - `namespace`: Attribute's namespace. + /// - `key`: The key of the attribute. + /// + /// Emits `AttributeCleared`. + /// + /// Weight: `O(1)` + #[pallet::call_index(21)] + #[pallet::weight(T::WeightInfo::clear_attribute())] + pub fn clear_attribute( + origin: OriginFor, + collection: T::CollectionId, + maybe_item: Option, + namespace: AttributeNamespace, + key: BoundedVec, + ) -> DispatchResult { + let maybe_check_owner = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; + Self::do_clear_attribute(maybe_check_owner, collection, maybe_item, namespace, key) + } + + /// Approve item's attributes to be changed by a delegated third-party account. + /// + /// Origin must be Signed and must be an owner of the `item`. + /// + /// - `collection`: A collection of the item. + /// - `item`: The item that holds attributes. + /// - `delegate`: The account to delegate permission to change attributes of the item. + /// + /// Emits `ItemAttributesApprovalAdded` on success. + #[pallet::call_index(22)] + #[pallet::weight(T::WeightInfo::approve_item_attributes())] + pub fn approve_item_attributes( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + delegate: AccountIdLookupOf, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + let delegate = T::Lookup::lookup(delegate)?; + Self::do_approve_item_attributes(origin, collection, item, delegate) + } + + /// Cancel the previously provided approval to change item's attributes. + /// All the previously set attributes by the `delegate` will be removed. + /// + /// Origin must be Signed and must be an owner of the `item`. + /// + /// - `collection`: Collection that the item is contained within. + /// - `item`: The item that holds attributes. + /// - `delegate`: The previously approved account to remove. + /// + /// Emits `ItemAttributesApprovalRemoved` on success. + #[pallet::call_index(23)] + #[pallet::weight(T::WeightInfo::cancel_item_attributes_approval( + witness.account_attributes + ))] + pub fn cancel_item_attributes_approval( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + delegate: AccountIdLookupOf, + witness: CancelAttributesApprovalWitness, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + let delegate = T::Lookup::lookup(delegate)?; + Self::do_cancel_item_attributes_approval(origin, collection, item, delegate, witness) + } + + /// Set the metadata for an item. + /// + /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the + /// `collection`. + /// + /// If the origin is Signed, then funds of signer are reserved according to the formula: + /// `MetadataDepositBase + DepositPerByte * data.len` taking into + /// account any already reserved funds. + /// + /// - `collection`: The identifier of the collection whose item's metadata to set. + /// - `item`: The identifier of the item whose metadata to set. + /// - `data`: The general information of this item. Limited in length by `StringLimit`. + /// + /// Emits `ItemMetadataSet`. + /// + /// Weight: `O(1)` + #[pallet::call_index(24)] + #[pallet::weight(T::WeightInfo::set_metadata())] + pub fn set_metadata( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + data: BoundedVec, + ) -> DispatchResult { + let maybe_check_owner = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; + Self::do_set_item_metadata(maybe_check_owner, collection, item, data) + } + + /// Clear the metadata for an item. + /// + /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the + /// `collection`. + /// + /// Any deposit is freed for the collection's owner. + /// + /// - `collection`: The identifier of the collection whose item's metadata to clear. + /// - `item`: The identifier of the item whose metadata to clear. + /// + /// Emits `ItemMetadataCleared`. + /// + /// Weight: `O(1)` + #[pallet::call_index(25)] + #[pallet::weight(T::WeightInfo::clear_metadata())] + pub fn clear_metadata( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + ) -> DispatchResult { + let maybe_check_owner = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; + Self::do_clear_item_metadata(maybe_check_owner, collection, item) + } + + /// Set the metadata for a collection. + /// + /// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Owner of + /// the `collection`. + /// + /// If the origin is `Signed`, then funds of signer are reserved according to the formula: + /// `MetadataDepositBase + DepositPerByte * data.len` taking into + /// account any already reserved funds. + /// + /// - `collection`: The identifier of the item whose metadata to update. + /// - `data`: The general information of this item. Limited in length by `StringLimit`. + /// + /// Emits `CollectionMetadataSet`. + /// + /// Weight: `O(1)` + #[pallet::call_index(26)] + #[pallet::weight(T::WeightInfo::set_collection_metadata())] + pub fn set_collection_metadata( + origin: OriginFor, + collection: T::CollectionId, + data: BoundedVec, + ) -> DispatchResult { + let maybe_check_owner = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; + Self::do_set_collection_metadata(maybe_check_owner, collection, data) + } + + /// Clear the metadata for a collection. + /// + /// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Owner of + /// the `collection`. + /// + /// Any deposit is freed for the collection's owner. + /// + /// - `collection`: The identifier of the collection whose metadata to clear. + /// + /// Emits `CollectionMetadataCleared`. + /// + /// Weight: `O(1)` + #[pallet::call_index(27)] + #[pallet::weight(T::WeightInfo::clear_collection_metadata())] + pub fn clear_collection_metadata( + origin: OriginFor, + collection: T::CollectionId, + ) -> DispatchResult { + let maybe_check_owner = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; + Self::do_clear_collection_metadata(maybe_check_owner, collection) + } + + /// Set (or reset) the acceptance of ownership for a particular account. + /// + /// Origin must be `Signed` and if `maybe_collection` is `Some`, then the signer must have a + /// provider reference. + /// + /// - `maybe_collection`: The identifier of the collection whose ownership the signer is + /// willing to accept, or if `None`, an indication that the signer is willing to accept no + /// ownership transferal. + /// + /// Emits `OwnershipAcceptanceChanged`. + #[pallet::call_index(28)] + #[pallet::weight(T::WeightInfo::set_accept_ownership())] + pub fn set_accept_ownership( + origin: OriginFor, + maybe_collection: Option, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::do_set_accept_ownership(who, maybe_collection) + } + + /// Set the maximum number of items a collection could have. + /// + /// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Owner of + /// the `collection`. + /// + /// - `collection`: The identifier of the collection to change. + /// - `max_supply`: The maximum number of items a collection could have. + /// + /// Emits `CollectionMaxSupplySet` event when successful. + #[pallet::call_index(29)] + #[pallet::weight(T::WeightInfo::set_collection_max_supply())] + pub fn set_collection_max_supply( + origin: OriginFor, + collection: T::CollectionId, + max_supply: u32, + ) -> DispatchResult { + let maybe_check_owner = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; + Self::do_set_collection_max_supply(maybe_check_owner, collection, max_supply) + } + + /// Update mint settings. + /// + /// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Owner of + /// the `collection`. + /// + /// - `collection`: The identifier of the collection to change. + /// - `mint_settings`: The new mint settings. + /// + /// Emits `CollectionMintSettingsUpdated` event when successful. + #[pallet::call_index(30)] + #[pallet::weight(T::WeightInfo::update_mint_settings())] + pub fn update_mint_settings( + origin: OriginFor, + collection: T::CollectionId, + mint_settings: MintSettings< + BalanceOf, + ::BlockNumber, + T::CollectionId, + >, + ) -> DispatchResult { + let maybe_check_owner = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; + Self::do_update_mint_settings(maybe_check_owner, collection, mint_settings) + } + + /// Set (or reset) the price for an item. + /// + /// Origin must be Signed and must be the owner of the asset `item`. + /// + /// - `collection`: The collection of the item. + /// - `item`: The item to set the price for. + /// - `price`: The price for the item. Pass `None`, to reset the price. + /// - `buyer`: Restricts the buy operation to a specific account. + /// + /// Emits `ItemPriceSet` on success if the price is not `None`. + /// Emits `ItemPriceRemoved` on success if the price is `None`. + #[pallet::call_index(31)] + #[pallet::weight(T::WeightInfo::set_price())] + pub fn set_price( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + price: Option>, + whitelisted_buyer: Option>, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + let whitelisted_buyer = whitelisted_buyer.map(T::Lookup::lookup).transpose()?; + Self::do_set_price(collection, item, origin, price, whitelisted_buyer) + } + + /// Allows to buy an item if it's up for sale. + /// + /// Origin must be Signed and must not be the owner of the `item`. + /// + /// - `collection`: The collection of the item. + /// - `item`: The item the sender wants to buy. + /// - `bid_price`: The price the sender is willing to pay. + /// + /// Emits `ItemBought` on success. + #[pallet::call_index(32)] + #[pallet::weight(T::WeightInfo::buy_item())] + pub fn buy_item( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + bid_price: ItemPrice, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + Self::do_buy_item(collection, item, origin, bid_price) + } + + /// Allows to pay the tips. + /// + /// Origin must be Signed. + /// + /// - `tips`: Tips array. + /// + /// Emits `TipSent` on every tip transfer. + #[pallet::call_index(33)] + #[pallet::weight(T::WeightInfo::pay_tips(tips.len() as u32))] + pub fn pay_tips( + origin: OriginFor, + tips: BoundedVec, T::MaxTips>, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + Self::do_pay_tips(origin, tips) + } + + /// Register a new atomic swap, declaring an intention to send an `item` in exchange for + /// `desired_item` from origin to target on the current blockchain. + /// The target can execute the swap during the specified `duration` of blocks (if set). + /// Additionally, the price could be set for the desired `item`. + /// + /// Origin must be Signed and must be an owner of the `item`. + /// + /// - `collection`: The collection of the item. + /// - `item`: The item an owner wants to give. + /// - `desired_collection`: The collection of the desired item. + /// - `desired_item`: The desired item an owner wants to receive. + /// - `maybe_price`: The price an owner is willing to pay or receive for the desired `item`. + /// - `duration`: A deadline for the swap. Specified by providing the number of blocks + /// after which the swap will expire. + /// + /// Emits `SwapCreated` on success. + #[pallet::call_index(34)] + #[pallet::weight(T::WeightInfo::create_swap())] + pub fn create_swap( + origin: OriginFor, + offered_collection: T::CollectionId, + offered_item: T::ItemId, + desired_collection: T::CollectionId, + maybe_desired_item: Option, + maybe_price: Option>>, + duration: ::BlockNumber, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + Self::do_create_swap( + origin, + offered_collection, + offered_item, + desired_collection, + maybe_desired_item, + maybe_price, + duration, + ) + } + + /// Cancel an atomic swap. + /// + /// Origin must be Signed. + /// Origin must be an owner of the `item` if the deadline hasn't expired. + /// + /// - `collection`: The collection of the item. + /// - `item`: The item an owner wants to give. + /// + /// Emits `SwapCancelled` on success. + #[pallet::call_index(35)] + #[pallet::weight(T::WeightInfo::cancel_swap())] + pub fn cancel_swap( + origin: OriginFor, + offered_collection: T::CollectionId, + offered_item: T::ItemId, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + Self::do_cancel_swap(origin, offered_collection, offered_item) + } + + /// Claim an atomic swap. + /// This method executes a pending swap, that was created by a counterpart before. + /// + /// Origin must be Signed and must be an owner of the `item`. + /// + /// - `send_collection`: The collection of the item to be sent. + /// - `send_item`: The item to be sent. + /// - `receive_collection`: The collection of the item to be received. + /// - `receive_item`: The item to be received. + /// - `witness_price`: A price that was previously agreed on. + /// + /// Emits `SwapClaimed` on success. + #[pallet::call_index(36)] + #[pallet::weight(T::WeightInfo::claim_swap())] + pub fn claim_swap( + origin: OriginFor, + send_collection: T::CollectionId, + send_item: T::ItemId, + receive_collection: T::CollectionId, + receive_item: T::ItemId, + witness_price: Option>>, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + Self::do_claim_swap( + origin, + send_collection, + send_item, + receive_collection, + receive_item, + witness_price, + ) + } + } +} diff --git a/frame/nfts/src/macros.rs b/frame/nfts/src/macros.rs new file mode 100644 index 0000000000..07a8f3b9f9 --- /dev/null +++ b/frame/nfts/src/macros.rs @@ -0,0 +1,74 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +macro_rules! impl_incrementable { + ($($type:ty),+) => { + $( + impl Incrementable for $type { + fn increment(&self) -> Self { + let mut val = self.clone(); + val.saturating_inc(); + val + } + + fn initial_value() -> Self { + 0 + } + } + )+ + }; +} +pub(crate) use impl_incrementable; + +macro_rules! impl_codec_bitflags { + ($wrapper:ty, $size:ty, $bitflag_enum:ty) => { + impl MaxEncodedLen for $wrapper { + fn max_encoded_len() -> usize { + <$size>::max_encoded_len() + } + } + impl Encode for $wrapper { + fn using_encoded R>(&self, f: F) -> R { + self.0.bits().using_encoded(f) + } + } + impl EncodeLike for $wrapper {} + impl Decode for $wrapper { + fn decode( + input: &mut I, + ) -> sp_std::result::Result { + let field = <$size>::decode(input)?; + Ok(Self(BitFlags::from_bits(field as $size).map_err(|_| "invalid value")?)) + } + } + + impl TypeInfo for $wrapper { + type Identity = Self; + + fn type_info() -> Type { + Type::builder() + .path(Path::new("BitFlags", module_path!())) + .type_params(vec![TypeParameter::new("T", Some(meta_type::<$bitflag_enum>()))]) + .composite( + Fields::unnamed() + .field(|f| f.ty::<$size>().type_name(stringify!($bitflag_enum))), + ) + } + } + }; +} +pub(crate) use impl_codec_bitflags; diff --git a/frame/nfts/src/mock.rs b/frame/nfts/src/mock.rs new file mode 100644 index 0000000000..f814b209d5 --- /dev/null +++ b/frame/nfts/src/mock.rs @@ -0,0 +1,123 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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. + +//! Test environment for Nfts pallet. + +use super::*; +use crate as pallet_nfts; + +use frame_support::{ + construct_runtime, parameter_types, + traits::{AsEnsureOriginWithArg, ConstU32, ConstU64}, +}; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, +}; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Nfts: pallet_nfts::{Pallet, Call, Storage, Event}, + } +); + +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +impl pallet_balances::Config for Test { + type Balance = u64; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU64<1>; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = (); + type MaxReserves = ConstU32<50>; + type ReserveIdentifier = [u8; 8]; +} + +parameter_types! { + pub storage Features: PalletFeatures = PalletFeatures::all_enabled(); +} + +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type CollectionId = u32; + type ItemId = u32; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = frame_system::EnsureRoot; + type Locker = (); + type CollectionDeposit = ConstU64<2>; + type ItemDeposit = ConstU64<1>; + type MetadataDepositBase = ConstU64<1>; + type AttributeDepositBase = ConstU64<1>; + type DepositPerByte = ConstU64<1>; + type StringLimit = ConstU32<50>; + type KeyLimit = ConstU32<50>; + type ValueLimit = ConstU32<50>; + type ApprovalsLimit = ConstU32<10>; + type ItemAttributesApprovalsLimit = ConstU32<2>; + type MaxTips = ConstU32<10>; + type MaxDeadlineDuration = ConstU64<10000>; + type Features = Features; + type WeightInfo = (); + #[cfg(feature = "runtime-benchmarks")] + type Helper = (); +} + +pub(crate) fn new_test_ext() -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs new file mode 100644 index 0000000000..18a3fd83b4 --- /dev/null +++ b/frame/nfts/src/tests.rs @@ -0,0 +1,2484 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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. + +//! Tests for Nfts pallet. + +use crate::{mock::*, Event, *}; +use enumflags2::BitFlags; +use frame_support::{ + assert_noop, assert_ok, + dispatch::Dispatchable, + traits::{ + tokens::nonfungibles_v2::{Destroy, Mutate}, + Currency, Get, + }, +}; +use pallet_balances::Error as BalancesError; +use sp_core::bounded::BoundedVec; +use sp_std::prelude::*; + +fn items() -> Vec<(u64, u32, u32)> { + let mut r: Vec<_> = Account::::iter().map(|x| x.0).collect(); + r.sort(); + let mut s: Vec<_> = Item::::iter().map(|x| (x.2.owner, x.0, x.1)).collect(); + s.sort(); + assert_eq!(r, s); + for collection in Item::::iter() + .map(|x| x.0) + .scan(None, |s, item| { + if s.map_or(false, |last| last == item) { + *s = Some(item); + Some(None) + } else { + Some(Some(item)) + } + }) + .flatten() + { + let details = Collection::::get(collection).unwrap(); + let items = Item::::iter_prefix(collection).count() as u32; + assert_eq!(details.items, items); + } + r +} + +fn collections() -> Vec<(u64, u32)> { + let mut r: Vec<_> = CollectionAccount::::iter().map(|x| (x.0, x.1)).collect(); + r.sort(); + let mut s: Vec<_> = Collection::::iter().map(|x| (x.1.owner, x.0)).collect(); + s.sort(); + assert_eq!(r, s); + r +} + +macro_rules! bvec { + ($( $x:tt )*) => { + vec![$( $x )*].try_into().unwrap() + } +} + +fn attributes(collection: u32) -> Vec<(Option, AttributeNamespace, Vec, Vec)> { + let mut s: Vec<_> = Attribute::::iter_prefix((collection,)) + .map(|(k, v)| (k.0, k.1, k.2.into(), v.0.into())) + .collect(); + s.sort_by_key(|k: &(Option, AttributeNamespace, Vec, Vec)| k.0); + s.sort_by_key(|k: &(Option, AttributeNamespace, Vec, Vec)| k.2.clone()); + s +} + +fn approvals(collection_id: u32, item_id: u32) -> Vec<(u64, Option)> { + let item = Item::::get(collection_id, item_id).unwrap(); + let s: Vec<_> = item.approvals.into_iter().collect(); + s +} + +fn item_attributes_approvals(collection_id: u32, item_id: u32) -> Vec { + let approvals = ItemAttributesApprovalsOf::::get(collection_id, item_id); + let s: Vec<_> = approvals.into_iter().collect(); + s +} + +fn events() -> Vec> { + let result = System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let mock::RuntimeEvent::Nfts(inner) = e { Some(inner) } else { None }) + .collect::>(); + + System::reset_events(); + + result +} + +fn collection_config_from_disabled_settings( + settings: BitFlags, +) -> CollectionConfigFor { + CollectionConfig { + settings: CollectionSettings::from_disabled(settings), + max_supply: None, + mint_settings: MintSettings::default(), + } +} + +fn collection_config_with_all_settings_enabled() -> CollectionConfigFor { + CollectionConfig { + settings: CollectionSettings::all_enabled(), + max_supply: None, + mint_settings: MintSettings::default(), + } +} + +fn default_collection_config() -> CollectionConfigFor { + collection_config_from_disabled_settings(CollectionSetting::DepositRequired.into()) +} + +fn default_item_config() -> ItemConfig { + ItemConfig { settings: ItemSettings::all_enabled() } +} + +fn item_config_from_disabled_settings(settings: BitFlags) -> ItemConfig { + ItemConfig { settings: ItemSettings::from_disabled(settings) } +} + +#[test] +fn basic_setup_works() { + new_test_ext().execute_with(|| { + assert_eq!(items(), vec![]); + }); +} + +#[test] +fn basic_minting_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); + assert_eq!(collections(), vec![(1, 0)]); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, None)); + assert_eq!(items(), vec![(1, 0, 42)]); + + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 2, default_collection_config())); + assert_eq!(collections(), vec![(1, 0), (2, 1)]); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 1, 69, 1, None)); + // assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(2), 1, 69, 1, default_item_config())); + assert_eq!(items(), vec![(1, 0, 42), (1, 1, 69)]); + }); +} + +#[test] +fn lifecycle_should_work() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&2, 100); + assert_ok!(Nfts::create( + RuntimeOrigin::signed(1), + 1, + collection_config_with_all_settings_enabled() + )); + assert_eq!(Balances::reserved_balance(&1), 2); + assert_eq!(collections(), vec![(1, 0)]); + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0, 0])); + assert_eq!(Balances::reserved_balance(&1), 5); + assert!(CollectionMetadataOf::::contains_key(0)); + + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 10, default_item_config())); + assert_eq!(Balances::reserved_balance(&1), 6); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 69, 20, default_item_config())); + assert_eq!(Balances::reserved_balance(&1), 7); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 70, 1, None)); + assert_eq!(items(), vec![(1, 0, 70), (10, 0, 42), (20, 0, 69)]); + assert_eq!(Collection::::get(0).unwrap().items, 3); + assert_eq!(Collection::::get(0).unwrap().item_metadatas, 0); + + assert_eq!(Balances::reserved_balance(&2), 0); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 70, 2)); + assert_eq!(Balances::reserved_balance(&2), 1); + + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![42, 42])); + assert_eq!(Balances::reserved_balance(&1), 10); + assert!(ItemMetadataOf::::contains_key(0, 42)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 69, bvec![69, 69])); + assert_eq!(Balances::reserved_balance(&1), 13); + assert!(ItemMetadataOf::::contains_key(0, 69)); + + let w = Nfts::get_destroy_witness(&0).unwrap(); + assert_eq!(w.items, 3); + assert_eq!(w.item_metadatas, 2); + assert_ok!(Nfts::destroy(RuntimeOrigin::signed(1), 0, w)); + assert_eq!(Balances::reserved_balance(&1), 0); + + assert!(!Collection::::contains_key(0)); + assert!(!CollectionConfigOf::::contains_key(0)); + assert!(!Item::::contains_key(0, 42)); + assert!(!Item::::contains_key(0, 69)); + assert!(!CollectionMetadataOf::::contains_key(0)); + assert!(!ItemMetadataOf::::contains_key(0, 42)); + assert!(!ItemMetadataOf::::contains_key(0, 69)); + assert_eq!(collections(), vec![]); + assert_eq!(items(), vec![]); + }); +} + +#[test] +fn destroy_with_bad_witness_should_not_work() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 100); + assert_ok!(Nfts::create( + RuntimeOrigin::signed(1), + 1, + collection_config_with_all_settings_enabled() + )); + + let w = Collection::::get(0).unwrap().destroy_witness(); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, None)); + assert_noop!(Nfts::destroy(RuntimeOrigin::signed(1), 0, w), Error::::BadWitness); + }); +} + +#[test] +fn mint_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, None)); + assert_eq!(Nfts::owner(0, 42).unwrap(), 1); + assert_eq!(collections(), vec![(1, 0)]); + assert_eq!(items(), vec![(1, 0, 42)]); + + // validate minting start and end settings + assert_ok!(Nfts::update_mint_settings( + RuntimeOrigin::signed(1), + 0, + MintSettings { + start_block: Some(2), + end_block: Some(3), + mint_type: MintType::Public, + ..Default::default() + } + )); + + System::set_block_number(1); + assert_noop!( + Nfts::mint(RuntimeOrigin::signed(2), 0, 43, 1, None), + Error::::MintNotStarted + ); + System::set_block_number(4); + assert_noop!( + Nfts::mint(RuntimeOrigin::signed(2), 0, 43, 1, None), + Error::::MintEnded + ); + + // validate price + assert_ok!(Nfts::update_mint_settings( + RuntimeOrigin::signed(1), + 0, + MintSettings { mint_type: MintType::Public, price: Some(1), ..Default::default() } + )); + Balances::make_free_balance_be(&2, 100); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 43, 2, None)); + assert_eq!(Balances::total_balance(&2), 99); + + // validate types + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); + assert_ok!(Nfts::update_mint_settings( + RuntimeOrigin::signed(1), + 1, + MintSettings { mint_type: MintType::HolderOf(0), ..Default::default() } + )); + assert_noop!( + Nfts::mint(RuntimeOrigin::signed(3), 1, 42, 3, None), + Error::::BadWitness + ); + assert_noop!( + Nfts::mint(RuntimeOrigin::signed(2), 1, 42, 2, None), + Error::::BadWitness + ); + assert_noop!( + Nfts::mint(RuntimeOrigin::signed(2), 1, 42, 2, Some(MintWitness { owner_of_item: 42 })), + Error::::BadWitness + ); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(2), + 1, + 42, + 2, + Some(MintWitness { owner_of_item: 43 }) + )); + + // can't mint twice + assert_noop!( + Nfts::mint(RuntimeOrigin::signed(2), 1, 46, 2, Some(MintWitness { owner_of_item: 43 })), + Error::::AlreadyClaimed + ); + }); +} + +#[test] +fn transfer_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); + + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(2), 0, 42, 3)); + assert_eq!(items(), vec![(3, 0, 42)]); + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(2), 0, 42, 4), + Error::::NoPermission + ); + + assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(3), 0, 42, 2, None)); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(2), 0, 42, 4)); + + // validate we can't transfer non-transferable items + let collection_id = 1; + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + 1, + collection_config_from_disabled_settings( + CollectionSetting::TransferableItems | CollectionSetting::DepositRequired + ) + )); + + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 1, 1, 42, default_item_config())); + + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(1), collection_id, 42, 3,), + Error::::ItemsNonTransferable + ); + }); +} + +#[test] +fn locking_transfer_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, None)); + assert_ok!(Nfts::lock_item_transfer(RuntimeOrigin::signed(1), 0, 42)); + assert_noop!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2), Error::::ItemLocked); + + assert_ok!(Nfts::unlock_item_transfer(RuntimeOrigin::signed(1), 0, 42)); + assert_ok!(Nfts::lock_collection( + RuntimeOrigin::signed(1), + 0, + CollectionSettings::from_disabled(CollectionSetting::TransferableItems.into()) + )); + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2), + Error::::ItemsNonTransferable + ); + + assert_ok!(Nfts::force_collection_config( + RuntimeOrigin::root(), + 0, + collection_config_with_all_settings_enabled(), + )); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2)); + }); +} + +#[test] +fn origin_guards_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, None)); + + Balances::make_free_balance_be(&2, 100); + assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(2), Some(0))); + assert_noop!( + Nfts::transfer_ownership(RuntimeOrigin::signed(2), 0, 2), + Error::::NoPermission + ); + assert_noop!( + Nfts::set_team(RuntimeOrigin::signed(2), 0, 2, 2, 2), + Error::::NoPermission + ); + assert_noop!( + Nfts::lock_item_transfer(RuntimeOrigin::signed(2), 0, 42), + Error::::NoPermission + ); + assert_noop!( + Nfts::unlock_item_transfer(RuntimeOrigin::signed(2), 0, 42), + Error::::NoPermission + ); + assert_noop!( + Nfts::mint(RuntimeOrigin::signed(2), 0, 69, 2, None), + Error::::NoPermission + ); + assert_noop!( + Nfts::burn(RuntimeOrigin::signed(2), 0, 42, None), + Error::::NoPermission + ); + let w = Nfts::get_destroy_witness(&0).unwrap(); + assert_noop!(Nfts::destroy(RuntimeOrigin::signed(2), 0, w), Error::::NoPermission); + }); +} + +#[test] +fn transfer_owner_should_work() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&2, 100); + Balances::make_free_balance_be(&3, 100); + assert_ok!(Nfts::create( + RuntimeOrigin::signed(1), + 1, + collection_config_with_all_settings_enabled() + )); + assert_eq!(collections(), vec![(1, 0)]); + assert_noop!( + Nfts::transfer_ownership(RuntimeOrigin::signed(1), 0, 2), + Error::::Unaccepted + ); + assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(2), Some(0))); + assert_ok!(Nfts::transfer_ownership(RuntimeOrigin::signed(1), 0, 2)); + + assert_eq!(collections(), vec![(2, 0)]); + assert_eq!(Balances::total_balance(&1), 98); + assert_eq!(Balances::total_balance(&2), 102); + assert_eq!(Balances::reserved_balance(&1), 0); + assert_eq!(Balances::reserved_balance(&2), 2); + + assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(1), Some(0))); + assert_noop!( + Nfts::transfer_ownership(RuntimeOrigin::signed(1), 0, 1), + Error::::NoPermission + ); + + // Mint and set metadata now and make sure that deposit gets transferred back. + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(2), 0, bvec![0u8; 20])); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, None)); + assert_eq!(Balances::reserved_balance(&1), 1); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(2), 0, 42, bvec![0u8; 20])); + assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(3), Some(0))); + assert_ok!(Nfts::transfer_ownership(RuntimeOrigin::signed(2), 0, 3)); + assert_eq!(collections(), vec![(3, 0)]); + assert_eq!(Balances::total_balance(&2), 58); + assert_eq!(Balances::total_balance(&3), 144); + assert_eq!(Balances::reserved_balance(&2), 0); + assert_eq!(Balances::reserved_balance(&3), 44); + + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2)); + assert_eq!(Balances::reserved_balance(&1), 0); + assert_eq!(Balances::reserved_balance(&2), 1); + + // 2's acceptance from before is reset when it became an owner, so it cannot be transferred + // without a fresh acceptance. + assert_noop!( + Nfts::transfer_ownership(RuntimeOrigin::signed(3), 0, 2), + Error::::Unaccepted + ); + }); +} + +#[test] +fn set_team_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); + assert_ok!(Nfts::set_team(RuntimeOrigin::signed(1), 0, 2, 3, 4)); + + assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 42, 2, None)); + assert_ok!(Nfts::lock_item_transfer(RuntimeOrigin::signed(4), 0, 42)); + assert_ok!(Nfts::unlock_item_transfer(RuntimeOrigin::signed(4), 0, 42)); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(3), 0, 42, 3)); + assert_ok!(Nfts::burn(RuntimeOrigin::signed(3), 0, 42, None)); + }); +} + +#[test] +fn set_collection_metadata_should_work() { + new_test_ext().execute_with(|| { + // Cannot add metadata to unknown item + assert_noop!( + Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 20]), + Error::::NoConfig, + ); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + 1, + collection_config_with_all_settings_enabled() + )); + // Cannot add metadata to unowned item + assert_noop!( + Nfts::set_collection_metadata(RuntimeOrigin::signed(2), 0, bvec![0u8; 20]), + Error::::NoPermission, + ); + + // Successfully add metadata and take deposit + Balances::make_free_balance_be(&1, 30); + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 20])); + assert_eq!(Balances::free_balance(&1), 9); + assert!(CollectionMetadataOf::::contains_key(0)); + + // Force origin works, too. + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::root(), 0, bvec![0u8; 18])); + + // Update deposit + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 15])); + assert_eq!(Balances::free_balance(&1), 14); + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 25])); + assert_eq!(Balances::free_balance(&1), 4); + + // Cannot over-reserve + assert_noop!( + Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 40]), + BalancesError::::InsufficientBalance, + ); + + // Can't set or clear metadata once frozen + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 15])); + assert_ok!(Nfts::lock_collection( + RuntimeOrigin::signed(1), + 0, + CollectionSettings::from_disabled(CollectionSetting::UnlockedMetadata.into()) + )); + assert_noop!( + Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 15]), + Error::::LockedCollectionMetadata, + ); + assert_noop!( + Nfts::clear_collection_metadata(RuntimeOrigin::signed(1), 0), + Error::::LockedCollectionMetadata + ); + + // Clear Metadata + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::root(), 0, bvec![0u8; 15])); + assert_noop!( + Nfts::clear_collection_metadata(RuntimeOrigin::signed(2), 0), + Error::::NoPermission + ); + assert_noop!( + Nfts::clear_collection_metadata(RuntimeOrigin::signed(1), 1), + Error::::UnknownCollection + ); + assert_noop!( + Nfts::clear_collection_metadata(RuntimeOrigin::signed(1), 0), + Error::::LockedCollectionMetadata + ); + assert_ok!(Nfts::clear_collection_metadata(RuntimeOrigin::root(), 0)); + assert!(!CollectionMetadataOf::::contains_key(0)); + }); +} + +#[test] +fn set_item_metadata_should_work() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 30); + + // Cannot add metadata to unknown item + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + 1, + collection_config_with_all_settings_enabled() + )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, None)); + // Cannot add metadata to unowned item + assert_noop!( + Nfts::set_metadata(RuntimeOrigin::signed(2), 0, 42, bvec![0u8; 20]), + Error::::NoPermission, + ); + + // Successfully add metadata and take deposit + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 20])); + assert_eq!(Balances::free_balance(&1), 8); + assert!(ItemMetadataOf::::contains_key(0, 42)); + + // Force origin works, too. + assert_ok!(Nfts::set_metadata(RuntimeOrigin::root(), 0, 42, bvec![0u8; 18])); + + // Update deposit + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 15])); + assert_eq!(Balances::free_balance(&1), 13); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 25])); + assert_eq!(Balances::free_balance(&1), 3); + + // Cannot over-reserve + assert_noop!( + Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 40]), + BalancesError::::InsufficientBalance, + ); + + // Can't set or clear metadata once frozen + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 15])); + assert_ok!(Nfts::lock_item_properties(RuntimeOrigin::signed(1), 0, 42, true, false)); + assert_noop!( + Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 15]), + Error::::LockedItemMetadata, + ); + assert_noop!( + Nfts::clear_metadata(RuntimeOrigin::signed(1), 0, 42), + Error::::LockedItemMetadata, + ); + + // Clear Metadata + assert_ok!(Nfts::set_metadata(RuntimeOrigin::root(), 0, 42, bvec![0u8; 15])); + assert_noop!( + Nfts::clear_metadata(RuntimeOrigin::signed(2), 0, 42), + Error::::NoPermission, + ); + assert_noop!( + Nfts::clear_metadata(RuntimeOrigin::signed(1), 1, 42), + Error::::UnknownCollection, + ); + assert_ok!(Nfts::clear_metadata(RuntimeOrigin::root(), 0, 42)); + assert!(!ItemMetadataOf::::contains_key(0, 42)); + }); +} + +#[test] +fn set_collection_owner_attributes_should_work() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 100); + + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + 1, + collection_config_with_all_settings_enabled() + )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1, None)); + + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + None, + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![0], + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + Some(0), + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![0], + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + Some(0), + AttributeNamespace::CollectionOwner, + bvec![1], + bvec![0], + )); + assert_eq!( + attributes(0), + vec![ + (None, AttributeNamespace::CollectionOwner, bvec![0], bvec![0]), + (Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![0]), + (Some(0), AttributeNamespace::CollectionOwner, bvec![1], bvec![0]), + ] + ); + assert_eq!(Balances::reserved_balance(1), 10); + assert_eq!(Collection::::get(0).unwrap().owner_deposit, 9); + + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + None, + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![0; 10], + )); + assert_eq!( + attributes(0), + vec![ + (None, AttributeNamespace::CollectionOwner, bvec![0], bvec![0; 10]), + (Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![0]), + (Some(0), AttributeNamespace::CollectionOwner, bvec![1], bvec![0]), + ] + ); + assert_eq!(Balances::reserved_balance(1), 19); + assert_eq!(Collection::::get(0).unwrap().owner_deposit, 18); + + assert_ok!(Nfts::clear_attribute( + RuntimeOrigin::signed(1), + 0, + Some(0), + AttributeNamespace::CollectionOwner, + bvec![1], + )); + assert_eq!( + attributes(0), + vec![ + (None, AttributeNamespace::CollectionOwner, bvec![0], bvec![0; 10]), + (Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![0]), + ] + ); + assert_eq!(Balances::reserved_balance(1), 16); + + let w = Nfts::get_destroy_witness(&0).unwrap(); + assert_ok!(Nfts::destroy(RuntimeOrigin::signed(1), 0, w)); + assert_eq!(attributes(0), vec![]); + assert_eq!(Balances::reserved_balance(1), 0); + }); +} + +#[test] +fn set_item_owner_attributes_should_work() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&2, 100); + Balances::make_free_balance_be(&3, 100); + + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + 1, + collection_config_with_all_settings_enabled() + )); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 0, 2, default_item_config())); + + // can't set for the collection + assert_noop!( + Nfts::set_attribute( + RuntimeOrigin::signed(2), + 0, + None, + AttributeNamespace::ItemOwner, + bvec![0], + bvec![0], + ), + Error::::NoPermission, + ); + // can't set for the non-owned item + assert_noop!( + Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![0], + bvec![0], + ), + Error::::NoPermission, + ); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(2), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![0], + bvec![0], + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(2), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![1], + bvec![0], + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(2), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![2], + bvec![0], + )); + assert_eq!( + attributes(0), + vec![ + (Some(0), AttributeNamespace::ItemOwner, bvec![0], bvec![0]), + (Some(0), AttributeNamespace::ItemOwner, bvec![1], bvec![0]), + (Some(0), AttributeNamespace::ItemOwner, bvec![2], bvec![0]), + ] + ); + assert_eq!(Balances::reserved_balance(2), 9); + + // validate an attribute can be updated + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(2), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![0], + bvec![0; 10], + )); + assert_eq!( + attributes(0), + vec![ + (Some(0), AttributeNamespace::ItemOwner, bvec![0], bvec![0; 10]), + (Some(0), AttributeNamespace::ItemOwner, bvec![1], bvec![0]), + (Some(0), AttributeNamespace::ItemOwner, bvec![2], bvec![0]), + ] + ); + assert_eq!(Balances::reserved_balance(2), 18); + + // validate only item's owner (or the root) can remove an attribute + assert_noop!( + Nfts::clear_attribute( + RuntimeOrigin::signed(1), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![1], + ), + Error::::NoPermission, + ); + assert_ok!(Nfts::clear_attribute( + RuntimeOrigin::signed(2), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![1], + )); + assert_eq!( + attributes(0), + vec![ + (Some(0), AttributeNamespace::ItemOwner, bvec![0], bvec![0; 10]), + (Some(0), AttributeNamespace::ItemOwner, bvec![2], bvec![0]) + ] + ); + assert_eq!(Balances::reserved_balance(2), 15); + + // transfer item + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(2), 0, 0, 3)); + + // validate the attribute are still here & the deposit belongs to the previous owner + assert_eq!( + attributes(0), + vec![ + (Some(0), AttributeNamespace::ItemOwner, bvec![0], bvec![0; 10]), + (Some(0), AttributeNamespace::ItemOwner, bvec![2], bvec![0]) + ] + ); + let key: BoundedVec<_, _> = bvec![0]; + let (_, deposit) = + Attribute::::get((0, Some(0), AttributeNamespace::ItemOwner, &key)).unwrap(); + assert_eq!(deposit.account, Some(2)); + assert_eq!(deposit.amount, 12); + + // on attribute update the deposit should be returned to the previous owner + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(3), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![0], + bvec![0; 11], + )); + let (_, deposit) = + Attribute::::get((0, Some(0), AttributeNamespace::ItemOwner, &key)).unwrap(); + assert_eq!(deposit.account, Some(3)); + assert_eq!(deposit.amount, 13); + assert_eq!(Balances::reserved_balance(2), 3); + assert_eq!(Balances::reserved_balance(3), 13); + + // validate attributes on item deletion + assert_ok!(Nfts::burn(RuntimeOrigin::signed(3), 0, 0, None)); + assert_eq!( + attributes(0), + vec![ + (Some(0), AttributeNamespace::ItemOwner, bvec![0], bvec![0; 11]), + (Some(0), AttributeNamespace::ItemOwner, bvec![2], bvec![0]) + ] + ); + assert_ok!(Nfts::clear_attribute( + RuntimeOrigin::signed(3), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![0], + )); + assert_ok!(Nfts::clear_attribute( + RuntimeOrigin::signed(2), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![2], + )); + assert_eq!(Balances::reserved_balance(2), 0); + assert_eq!(Balances::reserved_balance(3), 0); + }); +} + +#[test] +fn set_external_account_attributes_should_work() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&2, 100); + + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + 1, + collection_config_with_all_settings_enabled() + )); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 0, 1, default_item_config())); + assert_ok!(Nfts::approve_item_attributes(RuntimeOrigin::signed(1), 0, 0, 2)); + + assert_noop!( + Nfts::set_attribute( + RuntimeOrigin::signed(2), + 0, + Some(0), + AttributeNamespace::Account(1), + bvec![0], + bvec![0], + ), + Error::::NoPermission, + ); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(2), + 0, + Some(0), + AttributeNamespace::Account(2), + bvec![0], + bvec![0], + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(2), + 0, + Some(0), + AttributeNamespace::Account(2), + bvec![1], + bvec![0], + )); + assert_eq!( + attributes(0), + vec![ + (Some(0), AttributeNamespace::Account(2), bvec![0], bvec![0]), + (Some(0), AttributeNamespace::Account(2), bvec![1], bvec![0]), + ] + ); + assert_eq!(Balances::reserved_balance(2), 6); + + // remove permission to set attributes + assert_ok!(Nfts::cancel_item_attributes_approval( + RuntimeOrigin::signed(1), + 0, + 0, + 2, + CancelAttributesApprovalWitness { account_attributes: 2 }, + )); + assert_eq!(attributes(0), vec![]); + assert_eq!(Balances::reserved_balance(2), 0); + assert_noop!( + Nfts::set_attribute( + RuntimeOrigin::signed(2), + 0, + Some(0), + AttributeNamespace::Account(2), + bvec![0], + bvec![0], + ), + Error::::NoPermission, + ); + }); +} + +#[test] +fn validate_deposit_required_setting() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&2, 100); + Balances::make_free_balance_be(&3, 100); + + // with the disabled DepositRequired setting, only the collection's owner can set the + // attributes for free. + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 0, 2, default_item_config())); + assert_ok!(Nfts::approve_item_attributes(RuntimeOrigin::signed(2), 0, 0, 3)); + + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + Some(0), + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![0], + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(2), + 0, + Some(0), + AttributeNamespace::ItemOwner, + bvec![1], + bvec![0], + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(3), + 0, + Some(0), + AttributeNamespace::Account(3), + bvec![2], + bvec![0], + )); + assert_ok!(::AccountId, ItemConfig>>::set_attribute( + &0, + &0, + &[3], + &[0], + )); + assert_eq!( + attributes(0), + vec![ + (Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![0]), + (Some(0), AttributeNamespace::ItemOwner, bvec![1], bvec![0]), + (Some(0), AttributeNamespace::Account(3), bvec![2], bvec![0]), + (Some(0), AttributeNamespace::Pallet, bvec![3], bvec![0]), + ] + ); + assert_eq!(Balances::reserved_balance(1), 0); + assert_eq!(Balances::reserved_balance(2), 3); + assert_eq!(Balances::reserved_balance(3), 3); + }); +} + +#[test] +fn set_attribute_should_respect_lock() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 100); + + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + 1, + collection_config_with_all_settings_enabled(), + )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 1, 1, None)); + + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + None, + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![0], + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + Some(0), + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![0], + )); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + Some(1), + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![0], + )); + assert_eq!( + attributes(0), + vec![ + (None, AttributeNamespace::CollectionOwner, bvec![0], bvec![0]), + (Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![0]), + (Some(1), AttributeNamespace::CollectionOwner, bvec![0], bvec![0]), + ] + ); + assert_eq!(Balances::reserved_balance(1), 11); + + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![])); + assert_ok!(Nfts::lock_collection( + RuntimeOrigin::signed(1), + 0, + CollectionSettings::from_disabled(CollectionSetting::UnlockedAttributes.into()) + )); + + let e = Error::::LockedCollectionAttributes; + assert_noop!( + Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + None, + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![0], + ), + e + ); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + Some(0), + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![1], + )); + + assert_ok!(Nfts::lock_item_properties(RuntimeOrigin::signed(1), 0, 0, false, true)); + let e = Error::::LockedItemAttributes; + assert_noop!( + Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + Some(0), + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![1], + ), + e + ); + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(1), + 0, + Some(1), + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![1], + )); + }); +} + +#[test] +fn preserve_config_for_frozen_items() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 100); + + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + 1, + collection_config_with_all_settings_enabled() + )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 1, 1, None)); + + // if the item is not locked/frozen then the config gets deleted on item burn + assert_ok!(Nfts::burn(RuntimeOrigin::signed(1), 0, 1, Some(1))); + assert!(!ItemConfigOf::::contains_key(0, 1)); + + // lock the item and ensure the config stays unchanged + assert_ok!(Nfts::lock_item_properties(RuntimeOrigin::signed(1), 0, 0, true, true)); + + let expect_config = item_config_from_disabled_settings( + ItemSetting::UnlockedAttributes | ItemSetting::UnlockedMetadata, + ); + let config = ItemConfigOf::::get(0, 0).unwrap(); + assert_eq!(config, expect_config); + + assert_ok!(Nfts::burn(RuntimeOrigin::signed(1), 0, 0, Some(1))); + let config = ItemConfigOf::::get(0, 0).unwrap(); + assert_eq!(config, expect_config); + + // can't mint with the different config + assert_noop!( + Nfts::force_mint(RuntimeOrigin::signed(1), 0, 0, 1, default_item_config()), + Error::::InconsistentItemConfig + ); + + assert_ok!(Nfts::update_mint_settings( + RuntimeOrigin::signed(1), + 0, + MintSettings { + default_item_settings: ItemSettings::from_disabled( + ItemSetting::UnlockedAttributes | ItemSetting::UnlockedMetadata + ), + ..Default::default() + } + )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1, None)); + }); +} + +#[test] +fn force_update_collection_should_work() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 100); + + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + 1, + collection_config_with_all_settings_enabled() + )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, None)); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 69, 2, default_item_config())); + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0; 20])); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0; 20])); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 69, bvec![0; 20])); + assert_eq!(Balances::reserved_balance(1), 65); + + // force item status to be free holding + assert_ok!(Nfts::force_collection_config( + RuntimeOrigin::root(), + 0, + collection_config_from_disabled_settings(CollectionSetting::DepositRequired.into()), + )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 142, 1, None)); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 169, 2, default_item_config())); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 142, bvec![0; 20])); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 169, bvec![0; 20])); + + Balances::make_free_balance_be(&5, 100); + assert_ok!(Nfts::force_collection_owner(RuntimeOrigin::root(), 0, 5)); + assert_eq!(collections(), vec![(5, 0)]); + assert_eq!(Balances::reserved_balance(1), 2); + assert_eq!(Balances::reserved_balance(5), 63); + + assert_ok!(Nfts::redeposit(RuntimeOrigin::signed(5), 0, bvec![0, 42, 50, 69, 100])); + assert_eq!(Balances::reserved_balance(1), 0); + + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(5), 0, 42, bvec![0; 20])); + assert_eq!(Balances::reserved_balance(5), 42); + + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(5), 0, 69, bvec![0; 20])); + assert_eq!(Balances::reserved_balance(5), 21); + + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(5), 0, bvec![0; 20])); + assert_eq!(Balances::reserved_balance(5), 0); + + // validate new roles + assert_ok!(Nfts::set_team(RuntimeOrigin::root(), 0, 2, 3, 4)); + assert_eq!( + CollectionRoleOf::::get(0, 2).unwrap(), + CollectionRoles(CollectionRole::Issuer.into()) + ); + assert_eq!( + CollectionRoleOf::::get(0, 3).unwrap(), + CollectionRoles(CollectionRole::Admin.into()) + ); + assert_eq!( + CollectionRoleOf::::get(0, 4).unwrap(), + CollectionRoles(CollectionRole::Freezer.into()) + ); + + assert_ok!(Nfts::set_team(RuntimeOrigin::root(), 0, 3, 2, 3)); + + assert_eq!( + CollectionRoleOf::::get(0, 2).unwrap(), + CollectionRoles(CollectionRole::Admin.into()) + ); + assert_eq!( + CollectionRoleOf::::get(0, 3).unwrap(), + CollectionRoles(CollectionRole::Issuer | CollectionRole::Freezer) + ); + }); +} + +#[test] +fn burn_works() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 100); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + 1, + collection_config_with_all_settings_enabled() + )); + assert_ok!(Nfts::set_team(RuntimeOrigin::signed(1), 0, 2, 3, 4)); + + assert_noop!( + Nfts::burn(RuntimeOrigin::signed(5), 0, 42, Some(5)), + Error::::UnknownCollection + ); + + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(2), 0, 42, 5, default_item_config())); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(2), 0, 69, 5, default_item_config())); + assert_eq!(Balances::reserved_balance(1), 2); + + assert_noop!( + Nfts::burn(RuntimeOrigin::signed(0), 0, 42, None), + Error::::NoPermission + ); + assert_noop!( + Nfts::burn(RuntimeOrigin::signed(5), 0, 42, Some(6)), + Error::::WrongOwner + ); + + assert_ok!(Nfts::burn(RuntimeOrigin::signed(5), 0, 42, Some(5))); + assert_ok!(Nfts::burn(RuntimeOrigin::signed(3), 0, 69, Some(5))); + assert_eq!(Balances::reserved_balance(1), 0); + }); +} + +#[test] +fn approval_lifecycle_works() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); + assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(3), 0, 42, 4)); + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(3), 0, 42, 3), + Error::::NoPermission + ); + assert!(Item::::get(0, 42).unwrap().approvals.is_empty()); + + assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(4), 0, 42, 2, None)); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(2), 0, 42, 2)); + + // ensure we can't buy an item when the collection has a NonTransferableItems flag + let collection_id = 1; + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + 1, + collection_config_from_disabled_settings( + CollectionSetting::TransferableItems | CollectionSetting::DepositRequired + ) + )); + + assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 1, collection_id, 1, None)); + + assert_noop!( + Nfts::approve_transfer(RuntimeOrigin::signed(1), collection_id, 1, 2, None), + Error::::ItemsNonTransferable + ); + }); +} + +#[test] +fn cancel_approval_works() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); + + assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); + assert_noop!( + Nfts::cancel_approval(RuntimeOrigin::signed(2), 1, 42, 3), + Error::::UnknownItem + ); + assert_noop!( + Nfts::cancel_approval(RuntimeOrigin::signed(2), 0, 43, 3), + Error::::UnknownItem + ); + assert_noop!( + Nfts::cancel_approval(RuntimeOrigin::signed(3), 0, 42, 3), + Error::::NoPermission + ); + assert_noop!( + Nfts::cancel_approval(RuntimeOrigin::signed(2), 0, 42, 4), + Error::::NotDelegate + ); + + assert_ok!(Nfts::cancel_approval(RuntimeOrigin::signed(2), 0, 42, 3)); + assert_noop!( + Nfts::cancel_approval(RuntimeOrigin::signed(2), 0, 42, 3), + Error::::NotDelegate + ); + + let current_block = 1; + System::set_block_number(current_block); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 69, 2, default_item_config())); + // approval expires after 2 blocks. + assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, Some(2))); + assert_noop!( + Nfts::cancel_approval(RuntimeOrigin::signed(5), 0, 42, 3), + Error::::NoPermission + ); + + System::set_block_number(current_block + 3); + // 5 can cancel the approval since the deadline has passed. + assert_ok!(Nfts::cancel_approval(RuntimeOrigin::signed(5), 0, 42, 3)); + assert_eq!(approvals(0, 69), vec![]); + }); +} + +#[test] +fn approving_multiple_accounts_works() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); + + let current_block = 1; + System::set_block_number(current_block); + assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); + assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 4, None)); + assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 5, Some(2))); + assert_eq!(approvals(0, 42), vec![(3, None), (4, None), (5, Some(current_block + 2))]); + + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(4), 0, 42, 6)); + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(3), 0, 42, 7), + Error::::NoPermission + ); + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(5), 0, 42, 8), + Error::::NoPermission + ); + }); +} + +#[test] +fn approvals_limit_works() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); + + for i in 3..13 { + assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, i, None)); + } + // the limit is 10 + assert_noop!( + Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 14, None), + Error::::ReachedApprovalLimit + ); + }); +} + +#[test] +fn approval_deadline_works() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + assert!(System::block_number().is_zero()); + + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + 1, + collection_config_from_disabled_settings(CollectionSetting::DepositRequired.into()) + )); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); + + // the approval expires after the 2nd block. + assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, Some(2))); + + System::set_block_number(3); + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(3), 0, 42, 4), + Error::::ApprovalExpired + ); + System::set_block_number(1); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(3), 0, 42, 4)); + + assert_eq!(System::block_number(), 1); + // make a new approval with a deadline after 4 blocks, so it will expire after the 5th + // block. + assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(4), 0, 42, 6, Some(4))); + // this should still work. + System::set_block_number(5); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(6), 0, 42, 5)); + }); +} + +#[test] +fn cancel_approval_works_with_admin() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); + + assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); + assert_noop!( + Nfts::cancel_approval(RuntimeOrigin::signed(1), 1, 42, 1), + Error::::UnknownItem + ); + assert_noop!( + Nfts::cancel_approval(RuntimeOrigin::signed(1), 0, 43, 1), + Error::::UnknownItem + ); + assert_noop!( + Nfts::cancel_approval(RuntimeOrigin::signed(1), 0, 42, 4), + Error::::NotDelegate + ); + + assert_ok!(Nfts::cancel_approval(RuntimeOrigin::signed(1), 0, 42, 3)); + assert_noop!( + Nfts::cancel_approval(RuntimeOrigin::signed(1), 0, 42, 1), + Error::::NotDelegate + ); + }); +} + +#[test] +fn cancel_approval_works_with_force() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); + + assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); + assert_noop!( + Nfts::cancel_approval(RuntimeOrigin::root(), 1, 42, 1), + Error::::UnknownItem + ); + assert_noop!( + Nfts::cancel_approval(RuntimeOrigin::root(), 0, 43, 1), + Error::::UnknownItem + ); + assert_noop!( + Nfts::cancel_approval(RuntimeOrigin::root(), 0, 42, 4), + Error::::NotDelegate + ); + + assert_ok!(Nfts::cancel_approval(RuntimeOrigin::root(), 0, 42, 3)); + assert_noop!( + Nfts::cancel_approval(RuntimeOrigin::root(), 0, 42, 1), + Error::::NotDelegate + ); + }); +} + +#[test] +fn clear_all_transfer_approvals_works() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); + assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); + + assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); + assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 4, None)); + + assert_noop!( + Nfts::clear_all_transfer_approvals(RuntimeOrigin::signed(3), 0, 42), + Error::::NoPermission + ); + + assert_ok!(Nfts::clear_all_transfer_approvals(RuntimeOrigin::signed(2), 0, 42)); + + assert!(events().contains(&Event::::AllApprovalsCancelled { + collection: 0, + item: 42, + owner: 2, + })); + assert_eq!(approvals(0, 42), vec![]); + + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(3), 0, 42, 5), + Error::::NoPermission + ); + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(4), 0, 42, 5), + Error::::NoPermission + ); + }); +} + +#[test] +fn max_supply_should_work() { + new_test_ext().execute_with(|| { + let collection_id = 0; + let user_id = 1; + let max_supply = 1; + + // validate set_collection_max_supply + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); + assert_eq!(CollectionConfigOf::::get(collection_id).unwrap().max_supply, None); + + assert_ok!(Nfts::set_collection_max_supply( + RuntimeOrigin::signed(user_id), + collection_id, + max_supply + )); + assert_eq!( + CollectionConfigOf::::get(collection_id).unwrap().max_supply, + Some(max_supply) + ); + + assert!(events().contains(&Event::::CollectionMaxSupplySet { + collection: collection_id, + max_supply, + })); + + assert_ok!(Nfts::set_collection_max_supply( + RuntimeOrigin::signed(user_id), + collection_id, + max_supply + 1 + )); + assert_ok!(Nfts::lock_collection( + RuntimeOrigin::signed(user_id), + collection_id, + CollectionSettings::from_disabled(CollectionSetting::UnlockedMaxSupply.into()) + )); + assert_noop!( + Nfts::set_collection_max_supply( + RuntimeOrigin::signed(user_id), + collection_id, + max_supply + 2 + ), + Error::::MaxSupplyLocked + ); + + // validate we can't mint more to max supply + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 0, user_id, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 1, user_id, None)); + assert_noop!( + Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 2, user_id, None), + Error::::MaxSupplyReached + ); + }); +} + +#[test] +fn mint_settings_should_work() { + new_test_ext().execute_with(|| { + let collection_id = 0; + let user_id = 1; + let item_id = 0; + + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id), + collection_id, + item_id, + user_id, + None, + )); + assert_eq!( + ItemConfigOf::::get(collection_id, item_id) + .unwrap() + .settings + .get_disabled(), + ItemSettings::all_enabled().get_disabled() + ); + + let collection_id = 1; + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + user_id, + CollectionConfig { + mint_settings: MintSettings { + default_item_settings: ItemSettings::from_disabled( + ItemSetting::Transferable | ItemSetting::UnlockedMetadata + ), + ..Default::default() + }, + ..default_collection_config() + } + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id), + collection_id, + item_id, + user_id, + None, + )); + assert_eq!( + ItemConfigOf::::get(collection_id, item_id) + .unwrap() + .settings + .get_disabled(), + ItemSettings::from_disabled(ItemSetting::Transferable | ItemSetting::UnlockedMetadata) + .get_disabled() + ); + }); +} + +#[test] +fn set_price_should_work() { + new_test_ext().execute_with(|| { + let user_id = 1; + let collection_id = 0; + let item_1 = 1; + let item_2 = 2; + + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); + + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id), + collection_id, + item_1, + user_id, + None, + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id), + collection_id, + item_2, + user_id, + None, + )); + + assert_ok!(Nfts::set_price( + RuntimeOrigin::signed(user_id), + collection_id, + item_1, + Some(1), + None, + )); + + assert_ok!(Nfts::set_price( + RuntimeOrigin::signed(user_id), + collection_id, + item_2, + Some(2), + Some(3) + )); + + let item = ItemPriceOf::::get(collection_id, item_1).unwrap(); + assert_eq!(item.0, 1); + assert_eq!(item.1, None); + + let item = ItemPriceOf::::get(collection_id, item_2).unwrap(); + assert_eq!(item.0, 2); + assert_eq!(item.1, Some(3)); + + assert!(events().contains(&Event::::ItemPriceSet { + collection: collection_id, + item: item_1, + price: 1, + whitelisted_buyer: None, + })); + + // validate we can unset the price + assert_ok!(Nfts::set_price( + RuntimeOrigin::signed(user_id), + collection_id, + item_2, + None, + None + )); + assert!(events().contains(&Event::::ItemPriceRemoved { + collection: collection_id, + item: item_2 + })); + assert!(!ItemPriceOf::::contains_key(collection_id, item_2)); + + // ensure we can't set price when the items are non-transferable + let collection_id = 1; + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + user_id, + collection_config_from_disabled_settings( + CollectionSetting::TransferableItems | CollectionSetting::DepositRequired + ) + )); + + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id), + collection_id, + item_1, + user_id, + None, + )); + + assert_noop!( + Nfts::set_price(RuntimeOrigin::signed(user_id), collection_id, item_1, Some(2), None), + Error::::ItemsNonTransferable + ); + }); +} + +#[test] +fn buy_item_should_work() { + new_test_ext().execute_with(|| { + let user_1 = 1; + let user_2 = 2; + let user_3 = 3; + let collection_id = 0; + let item_1 = 1; + let item_2 = 2; + let item_3 = 3; + let price_1 = 20; + let price_2 = 30; + let initial_balance = 100; + + Balances::make_free_balance_be(&user_1, initial_balance); + Balances::make_free_balance_be(&user_2, initial_balance); + Balances::make_free_balance_be(&user_3, initial_balance); + + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_1, default_collection_config())); + + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_1, user_1, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_2, user_1, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_3, user_1, None)); + + assert_ok!(Nfts::set_price( + RuntimeOrigin::signed(user_1), + collection_id, + item_1, + Some(price_1), + None, + )); + + assert_ok!(Nfts::set_price( + RuntimeOrigin::signed(user_1), + collection_id, + item_2, + Some(price_2), + Some(user_3), + )); + + // can't buy for less + assert_noop!( + Nfts::buy_item(RuntimeOrigin::signed(user_2), collection_id, item_1, 1), + Error::::BidTooLow + ); + + // pass the higher price to validate it will still deduct correctly + assert_ok!(Nfts::buy_item( + RuntimeOrigin::signed(user_2), + collection_id, + item_1, + price_1 + 1, + )); + + // validate the new owner & balances + let item = Item::::get(collection_id, item_1).unwrap(); + assert_eq!(item.owner, user_2); + assert_eq!(Balances::total_balance(&user_1), initial_balance + price_1); + assert_eq!(Balances::total_balance(&user_2), initial_balance - price_1); + + // can't buy from yourself + assert_noop!( + Nfts::buy_item(RuntimeOrigin::signed(user_1), collection_id, item_2, price_2), + Error::::NoPermission + ); + + // can't buy when the item is listed for a specific buyer + assert_noop!( + Nfts::buy_item(RuntimeOrigin::signed(user_2), collection_id, item_2, price_2), + Error::::NoPermission + ); + + // can buy when I'm a whitelisted buyer + assert_ok!(Nfts::buy_item(RuntimeOrigin::signed(user_3), collection_id, item_2, price_2)); + + assert!(events().contains(&Event::::ItemBought { + collection: collection_id, + item: item_2, + price: price_2, + seller: user_1, + buyer: user_3, + })); + + // ensure we reset the buyer field + assert!(!ItemPriceOf::::contains_key(collection_id, item_2)); + + // can't buy when item is not for sale + assert_noop!( + Nfts::buy_item(RuntimeOrigin::signed(user_2), collection_id, item_3, price_2), + Error::::NotForSale + ); + + // ensure we can't buy an item when the collection or an item are frozen + { + assert_ok!(Nfts::set_price( + RuntimeOrigin::signed(user_1), + collection_id, + item_3, + Some(price_1), + None, + )); + + // lock the collection + assert_ok!(Nfts::lock_collection( + RuntimeOrigin::signed(user_1), + collection_id, + CollectionSettings::from_disabled(CollectionSetting::TransferableItems.into()) + )); + + let buy_item_call = mock::RuntimeCall::Nfts(crate::Call::::buy_item { + collection: collection_id, + item: item_3, + bid_price: price_1, + }); + assert_noop!( + buy_item_call.dispatch(RuntimeOrigin::signed(user_2)), + Error::::ItemsNonTransferable + ); + + // unlock the collection + assert_ok!(Nfts::force_collection_config( + RuntimeOrigin::root(), + collection_id, + collection_config_with_all_settings_enabled(), + )); + + // lock the transfer + assert_ok!(Nfts::lock_item_transfer( + RuntimeOrigin::signed(user_1), + collection_id, + item_3, + )); + + let buy_item_call = mock::RuntimeCall::Nfts(crate::Call::::buy_item { + collection: collection_id, + item: item_3, + bid_price: price_1, + }); + assert_noop!( + buy_item_call.dispatch(RuntimeOrigin::signed(user_2)), + Error::::ItemLocked + ); + } + }); +} + +#[test] +fn pay_tips_should_work() { + new_test_ext().execute_with(|| { + let user_1 = 1; + let user_2 = 2; + let user_3 = 3; + let collection_id = 0; + let item_id = 1; + let tip = 2; + let initial_balance = 100; + + Balances::make_free_balance_be(&user_1, initial_balance); + Balances::make_free_balance_be(&user_2, initial_balance); + Balances::make_free_balance_be(&user_3, initial_balance); + + assert_ok!(Nfts::pay_tips( + RuntimeOrigin::signed(user_1), + bvec![ + ItemTip { collection: collection_id, item: item_id, receiver: user_2, amount: tip }, + ItemTip { collection: collection_id, item: item_id, receiver: user_3, amount: tip }, + ] + )); + + assert_eq!(Balances::total_balance(&user_1), initial_balance - tip * 2); + assert_eq!(Balances::total_balance(&user_2), initial_balance + tip); + assert_eq!(Balances::total_balance(&user_3), initial_balance + tip); + + let events = events(); + assert!(events.contains(&Event::::TipSent { + collection: collection_id, + item: item_id, + sender: user_1, + receiver: user_2, + amount: tip, + })); + assert!(events.contains(&Event::::TipSent { + collection: collection_id, + item: item_id, + sender: user_1, + receiver: user_3, + amount: tip, + })); + }); +} + +#[test] +fn create_cancel_swap_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + let user_id = 1; + let collection_id = 0; + let item_1 = 1; + let item_2 = 2; + let price = 1; + let price_direction = PriceDirection::Receive; + let price_with_direction = PriceWithDirection { amount: price, direction: price_direction }; + let duration = 2; + let expect_deadline = 3; + + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); + + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id), + collection_id, + item_1, + user_id, + None, + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id), + collection_id, + item_2, + user_id, + None, + )); + + // validate desired item and the collection exists + assert_noop!( + Nfts::create_swap( + RuntimeOrigin::signed(user_id), + collection_id, + item_1, + collection_id, + Some(item_2 + 1), + Some(price_with_direction.clone()), + duration, + ), + Error::::UnknownItem + ); + assert_noop!( + Nfts::create_swap( + RuntimeOrigin::signed(user_id), + collection_id, + item_1, + collection_id + 1, + None, + Some(price_with_direction.clone()), + duration, + ), + Error::::UnknownCollection + ); + + let max_duration: u64 = ::MaxDeadlineDuration::get(); + assert_noop!( + Nfts::create_swap( + RuntimeOrigin::signed(user_id), + collection_id, + item_1, + collection_id, + Some(item_2), + Some(price_with_direction.clone()), + max_duration.saturating_add(1), + ), + Error::::WrongDuration + ); + + assert_ok!(Nfts::create_swap( + RuntimeOrigin::signed(user_id), + collection_id, + item_1, + collection_id, + Some(item_2), + Some(price_with_direction.clone()), + duration, + )); + + let swap = PendingSwapOf::::get(collection_id, item_1).unwrap(); + assert_eq!(swap.desired_collection, collection_id); + assert_eq!(swap.desired_item, Some(item_2)); + assert_eq!(swap.price, Some(price_with_direction.clone())); + assert_eq!(swap.deadline, expect_deadline); + + assert!(events().contains(&Event::::SwapCreated { + offered_collection: collection_id, + offered_item: item_1, + desired_collection: collection_id, + desired_item: Some(item_2), + price: Some(price_with_direction.clone()), + deadline: expect_deadline, + })); + + // validate we can cancel the swap + assert_ok!(Nfts::cancel_swap(RuntimeOrigin::signed(user_id), collection_id, item_1)); + assert!(events().contains(&Event::::SwapCancelled { + offered_collection: collection_id, + offered_item: item_1, + desired_collection: collection_id, + desired_item: Some(item_2), + price: Some(price_with_direction.clone()), + deadline: expect_deadline, + })); + assert!(!PendingSwapOf::::contains_key(collection_id, item_1)); + + // validate anyone can cancel the expired swap + assert_ok!(Nfts::create_swap( + RuntimeOrigin::signed(user_id), + collection_id, + item_1, + collection_id, + Some(item_2), + Some(price_with_direction.clone()), + duration, + )); + assert_noop!( + Nfts::cancel_swap(RuntimeOrigin::signed(user_id + 1), collection_id, item_1), + Error::::NoPermission + ); + System::set_block_number(expect_deadline + 1); + assert_ok!(Nfts::cancel_swap(RuntimeOrigin::signed(user_id + 1), collection_id, item_1)); + + // validate optional desired_item param + assert_ok!(Nfts::create_swap( + RuntimeOrigin::signed(user_id), + collection_id, + item_1, + collection_id, + None, + Some(price_with_direction), + duration, + )); + + let swap = PendingSwapOf::::get(collection_id, item_1).unwrap(); + assert_eq!(swap.desired_item, None); + }); +} + +#[test] +fn claim_swap_should_work() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + let user_1 = 1; + let user_2 = 2; + let collection_id = 0; + let item_1 = 1; + let item_2 = 2; + let item_3 = 3; + let item_4 = 4; + let item_5 = 5; + let price = 100; + let price_direction = PriceDirection::Receive; + let price_with_direction = + PriceWithDirection { amount: price, direction: price_direction.clone() }; + let duration = 2; + let initial_balance = 1000; + let deadline = 1 + duration; + + Balances::make_free_balance_be(&user_1, initial_balance); + Balances::make_free_balance_be(&user_2, initial_balance); + + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_1, default_collection_config())); + + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_1), + collection_id, + item_1,user_1, + None, + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(user_1), + collection_id, + item_2, + user_2, + default_item_config(), + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(user_1), + collection_id, + item_3, + user_2, + default_item_config(), + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_1), + collection_id, + item_4, + user_1, + None, + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(user_1), + collection_id, + item_5, + user_2, + default_item_config(), + )); + + assert_ok!(Nfts::create_swap( + RuntimeOrigin::signed(user_1), + collection_id, + item_1, + collection_id, + Some(item_2), + Some(price_with_direction.clone()), + duration, + )); + + // validate the deadline + System::set_block_number(5); + assert_noop!( + Nfts::claim_swap( + RuntimeOrigin::signed(user_2), + collection_id, + item_2, + collection_id, + item_1, + Some(price_with_direction.clone()), + ), + Error::::DeadlineExpired + ); + System::set_block_number(1); + + // validate edge cases + assert_noop!( + Nfts::claim_swap( + RuntimeOrigin::signed(user_2), + collection_id, + item_2, + collection_id, + item_4, // no swap was created for that asset + Some(price_with_direction.clone()), + ), + Error::::UnknownSwap + ); + assert_noop!( + Nfts::claim_swap( + RuntimeOrigin::signed(user_2), + collection_id, + item_4, // not my item + collection_id, + item_1, + Some(price_with_direction.clone()), + ), + Error::::NoPermission + ); + assert_noop!( + Nfts::claim_swap( + RuntimeOrigin::signed(user_2), + collection_id, + item_5, // my item, but not the one another part wants + collection_id, + item_1, + Some(price_with_direction.clone()), + ), + Error::::UnknownSwap + ); + assert_noop!( + Nfts::claim_swap( + RuntimeOrigin::signed(user_2), + collection_id, + item_2, + collection_id, + item_1, + Some(PriceWithDirection { amount: price + 1, direction: price_direction.clone() }), // wrong price + ), + Error::::UnknownSwap + ); + assert_noop!( + Nfts::claim_swap( + RuntimeOrigin::signed(user_2), + collection_id, + item_2, + collection_id, + item_1, + Some(PriceWithDirection { amount: price, direction: PriceDirection::Send }), // wrong direction + ), + Error::::UnknownSwap + ); + + assert_ok!(Nfts::claim_swap( + RuntimeOrigin::signed(user_2), + collection_id, + item_2, + collection_id, + item_1, + Some(price_with_direction.clone()), + )); + + // validate the new owner + let item = Item::::get(collection_id, item_1).unwrap(); + assert_eq!(item.owner, user_2); + let item = Item::::get(collection_id, item_2).unwrap(); + assert_eq!(item.owner, user_1); + + // validate the balances + assert_eq!(Balances::total_balance(&user_1), initial_balance + price); + assert_eq!(Balances::total_balance(&user_2), initial_balance - price); + + // ensure we reset the swap + assert!(!PendingSwapOf::::contains_key(collection_id, item_1)); + + // validate the event + assert!(events().contains(&Event::::SwapClaimed { + sent_collection: collection_id, + sent_item: item_2, + sent_item_owner: user_2, + received_collection: collection_id, + received_item: item_1, + received_item_owner: user_1, + price: Some(price_with_direction.clone()), + deadline, + })); + + // validate the optional desired_item param and another price direction + let price_direction = PriceDirection::Send; + let price_with_direction = PriceWithDirection { amount: price, direction: price_direction }; + Balances::make_free_balance_be(&user_1, initial_balance); + Balances::make_free_balance_be(&user_2, initial_balance); + + assert_ok!(Nfts::create_swap( + RuntimeOrigin::signed(user_1), + collection_id, + item_4, + collection_id, + None, + Some(price_with_direction.clone()), + duration, + )); + assert_ok!(Nfts::claim_swap( + RuntimeOrigin::signed(user_2), + collection_id, + item_1, + collection_id, + item_4, + Some(price_with_direction), + )); + let item = Item::::get(collection_id, item_1).unwrap(); + assert_eq!(item.owner, user_1); + let item = Item::::get(collection_id, item_4).unwrap(); + assert_eq!(item.owner, user_2); + + assert_eq!(Balances::total_balance(&user_1), initial_balance - price); + assert_eq!(Balances::total_balance(&user_2), initial_balance + price); + }); +} + +#[test] +fn various_collection_settings() { + new_test_ext().execute_with(|| { + // when we set only one value it's required to call .into() on it + let config = + collection_config_from_disabled_settings(CollectionSetting::TransferableItems.into()); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, config)); + + let config = CollectionConfigOf::::get(0).unwrap(); + assert!(!config.is_setting_enabled(CollectionSetting::TransferableItems)); + assert!(config.is_setting_enabled(CollectionSetting::UnlockedMetadata)); + + // no need to call .into() for multiple values + let config = collection_config_from_disabled_settings( + CollectionSetting::UnlockedMetadata | CollectionSetting::TransferableItems, + ); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, config)); + + let config = CollectionConfigOf::::get(1).unwrap(); + assert!(!config.is_setting_enabled(CollectionSetting::TransferableItems)); + assert!(!config.is_setting_enabled(CollectionSetting::UnlockedMetadata)); + + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); + }); +} + +#[test] +fn collection_locking_should_work() { + new_test_ext().execute_with(|| { + let user_id = 1; + let collection_id = 0; + + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + user_id, + collection_config_with_all_settings_enabled() + )); + + let lock_config = + collection_config_from_disabled_settings(CollectionSetting::DepositRequired.into()); + assert_noop!( + Nfts::lock_collection( + RuntimeOrigin::signed(user_id), + collection_id, + lock_config.settings, + ), + Error::::WrongSetting + ); + + // validate partial lock + let lock_config = collection_config_from_disabled_settings( + CollectionSetting::TransferableItems | CollectionSetting::UnlockedAttributes, + ); + assert_ok!(Nfts::lock_collection( + RuntimeOrigin::signed(user_id), + collection_id, + lock_config.settings, + )); + + let stored_config = CollectionConfigOf::::get(collection_id).unwrap(); + assert_eq!(stored_config, lock_config); + + // validate full lock + assert_ok!(Nfts::lock_collection( + RuntimeOrigin::signed(user_id), + collection_id, + CollectionSettings::from_disabled(CollectionSetting::UnlockedMetadata.into()), + )); + + let stored_config = CollectionConfigOf::::get(collection_id).unwrap(); + let full_lock_config = collection_config_from_disabled_settings( + CollectionSetting::TransferableItems | + CollectionSetting::UnlockedMetadata | + CollectionSetting::UnlockedAttributes, + ); + assert_eq!(stored_config, full_lock_config); + }); +} + +#[test] +fn pallet_level_feature_flags_should_work() { + new_test_ext().execute_with(|| { + Features::set(&PalletFeatures::from_disabled( + PalletFeature::Trading | PalletFeature::Approvals | PalletFeature::Attributes, + )); + + let user_id = 1; + let collection_id = 0; + let item_id = 1; + + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); + + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id), + collection_id, + item_id, + user_id, + None, + )); + + // PalletFeature::Trading + assert_noop!( + Nfts::set_price(RuntimeOrigin::signed(user_id), collection_id, item_id, Some(1), None), + Error::::MethodDisabled + ); + assert_noop!( + Nfts::buy_item(RuntimeOrigin::signed(user_id), collection_id, item_id, 1), + Error::::MethodDisabled + ); + + // PalletFeature::Approvals + assert_noop!( + Nfts::approve_transfer(RuntimeOrigin::signed(user_id), collection_id, item_id, 2, None), + Error::::MethodDisabled + ); + + // PalletFeature::Attributes + assert_noop!( + Nfts::set_attribute( + RuntimeOrigin::signed(user_id), + collection_id, + None, + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![0], + ), + Error::::MethodDisabled + ); + }) +} + +#[test] +fn group_roles_by_account_should_work() { + new_test_ext().execute_with(|| { + assert_eq!(Nfts::group_roles_by_account(vec![]), vec![]); + + let account_to_role = Nfts::group_roles_by_account(vec![ + (3, CollectionRole::Freezer), + (1, CollectionRole::Issuer), + (2, CollectionRole::Admin), + ]); + let expect = vec![ + (1, CollectionRoles(CollectionRole::Issuer.into())), + (2, CollectionRoles(CollectionRole::Admin.into())), + (3, CollectionRoles(CollectionRole::Freezer.into())), + ]; + assert_eq!(account_to_role, expect); + + let account_to_role = Nfts::group_roles_by_account(vec![ + (3, CollectionRole::Freezer), + (2, CollectionRole::Issuer), + (2, CollectionRole::Admin), + ]); + let expect = vec![ + (2, CollectionRoles(CollectionRole::Issuer | CollectionRole::Admin)), + (3, CollectionRoles(CollectionRole::Freezer.into())), + ]; + assert_eq!(account_to_role, expect); + }) +} + +#[test] +fn add_remove_item_attributes_approval_should_work() { + new_test_ext().execute_with(|| { + let user_1 = 1; + let user_2 = 2; + let user_3 = 3; + let user_4 = 4; + let collection_id = 0; + let item_id = 0; + + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_1, default_collection_config())); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_id, user_1, None)); + assert_ok!(Nfts::approve_item_attributes( + RuntimeOrigin::signed(user_1), + collection_id, + item_id, + user_2, + )); + assert_eq!(item_attributes_approvals(collection_id, item_id), vec![user_2]); + + assert_ok!(Nfts::approve_item_attributes( + RuntimeOrigin::signed(user_1), + collection_id, + item_id, + user_3, + )); + assert_ok!(Nfts::approve_item_attributes( + RuntimeOrigin::signed(user_1), + collection_id, + item_id, + user_2, + )); + assert_eq!(item_attributes_approvals(collection_id, item_id), vec![user_2, user_3]); + + assert_noop!( + Nfts::approve_item_attributes( + RuntimeOrigin::signed(user_1), + collection_id, + item_id, + user_4, + ), + Error::::ReachedApprovalLimit + ); + + assert_ok!(Nfts::cancel_item_attributes_approval( + RuntimeOrigin::signed(user_1), + collection_id, + item_id, + user_2, + CancelAttributesApprovalWitness { account_attributes: 1 }, + )); + assert_eq!(item_attributes_approvals(collection_id, item_id), vec![user_3]); + }) +} diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs new file mode 100644 index 0000000000..58b1acaaed --- /dev/null +++ b/frame/nfts/src/types.rs @@ -0,0 +1,465 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 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. + +//! Various basic types for use in the Nfts pallet. + +use super::*; +use crate::macros::*; +use codec::EncodeLike; +use enumflags2::{bitflags, BitFlags}; +use frame_support::{ + pallet_prelude::{BoundedVec, MaxEncodedLen}, + traits::Get, + BoundedBTreeMap, BoundedBTreeSet, +}; +use scale_info::{build::Fields, meta_type, Path, Type, TypeInfo, TypeParameter}; + +pub(super) type DepositBalanceOf = + <>::Currency as Currency<::AccountId>>::Balance; +pub(super) type CollectionDetailsFor = + CollectionDetails<::AccountId, DepositBalanceOf>; +pub(super) type ApprovalsOf = BoundedBTreeMap< + ::AccountId, + Option<::BlockNumber>, + >::ApprovalsLimit, +>; +pub(super) type ItemAttributesApprovals = + BoundedBTreeSet<::AccountId, >::ItemAttributesApprovalsLimit>; +pub(super) type ItemDepositOf = + ItemDeposit, ::AccountId>; +pub(super) type AttributeDepositOf = + AttributeDeposit, ::AccountId>; +pub(super) type ItemDetailsFor = + ItemDetails<::AccountId, ItemDepositOf, ApprovalsOf>; +pub(super) type BalanceOf = + <>::Currency as Currency<::AccountId>>::Balance; +pub(super) type ItemPrice = BalanceOf; +pub(super) type ItemTipOf = ItemTip< + >::CollectionId, + >::ItemId, + ::AccountId, + BalanceOf, +>; +pub(super) type CollectionConfigFor = CollectionConfig< + BalanceOf, + ::BlockNumber, + >::CollectionId, +>; + +pub trait Incrementable { + fn increment(&self) -> Self; + fn initial_value() -> Self; +} +impl_incrementable!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128); + +/// Information about a collection. +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct CollectionDetails { + /// Collection's owner. + pub(super) owner: AccountId, + /// The total balance deposited by the owner for all the storage data associated with this + /// collection. Used by `destroy`. + pub(super) owner_deposit: DepositBalance, + /// The total number of outstanding items of this collection. + pub(super) items: u32, + /// The total number of outstanding item metadata of this collection. + pub(super) item_metadatas: u32, + /// The total number of attributes for this collection. + pub(super) attributes: u32, +} + +/// Witness data for the destroy transactions. +#[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct DestroyWitness { + /// The total number of outstanding items of this collection. + #[codec(compact)] + pub items: u32, + /// The total number of items in this collection that have outstanding item metadata. + #[codec(compact)] + pub item_metadatas: u32, + /// The total number of attributes for this collection. + #[codec(compact)] + pub attributes: u32, +} + +impl CollectionDetails { + pub fn destroy_witness(&self) -> DestroyWitness { + DestroyWitness { + items: self.items, + item_metadatas: self.item_metadatas, + attributes: self.attributes, + } + } +} + +/// Witness data for items mint transactions. +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo)] +pub struct MintWitness { + /// Provide the id of the item in a required collection. + pub owner_of_item: ItemId, +} + +/// Information concerning the ownership of a single unique item. +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)] +pub struct ItemDetails { + /// The owner of this item. + pub(super) owner: AccountId, + /// The approved transferrer of this item, if one is set. + pub(super) approvals: Approvals, + /// The amount held in the pallet's default account for this item. Free-hold items will have + /// this as zero. + pub(super) deposit: Deposit, +} + +/// Information about the reserved item deposit. +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct ItemDeposit { + /// A depositor account. + pub(super) account: AccountId, + /// An amount that gets reserved. + pub(super) amount: DepositBalance, +} + +/// Information about the collection's metadata. +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)] +#[scale_info(skip_type_params(StringLimit))] +#[codec(mel_bound(DepositBalance: MaxEncodedLen))] +pub struct CollectionMetadata> { + /// The balance deposited for this metadata. + /// + /// This pays for the data stored in this struct. + pub(super) deposit: DepositBalance, + /// General information concerning this collection. Limited in length by `StringLimit`. This + /// will generally be either a JSON dump or the hash of some JSON which can be found on a + /// hash-addressable global publication system such as IPFS. + pub(super) data: BoundedVec, +} + +/// Information about the item's metadata. +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)] +#[scale_info(skip_type_params(StringLimit))] +#[codec(mel_bound(DepositBalance: MaxEncodedLen))] +pub struct ItemMetadata> { + /// The balance deposited for this metadata. + /// + /// This pays for the data stored in this struct. + pub(super) deposit: DepositBalance, + /// General information concerning this item. Limited in length by `StringLimit`. This will + /// generally be either a JSON dump or the hash of some JSON which can be found on a + /// hash-addressable global publication system such as IPFS. + pub(super) data: BoundedVec, +} + +/// Information about the tip. +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct ItemTip { + /// The collection of the item. + pub(super) collection: CollectionId, + /// An item of which the tip is sent for. + pub(super) item: ItemId, + /// A sender of the tip. + pub(super) receiver: AccountId, + /// An amount the sender is willing to tip. + pub(super) amount: Amount, +} + +/// Information about the pending swap. +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)] +pub struct PendingSwap { + /// The collection that contains the item that the user wants to receive. + pub(super) desired_collection: CollectionId, + /// The item the user wants to receive. + pub(super) desired_item: Option, + /// A price for the desired `item` with the direction. + pub(super) price: Option, + /// An optional deadline for the swap. + pub(super) deadline: Deadline, +} + +/// Information about the reserved attribute deposit. +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct AttributeDeposit { + /// A depositor account. + pub(super) account: Option, + /// An amount that gets reserved. + pub(super) amount: DepositBalance, +} + +/// Specifies whether the tokens will be sent or received. +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub enum PriceDirection { + /// Tokens will be sent. + Send, + /// Tokens will be received. + Receive, +} + +/// Holds the details about the price. +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct PriceWithDirection { + /// An amount. + pub(super) amount: Amount, + /// A direction (send or receive). + pub(super) direction: PriceDirection, +} + +/// Support for up to 64 user-enabled features on a collection. +#[bitflags] +#[repr(u64)] +#[derive(Copy, Clone, RuntimeDebug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] +pub enum CollectionSetting { + /// Items in this collection are transferable. + TransferableItems, + /// The metadata of this collection can be modified. + UnlockedMetadata, + /// Attributes of this collection can be modified. + UnlockedAttributes, + /// The supply of this collection can be modified. + UnlockedMaxSupply, + /// When this isn't set then the deposit is required to hold the items of this collection. + DepositRequired, +} + +/// Wrapper type for `BitFlags` that implements `Codec`. +#[derive(Clone, Copy, PartialEq, Eq, Default, RuntimeDebug)] +pub struct CollectionSettings(pub BitFlags); + +impl CollectionSettings { + pub fn all_enabled() -> Self { + Self(BitFlags::EMPTY) + } + pub fn get_disabled(&self) -> BitFlags { + self.0 + } + pub fn is_disabled(&self, setting: CollectionSetting) -> bool { + self.0.contains(setting) + } + pub fn from_disabled(settings: BitFlags) -> Self { + Self(settings) + } +} + +impl_codec_bitflags!(CollectionSettings, u64, CollectionSetting); + +/// Mint type. Can the NFT be create by anyone, or only the creator of the collection, +/// or only by wallets that already hold an NFT from a certain collection? +/// The ownership of a privately minted NFT is still publicly visible. +#[derive(Clone, Copy, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub enum MintType { + /// Only an `Issuer` could mint items. + Issuer, + /// Anyone could mint items. + Public, + /// Only holders of items in specified collection could mint new items. + HolderOf(CollectionId), +} + +/// Holds the information about minting. +#[derive(Clone, Copy, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct MintSettings { + /// Whether anyone can mint or if minters are restricted to some subset. + pub(super) mint_type: MintType, + /// An optional price per mint. + pub(super) price: Option, + /// When the mint starts. + pub(super) start_block: Option, + /// When the mint ends. + pub(super) end_block: Option, + /// Default settings each item will get during the mint. + pub(super) default_item_settings: ItemSettings, +} + +impl Default for MintSettings { + fn default() -> Self { + Self { + mint_type: MintType::Issuer, + price: None, + start_block: None, + end_block: None, + default_item_settings: ItemSettings::all_enabled(), + } + } +} + +/// A witness data to cancel attributes approval operation. +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo)] +pub struct CancelAttributesApprovalWitness { + /// An amount of attributes previously created by account. + pub account_attributes: u32, +} + +/// A list of possible pallet-level attributes. +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub enum PalletAttributes { + /// Marks an item as being used in order to claim another item. + UsedToClaim(CollectionId), +} + +/// Collection's configuration. +#[derive( + Clone, Copy, Decode, Default, Encode, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo, +)] +pub struct CollectionConfig { + /// Collection's settings. + pub(super) settings: CollectionSettings, + /// Collection's max supply. + pub(super) max_supply: Option, + /// Default settings each item will get during the mint. + pub(super) mint_settings: MintSettings, +} + +impl CollectionConfig { + pub fn is_setting_enabled(&self, setting: CollectionSetting) -> bool { + !self.settings.is_disabled(setting) + } + pub fn has_disabled_setting(&self, setting: CollectionSetting) -> bool { + self.settings.is_disabled(setting) + } + pub fn enable_setting(&mut self, setting: CollectionSetting) { + self.settings.0.remove(setting); + } + pub fn disable_setting(&mut self, setting: CollectionSetting) { + self.settings.0.insert(setting); + } +} + +/// Support for up to 64 user-enabled features on an item. +#[bitflags] +#[repr(u64)] +#[derive(Copy, Clone, RuntimeDebug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] +pub enum ItemSetting { + /// This item is transferable. + Transferable, + /// The metadata of this item can be modified. + UnlockedMetadata, + /// Attributes of this item can be modified. + UnlockedAttributes, +} + +/// Wrapper type for `BitFlags` that implements `Codec`. +#[derive(Clone, Copy, PartialEq, Eq, Default, RuntimeDebug)] +pub struct ItemSettings(pub BitFlags); + +impl ItemSettings { + pub fn all_enabled() -> Self { + Self(BitFlags::EMPTY) + } + pub fn get_disabled(&self) -> BitFlags { + self.0 + } + pub fn is_disabled(&self, setting: ItemSetting) -> bool { + self.0.contains(setting) + } + pub fn from_disabled(settings: BitFlags) -> Self { + Self(settings) + } +} + +impl_codec_bitflags!(ItemSettings, u64, ItemSetting); + +/// Item's configuration. +#[derive( + Encode, Decode, Default, PartialEq, RuntimeDebug, Clone, Copy, MaxEncodedLen, TypeInfo, +)] +pub struct ItemConfig { + /// Item's settings. + pub(super) settings: ItemSettings, +} + +impl ItemConfig { + pub fn is_setting_enabled(&self, setting: ItemSetting) -> bool { + !self.settings.is_disabled(setting) + } + pub fn has_disabled_setting(&self, setting: ItemSetting) -> bool { + self.settings.is_disabled(setting) + } + pub fn has_disabled_settings(&self) -> bool { + !self.settings.get_disabled().is_empty() + } + pub fn enable_setting(&mut self, setting: ItemSetting) { + self.settings.0.remove(setting); + } + pub fn disable_setting(&mut self, setting: ItemSetting) { + self.settings.0.insert(setting); + } +} + +/// Support for up to 64 system-enabled features on a collection. +#[bitflags] +#[repr(u64)] +#[derive(Copy, Clone, RuntimeDebug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] +pub enum PalletFeature { + /// Enable/disable trading operations. + Trading, + /// Allow/disallow setting attributes. + Attributes, + /// Allow/disallow transfer approvals. + Approvals, + /// Allow/disallow atomic items swap. + Swaps, +} + +/// Wrapper type for `BitFlags` that implements `Codec`. +#[derive(Default, RuntimeDebug)] +pub struct PalletFeatures(pub BitFlags); + +impl PalletFeatures { + pub fn all_enabled() -> Self { + Self(BitFlags::EMPTY) + } + pub fn from_disabled(features: BitFlags) -> Self { + Self(features) + } + pub fn is_enabled(&self, feature: PalletFeature) -> bool { + !self.0.contains(feature) + } +} +impl_codec_bitflags!(PalletFeatures, u64, PalletFeature); + +/// Support for up to 8 different roles for collections. +#[bitflags] +#[repr(u8)] +#[derive(Copy, Clone, RuntimeDebug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] +pub enum CollectionRole { + /// Can mint items. + Issuer, + /// Can freeze items. + Freezer, + /// Can thaw items, force transfers and burn items from any account. + Admin, +} + +/// A wrapper type that implements `Codec`. +#[derive(Clone, Copy, PartialEq, Eq, Default, RuntimeDebug)] +pub struct CollectionRoles(pub BitFlags); + +impl CollectionRoles { + pub fn none() -> Self { + Self(BitFlags::EMPTY) + } + pub fn has_role(&self, role: CollectionRole) -> bool { + self.0.contains(role) + } + pub fn add_role(&mut self, role: CollectionRole) { + self.0.insert(role); + } + pub fn max_roles() -> u8 { + let all: BitFlags = BitFlags::all(); + all.len() as u8 + } +} +impl_codec_bitflags!(CollectionRoles, u8, CollectionRole); diff --git a/frame/nfts/src/weights.rs b/frame/nfts/src/weights.rs new file mode 100644 index 0000000000..f05f8ca514 --- /dev/null +++ b/frame/nfts/src/weights.rs @@ -0,0 +1,851 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Autogenerated weights for pallet_nfts +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-12-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// /home/benchbot/cargo_target_dir/production/substrate +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/substrate/.git/.artifacts/bench.json +// --pallet=pallet_nfts +// --chain=dev +// --header=./HEADER-APACHE2 +// --output=./frame/nfts/src/weights.rs +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_nfts. +pub trait WeightInfo { + fn create() -> Weight; + fn force_create() -> Weight; + fn destroy(n: u32, m: u32, a: u32, ) -> Weight; + fn mint() -> Weight; + fn force_mint() -> Weight; + fn burn() -> Weight; + fn transfer() -> Weight; + fn redeposit(i: u32, ) -> Weight; + fn lock_item_transfer() -> Weight; + fn unlock_item_transfer() -> Weight; + fn lock_collection() -> Weight; + fn transfer_ownership() -> Weight; + fn set_team() -> Weight; + fn force_collection_owner() -> Weight; + fn force_collection_config() -> Weight; + fn lock_item_properties() -> Weight; + fn set_attribute() -> Weight; + fn force_set_attribute() -> Weight; + fn clear_attribute() -> Weight; + fn approve_item_attributes() -> Weight; + fn cancel_item_attributes_approval(n: u32, ) -> Weight; + fn set_metadata() -> Weight; + fn clear_metadata() -> Weight; + fn set_collection_metadata() -> Weight; + fn clear_collection_metadata() -> Weight; + fn approve_transfer() -> Weight; + fn cancel_approval() -> Weight; + fn clear_all_transfer_approvals() -> Weight; + fn set_accept_ownership() -> Weight; + fn set_collection_max_supply() -> Weight; + fn update_mint_settings() -> Weight; + fn set_price() -> Weight; + fn buy_item() -> Weight; + fn pay_tips(n: u32, ) -> Weight; + fn create_swap() -> Weight; + fn cancel_swap() -> Weight; + fn claim_swap() -> Weight; +} + +/// Weights for pallet_nfts using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: Nfts NextCollectionId (r:1 w:1) + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts CollectionRoleOf (r:0 w:1) + // Storage: Nfts CollectionConfigOf (r:0 w:1) + // Storage: Nfts CollectionAccount (r:0 w:1) + fn create() -> Weight { + // Minimum execution time: 44_312 nanoseconds. + Weight::from_ref_time(44_871_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(5)) + } + // Storage: Nfts NextCollectionId (r:1 w:1) + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts CollectionRoleOf (r:0 w:1) + // Storage: Nfts CollectionConfigOf (r:0 w:1) + // Storage: Nfts CollectionAccount (r:0 w:1) + fn force_create() -> Weight { + // Minimum execution time: 31_654 nanoseconds. + Weight::from_ref_time(32_078_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(5)) + } + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts Item (r:1001 w:1000) + // Storage: Nfts Attribute (r:1001 w:1000) + // Storage: Nfts ItemMetadataOf (r:0 w:1000) + // Storage: Nfts CollectionRoleOf (r:0 w:1) + // Storage: Nfts CollectionMetadataOf (r:0 w:1) + // Storage: Nfts CollectionConfigOf (r:0 w:1) + // Storage: Nfts ItemConfigOf (r:0 w:1000) + // Storage: Nfts Account (r:0 w:1000) + // Storage: Nfts CollectionAccount (r:0 w:1) + /// The range of component `n` is `[0, 1000]`. + /// The range of component `m` is `[0, 1000]`. + /// The range of component `a` is `[0, 1000]`. + fn destroy(n: u32, m: u32, a: u32, ) -> Weight { + // Minimum execution time: 19_183_393 nanoseconds. + Weight::from_ref_time(17_061_526_855) + // Standard Error: 16_689 + .saturating_add(Weight::from_ref_time(353_523).saturating_mul(n.into())) + // Standard Error: 16_689 + .saturating_add(Weight::from_ref_time(1_861_080).saturating_mul(m.into())) + // Standard Error: 16_689 + .saturating_add(Weight::from_ref_time(8_858_987).saturating_mul(a.into())) + .saturating_add(T::DbWeight::get().reads(1003)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(a.into()))) + .saturating_add(T::DbWeight::get().writes(3005)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(m.into()))) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(a.into()))) + } + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts Item (r:1 w:1) + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts CollectionRoleOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:1) + // Storage: Nfts Account (r:0 w:1) + fn mint() -> Weight { + // Minimum execution time: 57_753 nanoseconds. + Weight::from_ref_time(58_313_000) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + } + // Storage: Nfts CollectionRoleOf (r:1 w:0) + // Storage: Nfts Item (r:1 w:1) + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:1) + // Storage: Nfts Account (r:0 w:1) + fn force_mint() -> Weight { + // Minimum execution time: 56_429 nanoseconds. + Weight::from_ref_time(57_202_000) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + } + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts Item (r:1 w:1) + // Storage: Nfts CollectionRoleOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:1) + // Storage: Nfts Account (r:0 w:1) + // Storage: Nfts ItemPriceOf (r:0 w:1) + // Storage: Nfts ItemAttributesApprovalsOf (r:0 w:1) + // Storage: Nfts PendingSwapOf (r:0 w:1) + fn burn() -> Weight { + // Minimum execution time: 59_681 nanoseconds. + Weight::from_ref_time(60_058_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(7)) + } + // Storage: Nfts Collection (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) + // Storage: Nfts Item (r:1 w:1) + // Storage: Nfts CollectionRoleOf (r:1 w:0) + // Storage: System Account (r:1 w:1) + // Storage: Nfts Account (r:0 w:2) + // Storage: Nfts ItemPriceOf (r:0 w:1) + // Storage: Nfts PendingSwapOf (r:0 w:1) + fn transfer() -> Weight { + // Minimum execution time: 66_085 nanoseconds. + Weight::from_ref_time(67_065_000) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(6)) + } + // Storage: Nfts Collection (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts Item (r:102 w:102) + /// The range of component `i` is `[0, 5000]`. + fn redeposit(i: u32, ) -> Weight { + // Minimum execution time: 25_949 nanoseconds. + Weight::from_ref_time(26_106_000) + // Standard Error: 10_326 + .saturating_add(Weight::from_ref_time(11_496_776).saturating_mul(i.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(i.into()))) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(i.into()))) + } + // Storage: Nfts CollectionRoleOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:1) + fn lock_item_transfer() -> Weight { + // Minimum execution time: 30_080 nanoseconds. + Weight::from_ref_time(30_825_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: Nfts CollectionRoleOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:1) + fn unlock_item_transfer() -> Weight { + // Minimum execution time: 30_612 nanoseconds. + Weight::from_ref_time(31_422_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: Nfts CollectionRoleOf (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:1) + fn lock_collection() -> Weight { + // Minimum execution time: 27_470 nanoseconds. + Weight::from_ref_time(28_015_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: Nfts OwnershipAcceptance (r:1 w:1) + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts CollectionAccount (r:0 w:2) + fn transfer_ownership() -> Weight { + // Minimum execution time: 33_750 nanoseconds. + Weight::from_ref_time(34_139_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(4)) + } + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts CollectionRoleOf (r:0 w:4) + fn set_team() -> Weight { + // Minimum execution time: 36_565 nanoseconds. + Weight::from_ref_time(37_464_000) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(5)) + } + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts CollectionAccount (r:0 w:2) + fn force_collection_owner() -> Weight { + // Minimum execution time: 29_028 nanoseconds. + Weight::from_ref_time(29_479_000) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(3)) + } + // Storage: Nfts Collection (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:0 w:1) + fn force_collection_config() -> Weight { + // Minimum execution time: 24_695 nanoseconds. + Weight::from_ref_time(25_304_000) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: Nfts Collection (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:1) + fn lock_item_properties() -> Weight { + // Minimum execution time: 28_910 nanoseconds. + Weight::from_ref_time(29_186_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) + // Storage: Nfts Attribute (r:1 w:1) + fn set_attribute() -> Weight { + // Minimum execution time: 56_407 nanoseconds. + Weight::from_ref_time(58_176_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts Attribute (r:1 w:1) + fn force_set_attribute() -> Weight { + // Minimum execution time: 36_402 nanoseconds. + Weight::from_ref_time(37_034_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: Nfts Attribute (r:1 w:1) + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts ItemConfigOf (r:1 w:0) + fn clear_attribute() -> Weight { + // Minimum execution time: 52_022 nanoseconds. + Weight::from_ref_time(54_059_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: Nfts Item (r:1 w:0) + // Storage: Nfts ItemAttributesApprovalsOf (r:1 w:1) + fn approve_item_attributes() -> Weight { + // Minimum execution time: 28_475 nanoseconds. + Weight::from_ref_time(29_162_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: Nfts Item (r:1 w:0) + // Storage: Nfts ItemAttributesApprovalsOf (r:1 w:1) + // Storage: Nfts Attribute (r:1 w:0) + // Storage: System Account (r:1 w:1) + /// The range of component `n` is `[0, 1000]`. + fn cancel_item_attributes_approval(n: u32, ) -> Weight { + // Minimum execution time: 37_529 nanoseconds. + Weight::from_ref_time(38_023_000) + // Standard Error: 8_136 + .saturating_add(Weight::from_ref_time(7_452_872).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(2)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) + } + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts ItemConfigOf (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemMetadataOf (r:1 w:1) + fn set_metadata() -> Weight { + // Minimum execution time: 49_300 nanoseconds. + Weight::from_ref_time(49_790_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts ItemConfigOf (r:1 w:0) + // Storage: Nfts ItemMetadataOf (r:1 w:1) + fn clear_metadata() -> Weight { + // Minimum execution time: 47_248 nanoseconds. + Weight::from_ref_time(48_094_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts CollectionMetadataOf (r:1 w:1) + fn set_collection_metadata() -> Weight { + // Minimum execution time: 44_137 nanoseconds. + Weight::from_ref_time(44_905_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: Nfts Collection (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts CollectionMetadataOf (r:1 w:1) + fn clear_collection_metadata() -> Weight { + // Minimum execution time: 43_005 nanoseconds. + Weight::from_ref_time(43_898_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: Nfts Item (r:1 w:1) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts CollectionRoleOf (r:1 w:0) + fn approve_transfer() -> Weight { + // Minimum execution time: 36_344 nanoseconds. + Weight::from_ref_time(36_954_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: Nfts Item (r:1 w:1) + // Storage: Nfts CollectionRoleOf (r:1 w:0) + fn cancel_approval() -> Weight { + // Minimum execution time: 32_418 nanoseconds. + Weight::from_ref_time(33_029_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: Nfts Item (r:1 w:1) + // Storage: Nfts CollectionRoleOf (r:1 w:0) + fn clear_all_transfer_approvals() -> Weight { + // Minimum execution time: 31_448 nanoseconds. + Weight::from_ref_time(31_979_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: Nfts OwnershipAcceptance (r:1 w:1) + fn set_accept_ownership() -> Weight { + // Minimum execution time: 27_487 nanoseconds. + Weight::from_ref_time(28_080_000) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: Nfts CollectionConfigOf (r:1 w:1) + // Storage: Nfts Collection (r:1 w:0) + fn set_collection_max_supply() -> Weight { + // Minimum execution time: 28_235 nanoseconds. + Weight::from_ref_time(28_967_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: Nfts Collection (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:1) + fn update_mint_settings() -> Weight { + // Minimum execution time: 28_172 nanoseconds. + Weight::from_ref_time(28_636_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: Nfts Item (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) + // Storage: Nfts ItemPriceOf (r:0 w:1) + fn set_price() -> Weight { + // Minimum execution time: 35_336 nanoseconds. + Weight::from_ref_time(36_026_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: Nfts Item (r:1 w:1) + // Storage: Nfts ItemPriceOf (r:1 w:1) + // Storage: Nfts Collection (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) + // Storage: System Account (r:1 w:1) + // Storage: Nfts Account (r:0 w:2) + // Storage: Nfts PendingSwapOf (r:0 w:1) + fn buy_item() -> Weight { + // Minimum execution time: 70_971 nanoseconds. + Weight::from_ref_time(72_036_000) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(6)) + } + /// The range of component `n` is `[0, 10]`. + fn pay_tips(n: u32, ) -> Weight { + // Minimum execution time: 5_151 nanoseconds. + Weight::from_ref_time(11_822_888) + // Standard Error: 38_439 + .saturating_add(Weight::from_ref_time(3_511_844).saturating_mul(n.into())) + } + // Storage: Nfts Item (r:2 w:0) + // Storage: Nfts PendingSwapOf (r:0 w:1) + fn create_swap() -> Weight { + // Minimum execution time: 33_027 nanoseconds. + Weight::from_ref_time(33_628_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: Nfts PendingSwapOf (r:1 w:1) + // Storage: Nfts Item (r:1 w:0) + fn cancel_swap() -> Weight { + // Minimum execution time: 35_890 nanoseconds. + Weight::from_ref_time(36_508_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: Nfts Item (r:2 w:2) + // Storage: Nfts PendingSwapOf (r:1 w:2) + // Storage: Nfts Collection (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:2 w:0) + // Storage: System Account (r:1 w:1) + // Storage: Nfts Account (r:0 w:4) + // Storage: Nfts ItemPriceOf (r:0 w:2) + fn claim_swap() -> Weight { + // Minimum execution time: 101_076 nanoseconds. + Weight::from_ref_time(101_863_000) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(11)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: Nfts NextCollectionId (r:1 w:1) + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts CollectionRoleOf (r:0 w:1) + // Storage: Nfts CollectionConfigOf (r:0 w:1) + // Storage: Nfts CollectionAccount (r:0 w:1) + fn create() -> Weight { + // Minimum execution time: 44_312 nanoseconds. + Weight::from_ref_time(44_871_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(5)) + } + // Storage: Nfts NextCollectionId (r:1 w:1) + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts CollectionRoleOf (r:0 w:1) + // Storage: Nfts CollectionConfigOf (r:0 w:1) + // Storage: Nfts CollectionAccount (r:0 w:1) + fn force_create() -> Weight { + // Minimum execution time: 31_654 nanoseconds. + Weight::from_ref_time(32_078_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(5)) + } + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts Item (r:1001 w:1000) + // Storage: Nfts Attribute (r:1001 w:1000) + // Storage: Nfts ItemMetadataOf (r:0 w:1000) + // Storage: Nfts CollectionRoleOf (r:0 w:1) + // Storage: Nfts CollectionMetadataOf (r:0 w:1) + // Storage: Nfts CollectionConfigOf (r:0 w:1) + // Storage: Nfts ItemConfigOf (r:0 w:1000) + // Storage: Nfts Account (r:0 w:1000) + // Storage: Nfts CollectionAccount (r:0 w:1) + /// The range of component `n` is `[0, 1000]`. + /// The range of component `m` is `[0, 1000]`. + /// The range of component `a` is `[0, 1000]`. + fn destroy(n: u32, m: u32, a: u32, ) -> Weight { + // Minimum execution time: 19_183_393 nanoseconds. + Weight::from_ref_time(17_061_526_855) + // Standard Error: 16_689 + .saturating_add(Weight::from_ref_time(353_523).saturating_mul(n.into())) + // Standard Error: 16_689 + .saturating_add(Weight::from_ref_time(1_861_080).saturating_mul(m.into())) + // Standard Error: 16_689 + .saturating_add(Weight::from_ref_time(8_858_987).saturating_mul(a.into())) + .saturating_add(RocksDbWeight::get().reads(1003)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(a.into()))) + .saturating_add(RocksDbWeight::get().writes(3005)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(m.into()))) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(a.into()))) + } + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts Item (r:1 w:1) + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts CollectionRoleOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:1) + // Storage: Nfts Account (r:0 w:1) + fn mint() -> Weight { + // Minimum execution time: 57_753 nanoseconds. + Weight::from_ref_time(58_313_000) + .saturating_add(RocksDbWeight::get().reads(5)) + .saturating_add(RocksDbWeight::get().writes(4)) + } + // Storage: Nfts CollectionRoleOf (r:1 w:0) + // Storage: Nfts Item (r:1 w:1) + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:1) + // Storage: Nfts Account (r:0 w:1) + fn force_mint() -> Weight { + // Minimum execution time: 56_429 nanoseconds. + Weight::from_ref_time(57_202_000) + .saturating_add(RocksDbWeight::get().reads(5)) + .saturating_add(RocksDbWeight::get().writes(4)) + } + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts Item (r:1 w:1) + // Storage: Nfts CollectionRoleOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:1) + // Storage: Nfts Account (r:0 w:1) + // Storage: Nfts ItemPriceOf (r:0 w:1) + // Storage: Nfts ItemAttributesApprovalsOf (r:0 w:1) + // Storage: Nfts PendingSwapOf (r:0 w:1) + fn burn() -> Weight { + // Minimum execution time: 59_681 nanoseconds. + Weight::from_ref_time(60_058_000) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().writes(7)) + } + // Storage: Nfts Collection (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) + // Storage: Nfts Item (r:1 w:1) + // Storage: Nfts CollectionRoleOf (r:1 w:0) + // Storage: System Account (r:1 w:1) + // Storage: Nfts Account (r:0 w:2) + // Storage: Nfts ItemPriceOf (r:0 w:1) + // Storage: Nfts PendingSwapOf (r:0 w:1) + fn transfer() -> Weight { + // Minimum execution time: 66_085 nanoseconds. + Weight::from_ref_time(67_065_000) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(6)) + } + // Storage: Nfts Collection (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts Item (r:102 w:102) + /// The range of component `i` is `[0, 5000]`. + fn redeposit(i: u32, ) -> Weight { + // Minimum execution time: 25_949 nanoseconds. + Weight::from_ref_time(26_106_000) + // Standard Error: 10_326 + .saturating_add(Weight::from_ref_time(11_496_776).saturating_mul(i.into())) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(i.into()))) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(i.into()))) + } + // Storage: Nfts CollectionRoleOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:1) + fn lock_item_transfer() -> Weight { + // Minimum execution time: 30_080 nanoseconds. + Weight::from_ref_time(30_825_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: Nfts CollectionRoleOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:1) + fn unlock_item_transfer() -> Weight { + // Minimum execution time: 30_612 nanoseconds. + Weight::from_ref_time(31_422_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: Nfts CollectionRoleOf (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:1) + fn lock_collection() -> Weight { + // Minimum execution time: 27_470 nanoseconds. + Weight::from_ref_time(28_015_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: Nfts OwnershipAcceptance (r:1 w:1) + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts CollectionAccount (r:0 w:2) + fn transfer_ownership() -> Weight { + // Minimum execution time: 33_750 nanoseconds. + Weight::from_ref_time(34_139_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(4)) + } + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts CollectionRoleOf (r:0 w:4) + fn set_team() -> Weight { + // Minimum execution time: 36_565 nanoseconds. + Weight::from_ref_time(37_464_000) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(5)) + } + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts CollectionAccount (r:0 w:2) + fn force_collection_owner() -> Weight { + // Minimum execution time: 29_028 nanoseconds. + Weight::from_ref_time(29_479_000) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(3)) + } + // Storage: Nfts Collection (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:0 w:1) + fn force_collection_config() -> Weight { + // Minimum execution time: 24_695 nanoseconds. + Weight::from_ref_time(25_304_000) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: Nfts Collection (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:1) + fn lock_item_properties() -> Weight { + // Minimum execution time: 28_910 nanoseconds. + Weight::from_ref_time(29_186_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) + // Storage: Nfts Attribute (r:1 w:1) + fn set_attribute() -> Weight { + // Minimum execution time: 56_407 nanoseconds. + Weight::from_ref_time(58_176_000) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().writes(2)) + } + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts Attribute (r:1 w:1) + fn force_set_attribute() -> Weight { + // Minimum execution time: 36_402 nanoseconds. + Weight::from_ref_time(37_034_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) + } + // Storage: Nfts Attribute (r:1 w:1) + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts ItemConfigOf (r:1 w:0) + fn clear_attribute() -> Weight { + // Minimum execution time: 52_022 nanoseconds. + Weight::from_ref_time(54_059_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(2)) + } + // Storage: Nfts Item (r:1 w:0) + // Storage: Nfts ItemAttributesApprovalsOf (r:1 w:1) + fn approve_item_attributes() -> Weight { + // Minimum execution time: 28_475 nanoseconds. + Weight::from_ref_time(29_162_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: Nfts Item (r:1 w:0) + // Storage: Nfts ItemAttributesApprovalsOf (r:1 w:1) + // Storage: Nfts Attribute (r:1 w:0) + // Storage: System Account (r:1 w:1) + /// The range of component `n` is `[0, 1000]`. + fn cancel_item_attributes_approval(n: u32, ) -> Weight { + // Minimum execution time: 37_529 nanoseconds. + Weight::from_ref_time(38_023_000) + // Standard Error: 8_136 + .saturating_add(Weight::from_ref_time(7_452_872).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes(2)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into()))) + } + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts ItemConfigOf (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemMetadataOf (r:1 w:1) + fn set_metadata() -> Weight { + // Minimum execution time: 49_300 nanoseconds. + Weight::from_ref_time(49_790_000) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().writes(2)) + } + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts ItemConfigOf (r:1 w:0) + // Storage: Nfts ItemMetadataOf (r:1 w:1) + fn clear_metadata() -> Weight { + // Minimum execution time: 47_248 nanoseconds. + Weight::from_ref_time(48_094_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(2)) + } + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts Collection (r:1 w:1) + // Storage: Nfts CollectionMetadataOf (r:1 w:1) + fn set_collection_metadata() -> Weight { + // Minimum execution time: 44_137 nanoseconds. + Weight::from_ref_time(44_905_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(2)) + } + // Storage: Nfts Collection (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts CollectionMetadataOf (r:1 w:1) + fn clear_collection_metadata() -> Weight { + // Minimum execution time: 43_005 nanoseconds. + Weight::from_ref_time(43_898_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: Nfts Item (r:1 w:1) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts CollectionRoleOf (r:1 w:0) + fn approve_transfer() -> Weight { + // Minimum execution time: 36_344 nanoseconds. + Weight::from_ref_time(36_954_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: Nfts Item (r:1 w:1) + // Storage: Nfts CollectionRoleOf (r:1 w:0) + fn cancel_approval() -> Weight { + // Minimum execution time: 32_418 nanoseconds. + Weight::from_ref_time(33_029_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: Nfts Item (r:1 w:1) + // Storage: Nfts CollectionRoleOf (r:1 w:0) + fn clear_all_transfer_approvals() -> Weight { + // Minimum execution time: 31_448 nanoseconds. + Weight::from_ref_time(31_979_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: Nfts OwnershipAcceptance (r:1 w:1) + fn set_accept_ownership() -> Weight { + // Minimum execution time: 27_487 nanoseconds. + Weight::from_ref_time(28_080_000) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: Nfts CollectionConfigOf (r:1 w:1) + // Storage: Nfts Collection (r:1 w:0) + fn set_collection_max_supply() -> Weight { + // Minimum execution time: 28_235 nanoseconds. + Weight::from_ref_time(28_967_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: Nfts Collection (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:1) + fn update_mint_settings() -> Weight { + // Minimum execution time: 28_172 nanoseconds. + Weight::from_ref_time(28_636_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: Nfts Item (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) + // Storage: Nfts ItemPriceOf (r:0 w:1) + fn set_price() -> Weight { + // Minimum execution time: 35_336 nanoseconds. + Weight::from_ref_time(36_026_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: Nfts Item (r:1 w:1) + // Storage: Nfts ItemPriceOf (r:1 w:1) + // Storage: Nfts Collection (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:1 w:0) + // Storage: System Account (r:1 w:1) + // Storage: Nfts Account (r:0 w:2) + // Storage: Nfts PendingSwapOf (r:0 w:1) + fn buy_item() -> Weight { + // Minimum execution time: 70_971 nanoseconds. + Weight::from_ref_time(72_036_000) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(6)) + } + /// The range of component `n` is `[0, 10]`. + fn pay_tips(n: u32, ) -> Weight { + // Minimum execution time: 5_151 nanoseconds. + Weight::from_ref_time(11_822_888) + // Standard Error: 38_439 + .saturating_add(Weight::from_ref_time(3_511_844).saturating_mul(n.into())) + } + // Storage: Nfts Item (r:2 w:0) + // Storage: Nfts PendingSwapOf (r:0 w:1) + fn create_swap() -> Weight { + // Minimum execution time: 33_027 nanoseconds. + Weight::from_ref_time(33_628_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: Nfts PendingSwapOf (r:1 w:1) + // Storage: Nfts Item (r:1 w:0) + fn cancel_swap() -> Weight { + // Minimum execution time: 35_890 nanoseconds. + Weight::from_ref_time(36_508_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: Nfts Item (r:2 w:2) + // Storage: Nfts PendingSwapOf (r:1 w:2) + // Storage: Nfts Collection (r:1 w:0) + // Storage: Nfts CollectionConfigOf (r:1 w:0) + // Storage: Nfts ItemConfigOf (r:2 w:0) + // Storage: System Account (r:1 w:1) + // Storage: Nfts Account (r:0 w:4) + // Storage: Nfts ItemPriceOf (r:0 w:2) + fn claim_swap() -> Weight { + // Minimum execution time: 101_076 nanoseconds. + Weight::from_ref_time(101_863_000) + .saturating_add(RocksDbWeight::get().reads(8)) + .saturating_add(RocksDbWeight::get().writes(11)) + } +} diff --git a/frame/support/src/traits/tokens.rs b/frame/support/src/traits/tokens.rs index 77eb83adfb..03a24bd3ba 100644 --- a/frame/support/src/traits/tokens.rs +++ b/frame/support/src/traits/tokens.rs @@ -23,9 +23,11 @@ pub mod fungibles; pub mod imbalance; mod misc; pub mod nonfungible; +pub mod nonfungible_v2; pub mod nonfungibles; +pub mod nonfungibles_v2; pub use imbalance::Imbalance; pub use misc::{ - AssetId, Balance, BalanceConversion, BalanceStatus, DepositConsequence, ExistenceRequirement, - Locker, WithdrawConsequence, WithdrawReasons, + AssetId, AttributeNamespace, Balance, BalanceConversion, BalanceStatus, DepositConsequence, + ExistenceRequirement, Locker, WithdrawConsequence, WithdrawReasons, }; diff --git a/frame/support/src/traits/tokens/misc.rs b/frame/support/src/traits/tokens/misc.rs index 294d0e89c8..f9876ef477 100644 --- a/frame/support/src/traits/tokens/misc.rs +++ b/frame/support/src/traits/tokens/misc.rs @@ -126,6 +126,21 @@ pub enum BalanceStatus { Reserved, } +/// Attribute namespaces for non-fungible tokens. +#[derive( + Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen, +)] +pub enum AttributeNamespace { + /// An attribute was set by the pallet. + Pallet, + /// An attribute was set by collection's owner. + CollectionOwner, + /// An attribute was set by item's owner. + ItemOwner, + /// An attribute was set by pre-approved account. + Account(AccountId), +} + bitflags::bitflags! { /// Reasons for moving funds out of an account. #[derive(Encode, Decode, MaxEncodedLen)] diff --git a/frame/support/src/traits/tokens/nonfungible_v2.rs b/frame/support/src/traits/tokens/nonfungible_v2.rs new file mode 100644 index 0000000000..a1b75e62e4 --- /dev/null +++ b/frame/support/src/traits/tokens/nonfungible_v2.rs @@ -0,0 +1,248 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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. + +//! Traits for dealing with a single non-fungible item. +//! +//! This assumes a single-level namespace identified by `Inspect::ItemId`, and could +//! reasonably be implemented by pallets that want to expose a single collection of NFT-like +//! objects. +//! +//! For an NFT API that has dual-level namespacing, the traits in `nonfungibles` are better to +//! use. + +use super::nonfungibles_v2 as nonfungibles; +use crate::{ + dispatch::DispatchResult, + traits::{tokens::misc::AttributeNamespace, Get}, +}; +use codec::{Decode, Encode}; +use sp_runtime::TokenError; +use sp_std::prelude::*; + +/// Trait for providing an interface to a read-only NFT-like item. +pub trait Inspect { + /// Type for identifying an item. + type ItemId; + + /// Returns the owner of `item`, or `None` if the item doesn't exist or has no + /// owner. + fn owner(item: &Self::ItemId) -> Option; + + /// Returns the attribute value of `item` corresponding to `key`. + /// + /// By default this is `None`; no attributes are defined. + fn attribute( + _item: &Self::ItemId, + _namespace: &AttributeNamespace, + _key: &[u8], + ) -> Option> { + None + } + + /// Returns the strongly-typed attribute value of `item` corresponding to `key`. + /// + /// By default this just attempts to use `attribute`. + fn typed_attribute( + item: &Self::ItemId, + namespace: &AttributeNamespace, + key: &K, + ) -> Option { + key.using_encoded(|d| Self::attribute(item, namespace, d)) + .and_then(|v| V::decode(&mut &v[..]).ok()) + } + + /// Returns `true` if the `item` may be transferred. + /// + /// Default implementation is that all items are transferable. + fn can_transfer(_item: &Self::ItemId) -> bool { + true + } +} + +/// Interface for enumerating items in existence or owned by a given account over a collection +/// of NFTs. +pub trait InspectEnumerable: Inspect { + /// The iterator type for [`Self::items`]. + type ItemsIterator: Iterator; + /// The iterator type for [`Self::owned`]. + type OwnedIterator: Iterator; + + /// Returns an iterator of the items within a `collection` in existence. + fn items() -> Self::ItemsIterator; + + /// Returns an iterator of the items of all collections owned by `who`. + fn owned(who: &AccountId) -> Self::OwnedIterator; +} + +/// Trait for providing an interface for NFT-like items which may be minted, burned and/or have +/// attributes set on them. +pub trait Mutate: Inspect { + /// Mint some `item` to be owned by `who`. + /// + /// By default, this is not a supported operation. + fn mint_into( + _item: &Self::ItemId, + _who: &AccountId, + _config: &ItemConfig, + _deposit_collection_owner: bool, + ) -> DispatchResult { + Err(TokenError::Unsupported.into()) + } + + /// Burn some `item`. + /// + /// By default, this is not a supported operation. + fn burn(_item: &Self::ItemId, _maybe_check_owner: Option<&AccountId>) -> DispatchResult { + Err(TokenError::Unsupported.into()) + } + + /// Set attribute `value` of `item`'s `key`. + /// + /// By default, this is not a supported operation. + fn set_attribute(_item: &Self::ItemId, _key: &[u8], _value: &[u8]) -> DispatchResult { + Err(TokenError::Unsupported.into()) + } + + /// Attempt to set the strongly-typed attribute `value` of `item`'s `key`. + /// + /// By default this just attempts to use `set_attribute`. + fn set_typed_attribute( + item: &Self::ItemId, + key: &K, + value: &V, + ) -> DispatchResult { + key.using_encoded(|k| value.using_encoded(|v| Self::set_attribute(item, k, v))) + } +} + +/// Trait for transferring a non-fungible item. +pub trait Transfer: Inspect { + /// Transfer `item` into `destination` account. + fn transfer(item: &Self::ItemId, destination: &AccountId) -> DispatchResult; +} + +/// Convert a `nonfungibles` trait implementation into a `nonfungible` trait implementation by +/// identifying a single item. +pub struct ItemOf< + F: nonfungibles::Inspect, + A: Get<>::CollectionId>, + AccountId, +>(sp_std::marker::PhantomData<(F, A, AccountId)>); + +impl< + F: nonfungibles::Inspect, + A: Get<>::CollectionId>, + AccountId, + > Inspect for ItemOf +{ + type ItemId = >::ItemId; + fn owner(item: &Self::ItemId) -> Option { + >::owner(&A::get(), item) + } + fn attribute( + item: &Self::ItemId, + namespace: &AttributeNamespace, + key: &[u8], + ) -> Option> { + >::attribute(&A::get(), item, namespace, key) + } + fn typed_attribute( + item: &Self::ItemId, + namespace: &AttributeNamespace, + key: &K, + ) -> Option { + >::typed_attribute(&A::get(), item, namespace, key) + } + fn can_transfer(item: &Self::ItemId) -> bool { + >::can_transfer(&A::get(), item) + } +} + +impl< + F: nonfungibles::InspectEnumerable, + A: Get<>::CollectionId>, + AccountId, + > InspectEnumerable for ItemOf +{ + type ItemsIterator = >::ItemsIterator; + type OwnedIterator = + >::OwnedInCollectionIterator; + + fn items() -> Self::ItemsIterator { + >::items(&A::get()) + } + fn owned(who: &AccountId) -> Self::OwnedIterator { + >::owned_in_collection(&A::get(), who) + } +} + +impl< + F: nonfungibles::Mutate, + A: Get<>::CollectionId>, + AccountId, + ItemConfig, + > Mutate for ItemOf +{ + fn mint_into( + item: &Self::ItemId, + who: &AccountId, + config: &ItemConfig, + deposit_collection_owner: bool, + ) -> DispatchResult { + >::mint_into( + &A::get(), + item, + who, + config, + deposit_collection_owner, + ) + } + fn burn(item: &Self::ItemId, maybe_check_owner: Option<&AccountId>) -> DispatchResult { + >::burn(&A::get(), item, maybe_check_owner) + } + fn set_attribute(item: &Self::ItemId, key: &[u8], value: &[u8]) -> DispatchResult { + >::set_attribute( + &A::get(), + item, + key, + value, + ) + } + fn set_typed_attribute( + item: &Self::ItemId, + key: &K, + value: &V, + ) -> DispatchResult { + >::set_typed_attribute( + &A::get(), + item, + key, + value, + ) + } +} + +impl< + F: nonfungibles::Transfer, + A: Get<>::CollectionId>, + AccountId, + > Transfer for ItemOf +{ + fn transfer(item: &Self::ItemId, destination: &AccountId) -> DispatchResult { + >::transfer(&A::get(), item, destination) + } +} diff --git a/frame/support/src/traits/tokens/nonfungibles_v2.rs b/frame/support/src/traits/tokens/nonfungibles_v2.rs new file mode 100644 index 0000000000..d2f5f5529f --- /dev/null +++ b/frame/support/src/traits/tokens/nonfungibles_v2.rs @@ -0,0 +1,257 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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. + +//! Traits for dealing with multiple collections of non-fungible items. +//! +//! This assumes a dual-level namespace identified by `Inspect::ItemId`, and could +//! reasonably be implemented by pallets which want to expose multiple independent collections of +//! NFT-like objects. +//! +//! For an NFT API which has single-level namespacing, the traits in `nonfungible` are better to +//! use. +//! +//! Implementations of these traits may be converted to implementations of corresponding +//! `nonfungible` traits by using the `nonfungible::ItemOf` type adapter. + +use crate::{ + dispatch::{DispatchError, DispatchResult}, + traits::tokens::misc::AttributeNamespace, +}; +use codec::{Decode, Encode}; +use sp_runtime::TokenError; +use sp_std::prelude::*; + +/// Trait for providing an interface to many read-only NFT-like sets of items. +pub trait Inspect { + /// Type for identifying an item. + type ItemId; + + /// Type for identifying a collection (an identifier for an independent collection of + /// items). + type CollectionId; + + /// Returns the owner of `item` of `collection`, or `None` if the item doesn't exist + /// (or somehow has no owner). + fn owner(collection: &Self::CollectionId, item: &Self::ItemId) -> Option; + + /// Returns the owner of the `collection`, if there is one. For many NFTs this may not + /// make any sense, so users of this API should not be surprised to find a collection + /// results in `None` here. + fn collection_owner(_collection: &Self::CollectionId) -> Option { + None + } + + /// Returns the attribute value of `item` of `collection` corresponding to `key`. + /// + /// By default this is `None`; no attributes are defined. + fn attribute( + _collection: &Self::CollectionId, + _item: &Self::ItemId, + _namespace: &AttributeNamespace, + _key: &[u8], + ) -> Option> { + None + } + + /// Returns the strongly-typed attribute value of `item` of `collection` corresponding to + /// `key`. + /// + /// By default this just attempts to use `attribute`. + fn typed_attribute( + collection: &Self::CollectionId, + item: &Self::ItemId, + namespace: &AttributeNamespace, + key: &K, + ) -> Option { + key.using_encoded(|d| Self::attribute(collection, item, namespace, d)) + .and_then(|v| V::decode(&mut &v[..]).ok()) + } + + /// Returns the attribute value of `collection` corresponding to `key`. + /// + /// By default this is `None`; no attributes are defined. + fn collection_attribute(_collection: &Self::CollectionId, _key: &[u8]) -> Option> { + None + } + + /// Returns the strongly-typed attribute value of `collection` corresponding to `key`. + /// + /// By default this just attempts to use `collection_attribute`. + fn typed_collection_attribute( + collection: &Self::CollectionId, + key: &K, + ) -> Option { + key.using_encoded(|d| Self::collection_attribute(collection, d)) + .and_then(|v| V::decode(&mut &v[..]).ok()) + } + + /// Returns `true` if the `item` of `collection` may be transferred. + /// + /// Default implementation is that all items are transferable. + fn can_transfer(_collection: &Self::CollectionId, _item: &Self::ItemId) -> bool { + true + } +} + +/// Interface for enumerating items in existence or owned by a given account over many collections +/// of NFTs. +pub trait InspectEnumerable: Inspect { + /// The iterator type for [`Self::collections`]. + type CollectionsIterator: Iterator; + /// The iterator type for [`Self::items`]. + type ItemsIterator: Iterator; + /// The iterator type for [`Self::owned`]. + type OwnedIterator: Iterator; + /// The iterator type for [`Self::owned_in_collection`]. + type OwnedInCollectionIterator: Iterator; + + /// Returns an iterator of the collections in existence. + fn collections() -> Self::CollectionsIterator; + + /// Returns an iterator of the items of a `collection` in existence. + fn items(collection: &Self::CollectionId) -> Self::ItemsIterator; + + /// Returns an iterator of the items of all collections owned by `who`. + fn owned(who: &AccountId) -> Self::OwnedIterator; + + /// Returns an iterator of the items of `collection` owned by `who`. + fn owned_in_collection( + collection: &Self::CollectionId, + who: &AccountId, + ) -> Self::OwnedInCollectionIterator; +} + +/// Trait for providing the ability to create collections of nonfungible items. +pub trait Create: Inspect { + /// Create a `collection` of nonfungible items to be owned by `who` and managed by `admin`. + fn create_collection( + who: &AccountId, + admin: &AccountId, + config: &CollectionConfig, + ) -> Result; +} + +/// Trait for providing the ability to destroy collections of nonfungible items. +pub trait Destroy: Inspect { + /// The witness data needed to destroy an item. + type DestroyWitness; + + /// Provide the appropriate witness data needed to destroy an item. + fn get_destroy_witness(collection: &Self::CollectionId) -> Option; + + /// Destroy an existing fungible item. + /// * `collection`: The `CollectionId` to be destroyed. + /// * `witness`: Any witness data that needs to be provided to complete the operation + /// successfully. + /// * `maybe_check_owner`: An optional `AccountId` that can be used to authorize the destroy + /// command. If not provided, we will not do any authorization checks before destroying the + /// item. + /// + /// If successful, this function will return the actual witness data from the destroyed item. + /// This may be different than the witness data provided, and can be used to refund weight. + fn destroy( + collection: Self::CollectionId, + witness: Self::DestroyWitness, + maybe_check_owner: Option, + ) -> Result; +} + +/// Trait for providing an interface for multiple collections of NFT-like items which may be +/// minted, burned and/or have attributes set on them. +pub trait Mutate: Inspect { + /// Mint some `item` of `collection` to be owned by `who`. + /// + /// By default, this is not a supported operation. + fn mint_into( + _collection: &Self::CollectionId, + _item: &Self::ItemId, + _who: &AccountId, + _config: &ItemConfig, + _deposit_collection_owner: bool, + ) -> DispatchResult { + Err(TokenError::Unsupported.into()) + } + + /// Burn some `item` of `collection`. + /// + /// By default, this is not a supported operation. + fn burn( + _collection: &Self::CollectionId, + _item: &Self::ItemId, + _maybe_check_owner: Option<&AccountId>, + ) -> DispatchResult { + Err(TokenError::Unsupported.into()) + } + + /// Set attribute `value` of `item` of `collection`'s `key`. + /// + /// By default, this is not a supported operation. + fn set_attribute( + _collection: &Self::CollectionId, + _item: &Self::ItemId, + _key: &[u8], + _value: &[u8], + ) -> DispatchResult { + Err(TokenError::Unsupported.into()) + } + + /// Attempt to set the strongly-typed attribute `value` of `item` of `collection`'s `key`. + /// + /// By default this just attempts to use `set_attribute`. + fn set_typed_attribute( + collection: &Self::CollectionId, + item: &Self::ItemId, + key: &K, + value: &V, + ) -> DispatchResult { + key.using_encoded(|k| value.using_encoded(|v| Self::set_attribute(collection, item, k, v))) + } + + /// Set attribute `value` of `collection`'s `key`. + /// + /// By default, this is not a supported operation. + fn set_collection_attribute( + _collection: &Self::CollectionId, + _key: &[u8], + _value: &[u8], + ) -> DispatchResult { + Err(TokenError::Unsupported.into()) + } + + /// Attempt to set the strongly-typed attribute `value` of `collection`'s `key`. + /// + /// By default this just attempts to use `set_attribute`. + fn set_typed_collection_attribute( + collection: &Self::CollectionId, + key: &K, + value: &V, + ) -> DispatchResult { + key.using_encoded(|k| { + value.using_encoded(|v| Self::set_collection_attribute(collection, k, v)) + }) + } +} + +/// Trait for transferring non-fungible sets of items. +pub trait Transfer: Inspect { + /// Transfer `item` of `collection` into `destination` account. + fn transfer( + collection: &Self::CollectionId, + item: &Self::ItemId, + destination: &AccountId, + ) -> DispatchResult; +} diff --git a/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr b/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr index 170555665d..be31b39c11 100644 --- a/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr +++ b/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr @@ -26,5 +26,5 @@ error[E0277]: the trait bound `Vec: MaxEncodedLen` is not satisfied (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) - and 78 others + and 79 others = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageMyStorage, Vec>` to implement `StorageInfoTrait` diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr index a3af9897be..364eb5e6d5 100644 --- a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr @@ -28,7 +28,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 280 others + and 281 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` @@ -69,7 +69,7 @@ error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied (A, B, C, D) (A, B, C, D, E) (A, B, C, D, E, F) - and 162 others + and 163 others = note: required for `Bar` to implement `StaticTypeInfo` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` @@ -103,7 +103,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 280 others + and 281 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr index 9e87f87825..371e90323d 100644 --- a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr @@ -28,7 +28,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 280 others + and 281 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` @@ -69,7 +69,7 @@ error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied (A, B, C, D) (A, B, C, D, E) (A, B, C, D, E, F) - and 162 others + and 163 others = note: required for `Bar` to implement `StaticTypeInfo` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` @@ -103,7 +103,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 280 others + and 281 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` diff --git a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr index cce9fa70b3..b5443c6f32 100644 --- a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr +++ b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr @@ -13,5 +13,5 @@ error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) - and 78 others + and 79 others = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageInfoTrait` diff --git a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr index 877485dda2..afc7aaa876 100644 --- a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr +++ b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr @@ -13,6 +13,6 @@ error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) - and 78 others + and 79 others = note: required for `Key` to implement `KeyGeneratorMaxEncodedLen` = note: required for `frame_support::pallet_prelude::StorageNMap<_GeneratedPrefixForStorageFoo, Key, u32>` to implement `StorageInfoTrait` From ed31016ca2bfe24438c17e81fc98b97738af55ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 23 Dec 2022 23:28:11 +0100 Subject: [PATCH 076/127] Ignore empty authority changes (#13010) When something tries to enact an authority change with an empty authority set, we will ignore this now. --- frame/aura/src/lib.rs | 8 ++++++++ frame/babe/src/lib.rs | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/frame/aura/src/lib.rs b/frame/aura/src/lib.rs index 635e22c5d5..b19fbd57c5 100644 --- a/frame/aura/src/lib.rs +++ b/frame/aura/src/lib.rs @@ -153,7 +153,15 @@ impl Pallet { /// /// The storage will be applied immediately. /// And aura consensus log will be appended to block's log. + /// + /// This is a no-op if `new` is empty. pub fn change_authorities(new: BoundedVec) { + if new.is_empty() { + log::warn!(target: LOG_TARGET, "Ignoring empty authority change."); + + return + } + >::put(&new); let log = DigestItem::Consensus( diff --git a/frame/babe/src/lib.rs b/frame/babe/src/lib.rs index 16b2b21197..28ae4bfdc9 100644 --- a/frame/babe/src/lib.rs +++ b/frame/babe/src/lib.rs @@ -572,6 +572,8 @@ impl Pallet { /// /// Typically, this is not handled directly by the user, but by higher-level validator-set /// manager logic like `pallet-session`. + /// + /// This doesn't do anything if `authorities` is empty. pub fn enact_epoch_change( authorities: WeakBoundedVec<(AuthorityId, BabeAuthorityWeight), T::MaxAuthorities>, next_authorities: WeakBoundedVec<(AuthorityId, BabeAuthorityWeight), T::MaxAuthorities>, @@ -580,6 +582,12 @@ impl Pallet { // by the session module to be called before this. debug_assert!(Self::initialized().is_some()); + if authorities.is_empty() { + log::warn!(target: LOG_TARGET, "Ignoring empty epoch change."); + + return + } + // Update epoch index let epoch_index = EpochIndex::::get() .checked_add(1) From 999b1a18b084d9d87cd0cc0f230228e00fa15be6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= <123550+andresilva@users.noreply.github.com> Date: Sat, 24 Dec 2022 00:03:31 +0000 Subject: [PATCH 077/127] babe: allow skipping over empty epochs (#11727) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * babe: allow skipping epochs in pallet * babe: detect and skip epochs on client * babe: cleaner epoch util functions * babe: add test for runtime handling of skipped epochs * babe: simpler implementation of client handling of skipped epochs * babe: test client-side handling of skipped epochs * babe: add comments on client-side skipped epochs * babe: remove emptyline * babe: make it resilient to forks * babe: typo * babe: overflow-safe math * babe: add test for skipping epochs across different forks * Fix tests * FMT Co-authored-by: Bastian Köcher --- client/consensus/babe/src/lib.rs | 49 ++++- client/consensus/babe/src/tests.rs | 261 +++++++++++++++++++++++++++ frame/babe/src/lib.rs | 49 +++-- frame/babe/src/tests.rs | 31 ++++ primitives/consensus/babe/src/lib.rs | 19 ++ 5 files changed, 386 insertions(+), 23 deletions(-) diff --git a/client/consensus/babe/src/lib.rs b/client/consensus/babe/src/lib.rs index 1d5d4b5fe5..41c00169e5 100644 --- a/client/consensus/babe/src/lib.rs +++ b/client/consensus/babe/src/lib.rs @@ -543,7 +543,7 @@ fn aux_storage_cleanup + HeaderBackend, Block: B let stale_forks = match client.expand_forks(¬ification.stale_heads) { Ok(stale_forks) => stale_forks, Err((stale_forks, e)) => { - warn!(target: "babe", "{:?}", e,); + warn!(target: LOG_TARGET, "{:?}", e); stale_forks }, }; @@ -1511,11 +1511,12 @@ where if let Some(next_epoch_descriptor) = next_epoch_digest { old_epoch_changes = Some((*epoch_changes).clone()); - let viable_epoch = epoch_changes + let mut viable_epoch = epoch_changes .viable_epoch(&epoch_descriptor, |slot| Epoch::genesis(&self.config, slot)) .ok_or_else(|| { ConsensusError::ClientImport(Error::::FetchEpoch(parent_hash).into()) - })?; + })? + .into_cloned(); let epoch_config = next_config_digest .map(Into::into) @@ -1528,6 +1529,48 @@ where log::Level::Info }; + if viable_epoch.as_ref().end_slot() <= slot { + // some epochs must have been skipped as our current slot + // fits outside the current epoch. we will figure out + // which epoch it belongs to and we will re-use the same + // data for that epoch + let mut epoch_data = viable_epoch.as_mut(); + let skipped_epochs = + *slot.saturating_sub(epoch_data.start_slot) / epoch_data.duration; + + // NOTE: notice that we are only updating a local copy of the `Epoch`, this + // makes it so that when we insert the next epoch into `EpochChanges` below + // (after incrementing it), it will use the correct epoch index and start slot. + // we do not update the original epoch that will be re-used because there might + // be other forks (that we haven't imported) where the epoch isn't skipped, and + // to import those forks we want to keep the original epoch data. not updating + // the original epoch works because when we search the tree for which epoch to + // use for a given slot, we will search in-depth with the predicate + // `epoch.start_slot <= slot` which will still match correctly without updating + // `start_slot` to the correct value as below. + let epoch_index = epoch_data.epoch_index.checked_add(skipped_epochs).expect( + "epoch number is u64; it should be strictly smaller than number of slots; \ + slots relate in some way to wall clock time; \ + if u64 is not enough we should crash for safety; qed.", + ); + + let start_slot = skipped_epochs + .checked_mul(epoch_data.duration) + .and_then(|skipped_slots| epoch_data.start_slot.checked_add(skipped_slots)) + .expect( + "slot number is u64; it should relate in some way to wall clock time; \ + if u64 is not enough we should crash for safety; qed.", + ); + + warn!( + target: LOG_TARGET, + "👶 Epoch(s) skipped: from {} to {}", epoch_data.epoch_index, epoch_index, + ); + + epoch_data.epoch_index = epoch_index; + epoch_data.start_slot = Slot::from(start_slot); + } + log!( target: LOG_TARGET, log_level, diff --git a/client/consensus/babe/src/tests.rs b/client/consensus/babe/src/tests.rs index 84ac3d7341..f74864a003 100644 --- a/client/consensus/babe/src/tests.rs +++ b/client/consensus/babe/src/tests.rs @@ -28,6 +28,7 @@ use rand_chacha::{ use sc_block_builder::{BlockBuilder, BlockBuilderProvider}; use sc_client_api::{backend::TransactionFor, BlockchainEvents, Finalizer}; use sc_consensus::{BoxBlockImport, BoxJustificationImport}; +use sc_consensus_epochs::{EpochIdentifier, EpochIdentifierPosition}; use sc_consensus_slots::BackoffAuthoringOnFinalizedHeadLagging; use sc_network_test::{Block as TestBlock, *}; use sp_application_crypto::key_types::BABE; @@ -1109,3 +1110,263 @@ async fn obsolete_blocks_aux_data_cleanup() { // Present C4, C5 assert!(aux_data_check(&fork3_hashes, true)); } + +#[tokio::test] +async fn allows_skipping_epochs() { + let mut net = BabeTestNet::new(1); + + let peer = net.peer(0); + let data = peer.data.as_ref().expect("babe link set up during initialization"); + + let client = peer.client().as_client(); + let mut block_import = data.block_import.lock().take().expect("import set up during init"); + + let mut proposer_factory = DummyFactory { + client: client.clone(), + config: data.link.config.clone(), + epoch_changes: data.link.epoch_changes.clone(), + mutator: Arc::new(|_, _| ()), + }; + + let epoch_changes = data.link.epoch_changes.clone(); + let epoch_length = data.link.config.epoch_length; + + // we create all of the blocks in epoch 0 as well as a block in epoch 1 + let blocks = propose_and_import_blocks( + &client, + &mut proposer_factory, + &mut block_import, + client.chain_info().genesis_hash, + epoch_length as usize + 1, + ) + .await; + + // the first block in epoch 0 (#1) announces both epoch 0 and 1 (this is a + // special genesis epoch) + let epoch0 = epoch_changes + .shared_data() + .epoch(&EpochIdentifier { + position: EpochIdentifierPosition::Genesis0, + hash: blocks[0], + number: 1, + }) + .unwrap() + .clone(); + + assert_eq!(epoch0.epoch_index, 0); + assert_eq!(epoch0.start_slot, Slot::from(1)); + + let epoch1 = epoch_changes + .shared_data() + .epoch(&EpochIdentifier { + position: EpochIdentifierPosition::Genesis1, + hash: blocks[0], + number: 1, + }) + .unwrap() + .clone(); + + assert_eq!(epoch1.epoch_index, 1); + assert_eq!(epoch1.start_slot, Slot::from(epoch_length + 1)); + + // the first block in epoch 1 (#7) announces epoch 2. we will be skipping + // this epoch and therefore re-using its data for epoch 3 + let epoch2 = epoch_changes + .shared_data() + .epoch(&EpochIdentifier { + position: EpochIdentifierPosition::Regular, + hash: blocks[epoch_length as usize], + number: epoch_length + 1, + }) + .unwrap() + .clone(); + + assert_eq!(epoch2.epoch_index, 2); + assert_eq!(epoch2.start_slot, Slot::from(epoch_length * 2 + 1)); + + // we now author a block that belongs to epoch 3, thereby skipping epoch 2 + let last_block = client.expect_header(*blocks.last().unwrap()).unwrap(); + let block = propose_and_import_block( + &last_block, + Some((epoch_length * 3 + 1).into()), + &mut proposer_factory, + &mut block_import, + ) + .await; + + // and the first block in epoch 3 (#8) announces epoch 4 + let epoch4 = epoch_changes + .shared_data() + .epoch(&EpochIdentifier { + position: EpochIdentifierPosition::Regular, + hash: block, + number: epoch_length + 2, + }) + .unwrap() + .clone(); + + assert_eq!(epoch4.epoch_index, 4); + assert_eq!(epoch4.start_slot, Slot::from(epoch_length * 4 + 1)); + + // if we try to get the epoch data for a slot in epoch 3 + let epoch3 = epoch_changes + .shared_data() + .epoch_data_for_child_of( + descendent_query(&*client), + &block, + epoch_length + 2, + (epoch_length * 3 + 2).into(), + |slot| Epoch::genesis(&data.link.config, slot), + ) + .unwrap() + .unwrap(); + + // we get back the data for epoch 2 + assert_eq!(epoch3, epoch2); + + // but if we try to get the epoch data for a slot in epoch 4 + let epoch4_ = epoch_changes + .shared_data() + .epoch_data_for_child_of( + descendent_query(&*client), + &block, + epoch_length + 2, + (epoch_length * 4 + 1).into(), + |slot| Epoch::genesis(&data.link.config, slot), + ) + .unwrap() + .unwrap(); + + // we get epoch 4 as expected + assert_eq!(epoch4, epoch4_); +} + +#[tokio::test] +async fn allows_skipping_epochs_on_some_forks() { + let mut net = BabeTestNet::new(1); + + let peer = net.peer(0); + let data = peer.data.as_ref().expect("babe link set up during initialization"); + + let client = peer.client().as_client(); + let mut block_import = data.block_import.lock().take().expect("import set up during init"); + + let mut proposer_factory = DummyFactory { + client: client.clone(), + config: data.link.config.clone(), + epoch_changes: data.link.epoch_changes.clone(), + mutator: Arc::new(|_, _| ()), + }; + + let epoch_changes = data.link.epoch_changes.clone(); + let epoch_length = data.link.config.epoch_length; + + // we create all of the blocks in epoch 0 as well as two blocks in epoch 1 + let blocks = propose_and_import_blocks( + &client, + &mut proposer_factory, + &mut block_import, + client.chain_info().genesis_hash, + epoch_length as usize + 1, + ) + .await; + + // we now author a block that belongs to epoch 2, built on top of the last + // authored block in epoch 1. + let last_block = client.expect_header(*blocks.last().unwrap()).unwrap(); + + let epoch2_block = propose_and_import_block( + &last_block, + Some((epoch_length * 2 + 1).into()), + &mut proposer_factory, + &mut block_import, + ) + .await; + + // if we try to get the epoch data for a slot in epoch 2, we get the data that + // was previously announced when epoch 1 started + let epoch2 = epoch_changes + .shared_data() + .epoch_data_for_child_of( + descendent_query(&*client), + &epoch2_block, + epoch_length + 2, + (epoch_length * 2 + 2).into(), + |slot| Epoch::genesis(&data.link.config, slot), + ) + .unwrap() + .unwrap(); + + // we now author a block that belongs to epoch 3, built on top of the last + // authored block in epoch 1. authoring this block means we're skipping epoch 2 + // entirely on this fork + let epoch3_block = propose_and_import_block( + &last_block, + Some((epoch_length * 3 + 1).into()), + &mut proposer_factory, + &mut block_import, + ) + .await; + + // if we try to get the epoch data for a slot in epoch 3 + let epoch3_ = epoch_changes + .shared_data() + .epoch_data_for_child_of( + descendent_query(&*client), + &epoch3_block, + epoch_length + 2, + (epoch_length * 3 + 2).into(), + |slot| Epoch::genesis(&data.link.config, slot), + ) + .unwrap() + .unwrap(); + + // we get back the data for epoch 2 + assert_eq!(epoch3_, epoch2); + + // if we try to get the epoch data for a slot in epoch 4 in the fork + // where we skipped epoch 2, we should get the epoch data for epoch 4 + // that was announced at the beginning of epoch 3 + let epoch_data = epoch_changes + .shared_data() + .epoch_data_for_child_of( + descendent_query(&*client), + &epoch3_block, + epoch_length + 2, + (epoch_length * 4 + 1).into(), + |slot| Epoch::genesis(&data.link.config, slot), + ) + .unwrap() + .unwrap(); + + assert!(epoch_data != epoch3_); + + // if we try to get the epoch data for a slot in epoch 4 in the fork + // where we didn't skip epoch 2, we should get back the data for epoch 3, + // that was announced when epoch 2 started in that fork + let epoch_data = epoch_changes + .shared_data() + .epoch_data_for_child_of( + descendent_query(&*client), + &epoch2_block, + epoch_length + 2, + (epoch_length * 4 + 1).into(), + |slot| Epoch::genesis(&data.link.config, slot), + ) + .unwrap() + .unwrap(); + + assert!(epoch_data != epoch3_); + + let epoch3 = epoch_changes + .shared_data() + .epoch(&EpochIdentifier { + position: EpochIdentifierPosition::Regular, + hash: epoch2_block, + number: epoch_length + 2, + }) + .unwrap() + .clone(); + + assert_eq!(epoch_data, epoch3); +} diff --git a/frame/babe/src/lib.rs b/frame/babe/src/lib.rs index 28ae4bfdc9..eb87d65b05 100644 --- a/frame/babe/src/lib.rs +++ b/frame/babe/src/lib.rs @@ -588,10 +588,19 @@ impl Pallet { return } - // Update epoch index - let epoch_index = EpochIndex::::get() - .checked_add(1) - .expect("epoch indices will never reach 2^64 before the death of the universe; qed"); + // Update epoch index. + // + // NOTE: we figure out the epoch index from the slot, which may not + // necessarily be contiguous if the chain was offline for more than + // `T::EpochDuration` slots. When skipping from epoch N to e.g. N+4, we + // will be using the randomness and authorities for that epoch that had + // been previously announced for epoch N+1, and the randomness collected + // during the current epoch (N) will be used for epoch N+5. + let epoch_index = sp_consensus_babe::epoch_index( + CurrentSlot::::get(), + GenesisSlot::::get(), + T::EpochDuration::get(), + ); EpochIndex::::put(epoch_index); Authorities::::put(authorities); @@ -638,11 +647,16 @@ impl Pallet { } } - /// Finds the start slot of the current epoch. only guaranteed to - /// give correct results after `initialize` of the first block - /// in the chain (as its result is based off of `GenesisSlot`). + /// Finds the start slot of the current epoch. + /// + /// Only guaranteed to give correct results after `initialize` of the first + /// block in the chain (as its result is based off of `GenesisSlot`). pub fn current_epoch_start() -> Slot { - Self::epoch_start(EpochIndex::::get()) + sp_consensus_babe::epoch_start_slot( + EpochIndex::::get(), + GenesisSlot::::get(), + T::EpochDuration::get(), + ) } /// Produces information about the current epoch. @@ -666,9 +680,15 @@ impl Pallet { if u64 is not enough we should crash for safety; qed.", ); + let start_slot = sp_consensus_babe::epoch_start_slot( + next_epoch_index, + GenesisSlot::::get(), + T::EpochDuration::get(), + ); + Epoch { epoch_index: next_epoch_index, - start_slot: Self::epoch_start(next_epoch_index), + start_slot, duration: T::EpochDuration::get(), authorities: NextAuthorities::::get().to_vec(), randomness: NextRandomness::::get(), @@ -680,17 +700,6 @@ impl Pallet { } } - fn epoch_start(epoch_index: u64) -> Slot { - // (epoch_index * epoch_duration) + genesis_slot - - const PROOF: &str = "slot number is u64; it should relate in some way to wall clock time; \ - if u64 is not enough we should crash for safety; qed."; - - let epoch_start = epoch_index.checked_mul(T::EpochDuration::get()).expect(PROOF); - - epoch_start.checked_add(*GenesisSlot::::get()).expect(PROOF).into() - } - fn deposit_consensus(new: U) { let log = DigestItem::Consensus(BABE_ENGINE_ID, new.encode()); >::deposit_log(log) diff --git a/frame/babe/src/tests.rs b/frame/babe/src/tests.rs index d4132e6378..0b8a025471 100644 --- a/frame/babe/src/tests.rs +++ b/frame/babe/src/tests.rs @@ -948,3 +948,34 @@ fn generate_equivocation_report_blob() { println!("equivocation_proof.encode(): {:?}", equivocation_proof.encode()); }); } + +#[test] +fn skipping_over_epochs_works() { + let mut ext = new_test_ext(3); + + ext.execute_with(|| { + let epoch_duration: u64 = ::EpochDuration::get(); + + // this sets the genesis slot to 100; + let genesis_slot = 100; + go_to_block(1, genesis_slot); + + // we will author all blocks from epoch #0 and arrive at a point where + // we are in epoch #1. we should already have the randomness ready that + // will be used in epoch #2 + progress_to_block(epoch_duration + 1); + assert_eq!(EpochIndex::::get(), 1); + + // genesis randomness is an array of zeros + let randomness_for_epoch_2 = NextRandomness::::get(); + assert!(randomness_for_epoch_2 != [0; 32]); + + // we will now create a block for a slot that is part of epoch #4. + // we should appropriately increment the epoch index as well as re-use + // the randomness from epoch #2 on epoch #4 + go_to_block(System::block_number() + 1, genesis_slot + epoch_duration * 4); + + assert_eq!(EpochIndex::::get(), 4); + assert_eq!(Randomness::::get(), randomness_for_epoch_2); + }); +} diff --git a/primitives/consensus/babe/src/lib.rs b/primitives/consensus/babe/src/lib.rs index 621ab859b9..cb44afcb8d 100644 --- a/primitives/consensus/babe/src/lib.rs +++ b/primitives/consensus/babe/src/lib.rs @@ -360,6 +360,25 @@ pub struct Epoch { pub config: BabeEpochConfiguration, } +/// Returns the epoch index the given slot belongs to. +pub fn epoch_index(slot: Slot, genesis_slot: Slot, epoch_duration: u64) -> u64 { + *slot.saturating_sub(genesis_slot) / epoch_duration +} + +/// Returns the first slot at the given epoch index. +pub fn epoch_start_slot(epoch_index: u64, genesis_slot: Slot, epoch_duration: u64) -> Slot { + // (epoch_index * epoch_duration) + genesis_slot + + const PROOF: &str = "slot number is u64; it should relate in some way to wall clock time; \ + if u64 is not enough we should crash for safety; qed."; + + epoch_index + .checked_mul(epoch_duration) + .and_then(|slot| slot.checked_add(*genesis_slot)) + .expect(PROOF) + .into() +} + sp_api::decl_runtime_apis! { /// API necessary for block authorship with BABE. #[api_version(2)] From c16472e7e4a588d8e202c1ab3e865cf6e7b201c5 Mon Sep 17 00:00:00 2001 From: Ankan <10196091+Ank4n@users.noreply.github.com> Date: Mon, 26 Dec 2022 16:48:40 +0100 Subject: [PATCH 078/127] Allow StakingAdmin to set `min_commission` (#13018) * staking admin can set min commission * ".git/.scripts/bench-bot.sh" pallet dev pallet_staking * fmt * fix for pr comments Co-authored-by: command-bot <> --- bin/node/runtime/src/lib.rs | 2 +- frame/babe/src/mock.rs | 2 +- frame/fast-unstake/src/mock.rs | 2 +- frame/grandpa/src/mock.rs | 2 +- .../nomination-pools/benchmarking/src/mock.rs | 2 +- .../nomination-pools/test-staking/src/mock.rs | 2 +- frame/offences/benchmarking/src/mock.rs | 2 +- frame/root-offences/src/mock.rs | 2 +- frame/session/benchmarking/src/mock.rs | 2 +- frame/staking/src/benchmarking.rs | 7 + frame/staking/src/mock.rs | 14 +- frame/staking/src/pallet/mod.rs | 23 +- frame/staking/src/tests.rs | 40 ++ frame/staking/src/weights.rs | 385 +++++++++--------- 14 files changed, 286 insertions(+), 201 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 3e2701b227..0c946fa180 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -567,7 +567,7 @@ impl pallet_staking::Config for Runtime { type BondingDuration = BondingDuration; type SlashDeferDuration = SlashDeferDuration; /// A super-majority of the council can cancel the slash. - type SlashCancelOrigin = EitherOfDiverse< + type AdminOrigin = EitherOfDiverse< EnsureRoot, pallet_collective::EnsureProportionAtLeast, >; diff --git a/frame/babe/src/mock.rs b/frame/babe/src/mock.rs index 204de8aae1..dbeb588c18 100644 --- a/frame/babe/src/mock.rs +++ b/frame/babe/src/mock.rs @@ -195,7 +195,7 @@ impl pallet_staking::Config for Test { type SessionsPerEra = SessionsPerEra; type BondingDuration = BondingDuration; type SlashDeferDuration = SlashDeferDuration; - type SlashCancelOrigin = frame_system::EnsureRoot; + type AdminOrigin = frame_system::EnsureRoot; type SessionInterface = Self; type UnixTime = pallet_timestamp::Pallet; type EraPayout = pallet_staking::ConvertCurve; diff --git a/frame/fast-unstake/src/mock.rs b/frame/fast-unstake/src/mock.rs index b67dcf581e..3f974e5e1a 100644 --- a/frame/fast-unstake/src/mock.rs +++ b/frame/fast-unstake/src/mock.rs @@ -140,7 +140,7 @@ impl pallet_staking::Config for Runtime { type Reward = (); type SessionsPerEra = (); type SlashDeferDuration = (); - type SlashCancelOrigin = frame_system::EnsureRoot; + type AdminOrigin = frame_system::EnsureRoot; type BondingDuration = BondingDuration; type SessionInterface = (); type EraPayout = pallet_staking::ConvertCurve; diff --git a/frame/grandpa/src/mock.rs b/frame/grandpa/src/mock.rs index 1a97b1345f..131f6cafcd 100644 --- a/frame/grandpa/src/mock.rs +++ b/frame/grandpa/src/mock.rs @@ -199,7 +199,7 @@ impl pallet_staking::Config for Test { type SessionsPerEra = SessionsPerEra; type BondingDuration = BondingDuration; type SlashDeferDuration = (); - type SlashCancelOrigin = frame_system::EnsureRoot; + type AdminOrigin = frame_system::EnsureRoot; type SessionInterface = Self; type UnixTime = pallet_timestamp::Pallet; type EraPayout = pallet_staking::ConvertCurve; diff --git a/frame/nomination-pools/benchmarking/src/mock.rs b/frame/nomination-pools/benchmarking/src/mock.rs index 6959aa9783..06a6683859 100644 --- a/frame/nomination-pools/benchmarking/src/mock.rs +++ b/frame/nomination-pools/benchmarking/src/mock.rs @@ -102,7 +102,7 @@ impl pallet_staking::Config for Runtime { type Reward = (); type SessionsPerEra = (); type SlashDeferDuration = (); - type SlashCancelOrigin = frame_system::EnsureRoot; + type AdminOrigin = frame_system::EnsureRoot; type BondingDuration = ConstU32<3>; type SessionInterface = (); type EraPayout = pallet_staking::ConvertCurve; diff --git a/frame/nomination-pools/test-staking/src/mock.rs b/frame/nomination-pools/test-staking/src/mock.rs index 568dec7b3a..c67aec0134 100644 --- a/frame/nomination-pools/test-staking/src/mock.rs +++ b/frame/nomination-pools/test-staking/src/mock.rs @@ -116,7 +116,7 @@ impl pallet_staking::Config for Runtime { type Reward = (); type SessionsPerEra = (); type SlashDeferDuration = (); - type SlashCancelOrigin = frame_system::EnsureRoot; + type AdminOrigin = frame_system::EnsureRoot; type BondingDuration = BondingDuration; type SessionInterface = (); type EraPayout = pallet_staking::ConvertCurve; diff --git a/frame/offences/benchmarking/src/mock.rs b/frame/offences/benchmarking/src/mock.rs index de3a4eca63..223f551a6e 100644 --- a/frame/offences/benchmarking/src/mock.rs +++ b/frame/offences/benchmarking/src/mock.rs @@ -173,7 +173,7 @@ impl pallet_staking::Config for Test { type Reward = (); type SessionsPerEra = (); type SlashDeferDuration = (); - type SlashCancelOrigin = frame_system::EnsureRoot; + type AdminOrigin = frame_system::EnsureRoot; type BondingDuration = (); type SessionInterface = Self; type EraPayout = pallet_staking::ConvertCurve; diff --git a/frame/root-offences/src/mock.rs b/frame/root-offences/src/mock.rs index 65bfcad4b2..e1d9ee14ee 100644 --- a/frame/root-offences/src/mock.rs +++ b/frame/root-offences/src/mock.rs @@ -184,7 +184,7 @@ impl pallet_staking::Config for Test { type Reward = (); type SessionsPerEra = SessionsPerEra; type SlashDeferDuration = SlashDeferDuration; - type SlashCancelOrigin = frame_system::EnsureRoot; + type AdminOrigin = frame_system::EnsureRoot; type BondingDuration = BondingDuration; type SessionInterface = Self; type EraPayout = pallet_staking::ConvertCurve; diff --git a/frame/session/benchmarking/src/mock.rs b/frame/session/benchmarking/src/mock.rs index 2db7eb3851..0699640bc0 100644 --- a/frame/session/benchmarking/src/mock.rs +++ b/frame/session/benchmarking/src/mock.rs @@ -167,7 +167,7 @@ impl pallet_staking::Config for Test { type Reward = (); type SessionsPerEra = (); type SlashDeferDuration = (); - type SlashCancelOrigin = frame_system::EnsureRoot; + type AdminOrigin = frame_system::EnsureRoot; type BondingDuration = (); type SessionInterface = Self; type EraPayout = pallet_staking::ConvertCurve; diff --git a/frame/staking/src/benchmarking.rs b/frame/staking/src/benchmarking.rs index 81fa0f9d81..b3d32b26ec 100644 --- a/frame/staking/src/benchmarking.rs +++ b/frame/staking/src/benchmarking.rs @@ -922,6 +922,13 @@ benchmarks! { ); } + set_min_commission { + let min_commission = Perbill::max_value(); + }: _(RawOrigin::Root, min_commission) + verify { + assert_eq!(MinCommission::::get(), Perbill::from_percent(100)); + } + impl_benchmark_test_suite!( Staking, crate::mock::ExtBuilder::default().has_stakers(true), diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index d3affda052..843e452125 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -20,13 +20,14 @@ use crate::{self as pallet_staking, *}; use frame_election_provider_support::{onchain, SequentialPhragmen, VoteWeight}; use frame_support::{ - assert_ok, parameter_types, + assert_ok, ord_parameter_types, parameter_types, traits::{ - ConstU32, ConstU64, Currency, FindAuthor, GenesisBuild, Get, Hooks, Imbalance, - OnUnbalanced, OneSessionHandler, + ConstU32, ConstU64, Currency, EitherOfDiverse, FindAuthor, GenesisBuild, Get, Hooks, + Imbalance, OnUnbalanced, OneSessionHandler, }, weights::constants::RocksDbWeight, }; +use frame_system::{EnsureRoot, EnsureSignedBy}; use sp_core::H256; use sp_io; use sp_runtime::{ @@ -292,7 +293,7 @@ impl crate::pallet::pallet::Config for Test { type Reward = MockReward; type SessionsPerEra = SessionsPerEra; type SlashDeferDuration = SlashDeferDuration; - type SlashCancelOrigin = frame_system::EnsureRoot; + type AdminOrigin = EnsureOneOrRoot; type BondingDuration = BondingDuration; type SessionInterface = Self; type EraPayout = ConvertCurve; @@ -797,6 +798,11 @@ pub(crate) fn staking_events() -> Vec> { parameter_types! { static StakingEventsIndex: usize = 0; } +ord_parameter_types! { + pub const One: u64 = 1; +} + +type EnsureOneOrRoot = EitherOfDiverse, EnsureSignedBy>; pub(crate) fn staking_events_since_last_call() -> Vec> { let all: Vec<_> = System::events() diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index f04d13d05f..8e8a8d9c7f 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -183,8 +183,10 @@ pub mod pallet { #[pallet::constant] type SlashDeferDuration: Get; - /// The origin which can cancel a deferred slash. Root can always do this. - type SlashCancelOrigin: EnsureOrigin; + /// The origin which can manage less critical staking parameters that does not require root. + /// + /// Supported actions: (1) cancel deferred slash, (2) set minimum commission. + type AdminOrigin: EnsureOrigin; /// Interface for interacting with a session pallet. type SessionInterface: SessionInterface; @@ -1452,7 +1454,7 @@ pub mod pallet { /// Cancel enactment of a deferred slash. /// - /// Can be called by the `T::SlashCancelOrigin`. + /// Can be called by the `T::AdminOrigin`. /// /// Parameters: era and indices of the slashes for that era to kill. #[pallet::call_index(17)] @@ -1462,7 +1464,7 @@ pub mod pallet { era: EraIndex, slash_indices: Vec, ) -> DispatchResult { - T::SlashCancelOrigin::ensure_origin(origin)?; + T::AdminOrigin::ensure_origin(origin)?; ensure!(!slash_indices.is_empty(), Error::::EmptyTargets); ensure!(is_sorted_and_unique(&slash_indices), Error::::NotSortedAndUnique); @@ -1683,7 +1685,6 @@ pub mod pallet { config_op_exp!(MinCommission, min_commission); Ok(()) } - /// Declare a `controller` to stop participating as either a validator or nominator. /// /// Effects will be felt at the beginning of the next era. @@ -1792,6 +1793,18 @@ pub mod pallet { })?; Ok(()) } + + /// Sets the minimum amount of commission that each validators must maintain. + /// + /// This call has lower privilege requirements than `set_staking_config` and can be called + /// by the `T::AdminOrigin`. Root can always call this. + #[pallet::call_index(25)] + #[pallet::weight(T::WeightInfo::set_min_commission())] + pub fn set_min_commission(origin: OriginFor, new: Perbill) -> DispatchResult { + T::AdminOrigin::ensure_origin(origin)?; + MinCommission::::put(new); + Ok(()) + } } } diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 5c2766deee..46c3c97441 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -5726,6 +5726,46 @@ fn scale_validator_count_errors() { }) } +#[test] +fn set_min_commission_works_with_admin_origin() { + ExtBuilder::default().build_and_execute(|| { + // no minimum commission set initially + assert_eq!(MinCommission::::get(), Zero::zero()); + + // root can set min commission + assert_ok!(Staking::set_min_commission(RuntimeOrigin::root(), Perbill::from_percent(10))); + + assert_eq!(MinCommission::::get(), Perbill::from_percent(10)); + + // Non privileged origin can not set min_commission + assert_noop!( + Staking::set_min_commission(RuntimeOrigin::signed(2), Perbill::from_percent(15)), + BadOrigin + ); + + // Admin Origin can set min commission + assert_ok!(Staking::set_min_commission( + RuntimeOrigin::signed(1), + Perbill::from_percent(15), + )); + + // setting commission below min_commission fails + assert_noop!( + Staking::validate( + RuntimeOrigin::signed(10), + ValidatorPrefs { commission: Perbill::from_percent(14), blocked: false } + ), + Error::::CommissionTooLow + ); + + // setting commission >= min_commission works + assert_ok!(Staking::validate( + RuntimeOrigin::signed(10), + ValidatorPrefs { commission: Perbill::from_percent(15), blocked: false } + )); + }) +} + mod staking_interface { use frame_support::storage::with_storage_layer; use sp_staking::StakingInterface; diff --git a/frame/staking/src/weights.rs b/frame/staking/src/weights.rs index aebb8eeb9b..9c283f5a06 100644 --- a/frame/staking/src/weights.rs +++ b/frame/staking/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_staking //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-12-14, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-12-25, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 @@ -77,6 +77,7 @@ pub trait WeightInfo { fn set_staking_configs_all_remove() -> Weight; fn chill_other() -> Weight; fn force_apply_min_commission() -> Weight; + fn set_min_commission() -> Weight; } /// Weights for pallet_staking using the Substrate node and recommended hardware. @@ -88,8 +89,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn bond() -> Weight { - // Minimum execution time: 54_402 nanoseconds. - Weight::from_ref_time(55_096_000) + // Minimum execution time: 54_884 nanoseconds. + Weight::from_ref_time(55_487_000) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -99,8 +100,8 @@ impl WeightInfo for SubstrateWeight { // Storage: VoterList ListNodes (r:3 w:3) // Storage: VoterList ListBags (r:2 w:2) fn bond_extra() -> Weight { - // Minimum execution time: 94_407 nanoseconds. - Weight::from_ref_time(95_209_000) + // Minimum execution time: 95_115 nanoseconds. + Weight::from_ref_time(96_213_000) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(7)) } @@ -114,8 +115,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking Bonded (r:1 w:0) // Storage: VoterList ListBags (r:2 w:2) fn unbond() -> Weight { - // Minimum execution time: 101_046 nanoseconds. - Weight::from_ref_time(101_504_000) + // Minimum execution time: 102_031 nanoseconds. + Weight::from_ref_time(102_842_000) .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().writes(8)) } @@ -125,10 +126,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_update(s: u32, ) -> Weight { - // Minimum execution time: 45_452 nanoseconds. - Weight::from_ref_time(47_031_537) - // Standard Error: 491 - .saturating_add(Weight::from_ref_time(67_148).saturating_mul(s.into())) + // Minimum execution time: 46_569 nanoseconds. + Weight::from_ref_time(48_034_493) + // Standard Error: 654 + .saturating_add(Weight::from_ref_time(63_628).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -148,10 +149,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking SpanSlash (r:0 w:2) /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_kill(s: u32, ) -> Weight { - // Minimum execution time: 88_067 nanoseconds. - Weight::from_ref_time(93_309_587) - // Standard Error: 4_762 - .saturating_add(Weight::from_ref_time(1_114_938).saturating_mul(s.into())) + // Minimum execution time: 90_154 nanoseconds. + Weight::from_ref_time(95_725_631) + // Standard Error: 2_491 + .saturating_add(Weight::from_ref_time(1_110_795).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(13)) .saturating_add(T::DbWeight::get().writes(12)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -168,8 +169,8 @@ impl WeightInfo for SubstrateWeight { // Storage: VoterList CounterForListNodes (r:1 w:1) // Storage: Staking CounterForValidators (r:1 w:1) fn validate() -> Weight { - // Minimum execution time: 67_308 nanoseconds. - Weight::from_ref_time(68_266_000) + // Minimum execution time: 67_978 nanoseconds. + Weight::from_ref_time(69_153_000) .saturating_add(T::DbWeight::get().reads(11)) .saturating_add(T::DbWeight::get().writes(5)) } @@ -177,10 +178,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking Nominators (r:1 w:1) /// The range of component `k` is `[1, 128]`. fn kick(k: u32, ) -> Weight { - // Minimum execution time: 40_913 nanoseconds. - Weight::from_ref_time(48_140_584) - // Standard Error: 13_396 - .saturating_add(Weight::from_ref_time(6_862_893).saturating_mul(k.into())) + // Minimum execution time: 45_328 nanoseconds. + Weight::from_ref_time(47_719_103) + // Standard Error: 14_458 + .saturating_add(Weight::from_ref_time(6_999_252).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) @@ -198,10 +199,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking CounterForNominators (r:1 w:1) /// The range of component `n` is `[1, 16]`. fn nominate(n: u32, ) -> Weight { - // Minimum execution time: 73_490 nanoseconds. - Weight::from_ref_time(72_520_864) - // Standard Error: 7_090 - .saturating_add(Weight::from_ref_time(2_800_566).saturating_mul(n.into())) + // Minimum execution time: 74_650 nanoseconds. + Weight::from_ref_time(74_350_075) + // Standard Error: 10_527 + .saturating_add(Weight::from_ref_time(2_878_737).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(6)) @@ -214,58 +215,58 @@ impl WeightInfo for SubstrateWeight { // Storage: VoterList ListBags (r:1 w:1) // Storage: VoterList CounterForListNodes (r:1 w:1) fn chill() -> Weight { - // Minimum execution time: 66_293 nanoseconds. - Weight::from_ref_time(66_946_000) + // Minimum execution time: 67_790 nanoseconds. + Weight::from_ref_time(68_738_000) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(6)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking Payee (r:0 w:1) fn set_payee() -> Weight { - // Minimum execution time: 18_134 nanoseconds. - Weight::from_ref_time(18_497_000) + // Minimum execution time: 19_237 nanoseconds. + Weight::from_ref_time(19_534_000) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Staking Bonded (r:1 w:1) // Storage: Staking Ledger (r:2 w:2) fn set_controller() -> Weight { - // Minimum execution time: 26_728 nanoseconds. - Weight::from_ref_time(27_154_000) + // Minimum execution time: 27_288 nanoseconds. + Weight::from_ref_time(27_667_000) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } // Storage: Staking ValidatorCount (r:0 w:1) fn set_validator_count() -> Weight { - // Minimum execution time: 4_877 nanoseconds. - Weight::from_ref_time(5_028_000) + // Minimum execution time: 5_155 nanoseconds. + Weight::from_ref_time(5_464_000) .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Staking ForceEra (r:0 w:1) fn force_no_eras() -> Weight { - // Minimum execution time: 5_000 nanoseconds. - Weight::from_ref_time(5_290_000) + // Minimum execution time: 5_405 nanoseconds. + Weight::from_ref_time(5_670_000) .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Staking ForceEra (r:0 w:1) fn force_new_era() -> Weight { - // Minimum execution time: 5_093 nanoseconds. - Weight::from_ref_time(5_378_000) + // Minimum execution time: 5_459 nanoseconds. + Weight::from_ref_time(5_616_000) .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Staking ForceEra (r:0 w:1) fn force_new_era_always() -> Weight { - // Minimum execution time: 5_144 nanoseconds. - Weight::from_ref_time(5_454_000) + // Minimum execution time: 5_476 nanoseconds. + Weight::from_ref_time(5_692_000) .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Staking Invulnerables (r:0 w:1) /// The range of component `v` is `[0, 1000]`. fn set_invulnerables(v: u32, ) -> Weight { - // Minimum execution time: 5_190 nanoseconds. - Weight::from_ref_time(5_960_962) - // Standard Error: 41 - .saturating_add(Weight::from_ref_time(10_329).saturating_mul(v.into())) + // Minimum execution time: 5_544 nanoseconds. + Weight::from_ref_time(6_513_190) + // Standard Error: 76 + .saturating_add(Weight::from_ref_time(9_975).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Staking Bonded (r:1 w:1) @@ -283,10 +284,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking SpanSlash (r:0 w:2) /// The range of component `s` is `[0, 100]`. fn force_unstake(s: u32, ) -> Weight { - // Minimum execution time: 80_516 nanoseconds. - Weight::from_ref_time(86_317_884) - // Standard Error: 2_212 - .saturating_add(Weight::from_ref_time(1_103_962).saturating_mul(s.into())) + // Minimum execution time: 82_414 nanoseconds. + Weight::from_ref_time(88_511_246) + // Standard Error: 2_622 + .saturating_add(Weight::from_ref_time(1_131_814).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(11)) .saturating_add(T::DbWeight::get().writes(12)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -294,10 +295,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking UnappliedSlashes (r:1 w:1) /// The range of component `s` is `[1, 1000]`. fn cancel_deferred_slash(s: u32, ) -> Weight { - // Minimum execution time: 91_795 nanoseconds. - Weight::from_ref_time(904_524_900) - // Standard Error: 59_193 - .saturating_add(Weight::from_ref_time(4_944_680).saturating_mul(s.into())) + // Minimum execution time: 94_197 nanoseconds. + Weight::from_ref_time(903_418_326) + // Standard Error: 59_354 + .saturating_add(Weight::from_ref_time(4_948_354).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -312,10 +313,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) /// The range of component `n` is `[0, 256]`. fn payout_stakers_dead_controller(n: u32, ) -> Weight { - // Minimum execution time: 127_774 nanoseconds. - Weight::from_ref_time(178_857_156) - // Standard Error: 15_229 - .saturating_add(Weight::from_ref_time(22_112_174).saturating_mul(n.into())) + // Minimum execution time: 133_065 nanoseconds. + Weight::from_ref_time(197_555_906) + // Standard Error: 19_561 + .saturating_add(Weight::from_ref_time(22_683_426).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(2)) @@ -333,10 +334,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) /// The range of component `n` is `[0, 256]`. fn payout_stakers_alive_staked(n: u32, ) -> Weight { - // Minimum execution time: 161_910 nanoseconds. - Weight::from_ref_time(217_635_072) - // Standard Error: 30_726 - .saturating_add(Weight::from_ref_time(31_244_329).saturating_mul(n.into())) + // Minimum execution time: 164_719 nanoseconds. + Weight::from_ref_time(226_304_276) + // Standard Error: 31_675 + .saturating_add(Weight::from_ref_time(32_622_427).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -350,10 +351,10 @@ impl WeightInfo for SubstrateWeight { // Storage: VoterList ListBags (r:2 w:2) /// The range of component `l` is `[1, 32]`. fn rebond(l: u32, ) -> Weight { - // Minimum execution time: 92_986 nanoseconds. - Weight::from_ref_time(94_880_481) - // Standard Error: 2_007 - .saturating_add(Weight::from_ref_time(31_421).saturating_mul(l.into())) + // Minimum execution time: 95_631 nanoseconds. + Weight::from_ref_time(96_861_556) + // Standard Error: 2_114 + .saturating_add(Weight::from_ref_time(37_543).saturating_mul(l.into())) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(8)) } @@ -372,10 +373,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking SpanSlash (r:0 w:1) /// The range of component `s` is `[1, 100]`. fn reap_stash(s: u32, ) -> Weight { - // Minimum execution time: 92_750 nanoseconds. - Weight::from_ref_time(95_115_568) - // Standard Error: 2_037 - .saturating_add(Weight::from_ref_time(1_086_488).saturating_mul(s.into())) + // Minimum execution time: 95_251 nanoseconds. + Weight::from_ref_time(97_818_954) + // Standard Error: 2_356 + .saturating_add(Weight::from_ref_time(1_104_695).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().writes(12)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -396,19 +397,20 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking ErasStakers (r:0 w:1) // Storage: Staking ErasTotalStake (r:0 w:1) // Storage: Staking ErasStartSessionIndex (r:0 w:1) + // Storage: Staking MinimumActiveStake (r:0 w:1) /// The range of component `v` is `[1, 10]`. /// The range of component `n` is `[0, 100]`. fn new_era(v: u32, n: u32, ) -> Weight { - // Minimum execution time: 506_543 nanoseconds. - Weight::from_ref_time(507_261_000) - // Standard Error: 1_766_631 - .saturating_add(Weight::from_ref_time(59_139_153).saturating_mul(v.into())) - // Standard Error: 176_035 - .saturating_add(Weight::from_ref_time(13_512_781).saturating_mul(n.into())) + // Minimum execution time: 512_923 nanoseconds. + Weight::from_ref_time(514_740_000) + // Standard Error: 1_790_238 + .saturating_add(Weight::from_ref_time(59_320_539).saturating_mul(v.into())) + // Standard Error: 178_387 + .saturating_add(Weight::from_ref_time(13_902_705).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(206)) .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(3)) + .saturating_add(T::DbWeight::get().writes(4)) .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(v.into()))) } // Storage: VoterList CounterForListNodes (r:1 w:0) @@ -418,27 +420,29 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking Validators (r:500 w:0) // Storage: Staking Bonded (r:1500 w:0) // Storage: Staking Ledger (r:1500 w:0) + // Storage: Staking MinimumActiveStake (r:0 w:1) /// The range of component `v` is `[500, 1000]`. /// The range of component `n` is `[500, 1000]`. fn get_npos_voters(v: u32, n: u32, ) -> Weight { - // Minimum execution time: 24_155_382 nanoseconds. - Weight::from_ref_time(24_252_568_000) - // Standard Error: 319_250 - .saturating_add(Weight::from_ref_time(3_596_056).saturating_mul(v.into())) - // Standard Error: 319_250 - .saturating_add(Weight::from_ref_time(2_852_023).saturating_mul(n.into())) + // Minimum execution time: 24_913_316 nanoseconds. + Weight::from_ref_time(25_053_596_000) + // Standard Error: 324_610 + .saturating_add(Weight::from_ref_time(3_454_859).saturating_mul(v.into())) + // Standard Error: 324_610 + .saturating_add(Weight::from_ref_time(3_020_267).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(201)) .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Staking CounterForValidators (r:1 w:0) // Storage: Staking Validators (r:501 w:0) /// The range of component `v` is `[500, 1000]`. fn get_npos_targets(v: u32, ) -> Weight { - // Minimum execution time: 4_741_111 nanoseconds. - Weight::from_ref_time(113_360_179) - // Standard Error: 25_375 - .saturating_add(Weight::from_ref_time(9_494_142).saturating_mul(v.into())) + // Minimum execution time: 4_916_401 nanoseconds. + Weight::from_ref_time(81_160_966) + // Standard Error: 23_829 + .saturating_add(Weight::from_ref_time(9_883_413).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(v.into()))) } @@ -449,8 +453,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking MaxNominatorsCount (r:0 w:1) // Storage: Staking MinNominatorBond (r:0 w:1) fn set_staking_configs_all_set() -> Weight { - // Minimum execution time: 11_074 nanoseconds. - Weight::from_ref_time(11_312_000) + // Minimum execution time: 10_937 nanoseconds. + Weight::from_ref_time(11_324_000) .saturating_add(T::DbWeight::get().writes(6)) } // Storage: Staking MinCommission (r:0 w:1) @@ -460,8 +464,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking MaxNominatorsCount (r:0 w:1) // Storage: Staking MinNominatorBond (r:0 w:1) fn set_staking_configs_all_remove() -> Weight { - // Minimum execution time: 9_795 nanoseconds. - Weight::from_ref_time(10_116_000) + // Minimum execution time: 9_424 nanoseconds. + Weight::from_ref_time(10_021_000) .saturating_add(T::DbWeight::get().writes(6)) } // Storage: Staking Ledger (r:1 w:0) @@ -475,19 +479,25 @@ impl WeightInfo for SubstrateWeight { // Storage: VoterList ListBags (r:1 w:1) // Storage: VoterList CounterForListNodes (r:1 w:1) fn chill_other() -> Weight { - // Minimum execution time: 82_914 nanoseconds. - Weight::from_ref_time(83_848_000) + // Minimum execution time: 84_495 nanoseconds. + Weight::from_ref_time(85_559_000) .saturating_add(T::DbWeight::get().reads(11)) .saturating_add(T::DbWeight::get().writes(6)) } // Storage: Staking MinCommission (r:1 w:0) // Storage: Staking Validators (r:1 w:1) fn force_apply_min_commission() -> Weight { - // Minimum execution time: 20_317 nanoseconds. - Weight::from_ref_time(20_639_000) + // Minimum execution time: 20_385 nanoseconds. + Weight::from_ref_time(20_824_000) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } + // Storage: Staking MinCommission (r:0 w:1) + fn set_min_commission() -> Weight { + // Minimum execution time: 6_995 nanoseconds. + Weight::from_ref_time(7_213_000) + .saturating_add(T::DbWeight::get().writes(1)) + } } // For backwards compatibility and tests @@ -498,8 +508,8 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn bond() -> Weight { - // Minimum execution time: 54_402 nanoseconds. - Weight::from_ref_time(55_096_000) + // Minimum execution time: 54_884 nanoseconds. + Weight::from_ref_time(55_487_000) .saturating_add(RocksDbWeight::get().reads(4)) .saturating_add(RocksDbWeight::get().writes(4)) } @@ -509,8 +519,8 @@ impl WeightInfo for () { // Storage: VoterList ListNodes (r:3 w:3) // Storage: VoterList ListBags (r:2 w:2) fn bond_extra() -> Weight { - // Minimum execution time: 94_407 nanoseconds. - Weight::from_ref_time(95_209_000) + // Minimum execution time: 95_115 nanoseconds. + Weight::from_ref_time(96_213_000) .saturating_add(RocksDbWeight::get().reads(8)) .saturating_add(RocksDbWeight::get().writes(7)) } @@ -524,8 +534,8 @@ impl WeightInfo for () { // Storage: Staking Bonded (r:1 w:0) // Storage: VoterList ListBags (r:2 w:2) fn unbond() -> Weight { - // Minimum execution time: 101_046 nanoseconds. - Weight::from_ref_time(101_504_000) + // Minimum execution time: 102_031 nanoseconds. + Weight::from_ref_time(102_842_000) .saturating_add(RocksDbWeight::get().reads(12)) .saturating_add(RocksDbWeight::get().writes(8)) } @@ -535,10 +545,10 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_update(s: u32, ) -> Weight { - // Minimum execution time: 45_452 nanoseconds. - Weight::from_ref_time(47_031_537) - // Standard Error: 491 - .saturating_add(Weight::from_ref_time(67_148).saturating_mul(s.into())) + // Minimum execution time: 46_569 nanoseconds. + Weight::from_ref_time(48_034_493) + // Standard Error: 654 + .saturating_add(Weight::from_ref_time(63_628).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(4)) .saturating_add(RocksDbWeight::get().writes(3)) } @@ -558,10 +568,10 @@ impl WeightInfo for () { // Storage: Staking SpanSlash (r:0 w:2) /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_kill(s: u32, ) -> Weight { - // Minimum execution time: 88_067 nanoseconds. - Weight::from_ref_time(93_309_587) - // Standard Error: 4_762 - .saturating_add(Weight::from_ref_time(1_114_938).saturating_mul(s.into())) + // Minimum execution time: 90_154 nanoseconds. + Weight::from_ref_time(95_725_631) + // Standard Error: 2_491 + .saturating_add(Weight::from_ref_time(1_110_795).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(13)) .saturating_add(RocksDbWeight::get().writes(12)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -578,8 +588,8 @@ impl WeightInfo for () { // Storage: VoterList CounterForListNodes (r:1 w:1) // Storage: Staking CounterForValidators (r:1 w:1) fn validate() -> Weight { - // Minimum execution time: 67_308 nanoseconds. - Weight::from_ref_time(68_266_000) + // Minimum execution time: 67_978 nanoseconds. + Weight::from_ref_time(69_153_000) .saturating_add(RocksDbWeight::get().reads(11)) .saturating_add(RocksDbWeight::get().writes(5)) } @@ -587,10 +597,10 @@ impl WeightInfo for () { // Storage: Staking Nominators (r:1 w:1) /// The range of component `k` is `[1, 128]`. fn kick(k: u32, ) -> Weight { - // Minimum execution time: 40_913 nanoseconds. - Weight::from_ref_time(48_140_584) - // Standard Error: 13_396 - .saturating_add(Weight::from_ref_time(6_862_893).saturating_mul(k.into())) + // Minimum execution time: 45_328 nanoseconds. + Weight::from_ref_time(47_719_103) + // Standard Error: 14_458 + .saturating_add(Weight::from_ref_time(6_999_252).saturating_mul(k.into())) .saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(k.into()))) @@ -608,10 +618,10 @@ impl WeightInfo for () { // Storage: Staking CounterForNominators (r:1 w:1) /// The range of component `n` is `[1, 16]`. fn nominate(n: u32, ) -> Weight { - // Minimum execution time: 73_490 nanoseconds. - Weight::from_ref_time(72_520_864) - // Standard Error: 7_090 - .saturating_add(Weight::from_ref_time(2_800_566).saturating_mul(n.into())) + // Minimum execution time: 74_650 nanoseconds. + Weight::from_ref_time(74_350_075) + // Standard Error: 10_527 + .saturating_add(Weight::from_ref_time(2_878_737).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(12)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(6)) @@ -624,58 +634,58 @@ impl WeightInfo for () { // Storage: VoterList ListBags (r:1 w:1) // Storage: VoterList CounterForListNodes (r:1 w:1) fn chill() -> Weight { - // Minimum execution time: 66_293 nanoseconds. - Weight::from_ref_time(66_946_000) + // Minimum execution time: 67_790 nanoseconds. + Weight::from_ref_time(68_738_000) .saturating_add(RocksDbWeight::get().reads(8)) .saturating_add(RocksDbWeight::get().writes(6)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking Payee (r:0 w:1) fn set_payee() -> Weight { - // Minimum execution time: 18_134 nanoseconds. - Weight::from_ref_time(18_497_000) + // Minimum execution time: 19_237 nanoseconds. + Weight::from_ref_time(19_534_000) .saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Staking Bonded (r:1 w:1) // Storage: Staking Ledger (r:2 w:2) fn set_controller() -> Weight { - // Minimum execution time: 26_728 nanoseconds. - Weight::from_ref_time(27_154_000) + // Minimum execution time: 27_288 nanoseconds. + Weight::from_ref_time(27_667_000) .saturating_add(RocksDbWeight::get().reads(3)) .saturating_add(RocksDbWeight::get().writes(3)) } // Storage: Staking ValidatorCount (r:0 w:1) fn set_validator_count() -> Weight { - // Minimum execution time: 4_877 nanoseconds. - Weight::from_ref_time(5_028_000) + // Minimum execution time: 5_155 nanoseconds. + Weight::from_ref_time(5_464_000) .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Staking ForceEra (r:0 w:1) fn force_no_eras() -> Weight { - // Minimum execution time: 5_000 nanoseconds. - Weight::from_ref_time(5_290_000) + // Minimum execution time: 5_405 nanoseconds. + Weight::from_ref_time(5_670_000) .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Staking ForceEra (r:0 w:1) fn force_new_era() -> Weight { - // Minimum execution time: 5_093 nanoseconds. - Weight::from_ref_time(5_378_000) + // Minimum execution time: 5_459 nanoseconds. + Weight::from_ref_time(5_616_000) .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Staking ForceEra (r:0 w:1) fn force_new_era_always() -> Weight { - // Minimum execution time: 5_144 nanoseconds. - Weight::from_ref_time(5_454_000) + // Minimum execution time: 5_476 nanoseconds. + Weight::from_ref_time(5_692_000) .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Staking Invulnerables (r:0 w:1) /// The range of component `v` is `[0, 1000]`. fn set_invulnerables(v: u32, ) -> Weight { - // Minimum execution time: 5_190 nanoseconds. - Weight::from_ref_time(5_960_962) - // Standard Error: 41 - .saturating_add(Weight::from_ref_time(10_329).saturating_mul(v.into())) + // Minimum execution time: 5_544 nanoseconds. + Weight::from_ref_time(6_513_190) + // Standard Error: 76 + .saturating_add(Weight::from_ref_time(9_975).saturating_mul(v.into())) .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Staking Bonded (r:1 w:1) @@ -693,10 +703,10 @@ impl WeightInfo for () { // Storage: Staking SpanSlash (r:0 w:2) /// The range of component `s` is `[0, 100]`. fn force_unstake(s: u32, ) -> Weight { - // Minimum execution time: 80_516 nanoseconds. - Weight::from_ref_time(86_317_884) - // Standard Error: 2_212 - .saturating_add(Weight::from_ref_time(1_103_962).saturating_mul(s.into())) + // Minimum execution time: 82_414 nanoseconds. + Weight::from_ref_time(88_511_246) + // Standard Error: 2_622 + .saturating_add(Weight::from_ref_time(1_131_814).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(11)) .saturating_add(RocksDbWeight::get().writes(12)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -704,10 +714,10 @@ impl WeightInfo for () { // Storage: Staking UnappliedSlashes (r:1 w:1) /// The range of component `s` is `[1, 1000]`. fn cancel_deferred_slash(s: u32, ) -> Weight { - // Minimum execution time: 91_795 nanoseconds. - Weight::from_ref_time(904_524_900) - // Standard Error: 59_193 - .saturating_add(Weight::from_ref_time(4_944_680).saturating_mul(s.into())) + // Minimum execution time: 94_197 nanoseconds. + Weight::from_ref_time(903_418_326) + // Standard Error: 59_354 + .saturating_add(Weight::from_ref_time(4_948_354).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().writes(1)) } @@ -722,10 +732,10 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) /// The range of component `n` is `[0, 256]`. fn payout_stakers_dead_controller(n: u32, ) -> Weight { - // Minimum execution time: 127_774 nanoseconds. - Weight::from_ref_time(178_857_156) - // Standard Error: 15_229 - .saturating_add(Weight::from_ref_time(22_112_174).saturating_mul(n.into())) + // Minimum execution time: 133_065 nanoseconds. + Weight::from_ref_time(197_555_906) + // Standard Error: 19_561 + .saturating_add(Weight::from_ref_time(22_683_426).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(9)) .saturating_add(RocksDbWeight::get().reads((3_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(2)) @@ -743,10 +753,10 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) /// The range of component `n` is `[0, 256]`. fn payout_stakers_alive_staked(n: u32, ) -> Weight { - // Minimum execution time: 161_910 nanoseconds. - Weight::from_ref_time(217_635_072) - // Standard Error: 30_726 - .saturating_add(Weight::from_ref_time(31_244_329).saturating_mul(n.into())) + // Minimum execution time: 164_719 nanoseconds. + Weight::from_ref_time(226_304_276) + // Standard Error: 31_675 + .saturating_add(Weight::from_ref_time(32_622_427).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(10)) .saturating_add(RocksDbWeight::get().reads((5_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(3)) @@ -760,10 +770,10 @@ impl WeightInfo for () { // Storage: VoterList ListBags (r:2 w:2) /// The range of component `l` is `[1, 32]`. fn rebond(l: u32, ) -> Weight { - // Minimum execution time: 92_986 nanoseconds. - Weight::from_ref_time(94_880_481) - // Standard Error: 2_007 - .saturating_add(Weight::from_ref_time(31_421).saturating_mul(l.into())) + // Minimum execution time: 95_631 nanoseconds. + Weight::from_ref_time(96_861_556) + // Standard Error: 2_114 + .saturating_add(Weight::from_ref_time(37_543).saturating_mul(l.into())) .saturating_add(RocksDbWeight::get().reads(9)) .saturating_add(RocksDbWeight::get().writes(8)) } @@ -782,10 +792,10 @@ impl WeightInfo for () { // Storage: Staking SpanSlash (r:0 w:1) /// The range of component `s` is `[1, 100]`. fn reap_stash(s: u32, ) -> Weight { - // Minimum execution time: 92_750 nanoseconds. - Weight::from_ref_time(95_115_568) - // Standard Error: 2_037 - .saturating_add(Weight::from_ref_time(1_086_488).saturating_mul(s.into())) + // Minimum execution time: 95_251 nanoseconds. + Weight::from_ref_time(97_818_954) + // Standard Error: 2_356 + .saturating_add(Weight::from_ref_time(1_104_695).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(12)) .saturating_add(RocksDbWeight::get().writes(12)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -806,19 +816,20 @@ impl WeightInfo for () { // Storage: Staking ErasStakers (r:0 w:1) // Storage: Staking ErasTotalStake (r:0 w:1) // Storage: Staking ErasStartSessionIndex (r:0 w:1) + // Storage: Staking MinimumActiveStake (r:0 w:1) /// The range of component `v` is `[1, 10]`. /// The range of component `n` is `[0, 100]`. fn new_era(v: u32, n: u32, ) -> Weight { - // Minimum execution time: 506_543 nanoseconds. - Weight::from_ref_time(507_261_000) - // Standard Error: 1_766_631 - .saturating_add(Weight::from_ref_time(59_139_153).saturating_mul(v.into())) - // Standard Error: 176_035 - .saturating_add(Weight::from_ref_time(13_512_781).saturating_mul(n.into())) + // Minimum execution time: 512_923 nanoseconds. + Weight::from_ref_time(514_740_000) + // Standard Error: 1_790_238 + .saturating_add(Weight::from_ref_time(59_320_539).saturating_mul(v.into())) + // Standard Error: 178_387 + .saturating_add(Weight::from_ref_time(13_902_705).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(206)) .saturating_add(RocksDbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(RocksDbWeight::get().reads((4_u64).saturating_mul(n.into()))) - .saturating_add(RocksDbWeight::get().writes(3)) + .saturating_add(RocksDbWeight::get().writes(4)) .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(v.into()))) } // Storage: VoterList CounterForListNodes (r:1 w:0) @@ -828,27 +839,29 @@ impl WeightInfo for () { // Storage: Staking Validators (r:500 w:0) // Storage: Staking Bonded (r:1500 w:0) // Storage: Staking Ledger (r:1500 w:0) + // Storage: Staking MinimumActiveStake (r:0 w:1) /// The range of component `v` is `[500, 1000]`. /// The range of component `n` is `[500, 1000]`. fn get_npos_voters(v: u32, n: u32, ) -> Weight { - // Minimum execution time: 24_155_382 nanoseconds. - Weight::from_ref_time(24_252_568_000) - // Standard Error: 319_250 - .saturating_add(Weight::from_ref_time(3_596_056).saturating_mul(v.into())) - // Standard Error: 319_250 - .saturating_add(Weight::from_ref_time(2_852_023).saturating_mul(n.into())) + // Minimum execution time: 24_913_316 nanoseconds. + Weight::from_ref_time(25_053_596_000) + // Standard Error: 324_610 + .saturating_add(Weight::from_ref_time(3_454_859).saturating_mul(v.into())) + // Standard Error: 324_610 + .saturating_add(Weight::from_ref_time(3_020_267).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(201)) .saturating_add(RocksDbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(RocksDbWeight::get().reads((4_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Staking CounterForValidators (r:1 w:0) // Storage: Staking Validators (r:501 w:0) /// The range of component `v` is `[500, 1000]`. fn get_npos_targets(v: u32, ) -> Weight { - // Minimum execution time: 4_741_111 nanoseconds. - Weight::from_ref_time(113_360_179) - // Standard Error: 25_375 - .saturating_add(Weight::from_ref_time(9_494_142).saturating_mul(v.into())) + // Minimum execution time: 4_916_401 nanoseconds. + Weight::from_ref_time(81_160_966) + // Standard Error: 23_829 + .saturating_add(Weight::from_ref_time(9_883_413).saturating_mul(v.into())) .saturating_add(RocksDbWeight::get().reads(2)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(v.into()))) } @@ -859,8 +872,8 @@ impl WeightInfo for () { // Storage: Staking MaxNominatorsCount (r:0 w:1) // Storage: Staking MinNominatorBond (r:0 w:1) fn set_staking_configs_all_set() -> Weight { - // Minimum execution time: 11_074 nanoseconds. - Weight::from_ref_time(11_312_000) + // Minimum execution time: 10_937 nanoseconds. + Weight::from_ref_time(11_324_000) .saturating_add(RocksDbWeight::get().writes(6)) } // Storage: Staking MinCommission (r:0 w:1) @@ -870,8 +883,8 @@ impl WeightInfo for () { // Storage: Staking MaxNominatorsCount (r:0 w:1) // Storage: Staking MinNominatorBond (r:0 w:1) fn set_staking_configs_all_remove() -> Weight { - // Minimum execution time: 9_795 nanoseconds. - Weight::from_ref_time(10_116_000) + // Minimum execution time: 9_424 nanoseconds. + Weight::from_ref_time(10_021_000) .saturating_add(RocksDbWeight::get().writes(6)) } // Storage: Staking Ledger (r:1 w:0) @@ -885,17 +898,23 @@ impl WeightInfo for () { // Storage: VoterList ListBags (r:1 w:1) // Storage: VoterList CounterForListNodes (r:1 w:1) fn chill_other() -> Weight { - // Minimum execution time: 82_914 nanoseconds. - Weight::from_ref_time(83_848_000) + // Minimum execution time: 84_495 nanoseconds. + Weight::from_ref_time(85_559_000) .saturating_add(RocksDbWeight::get().reads(11)) .saturating_add(RocksDbWeight::get().writes(6)) } // Storage: Staking MinCommission (r:1 w:0) // Storage: Staking Validators (r:1 w:1) fn force_apply_min_commission() -> Weight { - // Minimum execution time: 20_317 nanoseconds. - Weight::from_ref_time(20_639_000) + // Minimum execution time: 20_385 nanoseconds. + Weight::from_ref_time(20_824_000) .saturating_add(RocksDbWeight::get().reads(2)) .saturating_add(RocksDbWeight::get().writes(1)) } + // Storage: Staking MinCommission (r:0 w:1) + fn set_min_commission() -> Weight { + // Minimum execution time: 6_995 nanoseconds. + Weight::from_ref_time(7_213_000) + .saturating_add(RocksDbWeight::get().writes(1)) + } } From 27e9a1c0fdb200c3736f513f3292b8b954e3d021 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Mon, 26 Dec 2022 17:37:09 +0100 Subject: [PATCH 079/127] Make CLI state pruning optional again (#13017) * Make CLI state pruning optional again The state pruning setting is stored in the database when it is created. In later runs it is fine to drop the `--state-pruning` CLI argument as the setting is stored in the database. The state db will only return an error if the stored state pruning doesn't match the state pruning given via CLI. Recently we improved the state pruning CLI handling and accidentally made the state pruning value always present (as we set some default value for the clap). If we could find out if a user has passed a value or the default value was taken, we could keep the default value in the CLI interface, but clap isn't supporting this right now. So, we need to go back and make `state_pruning` an optional with the default written into the docs. It also adds a test to ensure that we don't break this behavior again. * More docs --- bin/node/cli/tests/common.rs | 6 ++- .../cli/tests/remember_state_pruning_works.rs | 38 +++++++++++++++++ client/cli/src/params/pruning_params.rs | 41 +++++++++++++++---- 3 files changed, 75 insertions(+), 10 deletions(-) create mode 100644 bin/node/cli/tests/remember_state_pruning_works.rs diff --git a/bin/node/cli/tests/common.rs b/bin/node/cli/tests/common.rs index 358c09779d..c8f9e2b3d3 100644 --- a/bin/node/cli/tests/common.rs +++ b/bin/node/cli/tests/common.rs @@ -161,6 +161,7 @@ pub fn find_ws_url_from_output(read: impl Read + Send) -> (String, String) { let line = line.expect("failed to obtain next line from stdout for WS address discovery"); data.push_str(&line); + data.push_str("\n"); // does the line contain our port (we expect this specific output from substrate). let sock_addr = match line.split_once("Running JSON-RPC WS server: addr=") { @@ -170,7 +171,10 @@ pub fn find_ws_url_from_output(read: impl Read + Send) -> (String, String) { Some(format!("ws://{}", sock_addr)) }) - .expect("We should get a WebSocket address"); + .unwrap_or_else(|| { + eprintln!("Observed node output:\n{}", data); + panic!("We should get a WebSocket address") + }); (ws_url, data) } diff --git a/bin/node/cli/tests/remember_state_pruning_works.rs b/bin/node/cli/tests/remember_state_pruning_works.rs new file mode 100644 index 0000000000..5b8e34cc7a --- /dev/null +++ b/bin/node/cli/tests/remember_state_pruning_works.rs @@ -0,0 +1,38 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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 . + +use tempfile::tempdir; + +pub mod common; + +#[tokio::test] +#[cfg(unix)] +async fn remember_state_pruning_works() { + let base_path = tempdir().expect("could not create a temp dir"); + + // First run with `--state-pruning=archive`. + common::run_node_for_a_while( + base_path.path(), + &["--dev", "--state-pruning=archive", "--no-hardware-benchmarks"], + ) + .await; + + // Then run again without specifying the state pruning. + // This should load state pruning settings from the db. + common::run_node_for_a_while(base_path.path(), &["--dev", "--no-hardware-benchmarks"]).await; +} diff --git a/client/cli/src/params/pruning_params.rs b/client/cli/src/params/pruning_params.rs index 7e50f53d71..fd01ba67ba 100644 --- a/client/cli/src/params/pruning_params.rs +++ b/client/cli/src/params/pruning_params.rs @@ -28,21 +28,44 @@ pub struct PruningParams { /// This mode specifies when the block's state (ie, storage) /// should be pruned (ie, removed) from the database. /// + /// This setting can only be set on the first creation of the database. Every subsequent run + /// will load the pruning mode from the database and will error if the stored mode doesn't + /// match this CLI value. It is fine to drop this CLI flag for subsequent runs. + /// /// Possible values: - /// 'archive' Keep the state of all blocks. - /// 'archive-canonical' Keep only the state of finalized blocks. - /// number Keep the state of the last number of finalized blocks. - #[arg(alias = "pruning", long, value_name = "PRUNING_MODE", default_value = "256")] - pub state_pruning: DatabasePruningMode, + /// + /// - archive: + /// + /// Keep the state of all blocks. + /// + /// - 'archive-canonical' + /// + /// Keep only the state of finalized blocks. + /// + /// - number + /// + /// Keep the state of the last number of finalized blocks. + /// + /// [default: 256] + #[arg(alias = "pruning", long, value_name = "PRUNING_MODE")] + pub state_pruning: Option, /// Specify the blocks pruning mode. /// /// This mode specifies when the block's body (including justifications) /// should be pruned (ie, removed) from the database. /// /// Possible values: - /// 'archive' Keep all blocks. - /// 'archive-canonical' Keep only finalized blocks. - /// number Keep the last `number` of finalized blocks. + /// - 'archive' + /// + /// Keep all blocks. + /// + /// - 'archive-canonical' + /// + /// Keep only finalized blocks. + /// + /// - number + /// + /// Keep the last `number` of finalized blocks. #[arg( alias = "keep-blocks", long, @@ -55,7 +78,7 @@ pub struct PruningParams { impl PruningParams { /// Get the pruning value from the parameters pub fn state_pruning(&self) -> error::Result> { - Ok(Some(self.state_pruning.into())) + Ok(self.state_pruning.map(|v| v.into())) } /// Get the block pruning value from the parameters From 6fe8cd4ec19f7013b0ba2e790dce880ad07020b0 Mon Sep 17 00:00:00 2001 From: Dmitry Markin Date: Tue, 27 Dec 2022 13:05:12 +0300 Subject: [PATCH 080/127] Runtime diagnostics for leaked messages in unbounded channels (part 2) (#13020) * Fix code review issues * Clarify doc * Get rid of backtrace mutex * kick CI --- Cargo.lock | 64 +++++++++++++++++------- client/network/Cargo.toml | 1 + client/network/src/service/out_events.rs | 24 ++++----- client/utils/Cargo.toml | 1 + client/utils/src/mpsc.rs | 27 +++++----- 5 files changed, 69 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 19abb44e5f..c93a0b7fdd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,7 +18,16 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" dependencies = [ - "gimli", + "gimli 0.26.1", +] + +[[package]] +name = "addr2line" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" +dependencies = [ + "gimli 0.27.0", ] [[package]] @@ -316,16 +325,16 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.64" +version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e121dee8023ce33ab248d9ce1493df03c3b38a659b240096fcbd7048ff9c31f" +checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" dependencies = [ - "addr2line", + "addr2line 0.19.0", "cc", "cfg-if", "libc", - "miniz_oxide", - "object 0.27.1", + "miniz_oxide 0.6.2", + "object 0.30.0", "rustc-demangle", ] @@ -1017,7 +1026,7 @@ dependencies = [ "cranelift-codegen-shared", "cranelift-entity", "cranelift-isle", - "gimli", + "gimli 0.26.1", "log", "regalloc2", "smallvec", @@ -1820,7 +1829,7 @@ dependencies = [ "crc32fast", "libc", "libz-sys", - "miniz_oxide", + "miniz_oxide 0.4.4", ] [[package]] @@ -2445,6 +2454,12 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "gimli" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec7af912d60cdbd3677c1af9352ebae6fb8394d165568a2234df0fa00f87793" + [[package]] name = "git2" version = "0.14.2" @@ -3877,6 +3892,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "miniz_oxide" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + [[package]] name = "mio" version = "0.8.4" @@ -4611,22 +4635,22 @@ dependencies = [ [[package]] name = "object" -version = "0.27.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" +checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" dependencies = [ + "crc32fast", + "hashbrown 0.12.3", + "indexmap", "memchr", ] [[package]] name = "object" -version = "0.29.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +checksum = "239da7f290cfa979f43f85a8efeee9a8a76d0827c356d37f9d3d7254d6b537fb" dependencies = [ - "crc32fast", - "hashbrown 0.12.3", - "indexmap", "memchr", ] @@ -7756,6 +7780,7 @@ dependencies = [ "assert_matches", "async-trait", "asynchronous-codec", + "backtrace", "bytes", "either", "fnv", @@ -8403,6 +8428,7 @@ dependencies = [ name = "sc-utils" version = "4.0.0-dev" dependencies = [ + "backtrace", "futures", "futures-timer", "lazy_static", @@ -11142,7 +11168,7 @@ dependencies = [ "cranelift-frontend", "cranelift-native", "cranelift-wasm", - "gimli", + "gimli 0.26.1", "log", "object 0.29.0", "target-lexicon", @@ -11159,7 +11185,7 @@ checksum = "5c587c62e91c5499df62012b87b88890d0eb470b2ffecc5964e9da967b70c77c" dependencies = [ "anyhow", "cranelift-entity", - "gimli", + "gimli 0.26.1", "indexmap", "log", "object 0.29.0", @@ -11176,12 +11202,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "047839b5dabeae5424a078c19b8cc897e5943a7fadc69e3d888b9c9a897666b3" dependencies = [ - "addr2line", + "addr2line 0.17.0", "anyhow", "bincode", "cfg-if", "cpp_demangle", - "gimli", + "gimli 0.26.1", "log", "object 0.29.0", "rustc-demangle", diff --git a/client/network/Cargo.toml b/client/network/Cargo.toml index be9a74d2c1..35d4253f6b 100644 --- a/client/network/Cargo.toml +++ b/client/network/Cargo.toml @@ -17,6 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] array-bytes = "4.1" async-trait = "0.1" asynchronous-codec = "0.6" +backtrace = "0.3.67" bytes = "1" codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } either = "1.5.3" diff --git a/client/network/src/service/out_events.rs b/client/network/src/service/out_events.rs index 8febdd4726..5e0c8ac6a1 100644 --- a/client/network/src/service/out_events.rs +++ b/client/network/src/service/out_events.rs @@ -31,13 +31,13 @@ //! - Send events by calling [`OutChannels::send`]. Events are cloned for each sender in the //! collection. +use backtrace::Backtrace; use futures::{channel::mpsc, prelude::*, ready, stream::FusedStream}; use log::error; use parking_lot::Mutex; use prometheus_endpoint::{register, CounterVec, GaugeVec, Opts, PrometheusError, Registry, U64}; use sc_network_common::protocol::event::Event; use std::{ - backtrace::{Backtrace, BacktraceStatus}, cell::RefCell, fmt, pin::Pin, @@ -62,7 +62,7 @@ pub fn channel(name: &'static str, queue_size_warning: i64) -> (Sender, Receiver queue_size: queue_size.clone(), queue_size_warning, warning_fired: false, - creation_backtrace: Backtrace::capture(), + creation_backtrace: Backtrace::new_unresolved(), metrics: metrics.clone(), }; let rx = Receiver { inner: rx, name, queue_size, metrics }; @@ -91,7 +91,8 @@ pub struct Sender { warning_fired: bool, /// Backtrace of a place where the channel was created. creation_backtrace: Backtrace, - /// Clone of [`Receiver::metrics`]. + /// Clone of [`Receiver::metrics`]. Will be initialized when [`Sender`] is added to + /// [`OutChannels`] with `OutChannels::push()`. metrics: Arc>>>>, } @@ -193,17 +194,12 @@ impl OutChannels { let queue_size = sender.queue_size.fetch_add(1, Ordering::Relaxed); if queue_size == sender.queue_size_warning && !sender.warning_fired { sender.warning_fired = true; - match sender.creation_backtrace.status() { - BacktraceStatus::Captured => error!( - "The number of unprocessed events in channel `{}` reached {}.\n\ - The channel was created at:\n{}", - sender.name, sender.queue_size_warning, sender.creation_backtrace, - ), - _ => error!( - "The number of unprocessed events in channel `{}` reached {}.", - sender.name, sender.queue_size_warning, - ), - } + sender.creation_backtrace.resolve(); + error!( + "The number of unprocessed events in channel `{}` reached {}.\n\ + The channel was created at:\n{:?}", + sender.name, sender.queue_size_warning, sender.creation_backtrace, + ); } sender.inner.unbounded_send(event.clone()).is_ok() }); diff --git a/client/utils/Cargo.toml b/client/utils/Cargo.toml index 082ac3b55e..e805884535 100644 --- a/client/utils/Cargo.toml +++ b/client/utils/Cargo.toml @@ -10,6 +10,7 @@ description = "I/O for Substrate runtimes" readme = "README.md" [dependencies] +backtrace = "0.3.67" futures = "0.3.21" futures-timer = "3.0.2" lazy_static = "1.4.0" diff --git a/client/utils/src/mpsc.rs b/client/utils/src/mpsc.rs index 7db5e49f5b..d74703c4ab 100644 --- a/client/utils/src/mpsc.rs +++ b/client/utils/src/mpsc.rs @@ -37,6 +37,7 @@ mod inner { mod inner { // tracing implementation use crate::metrics::UNBOUNDED_CHANNELS_COUNTER; + use backtrace::Backtrace; use futures::{ channel::mpsc::{ self, SendError, TryRecvError, TrySendError, UnboundedReceiver, UnboundedSender, @@ -47,7 +48,6 @@ mod inner { }; use log::error; use std::{ - backtrace::{Backtrace, BacktraceStatus}, pin::Pin, sync::{ atomic::{AtomicBool, AtomicI64, Ordering}, @@ -108,7 +108,7 @@ mod inner { queue_size: queue_size.clone(), queue_size_warning, warning_fired: Arc::new(AtomicBool::new(false)), - creation_backtrace: Arc::new(Backtrace::capture()), + creation_backtrace: Arc::new(Backtrace::new_unresolved()), }; let receiver = TracingUnboundedReceiver { inner: r, name, queue_size }; (sender, receiver) @@ -149,23 +149,20 @@ mod inner { let queue_size = self.queue_size.fetch_add(1, Ordering::Relaxed); if queue_size == self.queue_size_warning && - !self.warning_fired.load(Ordering::Relaxed) + self.warning_fired + .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) + .is_ok() { // `warning_fired` and `queue_size` are not synchronized, so it's possible // that the warning is fired few times before the `warning_fired` is seen // by all threads. This seems better than introducing a mutex guarding them. - self.warning_fired.store(true, Ordering::Relaxed); - match self.creation_backtrace.status() { - BacktraceStatus::Captured => error!( - "The number of unprocessed messages in channel `{}` reached {}.\n\ - The channel was created at:\n{}", - self.name, self.queue_size_warning, self.creation_backtrace, - ), - _ => error!( - "The number of unprocessed messages in channel `{}` reached {}.", - self.name, self.queue_size_warning, - ), - } + let mut backtrace = (*self.creation_backtrace).clone(); + backtrace.resolve(); + error!( + "The number of unprocessed messages in channel `{}` reached {}.\n\ + The channel was created at:\n{:?}", + self.name, self.queue_size_warning, backtrace, + ); } s From 8d20ed51e4e4b420ba7ab2f498f6e49a5b93b915 Mon Sep 17 00:00:00 2001 From: Sasha Gryaznov Date: Tue, 27 Dec 2022 15:24:49 +0200 Subject: [PATCH 081/127] [contracts] Make debug buffer work like a FIFO pipe (#12953) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * make debug buffer work like a FIFO pipe * remove unused Error type * Remove panics * Update frame/contracts/src/exec.rs Co-authored-by: Sasha Gryaznov Co-authored-by: Alexander Theißen --- frame/contracts/src/exec.rs | 84 ++++++++++++++++------------- frame/contracts/src/lib.rs | 3 -- frame/contracts/src/wasm/mod.rs | 4 +- frame/contracts/src/wasm/runtime.rs | 4 +- 4 files changed, 52 insertions(+), 43 deletions(-) diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index f4dc3a0ec0..df1dd24856 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -279,7 +279,7 @@ pub trait Ext: sealing::Sealed { /// when the code is executing on-chain. /// /// Returns `true` if debug message recording is enabled. Otherwise `false` is returned. - fn append_debug_buffer(&mut self, msg: &str) -> Result; + fn append_debug_buffer(&mut self, msg: &str) -> bool; /// Call some dispatchable and return the result. fn call_runtime(&self, call: ::RuntimeCall) -> DispatchResultWithPostInfo; @@ -1334,16 +1334,34 @@ where &mut self.top_frame_mut().nested_gas } - fn append_debug_buffer(&mut self, msg: &str) -> Result { + fn append_debug_buffer(&mut self, msg: &str) -> bool { if let Some(buffer) = &mut self.debug_message { - if !msg.is_empty() { - buffer - .try_extend(&mut msg.bytes()) - .map_err(|_| Error::::DebugBufferExhausted)?; - } - Ok(true) + let mut msg = msg.bytes(); + let num_drain = { + let capacity = DebugBufferVec::::bound().checked_sub(buffer.len()).expect( + " + `buffer` is of type `DebugBufferVec`, + `DebugBufferVec` is a `BoundedVec`, + `BoundedVec::len()` <= `BoundedVec::bound()`; + qed + ", + ); + msg.len().saturating_sub(capacity).min(buffer.len()) + }; + buffer.drain(0..num_drain); + buffer + .try_extend(&mut msg) + .map_err(|_| { + log::debug!( + target: "runtime::contracts", + "Debug message to big (size={}) for debug buffer (bound={})", + msg.len(), DebugBufferVec::::bound(), + ); + }) + .ok(); + true } else { - Ok(false) + false } } @@ -2511,12 +2529,8 @@ mod tests { #[test] fn printing_works() { let code_hash = MockLoader::insert(Call, |ctx, _| { - ctx.ext - .append_debug_buffer("This is a test") - .expect("Maximum allowed debug buffer size exhausted!"); - ctx.ext - .append_debug_buffer("More text") - .expect("Maximum allowed debug buffer size exhausted!"); + ctx.ext.append_debug_buffer("This is a test"); + ctx.ext.append_debug_buffer("More text"); exec_success() }); @@ -2549,12 +2563,8 @@ mod tests { #[test] fn printing_works_on_fail() { let code_hash = MockLoader::insert(Call, |ctx, _| { - ctx.ext - .append_debug_buffer("This is a test") - .expect("Maximum allowed debug buffer size exhausted!"); - ctx.ext - .append_debug_buffer("More text") - .expect("Maximum allowed debug buffer size exhausted!"); + ctx.ext.append_debug_buffer("This is a test"); + ctx.ext.append_debug_buffer("More text"); exec_trapped() }); @@ -2587,7 +2597,7 @@ mod tests { #[test] fn debug_buffer_is_limited() { let code_hash = MockLoader::insert(Call, move |ctx, _| { - ctx.ext.append_debug_buffer("overflowing bytes")?; + ctx.ext.append_debug_buffer("overflowing bytes"); exec_success() }); @@ -2602,20 +2612,22 @@ mod tests { set_balance(&ALICE, min_balance * 10); place_contract(&BOB, code_hash); let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); - assert_err!( - MockStack::run_call( - ALICE, - BOB, - &mut gas_meter, - &mut storage_meter, - &schedule, - 0, - vec![], - Some(&mut debug_buffer), - Determinism::Deterministic, - ) - .map_err(|e| e.error), - Error::::DebugBufferExhausted + MockStack::run_call( + ALICE, + BOB, + &mut gas_meter, + &mut storage_meter, + &schedule, + 0, + vec![], + Some(&mut debug_buffer), + Determinism::Deterministic, + ) + .unwrap(); + assert_eq!( + &String::from_utf8(debug_buffer[DebugBufferVec::::bound() - 17..].to_vec()) + .unwrap(), + "overflowing bytes" ); }); } diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index 39626e43f4..778ceec961 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -857,9 +857,6 @@ pub mod pallet { CodeRejected, /// An indetermistic code was used in a context where this is not permitted. Indeterministic, - /// The debug buffer size used during contract execution exceeded the limit determined by - /// the `MaxDebugBufferLen` pallet config parameter. - DebugBufferExhausted, } /// A mapping from an original code hash to the original code, untouched by instrumentation. diff --git a/frame/contracts/src/wasm/mod.rs b/frame/contracts/src/wasm/mod.rs index 903ca0abb5..553bae59e7 100644 --- a/frame/contracts/src/wasm/mod.rs +++ b/frame/contracts/src/wasm/mod.rs @@ -588,9 +588,9 @@ mod tests { fn gas_meter(&mut self) -> &mut GasMeter { &mut self.gas_meter } - fn append_debug_buffer(&mut self, msg: &str) -> Result { + fn append_debug_buffer(&mut self, msg: &str) -> bool { self.debug_buffer.extend(msg.as_bytes()); - Ok(true) + true } fn call_runtime( &self, diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index aaacb9e5f8..7d67adc3de 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -2380,11 +2380,11 @@ pub mod env { str_len: u32, ) -> Result { ctx.charge_gas(RuntimeCosts::DebugMessage)?; - if ctx.ext.append_debug_buffer("")? { + if ctx.ext.append_debug_buffer("") { let data = ctx.read_sandbox_memory(memory, str_ptr, str_len)?; let msg = core::str::from_utf8(&data).map_err(|_| >::DebugMessageInvalidUTF8)?; - ctx.ext.append_debug_buffer(msg)?; + ctx.ext.append_debug_buffer(msg); return Ok(ReturnCode::Success) } Ok(ReturnCode::LoggingDisabled) From d2ef4b061f3802d7aa4254d6ce1f15b261a0fa61 Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Tue, 27 Dec 2022 14:48:17 +0100 Subject: [PATCH 082/127] Improve inactive fund tracking (#13009) * Improve inactive fund tracking * Resetting migration * Fix * Update frame/balances/src/migration.rs --- bin/node/executor/tests/basic.rs | 11 ++++++++++- frame/balances/src/migration.rs | 26 ++++++++++++++++++++++++++ frame/treasury/src/lib.rs | 19 ++++++++++++------- 3 files changed, 48 insertions(+), 8 deletions(-) diff --git a/bin/node/executor/tests/basic.rs b/bin/node/executor/tests/basic.rs index c88703b929..02b2a8787b 100644 --- a/bin/node/executor/tests/basic.rs +++ b/bin/node/executor/tests/basic.rs @@ -30,7 +30,7 @@ use sp_runtime::{ use kitchensink_runtime::{ constants::{currency::*, time::SLOT_DURATION}, Balances, CheckedExtrinsic, Header, Runtime, RuntimeCall, RuntimeEvent, System, - TransactionPayment, UncheckedExtrinsic, + TransactionPayment, Treasury, UncheckedExtrinsic, }; use node_primitives::{Balance, Hash}; use node_testing::keyring::*; @@ -398,6 +398,7 @@ fn full_native_block_import_works() { }); fees = t.execute_with(|| transfer_fee(&xt())); + let pot = t.execute_with(|| Treasury::pot()); executor_call(&mut t, "Core_execute_block", &block2.0, true).0.unwrap(); @@ -408,6 +409,14 @@ fn full_native_block_import_works() { ); assert_eq!(Balances::total_balance(&bob()), 179 * DOLLARS - fees); let events = vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Treasury(pallet_treasury::Event::UpdatedInactive { + reactivated: 0, + deactivated: pot, + }), + topics: vec![], + }, EventRecord { phase: Phase::ApplyExtrinsic(0), event: RuntimeEvent::System(frame_system::Event::ExtrinsicSuccess { diff --git a/frame/balances/src/migration.rs b/frame/balances/src/migration.rs index 08e1d8c7a2..b660ec9fd3 100644 --- a/frame/balances/src/migration.rs +++ b/frame/balances/src/migration.rs @@ -69,3 +69,29 @@ impl, A: Get>, I: 'static> OnRuntimeUpgrade migrate_v0_to_v1::(&A::get()) } } + +pub struct ResetInactive(PhantomData<(T, I)>); +impl, I: 'static> OnRuntimeUpgrade for ResetInactive { + fn on_runtime_upgrade() -> Weight { + let onchain_version = Pallet::::on_chain_storage_version(); + + if onchain_version == 1 { + // Remove the old `StorageVersion` type. + frame_support::storage::unhashed::kill(&frame_support::storage::storage_prefix( + Pallet::::name().as_bytes(), + "StorageVersion".as_bytes(), + )); + + InactiveIssuance::::kill(); + + // Set storage version to `0`. + StorageVersion::new(0).put::>(); + + log::info!(target: "runtime::balances", "Storage to version 0"); + T::DbWeight::get().reads_writes(1, 2) + } else { + log::info!(target: "runtime::balances", "Migration did not execute. This probably should be removed"); + T::DbWeight::get().reads(1) + } + } +} diff --git a/frame/treasury/src/lib.rs b/frame/treasury/src/lib.rs index 0ffc53d8b7..1bde123819 100644 --- a/frame/treasury/src/lib.rs +++ b/frame/treasury/src/lib.rs @@ -225,7 +225,8 @@ pub mod pallet { /// The amount which has been reported as inactive to Currency. #[pallet::storage] - pub type Inactive, I: 'static = ()> = StorageValue<_, BalanceOf, ValueQuery>; + pub type Deactivated, I: 'static = ()> = + StorageValue<_, BalanceOf, ValueQuery>; /// Proposal indices that have been approved but not yet awarded. #[pallet::storage] @@ -292,6 +293,8 @@ pub mod pallet { amount: BalanceOf, beneficiary: T::AccountId, }, + /// The inactive funds of the pallet have been updated. + UpdatedInactive { reactivated: BalanceOf, deactivated: BalanceOf }, } /// Error for the treasury pallet. @@ -321,13 +324,15 @@ pub mod pallet { /// # fn on_initialize(n: T::BlockNumber) -> Weight { let pot = Self::pot(); - let deactivated = Inactive::::get(); + let deactivated = Deactivated::::get(); if pot != deactivated { - match (pot > deactivated, pot.max(deactivated) - pot.min(deactivated)) { - (true, delta) => T::Currency::deactivate(delta), - (false, delta) => T::Currency::reactivate(delta), - } - Inactive::::put(&pot); + T::Currency::reactivate(deactivated); + T::Currency::deactivate(pot); + Deactivated::::put(&pot); + Self::deposit_event(Event::::UpdatedInactive { + reactivated: deactivated, + deactivated: pot, + }); } // Check to see if we should spend some funds! From 891d6a5c870ab88521183facafc811a203bb6541 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Wed, 28 Dec 2022 09:16:52 +0100 Subject: [PATCH 083/127] Print "stalled" task on shutdown (#13022) * Print "stalled" task on shutdown When the node is shutting down, we give the Tokio runtime 60 seconds to shutdown. If after these 60 seconds there are still running tasks, we now print these tasks. This should help debugging nodes that have stalled tasks. This pr introduces a `TaskRegistry` that keeps track of all running tasks. Each task registers and unregisters itself in this `TaskRegistry`. * Fix rustdoc * Update client/service/src/lib.rs --- Cargo.lock | 1 + client/cli/Cargo.toml | 1 + client/cli/src/runner.rs | 117 ++++++++++++++++++++----- client/service/src/lib.rs | 2 +- client/service/src/task_manager/mod.rs | 98 ++++++++++++++++++++- 5 files changed, 193 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c93a0b7fdd..b2aca09fe2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7280,6 +7280,7 @@ dependencies = [ "sp-keystore", "sp-panic-handler", "sp-runtime", + "sp-tracing", "sp-version", "tempfile", "thiserror", diff --git a/client/cli/Cargo.toml b/client/cli/Cargo.toml index a5de78da48..ee4426b91d 100644 --- a/client/cli/Cargo.toml +++ b/client/cli/Cargo.toml @@ -50,6 +50,7 @@ sp-version = { version = "5.0.0", path = "../../primitives/version" } [dev-dependencies] tempfile = "3.1.0" futures-timer = "3.0.1" +sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } [features] default = ["rocksdb"] diff --git a/client/cli/src/runner.rs b/client/cli/src/runner.rs index d4191feddf..1a532b3bbc 100644 --- a/client/cli/src/runner.rs +++ b/client/cli/src/runner.rs @@ -152,10 +152,38 @@ impl Runner { // // This is important to be done before we instruct the tokio runtime to shutdown. Otherwise // the tokio runtime will wait the full 60 seconds for all tasks to stop. - drop(task_manager); + let task_registry = task_manager.into_task_registry(); // Give all futures 60 seconds to shutdown, before tokio "leaks" them. - self.tokio_runtime.shutdown_timeout(Duration::from_secs(60)); + let shutdown_timeout = Duration::from_secs(60); + self.tokio_runtime.shutdown_timeout(shutdown_timeout); + + let running_tasks = task_registry.running_tasks(); + + if !running_tasks.is_empty() { + log::error!("Detected running(potentially stalled) tasks on shutdown:"); + running_tasks.iter().for_each(|(task, count)| { + let instances_desc = + if *count > 1 { format!("with {} instances ", count) } else { "".to_string() }; + + if task.is_default_group() { + log::error!( + "Task \"{}\" was still running {}after waiting {} seconds to finish.", + task.name, + instances_desc, + shutdown_timeout.as_secs(), + ); + } else { + log::error!( + "Task \"{}\" (Group: {}) was still running {}after waiting {} seconds to finish.", + task.name, + task.group, + instances_desc, + shutdown_timeout.as_secs(), + ); + } + }); + } res.map_err(Into::into) } @@ -388,34 +416,75 @@ mod tests { assert!((count as u128) < (Duration::from_secs(30).as_millis() / 50)); } + fn run_test_in_another_process( + test_name: &str, + test_body: impl FnOnce(), + ) -> Option { + if std::env::var("RUN_FORKED_TEST").is_ok() { + test_body(); + None + } else { + let output = std::process::Command::new(std::env::current_exe().unwrap()) + .arg(test_name) + .env("RUN_FORKED_TEST", "1") + .output() + .unwrap(); + + assert!(output.status.success()); + Some(output) + } + } + /// This test ensures that `run_node_until_exit` aborts waiting for "stuck" tasks after 60 /// seconds, aka doesn't wait until they are finished (which may never happen). #[test] fn ensure_run_until_exit_is_not_blocking_indefinitely() { - let runner = create_runner(); + let output = run_test_in_another_process( + "ensure_run_until_exit_is_not_blocking_indefinitely", + || { + sp_tracing::try_init_simple(); + + let runner = create_runner(); + + runner + .run_node_until_exit(move |cfg| async move { + let task_manager = + TaskManager::new(cfg.tokio_handle.clone(), None).unwrap(); + let (sender, receiver) = futures::channel::oneshot::channel(); + + // We need to use `spawn_blocking` here so that we get a dedicated thread + // for our future. This future is more blocking code that will never end. + task_manager.spawn_handle().spawn_blocking("test", None, async move { + let _ = sender.send(()); + loop { + std::thread::sleep(Duration::from_secs(30)); + } + }); + + task_manager.spawn_essential_handle().spawn_blocking( + "test2", + None, + async { + // Let's stop this essential task directly when our other task + // started. It will signal that the task manager should end. + let _ = receiver.await; + }, + ); + + Ok::<_, sc_service::Error>(task_manager) + }) + .unwrap_err(); + }, + ); - runner - .run_node_until_exit(move |cfg| async move { - let task_manager = TaskManager::new(cfg.tokio_handle.clone(), None).unwrap(); - let (sender, receiver) = futures::channel::oneshot::channel(); + let Some(output) = output else { return } ; - // We need to use `spawn_blocking` here so that we get a dedicated thread for our - // future. This future is more blocking code that will never end. - task_manager.spawn_handle().spawn_blocking("test", None, async move { - let _ = sender.send(()); - loop { - std::thread::sleep(Duration::from_secs(30)); - } - }); - - task_manager.spawn_essential_handle().spawn_blocking("test2", None, async { - // Let's stop this essential task directly when our other task started. - // It will signal that the task manager should end. - let _ = receiver.await; - }); + let stderr = dbg!(String::from_utf8(output.stderr).unwrap()); - Ok::<_, sc_service::Error>(task_manager) - }) - .unwrap_err(); + assert!( + stderr.contains("Task \"test\" was still running after waiting 60 seconds to finish.") + ); + assert!(!stderr + .contains("Task \"test2\" was still running after waiting 60 seconds to finish.")); } } diff --git a/client/service/src/lib.rs b/client/service/src/lib.rs index 8b3a29ba40..1529b822ad 100644 --- a/client/service/src/lib.rs +++ b/client/service/src/lib.rs @@ -83,7 +83,7 @@ pub use sc_transaction_pool::Options as TransactionPoolOptions; pub use sc_transaction_pool_api::{error::IntoPoolError, InPoolTransaction, TransactionPool}; #[doc(hidden)] pub use std::{ops::Deref, result::Result, sync::Arc}; -pub use task_manager::{SpawnTaskHandle, TaskManager, DEFAULT_GROUP_NAME}; +pub use task_manager::{SpawnTaskHandle, Task, TaskManager, TaskRegistry, DEFAULT_GROUP_NAME}; const DEFAULT_PROTOCOL_ID: &str = "sup"; diff --git a/client/service/src/task_manager/mod.rs b/client/service/src/task_manager/mod.rs index 23265f9672..d792122576 100644 --- a/client/service/src/task_manager/mod.rs +++ b/client/service/src/task_manager/mod.rs @@ -24,12 +24,19 @@ use futures::{ future::{pending, select, try_join_all, BoxFuture, Either}, Future, FutureExt, StreamExt, }; +use parking_lot::Mutex; use prometheus_endpoint::{ exponential_buckets, register, CounterVec, HistogramOpts, HistogramVec, Opts, PrometheusError, Registry, U64, }; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; -use std::{panic, pin::Pin, result::Result}; +use std::{ + collections::{hash_map::Entry, HashMap}, + panic, + pin::Pin, + result::Result, + sync::Arc, +}; use tokio::runtime::Handle; use tracing_futures::Instrument; @@ -72,6 +79,7 @@ pub struct SpawnTaskHandle { on_exit: exit_future::Exit, tokio_handle: Handle, metrics: Option, + task_registry: TaskRegistry, } impl SpawnTaskHandle { @@ -113,6 +121,7 @@ impl SpawnTaskHandle { ) { let on_exit = self.on_exit.clone(); let metrics = self.metrics.clone(); + let registry = self.task_registry.clone(); let group = match group.into() { GroupName::Specific(var) => var, @@ -129,6 +138,10 @@ impl SpawnTaskHandle { } let future = async move { + // Register the task and keep the "token" alive until the task is ended. Then this + // "token" will unregister this task. + let _registry_token = registry.register_task(name, group); + if let Some(metrics) = metrics { // Add some wrappers around `task`. let task = { @@ -298,6 +311,8 @@ pub struct TaskManager { /// terminates and gracefully shutdown. Also ends the parent `future()` if a child's essential /// task fails. children: Vec, + /// The registry of all running tasks. + task_registry: TaskRegistry, } impl TaskManager { @@ -324,6 +339,7 @@ impl TaskManager { essential_failed_rx, keep_alive: Box::new(()), children: Vec::new(), + task_registry: Default::default(), }) } @@ -333,6 +349,7 @@ impl TaskManager { on_exit: self.on_exit.clone(), tokio_handle: self.tokio_handle.clone(), metrics: self.metrics.clone(), + task_registry: self.task_registry.clone(), } } @@ -385,6 +402,14 @@ impl TaskManager { pub fn add_child(&mut self, child: TaskManager) { self.children.push(child); } + + /// Consume `self` and return the [`TaskRegistry`]. + /// + /// This [`TaskRegistry`] can be used to check for still running tasks after this task manager + /// was dropped. + pub fn into_task_registry(self) -> TaskRegistry { + self.task_registry + } } #[derive(Clone)] @@ -434,3 +459,74 @@ impl Metrics { }) } } + +/// Ensures that a [`Task`] is unregistered when this object is dropped. +struct UnregisterOnDrop { + task: Task, + registry: TaskRegistry, +} + +impl Drop for UnregisterOnDrop { + fn drop(&mut self) { + let mut tasks = self.registry.tasks.lock(); + + if let Entry::Occupied(mut entry) = (*tasks).entry(self.task.clone()) { + *entry.get_mut() -= 1; + + if *entry.get() == 0 { + entry.remove(); + } + } + } +} + +/// Represents a running async task in the [`TaskManager`]. +/// +/// As a task is identified by a name and a group, it is totally valid that there exists multiple +/// tasks with the same name and group. +#[derive(Clone, Hash, Eq, PartialEq)] +pub struct Task { + /// The name of the task. + pub name: &'static str, + /// The group this task is associated to. + pub group: &'static str, +} + +impl Task { + /// Returns if the `group` is the [`DEFAULT_GROUP_NAME`]. + pub fn is_default_group(&self) -> bool { + self.group == DEFAULT_GROUP_NAME + } +} + +/// Keeps track of all running [`Task`]s in [`TaskManager`]. +#[derive(Clone, Default)] +pub struct TaskRegistry { + tasks: Arc>>, +} + +impl TaskRegistry { + /// Register a task with the given `name` and `group`. + /// + /// Returns [`UnregisterOnDrop`] that ensures that the task is unregistered when this value is + /// dropped. + fn register_task(&self, name: &'static str, group: &'static str) -> UnregisterOnDrop { + let task = Task { name, group }; + + { + let mut tasks = self.tasks.lock(); + + *(*tasks).entry(task.clone()).or_default() += 1; + } + + UnregisterOnDrop { task, registry: self.clone() } + } + + /// Returns the running tasks. + /// + /// As a task is only identified by its `name` and `group`, there can be duplicate tasks. The + /// number per task represents the concurrently running tasks with the same identifier. + pub fn running_tasks(&self) -> HashMap { + (*self.tasks.lock()).clone() + } +} From 043a60b0f25c7b2abfcb213fa0af6fef946198c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Enrique=20Mu=C3=B1oz=20Mart=C3=ADn?= Date: Wed, 28 Dec 2022 13:52:15 +0100 Subject: [PATCH 084/127] Add ensure-ops family methods (#12967) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add ensure-ops family methods * fix cargo doc * add EnsureOp and EnsureOpAssign meta traits * move ensure module and ArithmeticError to sp-arithmetic * fix doc examples * reexport ensure module content * ensure mod private * reexport to sp-runtime * fix doc example * remove into(). in doc examples, minor doc changes * remove return value from assign methods * Update primitives/arithmetic/src/traits.rs Co-authored-by: Bastian Köcher * Update primitives/arithmetic/src/traits.rs Co-authored-by: Bastian Köcher * Update primitives/arithmetic/src/traits.rs Co-authored-by: Bastian Köcher * Update primitives/arithmetic/src/traits.rs Co-authored-by: Bastian Köcher * Update primitives/arithmetic/src/traits.rs Co-authored-by: Bastian Köcher * Update primitives/arithmetic/src/traits.rs Co-authored-by: Bastian Köcher * Update primitives/arithmetic/src/traits.rs Co-authored-by: Bastian Köcher * Update primitives/arithmetic/src/traits.rs Co-authored-by: Bastian Köcher * Update primitives/arithmetic/src/traits.rs Co-authored-by: Bastian Köcher * Update primitives/arithmetic/src/traits.rs Co-authored-by: Bastian Köcher * Update primitives/arithmetic/src/traits.rs Co-authored-by: Bastian Köcher * Update primitives/arithmetic/src/traits.rs Co-authored-by: Bastian Köcher * Update primitives/arithmetic/src/traits.rs Co-authored-by: Bastian Köcher * Update primitives/arithmetic/src/traits.rs Co-authored-by: Bastian Köcher * Update primitives/arithmetic/src/traits.rs Co-authored-by: Bastian Köcher * Update primitives/arithmetic/src/traits.rs Co-authored-by: Bastian Köcher * Update primitives/arithmetic/src/traits.rs Co-authored-by: Bastian Köcher * Update primitives/arithmetic/src/traits.rs Co-authored-by: Bastian Köcher * Update primitives/arithmetic/src/traits.rs Co-authored-by: Bastian Köcher * Update primitives/arithmetic/src/traits.rs Co-authored-by: Bastian Köcher * Update primitives/arithmetic/src/traits.rs Co-authored-by: Bastian Köcher * Update primitives/arithmetic/src/traits.rs Co-authored-by: Bastian Köcher * Update primitives/arithmetic/src/traits.rs Co-authored-by: Bastian Köcher * Update primitives/arithmetic/src/traits.rs Co-authored-by: Bastian Köcher * Update primitives/arithmetic/src/traits.rs Co-authored-by: Bastian Köcher * Update primitives/arithmetic/src/traits.rs Co-authored-by: Bastian Köcher * Update primitives/arithmetic/src/traits.rs Co-authored-by: Bastian Köcher * Update primitives/arithmetic/src/traits.rs Co-authored-by: Bastian Köcher * Update primitives/arithmetic/src/traits.rs Co-authored-by: Bastian Köcher * cargo fmt * Apply suggestions from code review * ".git/.scripts/fmt.sh" 1 Co-authored-by: Bastian Köcher Co-authored-by: command-bot <> --- primitives/arithmetic/src/lib.rs | 28 ++ primitives/arithmetic/src/traits.rs | 529 ++++++++++++++++++++++++++++ primitives/runtime/src/lib.rs | 28 +- primitives/runtime/src/traits.rs | 6 +- 4 files changed, 564 insertions(+), 27 deletions(-) diff --git a/primitives/arithmetic/src/lib.rs b/primitives/arithmetic/src/lib.rs index d7b326164b..f1f8fc8b59 100644 --- a/primitives/arithmetic/src/lib.rs +++ b/primitives/arithmetic/src/lib.rs @@ -50,6 +50,34 @@ pub use rational::{Rational128, RationalInfinite}; use sp_std::{cmp::Ordering, fmt::Debug, prelude::*}; use traits::{BaseArithmetic, One, SaturatedConversion, Unsigned, Zero}; +use codec::{Decode, Encode}; +use scale_info::TypeInfo; + +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; + +/// Arithmetic errors. +#[derive(Eq, PartialEq, Clone, Copy, Encode, Decode, Debug, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub enum ArithmeticError { + /// Underflow. + Underflow, + /// Overflow. + Overflow, + /// Division by zero. + DivisionByZero, +} + +impl From for &'static str { + fn from(e: ArithmeticError) -> &'static str { + match e { + ArithmeticError::Underflow => "An underflow would occur", + ArithmeticError::Overflow => "An overflow would occur", + ArithmeticError::DivisionByZero => "Division by zero", + } + } +} + /// Trait for comparing two numbers with an threshold. /// /// Returns: diff --git a/primitives/arithmetic/src/traits.rs b/primitives/arithmetic/src/traits.rs index 466d5696c7..7fa64d2866 100644 --- a/primitives/arithmetic/src/traits.rs +++ b/primitives/arithmetic/src/traits.rs @@ -18,6 +18,11 @@ //! Primitive traits for the runtime arithmetic. use codec::HasCompact; +pub use ensure::{ + Ensure, EnsureAdd, EnsureAddAssign, EnsureDiv, EnsureDivAssign, EnsureFixedPointNumber, + EnsureFrom, EnsureInto, EnsureMul, EnsureMulAssign, EnsureOp, EnsureOpAssign, EnsureSub, + EnsureSubAssign, +}; pub use integer_sqrt::IntegerSquareRoot; pub use num_traits::{ checked_pow, Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, @@ -302,3 +307,527 @@ pub trait SaturatedConversion { } } impl SaturatedConversion for T {} + +/// Arithmetic operations with safe error handling. +/// +/// This module provide a readable way to do safe arithmetics, turning this: +/// +/// ``` +/// # use sp_arithmetic::{traits::EnsureSub, ArithmeticError}; +/// # fn foo() -> Result<(), ArithmeticError> { +/// # let mut my_value: i32 = 1; +/// # let other_value: i32 = 1; +/// my_value = my_value.checked_sub(other_value).ok_or(ArithmeticError::Overflow)?; +/// # Ok(()) +/// # } +/// ``` +/// +/// into this: +/// +/// ``` +/// # use sp_arithmetic::{traits::EnsureSubAssign, ArithmeticError}; +/// # fn foo() -> Result<(), ArithmeticError> { +/// # let mut my_value: i32 = 1; +/// # let other_value: i32 = 1; +/// my_value.ensure_sub_assign(other_value)?; +/// # Ok(()) +/// # } +/// ``` +/// +/// choosing the correct [`ArithmeticError`](crate::ArithmeticError) it should return in case of +/// fail. +/// +/// The *EnsureOps* family functions follows the same behavior as *CheckedOps* but +/// returning an [`ArithmeticError`](crate::ArithmeticError) instead of `None`. +mod ensure { + use super::{CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, Zero}; + use crate::{ArithmeticError, FixedPointNumber, FixedPointOperand}; + + /// Performs addition that returns [`ArithmeticError`] instead of wrapping around on overflow. + pub trait EnsureAdd: CheckedAdd + PartialOrd + Zero + Copy { + /// Adds two numbers, checking for overflow. + /// + /// If it fails, [`ArithmeticError`] is returned. + /// + /// Similar to [`CheckedAdd::checked_add()`] but returning an [`ArithmeticError`] error. + /// + /// ``` + /// use sp_arithmetic::{traits::EnsureAdd, ArithmeticError}; + /// + /// fn overflow() -> Result<(), ArithmeticError> { + /// u32::MAX.ensure_add(1)?; + /// Ok(()) + /// } + /// + /// fn underflow() -> Result<(), ArithmeticError> { + /// i32::MIN.ensure_add(-1)?; + /// Ok(()) + /// } + /// + /// assert_eq!(overflow(), Err(ArithmeticError::Overflow)); + /// assert_eq!(underflow(), Err(ArithmeticError::Underflow)); + /// ``` + fn ensure_add(self, v: Self) -> Result { + self.checked_add(&v).ok_or_else(|| error::equivalent(v)) + } + } + + /// Performs subtraction that returns [`ArithmeticError`] instead of wrapping around on + /// underflow. + pub trait EnsureSub: CheckedSub + PartialOrd + Zero + Copy { + /// Subtracts two numbers, checking for overflow. + /// + /// If it fails, [`ArithmeticError`] is returned. + /// + /// Similar to [`CheckedSub::checked_sub()`] but returning an [`ArithmeticError`] error. + /// + /// ``` + /// use sp_arithmetic::{traits::EnsureSub, ArithmeticError}; + /// + /// fn underflow() -> Result<(), ArithmeticError> { + /// 0u32.ensure_sub(1)?; + /// Ok(()) + /// } + /// + /// fn overflow() -> Result<(), ArithmeticError> { + /// i32::MAX.ensure_sub(-1)?; + /// Ok(()) + /// } + /// + /// assert_eq!(underflow(), Err(ArithmeticError::Underflow)); + /// assert_eq!(overflow(), Err(ArithmeticError::Overflow)); + /// ``` + fn ensure_sub(self, v: Self) -> Result { + self.checked_sub(&v).ok_or_else(|| error::inverse(v)) + } + } + + /// Performs multiplication that returns [`ArithmeticError`] instead of wrapping around on + /// overflow. + pub trait EnsureMul: CheckedMul + PartialOrd + Zero + Copy { + /// Multiplies two numbers, checking for overflow. + /// + /// If it fails, [`ArithmeticError`] is returned. + /// + /// Similar to [`CheckedMul::checked_mul()`] but returning an [`ArithmeticError`] error. + /// + /// ``` + /// use sp_arithmetic::{traits::EnsureMul, ArithmeticError}; + /// + /// fn overflow() -> Result<(), ArithmeticError> { + /// u32::MAX.ensure_mul(2)?; + /// Ok(()) + /// } + /// + /// fn underflow() -> Result<(), ArithmeticError> { + /// i32::MAX.ensure_mul(-2)?; + /// Ok(()) + /// } + /// + /// assert_eq!(overflow(), Err(ArithmeticError::Overflow)); + /// assert_eq!(underflow(), Err(ArithmeticError::Underflow)); + /// ``` + fn ensure_mul(self, v: Self) -> Result { + self.checked_mul(&v).ok_or_else(|| error::multiplication(self, v)) + } + } + + /// Performs division that returns [`ArithmeticError`] instead of wrapping around on overflow. + pub trait EnsureDiv: CheckedDiv + PartialOrd + Zero + Copy { + /// Divides two numbers, checking for overflow. + /// + /// If it fails, [`ArithmeticError`] is returned. + /// + /// Similar to [`CheckedDiv::checked_div()`] but returning an [`ArithmeticError`] error. + /// + /// ``` + /// use sp_arithmetic::{traits::EnsureDiv, ArithmeticError}; + /// + /// fn extrinsic_zero() -> Result<(), ArithmeticError> { + /// 1.ensure_div(0)?; + /// Ok(()) + /// } + /// + /// fn overflow() -> Result<(), ArithmeticError> { + /// i64::MIN.ensure_div(-1)?; + /// Ok(()) + /// } + /// + /// assert_eq!(extrinsic_zero(), Err(ArithmeticError::DivisionByZero)); + /// assert_eq!(overflow(), Err(ArithmeticError::Overflow)); + /// ``` + fn ensure_div(self, v: Self) -> Result { + self.checked_div(&v).ok_or_else(|| error::division(self, v)) + } + } + + impl EnsureAdd for T {} + impl EnsureSub for T {} + impl EnsureMul for T {} + impl EnsureDiv for T {} + + /// Meta trait that supports all immutable arithmetic `Ensure*` operations + pub trait EnsureOp: EnsureAdd + EnsureSub + EnsureMul + EnsureDiv {} + impl EnsureOp for T {} + + /// Performs self addition that returns [`ArithmeticError`] instead of wrapping around on + /// overflow. + pub trait EnsureAddAssign: EnsureAdd { + /// Adds two numbers overwriting the left hand one, checking for overflow. + /// + /// If it fails, [`ArithmeticError`] is returned. + /// + /// ``` + /// use sp_arithmetic::{traits::EnsureAddAssign, ArithmeticError}; + /// + /// fn overflow() -> Result<(), ArithmeticError> { + /// let mut max = u32::MAX; + /// max.ensure_add_assign(1)?; + /// Ok(()) + /// } + /// + /// fn underflow() -> Result<(), ArithmeticError> { + /// let mut max = i32::MIN; + /// max.ensure_add_assign(-1)?; + /// Ok(()) + /// } + /// + /// assert_eq!(overflow(), Err(ArithmeticError::Overflow)); + /// assert_eq!(underflow(), Err(ArithmeticError::Underflow)); + /// ``` + fn ensure_add_assign(&mut self, v: Self) -> Result<(), ArithmeticError> { + *self = self.ensure_add(v)?; + Ok(()) + } + } + + /// Performs self subtraction that returns [`ArithmeticError`] instead of wrapping around on + /// underflow. + pub trait EnsureSubAssign: EnsureSub { + /// Subtracts two numbers overwriting the left hand one, checking for overflow. + /// + /// If it fails, [`ArithmeticError`] is returned. + /// + /// ``` + /// use sp_arithmetic::{traits::EnsureSubAssign, ArithmeticError}; + /// + /// fn underflow() -> Result<(), ArithmeticError> { + /// let mut zero: u32 = 0; + /// zero.ensure_sub_assign(1)?; + /// Ok(()) + /// } + /// + /// fn overflow() -> Result<(), ArithmeticError> { + /// let mut zero = i32::MAX; + /// zero.ensure_sub_assign(-1)?; + /// Ok(()) + /// } + /// + /// assert_eq!(underflow(), Err(ArithmeticError::Underflow)); + /// assert_eq!(overflow(), Err(ArithmeticError::Overflow)); + /// ``` + fn ensure_sub_assign(&mut self, v: Self) -> Result<(), ArithmeticError> { + *self = self.ensure_sub(v)?; + Ok(()) + } + } + + /// Performs self multiplication that returns [`ArithmeticError`] instead of wrapping around on + /// overflow. + pub trait EnsureMulAssign: EnsureMul { + /// Multiplies two numbers overwriting the left hand one, checking for overflow. + /// + /// If it fails, [`ArithmeticError`] is returned. + /// + /// ``` + /// use sp_arithmetic::{traits::EnsureMulAssign, ArithmeticError}; + /// + /// fn overflow() -> Result<(), ArithmeticError> { + /// let mut max = u32::MAX; + /// max.ensure_mul_assign(2)?; + /// Ok(()) + /// } + /// + /// fn underflow() -> Result<(), ArithmeticError> { + /// let mut max = i32::MAX; + /// max.ensure_mul_assign(-2)?; + /// Ok(()) + /// } + /// + /// assert_eq!(overflow(), Err(ArithmeticError::Overflow)); + /// assert_eq!(underflow(), Err(ArithmeticError::Underflow)); + /// ``` + fn ensure_mul_assign(&mut self, v: Self) -> Result<(), ArithmeticError> { + *self = self.ensure_mul(v)?; + Ok(()) + } + } + + /// Performs self division that returns [`ArithmeticError`] instead of wrapping around on + /// overflow. + pub trait EnsureDivAssign: EnsureDiv { + /// Divides two numbers overwriting the left hand one, checking for overflow. + /// + /// If it fails, [`ArithmeticError`] is returned. + /// + /// ``` + /// use sp_arithmetic::{traits::EnsureDivAssign, ArithmeticError, FixedI64}; + /// + /// fn extrinsic_zero() -> Result<(), ArithmeticError> { + /// let mut one = 1; + /// one.ensure_div_assign(0)?; + /// Ok(()) + /// } + /// + /// fn overflow() -> Result<(), ArithmeticError> { + /// let mut min = FixedI64::from(i64::MIN); + /// min.ensure_div_assign(FixedI64::from(-1))?; + /// Ok(()) + /// } + /// + /// assert_eq!(extrinsic_zero(), Err(ArithmeticError::DivisionByZero)); + /// assert_eq!(overflow(), Err(ArithmeticError::Overflow)); + /// ``` + fn ensure_div_assign(&mut self, v: Self) -> Result<(), ArithmeticError> { + *self = self.ensure_div(v)?; + Ok(()) + } + } + + impl EnsureAddAssign for T {} + impl EnsureSubAssign for T {} + impl EnsureMulAssign for T {} + impl EnsureDivAssign for T {} + + /// Meta trait that supports all assigned arithmetic `Ensure*` operations + pub trait EnsureOpAssign: + EnsureAddAssign + EnsureSubAssign + EnsureMulAssign + EnsureDivAssign + { + } + impl EnsureOpAssign + for T + { + } + + /// Meta trait that supports all arithmetic operations + pub trait Ensure: EnsureOp + EnsureOpAssign {} + impl Ensure for T {} + + /// Extends [`FixedPointNumber`] with the Ensure family functions. + pub trait EnsureFixedPointNumber: FixedPointNumber { + /// Creates `self` from a rational number. Equal to `n / d`. + /// + /// Returns [`ArithmeticError`] if `d == 0` or `n / d` exceeds accuracy. + /// + /// Similar to [`FixedPointNumber::checked_from_rational()`] but returning an + /// [`ArithmeticError`] error. + /// + /// ``` + /// use sp_arithmetic::{traits::EnsureFixedPointNumber, ArithmeticError, FixedI64}; + /// + /// fn extrinsic_zero() -> Result<(), ArithmeticError> { + /// FixedI64::ensure_from_rational(1, 0)?; + /// Ok(()) + /// } + /// + /// fn underflow() -> Result<(), ArithmeticError> { + /// FixedI64::ensure_from_rational(i64::MAX, -1)?; + /// Ok(()) + /// } + /// + /// assert_eq!(extrinsic_zero(), Err(ArithmeticError::DivisionByZero)); + /// assert_eq!(underflow(), Err(ArithmeticError::Underflow)); + /// ``` + fn ensure_from_rational( + n: N, + d: D, + ) -> Result { + ::checked_from_rational(n, d) + .ok_or_else(|| error::division(n, d)) + } + + /// Ensure multiplication for integer type `N`. Equal to `self * n`. + /// + /// Returns [`ArithmeticError`] if the result does not fit in `N`. + /// + /// Similar to [`FixedPointNumber::checked_mul_int()`] but returning an [`ArithmeticError`] + /// error. + /// + /// ``` + /// use sp_arithmetic::{traits::EnsureFixedPointNumber, ArithmeticError, FixedI64}; + /// + /// fn overflow() -> Result<(), ArithmeticError> { + /// FixedI64::from(i64::MAX).ensure_mul_int(2)?; + /// Ok(()) + /// } + /// + /// fn underflow() -> Result<(), ArithmeticError> { + /// FixedI64::from(i64::MAX).ensure_mul_int(-2)?; + /// Ok(()) + /// } + /// + /// assert_eq!(overflow(), Err(ArithmeticError::Overflow)); + /// assert_eq!(underflow(), Err(ArithmeticError::Underflow)); + /// ``` + fn ensure_mul_int(self, n: N) -> Result { + self.checked_mul_int(n).ok_or_else(|| error::multiplication(self, n)) + } + + /// Ensure division for integer type `N`. Equal to `self / d`. + /// + /// Returns [`ArithmeticError`] if the result does not fit in `N` or `d == 0`. + /// + /// Similar to [`FixedPointNumber::checked_div_int()`] but returning an [`ArithmeticError`] + /// error. + /// + /// ``` + /// use sp_arithmetic::{traits::EnsureFixedPointNumber, ArithmeticError, FixedI64}; + /// + /// fn extrinsic_zero() -> Result<(), ArithmeticError> { + /// FixedI64::from(1).ensure_div_int(0)?; + /// Ok(()) + /// } + /// + /// fn overflow() -> Result<(), ArithmeticError> { + /// FixedI64::from(i64::MIN).ensure_div_int(-1)?; + /// Ok(()) + /// } + /// + /// assert_eq!(extrinsic_zero(), Err(ArithmeticError::DivisionByZero)); + /// assert_eq!(overflow(), Err(ArithmeticError::Overflow)); + /// ``` + fn ensure_div_int(self, d: D) -> Result { + self.checked_div_int(d).ok_or_else(|| error::division(self, d)) + } + } + + impl EnsureFixedPointNumber for T {} + + /// Similar to [`TryFrom`] but returning an [`ArithmeticError`] error. + pub trait EnsureFrom: + TryFrom + PartialOrd + Zero + Copy + { + /// Performs the conversion returning an [`ArithmeticError`] if fails. + /// + /// Similar to [`TryFrom::try_from()`] but returning an [`ArithmeticError`] error. + /// + /// ``` + /// use sp_arithmetic::{traits::EnsureFrom, ArithmeticError}; + /// + /// fn overflow() -> Result<(), ArithmeticError> { + /// let byte: u8 = u8::ensure_from(256u16)?; + /// Ok(()) + /// } + /// + /// fn underflow() -> Result<(), ArithmeticError> { + /// let byte: i8 = i8::ensure_from(-129i16)?; + /// Ok(()) + /// } + /// + /// assert_eq!(overflow(), Err(ArithmeticError::Overflow)); + /// assert_eq!(underflow(), Err(ArithmeticError::Underflow)); + /// ``` + fn ensure_from(other: T) -> Result { + Self::try_from(other).map_err(|_| error::equivalent(other)) + } + } + + /// Similar to [`TryInto`] but returning an [`ArithmeticError`] error. + pub trait EnsureInto: + TryInto + PartialOrd + Zero + Copy + { + /// Performs the conversion returning an [`ArithmeticError`] if fails. + /// + /// Similar to [`TryInto::try_into()`] but returning an [`ArithmeticError`] error + /// + /// ``` + /// use sp_arithmetic::{traits::EnsureInto, ArithmeticError}; + /// + /// fn overflow() -> Result<(), ArithmeticError> { + /// let byte: u8 = 256u16.ensure_into()?; + /// Ok(()) + /// } + /// + /// fn underflow() -> Result<(), ArithmeticError> { + /// let byte: i8 = (-129i16).ensure_into()?; + /// Ok(()) + /// } + /// + /// assert_eq!(overflow(), Err(ArithmeticError::Overflow)); + /// assert_eq!(underflow(), Err(ArithmeticError::Underflow)); + /// ``` + fn ensure_into(self) -> Result { + self.try_into().map_err(|_| error::equivalent(self)) + } + } + + impl + PartialOrd + Zero + Copy, S: PartialOrd + Zero + Copy> EnsureFrom for T {} + impl + PartialOrd + Zero + Copy, S: PartialOrd + Zero + Copy> EnsureInto for T {} + + mod error { + use super::{ArithmeticError, Zero}; + + #[derive(PartialEq)] + enum Signum { + Negative, + Positive, + } + + impl From for Signum { + fn from(value: T) -> Self { + if value < Zero::zero() { + Signum::Negative + } else { + Signum::Positive + } + } + } + + impl sp_std::ops::Mul for Signum { + type Output = Self; + + fn mul(self, rhs: Self) -> Self { + if self != rhs { + Signum::Negative + } else { + Signum::Positive + } + } + } + + pub fn equivalent(r: R) -> ArithmeticError { + match Signum::from(r) { + Signum::Negative => ArithmeticError::Underflow, + Signum::Positive => ArithmeticError::Overflow, + } + } + + pub fn inverse(r: R) -> ArithmeticError { + match Signum::from(r) { + Signum::Negative => ArithmeticError::Overflow, + Signum::Positive => ArithmeticError::Underflow, + } + } + + pub fn multiplication( + l: L, + r: R, + ) -> ArithmeticError { + match Signum::from(l) * Signum::from(r) { + Signum::Negative => ArithmeticError::Underflow, + Signum::Positive => ArithmeticError::Overflow, + } + } + + pub fn division( + n: N, + d: D, + ) -> ArithmeticError { + if d.is_zero() { + ArithmeticError::DivisionByZero + } else { + multiplication(n, d) + } + } + } +} diff --git a/primitives/runtime/src/lib.rs b/primitives/runtime/src/lib.rs index e94efda86a..96fe7d2487 100644 --- a/primitives/runtime/src/lib.rs +++ b/primitives/runtime/src/lib.rs @@ -93,9 +93,9 @@ pub use sp_arithmetic::biguint; pub use sp_arithmetic::helpers_128bit; /// Re-export top-level arithmetic stuff. pub use sp_arithmetic::{ - traits::SaturatedConversion, FixedI128, FixedI64, FixedPointNumber, FixedPointOperand, - FixedU128, InnerOf, PerThing, PerU16, Perbill, Percent, Permill, Perquintill, Rational128, - Rounding, UpperOf, + traits::SaturatedConversion, ArithmeticError, FixedI128, FixedI64, FixedPointNumber, + FixedPointOperand, FixedU128, InnerOf, PerThing, PerU16, Perbill, Percent, Permill, + Perquintill, Rational128, Rounding, UpperOf, }; pub use either::Either; @@ -641,28 +641,6 @@ impl From for DispatchError { } } -/// Arithmetic errors. -#[derive(Eq, PartialEq, Clone, Copy, Encode, Decode, Debug, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub enum ArithmeticError { - /// Underflow. - Underflow, - /// Overflow. - Overflow, - /// Division by zero. - DivisionByZero, -} - -impl From for &'static str { - fn from(e: ArithmeticError) -> &'static str { - match e { - ArithmeticError::Underflow => "An underflow would occur", - ArithmeticError::Overflow => "An overflow would occur", - ArithmeticError::DivisionByZero => "Division by zero", - } - } -} - impl From for DispatchError { fn from(e: ArithmeticError) -> DispatchError { Self::Arithmetic(e) diff --git a/primitives/runtime/src/traits.rs b/primitives/runtime/src/traits.rs index 375475141b..8978cdb11c 100644 --- a/primitives/runtime/src/traits.rs +++ b/primitives/runtime/src/traits.rs @@ -33,8 +33,10 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize}; use sp_application_crypto::AppKey; pub use sp_arithmetic::traits::{ AtLeast32Bit, AtLeast32BitUnsigned, Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedShl, - CheckedShr, CheckedSub, IntegerSquareRoot, One, SaturatedConversion, Saturating, - UniqueSaturatedFrom, UniqueSaturatedInto, Zero, + CheckedShr, CheckedSub, Ensure, EnsureAdd, EnsureAddAssign, EnsureDiv, EnsureDivAssign, + EnsureFixedPointNumber, EnsureFrom, EnsureInto, EnsureMul, EnsureMulAssign, EnsureOp, + EnsureOpAssign, EnsureSub, EnsureSubAssign, IntegerSquareRoot, One, SaturatedConversion, + Saturating, UniqueSaturatedFrom, UniqueSaturatedInto, Zero, }; use sp_core::{self, storage::StateVersion, Hasher, RuntimeDebug, TypeId}; #[doc(hidden)] From 52e96b3a88c54bc62076a767b67a8bc2fe60dcec Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Thu, 29 Dec 2022 00:10:47 +0200 Subject: [PATCH 085/127] Derive `Debug` on `SyncStatus` (#13025) --- client/network/common/src/sync.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/network/common/src/sync.rs b/client/network/common/src/sync.rs index 5e8219c550..802cf75fc4 100644 --- a/client/network/common/src/sync.rs +++ b/client/network/common/src/sync.rs @@ -70,7 +70,7 @@ pub struct StateDownloadProgress { } /// Syncing status and statistics. -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct SyncStatus { /// Current global sync state. pub state: SyncState>, From 3addee20bc5f726244ebfba67b77569d27d0328e Mon Sep 17 00:00:00 2001 From: Vlad Date: Thu, 29 Dec 2022 19:20:41 +0400 Subject: [PATCH 086/127] Enable `zstd` for `rusty-cachier` (#13029) --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4d17b70028..06890c9ed3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -53,6 +53,7 @@ variables: BUILDAH_IMAGE: "quay.io/buildah/stable:v1.27" RUSTY_CACHIER_SINGLE_BRANCH: master RUSTY_CACHIER_DONT_OPERATE_ON_MAIN_BRANCH: "true" + RUSTY_CACHIER_COMPRESSION_METHOD: zstd ZOMBIENET_IMAGE: "docker.io/paritytech/zombienet:v1.3.22" default: From ed82c874b9cee5b7049730cf5983a02ebc9f89d5 Mon Sep 17 00:00:00 2001 From: Vlad Date: Thu, 29 Dec 2022 19:46:56 +0400 Subject: [PATCH 087/127] Unpin `ci-linux` and use the latest `production` tag as before (#13030) --- .gitlab-ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 06890c9ed3..a0b1464bae 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -48,8 +48,7 @@ variables: CARGO_INCREMENTAL: 0 DOCKER_OS: "debian:stretch" ARCH: "x86_64" - # staging image with rust 1.65 and nightly-2022-11-16 - CI_IMAGE: "paritytech/ci-linux@sha256:786869e731963b3cc0a4aa9deb83367ed9e87a6ae48b6eb029d62b0cab4d87c1" + CI_IMAGE: "paritytech/ci-linux:production" BUILDAH_IMAGE: "quay.io/buildah/stable:v1.27" RUSTY_CACHIER_SINGLE_BRANCH: master RUSTY_CACHIER_DONT_OPERATE_ON_MAIN_BRANCH: "true" From 55c64bcc2af5a6e5fc3eb245e638379ebe18a58d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89lo=C3=AFs?= Date: Fri, 30 Dec 2022 18:54:04 +0100 Subject: [PATCH 088/127] try-runtime: add cli option --export-proof (#12539) * try-runtime: add cli option --export-proof * extract proof in raw json format * fix build * fix(try-runtime execute-block): wrong block parsing * fmt * apply suggestions * Update utils/frame/try-runtime/cli/src/lib.rs Co-authored-by: Anton * Update utils/frame/try-runtime/cli/src/lib.rs Co-authored-by: Anton * Update utils/frame/try-runtime/cli/src/lib.rs Co-authored-by: Anton * Update utils/frame/try-runtime/cli/src/lib.rs Co-authored-by: Anton * split off external dependencies * fmt * fix try-runtime compilation Co-authored-by: Anton --- Cargo.lock | 1 + utils/frame/try-runtime/cli/Cargo.toml | 5 +- .../cli/src/commands/execute_block.rs | 1 + .../cli/src/commands/follow_chain.rs | 4 ++ .../cli/src/commands/on_runtime_upgrade.rs | 1 + utils/frame/try-runtime/cli/src/lib.rs | 51 +++++++++++++++++++ 6 files changed, 61 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b2aca09fe2..f82343e442 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10646,6 +10646,7 @@ dependencies = [ "sc-executor", "sc-service", "serde", + "serde_json", "sp-api", "sp-core", "sp-debug-derive", diff --git a/utils/frame/try-runtime/cli/Cargo.toml b/utils/frame/try-runtime/cli/Cargo.toml index c4cc2e757d..ed0f25cae7 100644 --- a/utils/frame/try-runtime/cli/Cargo.toml +++ b/utils/frame/try-runtime/cli/Cargo.toml @@ -30,11 +30,12 @@ sp-weights = { version = "4.0.0", path = "../../../../primitives/weights" } frame-try-runtime = { optional = true, path = "../../../../frame/try-runtime" } substrate-rpc-client = { path = "../../rpc/client" } -parity-scale-codec = "3.0.0" -hex = "0.4.3" clap = { version = "4.0.9", features = ["derive"] } +hex = { version = "0.4.3", default-features = false } log = "0.4.17" +parity-scale-codec = "3.0.0" serde = "1.0.136" +serde_json = "1.0.85" zstd = { version = "0.11.2", default-features = false } [dev-dependencies] diff --git a/utils/frame/try-runtime/cli/src/commands/execute_block.rs b/utils/frame/try-runtime/cli/src/commands/execute_block.rs index 80d34002fa..ee5e21af5d 100644 --- a/utils/frame/try-runtime/cli/src/commands/execute_block.rs +++ b/utils/frame/try-runtime/cli/src/commands/execute_block.rs @@ -134,6 +134,7 @@ where "TryRuntime_execute_block", &payload, full_extensions(), + shared.export_proof, )?; Ok(()) diff --git a/utils/frame/try-runtime/cli/src/commands/follow_chain.rs b/utils/frame/try-runtime/cli/src/commands/follow_chain.rs index 4eb3b3a8f3..e4f166fe7a 100644 --- a/utils/frame/try-runtime/cli/src/commands/follow_chain.rs +++ b/utils/frame/try-runtime/cli/src/commands/follow_chain.rs @@ -150,6 +150,10 @@ where "TryRuntime_execute_block", (block, command.state_root_check, command.try_state.clone()).encode().as_ref(), full_extensions(), + shared + .export_proof + .as_ref() + .map(|path| path.as_path().join(&format!("{}.json", number))), ); if let Err(why) = result { diff --git a/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs b/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs index 80fb5d31f7..2b2800d505 100644 --- a/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs +++ b/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs @@ -59,6 +59,7 @@ where "TryRuntime_on_runtime_upgrade", command.checks.encode().as_ref(), Default::default(), // we don't really need any extensions here. + shared.export_proof, )?; let (weight, total_weight) = <(Weight, Weight) as Decode>::decode(&mut &*encoded_result) diff --git a/utils/frame/try-runtime/cli/src/lib.rs b/utils/frame/try-runtime/cli/src/lib.rs index 47a9dfa3f6..aac2ad4238 100644 --- a/utils/frame/try-runtime/cli/src/lib.rs +++ b/utils/frame/try-runtime/cli/src/lib.rs @@ -523,6 +523,12 @@ pub struct SharedParams { #[arg(long)] pub heap_pages: Option, + /// Path to a file to export the storage proof into (as a JSON). + /// If several blocks are executed, the path is interpreted as a folder + /// where one file per block will be written (named `{block_number}-{block_hash}`). + #[clap(long)] + pub export_proof: Option, + /// Overwrite the `state_version`. /// /// Otherwise `remote-externalities` will automatically set the correct state version. @@ -863,6 +869,7 @@ pub(crate) fn state_machine_call_with_proof, ) -> sc_cli::Result<(OverlayedChanges, Vec)> { use parity_scale_codec::Encode; @@ -891,6 +898,32 @@ pub(crate) fn state_machine_call_with_proof &'static str { log::error!(target: LOG_TARGET, "rpc error: {:?}", error); "rpc error." } + +/// Converts a [`sp_state_machine::StorageProof`] into a JSON string. +fn storage_proof_to_raw_json(storage_proof: &sp_state_machine::StorageProof) -> String { + serde_json::Value::Object( + storage_proof + .to_memory_db::() + .drain() + .iter() + .map(|(key, (value, _n))| { + ( + format!("0x{}", hex::encode(key.as_bytes())), + serde_json::Value::String(format!("0x{}", hex::encode(value))), + ) + }) + .collect(), + ) + .to_string() +} From bcee526a9b73d2df9d5dea0f1a17677618d70b8e Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Mon, 2 Jan 2023 10:42:05 +0100 Subject: [PATCH 089/127] BlockId removal: refactor: BlockBackend::block|block_status (#13014) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * BlockId removal: refactor: BlockBackend::block|block_status It changes the arguments of: - `BlockBackend::block` - `BlockBackend::block_status` method from: `BlockId` to: `Block::Hash` This PR is part of BlockId::Number refactoring analysis (paritytech/substrate#11292) * non-obvious reworks * doc fix * Apply suggestions from code review Co-authored-by: Bastian Köcher Co-authored-by: Bastian Köcher Co-authored-by: parity-processbot <> --- client/api/src/client.rs | 13 +++-- client/cli/src/commands/export_blocks_cmd.rs | 4 +- client/network/sync/src/lib.rs | 11 ++--- .../rpc-spec-v2/src/chain_head/chain_head.rs | 2 +- client/rpc/src/chain/chain_full.rs | 7 +-- client/rpc/src/dev/mod.rs | 5 +- client/service/src/chain_ops/check_block.rs | 21 ++++---- client/service/src/chain_ops/export_blocks.rs | 12 +++-- client/service/src/client/client.rs | 48 +++++++++---------- client/service/test/src/client/mod.rs | 37 ++++---------- .../consensus/common/src/block_validation.rs | 12 ++--- .../frame/benchmarking-cli/src/block/bench.rs | 4 +- 12 files changed, 75 insertions(+), 101 deletions(-) diff --git a/client/api/src/client.rs b/client/api/src/client.rs index 05e3163dcc..0d00257fa7 100644 --- a/client/api/src/client.rs +++ b/client/api/src/client.rs @@ -21,7 +21,7 @@ use sp_consensus::BlockOrigin; use sp_core::storage::StorageKey; use sp_runtime::{ - generic::{BlockId, SignedBlock}, + generic::SignedBlock, traits::{Block as BlockT, NumberFor}, Justifications, }; @@ -120,14 +120,13 @@ pub trait BlockBackend { /// that are indexed by the runtime with `storage_index_transaction`. fn block_indexed_body(&self, hash: Block::Hash) -> sp_blockchain::Result>>>; - /// Get full block by id. - fn block(&self, id: &BlockId) -> sp_blockchain::Result>>; + /// Get full block by hash. + fn block(&self, hash: Block::Hash) -> sp_blockchain::Result>>; - /// Get block status. - fn block_status(&self, id: &BlockId) - -> sp_blockchain::Result; + /// Get block status by block hash. + fn block_status(&self, hash: Block::Hash) -> sp_blockchain::Result; - /// Get block justifications for the block with the given id. + /// Get block justifications for the block with the given hash. fn justifications(&self, hash: Block::Hash) -> sp_blockchain::Result>; /// Get block hash by number. diff --git a/client/cli/src/commands/export_blocks_cmd.rs b/client/cli/src/commands/export_blocks_cmd.rs index e2f83200e5..76fedff4d3 100644 --- a/client/cli/src/commands/export_blocks_cmd.rs +++ b/client/cli/src/commands/export_blocks_cmd.rs @@ -23,7 +23,7 @@ use crate::{ }; use clap::Parser; use log::info; -use sc_client_api::{BlockBackend, UsageProvider}; +use sc_client_api::{BlockBackend, HeaderBackend, UsageProvider}; use sc_service::{chain_ops::export_blocks, config::DatabaseSource}; use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; use std::{fmt::Debug, fs, io, path::PathBuf, str::FromStr, sync::Arc}; @@ -73,7 +73,7 @@ impl ExportBlocksCmd { ) -> error::Result<()> where B: BlockT, - C: BlockBackend + UsageProvider + 'static, + C: HeaderBackend + BlockBackend + UsageProvider + 'static, <::Number as FromStr>::Err: Debug, { if let Some(path) = database_config.path() { diff --git a/client/network/sync/src/lib.rs b/client/network/sync/src/lib.rs index 76f14a721a..312fc6f5b7 100644 --- a/client/network/sync/src/lib.rs +++ b/client/network/sync/src/lib.rs @@ -86,7 +86,6 @@ use sp_consensus::{ BlockOrigin, BlockStatus, }; use sp_runtime::{ - generic::BlockId, traits::{ Block as BlockT, CheckedSub, Hash, HashFor, Header as HeaderT, NumberFor, One, SaturatedConversion, Zero, @@ -1865,8 +1864,7 @@ where self.best_queued_number = info.best_number; if self.mode == SyncMode::Full && - self.client.block_status(&BlockId::hash(info.best_hash))? != - BlockStatus::InChainWithState + self.client.block_status(info.best_hash)? != BlockStatus::InChainWithState { self.import_existing = true; // Latest state is missing, start with the last finalized state or genesis instead. @@ -1898,7 +1896,7 @@ where if self.queue_blocks.contains(hash) { return Ok(BlockStatus::Queued) } - self.client.block_status(&BlockId::Hash(*hash)) + self.client.block_status(*hash) } /// Is the block corresponding to the given hash known? @@ -2455,9 +2453,7 @@ where if queue.contains(hash) { BlockStatus::Queued } else { - client - .block_status(&BlockId::Hash(*hash)) - .unwrap_or(BlockStatus::Unknown) + client.block_status(*hash).unwrap_or(BlockStatus::Unknown) } }, ) { @@ -3202,6 +3198,7 @@ mod test { }; use sp_blockchain::HeaderBackend; use sp_consensus::block_validation::DefaultBlockAnnounceValidator; + use sp_runtime::generic::BlockId; use substrate_test_runtime_client::{ runtime::{Block, Hash, Header}, BlockBuilderExt, ClientBlockImportExt, ClientExt, DefaultTestClientBuilderExt, TestClient, diff --git a/client/rpc-spec-v2/src/chain_head/chain_head.rs b/client/rpc-spec-v2/src/chain_head/chain_head.rs index c0ba9e165d..c63d373e04 100644 --- a/client/rpc-spec-v2/src/chain_head/chain_head.rs +++ b/client/rpc-spec-v2/src/chain_head/chain_head.rs @@ -573,7 +573,7 @@ where return } - let event = match client.block(&BlockId::Hash(hash)) { + let event = match client.block(hash) { Ok(Some(signed_block)) => { let extrinsics = signed_block.block.extrinsics(); let result = format!("0x{:?}", HexDisplay::from(&extrinsics.encode())); diff --git a/client/rpc/src/chain/chain_full.rs b/client/rpc/src/chain/chain_full.rs index ee7870e062..6ff544b0de 100644 --- a/client/rpc/src/chain/chain_full.rs +++ b/client/rpc/src/chain/chain_full.rs @@ -29,10 +29,7 @@ use futures::{ use jsonrpsee::SubscriptionSink; use sc_client_api::{BlockBackend, BlockchainEvents}; use sp_blockchain::HeaderBackend; -use sp_runtime::{ - generic::{BlockId, SignedBlock}, - traits::Block as BlockT, -}; +use sp_runtime::{generic::SignedBlock, traits::Block as BlockT}; /// Blockchain API backend for full nodes. Reads all the data from local database. pub struct FullChain { @@ -66,7 +63,7 @@ where } fn block(&self, hash: Option) -> Result>, Error> { - self.client.block(&BlockId::Hash(self.unwrap_or_best(hash))).map_err(client_err) + self.client.block(self.unwrap_or_best(hash)).map_err(client_err) } fn subscribe_all_heads(&self, sink: SubscriptionSink) { diff --git a/client/rpc/src/dev/mod.rs b/client/rpc/src/dev/mod.rs index ec9e61678f..e48a8ee4e7 100644 --- a/client/rpc/src/dev/mod.rs +++ b/client/rpc/src/dev/mod.rs @@ -69,10 +69,7 @@ where self.deny_unsafe.check_if_safe()?; let block = { - let block = self - .client - .block(&BlockId::Hash(hash)) - .map_err(|e| Error::BlockQueryError(Box::new(e)))?; + let block = self.client.block(hash).map_err(|e| Error::BlockQueryError(Box::new(e)))?; if let Some(block) = block { let (mut header, body) = block.block.deconstruct(); // Remove the `Seal` to ensure we have the number of digests as expected by the diff --git a/client/service/src/chain_ops/check_block.rs b/client/service/src/chain_ops/check_block.rs index 41a6c73c5f..c3380e5476 100644 --- a/client/service/src/chain_ops/check_block.rs +++ b/client/service/src/chain_ops/check_block.rs @@ -18,34 +18,37 @@ use crate::error::Error; use codec::Encode; -use futures::{future, prelude::*}; use sc_client_api::{BlockBackend, HeaderBackend}; use sc_consensus::import_queue::ImportQueue; use sp_runtime::{generic::BlockId, traits::Block as BlockT}; use crate::chain_ops::import_blocks; -use std::{pin::Pin, sync::Arc}; +use std::sync::Arc; /// Re-validate known block. -pub fn check_block( +pub async fn check_block( client: Arc, import_queue: IQ, block_id: BlockId, -) -> Pin> + Send>> +) -> Result<(), Error> where C: BlockBackend + HeaderBackend + Send + Sync + 'static, B: BlockT + for<'de> serde::Deserialize<'de>, IQ: ImportQueue + 'static, { - match client.block(&block_id) { - Ok(Some(block)) => { + let maybe_block = client + .block_hash_from_id(&block_id)? + .map(|hash| client.block(hash)) + .transpose()? + .flatten(); + match maybe_block { + Some(block) => { let mut buf = Vec::new(); 1u64.encode_to(&mut buf); block.encode_to(&mut buf); let reader = std::io::Cursor::new(buf); - import_blocks(client, import_queue, reader, true, true) + import_blocks(client, import_queue, reader, true, true).await }, - Ok(None) => Box::pin(future::err("Unknown block".into())), - Err(e) => Box::pin(future::err(format!("Error reading block: {}", e).into())), + None => Err("Unknown block")?, } } diff --git a/client/service/src/chain_ops/export_blocks.rs b/client/service/src/chain_ops/export_blocks.rs index d442a11f2c..9638722ef6 100644 --- a/client/service/src/chain_ops/export_blocks.rs +++ b/client/service/src/chain_ops/export_blocks.rs @@ -25,7 +25,7 @@ use sp_runtime::{ traits::{Block as BlockT, NumberFor, One, SaturatedConversion, Zero}, }; -use sc_client_api::{BlockBackend, UsageProvider}; +use sc_client_api::{BlockBackend, HeaderBackend, UsageProvider}; use std::{io::Write, pin::Pin, sync::Arc, task::Poll}; /// Performs the blocks export. @@ -37,7 +37,7 @@ pub fn export_blocks( binary: bool, ) -> Pin>>> where - C: BlockBackend + UsageProvider + 'static, + C: HeaderBackend + BlockBackend + UsageProvider + 'static, B: BlockT, { let mut block = from; @@ -75,7 +75,12 @@ where wrote_header = true; } - match client.block(&BlockId::number(block))? { + match client + .block_hash_from_id(&BlockId::number(block))? + .map(|hash| client.block(hash)) + .transpose()? + .flatten() + { Some(block) => if binary { output.write_all(&block.encode())?; @@ -83,7 +88,6 @@ where serde_json::to_writer(&mut output, &block) .map_err(|e| format!("Error writing JSON: {}", e))?; }, - // Reached end of the chain. None => return Poll::Ready(Ok(())), } if (block % 10000u32.into()).is_zero() { diff --git a/client/service/src/client/client.rs b/client/service/src/client/client.rs index 78f2174b89..18012fc193 100644 --- a/client/service/src/client/client.rs +++ b/client/service/src/client/client.rs @@ -784,7 +784,8 @@ where let parent_hash = import_block.header.parent_hash(); let at = BlockId::Hash(*parent_hash); let state_action = std::mem::replace(&mut import_block.state_action, StateAction::Skip); - let (enact_state, storage_changes) = match (self.block_status(&at)?, state_action) { + let (enact_state, storage_changes) = match (self.block_status(*parent_hash)?, state_action) + { (BlockStatus::KnownBad, _) => return Ok(PrepareStorageChangesResult::Discard(ImportResult::KnownBad)), ( @@ -1025,17 +1026,18 @@ where } /// Get block status. - pub fn block_status(&self, id: &BlockId) -> sp_blockchain::Result { + pub fn block_status(&self, hash: Block::Hash) -> sp_blockchain::Result { // this can probably be implemented more efficiently - if let BlockId::Hash(ref h) = id { - if self.importing_block.read().as_ref().map_or(false, |importing| h == importing) { - return Ok(BlockStatus::Queued) - } + if self + .importing_block + .read() + .as_ref() + .map_or(false, |importing| &hash == importing) + { + return Ok(BlockStatus::Queued) } - let hash_and_number = match *id { - BlockId::Hash(hash) => self.backend.blockchain().number(hash)?.map(|n| (hash, n)), - BlockId::Number(n) => self.backend.blockchain().hash(n)?.map(|hash| (hash, n)), - }; + + let hash_and_number = self.backend.blockchain().number(hash)?.map(|n| (hash, n)); match hash_and_number { Some((hash, number)) => if self.backend.have_state_at(hash, number) { @@ -1779,7 +1781,7 @@ where // Own status must be checked first. If the block and ancestry is pruned // this function must return `AlreadyInChain` rather than `MissingState` match self - .block_status(&BlockId::Hash(hash)) + .block_status(hash) .map_err(|e| ConsensusError::ClientImport(e.to_string()))? { BlockStatus::InChainWithState | BlockStatus::Queued => @@ -1792,7 +1794,7 @@ where } match self - .block_status(&BlockId::Hash(parent_hash)) + .block_status(parent_hash) .map_err(|e| ConsensusError::ClientImport(e.to_string()))? { BlockStatus::InChainWithState | BlockStatus::Queued => {}, @@ -1947,20 +1949,16 @@ where self.body(hash) } - fn block(&self, id: &BlockId) -> sp_blockchain::Result>> { - Ok(match self.backend.blockchain().block_hash_from_id(id)? { - Some(hash) => - match (self.header(hash)?, self.body(hash)?, self.justifications(hash)?) { - (Some(header), Some(extrinsics), justifications) => - Some(SignedBlock { block: Block::new(header, extrinsics), justifications }), - _ => None, - }, - None => None, + fn block(&self, hash: Block::Hash) -> sp_blockchain::Result>> { + Ok(match (self.header(hash)?, self.body(hash)?, self.justifications(hash)?) { + (Some(header), Some(extrinsics), justifications) => + Some(SignedBlock { block: Block::new(header, extrinsics), justifications }), + _ => None, }) } - fn block_status(&self, id: &BlockId) -> sp_blockchain::Result { - Client::block_status(self, id) + fn block_status(&self, hash: Block::Hash) -> sp_blockchain::Result { + Client::block_status(self, hash) } fn justifications(&self, hash: Block::Hash) -> sp_blockchain::Result> { @@ -2055,9 +2053,9 @@ where { fn block_status( &self, - id: &BlockId, + hash: B::Hash, ) -> Result> { - Client::block_status(self, id).map_err(|e| Box::new(e) as Box<_>) + Client::block_status(self, hash).map_err(|e| Box::new(e) as Box<_>) } } diff --git a/client/service/test/src/client/mod.rs b/client/service/test/src/client/mod.rs index 661cf83fc4..97c22a1cb5 100644 --- a/client/service/test/src/client/mod.rs +++ b/client/service/test/src/client/mod.rs @@ -1137,7 +1137,7 @@ fn get_block_by_bad_block_hash_returns_none() { let client = substrate_test_runtime_client::new(); let hash = H256::from_low_u64_be(5); - assert!(client.block(&BlockId::Hash(hash)).unwrap().is_none()); + assert!(client.block(hash).unwrap().is_none()); } #[test] @@ -1497,10 +1497,7 @@ fn returns_status_for_pruned_blocks() { block_on(client.check_block(check_block_a1.clone())).unwrap(), ImportResult::imported(false), ); - assert_eq!( - client.block_status(&BlockId::hash(check_block_a1.hash)).unwrap(), - BlockStatus::Unknown, - ); + assert_eq!(client.block_status(check_block_a1.hash).unwrap(), BlockStatus::Unknown); block_on(client.import_as_final(BlockOrigin::Own, a1.clone())).unwrap(); @@ -1508,10 +1505,7 @@ fn returns_status_for_pruned_blocks() { block_on(client.check_block(check_block_a1.clone())).unwrap(), ImportResult::AlreadyInChain, ); - assert_eq!( - client.block_status(&BlockId::hash(check_block_a1.hash)).unwrap(), - BlockStatus::InChainWithState, - ); + assert_eq!(client.block_status(check_block_a1.hash).unwrap(), BlockStatus::InChainWithState); let a2 = client .new_block_at(&BlockId::Hash(a1.hash()), Default::default(), false) @@ -1534,18 +1528,12 @@ fn returns_status_for_pruned_blocks() { block_on(client.check_block(check_block_a1.clone())).unwrap(), ImportResult::AlreadyInChain, ); - assert_eq!( - client.block_status(&BlockId::hash(check_block_a1.hash)).unwrap(), - BlockStatus::InChainPruned, - ); + assert_eq!(client.block_status(check_block_a1.hash).unwrap(), BlockStatus::InChainPruned); assert_eq!( block_on(client.check_block(check_block_a2.clone())).unwrap(), ImportResult::AlreadyInChain, ); - assert_eq!( - client.block_status(&BlockId::hash(check_block_a2.hash)).unwrap(), - BlockStatus::InChainWithState, - ); + assert_eq!(client.block_status(check_block_a2.hash).unwrap(), BlockStatus::InChainWithState); let a3 = client .new_block_at(&BlockId::Hash(a2.hash()), Default::default(), false) @@ -1569,26 +1557,17 @@ fn returns_status_for_pruned_blocks() { block_on(client.check_block(check_block_a1.clone())).unwrap(), ImportResult::AlreadyInChain, ); - assert_eq!( - client.block_status(&BlockId::hash(check_block_a1.hash)).unwrap(), - BlockStatus::InChainPruned, - ); + assert_eq!(client.block_status(check_block_a1.hash).unwrap(), BlockStatus::InChainPruned); assert_eq!( block_on(client.check_block(check_block_a2.clone())).unwrap(), ImportResult::AlreadyInChain, ); - assert_eq!( - client.block_status(&BlockId::hash(check_block_a2.hash)).unwrap(), - BlockStatus::InChainPruned, - ); + assert_eq!(client.block_status(check_block_a2.hash).unwrap(), BlockStatus::InChainPruned); assert_eq!( block_on(client.check_block(check_block_a3.clone())).unwrap(), ImportResult::AlreadyInChain, ); - assert_eq!( - client.block_status(&BlockId::hash(check_block_a3.hash)).unwrap(), - BlockStatus::InChainWithState, - ); + assert_eq!(client.block_status(check_block_a3.hash).unwrap(), BlockStatus::InChainWithState); let mut check_block_b1 = BlockCheckParams { hash: b1.hash(), diff --git a/primitives/consensus/common/src/block_validation.rs b/primitives/consensus/common/src/block_validation.rs index 71f3a80b27..1dc4210491 100644 --- a/primitives/consensus/common/src/block_validation.rs +++ b/primitives/consensus/common/src/block_validation.rs @@ -19,18 +19,18 @@ use crate::BlockStatus; use futures::FutureExt as _; -use sp_runtime::{generic::BlockId, traits::Block}; +use sp_runtime::traits::Block; use std::{error::Error, future::Future, pin::Pin, sync::Arc}; /// A type which provides access to chain information. pub trait Chain { - /// Retrieve the status of the block denoted by the given [`BlockId`]. - fn block_status(&self, id: &BlockId) -> Result>; + /// Retrieve the status of the block denoted by the given [`Block::Hash`]. + fn block_status(&self, hash: B::Hash) -> Result>; } impl, B: Block> Chain for Arc { - fn block_status(&self, id: &BlockId) -> Result> { - (&**self).block_status(id) + fn block_status(&self, hash: B::Hash) -> Result> { + (&**self).block_status(hash) } } @@ -60,7 +60,7 @@ pub trait BlockAnnounceValidator { /// Returning [`Validation::Failure`] will lead to a decrease of the /// peers reputation as it sent us invalid data. /// - /// The returned future should only resolve to an error iff there was an internal error + /// The returned future should only resolve to an error if there was an internal error /// validating the block announcement. If the block announcement itself is invalid, this should /// *always* return [`Validation::Failure`]. fn validate( diff --git a/utils/frame/benchmarking-cli/src/block/bench.rs b/utils/frame/benchmarking-cli/src/block/bench.rs index 578158d8a2..c69d95725e 100644 --- a/utils/frame/benchmarking-cli/src/block/bench.rs +++ b/utils/frame/benchmarking-cli/src/block/bench.rs @@ -94,9 +94,9 @@ where let block_num = BlockId::Number(i.into()); let parent_num = BlockId::Number(((i - 1) as u32).into()); let consumed = self.consumed_weight(&block_num)?; + let hash = self.client.expect_block_hash_from_id(&block_num)?; - let block = - self.client.block(&block_num)?.ok_or(format!("Block {} not found", block_num))?; + let block = self.client.block(hash)?.ok_or(format!("Block {} not found", block_num))?; let block = self.unsealed(block.block); let took = self.measure_block(&block, &parent_num)?; From cbd8f1b56fd8ab9af0d9317432cc735264c89d70 Mon Sep 17 00:00:00 2001 From: Sergej Sakac <73715684+Szegoo@users.noreply.github.com> Date: Mon, 2 Jan 2023 11:23:33 +0100 Subject: [PATCH 090/127] Forbid stray tokens in call index (#13036) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Check for call_index * fixes * pallet ui test for weight attribute * Update frame/support/procedural/src/pallet/parse/call.rs Co-authored-by: Bastian Köcher * fix * small fix * wrong return type * ... * . * final fix * update .stderr * commit * udpate * Update frame/support/procedural/src/pallet/parse/call.rs Co-authored-by: Bastian Köcher * update .stderr Co-authored-by: Bastian Köcher --- .../procedural/src/pallet/parse/call.rs | 4 ++ .../tests/pallet_ui/call_index_has_suffix.rs | 20 +++++++++ .../pallet_ui/call_index_has_suffix.stderr | 5 +++ .../pallet_ui/weight_argument_has_suffix.rs | 21 ++++++++++ .../weight_argument_has_suffix.stderr | 41 +++++++++++++++++++ 5 files changed, 91 insertions(+) create mode 100644 frame/support/test/tests/pallet_ui/call_index_has_suffix.rs create mode 100644 frame/support/test/tests/pallet_ui/call_index_has_suffix.stderr create mode 100644 frame/support/test/tests/pallet_ui/weight_argument_has_suffix.rs create mode 100644 frame/support/test/tests/pallet_ui/weight_argument_has_suffix.stderr diff --git a/frame/support/procedural/src/pallet/parse/call.rs b/frame/support/procedural/src/pallet/parse/call.rs index 9620038edf..3af3225484 100644 --- a/frame/support/procedural/src/pallet/parse/call.rs +++ b/frame/support/procedural/src/pallet/parse/call.rs @@ -93,6 +93,10 @@ impl syn::parse::Parse for FunctionAttr { let call_index_content; syn::parenthesized!(call_index_content in content); let index = call_index_content.parse::()?; + if !index.suffix().is_empty() { + let msg = "Number literal must not have a suffix"; + return Err(syn::Error::new(index.span(), msg)) + } Ok(FunctionAttr::CallIndex(index.base10_parse()?)) } else { Err(lookahead.error()) diff --git a/frame/support/test/tests/pallet_ui/call_index_has_suffix.rs b/frame/support/test/tests/pallet_ui/call_index_has_suffix.rs new file mode 100644 index 0000000000..abe4dc199b --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_index_has_suffix.rs @@ -0,0 +1,20 @@ +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::DispatchResultWithPostInfo; + use frame_system::pallet_prelude::OriginFor; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0something)] + pub fn foo(origin: OriginFor) -> DispatchResultWithPostInfo {} + } +} + +fn main() { +} diff --git a/frame/support/test/tests/pallet_ui/call_index_has_suffix.stderr b/frame/support/test/tests/pallet_ui/call_index_has_suffix.stderr new file mode 100644 index 0000000000..2f4cead6cf --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_index_has_suffix.stderr @@ -0,0 +1,5 @@ +error: Number literal must not have a suffix + --> tests/pallet_ui/call_index_has_suffix.rs:14:30 + | +14 | #[pallet::call_index(0something)] + | ^^^^^^^^^^ diff --git a/frame/support/test/tests/pallet_ui/weight_argument_has_suffix.rs b/frame/support/test/tests/pallet_ui/weight_argument_has_suffix.rs new file mode 100644 index 0000000000..99195d21be --- /dev/null +++ b/frame/support/test/tests/pallet_ui/weight_argument_has_suffix.rs @@ -0,0 +1,21 @@ +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::DispatchResultWithPostInfo; + use frame_system::pallet_prelude::OriginFor; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(10_000something)] + pub fn foo(origin: OriginFor) -> DispatchResultWithPostInfo { Ok(().into()) } + } +} + +fn main() { +} diff --git a/frame/support/test/tests/pallet_ui/weight_argument_has_suffix.stderr b/frame/support/test/tests/pallet_ui/weight_argument_has_suffix.stderr new file mode 100644 index 0000000000..c9b2010f88 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/weight_argument_has_suffix.stderr @@ -0,0 +1,41 @@ +error: invalid suffix `something` for number literal + --> tests/pallet_ui/weight_argument_has_suffix.rs:15:26 + | +15 | #[pallet::weight(10_000something)] + | ^^^^^^^^^^^^^^^ invalid suffix `something` + | + = help: the suffix must be one of the numeric types (`u32`, `isize`, `f32`, etc.) + +error[E0308]: mismatched types + --> tests/pallet_ui/weight_argument_has_suffix.rs:12:12 + | +12 | #[pallet::call] + | ^^^^ + | | + | expected trait `frame_support::dispatch::ClassifyDispatch`, found trait `frame_support::dispatch::WeighData` + | arguments to this function are incorrect + | + = note: expected reference `&dyn frame_support::dispatch::ClassifyDispatch<()>` + found reference `&dyn frame_support::dispatch::WeighData<()>` +note: associated function defined here + --> $WORKSPACE/frame/support/src/dispatch.rs + | + | fn classify_dispatch(&self, target: T) -> DispatchClass; + | ^^^^^^^^^^^^^^^^^ + +error[E0308]: mismatched types + --> tests/pallet_ui/weight_argument_has_suffix.rs:12:12 + | +12 | #[pallet::call] + | ^^^^ + | | + | expected trait `frame_support::dispatch::PaysFee`, found trait `frame_support::dispatch::WeighData` + | arguments to this function are incorrect + | + = note: expected reference `&dyn frame_support::dispatch::PaysFee<()>` + found reference `&dyn frame_support::dispatch::WeighData<()>` +note: associated function defined here + --> $WORKSPACE/frame/support/src/dispatch.rs + | + | fn pays_fee(&self, _target: T) -> Pays; + | ^^^^^^^^ From c32968aa7c02b84b10af7986fe6d82716d804bf4 Mon Sep 17 00:00:00 2001 From: Ankan <10196091+Ank4n@users.noreply.github.com> Date: Tue, 3 Jan 2023 20:45:58 +0100 Subject: [PATCH 091/127] Use #[pallet::storage_version] for pallet staking (#12728) * delete releases * use standard pallet storage version * migrate to standard storage version for staking * not compiling * keep old releases enum around for decoding * fix releases * rename old releases * retriggering ci * fix migration comments * doc update Co-authored-by: parity-processbot <> --- frame/staking/src/lib.rs | 25 ------ frame/staking/src/migrations.rs | 138 +++++++++++++++++++++++++------- frame/staking/src/pallet/mod.rs | 14 ++-- 3 files changed, 115 insertions(+), 62 deletions(-) diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 0f5b8e0123..3672056534 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -881,31 +881,6 @@ impl Default for Forcing { } } -// A value placed in storage that represents the current version of the Staking storage. This value -// is used by the `on_runtime_upgrade` logic to determine whether we run storage migration logic. -// This should match directly with the semantic versions of the Rust crate. -#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] -enum Releases { - V1_0_0Ancient, - V2_0_0, - V3_0_0, - V4_0_0, - V5_0_0, // blockable validators. - V6_0_0, // removal of all storage associated with offchain phragmen. - V7_0_0, // keep track of number of nominators / validators in map - V8_0_0, // populate `VoterList`. - V9_0_0, // inject validators into `VoterList` as well. - V10_0_0, // remove `EarliestUnappliedSlash`. - V11_0_0, // Move pallet storage prefix, e.g. BagsList -> VoterBagsList - V12_0_0, // remove `HistoryDepth`. -} - -impl Default for Releases { - fn default() -> Self { - Releases::V11_0_0 - } -} - /// A `Convert` implementation that finds the stash of the given controller account, /// if any. pub struct StashOf(sp_std::marker::PhantomData); diff --git a/frame/staking/src/migrations.rs b/frame/staking/src/migrations.rs index f2ccb4f8b0..6253c3feed 100644 --- a/frame/staking/src/migrations.rs +++ b/frame/staking/src/migrations.rs @@ -18,7 +18,87 @@ use super::*; use frame_election_provider_support::SortedListProvider; -use frame_support::traits::OnRuntimeUpgrade; +use frame_support::{ + dispatch::GetStorageVersion, pallet_prelude::ValueQuery, storage_alias, + traits::OnRuntimeUpgrade, +}; + +/// Used for release versioning upto v12. +/// +/// Obsolete from v13. Keeping around to make encoding/decoding of old migration code easier. +#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +enum ObsoleteReleases { + V1_0_0Ancient, + V2_0_0, + V3_0_0, + V4_0_0, + V5_0_0, // blockable validators. + V6_0_0, // removal of all storage associated with offchain phragmen. + V7_0_0, // keep track of number of nominators / validators in map + V8_0_0, // populate `VoterList`. + V9_0_0, // inject validators into `VoterList` as well. + V10_0_0, // remove `EarliestUnappliedSlash`. + V11_0_0, // Move pallet storage prefix, e.g. BagsList -> VoterBagsList + V12_0_0, // remove `HistoryDepth`. +} + +impl Default for ObsoleteReleases { + fn default() -> Self { + ObsoleteReleases::V12_0_0 + } +} + +/// Alias to the old storage item used for release versioning. Obsolete since v13. +#[storage_alias] +type StorageVersion = StorageValue, ObsoleteReleases, ValueQuery>; + +pub mod v13 { + use super::*; + + pub struct MigrateToV13(sp_std::marker::PhantomData); + impl OnRuntimeUpgrade for MigrateToV13 { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, &'static str> { + frame_support::ensure!( + StorageVersion::::get() == ObsoleteReleases::V12_0_0, + "Required v12 before upgrading to v13" + ); + + Ok(Default::default()) + } + + fn on_runtime_upgrade() -> Weight { + let current = Pallet::::current_storage_version(); + let onchain = StorageVersion::::get(); + + if current == 13 && onchain == ObsoleteReleases::V12_0_0 { + StorageVersion::::kill(); + current.put::>(); + + log!(info, "v13 applied successfully"); + T::DbWeight::get().reads_writes(1, 2) + } else { + log!(warn, "Skipping v13, should be removed"); + T::DbWeight::get().reads(1) + } + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_state: Vec) -> Result<(), &'static str> { + frame_support::ensure!( + Pallet::::on_chain_storage_version() == 13, + "v13 not applied" + ); + + frame_support::ensure!( + !StorageVersion::::exists(), + "Storage version not migrated correctly" + ); + + Ok(()) + } + } +} pub mod v12 { use super::*; @@ -36,7 +116,7 @@ pub mod v12 { #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, &'static str> { frame_support::ensure!( - StorageVersion::::get() == Releases::V11_0_0, + StorageVersion::::get() == ObsoleteReleases::V11_0_0, "Expected v11 before upgrading to v12" ); @@ -53,9 +133,9 @@ pub mod v12 { } fn on_runtime_upgrade() -> frame_support::weights::Weight { - if StorageVersion::::get() == Releases::V11_0_0 { + if StorageVersion::::get() == ObsoleteReleases::V11_0_0 { HistoryDepth::::kill(); - StorageVersion::::put(Releases::V12_0_0); + StorageVersion::::put(ObsoleteReleases::V12_0_0); log!(info, "v12 applied successfully"); T::DbWeight::get().reads_writes(1, 2) @@ -68,7 +148,7 @@ pub mod v12 { #[cfg(feature = "try-runtime")] fn post_upgrade(_state: Vec) -> Result<(), &'static str> { frame_support::ensure!( - StorageVersion::::get() == crate::Releases::V12_0_0, + StorageVersion::::get() == ObsoleteReleases::V12_0_0, "v12 not applied" ); Ok(()) @@ -92,7 +172,7 @@ pub mod v11 { #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, &'static str> { frame_support::ensure!( - StorageVersion::::get() == crate::Releases::V10_0_0, + StorageVersion::::get() == ObsoleteReleases::V10_0_0, "must upgrade linearly" ); let old_pallet_prefix = twox_128(N::get().as_bytes()); @@ -117,9 +197,9 @@ pub mod v11 { let old_pallet_name = N::get(); let new_pallet_name =

::name(); - if StorageVersion::::get() == Releases::V10_0_0 { + if StorageVersion::::get() == ObsoleteReleases::V10_0_0 { // bump version anyway, even if we don't need to move the prefix - StorageVersion::::put(Releases::V11_0_0); + StorageVersion::::put(ObsoleteReleases::V11_0_0); if new_pallet_name == old_pallet_name { log!( warn, @@ -139,7 +219,7 @@ pub mod v11 { #[cfg(feature = "try-runtime")] fn post_upgrade(_state: Vec) -> Result<(), &'static str> { frame_support::ensure!( - StorageVersion::::get() == crate::Releases::V11_0_0, + StorageVersion::::get() == ObsoleteReleases::V11_0_0, "wrong version after the upgrade" ); @@ -184,7 +264,7 @@ pub mod v10 { pub struct MigrateToV10(sp_std::marker::PhantomData); impl OnRuntimeUpgrade for MigrateToV10 { fn on_runtime_upgrade() -> frame_support::weights::Weight { - if StorageVersion::::get() == Releases::V9_0_0 { + if StorageVersion::::get() == ObsoleteReleases::V9_0_0 { let pending_slashes = as Store>::UnappliedSlashes::iter().take(512); for (era, slashes) in pending_slashes { for slash in slashes { @@ -196,7 +276,7 @@ pub mod v10 { } EarliestUnappliedSlash::::kill(); - StorageVersion::::put(Releases::V10_0_0); + StorageVersion::::put(ObsoleteReleases::V10_0_0); log!(info, "MigrateToV10 executed successfully"); T::DbWeight::get().reads_writes(1, 1) @@ -221,7 +301,7 @@ pub mod v9 { pub struct InjectValidatorsIntoVoterList(sp_std::marker::PhantomData); impl OnRuntimeUpgrade for InjectValidatorsIntoVoterList { fn on_runtime_upgrade() -> Weight { - if StorageVersion::::get() == Releases::V8_0_0 { + if StorageVersion::::get() == ObsoleteReleases::V8_0_0 { let prev_count = T::VoterList::count(); let weight_of_cached = Pallet::::weight_of_fn(); for (v, _) in Validators::::iter() { @@ -239,13 +319,13 @@ pub mod v9 { T::VoterList::count(), ); - StorageVersion::::put(crate::Releases::V9_0_0); + StorageVersion::::put(ObsoleteReleases::V9_0_0); T::BlockWeights::get().max_block } else { log!( warn, "InjectValidatorsIntoVoterList being executed on the wrong storage \ - version, expected Releases::V8_0_0" + version, expected ObsoleteReleases::V8_0_0" ); T::DbWeight::get().reads(1) } @@ -254,7 +334,7 @@ pub mod v9 { #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, &'static str> { frame_support::ensure!( - StorageVersion::::get() == crate::Releases::V8_0_0, + StorageVersion::::get() == ObsoleteReleases::V8_0_0, "must upgrade linearly" ); @@ -272,7 +352,7 @@ pub mod v9 { assert!(post_count == prev_count + validators); frame_support::ensure!( - StorageVersion::::get() == crate::Releases::V9_0_0, + StorageVersion::::get() == ObsoleteReleases::V9_0_0, "must upgrade " ); Ok(()) @@ -281,14 +361,15 @@ pub mod v9 { } pub mod v8 { - use crate::{Config, Nominators, Pallet, StorageVersion, Weight}; + use super::*; + use crate::{Config, Nominators, Pallet, Weight}; use frame_election_provider_support::SortedListProvider; use frame_support::traits::Get; #[cfg(feature = "try-runtime")] pub fn pre_migrate() -> Result<(), &'static str> { frame_support::ensure!( - StorageVersion::::get() == crate::Releases::V7_0_0, + StorageVersion::::get() == ObsoleteReleases::V7_0_0, "must upgrade linearly" ); @@ -298,8 +379,8 @@ pub mod v8 { /// Migration to sorted `VoterList`. pub fn migrate() -> Weight { - if StorageVersion::::get() == crate::Releases::V7_0_0 { - crate::log!(info, "migrating staking to Releases::V8_0_0"); + if StorageVersion::::get() == ObsoleteReleases::V7_0_0 { + crate::log!(info, "migrating staking to ObsoleteReleases::V8_0_0"); let migrated = T::VoterList::unsafe_regenerate( Nominators::::iter().map(|(id, _)| id), @@ -307,10 +388,10 @@ pub mod v8 { ); debug_assert_eq!(T::VoterList::try_state(), Ok(())); - StorageVersion::::put(crate::Releases::V8_0_0); + StorageVersion::::put(ObsoleteReleases::V8_0_0); crate::log!( info, - "👜 completed staking migration to Releases::V8_0_0 with {} voters migrated", + "👜 completed staking migration to ObsoleteReleases::V8_0_0 with {} voters migrated", migrated, ); @@ -348,20 +429,20 @@ pub mod v7 { ); assert!(Validators::::count().is_zero(), "Validators already set."); assert!(Nominators::::count().is_zero(), "Nominators already set."); - assert!(StorageVersion::::get() == Releases::V6_0_0); + assert!(StorageVersion::::get() == ObsoleteReleases::V6_0_0); Ok(()) } pub fn migrate() -> Weight { - log!(info, "Migrating staking to Releases::V7_0_0"); + log!(info, "Migrating staking to ObsoleteReleases::V7_0_0"); let validator_count = Validators::::iter().count() as u32; let nominator_count = Nominators::::iter().count() as u32; CounterForValidators::::put(validator_count); CounterForNominators::::put(nominator_count); - StorageVersion::::put(Releases::V7_0_0); - log!(info, "Completed staking migration to Releases::V7_0_0"); + StorageVersion::::put(ObsoleteReleases::V7_0_0); + log!(info, "Completed staking migration to ObsoleteReleases::V7_0_0"); T::DbWeight::get().reads_writes(validator_count.saturating_add(nominator_count).into(), 2) } @@ -403,7 +484,7 @@ pub mod v6 { /// Migrate storage to v6. pub fn migrate() -> Weight { - log!(info, "Migrating staking to Releases::V6_0_0"); + log!(info, "Migrating staking to ObsoleteReleases::V6_0_0"); SnapshotValidators::::kill(); SnapshotNominators::::kill(); @@ -412,7 +493,8 @@ pub mod v6 { EraElectionStatus::::kill(); IsCurrentSessionFinal::::kill(); - StorageVersion::::put(Releases::V6_0_0); + StorageVersion::::put(ObsoleteReleases::V6_0_0); + log!(info, "Done."); T::DbWeight::get().writes(6 + 1) } diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 8e8a8d9c7f..92502949ef 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -46,7 +46,7 @@ pub use impls::*; use crate::{ slashing, weights::WeightInfo, AccountIdLookupOf, ActiveEraInfo, BalanceOf, EraPayout, EraRewardPoints, Exposure, Forcing, NegativeImbalanceOf, Nominations, PositiveImbalanceOf, - Releases, RewardDestination, SessionInterface, StakingLedger, UnappliedSlash, UnlockChunk, + RewardDestination, SessionInterface, StakingLedger, UnappliedSlash, UnlockChunk, ValidatorPrefs, }; @@ -64,8 +64,12 @@ pub mod pallet { use super::*; + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(13); + #[pallet::pallet] #[pallet::generate_store(pub(crate) trait Store)] + #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); /// Possible operations on the configuration values of this pallet. @@ -561,13 +565,6 @@ pub mod pallet { #[pallet::getter(fn offending_validators)] pub type OffendingValidators = StorageValue<_, Vec<(u32, bool)>, ValueQuery>; - /// True if network has been upgraded to this version. - /// Storage version of the pallet. - /// - /// This is set to v7.0.0 for new networks. - #[pallet::storage] - pub(crate) type StorageVersion = StorageValue<_, Releases, ValueQuery>; - /// The threshold for when users can start calling `chill_other` for other validators / /// nominators. The threshold is compared to the actual number of validators / nominators /// (`CountFor*`) in the system compared to the configured max (`Max*Count`). @@ -618,7 +615,6 @@ pub mod pallet { ForceEra::::put(self.force_era); CanceledSlashPayout::::put(self.canceled_payout); SlashRewardFraction::::put(self.slash_reward_fraction); - StorageVersion::::put(Releases::V7_0_0); MinNominatorBond::::put(self.min_nominator_bond); MinValidatorBond::::put(self.min_validator_bond); if let Some(x) = self.max_validator_count { From e22166fa0d40342d3b373aeca0a33abc782b152c Mon Sep 17 00:00:00 2001 From: Liu-Cheng Xu Date: Wed, 4 Jan 2023 06:00:10 +0800 Subject: [PATCH 092/127] Enrich TooManySiblingBlocks error info (#13052) --- client/state-db/src/lib.rs | 5 +++-- client/state-db/src/noncanonical.rs | 7 ++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/client/state-db/src/lib.rs b/client/state-db/src/lib.rs index 5e01a0e063..961f641a1a 100644 --- a/client/state-db/src/lib.rs +++ b/client/state-db/src/lib.rs @@ -134,7 +134,7 @@ pub enum StateDbError { /// Invalid pruning mode specified. Contains expected mode. IncompatiblePruningModes { stored: PruningMode, requested: PruningMode }, /// Too many unfinalized sibling blocks inserted. - TooManySiblingBlocks, + TooManySiblingBlocks { number: u64 }, /// Trying to insert existing block. BlockAlreadyExists, /// Invalid metadata @@ -184,7 +184,8 @@ impl fmt::Debug for StateDbError { "Incompatible pruning modes [stored: {:?}; requested: {:?}]", stored, requested ), - Self::TooManySiblingBlocks => write!(f, "Too many sibling blocks inserted"), + Self::TooManySiblingBlocks { number } => + write!(f, "Too many sibling blocks at #{number} inserted"), Self::BlockAlreadyExists => write!(f, "Block already exists"), Self::Metadata(message) => write!(f, "Invalid metadata: {}", message), Self::BlockUnavailable => diff --git a/client/state-db/src/noncanonical.rs b/client/state-db/src/noncanonical.rs index 84ba94c052..b7eec8ab60 100644 --- a/client/state-db/src/noncanonical.rs +++ b/client/state-db/src/noncanonical.rs @@ -283,7 +283,12 @@ impl NonCanonicalOverlay { }; if level.blocks.len() >= MAX_BLOCKS_PER_LEVEL as usize { - return Err(StateDbError::TooManySiblingBlocks) + trace!( + target: "state-db", + "Too many sibling blocks at #{number}: {:?}", + level.blocks.iter().map(|b| &b.hash).collect::>() + ); + return Err(StateDbError::TooManySiblingBlocks { number }) } if level.blocks.iter().any(|b| b.hash == *hash) { return Err(StateDbError::BlockAlreadyExists) From 653eb0dd6f1eec87bc0443431df1d36700601257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Tue, 3 Jan 2023 23:00:34 +0100 Subject: [PATCH 093/127] Do not run `forced_canonicalization` for archive nodes (#13051) We don't canonicalize on archive nodes and thus `best_canonical` always returned `None`. So, the moment such a node tried to force canonicalize, it was trapped in some endless loop. This pr solves this by renaming `best_canonical` to `last_canonicalized` and also making the return value more clear by introducing a custom enum `LastCanonicalized`. --- client/db/src/lib.rs | 270 +++++++++++++++++++++++++------------ client/state-db/src/lib.rs | 36 ++++- 2 files changed, 216 insertions(+), 90 deletions(-) diff --git a/client/db/src/lib.rs b/client/db/src/lib.rs index 3e6332732d..c8495c4a02 100644 --- a/client/db/src/lib.rs +++ b/client/db/src/lib.rs @@ -63,7 +63,7 @@ use sc_client_api::{ utils::is_descendent_of, IoInfo, MemoryInfo, MemorySize, UsageInfo, }; -use sc_state_db::{IsPruned, StateDb}; +use sc_state_db::{IsPruned, LastCanonicalized, StateDb}; use sp_arithmetic::traits::Saturating; use sp_blockchain::{ well_known_cache_keys, Backend as _, CachedHeaderMetadata, Error as ClientError, HeaderBackend, @@ -1315,19 +1315,25 @@ impl Backend { &self, transaction: &mut Transaction, ) -> ClientResult<()> { - let best_canonical = || self.storage.state_db.best_canonical().unwrap_or(0); + let best_canonical = match self.storage.state_db.last_canonicalized() { + LastCanonicalized::None => 0, + LastCanonicalized::Block(b) => b, + // Nothing needs to be done when canonicalization is not happening. + LastCanonicalized::NotCanonicalizing => return Ok(()), + }; + let info = self.blockchain.info(); let best_number: u64 = self.blockchain.info().best_number.saturated_into(); - while best_number.saturating_sub(best_canonical()) > self.canonicalization_delay { - let to_canonicalize = best_canonical() + 1; - + for to_canonicalize in + best_canonical + 1..=best_number.saturating_sub(self.canonicalization_delay) + { let hash_to_canonicalize = sc_client_api::blockchain::HeaderBackend::hash( &self.blockchain, to_canonicalize.saturated_into(), )? .ok_or_else(|| { - let best_hash = info.best_hash; + let best_hash = info.best_hash; sp_blockchain::Error::Backend(format!( "Can't canonicalize missing block number #{to_canonicalize} when for best block {best_hash:?} (#{best_number})", @@ -1697,13 +1703,13 @@ impl Backend { } transaction.set_from_vec(columns::META, meta_keys::FINALIZED_BLOCK, lookup_key); - if sc_client_api::Backend::have_state_at(self, f_hash, f_num) && - self.storage - .state_db - .best_canonical() - .map(|c| f_num.saturated_into::() > c) - .unwrap_or(true) - { + let requires_canonicalization = match self.storage.state_db.last_canonicalized() { + LastCanonicalized::None => true, + LastCanonicalized::Block(b) => f_num.saturated_into::() > b, + LastCanonicalized::NotCanonicalizing => false, + }; + + if requires_canonicalization && sc_client_api::Backend::have_state_at(self, f_hash, f_num) { let commit = self.storage.state_db.canonicalize_block(&f_hash).map_err( sp_blockchain::Error::from_state_db::< sc_state_db::Error, @@ -3824,102 +3830,198 @@ pub(crate) mod tests { #[test] fn force_delayed_canonicalize_waiting_for_blocks_to_be_finalized() { - let backend = Backend::::new_test(10, 1); + let pruning_modes = [BlocksPruning::Some(10), BlocksPruning::KeepAll]; - let genesis = - insert_block(&backend, 0, Default::default(), None, Default::default(), vec![], None) + for pruning_mode in pruning_modes { + eprintln!("Running with pruning mode: {:?}", pruning_mode); + + let backend = Backend::::new_test_with_tx_storage(pruning_mode, 1); + + let genesis = insert_block( + &backend, + 0, + Default::default(), + None, + Default::default(), + vec![], + None, + ) + .unwrap(); + + let block1 = { + let mut op = backend.begin_operation().unwrap(); + backend.begin_state_operation(&mut op, genesis).unwrap(); + let mut header = Header { + number: 1, + parent_hash: genesis, + state_root: Default::default(), + digest: Default::default(), + extrinsics_root: Default::default(), + }; + + let storage = vec![(vec![1, 3, 5], None), (vec![5, 5, 5], Some(vec![4, 5, 6]))]; + + let (root, overlay) = op.old_state.storage_root( + storage.iter().map(|(k, v)| (k.as_slice(), v.as_ref().map(|v| &v[..]))), + StateVersion::V1, + ); + op.update_db_storage(overlay).unwrap(); + header.state_root = root.into(); + + op.update_storage(storage, Vec::new()).unwrap(); + + op.set_block_data( + header.clone(), + Some(Vec::new()), + None, + None, + NewBlockState::Normal, + ) .unwrap(); - let block1 = { - let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, genesis).unwrap(); - let header = Header { - number: 1, - parent_hash: genesis, - state_root: BlakeTwo256::trie_root(Vec::new(), StateVersion::V1), - digest: Default::default(), - extrinsics_root: Default::default(), + backend.commit_operation(op).unwrap(); + + header.hash() }; - op.set_block_data(header.clone(), Some(Vec::new()), None, None, NewBlockState::Normal) - .unwrap(); + if matches!(pruning_mode, BlocksPruning::Some(_)) { + assert_eq!( + LastCanonicalized::Block(0), + backend.storage.state_db.last_canonicalized() + ); + } - backend.commit_operation(op).unwrap(); + // This should not trigger any forced canonicalization as we didn't have imported any + // best block by now. + let block2 = { + let mut op = backend.begin_operation().unwrap(); + backend.begin_state_operation(&mut op, block1).unwrap(); + let mut header = Header { + number: 2, + parent_hash: block1, + state_root: Default::default(), + digest: Default::default(), + extrinsics_root: Default::default(), + }; - header.hash() - }; + let storage = vec![(vec![5, 5, 5], Some(vec![4, 5, 6, 2]))]; - assert_eq!(0, backend.storage.state_db.best_canonical().unwrap()); + let (root, overlay) = op.old_state.storage_root( + storage.iter().map(|(k, v)| (k.as_slice(), v.as_ref().map(|v| &v[..]))), + StateVersion::V1, + ); + op.update_db_storage(overlay).unwrap(); + header.state_root = root.into(); - // This should not trigger any forced canonicalization as we didn't have imported any best - // block by now. - let block2 = { - let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, block1).unwrap(); - let header = Header { - number: 2, - parent_hash: block1, - state_root: BlakeTwo256::trie_root(Vec::new(), StateVersion::V1), - digest: Default::default(), - extrinsics_root: Default::default(), - }; + op.update_storage(storage, Vec::new()).unwrap(); - op.set_block_data(header.clone(), Some(Vec::new()), None, None, NewBlockState::Normal) + op.set_block_data( + header.clone(), + Some(Vec::new()), + None, + None, + NewBlockState::Normal, + ) .unwrap(); - backend.commit_operation(op).unwrap(); + backend.commit_operation(op).unwrap(); - header.hash() - }; + header.hash() + }; - assert_eq!(0, backend.storage.state_db.best_canonical().unwrap()); + if matches!(pruning_mode, BlocksPruning::Some(_)) { + assert_eq!( + LastCanonicalized::Block(0), + backend.storage.state_db.last_canonicalized() + ); + } - // This should also not trigger it yet, because we import a best block, but the best block - // from the POV of the db is still at `0`. - let block3 = { - let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, block2).unwrap(); - let header = Header { - number: 3, - parent_hash: block2, - state_root: BlakeTwo256::trie_root(Vec::new(), StateVersion::V1), - digest: Default::default(), - extrinsics_root: Default::default(), - }; + // This should also not trigger it yet, because we import a best block, but the best + // block from the POV of the db is still at `0`. + let block3 = { + let mut op = backend.begin_operation().unwrap(); + backend.begin_state_operation(&mut op, block2).unwrap(); + let mut header = Header { + number: 3, + parent_hash: block2, + state_root: Default::default(), + digest: Default::default(), + extrinsics_root: Default::default(), + }; - op.set_block_data(header.clone(), Some(Vec::new()), None, None, NewBlockState::Best) - .unwrap(); + let storage = vec![(vec![5, 5, 5], Some(vec![4, 5, 6, 3]))]; - backend.commit_operation(op).unwrap(); + let (root, overlay) = op.old_state.storage_root( + storage.iter().map(|(k, v)| (k.as_slice(), v.as_ref().map(|v| &v[..]))), + StateVersion::V1, + ); + op.update_db_storage(overlay).unwrap(); + header.state_root = root.into(); - header.hash() - }; + op.update_storage(storage, Vec::new()).unwrap(); - // Now it should kick in. - let block4 = { - let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, block3).unwrap(); - let header = Header { - number: 4, - parent_hash: block3, - state_root: BlakeTwo256::trie_root(Vec::new(), StateVersion::V1), - digest: Default::default(), - extrinsics_root: Default::default(), + op.set_block_data( + header.clone(), + Some(Vec::new()), + None, + None, + NewBlockState::Best, + ) + .unwrap(); + + backend.commit_operation(op).unwrap(); + + header.hash() }; - op.set_block_data(header.clone(), Some(Vec::new()), None, None, NewBlockState::Best) + // Now it should kick in. + let block4 = { + let mut op = backend.begin_operation().unwrap(); + backend.begin_state_operation(&mut op, block3).unwrap(); + let mut header = Header { + number: 4, + parent_hash: block3, + state_root: Default::default(), + digest: Default::default(), + extrinsics_root: Default::default(), + }; + + let storage = vec![(vec![5, 5, 5], Some(vec![4, 5, 6, 4]))]; + + let (root, overlay) = op.old_state.storage_root( + storage.iter().map(|(k, v)| (k.as_slice(), v.as_ref().map(|v| &v[..]))), + StateVersion::V1, + ); + op.update_db_storage(overlay).unwrap(); + header.state_root = root.into(); + + op.update_storage(storage, Vec::new()).unwrap(); + + op.set_block_data( + header.clone(), + Some(Vec::new()), + None, + None, + NewBlockState::Best, + ) .unwrap(); - backend.commit_operation(op).unwrap(); + backend.commit_operation(op).unwrap(); - header.hash() - }; + header.hash() + }; - assert_eq!(2, backend.storage.state_db.best_canonical().unwrap()); + if matches!(pruning_mode, BlocksPruning::Some(_)) { + assert_eq!( + LastCanonicalized::Block(2), + backend.storage.state_db.last_canonicalized() + ); + } - assert_eq!(block1, backend.blockchain().hash(1).unwrap().unwrap()); - assert_eq!(block2, backend.blockchain().hash(2).unwrap().unwrap()); - assert_eq!(block3, backend.blockchain().hash(3).unwrap().unwrap()); - assert_eq!(block4, backend.blockchain().hash(4).unwrap().unwrap()); + assert_eq!(block1, backend.blockchain().hash(1).unwrap().unwrap()); + assert_eq!(block2, backend.blockchain().hash(2).unwrap().unwrap()); + assert_eq!(block3, backend.blockchain().hash(3).unwrap().unwrap()); + assert_eq!(block4, backend.blockchain().hash(4).unwrap().unwrap()); + } } } diff --git a/client/state-db/src/lib.rs b/client/state-db/src/lib.rs index 961f641a1a..1befd6dff3 100644 --- a/client/state-db/src/lib.rs +++ b/client/state-db/src/lib.rs @@ -283,6 +283,17 @@ fn to_meta_key(suffix: &[u8], data: &S) -> Vec { buffer } +/// Status information about the last canonicalized block. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum LastCanonicalized { + /// Not yet have canonicalized any block. + None, + /// The given block number is the last canonicalized block. + Block(u64), + /// No canonicalization is happening (pruning mode is archive all). + NotCanonicalizing, +} + pub struct StateDbSync { mode: PruningMode, non_canonical: NonCanonicalOverlay, @@ -349,15 +360,28 @@ impl StateDbSync { Ok(commit) } - fn best_canonical(&self) -> Option { - self.non_canonical.last_canonicalized_block_number() + /// Returns the block number of the last canonicalized block. + fn last_canonicalized(&self) -> LastCanonicalized { + if self.mode == PruningMode::ArchiveAll { + LastCanonicalized::NotCanonicalizing + } else { + self.non_canonical + .last_canonicalized_block_number() + .map(LastCanonicalized::Block) + .unwrap_or_else(|| LastCanonicalized::None) + } } fn is_pruned(&self, hash: &BlockHash, number: u64) -> IsPruned { match self.mode { PruningMode::ArchiveAll => IsPruned::NotPruned, PruningMode::ArchiveCanonical | PruningMode::Constrained(_) => { - if self.best_canonical().map(|c| number > c).unwrap_or(true) { + if self + .non_canonical + .last_canonicalized_block_number() + .map(|c| number > c) + .unwrap_or(true) + { if self.non_canonical.have_block(hash) { IsPruned::NotPruned } else { @@ -611,9 +635,9 @@ impl StateDb { self.db.write().remove(hash) } - /// Returns last finalized block number. - pub fn best_canonical(&self) -> Option { - return self.db.read().best_canonical() + /// Returns last canonicalized block. + pub fn last_canonicalized(&self) -> LastCanonicalized { + self.db.read().last_canonicalized() } /// Check if block is pruned away. From 6e52d60931e4398b8a974a0966821f5e27481b8b Mon Sep 17 00:00:00 2001 From: cuteolaf <53915161+cuteolaf@users.noreply.github.com> Date: Tue, 3 Jan 2023 14:41:31 -0800 Subject: [PATCH 094/127] remove unused constant: MaxValueSize (#12839) --- bin/node/runtime/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 0c946fa180..90a52ab697 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1181,7 +1181,6 @@ impl pallet_tips::Config for Runtime { parameter_types! { pub const DepositPerItem: Balance = deposit(1, 0); pub const DepositPerByte: Balance = deposit(0, 1); - pub const MaxValueSize: u32 = 16 * 1024; pub const DeletionQueueDepth: u32 = 128; // The lazy deletion runs inside on_initialize. pub DeletionWeightLimit: Weight = RuntimeBlockWeights::get() From 18ead61a6265e6fdfccd17e5c60250432071fb53 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Wed, 4 Jan 2023 10:30:12 +0200 Subject: [PATCH 095/127] Fix pallet's integration (#13056) --- frame/nfts/Cargo.toml | 2 ++ frame/nfts/src/lib.rs | 2 ++ frame/nfts/src/types.rs | 16 ++++++++-------- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/frame/nfts/Cargo.toml b/frame/nfts/Cargo.toml index 109dffdd10..d2cdb48532 100644 --- a/frame/nfts/Cargo.toml +++ b/frame/nfts/Cargo.toml @@ -20,6 +20,7 @@ scale-info = { version = "2.1.1", default-features = false, features = ["derive" frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } @@ -38,6 +39,7 @@ std = [ "frame-system/std", "log/std", "scale-info/std", + "sp-core/std", "sp-runtime/std", "sp-std/std", ] diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 2006d78959..dbd2c20481 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -1767,3 +1767,5 @@ pub mod pallet { } } } + +sp_core::generate_feature_enabled_macro!(runtime_benchmarks_enabled, feature = "runtime-benchmarks", $); diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index 58b1acaaed..9b89fb964b 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -272,15 +272,15 @@ pub enum MintType { #[derive(Clone, Copy, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct MintSettings { /// Whether anyone can mint or if minters are restricted to some subset. - pub(super) mint_type: MintType, + pub mint_type: MintType, /// An optional price per mint. - pub(super) price: Option, + pub price: Option, /// When the mint starts. - pub(super) start_block: Option, + pub start_block: Option, /// When the mint ends. - pub(super) end_block: Option, + pub end_block: Option, /// Default settings each item will get during the mint. - pub(super) default_item_settings: ItemSettings, + pub default_item_settings: ItemSettings, } impl Default for MintSettings { @@ -315,11 +315,11 @@ pub enum PalletAttributes { )] pub struct CollectionConfig { /// Collection's settings. - pub(super) settings: CollectionSettings, + pub settings: CollectionSettings, /// Collection's max supply. - pub(super) max_supply: Option, + pub max_supply: Option, /// Default settings each item will get during the mint. - pub(super) mint_settings: MintSettings, + pub mint_settings: MintSettings, } impl CollectionConfig { From 296772699280a07cc3512ebaf9e199cebbea651c Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Wed, 4 Jan 2023 10:30:43 +0200 Subject: [PATCH 096/127] Disallow burning externally locked nfts (#13054) * Disallow burning externally locked nfts * Update docs --- frame/nfts/src/features/create_delete_item.rs | 1 + frame/nfts/src/lib.rs | 4 +++- frame/uniques/src/functions.rs | 1 + frame/uniques/src/lib.rs | 4 +++- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/frame/nfts/src/features/create_delete_item.rs b/frame/nfts/src/features/create_delete_item.rs index 7fd745b2bf..8d6c8a2809 100644 --- a/frame/nfts/src/features/create_delete_item.rs +++ b/frame/nfts/src/features/create_delete_item.rs @@ -91,6 +91,7 @@ impl, I: 'static> Pallet { item: T::ItemId, with_details: impl FnOnce(&ItemDetailsFor) -> DispatchResult, ) -> DispatchResult { + ensure!(!T::Locker::is_locked(collection, item), Error::::ItemLocked); let owner = Collection::::try_mutate( &collection, |maybe_collection_details| -> Result { diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index dbd2c20481..9a6242143a 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -856,7 +856,9 @@ pub mod pallet { /// Destroy a single item. /// - /// Origin must be Signed and the sender should be the Admin of the `collection`. + /// Origin must be Signed and the signing account must be either: + /// - the Admin of the `collection`; + /// - the Owner of the `item`; /// /// - `collection`: The collection of the item to be burned. /// - `item`: The item to be burned. diff --git a/frame/uniques/src/functions.rs b/frame/uniques/src/functions.rs index 4e68f11394..03dba16eaa 100644 --- a/frame/uniques/src/functions.rs +++ b/frame/uniques/src/functions.rs @@ -189,6 +189,7 @@ impl, I: 'static> Pallet { item: T::ItemId, with_details: impl FnOnce(&CollectionDetailsFor, &ItemDetailsFor) -> DispatchResult, ) -> DispatchResult { + ensure!(!T::Locker::is_locked(collection, item), Error::::Locked); let owner = Collection::::try_mutate( &collection, |maybe_collection_details| -> Result { diff --git a/frame/uniques/src/lib.rs b/frame/uniques/src/lib.rs index 8157817d41..6b21477be4 100644 --- a/frame/uniques/src/lib.rs +++ b/frame/uniques/src/lib.rs @@ -577,7 +577,9 @@ pub mod pallet { /// Destroy a single item. /// - /// Origin must be Signed and the sender should be the Admin of the `collection`. + /// Origin must be Signed and the signing account must be either: + /// - the Admin of the `collection`; + /// - the Owner of the `item`; /// /// - `collection`: The collection of the item to be burned. /// - `item`: The item of the item to be burned. From d1fe09c801b3d7312f01b04f3204b1d0ec76db87 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Wed, 4 Jan 2023 10:31:24 +0200 Subject: [PATCH 097/127] Allow to clear attributes via traits (#13055) --- frame/nfts/src/impl_nonfungibles.rs | 43 +++++++++++++++++++ frame/nfts/src/tests.rs | 16 +++++++ .../src/traits/tokens/nonfungible_v2.rs | 24 +++++++++++ .../src/traits/tokens/nonfungibles_v2.rs | 39 +++++++++++++++++ 4 files changed, 122 insertions(+) diff --git a/frame/nfts/src/impl_nonfungibles.rs b/frame/nfts/src/impl_nonfungibles.rs index edfc29710b..ab4d7a16ec 100644 --- a/frame/nfts/src/impl_nonfungibles.rs +++ b/frame/nfts/src/impl_nonfungibles.rs @@ -237,6 +237,49 @@ impl, I: 'static> Mutate<::AccountId, ItemConfig }) }) } + + fn clear_attribute( + collection: &Self::CollectionId, + item: &Self::ItemId, + key: &[u8], + ) -> DispatchResult { + Self::do_clear_attribute( + None, + *collection, + Some(*item), + AttributeNamespace::Pallet, + Self::construct_attribute_key(key.to_vec())?, + ) + } + + fn clear_typed_attribute( + collection: &Self::CollectionId, + item: &Self::ItemId, + key: &K, + ) -> DispatchResult { + key.using_encoded(|k| { + >::clear_attribute(collection, item, k) + }) + } + + fn clear_collection_attribute(collection: &Self::CollectionId, key: &[u8]) -> DispatchResult { + Self::do_clear_attribute( + None, + *collection, + None, + AttributeNamespace::Pallet, + Self::construct_attribute_key(key.to_vec())?, + ) + } + + fn clear_typed_collection_attribute( + collection: &Self::CollectionId, + key: &K, + ) -> DispatchResult { + key.using_encoded(|k| { + >::clear_collection_attribute(collection, k) + }) + } } impl, I: 'static> Transfer for Pallet { diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index 18a3fd83b4..f8187b3777 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -1012,6 +1012,22 @@ fn validate_deposit_required_setting() { assert_eq!(Balances::reserved_balance(1), 0); assert_eq!(Balances::reserved_balance(2), 3); assert_eq!(Balances::reserved_balance(3), 3); + + assert_ok!( + ::AccountId, ItemConfig>>::clear_attribute( + &0, + &0, + &[3], + ) + ); + assert_eq!( + attributes(0), + vec![ + (Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![0]), + (Some(0), AttributeNamespace::ItemOwner, bvec![1], bvec![0]), + (Some(0), AttributeNamespace::Account(3), bvec![2], bvec![0]), + ] + ); }); } diff --git a/frame/support/src/traits/tokens/nonfungible_v2.rs b/frame/support/src/traits/tokens/nonfungible_v2.rs index a1b75e62e4..e1899020bb 100644 --- a/frame/support/src/traits/tokens/nonfungible_v2.rs +++ b/frame/support/src/traits/tokens/nonfungible_v2.rs @@ -127,6 +127,20 @@ pub trait Mutate: Inspect { ) -> DispatchResult { key.using_encoded(|k| value.using_encoded(|v| Self::set_attribute(item, k, v))) } + + /// Clear attribute of `item`'s `key`. + /// + /// By default, this is not a supported operation. + fn clear_attribute(_item: &Self::ItemId, _key: &[u8]) -> DispatchResult { + Err(TokenError::Unsupported.into()) + } + + /// Attempt to clear the strongly-typed attribute of `item`'s `key`. + /// + /// By default this just attempts to use `clear_attribute`. + fn clear_typed_attribute(item: &Self::ItemId, key: &K) -> DispatchResult { + key.using_encoded(|k| Self::clear_attribute(item, k)) + } } /// Trait for transferring a non-fungible item. @@ -234,6 +248,16 @@ impl< value, ) } + fn clear_attribute(item: &Self::ItemId, key: &[u8]) -> DispatchResult { + >::clear_attribute(&A::get(), item, key) + } + fn clear_typed_attribute(item: &Self::ItemId, key: &K) -> DispatchResult { + >::clear_typed_attribute( + &A::get(), + item, + key, + ) + } } impl< diff --git a/frame/support/src/traits/tokens/nonfungibles_v2.rs b/frame/support/src/traits/tokens/nonfungibles_v2.rs index d2f5f5529f..17c5d887de 100644 --- a/frame/support/src/traits/tokens/nonfungibles_v2.rs +++ b/frame/support/src/traits/tokens/nonfungibles_v2.rs @@ -244,6 +244,45 @@ pub trait Mutate: Inspect { value.using_encoded(|v| Self::set_collection_attribute(collection, k, v)) }) } + + /// Clear attribute of `item` of `collection`'s `key`. + /// + /// By default, this is not a supported operation. + fn clear_attribute( + _collection: &Self::CollectionId, + _item: &Self::ItemId, + _key: &[u8], + ) -> DispatchResult { + Err(TokenError::Unsupported.into()) + } + + /// Attempt to clear the strongly-typed attribute of `item` of `collection`'s `key`. + /// + /// By default this just attempts to use `clear_attribute`. + fn clear_typed_attribute( + collection: &Self::CollectionId, + item: &Self::ItemId, + key: &K, + ) -> DispatchResult { + key.using_encoded(|k| Self::clear_attribute(collection, item, k)) + } + + /// Clear attribute of `collection`'s `key`. + /// + /// By default, this is not a supported operation. + fn clear_collection_attribute(_collection: &Self::CollectionId, _key: &[u8]) -> DispatchResult { + Err(TokenError::Unsupported.into()) + } + + /// Attempt to clear the strongly-typed attribute of `collection`'s `key`. + /// + /// By default this just attempts to use `clear_attribute`. + fn clear_typed_collection_attribute( + collection: &Self::CollectionId, + key: &K, + ) -> DispatchResult { + key.using_encoded(|k| Self::clear_collection_attribute(collection, k)) + } } /// Trait for transferring non-fungible sets of items. From a052224682994e6b54c94c17572e061f690149e5 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Wed, 4 Jan 2023 13:44:41 +0100 Subject: [PATCH 098/127] Selectable on-runtime-upgrade checks (#13045) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Make try-runtime checks selectable Signed-off-by: Oliver Tale-Yazdi * Update frame/support/src/traits/try_runtime.rs Co-authored-by: Bastian Köcher * Add Clap wrapper for enum UpgradeCheckSelect Signed-off-by: Oliver Tale-Yazdi * Revert "Add Clap wrapper for enum UpgradeCheckSelect" This reverts commit e29538c1a79d1711b43addc9400d871f6aa32844. * fix pools sanity check * Set default for --checks to None Signed-off-by: Oliver Tale-Yazdi * Make --checks backwards comp Signed-off-by: Oliver Tale-Yazdi * Add clap attr comment Signed-off-by: Oliver Tale-Yazdi Signed-off-by: Oliver Tale-Yazdi Co-authored-by: Bastian Köcher Co-authored-by: kianenigma --- bin/node-template/runtime/src/lib.rs | 2 +- bin/node/runtime/src/lib.rs | 2 +- frame/executive/src/lib.rs | 10 +++-- frame/nomination-pools/src/lib.rs | 17 +++++--- frame/support/src/traits.rs | 2 +- frame/support/src/traits/try_runtime.rs | 42 ++++++++++++++++++- frame/try-runtime/src/lib.rs | 4 +- .../cli/src/commands/on_runtime_upgrade.rs | 21 +++++++--- 8 files changed, 79 insertions(+), 21 deletions(-) diff --git a/bin/node-template/runtime/src/lib.rs b/bin/node-template/runtime/src/lib.rs index f76b2c449e..f4372af525 100644 --- a/bin/node-template/runtime/src/lib.rs +++ b/bin/node-template/runtime/src/lib.rs @@ -539,7 +539,7 @@ impl_runtime_apis! { #[cfg(feature = "try-runtime")] impl frame_try_runtime::TryRuntime for Runtime { - fn on_runtime_upgrade(checks: bool) -> (Weight, Weight) { + fn on_runtime_upgrade(checks: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) { // NOTE: intentional unwrap: we don't want to propagate the error backwards, and want to // have a backtrace here. If any of the pre/post migration checks fail, we shall stop // right here and right now. diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 90a52ab697..a9e93a16f0 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -2196,7 +2196,7 @@ impl_runtime_apis! { #[cfg(feature = "try-runtime")] impl frame_try_runtime::TryRuntime for Runtime { - fn on_runtime_upgrade(checks: bool) -> (Weight, Weight) { + fn on_runtime_upgrade(checks: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) { // NOTE: intentional unwrap: we don't want to propagate the error backwards, and want to // have a backtrace here. If any of the pre/post migration checks fail, we shall stop // right here and right now. diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index 1f20e93f43..b454d1e276 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -333,8 +333,10 @@ where /// /// Runs the try-state code both before and after the migration function if `checks` is set to /// `true`. Also, if set to `true`, it runs the `pre_upgrade` and `post_upgrade` hooks. - pub fn try_runtime_upgrade(checks: bool) -> Result { - if checks { + pub fn try_runtime_upgrade( + checks: frame_try_runtime::UpgradeCheckSelect, + ) -> Result { + if checks.try_state() { let _guard = frame_support::StorageNoopGuard::default(); >::try_state( frame_system::Pallet::::block_number(), @@ -344,10 +346,10 @@ where let weight = <(COnRuntimeUpgrade, AllPalletsWithSystem) as OnRuntimeUpgrade>::try_on_runtime_upgrade( - checks, + checks.pre_and_post(), )?; - if checks { + if checks.try_state() { let _guard = frame_support::StorageNoopGuard::default(); >::try_state( frame_system::Pallet::::block_number(), diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 5e5385b62a..99bfc8b8c3 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -2470,12 +2470,17 @@ impl Pallet { for id in reward_pools { let account = Self::create_reward_account(id); - assert!( - T::Currency::free_balance(&account) >= T::Currency::minimum_balance(), - "reward pool of {id}: {:?} (ed = {:?})", - T::Currency::free_balance(&account), - T::Currency::minimum_balance() - ); + if T::Currency::free_balance(&account) < T::Currency::minimum_balance() { + log!( + warn, + "reward pool of {:?}: {:?} (ed = {:?}), should only happen because ED has \ + changed recently. Pool operators should be notified to top up the reward \ + account", + id, + T::Currency::free_balance(&account), + T::Currency::minimum_balance(), + ) + } } let mut pools_members = BTreeMap::::new(); diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index 63c86c1f68..f7b57f446f 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -121,4 +121,4 @@ pub use messages::{ #[cfg(feature = "try-runtime")] mod try_runtime; #[cfg(feature = "try-runtime")] -pub use try_runtime::{Select as TryStateSelect, TryState}; +pub use try_runtime::{Select as TryStateSelect, TryState, UpgradeCheckSelect}; diff --git a/frame/support/src/traits/try_runtime.rs b/frame/support/src/traits/try_runtime.rs index f741ca56a5..273af09a14 100644 --- a/frame/support/src/traits/try_runtime.rs +++ b/frame/support/src/traits/try_runtime.rs @@ -21,7 +21,7 @@ use impl_trait_for_tuples::impl_for_tuples; use sp_arithmetic::traits::AtLeast32BitUnsigned; use sp_std::prelude::*; -// Which state tests to execute. +/// Which state tests to execute. #[derive(codec::Encode, codec::Decode, Clone)] pub enum Select { /// None of them. @@ -81,6 +81,46 @@ impl sp_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)] +pub enum UpgradeCheckSelect { + /// Run no checks. + None, + /// Run the `try_state`, `pre_upgrade` and `post_upgrade` checks. + All, + /// Run the `pre_upgrade` and `post_upgrade` checks. + PreAndPost, + /// Run the `try_state` checks. + TryState, +} + +impl UpgradeCheckSelect { + /// Whether the pre- and post-upgrade checks are selected. + pub fn pre_and_post(&self) -> bool { + matches!(self, Self::All | Self::PreAndPost) + } + + /// Whether the try-state checks are selected. + pub fn try_state(&self) -> bool { + matches!(self, Self::All | Self::TryState) + } +} + +#[cfg(feature = "std")] +impl core::str::FromStr for UpgradeCheckSelect { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "none" => Ok(Self::None), + "all" => Ok(Self::All), + "pre-and-post" => Ok(Self::PreAndPost), + "try-state" => Ok(Self::TryState), + _ => Err("Invalid CheckSelector"), + } + } +} + /// Execute some checks to ensure the internal state of a pallet is consistent. /// /// Usually, these checks should check all of the invariants that are expected to be held on all of diff --git a/frame/try-runtime/src/lib.rs b/frame/try-runtime/src/lib.rs index 99c68d4dc6..d8b117d1ff 100644 --- a/frame/try-runtime/src/lib.rs +++ b/frame/try-runtime/src/lib.rs @@ -20,7 +20,7 @@ #![cfg_attr(not(feature = "std"), no_std)] #![cfg(feature = "try-runtime")] -pub use frame_support::traits::TryStateSelect; +pub use frame_support::traits::{TryStateSelect, UpgradeCheckSelect}; use frame_support::weights::Weight; sp_api::decl_runtime_apis! { @@ -37,7 +37,7 @@ sp_api::decl_runtime_apis! { /// If `checks` is `true`, `pre_migrate` and `post_migrate` of each migration and /// `try_state` of all pallets will be executed. Else, no. If checks are executed, the PoV /// tracking is likely inaccurate. - fn on_runtime_upgrade(checks: bool) -> (Weight, Weight); + fn on_runtime_upgrade(checks: UpgradeCheckSelect) -> (Weight, Weight); /// Execute the given block, but optionally disable state-root and signature checks. /// diff --git a/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs b/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs index 2b2800d505..81f81d6a55 100644 --- a/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs +++ b/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs @@ -16,6 +16,7 @@ // limitations under the License. use crate::{build_executor, state_machine_call_with_proof, SharedParams, State, LOG_TARGET}; +use frame_try_runtime::UpgradeCheckSelect; use parity_scale_codec::{Decode, Encode}; use sc_executor::sp_wasm_interface::HostFunctions; use sp_runtime::traits::{Block as BlockT, NumberFor}; @@ -29,12 +30,22 @@ pub struct OnRuntimeUpgradeCmd { #[command(subcommand)] pub state: State, - /// Execute `try_state`, `pre_upgrade` and `post_upgrade` checks as well. + /// Select which optional checks to perform. Selects all when no value is given. /// - /// This will perform more checks, but it will also makes the reported PoV/Weight be - /// inaccurate. - #[clap(long)] - pub checks: bool, + /// - `none`: Perform no checks (default when the arg is not present). + /// - `all`: Perform all checks (default when the arg is present). + /// - `pre-and-post`: Perform pre- and post-upgrade checks. + /// - `try-state`: Perform the try-state checks. + /// + /// Performing any checks will potentially invalidate the measured PoV/Weight. + // NOTE: The clap attributes make it backwards compatible with the previous `--checks` flag. + #[clap(long, + default_value = "None", + default_missing_value = "All", + num_args = 0..=1, + require_equals = true, + verbatim_doc_comment)] + pub checks: UpgradeCheckSelect, } pub(crate) async fn on_runtime_upgrade( From da3a34ff7861d9bb504d013f2ef89e8b0ce2017c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= <123550+andresilva@users.noreply.github.com> Date: Wed, 4 Jan 2023 12:49:02 +0000 Subject: [PATCH 099/127] grandpa: pass the actual best block to voting rules (#12477) * grandpa: pass the actual best block to voting rules * grandpa: add test for checking best header is passed to voting rule --- client/finality-grandpa/src/environment.rs | 131 +++++++++--------- client/finality-grandpa/src/tests.rs | 152 +++++++++++++++++++-- 2 files changed, 207 insertions(+), 76 deletions(-) diff --git a/client/finality-grandpa/src/environment.rs b/client/finality-grandpa/src/environment.rs index 9e63bef78a..40ec720c4e 100644 --- a/client/finality-grandpa/src/environment.rs +++ b/client/finality-grandpa/src/environment.rs @@ -1174,7 +1174,7 @@ where let base_header = match client.header(block)? { Some(h) => h, None => { - debug!( + warn!( target: LOG_TARGET, "Encountered error finding best chain containing {:?}: couldn't find base block", block, @@ -1194,75 +1194,82 @@ where "Finding best chain containing block {:?} with number limit {:?}", block, limit ); - let result = match select_chain.finality_target(block, None).await { - Ok(best_hash) => { - let best_header = client - .header(best_hash)? - .expect("Header known to exist after `finality_target` call; qed"); - - // check if our vote is currently being limited due to a pending change - let limit = limit.filter(|limit| limit < best_header.number()); - - let (base_header, best_header, target_header) = if let Some(target_number) = limit { - let mut target_header = best_header.clone(); - - // walk backwards until we find the target block - loop { - if *target_header.number() < target_number { - unreachable!( - "we are traversing backwards from a known block; \ - blocks are stored contiguously; \ - qed" - ); - } - - if *target_header.number() == target_number { - break - } - - target_header = client - .header(*target_header.parent_hash())? - .expect("Header known to exist after `finality_target` call; qed"); - } - - (base_header, best_header, target_header) - } else { - // otherwise just use the given best as the target - (base_header, best_header.clone(), best_header) - }; + let mut target_header = match select_chain.finality_target(block, None).await { + Ok(target_hash) => client + .header(target_hash)? + .expect("Header known to exist after `finality_target` call; qed"), + Err(err) => { + warn!( + target: LOG_TARGET, + "Encountered error finding best chain containing {:?}: couldn't find target block: {}", + block, + err, + ); - // restrict vote according to the given voting rule, if the - // voting rule doesn't restrict the vote then we keep the - // previous target. - // - // note that we pass the original `best_header`, i.e. before the - // authority set limit filter, which can be considered a - // mandatory/implicit voting rule. - // - // we also make sure that the restricted vote is higher than the - // round base (i.e. last finalized), otherwise the value - // returned by the given voting rule is ignored and the original - // target is used instead. - voting_rule - .restrict_vote(client.clone(), &base_header, &best_header, &target_header) - .await - .filter(|(_, restricted_number)| { - // we can only restrict votes within the interval [base, target] - restricted_number >= base_header.number() && - restricted_number < target_header.number() - }) - .or_else(|| Some((target_header.hash(), *target_header.number()))) + return Ok(None) }, - Err(e) => { + }; + + // NOTE: this is purposefully done after `finality_target` to prevent a case + // where in-between these two requests there is a block import and + // `finality_target` returns something higher than `best_chain`. + let best_header = match select_chain.best_chain().await { + Ok(best_header) => best_header, + Err(err) => { warn!( target: LOG_TARGET, - "Encountered error finding best chain containing {:?}: {}", block, e + "Encountered error finding best chain containing {:?}: couldn't find best block: {}", + block, + err, ); - None + + return Ok(None) }, }; - Ok(result) + if target_header.number() > best_header.number() { + return Err(Error::Safety( + "SelectChain returned a finality target higher than its best block".into(), + )) + } + + // check if our vote is currently being limited due to a pending change, + // in which case we will restrict our target header to the given limit + if let Some(target_number) = limit.filter(|limit| limit < target_header.number()) { + // walk backwards until we find the target block + loop { + if *target_header.number() < target_number { + unreachable!( + "we are traversing backwards from a known block; \ + blocks are stored contiguously; \ + qed" + ); + } + + if *target_header.number() == target_number { + break + } + + target_header = client + .header(*target_header.parent_hash())? + .expect("Header known to exist after `finality_target` call; qed"); + } + } + + // restrict vote according to the given voting rule, if the voting rule + // doesn't restrict the vote then we keep the previous target. + // + // we also make sure that the restricted vote is higher than the round base + // (i.e. last finalized), otherwise the value returned by the given voting + // rule is ignored and the original target is used instead. + Ok(voting_rule + .restrict_vote(client.clone(), &base_header, &best_header, &target_header) + .await + .filter(|(_, restricted_number)| { + // we can only restrict votes within the interval [base, target] + restricted_number >= base_header.number() && restricted_number < target_header.number() + }) + .or_else(|| Some((target_header.hash(), *target_header.number())))) } /// Finalize the given block and apply any authority set changes. If an diff --git a/client/finality-grandpa/src/tests.rs b/client/finality-grandpa/src/tests.rs index 4abcb05406..07ea464914 100644 --- a/client/finality-grandpa/src/tests.rs +++ b/client/finality-grandpa/src/tests.rs @@ -20,6 +20,7 @@ use super::*; use assert_matches::assert_matches; +use async_trait::async_trait; use environment::HasVoted; use futures_timer::Delay; use parking_lot::{Mutex, RwLock}; @@ -33,8 +34,7 @@ use sc_network_test::{ PeersFullClient, TestClient, TestNetFactory, }; use sp_api::{ApiRef, ProvideRuntimeApi}; -use sp_blockchain::Result; -use sp_consensus::BlockOrigin; +use sp_consensus::{BlockOrigin, Error as ConsensusError, SelectChain}; use sp_core::H256; use sp_finality_grandpa::{ AuthorityList, EquivocationProof, GrandpaApi, OpaqueKeyOwnershipProof, GRANDPA_ENGINE_ID, @@ -200,11 +200,50 @@ sp_api::mock_impl_runtime_apis! { } impl GenesisAuthoritySetProvider for TestApi { - fn get(&self) -> Result { + fn get(&self) -> sp_blockchain::Result { Ok(self.genesis_authorities.clone()) } } +/// A mock `SelectChain` that allows the user to set the return values for each +/// method. After the `SelectChain` methods are called the pending value is +/// discarded and another call to set new values must be performed. +#[derive(Clone, Default)] +struct MockSelectChain { + leaves: Arc>>>, + best_chain: Arc::Header>>>, + finality_target: Arc>>, +} + +impl MockSelectChain { + fn set_best_chain(&self, best: ::Header) { + *self.best_chain.lock() = Some(best); + } + + fn set_finality_target(&self, target: Hash) { + *self.finality_target.lock() = Some(target); + } +} + +#[async_trait] +impl SelectChain for MockSelectChain { + async fn leaves(&self) -> Result, ConsensusError> { + Ok(self.leaves.lock().take().unwrap()) + } + + async fn best_chain(&self) -> Result<::Header, ConsensusError> { + Ok(self.best_chain.lock().take().unwrap()) + } + + async fn finality_target( + &self, + _target_hash: Hash, + _maybe_max_number: Option>, + ) -> Result { + Ok(self.finality_target.lock().take().unwrap()) + } +} + const TEST_GOSSIP_DURATION: Duration = Duration::from_millis(500); fn make_ids(keys: &[Ed25519Keyring]) -> AuthorityList { @@ -1291,21 +1330,16 @@ async fn voter_catches_up_to_latest_round_when_behind() { future::select(test, drive_to_completion).await; } -type TestEnvironment = Environment< - substrate_test_runtime_client::Backend, - Block, - TestClient, - N, - LongestChain, - VR, ->; +type TestEnvironment = + Environment; -fn test_environment( +fn test_environment_with_select_chain( link: &TestLinkHalf, keystore: Option, network_service: N, + select_chain: SC, voting_rule: VR, -) -> TestEnvironment +) -> TestEnvironment where N: NetworkT, VR: VotingRule, @@ -1330,7 +1364,7 @@ where authority_set: authority_set.clone(), config: config.clone(), client: link.client.clone(), - select_chain: link.select_chain.clone(), + select_chain, set_id: authority_set.set_id(), voter_set_state: set_state.clone(), voters: Arc::new(authority_set.current_authorities()), @@ -1343,6 +1377,25 @@ where } } +fn test_environment( + link: &TestLinkHalf, + keystore: Option, + network_service: N, + voting_rule: VR, +) -> TestEnvironment, VR> +where + N: NetworkT, + VR: VotingRule, +{ + test_environment_with_select_chain( + link, + keystore, + network_service, + link.select_chain.clone(), + voting_rule, + ) +} + #[tokio::test] async fn grandpa_environment_respects_voting_rules() { use finality_grandpa::voter::Environment; @@ -1455,6 +1508,77 @@ async fn grandpa_environment_respects_voting_rules() { ); } +#[tokio::test] +async fn grandpa_environment_passes_actual_best_block_to_voting_rules() { + // NOTE: this is a "regression" test since initially we were not passing the + // best block to the voting rules + use finality_grandpa::voter::Environment; + + let peers = &[Ed25519Keyring::Alice]; + let voters = make_ids(peers); + + let mut net = GrandpaTestNet::new(TestApi::new(voters), 1, 0); + let peer = net.peer(0); + let network_service = peer.network_service().clone(); + let link = peer.data.lock().take().unwrap(); + let client = peer.client().as_client().clone(); + let select_chain = MockSelectChain::default(); + + // add 42 blocks + peer.push_blocks(42, false); + + // create an environment with a voting rule that always restricts votes to + // before the best block by 5 blocks + let env = test_environment_with_select_chain( + &link, + None, + network_service.clone(), + select_chain.clone(), + voting_rule::BeforeBestBlockBy(5), + ); + + // both best block and finality target are pointing to the same latest block, + // therefore we must restrict our vote on top of the given target (#21) + let hashof21 = client.expect_block_hash_from_id(&BlockId::Number(21)).unwrap(); + select_chain.set_best_chain(client.expect_header(hashof21).unwrap()); + select_chain.set_finality_target(client.expect_header(hashof21).unwrap().hash()); + + assert_eq!( + env.best_chain_containing(peer.client().info().finalized_hash) + .await + .unwrap() + .unwrap() + .1, + 16, + ); + + // the returned finality target is already 11 blocks from the best block, + // therefore there should be no further restriction by the voting rule + let hashof10 = client.expect_block_hash_from_id(&BlockId::Number(10)).unwrap(); + select_chain.set_best_chain(client.expect_header(hashof21).unwrap()); + select_chain.set_finality_target(client.expect_header(hashof10).unwrap().hash()); + + assert_eq!( + env.best_chain_containing(peer.client().info().finalized_hash) + .await + .unwrap() + .unwrap() + .1, + 10, + ); + + // returning a finality target that's higher than the best block is an + // inconsistent state that should be handled + let hashof42 = client.expect_block_hash_from_id(&BlockId::Number(42)).unwrap(); + select_chain.set_best_chain(client.expect_header(hashof21).unwrap()); + select_chain.set_finality_target(client.expect_header(hashof42).unwrap().hash()); + + assert_matches!( + env.best_chain_containing(peer.client().info().finalized_hash).await, + Err(CommandOrError::Error(Error::Safety(_))) + ); +} + #[tokio::test] async fn grandpa_environment_never_overwrites_round_voter_state() { use finality_grandpa::voter::Environment; From 68fc275a8e9e9f84023769c033f7251318544275 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Wed, 4 Jan 2023 14:00:30 +0100 Subject: [PATCH 100/127] Remove `Copy` from `Ensure*` traits (#13043) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove Copy from EnsureOp and EnsureOpAssign Signed-off-by: Oliver Tale-Yazdi * Remove Copy from EnsureFrom and EnsureInto Signed-off-by: Oliver Tale-Yazdi * Fix default impl Signed-off-by: Oliver Tale-Yazdi * Reuse assignment code in Ensure trait Signed-off-by: Oliver Tale-Yazdi * Require Ensure for all BaseArithmetic types Signed-off-by: Oliver Tale-Yazdi * Fix assign impls Co-authored-by: Luis Enrique Muñoz Martín Signed-off-by: Oliver Tale-Yazdi * Add tests Signed-off-by: Oliver Tale-Yazdi * Add success doc tests Signed-off-by: Oliver Tale-Yazdi Signed-off-by: Oliver Tale-Yazdi --- primitives/arithmetic/src/traits.rs | 304 +++++++++++++++++++++++----- 1 file changed, 251 insertions(+), 53 deletions(-) diff --git a/primitives/arithmetic/src/traits.rs b/primitives/arithmetic/src/traits.rs index 7fa64d2866..9e9dff8e6f 100644 --- a/primitives/arithmetic/src/traits.rs +++ b/primitives/arithmetic/src/traits.rs @@ -57,6 +57,7 @@ pub trait BaseArithmetic: + CheckedMul + CheckedDiv + CheckedRem + + Ensure + Saturating + PartialOrd + Ord @@ -113,6 +114,7 @@ impl< + CheckedMul + CheckedDiv + CheckedRem + + Ensure + Saturating + PartialOrd + Ord @@ -344,13 +346,24 @@ mod ensure { use crate::{ArithmeticError, FixedPointNumber, FixedPointOperand}; /// Performs addition that returns [`ArithmeticError`] instead of wrapping around on overflow. - pub trait EnsureAdd: CheckedAdd + PartialOrd + Zero + Copy { + pub trait EnsureAdd: EnsureAddAssign { /// Adds two numbers, checking for overflow. /// /// If it fails, [`ArithmeticError`] is returned. /// /// Similar to [`CheckedAdd::checked_add()`] but returning an [`ArithmeticError`] error. /// + /// # Examples + /// + /// ``` + /// use sp_arithmetic::traits::EnsureAdd; + /// + /// let a: i32 = 10; + /// let b: i32 = 20; + /// + /// assert_eq!(a.ensure_add(b), Ok(30)); + /// ``` + /// /// ``` /// use sp_arithmetic::{traits::EnsureAdd, ArithmeticError}; /// @@ -367,20 +380,32 @@ mod ensure { /// assert_eq!(overflow(), Err(ArithmeticError::Overflow)); /// assert_eq!(underflow(), Err(ArithmeticError::Underflow)); /// ``` - fn ensure_add(self, v: Self) -> Result { - self.checked_add(&v).ok_or_else(|| error::equivalent(v)) + fn ensure_add(mut self, v: Self) -> Result { + self.ensure_add_assign(v)?; + Ok(self) } } /// Performs subtraction that returns [`ArithmeticError`] instead of wrapping around on /// underflow. - pub trait EnsureSub: CheckedSub + PartialOrd + Zero + Copy { + pub trait EnsureSub: EnsureSubAssign { /// Subtracts two numbers, checking for overflow. /// /// If it fails, [`ArithmeticError`] is returned. /// /// Similar to [`CheckedSub::checked_sub()`] but returning an [`ArithmeticError`] error. /// + /// # Examples + /// + /// ``` + /// use sp_arithmetic::traits::EnsureSub; + /// + /// let a: i32 = 10; + /// let b: i32 = 20; + /// + /// assert_eq!(a.ensure_sub(b), Ok(-10)); + /// ``` + /// /// ``` /// use sp_arithmetic::{traits::EnsureSub, ArithmeticError}; /// @@ -397,20 +422,32 @@ mod ensure { /// assert_eq!(underflow(), Err(ArithmeticError::Underflow)); /// assert_eq!(overflow(), Err(ArithmeticError::Overflow)); /// ``` - fn ensure_sub(self, v: Self) -> Result { - self.checked_sub(&v).ok_or_else(|| error::inverse(v)) + fn ensure_sub(mut self, v: Self) -> Result { + self.ensure_sub_assign(v)?; + Ok(self) } } /// Performs multiplication that returns [`ArithmeticError`] instead of wrapping around on /// overflow. - pub trait EnsureMul: CheckedMul + PartialOrd + Zero + Copy { + pub trait EnsureMul: EnsureMulAssign { /// Multiplies two numbers, checking for overflow. /// /// If it fails, [`ArithmeticError`] is returned. /// /// Similar to [`CheckedMul::checked_mul()`] but returning an [`ArithmeticError`] error. /// + /// # Examples + /// + /// ``` + /// use sp_arithmetic::traits::EnsureMul; + /// + /// let a: i32 = 10; + /// let b: i32 = 20; + /// + /// assert_eq!(a.ensure_mul(b), Ok(200)); + /// ``` + /// /// ``` /// use sp_arithmetic::{traits::EnsureMul, ArithmeticError}; /// @@ -427,19 +464,31 @@ mod ensure { /// assert_eq!(overflow(), Err(ArithmeticError::Overflow)); /// assert_eq!(underflow(), Err(ArithmeticError::Underflow)); /// ``` - fn ensure_mul(self, v: Self) -> Result { - self.checked_mul(&v).ok_or_else(|| error::multiplication(self, v)) + fn ensure_mul(mut self, v: Self) -> Result { + self.ensure_mul_assign(v)?; + Ok(self) } } /// Performs division that returns [`ArithmeticError`] instead of wrapping around on overflow. - pub trait EnsureDiv: CheckedDiv + PartialOrd + Zero + Copy { + pub trait EnsureDiv: EnsureDivAssign { /// Divides two numbers, checking for overflow. /// /// If it fails, [`ArithmeticError`] is returned. /// /// Similar to [`CheckedDiv::checked_div()`] but returning an [`ArithmeticError`] error. /// + /// # Examples + /// + /// ``` + /// use sp_arithmetic::traits::EnsureDiv; + /// + /// let a: i32 = 20; + /// let b: i32 = 10; + /// + /// assert_eq!(a.ensure_div(b), Ok(2)); + /// ``` + /// /// ``` /// use sp_arithmetic::{traits::EnsureDiv, ArithmeticError}; /// @@ -456,15 +505,16 @@ mod ensure { /// assert_eq!(extrinsic_zero(), Err(ArithmeticError::DivisionByZero)); /// assert_eq!(overflow(), Err(ArithmeticError::Overflow)); /// ``` - fn ensure_div(self, v: Self) -> Result { - self.checked_div(&v).ok_or_else(|| error::division(self, v)) + fn ensure_div(mut self, v: Self) -> Result { + self.ensure_div_assign(v)?; + Ok(self) } } - impl EnsureAdd for T {} - impl EnsureSub for T {} - impl EnsureMul for T {} - impl EnsureDiv for T {} + impl EnsureAdd for T {} + impl EnsureSub for T {} + impl EnsureMul for T {} + impl EnsureDiv for T {} /// Meta trait that supports all immutable arithmetic `Ensure*` operations pub trait EnsureOp: EnsureAdd + EnsureSub + EnsureMul + EnsureDiv {} @@ -472,11 +522,23 @@ mod ensure { /// Performs self addition that returns [`ArithmeticError`] instead of wrapping around on /// overflow. - pub trait EnsureAddAssign: EnsureAdd { + pub trait EnsureAddAssign: CheckedAdd + PartialOrd + Zero { /// Adds two numbers overwriting the left hand one, checking for overflow. /// /// If it fails, [`ArithmeticError`] is returned. /// + /// # Examples + /// + /// ``` + /// use sp_arithmetic::traits::EnsureAddAssign; + /// + /// let mut a: i32 = 10; + /// let b: i32 = 20; + /// + /// a.ensure_add_assign(b).unwrap(); + /// assert_eq!(a, 30); + /// ``` + /// /// ``` /// use sp_arithmetic::{traits::EnsureAddAssign, ArithmeticError}; /// @@ -496,18 +558,30 @@ mod ensure { /// assert_eq!(underflow(), Err(ArithmeticError::Underflow)); /// ``` fn ensure_add_assign(&mut self, v: Self) -> Result<(), ArithmeticError> { - *self = self.ensure_add(v)?; + *self = self.checked_add(&v).ok_or_else(|| error::equivalent(&v))?; Ok(()) } } /// Performs self subtraction that returns [`ArithmeticError`] instead of wrapping around on /// underflow. - pub trait EnsureSubAssign: EnsureSub { + pub trait EnsureSubAssign: CheckedSub + PartialOrd + Zero { /// Subtracts two numbers overwriting the left hand one, checking for overflow. /// /// If it fails, [`ArithmeticError`] is returned. /// + /// # Examples + /// + /// ``` + /// use sp_arithmetic::traits::EnsureSubAssign; + /// + /// let mut a: i32 = 10; + /// let b: i32 = 20; + /// + /// a.ensure_sub_assign(b).unwrap(); + /// assert_eq!(a, -10); + /// ``` + /// /// ``` /// use sp_arithmetic::{traits::EnsureSubAssign, ArithmeticError}; /// @@ -527,18 +601,30 @@ mod ensure { /// assert_eq!(overflow(), Err(ArithmeticError::Overflow)); /// ``` fn ensure_sub_assign(&mut self, v: Self) -> Result<(), ArithmeticError> { - *self = self.ensure_sub(v)?; + *self = self.checked_sub(&v).ok_or_else(|| error::inverse(&v))?; Ok(()) } } /// Performs self multiplication that returns [`ArithmeticError`] instead of wrapping around on /// overflow. - pub trait EnsureMulAssign: EnsureMul { + pub trait EnsureMulAssign: CheckedMul + PartialOrd + Zero { /// Multiplies two numbers overwriting the left hand one, checking for overflow. /// /// If it fails, [`ArithmeticError`] is returned. /// + /// # Examples + /// + /// ``` + /// use sp_arithmetic::traits::EnsureMulAssign; + /// + /// let mut a: i32 = 10; + /// let b: i32 = 20; + /// + /// a.ensure_mul_assign(b).unwrap(); + /// assert_eq!(a, 200); + /// ``` + /// /// ``` /// use sp_arithmetic::{traits::EnsureMulAssign, ArithmeticError}; /// @@ -558,18 +644,30 @@ mod ensure { /// assert_eq!(underflow(), Err(ArithmeticError::Underflow)); /// ``` fn ensure_mul_assign(&mut self, v: Self) -> Result<(), ArithmeticError> { - *self = self.ensure_mul(v)?; + *self = self.checked_mul(&v).ok_or_else(|| error::multiplication(self, &v))?; Ok(()) } } /// Performs self division that returns [`ArithmeticError`] instead of wrapping around on /// overflow. - pub trait EnsureDivAssign: EnsureDiv { + pub trait EnsureDivAssign: CheckedDiv + PartialOrd + Zero { /// Divides two numbers overwriting the left hand one, checking for overflow. /// /// If it fails, [`ArithmeticError`] is returned. /// + /// # Examples + /// + /// ``` + /// use sp_arithmetic::traits::EnsureDivAssign; + /// + /// let mut a: i32 = 20; + /// let b: i32 = 10; + /// + /// a.ensure_div_assign(b).unwrap(); + /// assert_eq!(a, 2); + /// ``` + /// /// ``` /// use sp_arithmetic::{traits::EnsureDivAssign, ArithmeticError, FixedI64}; /// @@ -589,15 +687,15 @@ mod ensure { /// assert_eq!(overflow(), Err(ArithmeticError::Overflow)); /// ``` fn ensure_div_assign(&mut self, v: Self) -> Result<(), ArithmeticError> { - *self = self.ensure_div(v)?; + *self = self.checked_div(&v).ok_or_else(|| error::division(self, &v))?; Ok(()) } } - impl EnsureAddAssign for T {} - impl EnsureSubAssign for T {} - impl EnsureMulAssign for T {} - impl EnsureDivAssign for T {} + impl EnsureAddAssign for T {} + impl EnsureSubAssign for T {} + impl EnsureMulAssign for T {} + impl EnsureDivAssign for T {} /// Meta trait that supports all assigned arithmetic `Ensure*` operations pub trait EnsureOpAssign: @@ -609,7 +707,6 @@ mod ensure { { } - /// Meta trait that supports all arithmetic operations pub trait Ensure: EnsureOp + EnsureOpAssign {} impl Ensure for T {} @@ -643,7 +740,7 @@ mod ensure { d: D, ) -> Result { ::checked_from_rational(n, d) - .ok_or_else(|| error::division(n, d)) + .ok_or_else(|| error::division(&n, &d)) } /// Ensure multiplication for integer type `N`. Equal to `self * n`. @@ -670,7 +767,7 @@ mod ensure { /// assert_eq!(underflow(), Err(ArithmeticError::Underflow)); /// ``` fn ensure_mul_int(self, n: N) -> Result { - self.checked_mul_int(n).ok_or_else(|| error::multiplication(self, n)) + self.checked_mul_int(n).ok_or_else(|| error::multiplication(&self, &n)) } /// Ensure division for integer type `N`. Equal to `self / d`. @@ -697,16 +794,14 @@ mod ensure { /// assert_eq!(overflow(), Err(ArithmeticError::Overflow)); /// ``` fn ensure_div_int(self, d: D) -> Result { - self.checked_div_int(d).ok_or_else(|| error::division(self, d)) + self.checked_div_int(d).ok_or_else(|| error::division(&self, &d)) } } impl EnsureFixedPointNumber for T {} /// Similar to [`TryFrom`] but returning an [`ArithmeticError`] error. - pub trait EnsureFrom: - TryFrom + PartialOrd + Zero + Copy - { + pub trait EnsureFrom: TryFrom + PartialOrd + Zero { /// Performs the conversion returning an [`ArithmeticError`] if fails. /// /// Similar to [`TryFrom::try_from()`] but returning an [`ArithmeticError`] error. @@ -728,14 +823,13 @@ mod ensure { /// assert_eq!(underflow(), Err(ArithmeticError::Underflow)); /// ``` fn ensure_from(other: T) -> Result { - Self::try_from(other).map_err(|_| error::equivalent(other)) + let err = error::equivalent(&other); + Self::try_from(other).map_err(|_| err) } } /// Similar to [`TryInto`] but returning an [`ArithmeticError`] error. - pub trait EnsureInto: - TryInto + PartialOrd + Zero + Copy - { + pub trait EnsureInto: TryInto + PartialOrd + Zero { /// Performs the conversion returning an [`ArithmeticError`] if fails. /// /// Similar to [`TryInto::try_into()`] but returning an [`ArithmeticError`] error @@ -757,12 +851,13 @@ mod ensure { /// assert_eq!(underflow(), Err(ArithmeticError::Underflow)); /// ``` fn ensure_into(self) -> Result { - self.try_into().map_err(|_| error::equivalent(self)) + let err = error::equivalent(&self); + self.try_into().map_err(|_| err) } } - impl + PartialOrd + Zero + Copy, S: PartialOrd + Zero + Copy> EnsureFrom for T {} - impl + PartialOrd + Zero + Copy, S: PartialOrd + Zero + Copy> EnsureInto for T {} + impl + PartialOrd + Zero, S: PartialOrd + Zero> EnsureFrom for T {} + impl + PartialOrd + Zero, S: PartialOrd + Zero> EnsureInto for T {} mod error { use super::{ArithmeticError, Zero}; @@ -773,9 +868,9 @@ mod ensure { Positive, } - impl From for Signum { - fn from(value: T) -> Self { - if value < Zero::zero() { + impl From<&T> for Signum { + fn from(value: &T) -> Self { + if value < &Zero::zero() { Signum::Negative } else { Signum::Positive @@ -795,23 +890,23 @@ mod ensure { } } - pub fn equivalent(r: R) -> ArithmeticError { + pub fn equivalent(r: &R) -> ArithmeticError { match Signum::from(r) { Signum::Negative => ArithmeticError::Underflow, Signum::Positive => ArithmeticError::Overflow, } } - pub fn inverse(r: R) -> ArithmeticError { + pub fn inverse(r: &R) -> ArithmeticError { match Signum::from(r) { Signum::Negative => ArithmeticError::Overflow, Signum::Positive => ArithmeticError::Underflow, } } - pub fn multiplication( - l: L, - r: R, + pub fn multiplication( + l: &L, + r: &R, ) -> ArithmeticError { match Signum::from(l) * Signum::from(r) { Signum::Negative => ArithmeticError::Underflow, @@ -819,9 +914,9 @@ mod ensure { } } - pub fn division( - n: N, - d: D, + pub fn division( + n: &N, + d: &D, ) -> ArithmeticError { if d.is_zero() { ArithmeticError::DivisionByZero @@ -831,3 +926,106 @@ mod ensure { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::ArithmeticError; + use rand::{seq::SliceRandom, thread_rng, Rng}; + + #[test] + fn ensure_add_works() { + test_ensure(values(), &EnsureAdd::ensure_add, &CheckedAdd::checked_add); + } + + #[test] + fn ensure_sub_works() { + test_ensure(values(), &EnsureSub::ensure_sub, &CheckedSub::checked_sub); + } + + #[test] + fn ensure_mul_works() { + test_ensure(values(), &EnsureMul::ensure_mul, &CheckedMul::checked_mul); + } + + #[test] + fn ensure_div_works() { + test_ensure(values(), &EnsureDiv::ensure_div, &CheckedDiv::checked_div); + } + + #[test] + fn ensure_add_assign_works() { + test_ensure_assign(values(), &EnsureAddAssign::ensure_add_assign, &EnsureAdd::ensure_add); + } + + #[test] + fn ensure_sub_assign_works() { + test_ensure_assign(values(), &EnsureSubAssign::ensure_sub_assign, &EnsureSub::ensure_sub); + } + + #[test] + fn ensure_mul_assign_works() { + test_ensure_assign(values(), &EnsureMulAssign::ensure_mul_assign, &&EnsureMul::ensure_mul); + } + + #[test] + fn ensure_div_assign_works() { + test_ensure_assign(values(), &EnsureDivAssign::ensure_div_assign, &EnsureDiv::ensure_div); + } + + /// Test that the ensured function returns the expected un-ensured value. + fn test_ensure(pairs: Vec<(V, V)>, ensured: E, unensured: P) + where + V: Ensure + core::fmt::Debug + Copy, + E: Fn(V, V) -> Result, + P: Fn(&V, &V) -> Option, + { + for (a, b) in pairs.into_iter() { + match ensured(a, b) { + Ok(c) => { + assert_eq!(unensured(&a, &b), Some(c)) + }, + Err(_) => { + assert!(unensured(&a, &b).is_none()); + }, + } + } + } + + /// Test that the ensured function modifies `self` to the expected un-ensured value. + fn test_ensure_assign(pairs: Vec<(V, V)>, ensured: E, unensured: P) + where + V: Ensure + std::panic::RefUnwindSafe + std::panic::UnwindSafe + core::fmt::Debug + Copy, + E: Fn(&mut V, V) -> Result<(), ArithmeticError>, + P: Fn(V, V) -> Result + std::panic::RefUnwindSafe, + { + for (mut a, b) in pairs.into_iter() { + let old_a = a; + + match ensured(&mut a, b) { + Ok(()) => { + assert_eq!(unensured(old_a, b), Ok(a)); + }, + Err(err) => { + assert_eq!(a, old_a, "A stays unmodified in the error case"); + assert_eq!(unensured(old_a, b), Err(err)); + }, + } + } + } + + /// Generates some good values for testing integer arithmetic. + fn values() -> Vec<(i32, i32)> { + let mut rng = thread_rng(); + let mut one_dimension = || { + let mut ret = vec![0i32; 1007]; + // Some hard-coded interesting values. + ret[..7].copy_from_slice(&[-1, 0, 1, i32::MIN, i32::MAX, i32::MAX - 1, i32::MIN + 1]); + // … and some random ones. + rng.fill(&mut ret[7..]); + ret.shuffle(&mut rng); + ret + }; + one_dimension().into_iter().zip(one_dimension().into_iter()).collect() + } +} From ea387c634715793f806286abf1e64cabf9b7026f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Wed, 4 Jan 2023 14:17:18 +0100 Subject: [PATCH 101/127] Aura: Do not verify on state import (#13058) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Aura: Do not verify on state import When we import the state, we can not fetch authorities to verify the seal etc. So, we can directly skip any verification. * Skip checks as well for gap sync * Update client/consensus/aura/src/import_queue.rs Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com> * Review comment Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com> --- client/consensus/aura/src/import_queue.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/client/consensus/aura/src/import_queue.rs b/client/consensus/aura/src/import_queue.rs index d5cf40f333..0973b33d40 100644 --- a/client/consensus/aura/src/import_queue.rs +++ b/client/consensus/aura/src/import_queue.rs @@ -186,6 +186,19 @@ where &mut self, mut block: BlockImportParams, ) -> Result<(BlockImportParams, Option)>>), String> { + // When importing whole state we don't verify the seal as the state is not available. + if block.with_state() { + return Ok((block, Default::default())) + } + + // Skip checks that include execution, if being told so. + // + // This is done for example when gap syncing and it is expected that the block after the gap + // was checked/chosen properly, e.g. by warp syncing to this block using a finality proof. + if block.state_action.skip_execution_checks() { + return Ok((block, Default::default())) + } + let hash = block.header.hash(); let parent_hash = *block.header.parent_hash(); let authorities = authorities( From 05fe10b527dbf437aa48e530701110e2043a4816 Mon Sep 17 00:00:00 2001 From: Anton Date: Thu, 5 Jan 2023 16:03:41 +0400 Subject: [PATCH 102/127] upgrade libp2p to 0.50.0 (#12734) * upgrade libp2p to 0.50.0 * on_swarm_event and on_connection_handler_event * replace `Swarm::new` with `Swarm::with_threadpool_executor` * on_swarm_event and on_connection_handler_event part 2 * on_swarm_event and on_connection_handler_event part 3 * on_swarm_event and on_connection_handler_event part 4 * update libp2p * libp2p 0.50.0 * rename OutboundQueryCompleted to OutboundQueryProgressed refs https://github.com/libp2p/rust-libp2p/pull/2712 * remove unused var * accumulate outbound_query_records until query is finished * format code * use p_handler instead of new_handler https://github.com/paritytech/substrate/pull/12734#discussion_r1027640610 * pass ListenFailure to kademlia https://github.com/paritytech/substrate/pull/12734#discussion_r1034716664 * use tokio executor in tests https://github.com/paritytech/substrate/pull/12734#discussion_r1039291776 * use chrono Local::now instead of deprecated Local::today * remove unused vars from request_responses tests * attempt to fix pallet UI tests * restart CI * restart CI * restart CI * restart CI * restart CI * restart CI * restart CI * restart CI --- Cargo.lock | 3037 +++++++++++------ client/authority-discovery/Cargo.toml | 2 +- client/cli/Cargo.toml | 2 +- client/cli/src/runner.rs | 2 +- client/consensus/common/Cargo.toml | 2 +- client/network-gossip/Cargo.toml | 2 +- client/network/Cargo.toml | 2 +- client/network/bitswap/Cargo.toml | 2 +- client/network/common/Cargo.toml | 2 +- client/network/light/Cargo.toml | 2 +- client/network/src/behaviour.rs | 2 +- client/network/src/discovery.rs | 272 +- client/network/src/peer_info.rs | 295 +- client/network/src/protocol.rs | 88 +- .../src/protocol/notifications/behaviour.rs | 851 ++--- .../src/protocol/notifications/tests.rs | 101 +- client/network/src/request_responses.rs | 230 +- client/network/src/service.rs | 28 +- client/network/src/transport.rs | 10 +- client/network/sync/Cargo.toml | 2 +- client/network/test/Cargo.toml | 2 +- client/network/transactions/Cargo.toml | 2 +- client/offchain/Cargo.toml | 2 +- client/peerset/Cargo.toml | 2 +- client/telemetry/Cargo.toml | 2 +- client/telemetry/src/transport.rs | 2 +- ...ev_mode_without_arg_max_encoded_len.stderr | 2 +- ...age_ensure_span_are_ok_on_wrong_gen.stderr | 10 +- ...re_span_are_ok_on_wrong_gen_unnamed.stderr | 10 +- .../pallet_ui/storage_info_unsatisfied.stderr | 2 +- .../storage_info_unsatisfied_nmap.stderr | 2 +- 31 files changed, 2970 insertions(+), 2002 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f82343e442..d333b79c8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,7 +18,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" dependencies = [ - "gimli 0.26.1", + "gimli 0.26.2", ] [[package]] @@ -36,13 +36,34 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aead" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc95d1bdb8e6666b2b217308eeeb09f2d6728d104be3e31916cc74d15420331" +dependencies = [ + "generic-array 0.14.6", +] + [[package]] name = "aead" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.6", + "rand_core 0.6.4", +] + +[[package]] +name = "aes" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561" +dependencies = [ + "aes-soft", + "aesni", + "cipher 0.2.5", ] [[package]] @@ -52,45 +73,88 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ "cfg-if", - "cipher", + "cipher 0.3.0", "cpufeatures", "opaque-debug 0.3.0", ] +[[package]] +name = "aes-gcm" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5278b5fabbb9bd46e24aa69b2fdea62c99088e0a950a9be40e3e0101298f88da" +dependencies = [ + "aead 0.3.2", + "aes 0.6.0", + "cipher 0.2.5", + "ctr 0.6.0", + "ghash 0.3.1", + "subtle", +] + [[package]] name = "aes-gcm" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "ghash", + "aead 0.4.3", + "aes 0.7.5", + "cipher 0.3.0", + "ctr 0.8.0", + "ghash 0.4.4", "subtle", ] +[[package]] +name = "aes-soft" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072" +dependencies = [ + "cipher 0.2.5", + "opaque-debug 0.3.0", +] + +[[package]] +name = "aesni" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce" +dependencies = [ + "cipher 0.2.5", + "opaque-debug 0.3.0", +] + [[package]] name = "ahash" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.3", + "getrandom 0.2.8", "once_cell", "version_check", ] [[package]] name = "aho-corasick" -version = "0.7.18" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -102,30 +166,36 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.66" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" +checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" [[package]] name = "approx" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "072df7202e63b127ab55acfe16ce97013d5b97bf160489336d3f1840fd78e99e" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" dependencies = [ "num-traits", ] [[package]] name = "arbitrary" -version = "1.0.0" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29d47fbf90d5149a107494b15a7dc8d69b351be2db3bb9691740e88ec17fd880" + +[[package]] +name = "arc-swap" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "698b65a961a9d730fb45b6b0327e20207810c9f61ee421b082b27ba003f49e2b" +checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" [[package]] name = "array-bytes" -version = "4.1.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a913633b0c922e6b745072795f50d90ebea78ba31a57e2ac8c2fc7b50950949" +checksum = "f52f63c5c1316a16a4b35eaac8b76a98248961a533f061684cb2a7cb0eafb6c6" [[package]] name = "arrayref" @@ -146,114 +216,133 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" [[package]] -name = "asn1_der" -version = "0.7.4" +name = "asn1-rs" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6e24d2cce90c53b948c46271bfb053e4bdc2db9b5d3f65e20f8cf28a1b7fc3" +checksum = "30ff05a702273012438132f449575dbc804e27b2f3cbe3069aa237d26c98fa33" +dependencies = [ + "asn1-rs-derive 0.1.0", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror", + "time 0.3.17", +] [[package]] -name = "assert_cmd" -version = "2.0.6" +name = "asn1-rs" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba45b8163c49ab5f972e59a8a5a03b6d2972619d486e19ec9fe744f7c2753d3c" +checksum = "cf6690c370453db30743b373a60ba498fc0d6d83b11f4abfd87a84a075db5dd4" dependencies = [ - "bstr 1.0.1", - "doc-comment", - "predicates", - "predicates-core", - "predicates-tree", - "wait-timeout", + "asn1-rs-derive 0.4.0", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror", + "time 0.3.17", ] [[package]] -name = "assert_matches" -version = "1.5.0" +name = "asn1-rs-derive" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" +checksum = "db8b7511298d5b7784b40b092d9e9dcd3a627a5707e4b5e507931ab0d44eeebf" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] [[package]] -name = "async-channel" -version = "1.6.1" +name = "asn1-rs-derive" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" +checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" dependencies = [ - "concurrent-queue", - "event-listener", - "futures-core", + "proc-macro2", + "quote", + "syn", + "synstructure", ] [[package]] -name = "async-executor" -version = "1.4.0" +name = "asn1-rs-impl" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb877970c7b440ead138f6321a3b5395d6061183af779340b65e20c0fede9146" +checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" dependencies = [ - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "once_cell", - "vec-arena", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "async-global-executor" -version = "2.0.2" +name = "asn1_der" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22d1f4b888c298a027c99dc9048015fac177587de20fc30232a057dfbe24a21" + +[[package]] +name = "assert_cmd" +version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9586ec52317f36de58453159d48351bc244bc24ced3effc1fce22f3d48664af6" +checksum = "fa3d466004a8b4cb1bc34044240a2fd29d17607e2e3bd613eb44fd48e8100da3" dependencies = [ - "async-channel", - "async-executor", - "async-io", - "async-mutex", - "blocking", - "futures-lite", - "num_cpus", - "once_cell", + "bstr 1.1.0", + "doc-comment", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", ] +[[package]] +name = "assert_matches" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" + [[package]] name = "async-io" -version = "1.6.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a811e6a479f2439f0c04038796b5cfb3d2ad56c230e0f2d3f7b04d68cfee607b" +checksum = "8c374dda1ed3e7d8f0d9ba58715f924862c63eae6849c92d3a18e7fbde9e2794" dependencies = [ + "async-lock", + "autocfg", "concurrent-queue", "futures-lite", "libc", "log", - "once_cell", "parking", "polling", "slab", "socket2", "waker-fn", - "winapi", + "windows-sys 0.42.0", ] [[package]] name = "async-lock" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6" -dependencies = [ - "event-listener", -] - -[[package]] -name = "async-mutex" -version = "1.4.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" +checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685" dependencies = [ "event-listener", + "futures-lite", ] [[package]] name = "async-stream" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625" +checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" dependencies = [ "async-stream-impl", "futures-core", @@ -261,26 +350,20 @@ dependencies = [ [[package]] name = "async-stream-impl" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308" +checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "async-task" -version = "4.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0" - [[package]] name = "async-trait" -version = "0.1.59" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6e93155431f3931513b243d371981bb2770112b370c82745a1d19d2f99364" +checksum = "677d1d8ab452a3936018a687b20e6f7cf5363d713b732b8884001317b0e48aa3" dependencies = [ "proc-macro2", "quote", @@ -289,9 +372,9 @@ dependencies = [ [[package]] name = "asynchronous-codec" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0de5164e5edbf51c45fb8c2d9664ae1c095cce1b265ecf7569093c0d66ef690" +checksum = "06a0daa378f5fd10634e44b0a29b2a87b890657658e072a30d6f26e57ddee182" dependencies = [ "bytes", "futures-sink", @@ -312,7 +395,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi", ] @@ -333,16 +416,16 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide 0.6.2", + "miniz_oxide", "object 0.30.0", "rustc-demangle", ] [[package]] name = "base-x" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" +checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" [[package]] name = "base16ct" @@ -358,21 +441,21 @@ checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" [[package]] name = "base64" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64ct" -version = "1.5.1" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdca834647821e0b13d9539a8634eb62d3501b6b6c2cec1722786ee6671b851" +checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf" [[package]] name = "beef" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bed554bd50246729a1ec158d08aa3235d1b69d94ad120ebe187e28894787e736" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" dependencies = [ "serde", ] @@ -489,9 +572,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitvec" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1489fcb93a5bb47da0462ca93ad252ad6af2145cce58d10d46a83931ba9f016b" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" dependencies = [ "funty", "radium", @@ -501,11 +584,11 @@ dependencies = [ [[package]] name = "blake2" -version = "0.10.4" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest 0.10.3", + "digest 0.10.6", ] [[package]] @@ -516,7 +599,7 @@ checksum = "72936ee4afc7f8f736d1c38383b56480b5497b4617b4a77bdbf1d2ababc76127" dependencies = [ "arrayref", "arrayvec 0.7.2", - "constant_time_eq", + "constant_time_eq 0.1.5", ] [[package]] @@ -527,20 +610,20 @@ checksum = "db539cc2b5f6003621f1cd9ef92d7ded8ea5232c7de0f9faa2de251cd98730d4" dependencies = [ "arrayref", "arrayvec 0.7.2", - "constant_time_eq", + "constant_time_eq 0.1.5", ] [[package]] name = "blake3" -version = "1.3.1" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08e53fc5a564bb15bfe6fae56bd71522205f1f91893f9c0116edad6496c183f" +checksum = "42ae2468a89544a466886840aa467a25b766499f4f04bf7d9fcd10ecee9fccef" dependencies = [ "arrayref", "arrayvec 0.7.2", "cc", "cfg-if", - "constant_time_eq", + "constant_time_eq 0.2.4", ] [[package]] @@ -549,7 +632,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" dependencies = [ - "block-padding", + "block-padding 0.1.5", "byte-tools", "byteorder", "generic-array 0.12.4", @@ -561,16 +644,26 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.6", ] [[package]] name = "block-buffer" -version = "0.10.0" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array 0.14.6", +] + +[[package]] +name = "block-modes" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1d36a02058e76b040de25a4464ba1c80935655595b661505c8b39b664828b95" +checksum = "57a0e8073e8baa88212fb5823574c02ebccb395136ba9a164ab89379ec6072f0" dependencies = [ - "generic-array 0.14.4", + "block-padding 0.2.1", + "cipher 0.2.5", ] [[package]] @@ -583,18 +676,10 @@ dependencies = [ ] [[package]] -name = "blocking" -version = "1.0.2" +name = "block-padding" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e170dbede1f740736619b776d7251cb1b9095c435c34d8ca9f57fcd2f335e9" -dependencies = [ - "async-channel", - "async-task", - "atomic-waker", - "fastrand", - "futures-lite", - "once_cell", -] +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] name = "bs58" @@ -604,9 +689,9 @@ checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" [[package]] name = "bstr" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a40b47ad93e1a5404e6c18dec46b628214fee441c70f4ab5d6942142cc268a3d" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" dependencies = [ "lazy_static", "memchr", @@ -616,9 +701,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fca0852af221f458706eb0725c03e4ed6c46af9ac98e6a689d5e634215d594dd" +checksum = "b45ea9b00a7b3f2988e9a65ad3917e62123c38dba709b666506207be96d1790b" dependencies = [ "memchr", "once_cell", @@ -637,15 +722,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.6.1" +version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" [[package]] name = "byte-slice-cast" -version = "1.0.0" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65c1bf4a04a88c54f589125563643d773f3254b5c38571395e2b591c693bbc81" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" [[package]] name = "byte-tools" @@ -661,9 +746,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" [[package]] name = "bzip2-sys" @@ -676,26 +761,20 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "cache-padded" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" - [[package]] name = "camino" -version = "1.0.4" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4648c6d00a709aa069a236adcaae4f605a6241c72bf5bee79331a4b625921a9" +checksum = "88ad0e1e3e88dd237a156ab9f571021b8a158caa0ae44b1968a241efb5144c1e" dependencies = [ "serde", ] [[package]] name = "cargo-platform" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0226944a63d1bf35a3b5f948dd7c59e263db83695c9e8bffc4037de02e30f1d7" +checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" dependencies = [ "serde", ] @@ -708,29 +787,37 @@ checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" dependencies = [ "camino", "cargo-platform", - "semver 1.0.4", + "semver 1.0.16", "serde", "serde_json", ] [[package]] name = "cast" -version = "0.2.3" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0" -dependencies = [ - "rustc_version 0.2.3", -] +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.73" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" dependencies = [ "jobserver", ] +[[package]] +name = "ccm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aca1a8fbc20b50ac9673ff014abfb2b5f4085ee1a850d408f14a159c5853ac7" +dependencies = [ + "aead 0.3.2", + "cipher 0.2.5", + "subtle", +] + [[package]] name = "cexpr" version = "0.6.0" @@ -768,7 +855,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" dependencies = [ "cfg-if", - "cipher", + "cipher 0.3.0", "cpufeatures", "zeroize", ] @@ -779,9 +866,9 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5" dependencies = [ - "aead", + "aead 0.4.3", "chacha20", - "cipher", + "cipher 0.3.0", "poly1305", "zeroize", ] @@ -791,7 +878,7 @@ name = "chain-spec-builder" version = "2.0.0" dependencies = [ "ansi_term", - "clap 4.0.11", + "clap 4.0.32", "node-cli", "rand 0.8.5", "sc-chain-spec", @@ -802,14 +889,16 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" dependencies = [ - "libc", + "iana-time-zone", + "js-sys", "num-integer", "num-traits", - "time", + "time 0.1.45", + "wasm-bindgen", "winapi", ] @@ -826,13 +915,22 @@ dependencies = [ "unsigned-varint", ] +[[package]] +name = "cipher" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801" +dependencies = [ + "generic-array 0.14.6", +] + [[package]] name = "cipher" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.6", ] [[package]] @@ -846,9 +944,9 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.2.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "853eda514c284c2287f4bf20ae614f8781f40a81d32ecda6e91449304dfe077c" +checksum = "fa2e27ae6ab525c3d369ded447057bca5438d86dc3a68f6faafb8269ba82ebf3" dependencies = [ "glob", "libc", @@ -868,14 +966,14 @@ dependencies = [ [[package]] name = "clap" -version = "4.0.11" +version = "4.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ed45cc2c62a3eff523e718d8576ba762c83a3146151093283ac62ae11933a73" +checksum = "a7db700bc935f9e43e88d00b0850dae18a63773cfbec6d8e070fccf7fef89a39" dependencies = [ - "atty", "bitflags", "clap_derive", "clap_lex", + "is-terminal", "once_cell", "strsim", "termcolor", @@ -883,18 +981,18 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.0.5" +version = "4.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b0fba905b035a30d25c1b585bf1171690712fbb0ad3ac47214963aa4acc36c" +checksum = "10861370d2ba66b0f5989f83ebf35db6421713fd92351790e7fdd6c36774c56b" dependencies = [ - "clap 4.0.11", + "clap 4.0.32", ] [[package]] name = "clap_derive" -version = "4.0.10" +version = "4.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db342ce9fda24fb191e2ed4e102055a4d381c1086a06630174cd8da8d5d917ce" +checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" dependencies = [ "heck", "proc-macro-error", @@ -924,9 +1022,9 @@ dependencies = [ [[package]] name = "comfy-table" -version = "6.0.0" +version = "6.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121d8a5b0346092c18a4b2fd6f620d7a06f0eb7ac0a45860939a0884bc579c56" +checksum = "6e7b787b0dc42e8111badfdbe4c3059158ccb2db8780352fa1b01e8ccf45cc4d" dependencies = [ "strum", "strum_macros", @@ -935,18 +1033,18 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "1.2.2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" +checksum = "bd7bef69dc86e3c610e4e7aed41035e2a7ed12e72dd7530f61327a6579a4390b" dependencies = [ - "cache-padded", + "crossbeam-utils", ] [[package]] name = "const-oid" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "722e23542a15cea1f65d4a1419c4cfd7a26706c70871a13a04238ca3f40f1661" +checksum = "cec318a675afcb6a1ea1d4340e2d377e56e47c266f28043ceccbf4412ddfdd3b" [[package]] name = "constant_time_eq" @@ -954,6 +1052,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "constant_time_eq" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3ad85c1f65dc7b37604eb0e89748faf0b9653065f2a8ef69f96a687ec1e9279" + [[package]] name = "core-foundation" version = "0.9.3" @@ -981,43 +1085,42 @@ dependencies = [ [[package]] name = "cpp_demangle" -version = "0.3.2" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44919ecaf6f99e8e737bc239408931c9a01e9a6c74814fee8242dd2506b65390" +checksum = "eeaa953eaad386a53111e47172c2fedba671e5684c8dd601a5f474f4f118710f" dependencies = [ "cfg-if", - "glob", ] [[package]] name = "cpufeatures" -version = "0.2.1" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" dependencies = [ "libc", ] [[package]] name = "cpuid-bool" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" +checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba" [[package]] name = "cranelift-bforest" -version = "0.88.0" +version = "0.88.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b27bbd3e6c422cf6282b047bcdd51ecd9ca9f3497a3be0132ffa08e509b824b0" +checksum = "52056f6d0584484b57fa6c1a65c1fcb15f3780d8b6a758426d9e3084169b2ddd" dependencies = [ "cranelift-entity", ] [[package]] name = "cranelift-codegen" -version = "0.88.0" +version = "0.88.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872f5d4557a411b087bd731df6347c142ae1004e6467a144a7e33662e5715a01" +checksum = "18fed94c8770dc25d01154c3ffa64ed0b3ba9d583736f305fed7beebe5d9cf74" dependencies = [ "arrayvec 0.7.2", "bumpalo", @@ -1026,7 +1129,7 @@ dependencies = [ "cranelift-codegen-shared", "cranelift-entity", "cranelift-isle", - "gimli 0.26.1", + "gimli 0.26.2", "log", "regalloc2", "smallvec", @@ -1035,33 +1138,33 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.88.0" +version = "0.88.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b49fdebb29c62c1fc4da1eeebd609e9d530ecde24a9876def546275f73a244" +checksum = "1c451b81faf237d11c7e4f3165eeb6bac61112762c5cfe7b4c0fb7241474358f" dependencies = [ "cranelift-codegen-shared", ] [[package]] name = "cranelift-codegen-shared" -version = "0.88.0" +version = "0.88.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc0c091e2db055d4d7f6b7cec2d2ead286bcfaea3357c6a52c2a2613a8cb5ac" +checksum = "e7c940133198426d26128f08be2b40b0bd117b84771fd36798969c4d712d81fc" [[package]] name = "cranelift-entity" -version = "0.88.0" +version = "0.88.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "354a9597be87996c9b278655e68b8447f65dd907256855ad773864edee8d985c" +checksum = "87a0f1b2fdc18776956370cf8d9b009ded3f855350c480c1c52142510961f352" dependencies = [ "serde", ] [[package]] name = "cranelift-frontend" -version = "0.88.0" +version = "0.88.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cd8dd3fb8b82c772f4172e87ae1677b971676fffa7c4e3398e3047e650a266b" +checksum = "34897538b36b216cc8dd324e73263596d51b8cf610da6498322838b2546baf8a" dependencies = [ "cranelift-codegen", "log", @@ -1071,15 +1174,15 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.88.0" +version = "0.88.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b82527802b1f7d8da288adc28f1dc97ea52943f5871c041213f7b5035ac698a7" +checksum = "1b2629a569fae540f16a76b70afcc87ad7decb38dc28fa6c648ac73b51e78470" [[package]] name = "cranelift-native" -version = "0.88.0" +version = "0.88.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c30ba8b910f1be023af0c39109cb28a8809734942a6b3eecbf2de8993052ea5e" +checksum = "20937dab4e14d3e225c5adfc9c7106bafd4ac669bdb43027b911ff794c6fb318" dependencies = [ "cranelift-codegen", "libc", @@ -1088,9 +1191,9 @@ dependencies = [ [[package]] name = "cranelift-wasm" -version = "0.88.0" +version = "0.88.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "776a8916d201894aca9637a20814f1e11abc62acd5cfbe0b4eb2e63922756971" +checksum = "80fc2288957a94fd342a015811479de1837850924166d1f1856d8406e6f3609b" dependencies = [ "cranelift-codegen", "cranelift-entity", @@ -1102,20 +1205,35 @@ dependencies = [ "wasmtime-types", ] +[[package]] +name = "crc" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53757d12b596c16c78b83458d732a5d1a17ab3f53f2f7412f6fb57cc8a140ab3" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d0165d2900ae6778e36e80bbc4da3b5eefccee9ba939761f9c2882a5d9af3ff" + [[package]] name = "crc32fast" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if", ] [[package]] name = "criterion" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1604dafd25fba2fe2d5895a9da139f8dc9b319a5fe5354ca137cbbce4e178d10" +checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f" dependencies = [ "atty", "cast", @@ -1141,9 +1259,9 @@ dependencies = [ [[package]] name = "criterion-plot" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d00996de9f2f7559f7f4dc286073197f83e92256a59ed395f9aac01fe717da57" +checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876" dependencies = [ "cast", "itertools", @@ -1151,9 +1269,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.0" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" dependencies = [ "cfg-if", "crossbeam-utils", @@ -1161,9 +1279,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" dependencies = [ "cfg-if", "crossbeam-epoch", @@ -1172,25 +1290,24 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.5" +version = "0.9.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" +checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" dependencies = [ + "autocfg", "cfg-if", "crossbeam-utils", - "lazy_static", - "memoffset", + "memoffset 0.7.1", "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.8" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" dependencies = [ "cfg-if", - "lazy_static", ] [[package]] @@ -1201,23 +1318,23 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-bigint" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f2b443d17d49dad5ef0ede301c3179cc923b8822f3393b4d2c28c269dd4a122" +checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" dependencies = [ - "generic-array 0.14.4", - "rand_core 0.6.2", + "generic-array 0.14.6", + "rand_core 0.6.4", "subtle", "zeroize", ] [[package]] name = "crypto-common" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.6", "typenum", ] @@ -1227,27 +1344,37 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.6", "subtle", ] [[package]] name = "crypto-mac" -version = "0.11.1" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +checksum = "bff07008ec701e8028e2ceb8f83f0e4274ee62bd2dbdc4fefff2e9a91824081a" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.6", "subtle", ] [[package]] -name = "csv" +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array 0.14.6", + "subtle", +] + +[[package]] +name = "csv" version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" dependencies = [ - "bstr 0.2.15", + "bstr 0.2.17", "csv-core", "itoa 0.4.8", "ryu", @@ -1265,28 +1392,37 @@ dependencies = [ [[package]] name = "ctor" -version = "0.1.19" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8f45d9ad417bcef4817d614a501ab55cdd96a6fdb24f49aab89a54acfd66b19" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" dependencies = [ "quote", "syn", ] +[[package]] +name = "ctr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb4a30d54f7443bf3d6191dcd486aca19e67cb3c49fa7a06a319966346707e7f" +dependencies = [ + "cipher 0.2.5", +] + [[package]] name = "ctr" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" dependencies = [ - "cipher", + "cipher 0.3.0", ] [[package]] name = "curve25519-dalek" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434e1720189a637d44fe464f4df1e6eb900b4835255b14354497c78af37d9bb8" +checksum = "4a9b85542f99a2dfa2a1b8e192662741c9859a846b296bef1c92ef9b58b5a216" dependencies = [ "byteorder", "digest 0.8.1", @@ -1297,9 +1433,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "3.0.2" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f627126b946c25a4638eec0ea634fc52506dea98db118aae985118ce7c3d723f" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" dependencies = [ "byteorder", "digest 0.9.0", @@ -1310,22 +1446,23 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.0.0-pre.1" +version = "4.0.0-pre.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4033478fbf70d6acf2655ac70da91ee65852d69daf7a67bf7a2f518fb47aafcf" +checksum = "67bc65846be335cb20f4e52d49a437b773a2c1fdb42b19fc84e79e6f6771536f" dependencies = [ - "byteorder", - "digest 0.9.0", - "rand_core 0.6.2", + "cfg-if", + "fiat-crypto", + "packed_simd_2", + "platforms 3.0.2", "subtle", "zeroize", ] [[package]] name = "cxx" -version = "1.0.80" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b7d4e43b25d3c994662706a1d4fcfc32aaa6afd287502c111b237093bb23f3a" +checksum = "5add3fc1717409d029b20c5b6903fc0c0b02fa6741d820054f4a2efa5e5816fd" dependencies = [ "cc", "cxxbridge-flags", @@ -1335,9 +1472,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.80" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84f8829ddc213e2c1368e51a2564c552b65a8cb6a28f31e576270ac81d5e5827" +checksum = "b4c87959ba14bc6fbc61df77c3fcfe180fc32b93538c4f1031dd802ccb5f2ff0" dependencies = [ "cc", "codespan-reporting", @@ -1350,32 +1487,67 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.80" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e72537424b474af1460806647c41d4b6d35d09ef7fe031c5c2fa5766047cc56a" +checksum = "69a3e162fde4e594ed2b07d0f83c6c67b745e7f28ce58c6df5e6b6bef99dfb59" [[package]] name = "cxxbridge-macro" -version = "1.0.80" +version = "1.0.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e7e2adeb6a0d4a282e581096b06e1791532b7d576dcde5ccd9382acf55db8e6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "darling" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "309e4fb93eed90e1e14bea0da16b209f81813ba9fc7830c20ed151dd7bc0a4d7" +checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f" dependencies = [ + "fnv", + "ident_case", "proc-macro2", "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e" +dependencies = [ + "darling_core", + "quote", "syn", ] [[package]] name = "data-encoding" -version = "2.3.2" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" +checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb" [[package]] name = "data-encoding-macro" -version = "0.1.10" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a94feec3d2ba66c0b6621bca8bc6f68415b1e5c69af3586fdd0af9fd9f29b17" +checksum = "86927b7cd2fe88fa698b87404b287ab98d1a0063a34071d92e575b72d3029aca" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -1383,9 +1555,9 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f83e699727abca3c56e187945f303389590305ab2f0185ea445aa66e8d5f2a" +checksum = "a5bbed42daaa95e780b60a50546aa345b8413a1e46f9a40a12907d3598f038db" dependencies = [ "data-encoding", "syn", @@ -1393,14 +1565,74 @@ dependencies = [ [[package]] name = "der" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dd2ae565c0a381dde7fade45fce95984c568bdcb4700a4fdbe3175e0380b2f" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" dependencies = [ "const-oid", + "pem-rfc7468", "zeroize", ] +[[package]] +name = "der-parser" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe398ac75057914d7d07307bf67dc7f3f574a26783b4fc7805a20ffa9f506e82" +dependencies = [ + "asn1-rs 0.3.1", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "der-parser" +version = "8.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d4bc9b0db0a0df9ae64634ac5bdefb7afcb534e182275ca0beadbe486701c1" +dependencies = [ + "asn1-rs 0.5.1", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "derive_builder" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07adf7be193b71cc36b193d0f5fe60b918a3a9db4dad0449f57bcfd519704a3" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f91d4cfa921f1c05904dc3c57b4a32c38aed3340cce209f3a6fd1478babafc4" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_builder_macro" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f0314b72bed045f3a68671b3c86328386762c93f82d98c65c3cb5e5f573dd68" +dependencies = [ + "derive_builder_core", + "syn", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -1414,9 +1646,9 @@ dependencies = [ [[package]] name = "diff" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" [[package]] name = "difflib" @@ -1439,16 +1671,16 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.6", ] [[package]] name = "digest" -version = "0.10.3" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ - "block-buffer 0.10.0", + "block-buffer 0.10.3", "crypto-common", "subtle", ] @@ -1474,9 +1706,9 @@ dependencies = [ [[package]] name = "dirs-sys" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" dependencies = [ "libc", "redox_users", @@ -1495,20 +1727,21 @@ dependencies = [ ] [[package]] -name = "dissimilar" -version = "1.0.2" +name = "displaydoc" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4b29f4b9bb94bf267d57269fd0706d343a160937108e9619fe380645428abb" +checksum = "3bf95dc3f046b9da4f2d51833c0d3547d8564ef6910f5c1ed130306a75b92886" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] -name = "dns-parser" -version = "0.8.0" +name = "dissimilar" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4d33be9473d06f75f58220f71f7a9317aca647dc061dbd3c361b0bef505fbea" -dependencies = [ - "byteorder", - "quick-error", -] +checksum = "bd5f0c7e4bd266b8ab2550e6238d2e74977c23c15536ac7be45e9c95e2e3fbbb" [[package]] name = "doc-comment" @@ -1530,9 +1763,9 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] name = "dtoa" -version = "1.0.2" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5caaa75cbd2b960ff1e5392d2cfb1f44717fffe12fc1f32b7b5d1267f99732a6" +checksum = "c00704156a7de8df8da0911424e30c2049957b0a714542a44e05fe693dd85313" [[package]] name = "dyn-clonable" @@ -1557,15 +1790,15 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.5" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e50f3adc76d6a43f5ed73b698a87d0760ca74617f60f7c3b879003536fdd28" +checksum = "c9b0705efd4599c15a38151f4721f7bc388306f61084d3bfd50bd07fbca5cb60" [[package]] name = "ecdsa" -version = "0.14.7" +version = "0.14.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85789ce7dfbd0f0624c07ef653a08bb2ebf43d3e16531361f46d36dd54334fed" +checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" dependencies = [ "der", "elliptic-curve", @@ -1588,11 +1821,11 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" dependencies = [ - "curve25519-dalek 3.0.2", + "curve25519-dalek 3.2.0", "ed25519", "rand 0.7.3", "serde", - "sha2 0.9.8", + "sha2 0.9.9", "zeroize", ] @@ -1602,19 +1835,19 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" dependencies = [ - "curve25519-dalek 3.0.2", - "hashbrown 0.12.3", + "curve25519-dalek 3.2.0", + "hashbrown", "hex", - "rand_core 0.6.2", - "sha2 0.9.8", + "rand_core 0.6.4", + "sha2 0.9.9", "zeroize", ] [[package]] name = "either" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "elliptic-curve" @@ -1625,11 +1858,14 @@ dependencies = [ "base16ct", "crypto-bigint", "der", - "digest 0.10.3", + "digest 0.10.6", "ff", - "generic-array 0.14.4", + "generic-array 0.14.6", "group", - "rand_core 0.6.2", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", "sec1", "subtle", "zeroize", @@ -1669,9 +1905,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.9.0" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" dependencies = [ "atty", "humantime", @@ -1682,9 +1918,9 @@ dependencies = [ [[package]] name = "environmental" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b91989ae21441195d7d9b9993a2f9295c7e1a8c96255d8b729accddc124797" +checksum = "e48c92028aaa870e83d51c64e5d4e0b6981b360c522198c23959f219a4e1b15b" [[package]] name = "errno" @@ -1699,19 +1935,19 @@ dependencies = [ [[package]] name = "errno-dragonfly" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" dependencies = [ - "gcc", + "cc", "libc", ] [[package]] name = "event-listener" -version = "2.5.1" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "exit-future" @@ -1736,9 +1972,9 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fastrand" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" dependencies = [ "instant", ] @@ -1754,14 +1990,20 @@ dependencies = [ [[package]] name = "ff" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df689201f395c6b90dfe87127685f8dbfc083a5e779e613575d8bd7314300c3e" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" dependencies = [ - "rand_core 0.6.2", + "rand_core 0.6.4", "subtle", ] +[[package]] +name = "fiat-crypto" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a214f5bb88731d436478f3ae1f8a277b62124089ba9fb67f4f93fb100ef73c90" + [[package]] name = "file-per-thread-logger" version = "0.1.5" @@ -1774,14 +2016,14 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.16" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0408e2626025178a6a7f7ffc05a25bc47103229f19c113755de7bf63816290c" +checksum = "4e884668cd0c7480504233e951174ddc3b382f7c2666e3b7310b5c4e7b0c37f9" dependencies = [ "cfg-if", "libc", "redox_syscall", - "winapi", + "windows-sys 0.42.0", ] [[package]] @@ -1815,21 +2057,19 @@ dependencies = [ [[package]] name = "fixedbitset" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "398ea4fabe40b9b0d885340a2a991a44c8a645624075ad966d21f88688e2b69e" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.20" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" dependencies = [ - "cfg-if", "crc32fast", - "libc", "libz-sys", - "miniz_oxide 0.4.4", + "miniz_oxide", ] [[package]] @@ -1856,19 +2096,18 @@ dependencies = [ [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" dependencies = [ - "matches", "percent-encoding", ] [[package]] name = "fragile" -version = "1.2.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85dcb89d2b10c5f6133de2efd8c11959ce9dbb46a2f7a4cab208c4eeda6ce1ab" +checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" [[package]] name = "frame-benchmarking" @@ -1902,7 +2141,7 @@ dependencies = [ "Inflector", "array-bytes", "chrono", - "clap 4.0.11", + "clap 4.0.32", "comfy-table", "frame-benchmarking", "frame-support", @@ -1979,7 +2218,7 @@ dependencies = [ name = "frame-election-solution-type-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 4.0.11", + "clap 4.0.32", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-support", @@ -2234,9 +2473,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.21" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" dependencies = [ "futures-channel", "futures-core", @@ -2249,9 +2488,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.21" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" dependencies = [ "futures-core", "futures-sink", @@ -2259,15 +2498,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.21" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" [[package]] name = "futures-executor" -version = "0.3.21" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" dependencies = [ "futures-core", "futures-task", @@ -2277,15 +2516,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.21" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" +checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" [[package]] name = "futures-lite" -version = "1.11.3" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4481d0cd0de1d204a4fa55e7d45f07b1d958abcb06714b3446438e2eff695fb" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" dependencies = [ "fastrand", "futures-core", @@ -2298,9 +2537,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.21" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" dependencies = [ "proc-macro2", "quote", @@ -2309,26 +2548,26 @@ dependencies = [ [[package]] name = "futures-rustls" -version = "0.22.1" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01fe9932a224b72b45336d96040aa86386d674a31d0af27d800ea7bc8ca97fe" +checksum = "d2411eed028cdf8c8034eaf21f9915f956b6c3abec4d4c7949ee67f0721127bd" dependencies = [ "futures-io", - "rustls", - "webpki", + "rustls 0.20.7", + "webpki 0.22.0", ] [[package]] name = "futures-sink" -version = "0.3.21" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" +checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" [[package]] name = "futures-task" -version = "0.3.21" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" [[package]] name = "futures-timer" @@ -2338,9 +2577,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" -version = "0.3.21" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" dependencies = [ "futures-channel", "futures-core", @@ -2363,12 +2602,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "gcc" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" - [[package]] name = "generate-bags" version = "4.0.0-dev" @@ -2393,9 +2626,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.4" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", "version_check", @@ -2424,13 +2657,23 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.3" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", "libc", - "wasi 0.10.0+wasi-snapshot-preview1", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "ghash" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97304e4cd182c3846f7575ced3890c53012ce534ad9114046b0a9e00bb30a375" +dependencies = [ + "opaque-debug 0.3.0", + "polyval 0.4.5", ] [[package]] @@ -2440,14 +2683,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" dependencies = [ "opaque-debug 0.3.0", - "polyval", + "polyval 0.5.3", ] [[package]] name = "gimli" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" dependencies = [ "fallible-iterator", "indexmap", @@ -2462,9 +2705,9 @@ checksum = "dec7af912d60cdbd3677c1af9352ebae6fb8394d165568a2234df0fa00f87793" [[package]] name = "git2" -version = "0.14.2" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3826a6e0e2215d7a41c2bfc7c9244123969273f3476b939a226aac0ab56e9e3c" +checksum = "d0155506aab710a86160ddb504a480d2964d7ab5b9e62419be69e0032bc5931c" dependencies = [ "bitflags", "libc", @@ -2481,12 +2724,12 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "globset" -version = "0.4.6" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c152169ef1e421390738366d2f796655fec62621dabbd0fd476f905934061e4a" +checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a" dependencies = [ "aho-corasick", - "bstr 0.2.15", + "bstr 0.2.17", "fnv", "log", "regex", @@ -2494,20 +2737,20 @@ dependencies = [ [[package]] name = "group" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7391856def869c1c81063a03457c676fbcd419709c3dfb33d8d319de484b154d" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" dependencies = [ "ff", - "rand_core 0.6.2", + "rand_core 0.6.4", "subtle", ] [[package]] name = "h2" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" +checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" dependencies = [ "bytes", "fnv", @@ -2518,21 +2761,21 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.7.4", + "tokio-util", "tracing", ] [[package]] name = "half" -version = "1.7.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "handlebars" -version = "4.3.5" +version = "4.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433e4ab33f1213cdc25b5fa45c76881240cfe79284cf2b395e8b9e312a30a2fd" +checksum = "035ef95d03713f2c347a72547b7cd38cbc9af7cd51e6099fb62d586d4a6dee3a" dependencies = [ "log", "pest", @@ -2557,12 +2800,6 @@ dependencies = [ "crunchy", ] -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" - [[package]] name = "hashbrown" version = "0.12.3" @@ -2580,9 +2817,18 @@ checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" [[package]] name = "hermit-abi" -version = "0.1.18" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" dependencies = [ "libc", ] @@ -2593,6 +2839,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac 0.12.1", +] + [[package]] name = "hmac" version = "0.8.1" @@ -2603,6 +2858,16 @@ dependencies = [ "digest 0.9.0", ] +[[package]] +name = "hmac" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15" +dependencies = [ + "crypto-mac 0.10.1", + "digest 0.9.0", +] + [[package]] name = "hmac" version = "0.11.0" @@ -2619,7 +2884,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.3", + "digest 0.10.6", ] [[package]] @@ -2629,7 +2894,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" dependencies = [ "digest 0.9.0", - "generic-array 0.14.4", + "generic-array 0.14.6", "hmac 0.8.1", ] @@ -2664,7 +2929,7 @@ checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ "bytes", "fnv", - "itoa 1.0.4", + "itoa 1.0.5", ] [[package]] @@ -2692,9 +2957,9 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "humantime" @@ -2704,9 +2969,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.22" +version = "0.14.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abfba89e19b959ca163c7752ba59d737c1ceea53a5d31a149c805446fc958064" +checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" dependencies = [ "bytes", "futures-channel", @@ -2717,7 +2982,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.4", + "itoa 1.0.5", "pin-project-lite 0.2.9", "socket2", "tokio", @@ -2728,19 +2993,49 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.23.0" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" +checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" dependencies = [ "http", "hyper", "log", - "rustls", + "rustls 0.20.7", "rustls-native-certs", "tokio", "tokio-rustls", ] +[[package]] +name = "iana-time-zone" +version = "0.1.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + +[[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" @@ -2752,6 +3047,16 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "if-addrs" version = "0.7.0" @@ -2764,9 +3069,9 @@ dependencies = [ [[package]] name = "if-watch" -version = "2.0.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "065c008e570a43c00de6aed9714035e5ea6a498c255323db9091722af6ee67dd" +checksum = "ba7abdbb86e485125dad06c2691e1e393bf3b08c7b743b43aa162a00fd39062e" dependencies = [ "async-io", "core-foundation", @@ -2777,6 +3082,7 @@ dependencies = [ "log", "rtnetlink", "system-configuration", + "tokio", "windows", ] @@ -2811,12 +3117,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.8.0" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", - "hashbrown 0.11.2", + "hashbrown", "serde", ] @@ -2844,11 +3150,40 @@ dependencies = [ "num-traits", ] +[[package]] +name = "interceptor" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e8a11ae2da61704edada656798b61c94b35ecac2c58eb955156987d5e6be90b" +dependencies = [ + "async-trait", + "bytes", + "log", + "rand 0.8.5", + "rtcp", + "rtp", + "thiserror", + "tokio", + "waitgroup", + "webrtc-srtp", + "webrtc-util", +] + [[package]] name = "io-lifetimes" -version = "0.7.2" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24c3f4eff5495aee4c0399d7b6a0dc2b6e81be84242ffbfcf253ebacccc1d0cb" +checksum = "59ce5ef949d49ee85593fc4d3f3f95ad61657076395cbbce23e2121fc5542074" + +[[package]] +name = "io-lifetimes" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" +dependencies = [ + "libc", + "windows-sys 0.42.0", +] [[package]] name = "ip_network" @@ -2858,9 +3193,9 @@ checksum = "aa2f047c0a98b2f299aa5d6d7088443570faae494e9ae1305e48be000c9e0eb1" [[package]] name = "ipconfig" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "723519edce41262b05d4143ceb95050e4c614f483e78e9fd9e39a8275a84ad98" +checksum = "bd302af1b90f2463a98fa5ad469fc212c8e3175a41c3068601bfa2727591c5be" dependencies = [ "socket2", "widestring", @@ -2870,9 +3205,21 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.5.0" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11b0d96e660696543b251e58030cf9787df56da39dab19ad60eae7353040917e" + +[[package]] +name = "is-terminal" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" +checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" +dependencies = [ + "hermit-abi 0.2.6", + "io-lifetimes 1.0.3", + "rustix 0.36.6", + "windows-sys 0.42.0", +] [[package]] name = "itertools" @@ -2891,24 +3238,24 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" [[package]] name = "jobserver" -version = "0.1.21" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2" +checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.54" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1866b355d9c878e5e607473cbe3f63282c0b7aad2db1dbebf55076c686918254" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" dependencies = [ "wasm-bindgen", ] @@ -2943,7 +3290,7 @@ dependencies = [ "thiserror", "tokio", "tokio-rustls", - "tokio-util 0.7.4", + "tokio-util", "tracing", "webpki-roots", ] @@ -3006,7 +3353,7 @@ dependencies = [ "soketto", "tokio", "tokio-stream", - "tokio-util 0.7.4", + "tokio-util", "tower", "tracing", ] @@ -3039,21 +3386,24 @@ dependencies = [ [[package]] name = "k256" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3636d281d46c3b64182eb3a0a42b7b483191a2ecc3f05301fa67403f7c9bc949" +checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" dependencies = [ "cfg-if", "ecdsa", "elliptic-curve", - "sha2 0.10.2", + "sha2 0.10.6", ] [[package]] name = "keccak" -version = "0.1.0" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" +checksum = "3afef3b6eff9ce9d8ff9b3601125eec7f0c8cbac7abd14f355d053fa56c98768" +dependencies = [ + "cpufeatures", +] [[package]] name = "keccak-hasher" @@ -3208,21 +3558,21 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "leb128" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3576a87f2ba00f6f106fdfcd16db1d698d648a26ad8e0573cad8537c3c362d2a" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.126" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "libgit2-sys" -version = "0.13.2+1.4.2" +version = "0.13.4+1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a42de9a51a5c12e00fc0e4ca6bc2ea43582fc6418488e8f615e905d886f258b" +checksum = "d0fa6563431ede25f5cc7f6d803c6afbc1c5d3ad3d4925d12c882bf2b526f5d1" dependencies = [ "cc", "libc", @@ -3232,9 +3582,9 @@ dependencies = [ [[package]] name = "libloading" -version = "0.7.0" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" dependencies = [ "cfg-if", "winapi", @@ -3242,22 +3592,27 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.1" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" +checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a" + +[[package]] +name = "libm" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" [[package]] name = "libp2p" -version = "0.49.0" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec878fda12ebec479186b3914ebc48ff180fa4c51847e11a1a68bf65249e02c1" +checksum = "2e0a0d2f693675f49ded13c5d510c48b78069e23cbd9108d7ccd59f6dc568819" dependencies = [ "bytes", "futures", "futures-timer", - "getrandom 0.2.3", + "getrandom 0.2.8", "instant", - "lazy_static", "libp2p-core", "libp2p-dns", "libp2p-identify", @@ -3267,11 +3622,12 @@ dependencies = [ "libp2p-mplex", "libp2p-noise", "libp2p-ping", + "libp2p-quic", "libp2p-request-response", "libp2p-swarm", - "libp2p-swarm-derive", "libp2p-tcp", "libp2p-wasm-ext", + "libp2p-webrtc", "libp2p-websocket", "libp2p-yamux", "multiaddr", @@ -3282,9 +3638,9 @@ dependencies = [ [[package]] name = "libp2p-core" -version = "0.37.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799676bb0807c788065e57551c6527d461ad572162b0519d1958946ff9e0539d" +checksum = "b6a8fcd392ff67af6cc3f03b1426c41f7f26b6b9aff2dc632c1c56dd649e571f" dependencies = [ "asn1_der", "bs58", @@ -3294,18 +3650,19 @@ dependencies = [ "futures", "futures-timer", "instant", - "lazy_static", "log", "multiaddr", "multihash", "multistream-select", + "once_cell", "parking_lot 0.12.1", "pin-project", "prost", "prost-build", "rand 0.8.5", "rw-stream-sink", - "sha2 0.10.2", + "sec1", + "sha2 0.10.6", "smallvec", "thiserror", "unsigned-varint", @@ -3315,9 +3672,9 @@ dependencies = [ [[package]] name = "libp2p-dns" -version = "0.37.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2322c9fb40d99101def6a01612ee30500c89abbbecb6297b3cd252903a4c1720" +checksum = "8e42a271c1b49f789b92f7fc87749fa79ce5c7bdc88cbdfacb818a4bca47fec5" dependencies = [ "futures", "libp2p-core", @@ -3329,9 +3686,9 @@ dependencies = [ [[package]] name = "libp2p-identify" -version = "0.40.0" +version = "0.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf9a121f699e8719bda2e6e9e9b6ddafc6cff4602471d6481c1067930ccb29b" +checksum = "c052d0026f4817b44869bfb6810f4e1112f43aec8553f2cb38881c524b563abf" dependencies = [ "asynchronous-codec", "futures", @@ -3350,9 +3707,9 @@ dependencies = [ [[package]] name = "libp2p-kad" -version = "0.41.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6721c200e2021f6c3fab8b6cf0272ead8912d871610ee194ebd628cecf428f22" +checksum = "2766dcd2be8c87d5e1f35487deb22d765f49c6ae1251b3633efe3b25698bd3d2" dependencies = [ "arrayvec 0.7.2", "asynchronous-codec", @@ -3368,7 +3725,7 @@ dependencies = [ "prost", "prost-build", "rand 0.8.5", - "sha2 0.10.2", + "sha2 0.10.6", "smallvec", "thiserror", "uint", @@ -3378,12 +3735,11 @@ dependencies = [ [[package]] name = "libp2p-mdns" -version = "0.41.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "761704e727f7d68d58d7bc2231eafae5fc1b9814de24290f126df09d4bd37a15" +checksum = "04f378264aade9872d6ccd315c0accc18be3a35d15fc1b9c36e5b6f983b62b5b" dependencies = [ "data-encoding", - "dns-parser", "futures", "if-watch", "libp2p-core", @@ -3393,14 +3749,15 @@ dependencies = [ "smallvec", "socket2", "tokio", + "trust-dns-proto", "void", ] [[package]] name = "libp2p-metrics" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ee31b08e78b7b8bfd1c4204a9dd8a87b4fcdf6dafc57eb51701c1c264a81cb9" +checksum = "5ad8a64f29da86005c86a4d2728b8a0719e9b192f4092b609fd8790acb9dec55" dependencies = [ "libp2p-core", "libp2p-identify", @@ -3412,9 +3769,9 @@ dependencies = [ [[package]] name = "libp2p-mplex" -version = "0.37.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692664acfd98652de739a8acbb0a0d670f1d67190a49be6b4395e22c37337d89" +checksum = "03805b44107aa013e7cbbfa5627b31c36cbedfdfb00603c0311998882bc4bace" dependencies = [ "asynchronous-codec", "bytes", @@ -3430,31 +3787,32 @@ dependencies = [ [[package]] name = "libp2p-noise" -version = "0.40.0" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "048155686bd81fe6cb5efdef0c6290f25ad32a0a42e8f4f72625cf6a505a206f" +checksum = "a978cb57efe82e892ec6f348a536bfbd9fee677adbe5689d7a93ad3a9bffbf2e" dependencies = [ "bytes", - "curve25519-dalek 3.0.2", + "curve25519-dalek 3.2.0", "futures", - "lazy_static", "libp2p-core", "log", + "once_cell", "prost", "prost-build", "rand 0.8.5", - "sha2 0.10.2", + "sha2 0.10.6", "snow", "static_assertions", - "x25519-dalek", + "thiserror", + "x25519-dalek 1.1.1", "zeroize", ] [[package]] name = "libp2p-ping" -version = "0.40.1" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7228b9318d34689521349a86eb39a3c3a802c9efc99a0568062ffb80913e3f91" +checksum = "929fcace45a112536e22b3dcfd4db538723ef9c3cb79f672b98be2cc8e25f37f" dependencies = [ "futures", "futures-timer", @@ -3466,11 +3824,32 @@ dependencies = [ "void", ] +[[package]] +name = "libp2p-quic" +version = "0.7.0-alpha" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01e7c867e95c8130667b24409d236d37598270e6da69b3baf54213ba31ffca59" +dependencies = [ + "bytes", + "futures", + "futures-timer", + "if-watch", + "libp2p-core", + "libp2p-tls", + "log", + "parking_lot 0.12.1", + "quinn-proto", + "rand 0.8.5", + "rustls 0.20.7", + "thiserror", + "tokio", +] + [[package]] name = "libp2p-request-response" -version = "0.22.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8827af16a017b65311a410bb626205a9ad92ec0473967618425039fa5231adc1" +checksum = "3236168796727bfcf4927f766393415361e2c644b08bedb6a6b13d957c9a4884" dependencies = [ "async-trait", "bytes", @@ -3486,9 +3865,9 @@ dependencies = [ [[package]] name = "libp2p-swarm" -version = "0.40.1" +version = "0.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46d13df7c37807965d82930c0e4b04a659efcb6cca237373b206043db5398ecf" +checksum = "b2a35472fe3276b3855c00f1c032ea8413615e030256429ad5349cdf67c6e1a0" dependencies = [ "either", "fnv", @@ -3496,19 +3875,21 @@ dependencies = [ "futures-timer", "instant", "libp2p-core", + "libp2p-swarm-derive", "log", "pin-project", "rand 0.8.5", "smallvec", "thiserror", + "tokio", "void", ] [[package]] name = "libp2p-swarm-derive" -version = "0.30.1" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0eddc4497a8b5a506013c40e8189864f9c3a00db2b25671f428ae9007f3ba32" +checksum = "9d527d5827582abd44a6d80c07ff8b50b4ee238a8979e05998474179e79dc400" dependencies = [ "heck", "quote", @@ -3517,9 +3898,9 @@ dependencies = [ [[package]] name = "libp2p-tcp" -version = "0.37.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9839d96761491c6d3e238e70554b856956fca0ab60feb9de2cd08eed4473fa92" +checksum = "b4b257baf6df8f2df39678b86c578961d48cc8b68642a12f0f763f56c8e5858d" dependencies = [ "futures", "futures-timer", @@ -3531,11 +3912,29 @@ dependencies = [ "tokio", ] +[[package]] +name = "libp2p-tls" +version = "0.1.0-alpha" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7905ce0d040576634e8a3229a7587cc8beab83f79db6023800f1792895defa8" +dependencies = [ + "futures", + "futures-rustls", + "libp2p-core", + "rcgen 0.10.0", + "ring", + "rustls 0.20.7", + "thiserror", + "webpki 0.22.0", + "x509-parser 0.14.0", + "yasna", +] + [[package]] name = "libp2p-wasm-ext" -version = "0.37.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b5b8e7a73e379e47b1b77f8a82c4721e97eca01abcd18e9cd91a23ca6ce97" +checksum = "1bb1a35299860e0d4b3c02a3e74e3b293ad35ae0cee8a056363b0c862d082069" dependencies = [ "futures", "js-sys", @@ -3545,11 +3944,42 @@ dependencies = [ "wasm-bindgen-futures", ] +[[package]] +name = "libp2p-webrtc" +version = "0.4.0-alpha" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb6cd86dd68cba72308ea05de1cebf3ba0ae6e187c40548167955d4e3970f6a" +dependencies = [ + "async-trait", + "asynchronous-codec", + "bytes", + "futures", + "futures-timer", + "hex", + "if-watch", + "libp2p-core", + "libp2p-noise", + "log", + "multihash", + "prost", + "prost-build", + "prost-codec", + "rand 0.8.5", + "rcgen 0.9.3", + "serde", + "stun", + "thiserror", + "tinytemplate", + "tokio", + "tokio-util", + "webrtc", +] + [[package]] name = "libp2p-websocket" -version = "0.39.0" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3758ae6f89b2531a24b6d9f5776bda6a626b60a57600d7185d43dfa75ca5ecc4" +checksum = "1d705506030d5c0aaf2882437c70dab437605f21c5f9811978f694e6917a3b54" dependencies = [ "either", "futures", @@ -3566,9 +3996,9 @@ dependencies = [ [[package]] name = "libp2p-yamux" -version = "0.41.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30f079097a21ad017fc8139460630286f02488c8c13b26affb46623aa20d8845" +checksum = "4f63594a0aa818642d9d4915c791945053877253f08a3626f13416b5cd928a29" dependencies = [ "futures", "libp2p-core", @@ -3595,9 +4025,9 @@ dependencies = [ [[package]] name = "libsecp256k1" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0452aac8bab02242429380e9b2f94ea20cea2b37e2c1777a1358799bbe97f37" +checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" dependencies = [ "arrayref", "base64", @@ -3608,7 +4038,7 @@ dependencies = [ "libsecp256k1-gen-genmult", "rand 0.8.5", "serde", - "sha2 0.9.8", + "sha2 0.9.9", "typenum", ] @@ -3643,9 +4073,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.2" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "602113192b08db8f38796c4e85c39e960c145965140e918018bcde1952429655" +checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" dependencies = [ "cc", "libc", @@ -3655,18 +4085,18 @@ dependencies = [ [[package]] name = "link-cplusplus" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" dependencies = [ "cc", ] [[package]] name = "linked-hash-map" -version = "0.5.4" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linked_hash_set" @@ -3693,6 +4123,12 @@ version = "0.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d" +[[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + [[package]] name = "lite-json" version = "0.2.0" @@ -3713,10 +4149,11 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.6" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ + "autocfg", "scopeguard", ] @@ -3735,7 +4172,7 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6e8aaa3f231bb4bd57b84b2d5dc3ae7f350265df8aa96492e0bc394a1571909" dependencies = [ - "hashbrown 0.12.3", + "hashbrown", ] [[package]] @@ -3802,48 +4239,66 @@ dependencies = [ [[package]] name = "matches" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "matrixmultiply" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a8a15b776d9dfaecd44b03c5828c2199cddff5247215858aac14624f8d6b741" +checksum = "add85d4dd35074e6fedc608f8c8f513a3548619a9024b751949ef0e8e45a4d84" dependencies = [ "rawpointer", ] +[[package]] +name = "md-5" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" +dependencies = [ + "digest 0.10.6", +] + [[package]] name = "memchr" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memfd" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "480b5a5de855d11ff13195950bdc8b98b5e942ef47afc447f6615cdcc4e15d80" +checksum = "b20a59d985586e4a5aef64564ac77299f8586d8be6cf9106a5a40207e8908efb" dependencies = [ - "rustix", + "rustix 0.36.6", ] [[package]] name = "memmap2" -version = "0.5.0" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4647a11b578fead29cdbb34d4adef8dd3dc35b876c9c6d5240d83f205abfe96e" +checksum = "4b182332558b18d807c4ce1ca8ca983b34c3ee32765e47b3f0f69b90355cc1dc" dependencies = [ "libc", ] [[package]] name = "memoffset" -version = "0.6.4" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" dependencies = [ "autocfg", ] @@ -3855,7 +4310,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e0c7cba9ce19ac7ffd2053ac9f49843bbd3f4318feedfd74e85c19d5fb0ba66" dependencies = [ "hash-db", - "hashbrown 0.12.3", + "hashbrown", ] [[package]] @@ -3882,16 +4337,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "miniz_oxide" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" -dependencies = [ - "adler", - "autocfg", -] - [[package]] name = "miniz_oxide" version = "0.6.2" @@ -3903,14 +4348,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.36.1", + "windows-sys 0.42.0", ] [[package]] @@ -3954,9 +4399,9 @@ dependencies = [ [[package]] name = "mockall" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2be9a9090bc1cac2930688fa9478092a64c6a92ddc6ae0692d46b37d9cab709" +checksum = "50e4a1c770583dac7ab5e2f6c139153b783a53a1bbee9729613f193e59828326" dependencies = [ "cfg-if", "downcast", @@ -3969,9 +4414,9 @@ dependencies = [ [[package]] name = "mockall_derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86d702a0530a0141cf4ed147cf5ec7be6f2c187d4e37fcbefc39cf34116bfe8f" +checksum = "832663583d5fa284ca8810bf7015e46c9fff9622d3cf34bd1eea5003fec06dd0" dependencies = [ "cfg-if", "proc-macro2", @@ -3981,14 +4426,14 @@ dependencies = [ [[package]] name = "multiaddr" -version = "0.14.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c580bfdd8803cce319b047d239559a22f809094aaea4ac13902a1fdcfcd4261" +checksum = "a4aebdb21e90f81d13ed01dc84123320838e53963c2ca94b60b305d3fa64f31e" dependencies = [ "arrayref", - "bs58", "byteorder", "data-encoding", + "multibase", "multihash", "percent-encoding", "serde", @@ -4010,17 +4455,17 @@ dependencies = [ [[package]] name = "multihash" -version = "0.16.2" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3db354f401db558759dfc1e568d010a5d4146f4d3f637be1275ec4a3cf09689" +checksum = "1c346cf9999c631f002d8f977c4eaeaa0e6386f16007202308d0b3757522c2cc" dependencies = [ "blake2b_simd", "blake2s_simd", "blake3", "core2", - "digest 0.10.3", + "digest 0.10.6", "multihash-derive", - "sha2 0.10.2", + "sha2 0.10.6", "sha3", "unsigned-varint", ] @@ -4041,15 +4486,15 @@ dependencies = [ [[package]] name = "multimap" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1255076139a83bb467426e7f8d0134968a8118844faa755985e077cf31850333" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "multistream-select" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bc41247ec209813e2fd414d6e16b9d94297dacf3cd613fa6ef09cd4d9755c10" +checksum = "c8552ab875c1313b97b8d20cb857b9fd63e2d1d6a0a1b53ce9821e575405f27a" dependencies = [ "bytes", "futures", @@ -4156,35 +4601,36 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92b654097027250401127914afb37cb1f311df6610a9891ff07a757e94199027" dependencies = [ - "async-io", "bytes", "futures", "libc", "log", + "tokio", ] [[package]] name = "nix" -version = "0.23.1" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" +checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" dependencies = [ "bitflags", "cc", "cfg-if", "libc", - "memoffset", + "memoffset 0.6.5", ] [[package]] name = "nix" -version = "0.24.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc" +checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" dependencies = [ "bitflags", "cfg-if", "libc", + "memoffset 0.6.5", ] [[package]] @@ -4192,7 +4638,7 @@ name = "node-bench" version = "0.9.0-dev" dependencies = [ "array-bytes", - "clap 4.0.11", + "clap 4.0.32", "derive_more", "fs_extra", "futures", @@ -4229,7 +4675,7 @@ version = "3.0.0-dev" dependencies = [ "array-bytes", "assert_cmd", - "clap 4.0.11", + "clap 4.0.32", "clap_complete", "criterion", "frame-benchmarking-cli", @@ -4239,7 +4685,7 @@ dependencies = [ "jsonrpsee", "kitchensink-runtime", "log", - "nix 0.23.1", + "nix 0.23.2", "node-executor", "node-inspect", "node-primitives", @@ -4251,7 +4697,7 @@ dependencies = [ "pallet-timestamp", "pallet-transaction-payment", "parity-scale-codec", - "platforms", + "platforms 2.0.0", "rand 0.8.5", "regex", "sc-authority-discovery", @@ -4304,7 +4750,7 @@ dependencies = [ "substrate-rpc-client", "tempfile", "tokio", - "tokio-util 0.7.4", + "tokio-util", "try-runtime-cli", "wait-timeout", ] @@ -4349,7 +4795,7 @@ dependencies = [ name = "node-inspect" version = "0.9.0-dev" dependencies = [ - "clap 4.0.11", + "clap 4.0.32", "parity-scale-codec", "sc-cli", "sc-client-api", @@ -4408,7 +4854,7 @@ dependencies = [ name = "node-runtime-generate-bags" version = "3.0.0" dependencies = [ - "clap 4.0.11", + "clap 4.0.32", "generate-bags", "kitchensink-runtime", ] @@ -4417,7 +4863,7 @@ dependencies = [ name = "node-template" version = "4.0.0-dev" dependencies = [ - "clap 4.0.11", + "clap 4.0.32", "frame-benchmarking", "frame-benchmarking-cli", "frame-system", @@ -4536,13 +4982,12 @@ checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" [[package]] name = "nom" -version = "7.1.0" +version = "7.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" +checksum = "e5507769c4919c998e69e49c839d9dc6e693ede4cc4290d6ad8b41d4f09c548c" dependencies = [ "memchr", "minimal-lexical", - "version_check", ] [[package]] @@ -4574,28 +5019,28 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085" +checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" dependencies = [ "num-traits", ] [[package]] name = "num-format" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b862ff8df690cf089058c98b183676a7ed0f974cc08b426800093227cbff3b" +checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" dependencies = [ "arrayvec 0.7.2", - "itoa 1.0.4", + "itoa 1.0.5", ] [[package]] name = "num-integer" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", "num-traits", @@ -4615,21 +5060,21 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", - "libm", + "libm 0.2.6", ] [[package]] name = "num_cpus" -version = "1.13.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] @@ -4640,7 +5085,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" dependencies = [ "crc32fast", - "hashbrown 0.12.3", + "hashbrown", "indexmap", "memchr", ] @@ -4654,11 +5099,29 @@ dependencies = [ "memchr", ] +[[package]] +name = "oid-registry" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e20717fa0541f39bd146692035c37bedfa532b3e5071b35761082407546b2a" +dependencies = [ + "asn1-rs 0.3.1", +] + +[[package]] +name = "oid-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bedf36ffb6ba96c2eb7144ef6270557b52e54b20c0a8e1eb2ff99a6c6959bff" +dependencies = [ + "asn1-rs 0.5.1", +] + [[package]] name = "once_cell" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" [[package]] name = "oorandom" @@ -4680,21 +5143,21 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl-probe" -version = "0.1.2" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "os_str_bytes" -version = "6.0.0" +version = "6.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" [[package]] name = "output_vt100" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" +checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" dependencies = [ "winapi", ] @@ -4705,6 +5168,38 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "p256" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51f44edd08f51e2ade572f141051021c5af22677e42b7dd28a88155151c33594" +dependencies = [ + "ecdsa", + "elliptic-curve", + "sha2 0.10.6", +] + +[[package]] +name = "p384" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc8c5bf642dde52bb9e87c0ecd8ca5a76faac2eeed98dedb7c717997e1080aa" +dependencies = [ + "ecdsa", + "elliptic-curve", + "sha2 0.10.6", +] + +[[package]] +name = "packed_simd_2" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1914cd452d8fccd6f9db48147b29fd4ae05bea9dc5d9ad578509f72415de282" +dependencies = [ + "cfg-if", + "libm 0.1.4", +] + [[package]] name = "pallet-alliance" version = "4.0.0-dev" @@ -4719,7 +5214,7 @@ dependencies = [ "pallet-identity", "parity-scale-codec", "scale-info", - "sha2 0.10.2", + "sha2 0.10.6", "sp-core", "sp-io", "sp-runtime", @@ -6165,9 +6660,9 @@ dependencies = [ [[package]] name = "parity-scale-codec" -version = "3.1.3" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04bc9583b5e01cc8c70d89acc9af14ef9b1c29ee3a0074b2a9eea8c0fa396690" +checksum = "366e44391a8af4cfd6002ef6ba072bae071a96aafca98d7d448a34c5dca38b6a" dependencies = [ "arrayvec 0.7.2", "bitvec", @@ -6216,7 +6711,7 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", - "parking_lot_core 0.8.5", + "parking_lot_core 0.8.6", ] [[package]] @@ -6226,14 +6721,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.1", + "parking_lot_core 0.9.5", ] [[package]] name = "parking_lot_core" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" dependencies = [ "cfg-if", "instant", @@ -6245,22 +6740,22 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.1" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954" +checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-sys 0.32.0", + "windows-sys 0.42.0", ] [[package]] name = "paste" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" +checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" [[package]] name = "pbkdf2" @@ -6277,7 +6772,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ - "digest 0.10.3", + "digest 0.10.6", ] [[package]] @@ -6286,17 +6781,35 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" +[[package]] +name = "pem" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c64931a1a212348ec4f3b4362585eca7159d0d09cbdf4a7f74f02173596fd4" +dependencies = [ + "base64", +] + +[[package]] +name = "pem-rfc7468" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d159833a9105500e0398934e205e0773f0b27529557134ecfc51c27646adac" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pest" -version = "2.4.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc7bc69c062e492337d74d59b120c274fd3d261b6bf6d3207d499b4b379c41a" +checksum = "0f6e86fb9e7026527a0d46bc308b841d73170ef8f443e1807f6ef88526a816d4" dependencies = [ "thiserror", "ucd-trie", @@ -6304,9 +6817,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.4.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b75706b9642ebcb34dab3bc7750f811609a0eb1dd8b88c2d15bf628c1c65b2" +checksum = "96504449aa860c8dcde14f9fba5c58dc6658688ca1fe363589d6327b8662c603" dependencies = [ "pest", "pest_generator", @@ -6314,9 +6827,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.4.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f9272122f5979a6511a749af9db9bfc810393f63119970d7085fed1c4ea0db" +checksum = "798e0220d1111ae63d66cb66a5dcb3fc2d986d520b98e49e1852bfdb11d7c5e7" dependencies = [ "pest", "pest_meta", @@ -6327,9 +6840,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.4.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8717927f9b79515e565a64fe46c38b8cd0427e64c40680b14a7365ab09ac8d" +checksum = "984298b75898e30a843e278a9f2452c31e349a073a0ce6fd950a12a74464e065" dependencies = [ "once_cell", "pest", @@ -6338,9 +6851,9 @@ dependencies = [ [[package]] name = "petgraph" -version = "0.6.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a13a2fa9d0b63e5f22328828741e523766fff0ee9e779316902290dff3f824f" +checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" dependencies = [ "fixedbitset", "indexmap", @@ -6396,9 +6909,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.19" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "platforms" @@ -6406,11 +6919,17 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8d0eef3571242013a0d5dc84861c3ae4a652e56e12adf8bdc26ff5f8cb34c94" +[[package]] +name = "platforms" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d7ddaed09e0eb771a79ab0fd64609ba0afb0a8366421957936ad14cbd13630" + [[package]] name = "plotters" -version = "0.3.1" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a3fd9ec30b9749ce28cd91f255d569591cdf937fe280c312143e3c4bad6f2a" +checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97" dependencies = [ "num-traits", "plotters-backend", @@ -6421,31 +6940,31 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.0" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b07fffcddc1cb3a1de753caa4e4df03b79922ba43cf882acc1bdd7e8df9f4590" +checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" [[package]] name = "plotters-svg" -version = "0.3.0" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b38a02e23bd9604b842a812063aec4ef702b57989c37b655254bb61c471ad211" +checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f" dependencies = [ "plotters-backend", ] [[package]] name = "polling" -version = "2.4.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab4609a838d88b73d8238967b60dd115cc08d38e2bbaf51ee1e4b695f89122e2" +checksum = "22122d5ec4f9fe1b3916419b76be1e80bcb93f618d071d2edf841b137b2a2bd6" dependencies = [ "autocfg", "cfg-if", "libc", "log", "wepoll-ffi", - "winapi", + "windows-sys 0.42.0", ] [[package]] @@ -6459,6 +6978,17 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "polyval" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eebcc4aa140b9abd2bc40d9c3f7ccec842679cd79045ac3a7ac698c1a064b7cd" +dependencies = [ + "cpuid-bool", + "opaque-debug 0.3.0", + "universal-hash", +] + [[package]] name = "polyval" version = "0.5.3" @@ -6473,15 +7003,15 @@ dependencies = [ [[package]] name = "ppv-lite86" -version = "0.2.10" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "predicates" -version = "2.1.3" +version = "2.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6bd09a7f7e68f3f0bf710fb7ab9c4615a488b58b5f653382a687701e458c92" +checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd" dependencies = [ "difflib", "float-cmp", @@ -6493,37 +7023,47 @@ dependencies = [ [[package]] name = "predicates-core" -version = "1.0.2" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57e35a3326b75e49aa85f5dc6ec15b41108cf5aee58eabb1f274dd18b73c2451" +checksum = "72f883590242d3c6fc5bf50299011695fa6590c2c70eac95ee1bdb9a733ad1a2" [[package]] name = "predicates-tree" -version = "1.0.2" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15f553275e5721409451eb85e15fd9a860a6e5ab4496eb215987502b5f5391f2" +checksum = "54ff541861505aabf6ea722d2131ee980b8276e10a1297b94e896dd8b621850d" dependencies = [ "predicates-core", - "treeline", + "termtree", ] [[package]] name = "pretty_assertions" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89f989ac94207d048d92db058e4f6ec7342b0971fc58d1271ca148b799b3563" +checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755" dependencies = [ - "ansi_term", "ctor", "diff", "output_vt100", + "yansi", +] + +[[package]] +name = "prettyplease" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8992a85d8e93a28bdf76137db888d3874e3b230dee5ed8bebac4c9f7617773" +dependencies = [ + "proc-macro2", + "syn", ] [[package]] name = "primitive-types" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cfd65aea0c5fa0bfcc7c9e7ca828c921ef778f43d325325ec84bda371bfa75a" +checksum = "9f3486ccba82358b11a77516035647c34ba167dfa53312630de83b12bd4f3d66" dependencies = [ "fixed-hash", "impl-codec", @@ -6534,10 +7074,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" dependencies = [ + "once_cell", "thiserror", "toml", ] @@ -6568,35 +7109,35 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.46" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b" +checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" dependencies = [ "unicode-ident", ] [[package]] name = "prometheus" -version = "0.13.0" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f64969ffd5dd8f39bd57a68ac53c163a095ed9d0fb707146da1b27025a3504" +checksum = "449811d15fbdf5ceb5c1144416066429cf82316e2ec8ce0c1f6f8a02e7bbcf8c" dependencies = [ "cfg-if", "fnv", "lazy_static", "memchr", - "parking_lot 0.11.2", + "parking_lot 0.12.1", "thiserror", ] [[package]] name = "prometheus-client" -version = "0.18.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c473049631c233933d6286c88bbb7be30e62ec534cf99a9ae0079211f7fa603" +checksum = "83cd1b99916654a69008fd66b4f9397fbe08e6e51dfe23d4417acf5d3b8cb87c" dependencies = [ "dtoa", - "itoa 1.0.4", + "itoa 1.0.5", "parking_lot 0.12.1", "prometheus-client-derive-text-encode", ] @@ -6614,9 +7155,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.11.0" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "399c3c31cdec40583bb68f0b18403400d01ec4289c383aa047560439952c4dd7" +checksum = "c01db6702aa05baa3f57dec92b8eeeeb4cb19e894e73996b32a4093289e54592" dependencies = [ "bytes", "prost-derive", @@ -6624,9 +7165,9 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.11.1" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f835c582e6bd972ba8347313300219fed5bfa52caf175298d860b61ff6069bb" +checksum = "cb5320c680de74ba083512704acb90fe00f28f79207286a848e730c45dd73ed6" dependencies = [ "bytes", "heck", @@ -6635,18 +7176,20 @@ dependencies = [ "log", "multimap", "petgraph", + "prettyplease", "prost", "prost-types", "regex", + "syn", "tempfile", "which", ] [[package]] name = "prost-codec" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "011ae9ff8359df7915f97302d591cdd9e0e27fbd5a4ddc5bd13b71079bb20987" +checksum = "0dc34979ff898b6e141106178981ce2596c387ea6e62533facfc61a37fc879c0" dependencies = [ "asynchronous-codec", "bytes", @@ -6657,9 +7200,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.11.0" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7345d5f0e08c0536d7ac7229952590239e77abf0a0100a1b1d890add6ea96364" +checksum = "c8842bad1a5419bca14eac663ba798f6bc19c413c2fdceb5f3ba3b0932d96720" dependencies = [ "anyhow", "itertools", @@ -6670,9 +7213,9 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.11.1" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dfaa718ad76a44b3415e6c4d53b17c8f99160dcb3a99b10470fce8ad43f6e3e" +checksum = "017f79637768cde62820bc2d4fe0e45daaa027755c323ad077767c6c5f173091" dependencies = [ "bytes", "prost", @@ -6680,9 +7223,9 @@ dependencies = [ [[package]] name = "psm" -version = "0.1.12" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3abf49e5417290756acfd26501536358560c4a5cc4a0934d390939acb3e7083a" +checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" dependencies = [ "cc", ] @@ -6713,11 +7256,29 @@ dependencies = [ "pin-project-lite 0.1.12", ] +[[package]] +name = "quinn-proto" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4ced82a24bb281af338b9e8f94429b6eca01b4e66d899f40031f074e74c9" +dependencies = [ + "bytes", + "rand 0.8.5", + "ring", + "rustc-hash", + "rustls 0.20.7", + "slab", + "thiserror", + "tinyvec", + "tracing", + "webpki 0.22.0", +] + [[package]] name = "quote" -version = "1.0.18" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] @@ -6749,7 +7310,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha 0.3.1", - "rand_core 0.6.2", + "rand_core 0.6.4", ] [[package]] @@ -6769,7 +7330,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.2", + "rand_core 0.6.4", ] [[package]] @@ -6783,11 +7344,11 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.2" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.3", + "getrandom 0.2.8", ] [[package]] @@ -6815,7 +7376,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" dependencies = [ - "rand_core 0.6.2", + "rand_core 0.6.4", ] [[package]] @@ -6826,62 +7387,85 @@ checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" [[package]] name = "rayon" -version = "1.5.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674" +checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" dependencies = [ - "autocfg", - "crossbeam-deque", "either", "rayon-core", ] [[package]] name = "rayon-core" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" +checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3" dependencies = [ "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "lazy_static", "num_cpus", ] +[[package]] +name = "rcgen" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6413f3de1edee53342e6138e75b56d32e7bc6e332b3bd62d497b1929d4cfbcdd" +dependencies = [ + "pem", + "ring", + "time 0.3.17", + "x509-parser 0.13.2", + "yasna", +] + +[[package]] +name = "rcgen" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbe84efe2f38dea12e9bfc1f65377fdf03e53a18cb3b995faedf7934c7e785b" +dependencies = [ + "pem", + "ring", + "time 0.3.17", + "yasna", +] + [[package]] name = "redox_syscall" -version = "0.2.10" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] [[package]] name = "redox_users" -version = "0.4.0" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.3", + "getrandom 0.2.8", "redox_syscall", + "thiserror", ] [[package]] name = "ref-cast" -version = "1.0.6" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300f2a835d808734ee295d45007adacb9ebb29dd3ae2424acfa17930cae541da" +checksum = "8c78fb8c9293bcd48ef6fce7b4ca950ceaf21210de6e105a883ee280c0f7b9ed" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.6" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c38e3aecd2b21cb3959637b883bb3714bc7e43f0268b9a29d3743ee3e55cdd2" +checksum = "9f9c0c92af03644e4806106281fe2e068ac5bc0ae74a707266d06ea27bccee5f" dependencies = [ "proc-macro2", "quote", @@ -6902,9 +7486,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" dependencies = [ "aho-corasick", "memchr", @@ -6913,19 +7497,18 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ - "byteorder", "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "remove_dir_all" @@ -6948,9 +7531,9 @@ dependencies = [ [[package]] name = "rfc6979" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88c86280f057430a52f4861551b092a01b419b8eacefc7c995eacb9dc132fe32" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" dependencies = [ "crypto-bigint", "hmac 0.12.1", @@ -6984,34 +7567,70 @@ dependencies = [ [[package]] name = "rpassword" -version = "7.0.0" +version = "7.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b763cb66df1c928432cc35053f8bd4cec3335d8559fc16010017d16b3c1680" +checksum = "6678cf63ab3491898c0d021b493c94c9b221d91295294a2a5746eacbe5928322" dependencies = [ "libc", + "rtoolbox", "winapi", ] +[[package]] +name = "rtcp" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1919efd6d4a6a85d13388f9487549bb8e359f17198cc03ffd72f79b553873691" +dependencies = [ + "bytes", + "thiserror", + "webrtc-util", +] + [[package]] name = "rtnetlink" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "322c53fd76a18698f1c27381d58091de3a043d356aa5bd0d510608b565f469a0" dependencies = [ - "async-global-executor", "futures", "log", "netlink-packet-route", "netlink-proto", - "nix 0.24.2", + "nix 0.24.3", + "thiserror", + "tokio", +] + +[[package]] +name = "rtoolbox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "034e22c514f5c0cb8a10ff341b9b048b5ceb21591f31c8f44c43b960f9b3524a" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "rtp" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2a095411ff00eed7b12e4c6a118ba984d113e1079582570d56a5ee723f11f80" +dependencies = [ + "async-trait", + "bytes", + "rand 0.8.5", + "serde", "thiserror", + "webrtc-util", ] [[package]] name = "rustc-demangle" -version = "0.1.18" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" [[package]] name = "rustc-hash" @@ -7040,40 +7659,76 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.4", + "semver 1.0.16", +] + +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", ] [[package]] name = "rustix" -version = "0.35.9" +version = "0.35.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c825b8aa8010eb9ee99b75f05e10180b9278d161583034d7574c9d617aeada" +checksum = "727a1a6d65f786ec22df8a81ca3121107f235970dc1705ed681d3e6e8b9cd5f9" dependencies = [ "bitflags", "errno", - "io-lifetimes", + "io-lifetimes 0.7.5", "libc", - "linux-raw-sys", - "windows-sys 0.36.1", + "linux-raw-sys 0.0.46", + "windows-sys 0.42.0", +] + +[[package]] +name = "rustix" +version = "0.36.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4feacf7db682c6c329c4ede12649cd36ecab0f3be5b7d74e6a20304725db4549" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes 1.0.3", + "libc", + "linux-raw-sys 0.1.4", + "windows-sys 0.42.0", +] + +[[package]] +name = "rustls" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +dependencies = [ + "base64", + "log", + "ring", + "sct 0.6.1", + "webpki 0.21.4", ] [[package]] name = "rustls" -version = "0.20.2" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d37e5e2290f3e040b594b1a9e04377c2c671f1a1cfd9bfdef82106ac1c113f84" +checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c" dependencies = [ "log", "ring", - "sct", - "webpki", + "sct 0.7.0", + "webpki 0.22.0", ] [[package]] name = "rustls-native-certs" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca9ebdfa27d3fc180e42879037b5338ab1c040c06affd00d8338598e7800943" +checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" dependencies = [ "openssl-probe", "rustls-pemfile", @@ -7083,18 +7738,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "0.2.1" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" +checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" dependencies = [ "base64", ] [[package]] name = "rustversion" -version = "1.0.6" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" +checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" [[package]] name = "rusty-fork" @@ -7120,9 +7775,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.5" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" [[package]] name = "safe-mix" @@ -7252,7 +7907,7 @@ version = "0.10.0-dev" dependencies = [ "array-bytes", "chrono", - "clap 4.0.11", + "clap 4.0.32", "fdlimit", "futures", "futures-timer", @@ -7624,7 +8279,7 @@ dependencies = [ "tempfile", "tracing", "tracing-subscriber 0.2.25", - "wasmi 0.13.0", + "wasmi 0.13.2", "wat", ] @@ -7637,7 +8292,7 @@ dependencies = [ "sp-wasm-interface", "thiserror", "wasm-instrument 0.3.0", - "wasmi 0.13.0", + "wasmi 0.13.2", ] [[package]] @@ -7649,7 +8304,7 @@ dependencies = [ "sc-executor-common", "sp-runtime-interface", "sp-wasm-interface", - "wasmi 0.13.0", + "wasmi 0.13.2", ] [[package]] @@ -7662,7 +8317,7 @@ dependencies = [ "once_cell", "parity-scale-codec", "paste", - "rustix", + "rustix 0.35.13", "sc-allocator", "sc-executor-common", "sc-runtime-test", @@ -7819,7 +8474,7 @@ dependencies = [ "tempfile", "thiserror", "tokio", - "tokio-util 0.7.4", + "tokio-util", "unsigned-varint", "zeroize", ] @@ -8441,9 +9096,9 @@ dependencies = [ [[package]] name = "scale-info" -version = "2.2.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "333af15b02563b8182cd863f925bd31ef8fa86a0e095d30c091956057d436153" +checksum = "001cf62ece89779fd16105b5f515ad0e5cedcd5440d3dd806bb067978e7c3608" dependencies = [ "bitvec", "cfg-if", @@ -8455,9 +9110,9 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.2.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53f56acbd0743d29ffa08f911ab5397def774ad01bab3786804cf6ee057fb5e1" +checksum = "303959cf613a6f6efd19ed4b4ad5bf79966a13352716299ad532cfb115f4205c" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -8467,12 +9122,12 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" dependencies = [ "lazy_static", - "winapi", + "windows-sys 0.36.1", ] [[package]] @@ -8483,7 +9138,7 @@ checksum = "021b403afe70d81eea68f6ea12f6b3c9588e5d536a94c3bf80f15e7faa267862" dependencies = [ "arrayref", "arrayvec 0.5.2", - "curve25519-dalek 2.1.2", + "curve25519-dalek 2.1.3", "getrandom 0.1.16", "merlin", "rand 0.7.3", @@ -8501,9 +9156,19 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "scratch" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" +checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" + +[[package]] +name = "sct" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +dependencies = [ + "ring", + "untrusted", +] [[package]] name = "sct" @@ -8515,6 +9180,18 @@ dependencies = [ "untrusted", ] +[[package]] +name = "sdp" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d22a5ef407871893fd72b4562ee15e4742269b173959db4b8df6f538c414e13" +dependencies = [ + "rand 0.8.5", + "substring", + "thiserror", + "url", +] + [[package]] name = "sec1" version = "0.3.0" @@ -8523,7 +9200,7 @@ checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ "base16ct", "der", - "generic-array 0.14.4", + "generic-array 0.14.6", "pkcs8", "subtle", "zeroize", @@ -8531,18 +9208,18 @@ dependencies = [ [[package]] name = "secp256k1" -version = "0.24.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7649a0b3ffb32636e60c7ce0d70511eda9c52c658cd0634e194d5a19943aeff" +checksum = "d9512ffd81e3a3503ed401f79c33168b9148c75038956039166cd750eaa037c3" dependencies = [ "secp256k1-sys", ] [[package]] name = "secp256k1-sys" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7058dc8eaf3f2810d7828680320acda0b25a288f6d288e19278e249bbf74226b" +checksum = "83080e2c2fc1006e625be82e5d1eb6a43b7fd9578b617fcc55814daf286bba4b" dependencies = [ "cc", ] @@ -8558,9 +9235,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.3.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b239a3d5db51252f6f48f42172c65317f37202f4a21021bf5f9d40a408f4592c" +checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" dependencies = [ "bitflags", "core-foundation", @@ -8571,9 +9248,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.3.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e4effb91b4b8b6fb7732e670b6cee160278ff8e6bf485c7805d9e319d76e284" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" dependencies = [ "core-foundation-sys", "libc", @@ -8599,9 +9276,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.4" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012" +checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" dependencies = [ "serde", ] @@ -8614,18 +9291,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.145" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" dependencies = [ "serde_derive", ] [[package]] name = "serde_cbor" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e18acfa2f90e8b735b2836ab8d538de304cbb6729a7360729ea5a895d15a622" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" dependencies = [ "half", "serde", @@ -8633,9 +9310,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.145" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", @@ -8644,37 +9321,37 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.85" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" +checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" dependencies = [ - "itoa 1.0.4", + "itoa 1.0.5", "ryu", "serde", ] [[package]] name = "sha-1" -version = "0.9.4" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfebf75d25bd900fd1e7d11501efab59bc846dbc76196839663e6637bba9f25f" +checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" dependencies = [ "block-buffer 0.9.0", "cfg-if", - "cpuid-bool", + "cpufeatures", "digest 0.9.0", "opaque-debug 0.3.0", ] [[package]] name = "sha1" -version = "0.10.4" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "006769ba83e921b3085caa8334186b00cf92b4cb1a6cf4632fbccc8eff5c7549" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.3", + "digest 0.10.6", ] [[package]] @@ -8691,9 +9368,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", "cfg-if", @@ -8704,22 +9381,22 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.2" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.3", + "digest 0.10.6", ] [[package]] name = "sha3" -version = "0.10.0" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f935e31cf406e8c0e96c2815a5516181b7004ae8c5f296293221e9b1e356bd" +checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" dependencies = [ - "digest 0.10.3", + "digest 0.10.6", "keccak", ] @@ -8734,27 +9411,27 @@ dependencies = [ [[package]] name = "shlex" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a568c8f2cd051a4d283bd6eb0343ac214c1b0f1ac19f93e1175b2dee38c73d" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" [[package]] name = "signal-hook-registry" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" dependencies = [ "libc", ] [[package]] name = "signature" -version = "1.6.3" +version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb766570a2825fa972bceff0d195727876a9cdf2460ab2e52d455dc2de47fd9" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" dependencies = [ - "digest 0.10.3", - "rand_core 0.6.2", + "digest 0.10.6", + "rand_core 0.6.4", ] [[package]] @@ -8771,9 +9448,12 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.2" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] [[package]] name = "slice-group-by" @@ -8789,9 +9469,9 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "snap" -version = "1.0.5" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45456094d1983e2ee2a18fdfebce3189fa451699d0502cb8e3b49dba5ba41451" +checksum = "5e9f0ab6ef7eb7353d9119c170a436d1bf248eea575ac42d19d12f4e34130831" [[package]] name = "snow" @@ -8799,22 +9479,22 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "774d05a3edae07ce6d68ea6984f3c05e9bba8927e3dd591e3b479e5b03213d0d" dependencies = [ - "aes-gcm", + "aes-gcm 0.9.4", "blake2", "chacha20poly1305", - "curve25519-dalek 4.0.0-pre.1", - "rand_core 0.6.2", + "curve25519-dalek 4.0.0-pre.5", + "rand_core 0.6.4", "ring", "rustc_version 0.4.0", - "sha2 0.10.2", + "sha2 0.10.6", "subtle", ] [[package]] name = "socket2" -version = "0.4.4" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" dependencies = [ "libc", "winapi", @@ -9149,8 +9829,8 @@ version = "5.0.0" dependencies = [ "blake2", "byteorder", - "digest 0.10.3", - "sha2 0.10.2", + "digest 0.10.6", + "sha2 0.10.6", "sha3", "sp-std", "twox-hash", @@ -9321,7 +10001,7 @@ dependencies = [ name = "sp-npos-elections-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 4.0.11", + "clap 4.0.32", "honggfuzz", "parity-scale-codec", "rand 0.8.5", @@ -9597,7 +10277,7 @@ dependencies = [ "array-bytes", "criterion", "hash-db", - "hashbrown 0.12.3", + "hashbrown", "lazy_static", "lru", "memory-db", @@ -9651,7 +10331,7 @@ dependencies = [ "log", "parity-scale-codec", "sp-std", - "wasmi 0.13.0", + "wasmi 0.13.2", "wasmtime", ] @@ -9693,9 +10373,9 @@ dependencies = [ [[package]] name = "ss58-registry" -version = "1.34.0" +version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37a9821878e1f13aba383aa40a86fb1b33c7265774ec91e32563cb1dd1577496" +checksum = "23d92659e7d18d82b803824a9ba5a6022cff101c3491d027c1c1d8d30e749284" dependencies = [ "Inflector", "num-format", @@ -9728,7 +10408,7 @@ dependencies = [ "cfg_aliases", "libc", "parking_lot 0.11.2", - "parking_lot_core 0.8.5", + "parking_lot_core 0.8.6", "static_init_macro", "winapi", ] @@ -9776,9 +10456,9 @@ dependencies = [ [[package]] name = "strum_macros" -version = "0.24.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4faebde00e8ff94316c01800f9054fd2ba77d30d9e922541913051d1d978918b" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ "heck", "proc-macro2", @@ -9787,11 +10467,30 @@ dependencies = [ "syn", ] +[[package]] +name = "stun" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7e94b1ec00bad60e6410e058b52f1c66de3dc5fe4d62d09b3e52bb7d3b73e25" +dependencies = [ + "base64", + "crc", + "lazy_static", + "md-5", + "rand 0.8.5", + "ring", + "subtle", + "thiserror", + "tokio", + "url", + "webrtc-util", +] + [[package]] name = "subkey" version = "2.0.2" dependencies = [ - "clap 4.0.11", + "clap 4.0.32", "sc-cli", ] @@ -9804,7 +10503,7 @@ dependencies = [ "hmac 0.11.0", "pbkdf2 0.8.0", "schnorrkel", - "sha2 0.9.8", + "sha2 0.9.9", "zeroize", ] @@ -9812,14 +10511,14 @@ dependencies = [ name = "substrate-build-script-utils" version = "3.0.0" dependencies = [ - "platforms", + "platforms 2.0.0", ] [[package]] name = "substrate-frame-cli" version = "4.0.0-dev" dependencies = [ - "clap 4.0.11", + "clap 4.0.32", "frame-support", "frame-system", "sc-cli", @@ -10062,17 +10761,26 @@ dependencies = [ "wasm-opt", ] +[[package]] +name = "substring" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ee6433ecef213b2e72f587ef64a2f5943e7cd16fbd82dbe8bc07486c534c86" +dependencies = [ + "autocfg", +] + [[package]] name = "subtle" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.98" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", "quote", @@ -10081,9 +10789,9 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.12.4" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2", "quote", @@ -10120,9 +10828,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "target-lexicon" -version = "0.12.3" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fa7e55043acb85fca6b3c01485a2eeb6b69c5d21002e273c79e465f43b7ac1" +checksum = "9410d0f6853b1d94f0e519fb95df60f29d2c1eff2d921ffdf01a4c8a3b54f12d" [[package]] name = "tempfile" @@ -10140,13 +10848,19 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" dependencies = [ "winapi-util", ] +[[package]] +name = "termtree" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95059e91184749cb66be6dc994f67f182b6d897cb3df74a5bf66b5e709295fd8" + [[package]] name = "textwrap" version = "0.11.0" @@ -10158,18 +10872,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", @@ -10202,9 +10916,9 @@ dependencies = [ [[package]] name = "tikv-jemalloc-sys" -version = "0.5.1+5.3.0-patched" +version = "0.5.2+5.3.0-patched" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "931e876f91fed0827f863a2d153897790da0b24d882c721a79cb3beb0b903261" +checksum = "ec45c14da997d0925c7835883e4d5c181f196fa142f8c19d7643d1e9af2592c3" dependencies = [ "cc", "fs_extra", @@ -10213,15 +10927,42 @@ dependencies = [ [[package]] name = "time" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" dependencies = [ "libc", "wasi 0.10.0+wasi-snapshot-preview1", "winapi", ] +[[package]] +name = "time" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +dependencies = [ + "itoa 1.0.5", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + +[[package]] +name = "time-macros" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +dependencies = [ + "time-core", +] + [[package]] name = "tiny-bip39" version = "1.0.0" @@ -10234,7 +10975,7 @@ dependencies = [ "pbkdf2 0.11.0", "rand 0.8.5", "rustc-hash", - "sha2 0.10.2", + "sha2 0.10.6", "thiserror", "unicode-normalization", "wasm-bindgen", @@ -10262,9 +11003,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.1.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317cca572a0e89c3ce0ca1f1bdc9369547fe318a683418e42ac8f59d14701023" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] @@ -10277,9 +11018,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.22.0" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76ce4a75fb488c605c54bf610f221cea8b0dafb53333c1a67e8ee199dcd2ae3" +checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46" dependencies = [ "autocfg", "bytes", @@ -10292,14 +11033,14 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "winapi", + "windows-sys 0.42.0", ] [[package]] name = "tokio-macros" -version = "1.7.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", @@ -10308,25 +11049,25 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.23.2" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a27d5f2b839802bd8267fa19b0530f5a08b9c08cd417976be2a65d130fe1c11b" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ - "rustls", + "rustls 0.20.7", "tokio", - "webpki", + "webpki 0.22.0", ] [[package]] name = "tokio-stream" -version = "0.1.7" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b2f3f698253f03119ac0102beaa64f67a67e08074d03a22d18784104543727f" +checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" dependencies = [ "futures-core", "pin-project-lite 0.2.9", "tokio", - "tokio-util 0.6.10", + "tokio-util", ] [[package]] @@ -10344,38 +11085,24 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.6.10" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" dependencies = [ "bytes", "futures-core", + "futures-io", "futures-sink", - "log", "pin-project-lite 0.2.9", "tokio", + "tracing", ] [[package]] -name = "tokio-util" -version = "0.7.4" +name = "toml" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" -dependencies = [ - "bytes", - "futures-core", - "futures-io", - "futures-sink", - "pin-project-lite 0.2.9", - "tokio", - "tracing", -] - -[[package]] -name = "toml" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f" dependencies = [ "serde", ] @@ -10417,15 +11144,15 @@ checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" [[package]] name = "tower-service" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.35" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", "log", @@ -10436,9 +11163,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", @@ -10478,9 +11205,9 @@ dependencies = [ [[package]] name = "tracing-serde" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb65ea441fbb84f9f6748fd496cf7f63ec9af5bca94dd86456978d055e8eb28b" +checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" dependencies = [ "serde", "tracing-core", @@ -10527,12 +11254,6 @@ dependencies = [ "tracing-log", ] -[[package]] -name = "treeline" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41" - [[package]] name = "trie-bench" version = "0.33.0" @@ -10556,7 +11277,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "004e1e8f92535694b4cb1444dc5a8073ecf0815e3357f729638b9f8fc4062908" dependencies = [ "hash-db", - "hashbrown 0.12.3", + "hashbrown", "log", "rustc-hex", "smallvec", @@ -10594,11 +11315,12 @@ dependencies = [ "futures-channel", "futures-io", "futures-util", - "idna", + "idna 0.2.3", "ipnet", "lazy_static", "rand 0.8.5", "smallvec", + "socket2", "thiserror", "tinyvec", "tokio", @@ -10636,7 +11358,7 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" name = "try-runtime-cli" version = "0.10.0-dev" dependencies = [ - "clap 4.0.11", + "clap 4.0.32", "frame-remote-externalities", "frame-try-runtime", "hex", @@ -10665,9 +11387,9 @@ dependencies = [ [[package]] name = "trybuild" -version = "1.0.60" +version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0da18123d1316f5a65fc9b94e30a0fcf58afb1daff1b8e18f41dc30f5bfc38c8" +checksum = "ed01de3de062db82c0920b5cabe804f88d599a3f217932292597c678c903754d" dependencies = [ "dissimilar", "glob", @@ -10681,9 +11403,28 @@ dependencies = [ [[package]] name = "tt-call" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e66dcbec4290c69dd03c57e76c2469ea5c7ce109c6dd4351c13055cf71ea055" +checksum = "f4f195fd851901624eee5a58c4bb2b4f06399148fcd0ed336e6f1cb60a9881df" + +[[package]] +name = "turn" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4712ee30d123ec7ae26d1e1b218395a16c87cdbaf4b3925d170d684af62ea5e8" +dependencies = [ + "async-trait", + "base64", + "futures", + "log", + "md-5", + "rand 0.8.5", + "ring", + "stun", + "thiserror", + "tokio", + "webrtc-util", +] [[package]] name = "twox-hash" @@ -10692,28 +11433,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ "cfg-if", - "digest 0.10.3", + "digest 0.10.6", "rand 0.8.5", "static_assertions", ] [[package]] name = "typenum" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "ucd-trie" -version = "0.1.3" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" [[package]] name = "uint" -version = "0.9.0" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e11fe9a9348741cf134085ad57c249508345fe16411b3d7fb4ff2da2f1d6382e" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" dependencies = [ "byteorder", "crunchy", @@ -10723,18 +11464,15 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.4" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" -dependencies = [ - "matches", -] +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.1" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] name = "unicode-normalization" @@ -10747,15 +11485,15 @@ dependencies = [ [[package]] name = "unicode-width" -version = "0.1.8" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "unicode-xid" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "universal-hash" @@ -10763,7 +11501,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.6", "subtle", ] @@ -10787,16 +11525,24 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.2.1" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ccd964113622c8e9322cfac19eb1004a07e636c545f325da085d5cdde6f1f8b" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" dependencies = [ "form_urlencoded", - "idna", - "matches", + "idna 0.3.0", "percent-encoding", ] +[[package]] +name = "uuid" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" +dependencies = [ + "getrandom 0.2.8", +] + [[package]] name = "valuable" version = "0.1.0" @@ -10805,21 +11551,15 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "vcpkg" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb" - -[[package]] -name = "vec-arena" -version = "1.0.0" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eafc1b9b2dfc6f5529177b62cf806484db55b32dc7c9658a118e11bbeb33061d" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "void" @@ -10836,6 +11576,15 @@ dependencies = [ "libc", ] +[[package]] +name = "waitgroup" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1f50000a783467e6c0200f9d10642f4bc424e39efc1b770203e88b488f79292" +dependencies = [ + "atomic-waker", +] + [[package]] name = "waker-fn" version = "1.1.0" @@ -10883,9 +11632,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.77" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e68338db6becec24d3c7977b5bf8a48be992c934b5d07177e3931f5dc9b076c" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -10893,13 +11642,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.77" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f34c405b4f0658583dba0c1c7c9b694f3cac32655db463b56c254a1c75269523" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" dependencies = [ "bumpalo", - "lazy_static", "log", + "once_cell", "proc-macro2", "quote", "syn", @@ -10908,9 +11657,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.20" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3de431a2910c86679c34283a33f66f4e4abd7e0aec27b6669060148872aadf94" +checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" dependencies = [ "cfg-if", "js-sys", @@ -10920,9 +11669,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.77" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d5a6580be83b19dc570a8f9c324251687ab2184e57086f71625feb57ec77c8" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -10930,9 +11679,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.77" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3775a030dc6f5a0afd8a84981a21cc92a781eb429acef9ecce476d0c9113e92" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", @@ -10943,9 +11692,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.77" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c279e376c7a8e8752a8f1eaa35b7b0bee6bb9fb0cdacfa97cc3f1f289c87e2b4" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" + +[[package]] +name = "wasm-encoder" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05632e0a66a6ed8cca593c24223aabd6262f256c3693ad9822c315285f010614" +dependencies = [ + "leb128", +] [[package]] name = "wasm-instrument" @@ -11023,13 +11781,13 @@ dependencies = [ [[package]] name = "wasmi" -version = "0.13.0" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc13b3c219ca9aafeec59150d80d89851df02e0061bc357b4d66fc55a8d38787" +checksum = "06c326c93fbf86419608361a2c925a31754cf109da1b8b55737070b4d6669422" dependencies = [ "parity-wasm", "wasmi-validation", - "wasmi_core 0.2.0", + "wasmi_core 0.2.1", ] [[package]] @@ -11061,12 +11819,12 @@ checksum = "a1ea379cbb0b41f3a9f0bf7b47036d036aae7f43383d8cc487d4deccf40dee0a" [[package]] name = "wasmi_core" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a088e8c4c59c6f2b9eae169bf86328adccc477c00b56d3661e3e9fb397b184" +checksum = "57d20cb3c59b788653d99541c646c561c9dd26506f25c0cebfe810659c54c6d7" dependencies = [ "downcast-rs", - "libm", + "libm 0.2.6", "memory_units", "num-rational", "num-traits", @@ -11079,7 +11837,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5bf998ab792be85e20e771fe14182b4295571ad1d4f89d3da521c1bef5f597a" dependencies = [ "downcast-rs", - "libm", + "libm 0.2.6", "num-traits", ] @@ -11103,9 +11861,9 @@ dependencies = [ [[package]] name = "wasmtime" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a10dc9784d8c3a33c970e3939180424955f08af2e7f20368ec02685a0e8f065" +checksum = "4ad5af6ba38311282f2a21670d96e78266e8c8e2f38cbcd52c254df6ccbc7731" dependencies = [ "anyhow", "bincode", @@ -11131,18 +11889,18 @@ dependencies = [ [[package]] name = "wasmtime-asm-macros" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee4dbdc6daf68528cad1275ac91e3f51848ce9824385facc94c759f529decdf8" +checksum = "45de63ddfc8b9223d1adc8f7b2ee5f35d1f6d112833934ad7ea66e4f4339e597" dependencies = [ "cfg-if", ] [[package]] name = "wasmtime-cache" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f507f3fa1ee1b2f9a83644e2514242b1dfe580782c0eb042f1ef70255bc4ffe" +checksum = "bcd849399d17d2270141cfe47fa0d91ee52d5f8ea9b98cf7ddde0d53e5f79882" dependencies = [ "anyhow", "base64", @@ -11150,9 +11908,9 @@ dependencies = [ "directories-next", "file-per-thread-logger", "log", - "rustix", + "rustix 0.35.13", "serde", - "sha2 0.9.8", + "sha2 0.9.9", "toml", "windows-sys 0.36.1", "zstd", @@ -11160,9 +11918,9 @@ dependencies = [ [[package]] name = "wasmtime-cranelift" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f03cf79d982fc68e94ba0bea6a300a3b94621c4eb9705eece0a4f06b235a3b5" +checksum = "4bd91339b742ff20bfed4532a27b73c86b5bcbfedd6bea2dcdf2d64471e1b5c6" dependencies = [ "anyhow", "cranelift-codegen", @@ -11170,7 +11928,7 @@ dependencies = [ "cranelift-frontend", "cranelift-native", "cranelift-wasm", - "gimli 0.26.1", + "gimli 0.26.2", "log", "object 0.29.0", "target-lexicon", @@ -11181,13 +11939,13 @@ dependencies = [ [[package]] name = "wasmtime-environ" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c587c62e91c5499df62012b87b88890d0eb470b2ffecc5964e9da967b70c77c" +checksum = "ebb881c61f4f627b5d45c54e629724974f8a8890d455bcbe634330cc27309644" dependencies = [ "anyhow", "cranelift-entity", - "gimli 0.26.1", + "gimli 0.26.2", "indexmap", "log", "object 0.29.0", @@ -11200,20 +11958,20 @@ dependencies = [ [[package]] name = "wasmtime-jit" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "047839b5dabeae5424a078c19b8cc897e5943a7fadc69e3d888b9c9a897666b3" +checksum = "1985c628011fe26adf5e23a5301bdc79b245e0e338f14bb58b39e4e25e4d8681" dependencies = [ "addr2line 0.17.0", "anyhow", "bincode", "cfg-if", "cpp_demangle", - "gimli 0.26.1", + "gimli 0.26.2", "log", "object 0.29.0", "rustc-demangle", - "rustix", + "rustix 0.35.13", "serde", "target-lexicon", "thiserror", @@ -11225,20 +11983,20 @@ dependencies = [ [[package]] name = "wasmtime-jit-debug" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b299569abf6f99b7b8e020afaf84a700e8636c6a42e242069267322cd5818235" +checksum = "f671b588486f5ccec8c5a3dba6b4c07eac2e66ab8c60e6f4e53717c77f709731" dependencies = [ "object 0.29.0", "once_cell", - "rustix", + "rustix 0.35.13", ] [[package]] name = "wasmtime-runtime" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae79e0515160bd5abee5df50a16c4eb8db9f71b530fc988ae1d9ce34dcb8dd01" +checksum = "ee8f92ad4b61736339c29361da85769ebc200f184361959d1792832e592a1afd" dependencies = [ "anyhow", "cc", @@ -11248,10 +12006,10 @@ dependencies = [ "log", "mach", "memfd", - "memoffset", + "memoffset 0.6.5", "paste", "rand 0.8.5", - "rustix", + "rustix 0.35.13", "thiserror", "wasmtime-asm-macros", "wasmtime-environ", @@ -11261,9 +12019,9 @@ dependencies = [ [[package]] name = "wasmtime-types" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "790cf43ee8e2d5dad1780af30f00d7a972b74725fb1e4f90c28d62733819b185" +checksum = "d23d61cb4c46e837b431196dd06abb11731541021916d03476a178b54dc07aeb" dependencies = [ "cranelift-entity", "serde", @@ -11273,32 +12031,45 @@ dependencies = [ [[package]] name = "wast" -version = "38.0.0" +version = "50.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ebc29df4629f497e0893aacd40f13a4a56b85ef6eb4ab6d603f07244f1a7bf2" +checksum = "a2cbb59d4ac799842791fe7e806fa5dbbf6b5554d538e51cc8e176db6ff0ae34" dependencies = [ "leb128", + "memchr", + "unicode-width", + "wasm-encoder", ] [[package]] name = "wat" -version = "1.0.40" +version = "1.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcfaeb27e2578d2c6271a45609f4a055e6d7ba3a12eff35b1fd5ba147bdf046" +checksum = "584aaf7a1ecf4d383bbe1a25eeab0cbb8ff96acc6796707ff65cde48f4632f15" dependencies = [ "wast", ] [[package]] name = "web-sys" -version = "0.3.54" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a84d70d1ec7d2da2d26a5bd78f4bca1b8c3254805363ce743b7a05bc30d195a" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" dependencies = [ "js-sys", "wasm-bindgen", ] +[[package]] +name = "webpki" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "webpki" version = "0.22.0" @@ -11311,11 +12082,223 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.22.2" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +dependencies = [ + "webpki 0.22.0", +] + +[[package]] +name = "webrtc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d3bc9049bdb2cea52f5fd4f6f728184225bdb867ed0dc2410eab6df5bdd67bb" +dependencies = [ + "arc-swap", + "async-trait", + "bytes", + "hex", + "interceptor", + "lazy_static", + "log", + "rand 0.8.5", + "rcgen 0.9.3", + "regex", + "ring", + "rtcp", + "rtp", + "rustls 0.19.1", + "sdp", + "serde", + "serde_json", + "sha2 0.10.6", + "stun", + "thiserror", + "time 0.3.17", + "tokio", + "turn", + "url", + "waitgroup", + "webrtc-data", + "webrtc-dtls", + "webrtc-ice", + "webrtc-mdns", + "webrtc-media", + "webrtc-sctp", + "webrtc-srtp", + "webrtc-util", +] + +[[package]] +name = "webrtc-data" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef36a4d12baa6e842582fe9ec16a57184ba35e1a09308307b67d43ec8883100" +dependencies = [ + "bytes", + "derive_builder", + "log", + "thiserror", + "tokio", + "webrtc-sctp", + "webrtc-util", +] + +[[package]] +name = "webrtc-dtls" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7021987ae0a2ed6c8cd33f68e98e49bb6e74ffe9543310267b48a1bbe3900e5f" +dependencies = [ + "aes 0.6.0", + "aes-gcm 0.8.0", + "async-trait", + "bincode", + "block-modes", + "byteorder", + "ccm", + "curve25519-dalek 3.2.0", + "der-parser 8.1.0", + "elliptic-curve", + "hkdf", + "hmac 0.10.1", + "log", + "oid-registry 0.6.1", + "p256", + "p384", + "rand 0.8.5", + "rand_core 0.6.4", + "rcgen 0.9.3", + "ring", + "rustls 0.19.1", + "sec1", + "serde", + "sha-1", + "sha2 0.9.9", + "signature", + "subtle", + "thiserror", + "tokio", + "webpki 0.21.4", + "webrtc-util", + "x25519-dalek 2.0.0-pre.1", + "x509-parser 0.13.2", +] + +[[package]] +name = "webrtc-ice" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "552ceb903e957524388c4d3475725ff2c8b7960922063af6ce53c9a43da07449" +checksum = "494483fbb2f5492620871fdc78b084aed8807377f6e3fe88b2e49f0a9c9c41d7" dependencies = [ - "webpki", + "arc-swap", + "async-trait", + "crc", + "log", + "rand 0.8.5", + "serde", + "serde_json", + "stun", + "thiserror", + "tokio", + "turn", + "url", + "uuid", + "waitgroup", + "webrtc-mdns", + "webrtc-util", +] + +[[package]] +name = "webrtc-mdns" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f08dfd7a6e3987e255c4dbe710dde5d94d0f0574f8a21afa95d171376c143106" +dependencies = [ + "log", + "socket2", + "thiserror", + "tokio", + "webrtc-util", +] + +[[package]] +name = "webrtc-media" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee2a3c157a040324e5049bcbd644ffc9079e6738fa2cfab2bcff64e5cc4c00d7" +dependencies = [ + "byteorder", + "bytes", + "derive_builder", + "displaydoc", + "rand 0.8.5", + "rtp", + "thiserror", + "webrtc-util", +] + +[[package]] +name = "webrtc-sctp" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d47adcd9427eb3ede33d5a7f3424038f63c965491beafcc20bc650a2f6679c0" +dependencies = [ + "arc-swap", + "async-trait", + "bytes", + "crc", + "log", + "rand 0.8.5", + "thiserror", + "tokio", + "webrtc-util", +] + +[[package]] +name = "webrtc-srtp" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6183edc4c1c6c0175f8812eefdce84dfa0aea9c3ece71c2bf6ddd3c964de3da5" +dependencies = [ + "aead 0.4.3", + "aes 0.7.5", + "aes-gcm 0.9.4", + "async-trait", + "byteorder", + "bytes", + "ctr 0.8.0", + "hmac 0.11.0", + "log", + "rtcp", + "rtp", + "sha-1", + "subtle", + "thiserror", + "tokio", + "webrtc-util", +] + +[[package]] +name = "webrtc-util" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f1db1727772c05cf7a2cfece52c3aca8045ca1e176cd517d323489aa3c6d87" +dependencies = [ + "async-trait", + "bitflags", + "bytes", + "cc", + "ipnet", + "lazy_static", + "libc", + "log", + "nix 0.24.3", + "rand 0.8.5", + "thiserror", + "tokio", + "winapi", ] [[package]] @@ -11329,12 +12312,13 @@ dependencies = [ [[package]] name = "which" -version = "4.0.2" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87c14ef7e1b8b8ecfc75d5eca37949410046e66f15d185c01d70824f1f8111ef" +checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" dependencies = [ + "either", "libc", - "thiserror", + "once_cell", ] [[package]] @@ -11387,19 +12371,6 @@ dependencies = [ "windows_x86_64_msvc 0.34.0", ] -[[package]] -name = "windows-sys" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6" -dependencies = [ - "windows_aarch64_msvc 0.32.0", - "windows_i686_gnu 0.32.0", - "windows_i686_msvc 0.32.0", - "windows_x86_64_gnu 0.32.0", - "windows_x86_64_msvc 0.32.0", -] - [[package]] name = "windows-sys" version = "0.36.1" @@ -11414,10 +12385,25 @@ dependencies = [ ] [[package]] -name = "windows_aarch64_msvc" -version = "0.32.0" +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.0", + "windows_i686_gnu 0.42.0", + "windows_i686_msvc 0.42.0", + "windows_x86_64_gnu 0.42.0", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" [[package]] name = "windows_aarch64_msvc" @@ -11432,10 +12418,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" [[package]] -name = "windows_i686_gnu" -version = "0.32.0" +name = "windows_aarch64_msvc" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" [[package]] name = "windows_i686_gnu" @@ -11450,10 +12436,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" [[package]] -name = "windows_i686_msvc" -version = "0.32.0" +name = "windows_i686_gnu" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" [[package]] name = "windows_i686_msvc" @@ -11468,10 +12454,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" [[package]] -name = "windows_x86_64_gnu" -version = "0.32.0" +name = "windows_i686_msvc" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" [[package]] name = "windows_x86_64_gnu" @@ -11486,10 +12472,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" [[package]] -name = "windows_x86_64_msvc" -version = "0.32.0" +name = "windows_x86_64_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" [[package]] name = "windows_x86_64_msvc" @@ -11503,20 +12495,26 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" + [[package]] name = "winreg" -version = "0.7.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" dependencies = [ "winapi", ] [[package]] name = "wyz" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" dependencies = [ "tap", ] @@ -11527,11 +12525,59 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f" dependencies = [ - "curve25519-dalek 3.0.2", + "curve25519-dalek 3.2.0", "rand_core 0.5.1", "zeroize", ] +[[package]] +name = "x25519-dalek" +version = "2.0.0-pre.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5da623d8af10a62342bcbbb230e33e58a63255a58012f8653c578e54bab48df" +dependencies = [ + "curve25519-dalek 3.2.0", + "rand_core 0.6.4", + "zeroize", +] + +[[package]] +name = "x509-parser" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9bace5b5589ffead1afb76e43e34cff39cd0f3ce7e170ae0c29e53b88eb1c" +dependencies = [ + "asn1-rs 0.3.1", + "base64", + "data-encoding", + "der-parser 7.0.0", + "lazy_static", + "nom", + "oid-registry 0.4.0", + "ring", + "rusticata-macros", + "thiserror", + "time 0.3.17", +] + +[[package]] +name = "x509-parser" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0ecbeb7b67ce215e40e3cc7f2ff902f94a223acf44995934763467e7b1febc8" +dependencies = [ + "asn1-rs 0.5.1", + "base64", + "data-encoding", + "der-parser 8.1.0", + "lazy_static", + "nom", + "oid-registry 0.6.1", + "rusticata-macros", + "thiserror", + "time 0.3.17", +] + [[package]] name = "yamux" version = "0.10.2" @@ -11546,6 +12592,21 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + +[[package]] +name = "yasna" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aed2e7a52e3744ab4d0c05c20aa065258e84c49fd4226f5191b2ed29712710b4" +dependencies = [ + "time 0.3.17", +] + [[package]] name = "zeroize" version = "1.5.7" @@ -11557,9 +12618,9 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.3.2" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" +checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c" dependencies = [ "proc-macro2", "quote", @@ -11588,9 +12649,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.1+zstd.1.5.2" +version = "2.0.4+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fd07cbbc53846d9145dbffdf6dd09a7a0aa52be46741825f5c97bdd4f73f12b" +checksum = "4fa202f2ef00074143e219d15b62ffc317d17cc33909feac471c044087cad7b0" dependencies = [ "cc", "libc", diff --git a/client/authority-discovery/Cargo.toml b/client/authority-discovery/Cargo.toml index 5b07e4b7bb..a24002517c 100644 --- a/client/authority-discovery/Cargo.toml +++ b/client/authority-discovery/Cargo.toml @@ -21,7 +21,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = futures = "0.3.21" futures-timer = "3.0.1" ip_network = "0.4.1" -libp2p = { version = "0.49.0", default-features = false, features = ["kad"] } +libp2p = { version = "0.50.0", features = ["kad"] } log = "0.4.17" prost = "0.11" rand = "0.8.5" diff --git a/client/cli/Cargo.toml b/client/cli/Cargo.toml index ee4426b91d..5e1f2bab4b 100644 --- a/client/cli/Cargo.toml +++ b/client/cli/Cargo.toml @@ -18,7 +18,7 @@ chrono = "0.4.10" clap = { version = "4.0.9", features = ["derive", "string"] } fdlimit = "0.2.1" futures = "0.3.21" -libp2p = "0.49.0" +libp2p = "0.50.0" log = "0.4.17" names = { version = "0.13.0", default-features = false } parity-scale-codec = "3.0.0" diff --git a/client/cli/src/runner.rs b/client/cli/src/runner.rs index 1a532b3bbc..c7a6f1f3c0 100644 --- a/client/cli/src/runner.rs +++ b/client/cli/src/runner.rs @@ -228,7 +228,7 @@ impl Runner { pub fn print_node_infos(config: &Configuration) { info!("{}", C::impl_name()); info!("✌️ version {}", C::impl_version()); - info!("❤️ by {}, {}-{}", C::author(), C::copyright_start_year(), Local::today().year()); + info!("❤️ by {}, {}-{}", C::author(), C::copyright_start_year(), Local::now().year()); info!("📋 Chain specification: {}", config.chain_spec.name()); info!("🏷 Node name: {}", config.network.node_name); info!("👤 Role: {}", config.display_role()); diff --git a/client/consensus/common/Cargo.toml b/client/consensus/common/Cargo.toml index b61c6a4334..87b052d434 100644 --- a/client/consensus/common/Cargo.toml +++ b/client/consensus/common/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] async-trait = "0.1.57" futures = { version = "0.3.21", features = ["thread-pool"] } futures-timer = "3.0.1" -libp2p = { version = "0.49.0", default-features = false } +libp2p = "0.50.0" log = "0.4.17" mockall = "0.11.2" parking_lot = "0.12.1" diff --git a/client/network-gossip/Cargo.toml b/client/network-gossip/Cargo.toml index c40a830f92..ef23062768 100644 --- a/client/network-gossip/Cargo.toml +++ b/client/network-gossip/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] ahash = "0.7.6" futures = "0.3.21" futures-timer = "3.0.1" -libp2p = { version = "0.49.0", default-features = false } +libp2p = "0.50.0" log = "0.4.17" lru = "0.8.1" tracing = "0.1.29" diff --git a/client/network/Cargo.toml b/client/network/Cargo.toml index 35d4253f6b..4356cc5df1 100644 --- a/client/network/Cargo.toml +++ b/client/network/Cargo.toml @@ -25,7 +25,7 @@ fnv = "1.0.6" futures = "0.3.21" futures-timer = "3.0.2" ip_network = "0.4.1" -libp2p = { version = "0.49.0", features = ["dns", "identify", "kad", "mdns", "mplex", "noise", "ping", "tcp", "tokio", "yamux", "websocket"] } +libp2p = { version = "0.50.0", features = ["dns", "identify", "kad", "macros", "mdns", "mplex", "noise", "ping", "tcp", "tokio", "yamux", "websocket"] } log = "0.4.17" lru = "0.8.1" parking_lot = "0.12.1" diff --git a/client/network/bitswap/Cargo.toml b/client/network/bitswap/Cargo.toml index c4c7850b71..0bbb5ff09e 100644 --- a/client/network/bitswap/Cargo.toml +++ b/client/network/bitswap/Cargo.toml @@ -18,7 +18,7 @@ prost-build = "0.11" [dependencies] cid = "0.8.6" futures = "0.3.21" -libp2p = "0.49.0" +libp2p = "0.50.0" log = "0.4.17" prost = "0.11" thiserror = "1.0" diff --git a/client/network/common/Cargo.toml b/client/network/common/Cargo.toml index 545ae8a8af..e2dd7d0894 100644 --- a/client/network/common/Cargo.toml +++ b/client/network/common/Cargo.toml @@ -24,7 +24,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", features = [ ] } futures = "0.3.21" futures-timer = "3.0.2" -libp2p = { version = "0.49.0", features = [ "request-response", "kad" ] } +libp2p = { version = "0.50.0", features = ["request-response", "kad"] } linked_hash_set = "0.1.3" prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../../utils/prometheus" } smallvec = "1.8.0" diff --git a/client/network/light/Cargo.toml b/client/network/light/Cargo.toml index 5b84a0adde..8a3e903772 100644 --- a/client/network/light/Cargo.toml +++ b/client/network/light/Cargo.toml @@ -21,7 +21,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", features = [ "derive", ] } futures = "0.3.21" -libp2p = "0.49.0" +libp2p = "0.50.0" log = "0.4.16" prost = "0.11" sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } diff --git a/client/network/src/behaviour.rs b/client/network/src/behaviour.rs index 3a977edbca..eac74f7e6e 100644 --- a/client/network/src/behaviour.rs +++ b/client/network/src/behaviour.rs @@ -29,7 +29,7 @@ use libp2p::{ core::{Multiaddr, PeerId, PublicKey}, identify::Info as IdentifyInfo, kad::record, - NetworkBehaviour, + swarm::NetworkBehaviour, }; use sc_network_common::{ diff --git a/client/network/src/discovery.rs b/client/network/src/discovery.rs index 13b153be11..16dbc7662c 100644 --- a/client/network/src/discovery.rs +++ b/client/network/src/discovery.rs @@ -51,23 +51,23 @@ use futures::prelude::*; use futures_timer::Delay; use ip_network::IpNetwork; use libp2p::{ - core::{ - connection::ConnectionId, transport::ListenerId, ConnectedPoint, Multiaddr, PeerId, - PublicKey, - }, + core::{connection::ConnectionId, Multiaddr, PeerId, PublicKey}, kad::{ handler::KademliaHandlerProto, record::{ self, store::{MemoryStore, RecordStore}, }, - GetClosestPeersError, Kademlia, KademliaBucketInserts, KademliaConfig, KademliaEvent, - QueryId, QueryResult, Quorum, Record, + GetClosestPeersError, GetRecordOk, Kademlia, KademliaBucketInserts, KademliaConfig, + KademliaEvent, QueryId, QueryResult, Quorum, Record, }, - mdns::{MdnsConfig, MdnsEvent, TokioMdns}, + mdns::{self, tokio::Behaviour as TokioMdns}, multiaddr::Protocol, swarm::{ - behaviour::toggle::{Toggle, ToggleIntoConnectionHandler}, + behaviour::{ + toggle::{Toggle, ToggleIntoConnectionHandler}, + DialFailure, FromSwarm, NewExternalAddr, + }, ConnectionHandler, DialError, IntoConnectionHandler, NetworkBehaviour, NetworkBehaviourAction, PollParameters, }, @@ -78,7 +78,6 @@ use sp_core::hexdisplay::HexDisplay; use std::{ cmp, collections::{HashMap, HashSet, VecDeque}, - io, num::NonZeroUsize, task::{Context, Poll}, time::Duration, @@ -235,7 +234,7 @@ impl DiscoveryConfig { allow_private_ipv4, discovery_only_if_under_num, mdns: if enable_mdns { - match TokioMdns::new(MdnsConfig::default()) { + match TokioMdns::new(mdns::Config::default()) { Ok(mdns) => Some(mdns), Err(err) => { warn!(target: "sub-libp2p", "Failed to initialize mDNS: {:?}", err); @@ -250,6 +249,7 @@ impl DiscoveryConfig { NonZeroUsize::new(MAX_KNOWN_EXTERNAL_ADDRESSES) .expect("value is a constant; constant is non-zero; qed."), ), + outbound_query_records: Vec::new(), } } } @@ -287,6 +287,8 @@ pub struct DiscoveryBehaviour { allow_non_globals_in_dht: bool, /// A cache of discovered external addresses. Only used for logging purposes. known_external_addresses: LruHashSet, + /// A cache of outbound query records. + outbound_query_records: Vec<(record::Key, Vec)>, } impl DiscoveryBehaviour { @@ -367,7 +369,7 @@ impl DiscoveryBehaviour { /// A corresponding `ValueFound` or `ValueNotFound` event will later be generated. pub fn get_value(&mut self, key: record::Key) { if let Some(k) = self.kademlia.as_mut() { - k.get_record(key.clone(), Quorum::One); + k.get_record(key.clone()); } } @@ -516,127 +518,84 @@ impl NetworkBehaviour for DiscoveryBehaviour { list } - fn inject_address_change( - &mut self, - peer_id: &PeerId, - connection_id: &ConnectionId, - old: &ConnectedPoint, - new: &ConnectedPoint, - ) { - self.kademlia.inject_address_change(peer_id, connection_id, old, new) - } - - fn inject_connection_established( - &mut self, - peer_id: &PeerId, - conn: &ConnectionId, - endpoint: &ConnectedPoint, - failed_addresses: Option<&Vec>, - other_established: usize, - ) { - self.num_connections += 1; - self.kademlia.inject_connection_established( - peer_id, - conn, - endpoint, - failed_addresses, - other_established, - ) - } + fn on_swarm_event(&mut self, event: FromSwarm) { + match event { + FromSwarm::ConnectionEstablished(e) => { + self.num_connections += 1; + self.kademlia.on_swarm_event(FromSwarm::ConnectionEstablished(e)); + }, + FromSwarm::ConnectionClosed(e) => { + self.num_connections -= 1; + self.kademlia.on_swarm_event(FromSwarm::ConnectionClosed(e)); + }, + FromSwarm::DialFailure(e @ DialFailure { peer_id, error, .. }) => { + if let Some(peer_id) = peer_id { + if let DialError::Transport(errors) = error { + if let Some(list) = self.ephemeral_addresses.get_mut(&peer_id) { + for (addr, _error) in errors { + list.retain(|a| a != addr); + } + } + } + } - fn inject_connection_closed( - &mut self, - peer_id: &PeerId, - conn: &ConnectionId, - endpoint: &ConnectedPoint, - handler: ::Handler, - remaining_established: usize, - ) { - self.num_connections -= 1; - self.kademlia.inject_connection_closed( - peer_id, - conn, - endpoint, - handler, - remaining_established, - ) - } + self.kademlia.on_swarm_event(FromSwarm::DialFailure(e)); + }, + FromSwarm::ListenerClosed(e) => { + self.kademlia.on_swarm_event(FromSwarm::ListenerClosed(e)); + }, + FromSwarm::ListenFailure(e) => { + self.kademlia.on_swarm_event(FromSwarm::ListenFailure(e)); + }, + FromSwarm::ListenerError(e) => { + self.kademlia.on_swarm_event(FromSwarm::ListenerError(e)); + }, + FromSwarm::ExpiredExternalAddr(e) => { + // We intentionally don't remove the element from `known_external_addresses` in + // order to not print the log line again. - fn inject_dial_failure( - &mut self, - peer_id: Option, - handler: Self::ConnectionHandler, - error: &DialError, - ) { - if let Some(peer_id) = peer_id { - if let DialError::Transport(errors) = error { - if let Some(list) = self.ephemeral_addresses.get_mut(&peer_id) { - for (addr, _error) in errors { - list.retain(|a| a != addr); + self.kademlia.on_swarm_event(FromSwarm::ExpiredExternalAddr(e)); + }, + FromSwarm::NewListener(e) => { + self.kademlia.on_swarm_event(FromSwarm::NewListener(e)); + }, + FromSwarm::ExpiredListenAddr(e) => { + self.kademlia.on_swarm_event(FromSwarm::ExpiredListenAddr(e)); + }, + FromSwarm::NewExternalAddr(e @ NewExternalAddr { addr }) => { + let new_addr = addr.clone().with(Protocol::P2p(self.local_peer_id.into())); + + if Self::can_add_to_dht(addr) { + // NOTE: we might re-discover the same address multiple times + // in which case we just want to refrain from logging. + if self.known_external_addresses.insert(new_addr.clone()) { + info!( + target: "sub-libp2p", + "🔍 Discovered new external address for our node: {}", + new_addr, + ); } } - } - } - self.kademlia.inject_dial_failure(peer_id, handler, error) + self.kademlia.on_swarm_event(FromSwarm::NewExternalAddr(e)); + }, + FromSwarm::AddressChange(e) => { + self.kademlia.on_swarm_event(FromSwarm::AddressChange(e)); + }, + FromSwarm::NewListenAddr(e) => { + self.kademlia.on_swarm_event(FromSwarm::NewListenAddr(e)); + }, + } } - fn inject_event( + fn on_connection_handler_event( &mut self, peer_id: PeerId, - connection: ConnectionId, - event: <::Handler as ConnectionHandler>::OutEvent, + connection_id: ConnectionId, + event: <::Handler as + ConnectionHandler>::OutEvent, ) { - self.kademlia.inject_event(peer_id, connection, event) - } - - fn inject_new_external_addr(&mut self, addr: &Multiaddr) { - let new_addr = addr.clone().with(Protocol::P2p(self.local_peer_id.into())); - - if Self::can_add_to_dht(addr) { - // NOTE: we might re-discover the same address multiple times - // in which case we just want to refrain from logging. - if self.known_external_addresses.insert(new_addr.clone()) { - info!( - target: "sub-libp2p", - "🔍 Discovered new external address for our node: {}", - new_addr, - ); - } - } - - self.kademlia.inject_new_external_addr(addr) - } - - fn inject_expired_external_addr(&mut self, addr: &Multiaddr) { - // We intentionally don't remove the element from `known_external_addresses` in order - // to not print the log line again. - - self.kademlia.inject_expired_external_addr(addr) - } - - fn inject_expired_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { - self.kademlia.inject_expired_listen_addr(id, addr) - } - - fn inject_new_listener(&mut self, id: ListenerId) { - self.kademlia.inject_new_listener(id) - } - - fn inject_new_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { - self.kademlia.inject_new_listen_addr(id, addr) - } - - fn inject_listen_failure(&mut self, _: &Multiaddr, _: &Multiaddr, _: Self::ConnectionHandler) { - // NetworkBehaviour::inject_listen_failure on Kademlia does nothing. - } - - fn inject_listener_error(&mut self, id: ListenerId, err: &(dyn std::error::Error + 'static)) { - self.kademlia.inject_listener_error(id, err) - } - - fn inject_listener_closed(&mut self, id: ListenerId, reason: Result<(), &io::Error>) { - self.kademlia.inject_listener_closed(id, reason) + self.kademlia.on_connection_handler_event(peer_id, connection_id, event); } fn poll( @@ -705,7 +664,7 @@ impl NetworkBehaviour for DiscoveryBehaviour { KademliaEvent::InboundRequest { .. } => { // We are not interested in this event at the moment. }, - KademliaEvent::OutboundQueryCompleted { + KademliaEvent::OutboundQueryProgressed { result: QueryResult::GetClosestPeers(res), .. } => match res { @@ -730,24 +689,36 @@ impl NetworkBehaviour for DiscoveryBehaviour { } }, }, - KademliaEvent::OutboundQueryCompleted { + KademliaEvent::OutboundQueryProgressed { result: QueryResult::GetRecord(res), stats, + step, .. } => { let ev = match res { - Ok(ok) => { - let results = ok - .records - .into_iter() - .map(|r| (r.record.key, r.record.value)) - .collect(); - - DiscoveryOut::ValueFound( - results, - stats.duration().unwrap_or_default(), - ) - }, + Ok(ok) => + if let GetRecordOk::FoundRecord(r) = ok { + self.outbound_query_records + .push((r.record.key, r.record.value)); + continue + } else { + debug!( + target: "sub-libp2p", + "Libp2p => Query progressed to {:?} step (last: {:?})", + step.count, + step.last, + ); + if step.last { + let records = + self.outbound_query_records.drain(..).collect(); + DiscoveryOut::ValueFound( + records, + stats.duration().unwrap_or_default(), + ) + } else { + continue + } + }, Err(e @ libp2p::kad::GetRecordError::NotFound { .. }) => { trace!( target: "sub-libp2p", @@ -773,7 +744,7 @@ impl NetworkBehaviour for DiscoveryBehaviour { }; return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) }, - KademliaEvent::OutboundQueryCompleted { + KademliaEvent::OutboundQueryProgressed { result: QueryResult::PutRecord(res), stats, .. @@ -795,7 +766,7 @@ impl NetworkBehaviour for DiscoveryBehaviour { }; return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) }, - KademliaEvent::OutboundQueryCompleted { + KademliaEvent::OutboundQueryProgressed { result: QueryResult::RepublishRecord(res), .. } => match res { @@ -811,7 +782,7 @@ impl NetworkBehaviour for DiscoveryBehaviour { ), }, // We never start any other type of query. - KademliaEvent::OutboundQueryCompleted { result: e, .. } => { + KademliaEvent::OutboundQueryProgressed { result: e, .. } => { warn!(target: "sub-libp2p", "Libp2p => Unhandled Kademlia event: {:?}", e) }, }, @@ -841,7 +812,7 @@ impl NetworkBehaviour for DiscoveryBehaviour { while let Poll::Ready(ev) = mdns.poll(cx, params) { match ev { NetworkBehaviourAction::GenerateEvent(event) => match event { - MdnsEvent::Discovered(list) => { + mdns::Event::Discovered(list) => { if self.num_connections >= self.discovery_only_if_under_num { continue } @@ -852,7 +823,7 @@ impl NetworkBehaviour for DiscoveryBehaviour { return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) } }, - MdnsEvent::Expired(_) => {}, + mdns::Event::Expired(_) => {}, }, NetworkBehaviourAction::Dial { .. } => { unreachable!("mDNS never dials!"); @@ -907,12 +878,19 @@ mod tests { }, identity::{ed25519, Keypair}, noise, - swarm::{Swarm, SwarmEvent}, + swarm::{Executor, Swarm, SwarmEvent}, yamux, Multiaddr, }; use sc_network_common::config::ProtocolId; use sp_core::hash::H256; - use std::{collections::HashSet, task::Poll}; + use std::{collections::HashSet, pin::Pin, task::Poll}; + + struct TokioExecutor(tokio::runtime::Runtime); + impl Executor for TokioExecutor { + fn exec(&self, f: Pin + Send>>) { + let _ = self.0.spawn(f); + } + } #[test] fn discovery_working() { @@ -949,7 +927,13 @@ mod tests { config.finish() }; - let mut swarm = Swarm::new(transport, behaviour, keypair.public().to_peer_id()); + let runtime = tokio::runtime::Runtime::new().unwrap(); + let mut swarm = Swarm::with_executor( + transport, + behaviour, + keypair.public().to_peer_id(), + TokioExecutor(runtime), + ); let listen_addr: Multiaddr = format!("/memory/{}", rand::random::()).parse().unwrap(); diff --git a/client/network/src/peer_info.rs b/client/network/src/peer_info.rs index e04d006f50..97604a82c3 100644 --- a/client/network/src/peer_info.rs +++ b/client/network/src/peer_info.rs @@ -19,16 +19,17 @@ use fnv::FnvHashMap; use futures::prelude::*; use libp2p::{ - core::{ - connection::ConnectionId, either::EitherOutput, transport::ListenerId, ConnectedPoint, - PeerId, PublicKey, - }, + core::{connection::ConnectionId, either::EitherOutput, ConnectedPoint, PeerId, PublicKey}, identify::{ Behaviour as Identify, Config as IdentifyConfig, Event as IdentifyEvent, Info as IdentifyInfo, }, ping::{Behaviour as Ping, Config as PingConfig, Event as PingEvent, Success as PingSuccess}, swarm::{ + behaviour::{ + AddressChange, ConnectionClosed, ConnectionEstablished, DialFailure, FromSwarm, + ListenFailure, + }, ConnectionHandler, IntoConnectionHandler, IntoConnectionHandlerSelect, NetworkBehaviour, NetworkBehaviourAction, PollParameters, }, @@ -39,7 +40,6 @@ use sc_network_common::utils::interval; use smallvec::SmallVec; use std::{ collections::hash_map::Entry, - error, io, pin::Pin, task::{Context, Poll}, time::{Duration, Instant}, @@ -188,172 +188,151 @@ impl NetworkBehaviour for PeerInfoBehaviour { list } - fn inject_address_change( - &mut self, - peer_id: &PeerId, - conn: &ConnectionId, - old: &ConnectedPoint, - new: &ConnectedPoint, - ) { - self.ping.inject_address_change(peer_id, conn, old, new); - self.identify.inject_address_change(peer_id, conn, old, new); - - if let Some(entry) = self.nodes_info.get_mut(peer_id) { - if let Some(endpoint) = entry.endpoints.iter_mut().find(|e| e == &old) { - *endpoint = new.clone(); - } else { - error!(target: "sub-libp2p", - "Unknown address change for peer {:?} from {:?} to {:?}", peer_id, old, new); - } - } else { - error!(target: "sub-libp2p", - "Unknown peer {:?} to change address from {:?} to {:?}", peer_id, old, new); - } - } - - fn inject_connection_established( - &mut self, - peer_id: &PeerId, - conn: &ConnectionId, - endpoint: &ConnectedPoint, - failed_addresses: Option<&Vec>, - other_established: usize, - ) { - self.ping.inject_connection_established( - peer_id, - conn, - endpoint, - failed_addresses, - other_established, - ); - self.identify.inject_connection_established( - peer_id, - conn, - endpoint, - failed_addresses, - other_established, - ); - match self.nodes_info.entry(*peer_id) { - Entry::Vacant(e) => { - e.insert(NodeInfo::new(endpoint.clone())); + fn on_swarm_event(&mut self, event: FromSwarm) { + match event { + FromSwarm::ConnectionEstablished( + e @ ConnectionEstablished { peer_id, endpoint, .. }, + ) => { + self.ping.on_swarm_event(FromSwarm::ConnectionEstablished(e)); + self.identify.on_swarm_event(FromSwarm::ConnectionEstablished(e)); + + match self.nodes_info.entry(peer_id) { + Entry::Vacant(e) => { + e.insert(NodeInfo::new(endpoint.clone())); + }, + Entry::Occupied(e) => { + let e = e.into_mut(); + if e.info_expire.as_ref().map(|exp| *exp < Instant::now()).unwrap_or(false) + { + e.client_version = None; + e.latest_ping = None; + } + e.info_expire = None; + e.endpoints.push(endpoint.clone()); + }, + } }, - Entry::Occupied(e) => { - let e = e.into_mut(); - if e.info_expire.as_ref().map(|exp| *exp < Instant::now()).unwrap_or(false) { - e.client_version = None; - e.latest_ping = None; + FromSwarm::ConnectionClosed(ConnectionClosed { + peer_id, + connection_id, + endpoint, + handler, + remaining_established, + }) => { + let (ping_handler, identity_handler) = handler.into_inner(); + self.ping.on_swarm_event(FromSwarm::ConnectionClosed(ConnectionClosed { + peer_id, + connection_id, + endpoint, + handler: ping_handler, + remaining_established, + })); + self.identify.on_swarm_event(FromSwarm::ConnectionClosed(ConnectionClosed { + peer_id, + connection_id, + endpoint, + handler: identity_handler, + remaining_established, + })); + + if let Some(entry) = self.nodes_info.get_mut(&peer_id) { + if remaining_established == 0 { + entry.info_expire = Some(Instant::now() + CACHE_EXPIRE); + } + entry.endpoints.retain(|ep| ep != endpoint) + } else { + error!(target: "sub-libp2p", + "Unknown connection to {:?} closed: {:?}", peer_id, endpoint); } - e.info_expire = None; - e.endpoints.push(endpoint.clone()); + }, + FromSwarm::DialFailure(DialFailure { peer_id, handler, error }) => { + let (ping_handler, identity_handler) = handler.into_inner(); + self.ping.on_swarm_event(FromSwarm::DialFailure(DialFailure { + peer_id, + handler: ping_handler, + error, + })); + self.identify.on_swarm_event(FromSwarm::DialFailure(DialFailure { + peer_id, + handler: identity_handler, + error, + })); + }, + FromSwarm::ListenerClosed(e) => { + self.ping.on_swarm_event(FromSwarm::ListenerClosed(e)); + self.identify.on_swarm_event(FromSwarm::ListenerClosed(e)); + }, + FromSwarm::ListenFailure(ListenFailure { local_addr, send_back_addr, handler }) => { + let (ping_handler, identity_handler) = handler.into_inner(); + self.ping.on_swarm_event(FromSwarm::ListenFailure(ListenFailure { + local_addr, + send_back_addr, + handler: ping_handler, + })); + self.identify.on_swarm_event(FromSwarm::ListenFailure(ListenFailure { + local_addr, + send_back_addr, + handler: identity_handler, + })); + }, + FromSwarm::ListenerError(e) => { + self.ping.on_swarm_event(FromSwarm::ListenerError(e)); + self.identify.on_swarm_event(FromSwarm::ListenerError(e)); + }, + FromSwarm::ExpiredExternalAddr(e) => { + self.ping.on_swarm_event(FromSwarm::ExpiredExternalAddr(e)); + self.identify.on_swarm_event(FromSwarm::ExpiredExternalAddr(e)); + }, + FromSwarm::NewListener(e) => { + self.ping.on_swarm_event(FromSwarm::NewListener(e)); + self.identify.on_swarm_event(FromSwarm::NewListener(e)); + }, + FromSwarm::ExpiredListenAddr(e) => { + self.ping.on_swarm_event(FromSwarm::ExpiredListenAddr(e)); + self.identify.on_swarm_event(FromSwarm::ExpiredListenAddr(e)); + }, + FromSwarm::NewExternalAddr(e) => { + self.ping.on_swarm_event(FromSwarm::NewExternalAddr(e)); + self.identify.on_swarm_event(FromSwarm::NewExternalAddr(e)); + }, + FromSwarm::AddressChange(e @ AddressChange { peer_id, old, new, .. }) => { + self.ping.on_swarm_event(FromSwarm::AddressChange(e)); + self.identify.on_swarm_event(FromSwarm::AddressChange(e)); + + if let Some(entry) = self.nodes_info.get_mut(&peer_id) { + if let Some(endpoint) = entry.endpoints.iter_mut().find(|e| e == &old) { + *endpoint = new.clone(); + } else { + error!(target: "sub-libp2p", + "Unknown address change for peer {:?} from {:?} to {:?}", peer_id, old, new); + } + } else { + error!(target: "sub-libp2p", + "Unknown peer {:?} to change address from {:?} to {:?}", peer_id, old, new); + } + }, + FromSwarm::NewListenAddr(e) => { + self.ping.on_swarm_event(FromSwarm::NewListenAddr(e)); + self.identify.on_swarm_event(FromSwarm::NewListenAddr(e)); }, } } - fn inject_connection_closed( - &mut self, - peer_id: &PeerId, - conn: &ConnectionId, - endpoint: &ConnectedPoint, - handler: ::Handler, - remaining_established: usize, - ) { - let (ping_handler, identity_handler) = handler.into_inner(); - self.identify.inject_connection_closed( - peer_id, - conn, - endpoint, - identity_handler, - remaining_established, - ); - self.ping.inject_connection_closed( - peer_id, - conn, - endpoint, - ping_handler, - remaining_established, - ); - - if let Some(entry) = self.nodes_info.get_mut(peer_id) { - if remaining_established == 0 { - entry.info_expire = Some(Instant::now() + CACHE_EXPIRE); - } - entry.endpoints.retain(|ep| ep != endpoint) - } else { - error!(target: "sub-libp2p", - "Unknown connection to {:?} closed: {:?}", peer_id, endpoint); - } - } - - fn inject_event( + fn on_connection_handler_event( &mut self, peer_id: PeerId, - connection: ConnectionId, - event: <::Handler as ConnectionHandler>::OutEvent, + connection_id: ConnectionId, + event: <::Handler as + ConnectionHandler>::OutEvent, ) { match event { - EitherOutput::First(event) => self.ping.inject_event(peer_id, connection, event), - EitherOutput::Second(event) => self.identify.inject_event(peer_id, connection, event), + EitherOutput::First(event) => + self.ping.on_connection_handler_event(peer_id, connection_id, event), + EitherOutput::Second(event) => + self.identify.on_connection_handler_event(peer_id, connection_id, event), } } - fn inject_dial_failure( - &mut self, - peer_id: Option, - handler: Self::ConnectionHandler, - error: &libp2p::swarm::DialError, - ) { - let (ping_handler, identity_handler) = handler.into_inner(); - self.identify.inject_dial_failure(peer_id, identity_handler, error); - self.ping.inject_dial_failure(peer_id, ping_handler, error); - } - - fn inject_new_listener(&mut self, id: ListenerId) { - self.ping.inject_new_listener(id); - self.identify.inject_new_listener(id); - } - - fn inject_new_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { - self.ping.inject_new_listen_addr(id, addr); - self.identify.inject_new_listen_addr(id, addr); - } - - fn inject_expired_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { - self.ping.inject_expired_listen_addr(id, addr); - self.identify.inject_expired_listen_addr(id, addr); - } - - fn inject_new_external_addr(&mut self, addr: &Multiaddr) { - self.ping.inject_new_external_addr(addr); - self.identify.inject_new_external_addr(addr); - } - - fn inject_expired_external_addr(&mut self, addr: &Multiaddr) { - self.ping.inject_expired_external_addr(addr); - self.identify.inject_expired_external_addr(addr); - } - - fn inject_listen_failure( - &mut self, - local_addr: &Multiaddr, - send_back_addr: &Multiaddr, - handler: Self::ConnectionHandler, - ) { - let (ping_handler, identity_handler) = handler.into_inner(); - self.identify - .inject_listen_failure(local_addr, send_back_addr, identity_handler); - self.ping.inject_listen_failure(local_addr, send_back_addr, ping_handler); - } - - fn inject_listener_error(&mut self, id: ListenerId, err: &(dyn error::Error + 'static)) { - self.ping.inject_listener_error(id, err); - self.identify.inject_listener_error(id, err); - } - - fn inject_listener_closed(&mut self, id: ListenerId, reason: Result<(), &io::Error>) { - self.ping.inject_listener_closed(id, reason); - self.identify.inject_listener_closed(id, reason); - } - fn poll( &mut self, cx: &mut Context, diff --git a/client/network/src/protocol.rs b/client/network/src/protocol.rs index 1f0ec9c0d1..fd25c6526f 100644 --- a/client/network/src/protocol.rs +++ b/client/network/src/protocol.rs @@ -22,10 +22,10 @@ use bytes::Bytes; use codec::{Decode, DecodeAll, Encode}; use futures::prelude::*; use libp2p::{ - core::{connection::ConnectionId, transport::ListenerId, ConnectedPoint}, + core::connection::ConnectionId, swarm::{ - ConnectionHandler, IntoConnectionHandler, NetworkBehaviour, NetworkBehaviourAction, - PollParameters, + behaviour::FromSwarm, ConnectionHandler, IntoConnectionHandler, NetworkBehaviour, + NetworkBehaviourAction, PollParameters, }, Multiaddr, PeerId, }; @@ -49,7 +49,7 @@ use sp_arithmetic::traits::SaturatedConversion; use sp_runtime::traits::{Block as BlockT, CheckedSub, Header as HeaderT, NumberFor, Zero}; use std::{ collections::{HashMap, HashSet, VecDeque}, - io, iter, + iter, num::NonZeroUsize, pin::Pin, sync::Arc, @@ -969,47 +969,18 @@ where self.behaviour.addresses_of_peer(peer_id) } - fn inject_connection_established( - &mut self, - peer_id: &PeerId, - conn: &ConnectionId, - endpoint: &ConnectedPoint, - failed_addresses: Option<&Vec>, - other_established: usize, - ) { - self.behaviour.inject_connection_established( - peer_id, - conn, - endpoint, - failed_addresses, - other_established, - ) + fn on_swarm_event(&mut self, event: FromSwarm) { + self.behaviour.on_swarm_event(event); } - fn inject_connection_closed( - &mut self, - peer_id: &PeerId, - conn: &ConnectionId, - endpoint: &ConnectedPoint, - handler: ::Handler, - remaining_established: usize, - ) { - self.behaviour.inject_connection_closed( - peer_id, - conn, - endpoint, - handler, - remaining_established, - ) - } - - fn inject_event( + fn on_connection_handler_event( &mut self, peer_id: PeerId, - connection: ConnectionId, - event: <::Handler as ConnectionHandler>::OutEvent, + connection_id: ConnectionId, + event: <::Handler as + ConnectionHandler>::OutEvent, ) { - self.behaviour.inject_event(peer_id, connection, event) + self.behaviour.on_connection_handler_event(peer_id, connection_id, event); } fn poll( @@ -1245,41 +1216,4 @@ where cx.waker().wake_by_ref(); Poll::Pending } - - fn inject_dial_failure( - &mut self, - peer_id: Option, - handler: Self::ConnectionHandler, - error: &libp2p::swarm::DialError, - ) { - self.behaviour.inject_dial_failure(peer_id, handler, error); - } - - fn inject_new_listener(&mut self, id: ListenerId) { - self.behaviour.inject_new_listener(id) - } - - fn inject_new_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { - self.behaviour.inject_new_listen_addr(id, addr) - } - - fn inject_expired_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { - self.behaviour.inject_expired_listen_addr(id, addr) - } - - fn inject_new_external_addr(&mut self, addr: &Multiaddr) { - self.behaviour.inject_new_external_addr(addr) - } - - fn inject_expired_external_addr(&mut self, addr: &Multiaddr) { - self.behaviour.inject_expired_external_addr(addr) - } - - fn inject_listener_error(&mut self, id: ListenerId, err: &(dyn std::error::Error + 'static)) { - self.behaviour.inject_listener_error(id, err); - } - - fn inject_listener_closed(&mut self, id: ListenerId, reason: Result<(), &io::Error>) { - self.behaviour.inject_listener_closed(id, reason); - } } diff --git a/client/network/src/protocol/notifications/behaviour.rs b/client/network/src/protocol/notifications/behaviour.rs index 04f6fe445a..2db4686712 100644 --- a/client/network/src/protocol/notifications/behaviour.rs +++ b/client/network/src/protocol/notifications/behaviour.rs @@ -24,8 +24,10 @@ use bytes::BytesMut; use fnv::FnvHashMap; use futures::prelude::*; use libp2p::{ - core::{connection::ConnectionId, ConnectedPoint, Multiaddr, PeerId}, + core::{connection::ConnectionId, Multiaddr, PeerId}, swarm::{ + behaviour::{ConnectionClosed, ConnectionEstablished, DialFailure, FromSwarm}, + handler::ConnectionHandler, DialError, IntoConnectionHandler, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, PollParameters, }, @@ -1060,202 +1062,310 @@ impl NetworkBehaviour for Notifications { Vec::new() } - fn inject_connection_established( - &mut self, - peer_id: &PeerId, - conn: &ConnectionId, - endpoint: &ConnectedPoint, - _failed_addresses: Option<&Vec>, - _other_established: usize, - ) { - for set_id in (0..self.notif_protocols.len()).map(sc_peerset::SetId::from) { - match self.peers.entry((*peer_id, set_id)).or_insert(PeerState::Poisoned) { - // Requested | PendingRequest => Enabled - st @ &mut PeerState::Requested | st @ &mut PeerState::PendingRequest { .. } => { - trace!(target: "sub-libp2p", - "Libp2p => Connected({}, {:?}, {:?}): Connection was requested by PSM.", - peer_id, set_id, endpoint - ); - trace!(target: "sub-libp2p", "Handler({:?}, {:?}) <= Open({:?})", peer_id, *conn, set_id); - self.events.push_back(NetworkBehaviourAction::NotifyHandler { - peer_id: *peer_id, - handler: NotifyHandler::One(*conn), - event: NotifsHandlerIn::Open { protocol_index: set_id.into() }, - }); + fn on_swarm_event(&mut self, event: FromSwarm) { + match event { + FromSwarm::ConnectionEstablished(ConnectionEstablished { + peer_id, + endpoint, + connection_id, + .. + }) => { + for set_id in (0..self.notif_protocols.len()).map(sc_peerset::SetId::from) { + match self.peers.entry((peer_id, set_id)).or_insert(PeerState::Poisoned) { + // Requested | PendingRequest => Enabled + st @ &mut PeerState::Requested | + st @ &mut PeerState::PendingRequest { .. } => { + trace!(target: "sub-libp2p", + "Libp2p => Connected({}, {:?}, {:?}): Connection was requested by PSM.", + peer_id, set_id, endpoint + ); + trace!(target: "sub-libp2p", "Handler({:?}, {:?}) <= Open({:?})", peer_id, connection_id, set_id); + self.events.push_back(NetworkBehaviourAction::NotifyHandler { + peer_id, + handler: NotifyHandler::One(connection_id), + event: NotifsHandlerIn::Open { protocol_index: set_id.into() }, + }); + + let mut connections = SmallVec::new(); + connections.push((connection_id, ConnectionState::Opening)); + *st = PeerState::Enabled { connections }; + }, - let mut connections = SmallVec::new(); - connections.push((*conn, ConnectionState::Opening)); - *st = PeerState::Enabled { connections }; - }, + // Poisoned gets inserted above if the entry was missing. + // Ø | Backoff => Disabled + st @ &mut PeerState::Poisoned | st @ &mut PeerState::Backoff { .. } => { + let backoff_until = + if let PeerState::Backoff { timer_deadline, .. } = st { + Some(*timer_deadline) + } else { + None + }; + trace!(target: "sub-libp2p", + "Libp2p => Connected({}, {:?}, {:?}, {:?}): Not requested by PSM, disabling.", + peer_id, set_id, endpoint, connection_id); + + let mut connections = SmallVec::new(); + connections.push((connection_id, ConnectionState::Closed)); + *st = PeerState::Disabled { connections, backoff_until }; + }, - // Poisoned gets inserted above if the entry was missing. - // Ø | Backoff => Disabled - st @ &mut PeerState::Poisoned | st @ &mut PeerState::Backoff { .. } => { - let backoff_until = if let PeerState::Backoff { timer_deadline, .. } = st { - Some(*timer_deadline) + // In all other states, add this new connection to the list of closed + // inactive connections. + PeerState::Incoming { connections, .. } | + PeerState::Disabled { connections, .. } | + PeerState::DisabledPendingEnable { connections, .. } | + PeerState::Enabled { connections, .. } => { + trace!(target: "sub-libp2p", + "Libp2p => Connected({}, {:?}, {:?}, {:?}): Secondary connection. Leaving closed.", + peer_id, set_id, endpoint, connection_id); + connections.push((connection_id, ConnectionState::Closed)); + }, + } + } + }, + FromSwarm::ConnectionClosed(ConnectionClosed { peer_id, connection_id, .. }) => { + for set_id in (0..self.notif_protocols.len()).map(sc_peerset::SetId::from) { + let mut entry = if let Entry::Occupied(entry) = + self.peers.entry((peer_id, set_id)) + { + entry } else { - None + error!(target: "sub-libp2p", "inject_connection_closed: State mismatch in the custom protos handler"); + debug_assert!(false); + return }; - trace!(target: "sub-libp2p", - "Libp2p => Connected({}, {:?}, {:?}, {:?}): Not requested by PSM, disabling.", - peer_id, set_id, endpoint, *conn); - - let mut connections = SmallVec::new(); - connections.push((*conn, ConnectionState::Closed)); - *st = PeerState::Disabled { connections, backoff_until }; - }, - // In all other states, add this new connection to the list of closed inactive - // connections. - PeerState::Incoming { connections, .. } | - PeerState::Disabled { connections, .. } | - PeerState::DisabledPendingEnable { connections, .. } | - PeerState::Enabled { connections, .. } => { - trace!(target: "sub-libp2p", - "Libp2p => Connected({}, {:?}, {:?}, {:?}): Secondary connection. Leaving closed.", - peer_id, set_id, endpoint, *conn); - connections.push((*conn, ConnectionState::Closed)); - }, - } - } - } + match mem::replace(entry.get_mut(), PeerState::Poisoned) { + // Disabled => Disabled | Backoff | Ø + PeerState::Disabled { mut connections, backoff_until } => { + trace!(target: "sub-libp2p", "Libp2p => Disconnected({}, {:?}, {:?}): Disabled.", + peer_id, set_id, connection_id); - fn inject_connection_closed( - &mut self, - peer_id: &PeerId, - conn: &ConnectionId, - _endpoint: &ConnectedPoint, - _handler: ::Handler, - _remaining_established: usize, - ) { - for set_id in (0..self.notif_protocols.len()).map(sc_peerset::SetId::from) { - let mut entry = if let Entry::Occupied(entry) = self.peers.entry((*peer_id, set_id)) { - entry - } else { - error!(target: "sub-libp2p", "inject_connection_closed: State mismatch in the custom protos handler"); - debug_assert!(false); - return - }; + if let Some(pos) = + connections.iter().position(|(c, _)| *c == connection_id) + { + connections.remove(pos); + } else { + debug_assert!(false); + error!(target: "sub-libp2p", + "inject_connection_closed: State mismatch in the custom protos handler"); + } - match mem::replace(entry.get_mut(), PeerState::Poisoned) { - // Disabled => Disabled | Backoff | Ø - PeerState::Disabled { mut connections, backoff_until } => { - trace!(target: "sub-libp2p", "Libp2p => Disconnected({}, {:?}, {:?}): Disabled.", - peer_id, set_id, *conn); + if connections.is_empty() { + if let Some(until) = backoff_until { + let now = Instant::now(); + if until > now { + let delay_id = self.next_delay_id; + self.next_delay_id.0 += 1; + let delay = futures_timer::Delay::new(until - now); + self.delays.push( + async move { + delay.await; + (delay_id, peer_id, set_id) + } + .boxed(), + ); + + *entry.get_mut() = PeerState::Backoff { + timer: delay_id, + timer_deadline: until, + }; + } else { + entry.remove(); + } + } else { + entry.remove(); + } + } else { + *entry.get_mut() = + PeerState::Disabled { connections, backoff_until }; + } + }, - if let Some(pos) = connections.iter().position(|(c, _)| *c == *conn) { - connections.remove(pos); - } else { - debug_assert!(false); - error!(target: "sub-libp2p", - "inject_connection_closed: State mismatch in the custom protos handler"); - } + // DisabledPendingEnable => DisabledPendingEnable | Backoff + PeerState::DisabledPendingEnable { + mut connections, + timer_deadline, + timer, + } => { + trace!( + target: "sub-libp2p", + "Libp2p => Disconnected({}, {:?}, {:?}): Disabled but pending enable.", + peer_id, set_id, connection_id + ); - if connections.is_empty() { - if let Some(until) = backoff_until { - let now = Instant::now(); - if until > now { - let delay_id = self.next_delay_id; - self.next_delay_id.0 += 1; - let delay = futures_timer::Delay::new(until - now); - let peer_id = *peer_id; - self.delays.push( - async move { - delay.await; - (delay_id, peer_id, set_id) - } - .boxed(), - ); + if let Some(pos) = + connections.iter().position(|(c, _)| *c == connection_id) + { + connections.remove(pos); + } else { + error!(target: "sub-libp2p", + "inject_connection_closed: State mismatch in the custom protos handler"); + debug_assert!(false); + } - *entry.get_mut() = - PeerState::Backoff { timer: delay_id, timer_deadline: until }; + if connections.is_empty() { + trace!(target: "sub-libp2p", "PSM <= Dropped({}, {:?})", peer_id, set_id); + self.peerset.dropped(set_id, peer_id, DropReason::Unknown); + *entry.get_mut() = PeerState::Backoff { timer, timer_deadline }; } else { - entry.remove(); + *entry.get_mut() = PeerState::DisabledPendingEnable { + connections, + timer_deadline, + timer, + }; } - } else { - entry.remove(); - } - } else { - *entry.get_mut() = PeerState::Disabled { connections, backoff_until }; - } - }, + }, - // DisabledPendingEnable => DisabledPendingEnable | Backoff - PeerState::DisabledPendingEnable { mut connections, timer_deadline, timer } => { - trace!( - target: "sub-libp2p", - "Libp2p => Disconnected({}, {:?}, {:?}): Disabled but pending enable.", - peer_id, set_id, *conn - ); + // Incoming => Incoming | Disabled | Backoff | Ø + PeerState::Incoming { mut connections, backoff_until } => { + trace!( + target: "sub-libp2p", + "Libp2p => Disconnected({}, {:?}, {:?}): OpenDesiredByRemote.", + peer_id, set_id, connection_id + ); - if let Some(pos) = connections.iter().position(|(c, _)| *c == *conn) { - connections.remove(pos); - } else { - error!(target: "sub-libp2p", - "inject_connection_closed: State mismatch in the custom protos handler"); - debug_assert!(false); - } + debug_assert!(connections + .iter() + .any(|(_, s)| matches!(s, ConnectionState::OpenDesiredByRemote))); - if connections.is_empty() { - trace!(target: "sub-libp2p", "PSM <= Dropped({}, {:?})", peer_id, set_id); - self.peerset.dropped(set_id, *peer_id, DropReason::Unknown); - *entry.get_mut() = PeerState::Backoff { timer, timer_deadline }; - } else { - *entry.get_mut() = - PeerState::DisabledPendingEnable { connections, timer_deadline, timer }; - } - }, + if let Some(pos) = + connections.iter().position(|(c, _)| *c == connection_id) + { + connections.remove(pos); + } else { + error!(target: "sub-libp2p", + "inject_connection_closed: State mismatch in the custom protos handler"); + debug_assert!(false); + } - // Incoming => Incoming | Disabled | Backoff | Ø - PeerState::Incoming { mut connections, backoff_until } => { - trace!( - target: "sub-libp2p", - "Libp2p => Disconnected({}, {:?}, {:?}): OpenDesiredByRemote.", - peer_id, set_id, *conn - ); + let no_desired_left = !connections + .iter() + .any(|(_, s)| matches!(s, ConnectionState::OpenDesiredByRemote)); + + // If no connection is `OpenDesiredByRemote` anymore, clean up the + // peerset incoming request. + if no_desired_left { + // In the incoming state, we don't report "Dropped". Instead we will + // just ignore the corresponding Accept/Reject. + if let Some(state) = self + .incoming + .iter_mut() + .find(|i| i.alive && i.set_id == set_id && i.peer_id == peer_id) + { + state.alive = false; + } else { + error!(target: "sub-libp2p", "State mismatch in libp2p: no entry in \ + incoming corresponding to an incoming state in peers"); + debug_assert!(false); + } + } - debug_assert!(connections - .iter() - .any(|(_, s)| matches!(s, ConnectionState::OpenDesiredByRemote))); + if connections.is_empty() { + if let Some(until) = backoff_until { + let now = Instant::now(); + if until > now { + let delay_id = self.next_delay_id; + self.next_delay_id.0 += 1; + let delay = futures_timer::Delay::new(until - now); + self.delays.push( + async move { + delay.await; + (delay_id, peer_id, set_id) + } + .boxed(), + ); + + *entry.get_mut() = PeerState::Backoff { + timer: delay_id, + timer_deadline: until, + }; + } else { + entry.remove(); + } + } else { + entry.remove(); + } + } else if no_desired_left { + // If no connection is `OpenDesiredByRemote` anymore, switch to + // `Disabled`. + *entry.get_mut() = + PeerState::Disabled { connections, backoff_until }; + } else { + *entry.get_mut() = + PeerState::Incoming { connections, backoff_until }; + } + }, - if let Some(pos) = connections.iter().position(|(c, _)| *c == *conn) { - connections.remove(pos); - } else { - error!(target: "sub-libp2p", - "inject_connection_closed: State mismatch in the custom protos handler"); - debug_assert!(false); - } + // Enabled => Enabled | Backoff + // Peers are always backed-off when disconnecting while Enabled. + PeerState::Enabled { mut connections } => { + trace!( + target: "sub-libp2p", + "Libp2p => Disconnected({}, {:?}, {:?}): Enabled.", + peer_id, set_id, connection_id + ); - let no_desired_left = !connections - .iter() - .any(|(_, s)| matches!(s, ConnectionState::OpenDesiredByRemote)); - - // If no connection is `OpenDesiredByRemote` anymore, clean up the peerset - // incoming request. - if no_desired_left { - // In the incoming state, we don't report "Dropped". Instead we will just - // ignore the corresponding Accept/Reject. - if let Some(state) = self - .incoming - .iter_mut() - .find(|i| i.alive && i.set_id == set_id && i.peer_id == *peer_id) - { - state.alive = false; - } else { - error!(target: "sub-libp2p", "State mismatch in libp2p: no entry in \ - incoming corresponding to an incoming state in peers"); - debug_assert!(false); - } - } + debug_assert!(connections.iter().any(|(_, s)| matches!( + s, + ConnectionState::Opening | ConnectionState::Open(_) + ))); + + if let Some(pos) = + connections.iter().position(|(c, _)| *c == connection_id) + { + let (_, state) = connections.remove(pos); + if let ConnectionState::Open(_) = state { + if let Some((replacement_pos, replacement_sink)) = connections + .iter() + .enumerate() + .find_map(|(num, (_, s))| match s { + ConnectionState::Open(s) => Some((num, s.clone())), + _ => None, + }) { + if pos <= replacement_pos { + trace!( + target: "sub-libp2p", + "External API <= Sink replaced({}, {:?})", + peer_id, set_id + ); + let event = NotificationsOut::CustomProtocolReplaced { + peer_id, + set_id, + notifications_sink: replacement_sink, + }; + self.events.push_back( + NetworkBehaviourAction::GenerateEvent(event), + ); + } + } else { + trace!( + target: "sub-libp2p", "External API <= Closed({}, {:?})", + peer_id, set_id + ); + let event = NotificationsOut::CustomProtocolClosed { + peer_id, + set_id, + }; + self.events.push_back( + NetworkBehaviourAction::GenerateEvent(event), + ); + } + } + } else { + error!(target: "sub-libp2p", + "inject_connection_closed: State mismatch in the custom protos handler"); + debug_assert!(false); + } + + if connections.is_empty() { + trace!(target: "sub-libp2p", "PSM <= Dropped({}, {:?})", peer_id, set_id); + self.peerset.dropped(set_id, peer_id, DropReason::Unknown); + let ban_dur = Uniform::new(5, 10).sample(&mut rand::thread_rng()); - if connections.is_empty() { - if let Some(until) = backoff_until { - let now = Instant::now(); - if until > now { let delay_id = self.next_delay_id; self.next_delay_id.0 += 1; - let delay = futures_timer::Delay::new(until - now); - let peer_id = *peer_id; + let delay = futures_timer::Delay::new(Duration::from_secs(ban_dur)); self.delays.push( async move { delay.await; @@ -1264,209 +1374,137 @@ impl NetworkBehaviour for Notifications { .boxed(), ); + *entry.get_mut() = PeerState::Backoff { + timer: delay_id, + timer_deadline: Instant::now() + Duration::from_secs(ban_dur), + }; + } else if !connections.iter().any(|(_, s)| { + matches!(s, ConnectionState::Opening | ConnectionState::Open(_)) + }) { + trace!(target: "sub-libp2p", "PSM <= Dropped({}, {:?})", peer_id, set_id); + self.peerset.dropped(set_id, peer_id, DropReason::Unknown); + *entry.get_mut() = - PeerState::Backoff { timer: delay_id, timer_deadline: until }; + PeerState::Disabled { connections, backoff_until: None }; } else { - entry.remove(); + *entry.get_mut() = PeerState::Enabled { connections }; } - } else { - entry.remove(); - } - } else if no_desired_left { - // If no connection is `OpenDesiredByRemote` anymore, switch to `Disabled`. - *entry.get_mut() = PeerState::Disabled { connections, backoff_until }; - } else { - *entry.get_mut() = PeerState::Incoming { connections, backoff_until }; + }, + + PeerState::Requested | + PeerState::PendingRequest { .. } | + PeerState::Backoff { .. } => { + // This is a serious bug either in this state machine or in libp2p. + error!(target: "sub-libp2p", + "`inject_connection_closed` called for unknown peer {}", + peer_id); + debug_assert!(false); + }, + PeerState::Poisoned => { + error!(target: "sub-libp2p", "State of peer {} is poisoned", peer_id); + debug_assert!(false); + }, } - }, + } + }, + FromSwarm::DialFailure(DialFailure { peer_id, error, .. }) => { + if let DialError::Transport(errors) = error { + for (addr, error) in errors.iter() { + trace!(target: "sub-libp2p", "Libp2p => Reach failure for {:?} through {:?}: {:?}", peer_id, addr, error); + } + } - // Enabled => Enabled | Backoff - // Peers are always backed-off when disconnecting while Enabled. - PeerState::Enabled { mut connections } => { - trace!( - target: "sub-libp2p", - "Libp2p => Disconnected({}, {:?}, {:?}): Enabled.", - peer_id, set_id, *conn - ); + if let Some(peer_id) = peer_id { + trace!(target: "sub-libp2p", "Libp2p => Dial failure for {:?}", peer_id); + + for set_id in (0..self.notif_protocols.len()).map(sc_peerset::SetId::from) { + if let Entry::Occupied(mut entry) = self.peers.entry((peer_id, set_id)) { + match mem::replace(entry.get_mut(), PeerState::Poisoned) { + // The peer is not in our list. + st @ PeerState::Backoff { .. } => { + *entry.into_mut() = st; + }, + + // "Basic" situation: we failed to reach a peer that the peerset + // requested. + st @ PeerState::Requested | + st @ PeerState::PendingRequest { .. } => { + trace!(target: "sub-libp2p", "PSM <= Dropped({}, {:?})", peer_id, set_id); + self.peerset.dropped(set_id, peer_id, DropReason::Unknown); + + let now = Instant::now(); + let ban_duration = match st { + PeerState::PendingRequest { timer_deadline, .. } + if timer_deadline > now => + cmp::max(timer_deadline - now, Duration::from_secs(5)), + _ => Duration::from_secs(5), + }; - debug_assert!(connections.iter().any(|(_, s)| matches!( - s, - ConnectionState::Opening | ConnectionState::Open(_) - ))); - - if let Some(pos) = connections.iter().position(|(c, _)| *c == *conn) { - let (_, state) = connections.remove(pos); - if let ConnectionState::Open(_) = state { - if let Some((replacement_pos, replacement_sink)) = - connections.iter().enumerate().find_map(|(num, (_, s))| match s { - ConnectionState::Open(s) => Some((num, s.clone())), - _ => None, - }) { - if pos <= replacement_pos { - trace!( - target: "sub-libp2p", - "External API <= Sink replaced({}, {:?})", - peer_id, set_id + let delay_id = self.next_delay_id; + self.next_delay_id.0 += 1; + let delay = futures_timer::Delay::new(ban_duration); + let peer_id = peer_id; + self.delays.push( + async move { + delay.await; + (delay_id, peer_id, set_id) + } + .boxed(), ); - let event = NotificationsOut::CustomProtocolReplaced { - peer_id: *peer_id, - set_id, - notifications_sink: replacement_sink, + + *entry.into_mut() = PeerState::Backoff { + timer: delay_id, + timer_deadline: now + ban_duration, }; - self.events - .push_back(NetworkBehaviourAction::GenerateEvent(event)); - } - } else { - trace!( - target: "sub-libp2p", "External API <= Closed({}, {:?})", - peer_id, set_id - ); - let event = NotificationsOut::CustomProtocolClosed { - peer_id: *peer_id, - set_id, - }; - self.events.push_back(NetworkBehaviourAction::GenerateEvent(event)); + }, + + // We can still get dial failures even if we are already connected + // to the peer, as an extra diagnostic for an earlier attempt. + st @ PeerState::Disabled { .. } | + st @ PeerState::Enabled { .. } | + st @ PeerState::DisabledPendingEnable { .. } | + st @ PeerState::Incoming { .. } => { + *entry.into_mut() = st; + }, + + PeerState::Poisoned => { + error!(target: "sub-libp2p", "State of {:?} is poisoned", peer_id); + debug_assert!(false); + }, } } - } else { - error!(target: "sub-libp2p", - "inject_connection_closed: State mismatch in the custom protos handler"); - debug_assert!(false); } - - if connections.is_empty() { - trace!(target: "sub-libp2p", "PSM <= Dropped({}, {:?})", peer_id, set_id); - self.peerset.dropped(set_id, *peer_id, DropReason::Unknown); - let ban_dur = Uniform::new(5, 10).sample(&mut rand::thread_rng()); - - let delay_id = self.next_delay_id; - self.next_delay_id.0 += 1; - let delay = futures_timer::Delay::new(Duration::from_secs(ban_dur)); - let peer_id = *peer_id; - self.delays.push( - async move { - delay.await; - (delay_id, peer_id, set_id) - } - .boxed(), - ); - - *entry.get_mut() = PeerState::Backoff { - timer: delay_id, - timer_deadline: Instant::now() + Duration::from_secs(ban_dur), - }; - } else if !connections.iter().any(|(_, s)| { - matches!(s, ConnectionState::Opening | ConnectionState::Open(_)) - }) { - trace!(target: "sub-libp2p", "PSM <= Dropped({}, {:?})", peer_id, set_id); - self.peerset.dropped(set_id, *peer_id, DropReason::Unknown); - - *entry.get_mut() = PeerState::Disabled { connections, backoff_until: None }; - } else { - *entry.get_mut() = PeerState::Enabled { connections }; - } - }, - - PeerState::Requested | - PeerState::PendingRequest { .. } | - PeerState::Backoff { .. } => { - // This is a serious bug either in this state machine or in libp2p. - error!(target: "sub-libp2p", - "`inject_connection_closed` called for unknown peer {}", - peer_id); - debug_assert!(false); - }, - PeerState::Poisoned => { - error!(target: "sub-libp2p", "State of peer {} is poisoned", peer_id); - debug_assert!(false); - }, - } + } + }, + FromSwarm::ListenerClosed(_) => {}, + FromSwarm::ListenFailure(_) => {}, + FromSwarm::ListenerError(_) => {}, + FromSwarm::ExpiredExternalAddr(_) => {}, + FromSwarm::NewListener(_) => {}, + FromSwarm::ExpiredListenAddr(_) => {}, + FromSwarm::NewExternalAddr(_) => {}, + FromSwarm::AddressChange(_) => {}, + FromSwarm::NewListenAddr(_) => {}, } } - fn inject_dial_failure( + fn on_connection_handler_event( &mut self, - peer_id: Option, - _: Self::ConnectionHandler, - error: &DialError, + peer_id: PeerId, + connection_id: ConnectionId, + event: <::Handler as + ConnectionHandler>::OutEvent, ) { - if let DialError::Transport(errors) = error { - for (addr, error) in errors.iter() { - trace!(target: "sub-libp2p", "Libp2p => Reach failure for {:?} through {:?}: {:?}", peer_id, addr, error); - } - } - - if let Some(peer_id) = peer_id { - trace!(target: "sub-libp2p", "Libp2p => Dial failure for {:?}", peer_id); - - for set_id in (0..self.notif_protocols.len()).map(sc_peerset::SetId::from) { - if let Entry::Occupied(mut entry) = self.peers.entry((peer_id, set_id)) { - match mem::replace(entry.get_mut(), PeerState::Poisoned) { - // The peer is not in our list. - st @ PeerState::Backoff { .. } => { - *entry.into_mut() = st; - }, - - // "Basic" situation: we failed to reach a peer that the peerset requested. - st @ PeerState::Requested | st @ PeerState::PendingRequest { .. } => { - trace!(target: "sub-libp2p", "PSM <= Dropped({}, {:?})", peer_id, set_id); - self.peerset.dropped(set_id, peer_id, DropReason::Unknown); - - let now = Instant::now(); - let ban_duration = match st { - PeerState::PendingRequest { timer_deadline, .. } - if timer_deadline > now => - cmp::max(timer_deadline - now, Duration::from_secs(5)), - _ => Duration::from_secs(5), - }; - - let delay_id = self.next_delay_id; - self.next_delay_id.0 += 1; - let delay = futures_timer::Delay::new(ban_duration); - let peer_id = peer_id; - self.delays.push( - async move { - delay.await; - (delay_id, peer_id, set_id) - } - .boxed(), - ); - - *entry.into_mut() = PeerState::Backoff { - timer: delay_id, - timer_deadline: now + ban_duration, - }; - }, - - // We can still get dial failures even if we are already connected to the - // peer, as an extra diagnostic for an earlier attempt. - st @ PeerState::Disabled { .. } | - st @ PeerState::Enabled { .. } | - st @ PeerState::DisabledPendingEnable { .. } | - st @ PeerState::Incoming { .. } => { - *entry.into_mut() = st; - }, - - PeerState::Poisoned => { - error!(target: "sub-libp2p", "State of {:?} is poisoned", peer_id); - debug_assert!(false); - }, - } - } - } - } - } - - fn inject_event(&mut self, source: PeerId, connection: ConnectionId, event: NotifsHandlerOut) { match event { NotifsHandlerOut::OpenDesiredByRemote { protocol_index } => { let set_id = sc_peerset::SetId::from(protocol_index); trace!(target: "sub-libp2p", "Handler({:?}, {:?}]) => OpenDesiredByRemote({:?})", - source, connection, set_id); + peer_id, connection_id, set_id); - let mut entry = if let Entry::Occupied(entry) = self.peers.entry((source, set_id)) { + let mut entry = if let Entry::Occupied(entry) = self.peers.entry((peer_id, set_id)) + { entry } else { error!( @@ -1484,7 +1522,7 @@ impl NetworkBehaviour for Notifications { .iter() .any(|(_, s)| matches!(s, ConnectionState::OpenDesiredByRemote))); if let Some((_, connec_state)) = - connections.iter_mut().find(|(c, _)| *c == connection) + connections.iter_mut().find(|(c, _)| *c == connection_id) { if let ConnectionState::Closed = *connec_state { *connec_state = ConnectionState::OpenDesiredByRemote; @@ -1517,14 +1555,14 @@ impl NetworkBehaviour for Notifications { ))); if let Some((_, connec_state)) = - connections.iter_mut().find(|(c, _)| *c == connection) + connections.iter_mut().find(|(c, _)| *c == connection_id) { if let ConnectionState::Closed = *connec_state { trace!(target: "sub-libp2p", "Handler({:?}, {:?}) <= Open({:?})", - source, connection, set_id); + peer_id, connection_id, set_id); self.events.push_back(NetworkBehaviourAction::NotifyHandler { - peer_id: source, - handler: NotifyHandler::One(connection), + peer_id, + handler: NotifyHandler::One(connection_id), event: NotifsHandlerIn::Open { protocol_index: set_id.into() }, }); *connec_state = ConnectionState::Opening; @@ -1554,7 +1592,7 @@ impl NetworkBehaviour for Notifications { // Disabled => Disabled | Incoming PeerState::Disabled { mut connections, backoff_until } => { if let Some((_, connec_state)) = - connections.iter_mut().find(|(c, _)| *c == connection) + connections.iter_mut().find(|(c, _)| *c == connection_id) { if let ConnectionState::Closed = *connec_state { *connec_state = ConnectionState::OpenDesiredByRemote; @@ -1563,10 +1601,10 @@ impl NetworkBehaviour for Notifications { self.next_incoming_index.0 += 1; trace!(target: "sub-libp2p", "PSM <= Incoming({}, {:?}).", - source, incoming_id); - self.peerset.incoming(set_id, source, incoming_id); + peer_id, incoming_id); + self.peerset.incoming(set_id, peer_id, incoming_id); self.incoming.push(IncomingPeer { - peer_id: source, + peer_id, set_id, alive: true, incoming_id, @@ -1598,14 +1636,14 @@ impl NetworkBehaviour for Notifications { // DisabledPendingEnable => Enabled | DisabledPendingEnable PeerState::DisabledPendingEnable { mut connections, timer, timer_deadline } => { if let Some((_, connec_state)) = - connections.iter_mut().find(|(c, _)| *c == connection) + connections.iter_mut().find(|(c, _)| *c == connection_id) { if let ConnectionState::Closed = *connec_state { trace!(target: "sub-libp2p", "Handler({:?}, {:?}) <= Open({:?})", - source, connection, set_id); + peer_id, connection_id, set_id); self.events.push_back(NetworkBehaviourAction::NotifyHandler { - peer_id: source, - handler: NotifyHandler::One(connection), + peer_id, + handler: NotifyHandler::One(connection_id), event: NotifsHandlerIn::Open { protocol_index: set_id.into() }, }); *connec_state = ConnectionState::Opening; @@ -1649,9 +1687,10 @@ impl NetworkBehaviour for Notifications { trace!(target: "sub-libp2p", "Handler({}, {:?}) => CloseDesired({:?})", - source, connection, set_id); + peer_id, connection_id, set_id); - let mut entry = if let Entry::Occupied(entry) = self.peers.entry((source, set_id)) { + let mut entry = if let Entry::Occupied(entry) = self.peers.entry((peer_id, set_id)) + { entry } else { error!(target: "sub-libp2p", "CloseDesired: State mismatch in the custom protos handler"); @@ -1668,7 +1707,7 @@ impl NetworkBehaviour for Notifications { ))); let pos = if let Some(pos) = - connections.iter().position(|(c, _)| *c == connection) + connections.iter().position(|(c, _)| *c == connection_id) { pos } else { @@ -1686,10 +1725,10 @@ impl NetworkBehaviour for Notifications { debug_assert!(matches!(connections[pos].1, ConnectionState::Open(_))); connections[pos].1 = ConnectionState::Closing; - trace!(target: "sub-libp2p", "Handler({}, {:?}) <= Close({:?})", source, connection, set_id); + trace!(target: "sub-libp2p", "Handler({}, {:?}) <= Close({:?})", peer_id, connection_id, set_id); self.events.push_back(NetworkBehaviourAction::NotifyHandler { - peer_id: source, - handler: NotifyHandler::One(connection), + peer_id, + handler: NotifyHandler::One(connection_id), event: NotifsHandlerIn::Close { protocol_index: set_id.into() }, }); @@ -1699,9 +1738,9 @@ impl NetworkBehaviour for Notifications { _ => None, }) { if pos <= replacement_pos { - trace!(target: "sub-libp2p", "External API <= Sink replaced({:?})", source); + trace!(target: "sub-libp2p", "External API <= Sink replaced({:?})", peer_id); let event = NotificationsOut::CustomProtocolReplaced { - peer_id: source, + peer_id, set_id, notifications_sink: replacement_sink, }; @@ -1715,17 +1754,16 @@ impl NetworkBehaviour for Notifications { .iter() .any(|(_, s)| matches!(s, ConnectionState::Opening)) { - trace!(target: "sub-libp2p", "PSM <= Dropped({}, {:?})", source, set_id); - self.peerset.dropped(set_id, source, DropReason::Refused); + trace!(target: "sub-libp2p", "PSM <= Dropped({}, {:?})", peer_id, set_id); + self.peerset.dropped(set_id, peer_id, DropReason::Refused); *entry.into_mut() = PeerState::Disabled { connections, backoff_until: None }; } else { *entry.into_mut() = PeerState::Enabled { connections }; } - trace!(target: "sub-libp2p", "External API <= Closed({}, {:?})", source, set_id); - let event = - NotificationsOut::CustomProtocolClosed { peer_id: source, set_id }; + trace!(target: "sub-libp2p", "External API <= Closed({}, {:?})", peer_id, set_id); + let event = NotificationsOut::CustomProtocolClosed { peer_id, set_id }; self.events.push_back(NetworkBehaviourAction::GenerateEvent(event)); } }, @@ -1749,16 +1787,16 @@ impl NetworkBehaviour for Notifications { trace!(target: "sub-libp2p", "Handler({}, {:?}) => CloseResult({:?})", - source, connection, set_id); + peer_id, connection_id, set_id); - match self.peers.get_mut(&(source, set_id)) { + match self.peers.get_mut(&(peer_id, set_id)) { // Move the connection from `Closing` to `Closed`. Some(PeerState::Incoming { connections, .. }) | Some(PeerState::DisabledPendingEnable { connections, .. }) | Some(PeerState::Disabled { connections, .. }) | Some(PeerState::Enabled { connections, .. }) => { if let Some((_, connec_state)) = connections.iter_mut().find(|(c, s)| { - *c == connection && matches!(s, ConnectionState::Closing) + *c == connection_id && matches!(s, ConnectionState::Closing) }) { *connec_state = ConnectionState::Closed; } else { @@ -1787,9 +1825,9 @@ impl NetworkBehaviour for Notifications { let set_id = sc_peerset::SetId::from(protocol_index); trace!(target: "sub-libp2p", "Handler({}, {:?}) => OpenResultOk({:?})", - source, connection, set_id); + peer_id, connection_id, set_id); - match self.peers.get_mut(&(source, set_id)) { + match self.peers.get_mut(&(peer_id, set_id)) { Some(PeerState::Enabled { connections, .. }) => { debug_assert!(connections.iter().any(|(_, s)| matches!( s, @@ -1799,12 +1837,12 @@ impl NetworkBehaviour for Notifications { connections.iter().any(|(_, s)| matches!(s, ConnectionState::Open(_))); if let Some((_, connec_state)) = connections.iter_mut().find(|(c, s)| { - *c == connection && matches!(s, ConnectionState::Opening) + *c == connection_id && matches!(s, ConnectionState::Opening) }) { if !any_open { - trace!(target: "sub-libp2p", "External API <= Open({}, {:?})", source, set_id); + trace!(target: "sub-libp2p", "External API <= Open({}, {:?})", peer_id, set_id); let event = NotificationsOut::CustomProtocolOpen { - peer_id: source, + peer_id, set_id, negotiated_fallback, received_handshake, @@ -1815,7 +1853,8 @@ impl NetworkBehaviour for Notifications { *connec_state = ConnectionState::Open(notifications_sink); } else if let Some((_, connec_state)) = connections.iter_mut().find(|(c, s)| { - *c == connection && matches!(s, ConnectionState::OpeningThenClosing) + *c == connection_id && + matches!(s, ConnectionState::OpeningThenClosing) }) { *connec_state = ConnectionState::Closing; } else { @@ -1829,7 +1868,7 @@ impl NetworkBehaviour for Notifications { Some(PeerState::DisabledPendingEnable { connections, .. }) | Some(PeerState::Disabled { connections, .. }) => { if let Some((_, connec_state)) = connections.iter_mut().find(|(c, s)| { - *c == connection && matches!(s, ConnectionState::OpeningThenClosing) + *c == connection_id && matches!(s, ConnectionState::OpeningThenClosing) }) { *connec_state = ConnectionState::Closing; } else { @@ -1852,9 +1891,10 @@ impl NetworkBehaviour for Notifications { let set_id = sc_peerset::SetId::from(protocol_index); trace!(target: "sub-libp2p", "Handler({:?}, {:?}) => OpenResultErr({:?})", - source, connection, set_id); + peer_id, connection_id, set_id); - let mut entry = if let Entry::Occupied(entry) = self.peers.entry((source, set_id)) { + let mut entry = if let Entry::Occupied(entry) = self.peers.entry((peer_id, set_id)) + { entry } else { error!(target: "sub-libp2p", "OpenResultErr: State mismatch in the custom protos handler"); @@ -1870,12 +1910,13 @@ impl NetworkBehaviour for Notifications { ))); if let Some((_, connec_state)) = connections.iter_mut().find(|(c, s)| { - *c == connection && matches!(s, ConnectionState::Opening) + *c == connection_id && matches!(s, ConnectionState::Opening) }) { *connec_state = ConnectionState::Closed; } else if let Some((_, connec_state)) = connections.iter_mut().find(|(c, s)| { - *c == connection && matches!(s, ConnectionState::OpeningThenClosing) + *c == connection_id && + matches!(s, ConnectionState::OpeningThenClosing) }) { *connec_state = ConnectionState::Closing; } else { @@ -1887,8 +1928,8 @@ impl NetworkBehaviour for Notifications { if !connections.iter().any(|(_, s)| { matches!(s, ConnectionState::Opening | ConnectionState::Open(_)) }) { - trace!(target: "sub-libp2p", "PSM <= Dropped({:?})", source); - self.peerset.dropped(set_id, source, DropReason::Refused); + trace!(target: "sub-libp2p", "PSM <= Dropped({:?})", peer_id); + self.peerset.dropped(set_id, peer_id, DropReason::Refused); let ban_dur = Uniform::new(5, 10).sample(&mut rand::thread_rng()); *entry.into_mut() = PeerState::Disabled { @@ -1908,7 +1949,7 @@ impl NetworkBehaviour for Notifications { PeerState::DisabledPendingEnable { connections, .. } => { if let Some((_, connec_state)) = connections.iter_mut().find(|(c, s)| { - *c == connection && + *c == connection_id && matches!(s, ConnectionState::OpeningThenClosing) }) { *connec_state = ConnectionState::Closing; @@ -1937,30 +1978,30 @@ impl NetworkBehaviour for Notifications { NotifsHandlerOut::Notification { protocol_index, message } => { let set_id = sc_peerset::SetId::from(protocol_index); - if self.is_open(&source, set_id) { + if self.is_open(&peer_id, set_id) { trace!( target: "sub-libp2p", "Handler({:?}) => Notification({}, {:?}, {} bytes)", - connection, - source, + connection_id, + peer_id, set_id, message.len() ); trace!( target: "sub-libp2p", "External API <= Message({}, {:?})", - source, + peer_id, set_id, ); - let event = NotificationsOut::Notification { peer_id: source, set_id, message }; + let event = NotificationsOut::Notification { peer_id, set_id, message }; self.events.push_back(NetworkBehaviourAction::GenerateEvent(event)); } else { trace!( target: "sub-libp2p", "Handler({:?}) => Post-close notification({}, {:?}, {} bytes)", - connection, - source, + connection_id, + peer_id, set_id, message.len() ); diff --git a/client/network/src/protocol/notifications/tests.rs b/client/network/src/protocol/notifications/tests.rs index fa79366d20..6e85869748 100644 --- a/client/network/src/protocol/notifications/tests.rs +++ b/client/network/src/protocol/notifications/tests.rs @@ -22,24 +22,28 @@ use crate::protocol::notifications::{Notifications, NotificationsOut, ProtocolCo use futures::prelude::*; use libp2p::{ - core::{ - connection::ConnectionId, - transport::{ListenerId, MemoryTransport}, - upgrade, ConnectedPoint, - }, + core::{connection::ConnectionId, transport::MemoryTransport, upgrade}, identity, noise, swarm::{ - ConnectionHandler, DialError, IntoConnectionHandler, NetworkBehaviour, + behaviour::FromSwarm, ConnectionHandler, Executor, IntoConnectionHandler, NetworkBehaviour, NetworkBehaviourAction, PollParameters, Swarm, SwarmEvent, }, yamux, Multiaddr, PeerId, Transport, }; use std::{ - error, io, iter, + iter, + pin::Pin, task::{Context, Poll}, time::Duration, }; +struct TokioExecutor(tokio::runtime::Runtime); +impl Executor for TokioExecutor { + fn exec(&self, f: Pin + Send>>) { + let _ = self.0.spawn(f); + } +} + /// Builds two nodes that have each other as bootstrap nodes. /// This is to be used only for testing, and a panic will happen if something goes wrong. fn build_nodes() -> (Swarm, Swarm) { @@ -100,7 +104,13 @@ fn build_nodes() -> (Swarm, Swarm) { .collect(), }; - let mut swarm = Swarm::new(transport, behaviour, keypairs[index].public().to_peer_id()); + let runtime = tokio::runtime::Runtime::new().unwrap(); + let mut swarm = Swarm::with_executor( + transport, + behaviour, + keypairs[index].public().to_peer_id(), + TokioExecutor(runtime), + ); swarm.listen_on(addrs[index].clone()).unwrap(); out.push(swarm); } @@ -150,42 +160,18 @@ impl NetworkBehaviour for CustomProtoWithAddr { list } - fn inject_connection_established( - &mut self, - peer_id: &PeerId, - conn: &ConnectionId, - endpoint: &ConnectedPoint, - failed_addresses: Option<&Vec>, - other_established: usize, - ) { - self.inner.inject_connection_established( - peer_id, - conn, - endpoint, - failed_addresses, - other_established, - ) - } - - fn inject_connection_closed( - &mut self, - peer_id: &PeerId, - conn: &ConnectionId, - endpoint: &ConnectedPoint, - handler: ::Handler, - remaining_established: usize, - ) { - self.inner - .inject_connection_closed(peer_id, conn, endpoint, handler, remaining_established) + fn on_swarm_event(&mut self, event: FromSwarm) { + self.inner.on_swarm_event(event); } - fn inject_event( + fn on_connection_handler_event( &mut self, peer_id: PeerId, - connection: ConnectionId, - event: <::Handler as ConnectionHandler>::OutEvent, + connection_id: ConnectionId, + event: <::Handler as + ConnectionHandler>::OutEvent, ) { - self.inner.inject_event(peer_id, connection, event) + self.inner.on_connection_handler_event(peer_id, connection_id, event); } fn poll( @@ -195,43 +181,6 @@ impl NetworkBehaviour for CustomProtoWithAddr { ) -> Poll> { self.inner.poll(cx, params) } - - fn inject_dial_failure( - &mut self, - peer_id: Option, - handler: Self::ConnectionHandler, - error: &DialError, - ) { - self.inner.inject_dial_failure(peer_id, handler, error) - } - - fn inject_new_listener(&mut self, id: ListenerId) { - self.inner.inject_new_listener(id) - } - - fn inject_new_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { - self.inner.inject_new_listen_addr(id, addr) - } - - fn inject_expired_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { - self.inner.inject_expired_listen_addr(id, addr) - } - - fn inject_new_external_addr(&mut self, addr: &Multiaddr) { - self.inner.inject_new_external_addr(addr) - } - - fn inject_expired_external_addr(&mut self, addr: &Multiaddr) { - self.inner.inject_expired_external_addr(addr) - } - - fn inject_listener_error(&mut self, id: ListenerId, err: &(dyn error::Error + 'static)) { - self.inner.inject_listener_error(id, err); - } - - fn inject_listener_closed(&mut self, id: ListenerId, reason: Result<(), &io::Error>) { - self.inner.inject_listener_closed(id, reason); - } } #[test] diff --git a/client/network/src/request_responses.rs b/client/network/src/request_responses.rs index d49cbd8051..45f1030400 100644 --- a/client/network/src/request_responses.rs +++ b/client/network/src/request_responses.rs @@ -40,14 +40,16 @@ use futures::{ prelude::*, }; use libp2p::{ - core::{connection::ConnectionId, transport::ListenerId, ConnectedPoint, Multiaddr, PeerId}, + core::{connection::ConnectionId, Multiaddr, PeerId}, request_response::{ handler::RequestResponseHandler, ProtocolSupport, RequestResponse, RequestResponseCodec, RequestResponseConfig, RequestResponseEvent, RequestResponseMessage, ResponseChannel, }, swarm::{ - handler::multi::MultiHandler, ConnectionHandler, IntoConnectionHandler, NetworkBehaviour, - NetworkBehaviourAction, PollParameters, + behaviour::{ConnectionClosed, DialFailure, FromSwarm, ListenFailure}, + handler::multi::MultiHandler, + ConnectionHandler, IntoConnectionHandler, NetworkBehaviour, NetworkBehaviourAction, + PollParameters, }, }; use sc_network_common::{ @@ -312,120 +314,119 @@ impl NetworkBehaviour for RequestResponsesBehaviour { Vec::new() } - fn inject_connection_established( - &mut self, - peer_id: &PeerId, - conn: &ConnectionId, - endpoint: &ConnectedPoint, - failed_addresses: Option<&Vec>, - other_established: usize, - ) { - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::inject_connection_established( - p, + fn on_swarm_event(&mut self, event: FromSwarm) { + match event { + FromSwarm::ConnectionEstablished(e) => + for (p, _) in self.protocols.values_mut() { + NetworkBehaviour::on_swarm_event(p, FromSwarm::ConnectionEstablished(e)); + }, + FromSwarm::ConnectionClosed(ConnectionClosed { peer_id, - conn, + connection_id, endpoint, - failed_addresses, - other_established, - ) - } - } - - fn inject_connection_closed( - &mut self, - peer_id: &PeerId, - conn: &ConnectionId, - endpoint: &ConnectedPoint, - handler: ::Handler, - remaining_established: usize, - ) { - for (p_name, event) in handler.into_iter() { - if let Some((proto, _)) = self.protocols.get_mut(p_name.as_str()) { - proto.inject_connection_closed( - peer_id, - conn, - endpoint, - event, - remaining_established, - ) - } else { - log::error!( - target: "sub-libp2p", - "inject_connection_closed: no request-response instance registered for protocol {:?}", - p_name, - ) - } + handler, + remaining_established, + }) => + for (p_name, p_handler) in handler.into_iter() { + if let Some((proto, _)) = self.protocols.get_mut(p_name.as_str()) { + proto.on_swarm_event(FromSwarm::ConnectionClosed(ConnectionClosed { + peer_id, + connection_id, + endpoint, + handler: p_handler, + remaining_established, + })); + } else { + log::error!( + target: "sub-libp2p", + "on_swarm_event/connection_closed: no request-response instance registered for protocol {:?}", + p_name, + ) + } + }, + FromSwarm::DialFailure(DialFailure { peer_id, error, handler }) => + for (p_name, p_handler) in handler.into_iter() { + if let Some((proto, _)) = self.protocols.get_mut(p_name.as_str()) { + proto.on_swarm_event(FromSwarm::DialFailure(DialFailure { + peer_id, + handler: p_handler, + error, + })); + } else { + log::error!( + target: "sub-libp2p", + "on_swarm_event/dial_failure: no request-response instance registered for protocol {:?}", + p_name, + ) + } + }, + FromSwarm::ListenerClosed(e) => + for (p, _) in self.protocols.values_mut() { + NetworkBehaviour::on_swarm_event(p, FromSwarm::ListenerClosed(e)); + }, + FromSwarm::ListenFailure(ListenFailure { local_addr, send_back_addr, handler }) => + for (p_name, p_handler) in handler.into_iter() { + if let Some((proto, _)) = self.protocols.get_mut(p_name.as_str()) { + proto.on_swarm_event(FromSwarm::ListenFailure(ListenFailure { + local_addr, + send_back_addr, + handler: p_handler, + })); + } else { + log::error!( + target: "sub-libp2p", + "on_swarm_event/listen_failure: no request-response instance registered for protocol {:?}", + p_name, + ) + } + }, + FromSwarm::ListenerError(e) => + for (p, _) in self.protocols.values_mut() { + NetworkBehaviour::on_swarm_event(p, FromSwarm::ListenerError(e)); + }, + FromSwarm::ExpiredExternalAddr(e) => + for (p, _) in self.protocols.values_mut() { + NetworkBehaviour::on_swarm_event(p, FromSwarm::ExpiredExternalAddr(e)); + }, + FromSwarm::NewListener(e) => + for (p, _) in self.protocols.values_mut() { + NetworkBehaviour::on_swarm_event(p, FromSwarm::NewListener(e)); + }, + FromSwarm::ExpiredListenAddr(e) => + for (p, _) in self.protocols.values_mut() { + NetworkBehaviour::on_swarm_event(p, FromSwarm::ExpiredListenAddr(e)); + }, + FromSwarm::NewExternalAddr(e) => + for (p, _) in self.protocols.values_mut() { + NetworkBehaviour::on_swarm_event(p, FromSwarm::NewExternalAddr(e)); + }, + FromSwarm::AddressChange(e) => + for (p, _) in self.protocols.values_mut() { + NetworkBehaviour::on_swarm_event(p, FromSwarm::AddressChange(e)); + }, + FromSwarm::NewListenAddr(e) => + for (p, _) in self.protocols.values_mut() { + NetworkBehaviour::on_swarm_event(p, FromSwarm::NewListenAddr(e)); + }, } } - fn inject_event( + fn on_connection_handler_event( &mut self, peer_id: PeerId, - connection: ConnectionId, - (p_name, event): ::OutEvent, + connection_id: ConnectionId, + (p_name, event): <::Handler as + ConnectionHandler>::OutEvent, ) { if let Some((proto, _)) = self.protocols.get_mut(&*p_name) { - return proto.inject_event(peer_id, connection, event) + return proto.on_connection_handler_event(peer_id, connection_id, event) } - log::warn!(target: "sub-libp2p", - "inject_node_event: no request-response instance registered for protocol {:?}", - p_name) - } - - fn inject_new_external_addr(&mut self, addr: &Multiaddr) { - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::inject_new_external_addr(p, addr) - } - } - - fn inject_expired_external_addr(&mut self, addr: &Multiaddr) { - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::inject_expired_external_addr(p, addr) - } - } - - fn inject_expired_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::inject_expired_listen_addr(p, id, addr) - } - } - - fn inject_dial_failure( - &mut self, - peer_id: Option, - _: Self::ConnectionHandler, - error: &libp2p::swarm::DialError, - ) { - for (p, _) in self.protocols.values_mut() { - let handler = p.new_handler(); - NetworkBehaviour::inject_dial_failure(p, peer_id, handler, error) - } - } - - fn inject_new_listener(&mut self, id: ListenerId) { - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::inject_new_listener(p, id) - } - } - - fn inject_new_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::inject_new_listen_addr(p, id, addr) - } - } - - fn inject_listener_error(&mut self, id: ListenerId, err: &(dyn std::error::Error + 'static)) { - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::inject_listener_error(p, id, err) - } - } - - fn inject_listener_closed(&mut self, id: ListenerId, reason: Result<(), &io::Error>) { - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::inject_listener_closed(p, id, reason) - } + log::warn!( + target: "sub-libp2p", + "on_connection_handler_event: no request-response instance registered for protocol {:?}", + p_name + ); } fn poll( @@ -919,12 +920,19 @@ mod tests { }, identity::Keypair, noise, - swarm::{Swarm, SwarmEvent}, + swarm::{Executor, Swarm, SwarmEvent}, Multiaddr, }; use sc_peerset::{Peerset, PeersetConfig, SetConfig}; use std::{iter, time::Duration}; + struct TokioExecutor(tokio::runtime::Runtime); + impl Executor for TokioExecutor { + fn exec(&self, f: Pin + Send>>) { + let _ = self.0.spawn(f); + } + } + fn build_swarm( list: impl Iterator, ) -> (Swarm, Multiaddr, Peerset) { @@ -953,7 +961,13 @@ mod tests { let behaviour = RequestResponsesBehaviour::new(list, handle).unwrap(); - let mut swarm = Swarm::new(transport, behaviour, keypair.public().to_peer_id()); + let runtime = tokio::runtime::Runtime::new().unwrap(); + let mut swarm = Swarm::with_executor( + transport, + behaviour, + keypair.public().to_peer_id(), + TokioExecutor(runtime), + ); let listen_addr: Multiaddr = format!("/memory/{}", rand::random::()).parse().unwrap(); swarm.listen_on(listen_addr.clone()).unwrap(); diff --git a/client/network/src/service.rs b/client/network/src/service.rs index 66de263a19..f943a03f50 100644 --- a/client/network/src/service.rs +++ b/client/network/src/service.rs @@ -40,13 +40,13 @@ use crate::{ use futures::{channel::oneshot, prelude::*}; use libp2p::{ - core::{either::EitherError, upgrade, ConnectedPoint, Executor}, + core::{either::EitherError, upgrade, ConnectedPoint}, identify::Info as IdentifyInfo, kad::record::Key as KademliaKey, multiaddr, ping::Failure as PingFailure, swarm::{ - AddressScore, ConnectionError, ConnectionLimits, DialError, NetworkBehaviour, + AddressScore, ConnectionError, ConnectionLimits, DialError, Executor, NetworkBehaviour, PendingConnectionError, Swarm, SwarmBuilder, SwarmEvent, }, Multiaddr, PeerId, @@ -370,7 +370,21 @@ where } }; - let mut builder = SwarmBuilder::new(transport, behaviour, local_peer_id) + let builder = { + struct SpawnImpl(F); + impl + Send>>)> Executor for SpawnImpl { + fn exec(&self, f: Pin + Send>>) { + (self.0)(f) + } + } + SwarmBuilder::with_executor( + transport, + behaviour, + local_peer_id, + SpawnImpl(params.executor), + ) + }; + let builder = builder .connection_limits( ConnectionLimits::default() .with_max_established_per_peer(Some(crate::MAX_CONNECTIONS_PER_PEER as u32)) @@ -383,14 +397,6 @@ where .connection_event_buffer_size(1024) .max_negotiating_inbound_streams(2048); - struct SpawnImpl(F); - impl + Send>>)> Executor for SpawnImpl { - fn exec(&self, f: Pin + Send>>) { - (self.0)(f) - } - } - builder = builder.executor(Box::new(SpawnImpl(params.executor))); - (builder.build(), bandwidth) }; diff --git a/client/network/src/transport.rs b/client/network/src/transport.rs index b0d5f0235b..6c792a0c38 100644 --- a/client/network/src/transport.rs +++ b/client/network/src/transport.rs @@ -54,17 +54,17 @@ pub fn build_transport( ) -> (Boxed<(PeerId, StreamMuxerBox)>, Arc) { // Build the base layer of the transport. let transport = if !memory_only { - let tcp_config = tcp::GenTcpConfig::new().nodelay(true); - let desktop_trans = tcp::TokioTcpTransport::new(tcp_config.clone()); + let tcp_config = tcp::Config::new().nodelay(true); + let desktop_trans = tcp::tokio::Transport::new(tcp_config.clone()); let desktop_trans = websocket::WsConfig::new(desktop_trans) - .or_transport(tcp::TokioTcpTransport::new(tcp_config.clone())); + .or_transport(tcp::tokio::Transport::new(tcp_config.clone())); let dns_init = dns::TokioDnsConfig::system(desktop_trans); EitherTransport::Left(if let Ok(dns) = dns_init { EitherTransport::Left(dns) } else { - let desktop_trans = tcp::TokioTcpTransport::new(tcp_config.clone()); + let desktop_trans = tcp::tokio::Transport::new(tcp_config.clone()); let desktop_trans = websocket::WsConfig::new(desktop_trans) - .or_transport(tcp::TokioTcpTransport::new(tcp_config)); + .or_transport(tcp::tokio::Transport::new(tcp_config)); EitherTransport::Right(desktop_trans.map_err(dns::DnsErr::Transport)) }) } else { diff --git a/client/network/sync/Cargo.toml b/client/network/sync/Cargo.toml index e29d804716..d2cbeb5ec1 100644 --- a/client/network/sync/Cargo.toml +++ b/client/network/sync/Cargo.toml @@ -20,7 +20,7 @@ array-bytes = "4.1" async-trait = "0.1.58" codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } futures = "0.3.21" -libp2p = "0.49.0" +libp2p = "0.50.0" log = "0.4.17" lru = "0.8.1" mockall = "0.11.2" diff --git a/client/network/test/Cargo.toml b/client/network/test/Cargo.toml index 25e4df00e0..8368fa2787 100644 --- a/client/network/test/Cargo.toml +++ b/client/network/test/Cargo.toml @@ -17,7 +17,7 @@ tokio = "1.22.0" async-trait = "0.1.57" futures = "0.3.21" futures-timer = "3.0.1" -libp2p = { version = "0.49.0", default-features = false } +libp2p = "0.50.0" log = "0.4.17" parking_lot = "0.12.1" rand = "0.8.5" diff --git a/client/network/transactions/Cargo.toml b/client/network/transactions/Cargo.toml index 1255dededc..df13cdcb82 100644 --- a/client/network/transactions/Cargo.toml +++ b/client/network/transactions/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] array-bytes = "4.1" codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } futures = "0.3.21" -libp2p = "0.49.0" +libp2p = "0.50.0" log = "0.4.17" pin-project = "1.0.12" prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../../utils/prometheus" } diff --git a/client/offchain/Cargo.toml b/client/offchain/Cargo.toml index baeec0b6d7..09df9187fb 100644 --- a/client/offchain/Cargo.toml +++ b/client/offchain/Cargo.toml @@ -21,7 +21,7 @@ futures = "0.3.21" futures-timer = "3.0.2" hyper = { version = "0.14.16", features = ["stream", "http2"] } hyper-rustls = { version = "0.23.0", features = ["http2"] } -libp2p = { version = "0.49.0", default-features = false } +libp2p = "0.50.0" num_cpus = "1.13" once_cell = "1.8" parking_lot = "0.12.1" diff --git a/client/peerset/Cargo.toml b/client/peerset/Cargo.toml index 8625fe40c2..a09508c831 100644 --- a/client/peerset/Cargo.toml +++ b/client/peerset/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] futures = "0.3.21" -libp2p = { version = "0.49.0", default-features = false } +libp2p = "0.50.0" log = "0.4.17" serde_json = "1.0.85" wasm-timer = "0.2" diff --git a/client/telemetry/Cargo.toml b/client/telemetry/Cargo.toml index 6ef69d1d97..4d09a28370 100644 --- a/client/telemetry/Cargo.toml +++ b/client/telemetry/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] chrono = "0.4.19" futures = "0.3.21" -libp2p = { version = "0.49.0", default-features = false, features = ["dns", "tcp", "tokio", "wasm-ext", "websocket"] } +libp2p = { version = "0.50.0", features = ["dns", "tcp", "tokio", "wasm-ext", "websocket"] } log = "0.4.17" parking_lot = "0.12.1" pin-project = "1.0.12" diff --git a/client/telemetry/src/transport.rs b/client/telemetry/src/transport.rs index d8bd138c5a..5e940bbe54 100644 --- a/client/telemetry/src/transport.rs +++ b/client/telemetry/src/transport.rs @@ -30,7 +30,7 @@ const CONNECT_TIMEOUT: Duration = Duration::from_secs(20); pub(crate) fn initialize_transport() -> Result { let transport = { - let tcp_transport = libp2p::tcp::TokioTcpTransport::new(libp2p::tcp::GenTcpConfig::new()); + let tcp_transport = libp2p::tcp::tokio::Transport::new(libp2p::tcp::Config::new()); let inner = libp2p::dns::TokioDnsConfig::system(tcp_transport)?; libp2p::websocket::framed::WsConfig::new(inner).and_then(|connec, _| { let connec = connec diff --git a/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr b/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr index be31b39c11..c9de991135 100644 --- a/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr +++ b/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr @@ -26,5 +26,5 @@ error[E0277]: the trait bound `Vec: MaxEncodedLen` is not satisfied (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) - and 79 others + and $N others = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageMyStorage, Vec>` to implement `StorageInfoTrait` diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr index 364eb5e6d5..bc6d98b8da 100644 --- a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr @@ -28,7 +28,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 281 others + and $N others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` @@ -48,7 +48,7 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied Rc Vec bytes::bytes::Bytes - and 3 others + and $N others = note: required for `Bar` to implement `Encode` = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` @@ -69,7 +69,7 @@ error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied (A, B, C, D) (A, B, C, D, E) (A, B, C, D, E, F) - and 163 others + and $N others = note: required for `Bar` to implement `StaticTypeInfo` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` @@ -103,7 +103,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 281 others + and $N others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` @@ -123,7 +123,7 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied Rc Vec bytes::bytes::Bytes - and 3 others + and $N others = note: required for `Bar` to implement `Encode` = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr index 371e90323d..1c010d662d 100644 --- a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr @@ -28,7 +28,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 281 others + and $N others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` @@ -48,7 +48,7 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied Rc Vec bytes::bytes::Bytes - and 3 others + and $N others = note: required for `Bar` to implement `Encode` = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` @@ -69,7 +69,7 @@ error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied (A, B, C, D) (A, B, C, D, E) (A, B, C, D, E, F) - and 163 others + and $N others = note: required for `Bar` to implement `StaticTypeInfo` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` @@ -103,7 +103,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 281 others + and $N others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` @@ -123,7 +123,7 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied Rc Vec bytes::bytes::Bytes - and 3 others + and $N others = note: required for `Bar` to implement `Encode` = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` diff --git a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr index b5443c6f32..c17f9eaa03 100644 --- a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr +++ b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr @@ -13,5 +13,5 @@ error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) - and 79 others + and $N others = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageInfoTrait` diff --git a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr index afc7aaa876..c34c796fe5 100644 --- a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr +++ b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr @@ -13,6 +13,6 @@ error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) - and 79 others + and $N others = note: required for `Key` to implement `KeyGeneratorMaxEncodedLen` = note: required for `frame_support::pallet_prelude::StorageNMap<_GeneratedPrefixForStorageFoo, Key, u32>` to implement `StorageInfoTrait` From fb22096d2ec6bf38e67ce811ad2c31415237a9a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= <123550+andresilva@users.noreply.github.com> Date: Thu, 5 Jan 2023 12:33:08 +0000 Subject: [PATCH 103/127] grandpa: remove deprecated afg log target (#13064) * grandpa: remove deprecated afg log target * grandpa: define log targets in primitives --- client/finality-grandpa/src/lib.rs | 6 +++--- client/tracing/src/logging/mod.rs | 8 ++++---- frame/grandpa/src/lib.rs | 4 +--- primitives/finality-grandpa/Cargo.toml | 4 ++-- primitives/finality-grandpa/src/lib.rs | 11 +++++++---- 5 files changed, 17 insertions(+), 16 deletions(-) diff --git a/client/finality-grandpa/src/lib.rs b/client/finality-grandpa/src/lib.rs index 1597e60bd6..8758efef60 100644 --- a/client/finality-grandpa/src/lib.rs +++ b/client/finality-grandpa/src/lib.rs @@ -76,6 +76,9 @@ use sp_application_crypto::AppKey; use sp_blockchain::{Error as ClientError, HeaderBackend, HeaderMetadata, Result as ClientResult}; use sp_consensus::SelectChain; use sp_core::crypto::ByteArray; +use sp_finality_grandpa::{ + AuthorityList, AuthoritySignature, SetId, CLIENT_LOG_TARGET as LOG_TARGET, +}; use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; use sp_runtime::{ generic::BlockId, @@ -93,8 +96,6 @@ use std::{ time::Duration, }; -const LOG_TARGET: &str = "grandpa"; - // utility logging macro that takes as first argument a conditional to // decide whether to log under debug or info level (useful to restrict // logging under initial sync). @@ -142,7 +143,6 @@ pub use voting_rule::{ use aux_schema::PersistentData; use communication::{Network as NetworkT, NetworkBridge}; use environment::{Environment, VoterSetState}; -use sp_finality_grandpa::{AuthorityList, AuthoritySignature, SetId}; use until_imported::UntilGlobalMessageBlocksImported; // Re-export these two because it's just so damn convenient. diff --git a/client/tracing/src/logging/mod.rs b/client/tracing/src/logging/mod.rs index 978e24df68..77307ad999 100644 --- a/client/tracing/src/logging/mod.rs +++ b/client/tracing/src/logging/mod.rs @@ -377,7 +377,7 @@ mod tests { fn test_logger_filters() { run_test_in_another_process("test_logger_filters", || { let test_directives = - "afg=debug,sync=trace,client=warn,telemetry,something-with-dash=error"; + "grandpa=debug,sync=trace,client=warn,telemetry,something-with-dash=error"; init_logger(&test_directives); tracing::dispatcher::get_default(|dispatcher| { @@ -402,9 +402,9 @@ mod tests { dispatcher.enabled(&metadata) }; - assert!(test_filter("afg", Level::INFO)); - assert!(test_filter("afg", Level::DEBUG)); - assert!(!test_filter("afg", Level::TRACE)); + assert!(test_filter("grandpa", Level::INFO)); + assert!(test_filter("grandpa", Level::DEBUG)); + assert!(!test_filter("grandpa", Level::TRACE)); assert!(test_filter("sync", Level::TRACE)); assert!(test_filter("client", Level::WARN)); diff --git a/frame/grandpa/src/lib.rs b/frame/grandpa/src/lib.rs index 716cf54865..aa09b445c6 100644 --- a/frame/grandpa/src/lib.rs +++ b/frame/grandpa/src/lib.rs @@ -37,7 +37,7 @@ use codec::{self as codec, Decode, Encode, MaxEncodedLen}; pub use fg_primitives::{AuthorityId, AuthorityList, AuthorityWeight, VersionedAuthorityList}; use fg_primitives::{ ConsensusLog, EquivocationProof, ScheduledChange, SetId, GRANDPA_AUTHORITIES_KEY, - GRANDPA_ENGINE_ID, + GRANDPA_ENGINE_ID, RUNTIME_LOG_TARGET as LOG_TARGET, }; use frame_support::{ dispatch::{DispatchResultWithPostInfo, Pays}, @@ -70,8 +70,6 @@ pub use equivocation::{ pub use pallet::*; -const LOG_TARGET: &str = "runtime::grandpa"; - #[frame_support::pallet] pub mod pallet { use super::*; diff --git a/primitives/finality-grandpa/Cargo.toml b/primitives/finality-grandpa/Cargo.toml index 1c8011ff76..de8de99eed 100644 --- a/primitives/finality-grandpa/Cargo.toml +++ b/primitives/finality-grandpa/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } grandpa = { package = "finality-grandpa", version = "0.16.0", default-features = false, features = ["derive-codec"] } -log = { version = "0.4.17", optional = true } +log = { version = "0.4.17", default-features = false } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", features = ["derive"], optional = true } sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } @@ -31,7 +31,7 @@ default = ["std"] std = [ "codec/std", "grandpa/std", - "log", + "log/std", "scale-info/std", "serde", "sp-api/std", diff --git a/primitives/finality-grandpa/src/lib.rs b/primitives/finality-grandpa/src/lib.rs index f92fee4f12..7b3b55f49c 100644 --- a/primitives/finality-grandpa/src/lib.rs +++ b/primitives/finality-grandpa/src/lib.rs @@ -35,8 +35,10 @@ use sp_runtime::{ }; use sp_std::{borrow::Cow, vec::Vec}; -#[cfg(feature = "std")] -use log::debug; +/// The log target to be used by client code. +pub const CLIENT_LOG_TARGET: &str = "grandpa"; +/// The log target to be used by runtime code. +pub const RUNTIME_LOG_TARGET: &str = "runtime::grandpa"; /// Key type for GRANDPA module. pub const KEY_TYPE: sp_core::crypto::KeyTypeId = sp_application_crypto::key_types::GRANDPA; @@ -428,8 +430,9 @@ where let valid = id.verify(&buf, signature); if !valid { - #[cfg(feature = "std")] - debug!(target: "afg", "Bad signature on message from {:?}", id); + let log_target = if cfg!(feature = "std") { CLIENT_LOG_TARGET } else { RUNTIME_LOG_TARGET }; + + log::debug!(target: log_target, "Bad signature on message from {:?}", id); } valid From 6376f13ac0bf8811be3ffc87fa535357699feefc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Thu, 5 Jan 2023 13:45:40 +0100 Subject: [PATCH 104/127] Return `RuntimeVersion` of overwritten/substituted wasm binary (#13066) * Adds test * Ensure we are using the runtime version of the override/substitute wasm * Update client/service/src/client/call_executor.rs Co-authored-by: Anton Co-authored-by: Anton --- client/service/src/client/call_executor.rs | 151 +++++++++++++----- client/service/src/client/wasm_override.rs | 31 ++-- client/service/src/client/wasm_substitutes.rs | 33 ++-- 3 files changed, 145 insertions(+), 70 deletions(-) diff --git a/client/service/src/client/call_executor.rs b/client/service/src/client/call_executor.rs index bf7e8b3a65..7ee19671dd 100644 --- a/client/service/src/client/call_executor.rs +++ b/client/service/src/client/call_executor.rs @@ -37,7 +37,6 @@ pub struct LocalCallExecutor { wasm_override: Arc>, wasm_substitutes: WasmSubstitutes, spawn_handle: Box, - client_config: ClientConfig, execution_extensions: Arc>, } @@ -61,7 +60,7 @@ where .transpose()?; let wasm_substitutes = WasmSubstitutes::new( - client_config.wasm_runtime_substitutes.clone(), + client_config.wasm_runtime_substitutes, executor.clone(), backend.clone(), )?; @@ -71,7 +70,6 @@ where executor, wasm_override: Arc::new(wasm_override), spawn_handle, - client_config, wasm_substitutes, execution_extensions: Arc::new(execution_extensions), }) @@ -84,34 +82,55 @@ where &'a self, onchain_code: RuntimeCode<'a>, id: &BlockId, - ) -> sp_blockchain::Result> + ) -> sp_blockchain::Result<(RuntimeCode<'a>, RuntimeVersion)> where Block: BlockT, B: backend::Backend, { - let spec = CallExecutor::runtime_version(self, id)?; - let code = - if let Some(d) = - self.wasm_override.as_ref().as_ref().and_then(|o| { - o.get(&spec.spec_version, onchain_code.heap_pages, &spec.spec_name) - }) { - log::debug!(target: "wasm_overrides", "using WASM override for block {}", id); - d - } else if let Some(s) = - self.wasm_substitutes.get(spec.spec_version, onchain_code.heap_pages, id) - { - log::debug!(target: "wasm_substitutes", "Using WASM substitute for block {:?}", id); - s - } else { - log::debug!( - target: "wasm_overrides", - "No WASM override available for block {}, using onchain code", - id - ); - onchain_code - }; - - Ok(code) + let on_chain_version = self.on_chain_runtime_version(id)?; + let code_and_version = if let Some(d) = self.wasm_override.as_ref().as_ref().and_then(|o| { + o.get( + &on_chain_version.spec_version, + onchain_code.heap_pages, + &on_chain_version.spec_name, + ) + }) { + log::debug!(target: "wasm_overrides", "using WASM override for block {}", id); + d + } else if let Some(s) = + self.wasm_substitutes + .get(on_chain_version.spec_version, onchain_code.heap_pages, id) + { + log::debug!(target: "wasm_substitutes", "Using WASM substitute for block {:?}", id); + s + } else { + log::debug!( + target: "wasm_overrides", + "Neither WASM override nor substitute available for block {id}, using onchain code", + ); + (onchain_code, on_chain_version) + }; + + Ok(code_and_version) + } + + /// Returns the on chain runtime version. + fn on_chain_runtime_version( + &self, + id: &BlockId, + ) -> sp_blockchain::Result { + let mut overlay = OverlayedChanges::default(); + + let at_hash = self.backend.blockchain().expect_block_hash_from_id(id)?; + let state = self.backend.state_at(at_hash)?; + let mut cache = StorageTransactionCache::::default(); + let mut ext = Ext::new(&mut overlay, &mut cache, &state, None); + let state_runtime_code = sp_state_machine::backend::BackendRuntimeCode::new(&state); + let runtime_code = + state_runtime_code.runtime_code().map_err(sp_blockchain::Error::RuntimeCode)?; + self.executor + .runtime_version(&mut ext, &runtime_code) + .map_err(|e| sp_blockchain::Error::VersionInvalid(e.to_string())) } } @@ -125,7 +144,6 @@ where executor: self.executor.clone(), wasm_override: self.wasm_override.clone(), spawn_handle: self.spawn_handle.clone(), - client_config: self.client_config.clone(), wasm_substitutes: self.wasm_substitutes.clone(), execution_extensions: self.execution_extensions.clone(), } @@ -162,7 +180,7 @@ where let runtime_code = state_runtime_code.runtime_code().map_err(sp_blockchain::Error::RuntimeCode)?; - let runtime_code = self.check_override(runtime_code, at)?; + let runtime_code = self.check_override(runtime_code, at)?.0; let extensions = self.execution_extensions.extensions( at_hash, @@ -214,7 +232,7 @@ where let runtime_code = state_runtime_code.runtime_code().map_err(sp_blockchain::Error::RuntimeCode)?; - let runtime_code = self.check_override(runtime_code, at)?; + let runtime_code = self.check_override(runtime_code, at)?.0; match recorder { Some(recorder) => { @@ -258,18 +276,13 @@ where } fn runtime_version(&self, id: &BlockId) -> sp_blockchain::Result { - let mut overlay = OverlayedChanges::default(); - let at_hash = self.backend.blockchain().expect_block_hash_from_id(id)?; let state = self.backend.state_at(at_hash)?; - let mut cache = StorageTransactionCache::::default(); - let mut ext = Ext::new(&mut overlay, &mut cache, &state, None); let state_runtime_code = sp_state_machine::backend::BackendRuntimeCode::new(&state); + let runtime_code = state_runtime_code.runtime_code().map_err(sp_blockchain::Error::RuntimeCode)?; - self.executor - .runtime_version(&mut ext, &runtime_code) - .map_err(|e| sp_blockchain::Error::VersionInvalid(e.to_string())) + self.check_override(runtime_code, id).map(|(_, v)| v) } fn prove_execution( @@ -287,7 +300,7 @@ where let state_runtime_code = sp_state_machine::backend::BackendRuntimeCode::new(trie_backend); let runtime_code = state_runtime_code.runtime_code().map_err(sp_blockchain::Error::RuntimeCode)?; - let runtime_code = self.check_override(runtime_code, at)?; + let runtime_code = self.check_override(runtime_code, at)?.0; sp_state_machine::prove_execution_on_trie_backend( trie_backend, @@ -352,6 +365,7 @@ mod tests { testing::TaskExecutor, traits::{FetchRuntimeCode, WrappedRuntimeCode}, }; + use std::collections::HashMap; use substrate_test_runtime_client::{runtime, GenesisInit, LocalExecutorDispatch}; #[test] @@ -395,7 +409,7 @@ mod tests { Box::new(TaskExecutor::new()), None, None, - Default::default(), + client_config, ) .expect("Creates a client"); @@ -404,7 +418,6 @@ mod tests { executor: executor.clone(), wasm_override: Arc::new(Some(overrides)), spawn_handle: Box::new(TaskExecutor::new()), - client_config, wasm_substitutes: WasmSubstitutes::new( Default::default(), executor.clone(), @@ -420,8 +433,64 @@ mod tests { let check = call_executor .check_override(onchain_code, &BlockId::Number(Default::default())) - .expect("RuntimeCode override"); + .expect("RuntimeCode override") + .0; assert_eq!(Some(vec![2, 2, 2, 2, 2, 2, 2, 2]), check.fetch_runtime_code().map(Into::into)); } + + #[test] + fn returns_runtime_version_from_substitute() { + const SUBSTITUTE_SPEC_NAME: &str = "substitute-spec-name-cool"; + + let executor = NativeElseWasmExecutor::::new( + WasmExecutionMethod::Interpreted, + Some(128), + 1, + 2, + ); + + let backend = Arc::new(in_mem::Backend::::new()); + + // Let's only override the `spec_name` for our testing purposes. + let substitute = sp_version::embed::embed_runtime_version( + &substrate_test_runtime::WASM_BINARY_BLOATY.unwrap(), + sp_version::RuntimeVersion { + spec_name: SUBSTITUTE_SPEC_NAME.into(), + ..substrate_test_runtime::VERSION + }, + ) + .unwrap(); + + let client_config = crate::client::ClientConfig { + wasm_runtime_substitutes: vec![(0, substitute)].into_iter().collect::>(), + ..Default::default() + }; + + let genesis_block_builder = crate::GenesisBlockBuilder::new( + &substrate_test_runtime_client::GenesisParameters::default().genesis_storage(), + !client_config.no_genesis, + backend.clone(), + executor.clone(), + ) + .expect("Creates genesis block builder"); + + // client is used for the convenience of creating and inserting the genesis block. + let client = + crate::client::new_with_backend::<_, _, runtime::Block, _, runtime::RuntimeApi>( + backend.clone(), + executor.clone(), + genesis_block_builder, + None, + Box::new(TaskExecutor::new()), + None, + None, + client_config, + ) + .expect("Creates a client"); + + let version = client.runtime_version_at(&BlockId::Number(0)).unwrap(); + + assert_eq!(SUBSTITUTE_SPEC_NAME, &*version.spec_name); + } } diff --git a/client/service/src/client/wasm_override.rs b/client/service/src/client/wasm_override.rs index 5fc748f3e8..03a5704adf 100644 --- a/client/service/src/client/wasm_override.rs +++ b/client/service/src/client/wasm_override.rs @@ -62,16 +62,16 @@ struct WasmBlob { hash: Vec, /// The path where this blob was found. path: PathBuf, - /// The `spec_name` found in the runtime version of this blob. - spec_name: String, + /// The runtime version of this blob. + version: RuntimeVersion, /// When was the last time we have warned about the wasm blob having /// a wrong `spec_name`? last_warn: parking_lot::Mutex>, } impl WasmBlob { - fn new(code: Vec, hash: Vec, path: PathBuf, spec_name: String) -> Self { - Self { code, hash, path, spec_name, last_warn: Default::default() } + fn new(code: Vec, hash: Vec, path: PathBuf, version: RuntimeVersion) -> Self { + Self { code, hash, path, version, last_warn: Default::default() } } fn runtime_code(&self, heap_pages: Option) -> RuntimeCode { @@ -141,10 +141,10 @@ impl WasmOverride { spec: &u32, pages: Option, spec_name: &str, - ) -> Option> { + ) -> Option<(RuntimeCode<'a>, RuntimeVersion)> { self.overrides.get(spec).and_then(|w| { - if spec_name == w.spec_name { - Some(w.runtime_code(pages)) + if spec_name == &*w.version.spec_name { + Some((w.runtime_code(pages), w.version.clone())) } else { let mut last_warn = w.last_warn.lock(); let now = Instant::now(); @@ -155,7 +155,7 @@ impl WasmOverride { tracing::warn!( target = "wasm_overrides", on_chain_spec_name = %spec_name, - override_spec_name = %w.spec_name, + override_spec_name = %w.version, spec_version = %spec, wasm_file = %w.path.display(), "On chain and override `spec_name` do not match! Ignoring override.", @@ -197,8 +197,7 @@ impl WasmOverride { "Found wasm override.", ); - let wasm = - WasmBlob::new(code, code_hash, path.clone(), version.spec_name.to_string()); + let wasm = WasmBlob::new(code, code_hash, path.clone(), version.clone()); if let Some(other) = overrides.insert(version.spec_version, wasm) { tracing::info!( @@ -246,19 +245,19 @@ impl WasmOverride { /// Returns a WasmOverride struct filled with dummy data for testing. #[cfg(test)] pub fn dummy_overrides() -> WasmOverride { + let version = RuntimeVersion { spec_name: "test".into(), ..Default::default() }; let mut overrides = HashMap::new(); overrides.insert( 0, - WasmBlob::new(vec![0, 0, 0, 0, 0, 0, 0, 0], vec![0], PathBuf::new(), "test".into()), + WasmBlob::new(vec![0, 0, 0, 0, 0, 0, 0, 0], vec![0], PathBuf::new(), version.clone()), ); overrides.insert( 1, - WasmBlob::new(vec![1, 1, 1, 1, 1, 1, 1, 1], vec![1], PathBuf::new(), "test".into()), - ); - overrides.insert( - 2, - WasmBlob::new(vec![2, 2, 2, 2, 2, 2, 2, 2], vec![2], PathBuf::new(), "test".into()), + WasmBlob::new(vec![1, 1, 1, 1, 1, 1, 1, 1], vec![1], PathBuf::new(), version.clone()), ); + overrides + .insert(2, WasmBlob::new(vec![2, 2, 2, 2, 2, 2, 2, 2], vec![2], PathBuf::new(), version)); + WasmOverride { overrides } } diff --git a/client/service/src/client/wasm_substitutes.rs b/client/service/src/client/wasm_substitutes.rs index f826fa3613..a7a3b2cbb2 100644 --- a/client/service/src/client/wasm_substitutes.rs +++ b/client/service/src/client/wasm_substitutes.rs @@ -21,7 +21,7 @@ use sc_client_api::backend; use sc_executor::RuntimeVersionOf; use sp_blockchain::{HeaderBackend, Result}; -use sp_core::traits::{FetchRuntimeCode, RuntimeCode}; +use sp_core::traits::{FetchRuntimeCode, RuntimeCode, WrappedRuntimeCode}; use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, NumberFor}, @@ -41,12 +41,13 @@ struct WasmSubstitute { hash: Vec, /// The block number on which we should start using the substitute. block_number: NumberFor, + version: RuntimeVersion, } impl WasmSubstitute { - fn new(code: Vec, block_number: NumberFor) -> Self { + fn new(code: Vec, block_number: NumberFor, version: RuntimeVersion) -> Self { let hash = make_hash(&code); - Self { code, hash, block_number } + Self { code, hash, block_number, version } } fn runtime_code(&self, heap_pages: Option) -> RuntimeCode { @@ -122,9 +123,17 @@ where let substitutes = substitutes .into_iter() .map(|(block_number, code)| { - let substitute = WasmSubstitute::new(code, block_number); - let version = Self::runtime_version(&executor, &substitute)?; - Ok((version.spec_version, substitute)) + let runtime_code = RuntimeCode { + code_fetcher: &WrappedRuntimeCode((&code).into()), + heap_pages: None, + hash: Vec::new(), + }; + let version = Self::runtime_version(&executor, &runtime_code)?; + let spec_version = version.spec_version; + + let substitute = WasmSubstitute::new(code, block_number, version); + + Ok((spec_version, substitute)) }) .collect::>>()?; @@ -139,18 +148,16 @@ where spec: u32, pages: Option, block_id: &BlockId, - ) -> Option> { + ) -> Option<(RuntimeCode<'_>, RuntimeVersion)> { let s = self.substitutes.get(&spec)?; - s.matches(block_id, &*self.backend).then(|| s.runtime_code(pages)) + s.matches(block_id, &*self.backend) + .then(|| (s.runtime_code(pages), s.version.clone())) } - fn runtime_version( - executor: &Executor, - code: &WasmSubstitute, - ) -> Result { + fn runtime_version(executor: &Executor, code: &RuntimeCode) -> Result { let mut ext = BasicExternalities::default(); executor - .runtime_version(&mut ext, &code.runtime_code(None)) + .runtime_version(&mut ext, code) .map_err(|e| WasmSubstituteError::VersionInvalid(e.to_string()).into()) } } From e4a4436732d1c77eabf25cc0c5b3ca8f7d29d117 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= <123550+andresilva@users.noreply.github.com> Date: Thu, 5 Jan 2023 15:19:24 +0000 Subject: [PATCH 105/127] aura: remove stale check to skip execution (#13074) --- client/consensus/aura/src/import_queue.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/client/consensus/aura/src/import_queue.rs b/client/consensus/aura/src/import_queue.rs index 0973b33d40..3ac26345db 100644 --- a/client/consensus/aura/src/import_queue.rs +++ b/client/consensus/aura/src/import_queue.rs @@ -246,14 +246,14 @@ where // skip the inherents verification if the runtime API is old or not expected to // exist. - if !block.state_action.skip_execution_checks() && - self.client - .runtime_api() - .has_api_with::, _>( - &BlockId::Hash(parent_hash), - |v| v >= 2, - ) - .map_err(|e| e.to_string())? + if self + .client + .runtime_api() + .has_api_with::, _>( + &BlockId::Hash(parent_hash), + |v| v >= 2, + ) + .map_err(|e| e.to_string())? { self.check_inherents( new_block.clone(), From 934d42aefb51b797ee9ef41270bc041b1c1c6025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Thu, 5 Jan 2023 22:11:27 +0100 Subject: [PATCH 106/127] Kademlia: Speed-up the record fetching (#13081) Before libp2p 0.50.0 we used a quorum of one to fetch records from the DHT. In the pr that upgraded to libp2p 0.50.0 we accidentally changed this behavior. This pr brings back the old behavior of using a qorum of one and thus, a faster discovery. After finding the first value, we directly finish the query. There was also another behavior change in libp2p, they stopped automatic caching on remote nodes. This pr also brings back the remote caching on nodes that are nearest to the key from our point of view of the network. The pr that changed the behavior in libp2p: https://github.com/libp2p/rust-libp2p/pull/2712 --- client/network/src/discovery.rs | 75 ++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 25 deletions(-) diff --git a/client/network/src/discovery.rs b/client/network/src/discovery.rs index 16dbc7662c..7abc723c40 100644 --- a/client/network/src/discovery.rs +++ b/client/network/src/discovery.rs @@ -249,7 +249,7 @@ impl DiscoveryConfig { NonZeroUsize::new(MAX_KNOWN_EXTERNAL_ADDRESSES) .expect("value is a constant; constant is non-zero; qed."), ), - outbound_query_records: Vec::new(), + records_to_publish: Default::default(), } } } @@ -287,8 +287,12 @@ pub struct DiscoveryBehaviour { allow_non_globals_in_dht: bool, /// A cache of discovered external addresses. Only used for logging purposes. known_external_addresses: LruHashSet, - /// A cache of outbound query records. - outbound_query_records: Vec<(record::Key, Vec)>, + /// Records to publish per QueryId. + /// + /// After finishing a Kademlia query, libp2p will return us a list of the closest peers that + /// did not return the record(in `FinishedWithNoAdditionalRecord`). We will then put the record + /// to these peers. + records_to_publish: HashMap, } impl DiscoveryBehaviour { @@ -692,33 +696,54 @@ impl NetworkBehaviour for DiscoveryBehaviour { KademliaEvent::OutboundQueryProgressed { result: QueryResult::GetRecord(res), stats, - step, + id, .. } => { let ev = match res { - Ok(ok) => - if let GetRecordOk::FoundRecord(r) = ok { - self.outbound_query_records - .push((r.record.key, r.record.value)); + Ok(GetRecordOk::FoundRecord(r)) => { + debug!( + target: "sub-libp2p", + "Libp2p => Found record ({:?}) with value: {:?}", + r.record.key, + r.record.value, + ); + + // Let's directly finish the query, as we are only interested in a + // quorum of 1. + if let Some(kad) = self.kademlia.as_mut() { + if let Some(mut query) = kad.query_mut(&id) { + query.finish(); + } + } + + self.records_to_publish.insert(id, r.record.clone()); + + DiscoveryOut::ValueFound( + vec![(r.record.key, r.record.value)], + stats.duration().unwrap_or_default(), + ) + }, + Ok(GetRecordOk::FinishedWithNoAdditionalRecord { + cache_candidates, + }) => { + if cache_candidates.is_empty() { continue - } else { - debug!( - target: "sub-libp2p", - "Libp2p => Query progressed to {:?} step (last: {:?})", - step.count, - step.last, - ); - if step.last { - let records = - self.outbound_query_records.drain(..).collect(); - DiscoveryOut::ValueFound( - records, - stats.duration().unwrap_or_default(), - ) - } else { - continue + } + + // Put the record to the `cache_candidates` that are nearest to the + // record key from our point of view of the network. + if let Some(record) = self.records_to_publish.remove(&id) { + if let Some(kad) = self.kademlia.as_mut() { + kad.put_record_to( + record, + cache_candidates.into_iter().map(|v| v.1), + Quorum::One, + ); } - }, + } + + continue + }, Err(e @ libp2p::kad::GetRecordError::NotFound { .. }) => { trace!( target: "sub-libp2p", From 8d17a16a5653974ec17ed617306606e9dbb6e4b4 Mon Sep 17 00:00:00 2001 From: Sasha Gryaznov Date: Fri, 6 Jan 2023 01:22:43 +0200 Subject: [PATCH 107/127] [contracts] Add docs generator for the contracts API to the `#[define_env]` macro (#13032) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * macro to expand traits for host functions documentation * other way: same Doc trait in seal modules * added docs for macro, and remove `doc` attribute * fmt * Apply suggestions from code review Co-authored-by: Alexander Theißen * make docs to be generated into re-exported `api_doc` module; fix unrelated elder docs; * make it compile without `doc` attr passed to macro * make alias functions indicated explicitly in docs * tidy up docs * refactored a bit * macro to auto-add doc warning for unstable functions * invoke macro with no doc generation by default * addressed review comments * hide api_doc module behind cfg(doc) Co-authored-by: Alexander Theißen --- frame/contracts/proc-macro/src/lib.rs | 153 ++++++++++++++++++--- frame/contracts/src/exec.rs | 2 +- frame/contracts/src/lib.rs | 3 + frame/contracts/src/wasm/mod.rs | 9 +- frame/contracts/src/wasm/runtime.rs | 191 +++++++++++++------------- 5 files changed, 242 insertions(+), 116 deletions(-) diff --git a/frame/contracts/proc-macro/src/lib.rs b/frame/contracts/proc-macro/src/lib.rs index ded5880a2d..a31d39f47e 100644 --- a/frame/contracts/proc-macro/src/lib.rs +++ b/frame/contracts/proc-macro/src/lib.rs @@ -30,9 +30,12 @@ use alloc::{ vec::Vec, }; use proc_macro::TokenStream; -use proc_macro2::TokenStream as TokenStream2; +use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::{quote, quote_spanned, ToTokens}; -use syn::{parse_macro_input, spanned::Spanned, Data, DeriveInput, FnArg, Ident}; +use syn::{ + parse_macro_input, punctuated::Punctuated, spanned::Spanned, token::Comma, Data, DeriveInput, + FnArg, Ident, +}; /// This derives `Debug` for a struct where each field must be of some numeric type. /// It interprets each field as its represents some weight and formats it as times so that @@ -158,6 +161,7 @@ struct HostFn { name: String, returns: HostFnReturn, is_stable: bool, + alias_to: Option, } enum HostFnReturn { @@ -187,7 +191,7 @@ impl ToTokens for HostFn { } impl HostFn { - pub fn try_from(item: syn::ItemFn) -> syn::Result { + pub fn try_from(mut item: syn::ItemFn) -> syn::Result { let err = |span, msg| { let msg = format!("Invalid host function definition. {}", msg); syn::Error::new(span, msg) @@ -198,10 +202,10 @@ impl HostFn { "only #[version()], #[unstable] and #[prefixed_alias] attributes are allowed."; let span = item.span(); let mut attrs = item.attrs.clone(); - attrs.retain(|a| !(a.path.is_ident("doc") || a.path.is_ident("prefixed_alias"))); - let name = item.sig.ident.to_string(); + attrs.retain(|a| !a.path.is_ident("doc")); let mut maybe_module = None; let mut is_stable = true; + let mut alias_to = None; while let Some(attr) = attrs.pop() { let ident = attr.path.get_ident().ok_or(err(span, msg))?.to_string(); match ident.as_str() { @@ -219,9 +223,17 @@ impl HostFn { } is_stable = false; }, + "prefixed_alias" => { + alias_to = Some(item.sig.ident.to_string()); + item.sig.ident = syn::Ident::new( + &format!("seal_{}", &item.sig.ident.to_string()), + item.sig.ident.span(), + ); + }, _ => return Err(err(span, msg)), } } + let name = item.sig.ident.to_string(); // process arguments: The first and second arg are treated differently (ctx, memory) // they must exist and be `ctx: _` and `memory: _`. @@ -317,6 +329,7 @@ impl HostFn { name, returns, is_stable, + alias_to, }) }, _ => Err(err(span, &msg)), @@ -348,19 +361,15 @@ impl EnvDef { .iter() .filter_map(extract_fn) .filter(|i| i.attrs.iter().any(selector)) - .map(|mut i| { - i.attrs.retain(|i| !selector(i)); - i.sig.ident = syn::Ident::new( - &format!("seal_{}", &i.sig.ident.to_string()), - i.sig.ident.span(), - ); - i - }) .map(|i| HostFn::try_from(i)); let host_funcs = items .iter() .filter_map(extract_fn) + .map(|mut i| { + i.attrs.retain(|i| !selector(i)); + i + }) .map(|i| HostFn::try_from(i)) .chain(aliases) .collect::, _>>()?; @@ -383,16 +392,111 @@ fn is_valid_special_arg(idx: usize, arg: &FnArg) -> bool { matches!(*pat.ty, syn::Type::Infer(_)) } +/// Expands documentation for host functions. +fn expand_docs(def: &mut EnvDef) -> TokenStream2 { + let mut modules = def.host_funcs.iter().map(|f| f.module.clone()).collect::>(); + modules.sort(); + modules.dedup(); + + let doc_selector = |a: &syn::Attribute| a.path.is_ident("doc"); + let docs = modules.iter().map(|m| { + let funcs = def.host_funcs.iter_mut().map(|f| { + if *m == f.module { + // Remove auxiliary args: `ctx: _` and `memory: _` + f.item.sig.inputs = f + .item + .sig + .inputs + .iter() + .skip(2) + .map(|p| p.clone()) + .collect::>(); + let func_decl = f.item.sig.to_token_stream(); + let func_doc = if let Some(origin_fn) = &f.alias_to { + let alias_doc = format!( + "This is just an alias function to [`{0}()`][`Self::{0}`] with backwards-compatible prefixed identifier.", + origin_fn, + ); + quote! { #[doc = #alias_doc] } + + } else { + let func_docs = f.item.attrs.iter().filter(|a| doc_selector(a)).map(|d| { + let docs = d.to_token_stream(); + quote! { #docs } + }); + let unstable_notice = if !f.is_stable { + let warning = "\n # Unstable\n\n \ + This function is unstable and it is a subject to change (or removal) in the future.\n \ + Do not deploy a contract using it to a production chain."; + quote! { #[doc = #warning] } + } else { + quote! {} + }; + quote! { + #( #func_docs )* + #unstable_notice + } + }; + quote! { + #func_doc + #func_decl; + } + } else { + quote! {} + } + }); + + let module = Ident::new(m, Span::call_site()); + let module_doc = format!( + "Documentation of the API available to contracts by importing `{}` WASM module.", + module + ); + + quote! { + #[doc = #module_doc] + pub mod #module { + use crate::wasm::runtime::{TrapReason, ReturnCode}; + /// Every function in this trait represents (at least) one function that can be imported by a contract. + /// + /// The function's identifier is to be set as the name in the import definition. + /// Where it is specifically indicated, an _alias_ function having `seal_`-prefixed identifier and + /// just the same signature and body, is also available (for backwards-compatibility purposes). + pub trait Api { + #( #funcs )* + } + } + } + }); + quote! { + #( #docs )* + } +} + /// Expands environment definiton. /// Should generate source code for: /// - implementations of the host functions to be added to the wasm runtime environment (see /// `expand_impls()`). -fn expand_env(def: &mut EnvDef) -> TokenStream2 { +fn expand_env(def: &mut EnvDef, docs: bool) -> TokenStream2 { let impls = expand_impls(def); + let docs = docs.then_some(expand_docs(def)).unwrap_or(TokenStream2::new()); quote! { pub struct Env; #impls + /// Contains the documentation of the API available to contracts. + /// + /// In order to generate this documentation, pass `doc` attribute to the [`#[define_env]`][`macro@define_env`] macro: + /// `#[define_env(doc)]`, and then run `cargo doc`. + /// + /// This module is not meant to be used by any code. Rather, it is meant to be consumed by humans through rustdoc. + /// + /// Every function described in this module's sub module's traits uses this sub module's identifier + /// as its imported module name. The identifier of the function is the function's imported name. + /// According to the [WASM spec of imports](https://webassembly.github.io/spec/core/text/modules.html#text-import). + #[cfg(doc)] + pub mod api_doc { + #docs + } } } @@ -579,10 +683,25 @@ fn expand_functions( /// /// The implementation on `()` can be used in places where no `Ext` exists, yet. This is useful /// when only checking whether a code can be instantiated without actually executing any code. +/// +/// # Generating Documentation +/// +/// Passing `doc` attribute to the macro (like `#[define_env(doc)]`) will make it also expand +/// additional `pallet_contracts::api_doc::seal0`, `pallet_contracts::api_doc::seal1`, +/// `...` modules each having its `Api` trait containing functions holding documentation for every +/// host function defined by the macro. +/// +/// To build up these docs, run: +/// +/// ```nocompile +/// cargo doc +/// ``` #[proc_macro_attribute] pub fn define_env(attr: TokenStream, item: TokenStream) -> TokenStream { - if !attr.is_empty() { - let msg = "Invalid `define_env` attribute macro: expected no attributes: `#[define_env]`."; + if !attr.is_empty() && !(attr.to_string() == "doc".to_string()) { + let msg = r#"Invalid `define_env` attribute macro: expected either no attributes or a single `doc` attibute: + - `#[define_env]` + - `#[define_env(doc)]`"#; let span = TokenStream2::from(attr).span(); return syn::Error::new(span, msg).to_compile_error().into() } @@ -590,7 +709,7 @@ pub fn define_env(attr: TokenStream, item: TokenStream) -> TokenStream { let item = syn::parse_macro_input!(item as syn::ItemMod); match EnvDef::try_from(item) { - Ok(mut def) => expand_env(&mut def).into(), + Ok(mut def) => expand_env(&mut def, !attr.is_empty()).into(), Err(e) => e.to_compile_error().into(), } } diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index df1dd24856..8ae70b804d 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -462,7 +462,7 @@ enum FrameArgs<'a, T: Config, E> { /// If `None` the contract info needs to be reloaded from storage. cached_info: Option>, /// This frame was created by `seal_delegate_call` and hence uses different code than - /// what is stored at [`Self::dest`]. Its caller ([`Frame::delegated_caller`]) is the + /// what is stored at [`Self::Call::dest`]. Its caller ([`DelegatedCall::caller`]) is the /// account which called the caller contract delegated_call: Option>, }, diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index 778ceec961..9df790e09a 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -134,6 +134,9 @@ pub use crate::{ wasm::Determinism, }; +#[cfg(doc)] +pub use crate::wasm::api_doc; + type CodeHash = ::Hash; type TrieId = BoundedVec>; type BalanceOf = diff --git a/frame/contracts/src/wasm/mod.rs b/frame/contracts/src/wasm/mod.rs index 553bae59e7..f1d0ccc7c2 100644 --- a/frame/contracts/src/wasm/mod.rs +++ b/frame/contracts/src/wasm/mod.rs @@ -28,6 +28,10 @@ pub use crate::wasm::{ prepare::TryInstantiate, runtime::{CallFlags, Environment, ReturnCode, Runtime, RuntimeCosts}, }; + +#[cfg(doc)] +pub use crate::wasm::runtime::api_doc; + use crate::{ exec::{ExecResult, Executable, ExportedFunction, Ext}, gas::GasMeter, @@ -144,7 +148,7 @@ impl PrefabWasmModule { /// Create the module by checking and instrumenting `original_code`. /// /// This does **not** store the module. For this one need to either call [`Self::store`] - /// or [`::execute`]. + /// or [`::execute`][`Executable::execute`]. pub fn from_code( original_code: Vec, schedule: &Schedule, @@ -164,7 +168,8 @@ impl PrefabWasmModule { /// Store the code without instantiating it. /// - /// Otherwise the code is stored when [`::execute`] is called. + /// Otherwise the code is stored when [`::execute`][`Executable::execute`] + /// is called. pub fn store(self) -> DispatchResult { code_cache::store(self, false) } diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index 7d67adc3de..098f8d280e 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -52,7 +52,7 @@ pub trait Environment { /// Type of a storage key. #[allow(dead_code)] enum KeyType { - /// Deprecated fix sized key [0;32]. + /// Deprecated fix sized key `[u8;32]`. Fix, /// Variable sized key used in transparent hashing, /// cannot be larger than MaxStorageKeyLen. @@ -996,14 +996,14 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> { // Any input that leads to a out of bound error (reading or writing) or failing to decode // data passed to the supervisor will lead to a trap. This is not documented explicitly // for every function. -#[define_env] +#[define_env(doc)] pub mod env { /// Account for used gas. Traps if gas used is greater than gas limit. /// /// NOTE: This is a implementation defined call and is NOT a part of the public API. /// This call is supposed to be called only by instrumentation injected code. /// - /// - amount: How much gas is used. + /// - `amount`: How much gas is used. fn gas(ctx: _, _memory: _, amount: u64) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::MeteringBlock(amount))?; Ok(()) @@ -1011,8 +1011,8 @@ pub mod env { /// Set the value at the given key in the contract storage. /// - /// Equivalent to the newer version of `seal_set_storage` with the exception of the return - /// type. Still a valid thing to call when not interested in the return value. + /// Equivalent to the newer version [`super::seal1::Api::set_storage`] with the exception of the + /// return type. Still a valid thing to call when not interested in the return value. #[prefixed_alias] fn set_storage( ctx: _, @@ -1085,8 +1085,8 @@ pub mod env { /// Clear the value at the given key in the contract storage. /// - /// Equivalent to the newer version of `seal_clear_storage` with the exception of the return - /// type. Still a valid thing to call when not interested in the return value. + /// Equivalent to the newer version [`super::seal1::Api::clear_storage`] with the exception of + /// the return type. Still a valid thing to call when not interested in the return value. #[prefixed_alias] fn clear_storage(ctx: _, memory: _, key_ptr: u32) -> Result<(), TrapReason> { ctx.clear_storage(memory, KeyType::Fix, key_ptr).map(|_| ()) @@ -1152,7 +1152,7 @@ pub mod env { /// /// # Errors /// - /// `ReturnCode::KeyNotFound` + /// - `ReturnCode::KeyNotFound` #[version(1)] #[prefixed_alias] fn get_storage( @@ -1215,7 +1215,7 @@ pub mod env { /// /// # Errors /// - /// `ReturnCode::KeyNotFound` + /// - `ReturnCode::KeyNotFound` #[prefixed_alias] fn take_storage( ctx: _, @@ -1245,16 +1245,16 @@ pub mod env { /// /// # Parameters /// - /// - account_ptr: a pointer to the address of the beneficiary account Should be decodable as an - /// `T::AccountId`. Traps otherwise. - /// - account_len: length of the address buffer. - /// - value_ptr: a pointer to the buffer with value, how much value to send. Should be decodable - /// as a `T::Balance`. Traps otherwise. - /// - value_len: length of the value buffer. + /// - `account_ptr`: a pointer to the address of the beneficiary account Should be decodable as + /// an `T::AccountId`. Traps otherwise. + /// - `account_len`: length of the address buffer. + /// - `value_ptr`: a pointer to the buffer with value, how much value to send. Should be + /// decodable as a `T::Balance`. Traps otherwise. + /// - `value_len`: length of the value buffer. /// /// # Errors /// - /// `ReturnCode::TransferFailed` + /// - `ReturnCode::TransferFailed` #[prefixed_alias] fn transfer( ctx: _, @@ -1288,8 +1288,9 @@ pub mod env { /// # Note /// /// The values `_callee_len` and `_value_len` are ignored because the encoded sizes - /// of those types are fixed through `[`MaxEncodedLen`]. The fields exist for backwards - /// compatibility. Consider switching to the newest version of this function. + /// of those types are fixed through + /// [`codec::MaxEncodedLen`]. The fields exist + /// for backwards compatibility. Consider switching to the newest version of this function. #[prefixed_alias] fn call( ctx: _, @@ -1323,16 +1324,16 @@ pub mod env { /// /// # Parameters /// - /// - flags: See [`CallFlags`] for a documenation of the supported flags. - /// - callee_ptr: a pointer to the address of the callee contract. Should be decodable as an + /// - `flags`: See `crate::wasm::runtime::CallFlags` for a documenation of the supported flags. + /// - `callee_ptr`: a pointer to the address of the callee contract. Should be decodable as an /// `T::AccountId`. Traps otherwise. - /// - gas: how much gas to devote to the execution. - /// - value_ptr: a pointer to the buffer with value, how much value to send. Should be decodable - /// as a `T::Balance`. Traps otherwise. - /// - input_data_ptr: a pointer to a buffer to be used as input data to the callee. - /// - input_data_len: length of the input data buffer. - /// - output_ptr: a pointer where the output buffer is copied to. - /// - output_len_ptr: in-out pointer to where the length of the buffer is read from and the + /// - `gas`: how much gas to devote to the execution. + /// - `value_ptr`: a pointer to the buffer with value, how much value to send. Should be + /// decodable as a `T::Balance`. Traps otherwise. + /// - `input_data_ptr`: a pointer to a buffer to be used as input data to the callee. + /// - `input_data_len`: length of the input data buffer. + /// - `output_ptr`: a pointer where the output buffer is copied to. + /// - `output_len_ptr`: in-out pointer to where the length of the buffer is read from and the /// actual length is written to. /// /// # Errors @@ -1340,10 +1341,10 @@ pub mod env { /// An error means that the call wasn't successful output buffer is returned unless /// stated otherwise. /// - /// `ReturnCode::CalleeReverted`: Output buffer is returned. - /// `ReturnCode::CalleeTrapped` - /// `ReturnCode::TransferFailed` - /// `ReturnCode::NotCallable` + /// - `ReturnCode::CalleeReverted`: Output buffer is returned. + /// - `ReturnCode::CalleeTrapped` + /// - `ReturnCode::TransferFailed` + /// - `ReturnCode::NotCallable` #[version(1)] #[prefixed_alias] fn call( @@ -1377,12 +1378,12 @@ pub mod env { /// /// # Parameters /// - /// - flags: See [`CallFlags`] for a documentation of the supported flags. - /// - code_hash: a pointer to the hash of the code to be called. - /// - input_data_ptr: a pointer to a buffer to be used as input data to the callee. - /// - input_data_len: length of the input data buffer. - /// - output_ptr: a pointer where the output buffer is copied to. - /// - output_len_ptr: in-out pointer to where the length of the buffer is read from and the + /// - `flags`: see `crate::wasm::runtime::CallFlags` for a documentation of the supported flags. + /// - `code_hash`: a pointer to the hash of the code to be called. + /// - `input_data_ptr`: a pointer to a buffer to be used as input data to the callee. + /// - `input_data_len`: length of the input data buffer. + /// - `output_ptr`: a pointer where the output buffer is copied to. + /// - `output_len_ptr`: in-out pointer to where the length of the buffer is read from and the /// actual length is written to. /// /// # Errors @@ -1390,9 +1391,9 @@ pub mod env { /// An error means that the call wasn't successful and no output buffer is returned unless /// stated otherwise. /// - /// `ReturnCode::CalleeReverted`: Output buffer is returned. - /// `ReturnCode::CalleeTrapped` - /// `ReturnCode::CodeNotFound` + /// - `ReturnCode::CalleeReverted`: Output buffer is returned. + /// - `ReturnCode::CalleeTrapped` + /// - `ReturnCode::CodeNotFound` #[prefixed_alias] fn delegate_call( ctx: _, @@ -1425,8 +1426,9 @@ pub mod env { /// # Note /// /// The values `_code_hash_len` and `_value_len` are ignored because the encoded sizes - /// of those types are fixed through `[`MaxEncodedLen`]. The fields exist for backwards - /// compatibility. Consider switching to the newest version of this function. + /// of those types are fixed through + /// [`codec::MaxEncodedLen`]. The fields exist + /// for backwards compatibility. Consider switching to the newest version of this function. #[prefixed_alias] fn instantiate( ctx: _, @@ -1474,20 +1476,20 @@ pub mod env { /// /// # Parameters /// - /// - code_hash_ptr: a pointer to the buffer that contains the initializer code. - /// - gas: how much gas to devote to the execution of the initializer code. - /// - value_ptr: a pointer to the buffer with value, how much value to send. Should be decodable - /// as a `T::Balance`. Traps otherwise. - /// - input_data_ptr: a pointer to a buffer to be used as input data to the initializer code. - /// - input_data_len: length of the input data buffer. - /// - address_ptr: a pointer where the new account's address is copied to. - /// - address_len_ptr: in-out pointer to where the length of the buffer is read from and the + /// - `code_hash_ptr`: a pointer to the buffer that contains the initializer code. + /// - `gas`: how much gas to devote to the execution of the initializer code. + /// - `value_ptr`: a pointer to the buffer with value, how much value to send. Should be + /// decodable as a `T::Balance`. Traps otherwise. + /// - `input_data_ptr`: a pointer to a buffer to be used as input data to the initializer code. + /// - `input_data_len`: length of the input data buffer. + /// - `address_ptr`: a pointer where the new account's address is copied to. + /// - `address_len_ptr`: in-out pointer to where the length of the buffer is read from and the /// actual length is written to. - /// - output_ptr: a pointer where the output buffer is copied to. - /// - output_len_ptr: in-out pointer to where the length of the buffer is read from and the + /// - `output_ptr`: a pointer where the output buffer is copied to. + /// - `output_len_ptr`: in-out pointer to where the length of the buffer is read from and the /// actual length is written to. - /// - salt_ptr: Pointer to raw bytes used for address derivation. See `fn contract_address`. - /// - salt_len: length in bytes of the supplied salt. + /// - `salt_ptr`: Pointer to raw bytes used for address derivation. See `fn contract_address`. + /// - `salt_len`: length in bytes of the supplied salt. /// /// # Errors /// @@ -1497,10 +1499,10 @@ pub mod env { /// An error means that the account wasn't created and no address or output buffer /// is returned unless stated otherwise. /// - /// `ReturnCode::CalleeReverted`: Output buffer is returned. - /// `ReturnCode::CalleeTrapped` - /// `ReturnCode::TransferFailed` - /// `ReturnCode::CodeNotFound` + /// - `ReturnCode::CalleeReverted`: Output buffer is returned. + /// - `ReturnCode::CalleeTrapped` + /// - `ReturnCode::TransferFailed` + /// - `ReturnCode::CodeNotFound` #[version(1)] #[prefixed_alias] fn instantiate( @@ -1562,7 +1564,7 @@ pub mod env { /// execution of the destroyed contract is halted. Or it failed during the termination /// which is considered fatal and results in a trap + rollback. /// - /// - beneficiary_ptr: a pointer to the address of the beneficiary account where all where all + /// - `beneficiary_ptr`: a pointer to the address of the beneficiary account where all where all /// remaining funds of the caller are transferred. Should be decodable as an `T::AccountId`. /// Traps otherwise. /// @@ -1586,7 +1588,7 @@ pub mod env { /// /// # Note /// - /// This function traps if the input was previously forwarded by a `seal_call`. + /// This function traps if the input was previously forwarded by a [`call()`][`Self::call()`]. #[prefixed_alias] fn input(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::InputBase)?; @@ -1606,7 +1608,7 @@ pub mod env { /// This function never returns as it stops execution of the caller. /// This is the only way to return a data buffer to the caller. Returning from /// execution without calling this function is equivalent to calling: - /// ``` + /// ```nocompile /// seal_return(0, 0, 0); /// ``` /// @@ -1659,10 +1661,10 @@ pub mod env { /// /// # Parameters /// - /// - account_ptr: a pointer to the address of the beneficiary account Should be decodable as an - /// `T::AccountId`. Traps otherwise. + /// - `account_ptr`: a pointer to the address of the beneficiary account Should be decodable as + /// an `T::AccountId`. Traps otherwise. /// - /// Returned value is a u32-encoded boolean: (0 = false, 1 = true). + /// Returned value is a `u32`-encoded boolean: (0 = false, 1 = true). #[prefixed_alias] fn is_contract(ctx: _, memory: _, account_ptr: u32) -> Result { ctx.charge_gas(RuntimeCosts::IsContract)?; @@ -1684,7 +1686,7 @@ pub mod env { /// /// # Errors /// - /// `ReturnCode::KeyNotFound` + /// - `ReturnCode::KeyNotFound` #[prefixed_alias] fn code_hash( ctx: _, @@ -1734,14 +1736,14 @@ pub mod env { /// Checks whether the caller of the current contract is the origin of the whole call stack. /// - /// Prefer this over `seal_is_contract` when checking whether your contract is being called by a - /// contract or a plain account. The reason is that it performs better since it does not need to - /// do any storage lookups. + /// Prefer this over [`is_contract()`][`Self::is_contract`] when checking whether your contract + /// is being called by a contract or a plain account. The reason is that it performs better + /// since it does not need to do any storage lookups. /// - /// A return value of`true` indicates that this contract is being called by a plain account + /// A return value of `true` indicates that this contract is being called by a plain account /// and `false` indicates that the caller is another contract. /// - /// Returned value is a u32-encoded boolean: (0 = false, 1 = true). + /// Returned value is a `u32`-encoded boolean: (`0 = false`, `1 = true`). #[prefixed_alias] fn caller_is_origin(ctx: _, _memory: _) -> Result { ctx.charge_gas(RuntimeCosts::CallerIsOrigin)?; @@ -1774,7 +1776,7 @@ pub mod env { /// `out_ptr`. This call overwrites it with the size of the value. If the available /// space at `out_ptr` is less than the size of the value a trap is triggered. /// - /// The data is encoded as T::Balance. + /// The data is encoded as `T::Balance`. /// /// # Note /// @@ -1822,14 +1824,14 @@ pub mod env { )?) } - /// Stores the **free* balance of the current account into the supplied buffer. + /// Stores the *free* balance of the current account into the supplied buffer. /// /// The value is stored to linear memory at the address pointed to by `out_ptr`. /// `out_len_ptr` must point to a u32 value that describes the available space at /// `out_ptr`. This call overwrites it with the size of the value. If the available /// space at `out_ptr` is less than the size of the value a trap is triggered. /// - /// The data is encoded as T::Balance. + /// The data is encoded as `T::Balance`. #[prefixed_alias] fn balance(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::Balance)?; @@ -1846,11 +1848,11 @@ pub mod env { /// Stores the value transferred along with this call/instantiate into the supplied buffer. /// /// The value is stored to linear memory at the address pointed to by `out_ptr`. - /// `out_len_ptr` must point to a u32 value that describes the available space at + /// `out_len_ptr` must point to a `u32` value that describes the available space at /// `out_ptr`. This call overwrites it with the size of the value. If the available /// space at `out_ptr` is less than the size of the value a trap is triggered. /// - /// The data is encoded as T::Balance. + /// The data is encoded as `T::Balance`. #[prefixed_alias] fn value_transferred( ctx: _, @@ -1876,11 +1878,12 @@ pub mod env { /// `out_ptr`. This call overwrites it with the size of the value. If the available /// space at `out_ptr` is less than the size of the value a trap is triggered. /// - /// The data is encoded as T::Hash. + /// The data is encoded as `T::Hash`. /// /// # Deprecation /// - /// This function is deprecated. Users should migrate to the version in the "seal1" module. + /// This function is deprecated. Users should migrate to the [`super::seal1::Api::random()`] + /// version. #[prefixed_alias] fn random( ctx: _, @@ -1972,7 +1975,7 @@ pub mod env { /// Stores the minimum balance (a.k.a. existential deposit) into the supplied buffer. /// - /// The data is encoded as T::Balance. + /// The data is encoded as `T::Balance`. #[prefixed_alias] fn minimum_balance( ctx: _, @@ -2000,7 +2003,7 @@ pub mod env { /// /// # Deprecation /// - /// There is no longer a tombstone deposit. This function always returns 0. + /// There is no longer a tombstone deposit. This function always returns `0`. #[prefixed_alias] fn tombstone_deposit( ctx: _, @@ -2067,11 +2070,12 @@ pub mod env { /// Deposit a contract event with the data buffer and optional list of topics. There is a limit /// on the maximum number of topics specified by `event_topics`. /// - /// - topics_ptr - a pointer to the buffer of topics encoded as `Vec`. The value of - /// this is ignored if `topics_len` is set to 0. The topics list can't contain duplicates. - /// - topics_len - the length of the topics buffer. Pass 0 if you want to pass an empty vector. - /// - data_ptr - a pointer to a raw data buffer which will saved along the event. - /// - data_len - the length of the data buffer. + /// - `topics_ptr`: a pointer to the buffer of topics encoded as `Vec`. The value of + /// this is ignored if `topics_len` is set to `0`. The topics list can't contain duplicates. + /// - `topics_len`: the length of the topics buffer. Pass 0 if you want to pass an empty + /// vector. + /// - `data_ptr`: a pointer to a raw data buffer which will saved along the event. + /// - `data_len`: the length of the data buffer. #[prefixed_alias] fn deposit_event( ctx: _, @@ -2422,11 +2426,6 @@ pub mod env { /// - Provide functionality **exclusively** to contracts. /// - Provide custom weights. /// - Avoid the need to keep the `Call` data structure stable. - /// - /// # Unstable - /// - /// This function is unstable and subject to change (or removal) in the future. Do not - /// deploy a contract using it to a production chain. #[unstable] #[prefixed_alias] fn call_runtime( @@ -2466,7 +2465,7 @@ pub mod env { /// /// # Errors /// - /// `ReturnCode::EcdsaRecoverFailed` + /// - `ReturnCode::EcdsaRecoverFailed` #[prefixed_alias] fn ecdsa_recover( ctx: _, @@ -2513,8 +2512,8 @@ pub mod env { /// /// 3. If a contract calls into itself after changing its code the new call would use /// the new code. However, if the original caller panics after returning from the sub call it - /// would revert the changes made by `seal_set_code_hash` and the next caller would use - /// the old code. + /// would revert the changes made by [`set_code_hash()`][`Self::set_code_hash`] and the next + /// caller would use the old code. /// /// # Parameters /// @@ -2522,7 +2521,7 @@ pub mod env { /// /// # Errors /// - /// `ReturnCode::CodeNotFound` + /// - `ReturnCode::CodeNotFound` #[prefixed_alias] fn set_code_hash(ctx: _, memory: _, code_hash_ptr: u32) -> Result { ctx.charge_gas(RuntimeCosts::SetCodeHash)?; @@ -2552,7 +2551,7 @@ pub mod env { /// /// # Errors /// - /// `ReturnCode::EcdsaRecoverFailed` + /// - `ReturnCode::EcdsaRecoverFailed` #[prefixed_alias] fn ecdsa_to_eth_address( ctx: _, @@ -2578,7 +2577,7 @@ pub mod env { /// /// # Return Value /// - /// Returns 0 when there is no reentrancy. + /// Returns `0` when there is no reentrancy. #[unstable] fn reentrance_count(ctx: _, memory: _) -> Result { ctx.charge_gas(RuntimeCosts::ReentrantCount)?; @@ -2594,7 +2593,7 @@ pub mod env { /// /// # Return Value /// - /// Returns 0 when the contract does not exist on the call stack. + /// Returns `0` when the contract does not exist on the call stack. #[unstable] fn account_reentrance_count(ctx: _, memory: _, account_ptr: u32) -> Result { ctx.charge_gas(RuntimeCosts::AccountEntranceCount)?; From bc73ebf85039dad2cad399be6effe343ad9552e9 Mon Sep 17 00:00:00 2001 From: Falco Hirschenberger Date: Fri, 6 Jan 2023 09:45:56 +0100 Subject: [PATCH 108/127] Update trybuild to avoid random test failures. (#13073) * Update trybuild to avoid random test failures. A feature was added to trybuild >1.0.70 avoid failing on different variant counts in the line `and 278 others` fixes #12955 * Update all trybuild deps to latest version * Update Cargo.lock --- Cargo.lock | 4 ++-- frame/election-provider-support/solution-type/Cargo.toml | 2 +- frame/support/test/Cargo.toml | 2 +- primitives/api/test/Cargo.toml | 2 +- primitives/runtime-interface/Cargo.toml | 2 +- test-utils/Cargo.toml | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d333b79c8c..a3dc04a397 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11387,9 +11387,9 @@ dependencies = [ [[package]] name = "trybuild" -version = "1.0.73" +version = "1.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed01de3de062db82c0920b5cabe804f88d599a3f217932292597c678c903754d" +checksum = "654bfc024d30963fce210f22f98956407fe8c8365eb85a1fa530f6f8844137ed" dependencies = [ "dissimilar", "glob", diff --git a/frame/election-provider-support/solution-type/Cargo.toml b/frame/election-provider-support/solution-type/Cargo.toml index 5a0c46cb83..50c39c547f 100644 --- a/frame/election-provider-support/solution-type/Cargo.toml +++ b/frame/election-provider-support/solution-type/Cargo.toml @@ -27,4 +27,4 @@ sp-arithmetic = { version = "6.0.0", path = "../../../primitives/arithmetic" } # used by generate_solution_type: frame-election-provider-support = { version = "4.0.0-dev", path = ".." } frame-support = { version = "4.0.0-dev", path = "../../support" } -trybuild = "1.0.60" +trybuild = "1.0.74" diff --git a/frame/support/test/Cargo.toml b/frame/support/test/Cargo.toml index 0ac3d90f59..7fe1bcb567 100644 --- a/frame/support/test/Cargo.toml +++ b/frame/support/test/Cargo.toml @@ -23,7 +23,7 @@ sp-runtime = { version = "7.0.0", default-features = false, path = "../../../pri sp-core = { version = "7.0.0", default-features = false, path = "../../../primitives/core" } sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } sp-version = { version = "5.0.0", default-features = false, path = "../../../primitives/version" } -trybuild = { version = "1.0.60", features = [ "diff" ] } +trybuild = { version = "1.0.74", features = [ "diff" ] } pretty_assertions = "1.2.1" rustversion = "1.0.6" frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } diff --git a/primitives/api/test/Cargo.toml b/primitives/api/test/Cargo.toml index edc0d43e91..7f15287cf3 100644 --- a/primitives/api/test/Cargo.toml +++ b/primitives/api/test/Cargo.toml @@ -21,7 +21,7 @@ sp-consensus = { version = "0.10.0-dev", path = "../../consensus/common" } sc-block-builder = { version = "0.10.0-dev", path = "../../../client/block-builder" } codec = { package = "parity-scale-codec", version = "3.0.0" } sp-state-machine = { version = "0.13.0", path = "../../state-machine" } -trybuild = "1.0.60" +trybuild = "1.0.74" rustversion = "1.0.6" [dev-dependencies] diff --git a/primitives/runtime-interface/Cargo.toml b/primitives/runtime-interface/Cargo.toml index 09f4b83d68..da746179e2 100644 --- a/primitives/runtime-interface/Cargo.toml +++ b/primitives/runtime-interface/Cargo.toml @@ -32,7 +32,7 @@ sp-state-machine = { version = "0.13.0", path = "../state-machine" } sp-core = { version = "7.0.0", path = "../core" } sp-io = { version = "7.0.0", path = "../io" } rustversion = "1.0.6" -trybuild = "1.0.60" +trybuild = "1.0.74" [features] default = [ "std" ] diff --git a/test-utils/Cargo.toml b/test-utils/Cargo.toml index cd4839fa1c..1b98b23e0b 100644 --- a/test-utils/Cargo.toml +++ b/test-utils/Cargo.toml @@ -18,5 +18,5 @@ tokio = { version = "1.22.0", features = ["macros", "time"] } substrate-test-utils-derive = { version = "0.10.0-dev", path = "./derive" } [dev-dependencies] -trybuild = { version = "1.0.53", features = [ "diff" ] } +trybuild = { version = "1.0.74", features = [ "diff" ] } sc-service = { version = "0.10.0-dev", path = "../client/service" } From 363ce0a9a8df4fa8071f35974b917333b0ef300d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 6 Jan 2023 10:41:17 +0100 Subject: [PATCH 109/127] Do not leak records (#13086) --- client/network/src/discovery.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/client/network/src/discovery.rs b/client/network/src/discovery.rs index 7abc723c40..5421db8194 100644 --- a/client/network/src/discovery.rs +++ b/client/network/src/discovery.rs @@ -716,6 +716,8 @@ impl NetworkBehaviour for DiscoveryBehaviour { } } + // Will be removed below when we receive + // `FinishedWithNoAdditionalRecord`. self.records_to_publish.insert(id, r.record.clone()); DiscoveryOut::ValueFound( @@ -726,13 +728,14 @@ impl NetworkBehaviour for DiscoveryBehaviour { Ok(GetRecordOk::FinishedWithNoAdditionalRecord { cache_candidates, }) => { - if cache_candidates.is_empty() { - continue - } - - // Put the record to the `cache_candidates` that are nearest to the - // record key from our point of view of the network. + // We always need to remove the record to not leak any data! if let Some(record) = self.records_to_publish.remove(&id) { + if cache_candidates.is_empty() { + continue + } + + // Put the record to the `cache_candidates` that are nearest to + // the record key from our point of view of the network. if let Some(kad) = self.kademlia.as_mut() { kad.put_record_to( record, From 90ffe716b88f6dc64eeb530332267a86e66668b4 Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Fri, 6 Jan 2023 12:24:12 +0200 Subject: [PATCH 110/127] Only use `substrate-wasm-builder` when `std` feature is enabled. (#12790) Co-authored-by: parity-processbot <> --- bin/node-template/runtime/Cargo.toml | 3 +- bin/node-template/runtime/build.rs | 15 +++---- bin/node/runtime/Cargo.toml | 3 +- bin/node/runtime/build.rs | 15 +++---- client/executor/runtime-test/Cargo.toml | 3 +- client/executor/runtime-test/build.rs | 32 ++++++++------- .../test-wasm-deprecated/Cargo.toml | 10 ++++- .../test-wasm-deprecated/build.rs | 15 +++---- .../runtime-interface/test-wasm/Cargo.toml | 10 ++++- .../runtime-interface/test-wasm/build.rs | 15 +++---- test-utils/runtime/Cargo.toml | 3 +- test-utils/runtime/build.rs | 40 ++++++++++--------- utils/wasm-builder/README.md | 25 ++++++------ 13 files changed, 109 insertions(+), 80 deletions(-) diff --git a/bin/node-template/runtime/Cargo.toml b/bin/node-template/runtime/Cargo.toml index 1a3c5bd842..5dc7b03d39 100644 --- a/bin/node-template/runtime/Cargo.toml +++ b/bin/node-template/runtime/Cargo.toml @@ -51,7 +51,7 @@ frame-system-benchmarking = { version = "4.0.0-dev", default-features = false, p pallet-template = { version = "4.0.0-dev", default-features = false, path = "../pallets/template" } [build-dependencies] -substrate-wasm-builder = { version = "5.0.0-dev", path = "../../../utils/wasm-builder" } +substrate-wasm-builder = { version = "5.0.0-dev", path = "../../../utils/wasm-builder", optional = true } [features] default = ["std"] @@ -86,6 +86,7 @@ std = [ "sp-std/std", "sp-transaction-pool/std", "sp-version/std", + "substrate-wasm-builder", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", diff --git a/bin/node-template/runtime/build.rs b/bin/node-template/runtime/build.rs index 9b53d2457d..c03d618535 100644 --- a/bin/node-template/runtime/build.rs +++ b/bin/node-template/runtime/build.rs @@ -1,9 +1,10 @@ -use substrate_wasm_builder::WasmBuilder; - fn main() { - WasmBuilder::new() - .with_current_project() - .export_heap_base() - .import_memory() - .build() + #[cfg(feature = "std")] + { + substrate_wasm_builder::WasmBuilder::new() + .with_current_project() + .export_heap_base() + .import_memory() + .build(); + } } diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 201e3a85f8..7037be9d7c 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -113,7 +113,7 @@ pallet-vesting = { version = "4.0.0-dev", default-features = false, path = "../. pallet-whitelist = { version = "4.0.0-dev", default-features = false, path = "../../../frame/whitelist" } [build-dependencies] -substrate-wasm-builder = { version = "5.0.0-dev", path = "../../../utils/wasm-builder" } +substrate-wasm-builder = { version = "5.0.0-dev", path = "../../../utils/wasm-builder", optional = true } [features] default = ["std"] @@ -205,6 +205,7 @@ std = [ "sp-io/std", "pallet-child-bounties/std", "pallet-alliance/std", + "substrate-wasm-builder", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", diff --git a/bin/node/runtime/build.rs b/bin/node/runtime/build.rs index b773ed9cf6..70f84bc00a 100644 --- a/bin/node/runtime/build.rs +++ b/bin/node/runtime/build.rs @@ -15,12 +15,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -use substrate_wasm_builder::WasmBuilder; - fn main() { - WasmBuilder::new() - .with_current_project() - .export_heap_base() - .import_memory() - .build() + #[cfg(feature = "std")] + { + substrate_wasm_builder::WasmBuilder::new() + .with_current_project() + .export_heap_base() + .import_memory() + .build(); + } } diff --git a/client/executor/runtime-test/Cargo.toml b/client/executor/runtime-test/Cargo.toml index eadf547823..e99f3caa94 100644 --- a/client/executor/runtime-test/Cargo.toml +++ b/client/executor/runtime-test/Cargo.toml @@ -19,7 +19,7 @@ sp-runtime = { version = "7.0.0", default-features = false, path = "../../../pri sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } [build-dependencies] -substrate-wasm-builder = { version = "5.0.0-dev", path = "../../../utils/wasm-builder" } +substrate-wasm-builder = { version = "5.0.0-dev", path = "../../../utils/wasm-builder", optional = true } [features] default = ["std"] @@ -28,4 +28,5 @@ std = [ "sp-io/std", "sp-runtime/std", "sp-std/std", + "substrate-wasm-builder", ] diff --git a/client/executor/runtime-test/build.rs b/client/executor/runtime-test/build.rs index 27f931a542..d3aceedf9e 100644 --- a/client/executor/runtime-test/build.rs +++ b/client/executor/runtime-test/build.rs @@ -16,22 +16,26 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use substrate_wasm_builder::WasmBuilder; - fn main() { // regular build - WasmBuilder::new() - .with_current_project() - .export_heap_base() - .import_memory() - .build(); + #[cfg(feature = "std")] + { + substrate_wasm_builder::WasmBuilder::new() + .with_current_project() + .export_heap_base() + .import_memory() + .build(); + } // and building with tracing activated - WasmBuilder::new() - .with_current_project() - .export_heap_base() - .import_memory() - .set_file_name("wasm_binary_with_tracing.rs") - .append_to_rust_flags(r#"--cfg feature="with-tracing""#) - .build(); + #[cfg(feature = "std")] + { + substrate_wasm_builder::WasmBuilder::new() + .with_current_project() + .export_heap_base() + .import_memory() + .set_file_name("wasm_binary_with_tracing.rs") + .append_to_rust_flags(r#"--cfg feature="with-tracing""#) + .build(); + } } diff --git a/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml b/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml index 32d78ec2ce..ec07481234 100644 --- a/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml +++ b/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml @@ -19,8 +19,14 @@ sp-runtime-interface = { version = "7.0.0", default-features = false, path = ".. sp-std = { version = "5.0.0", default-features = false, path = "../../std" } [build-dependencies] -substrate-wasm-builder = { version = "5.0.0-dev", path = "../../../utils/wasm-builder" } +substrate-wasm-builder = { version = "5.0.0-dev", path = "../../../utils/wasm-builder", optional = true } [features] default = [ "std" ] -std = [ "sp-core/std", "sp-io/std", "sp-runtime-interface/std", "sp-std/std" ] +std = [ + "sp-core/std", + "sp-io/std", + "sp-runtime-interface/std", + "sp-std/std", + "substrate-wasm-builder", +] diff --git a/primitives/runtime-interface/test-wasm-deprecated/build.rs b/primitives/runtime-interface/test-wasm-deprecated/build.rs index b773ed9cf6..70f84bc00a 100644 --- a/primitives/runtime-interface/test-wasm-deprecated/build.rs +++ b/primitives/runtime-interface/test-wasm-deprecated/build.rs @@ -15,12 +15,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -use substrate_wasm_builder::WasmBuilder; - fn main() { - WasmBuilder::new() - .with_current_project() - .export_heap_base() - .import_memory() - .build() + #[cfg(feature = "std")] + { + substrate_wasm_builder::WasmBuilder::new() + .with_current_project() + .export_heap_base() + .import_memory() + .build(); + } } diff --git a/primitives/runtime-interface/test-wasm/Cargo.toml b/primitives/runtime-interface/test-wasm/Cargo.toml index 5fb4850af8..1061a54d7f 100644 --- a/primitives/runtime-interface/test-wasm/Cargo.toml +++ b/primitives/runtime-interface/test-wasm/Cargo.toml @@ -20,8 +20,14 @@ sp-runtime-interface = { version = "7.0.0", default-features = false, path = ".. sp-std = { version = "5.0.0", default-features = false, path = "../../std" } [build-dependencies] -substrate-wasm-builder = { version = "5.0.0-dev", path = "../../../utils/wasm-builder" } +substrate-wasm-builder = { version = "5.0.0-dev", path = "../../../utils/wasm-builder", optional = true } [features] default = [ "std" ] -std = [ "sp-core/std", "sp-io/std", "sp-runtime-interface/std", "sp-std/std" ] +std = [ + "sp-core/std", + "sp-io/std", + "sp-runtime-interface/std", + "sp-std/std", + "substrate-wasm-builder", +] diff --git a/primitives/runtime-interface/test-wasm/build.rs b/primitives/runtime-interface/test-wasm/build.rs index b773ed9cf6..70f84bc00a 100644 --- a/primitives/runtime-interface/test-wasm/build.rs +++ b/primitives/runtime-interface/test-wasm/build.rs @@ -15,12 +15,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -use substrate_wasm_builder::WasmBuilder; - fn main() { - WasmBuilder::new() - .with_current_project() - .export_heap_base() - .import_memory() - .build() + #[cfg(feature = "std")] + { + substrate_wasm_builder::WasmBuilder::new() + .with_current_project() + .export_heap_base() + .import_memory() + .build(); + } } diff --git a/test-utils/runtime/Cargo.toml b/test-utils/runtime/Cargo.toml index 7576a63ac3..cff0227d03 100644 --- a/test-utils/runtime/Cargo.toml +++ b/test-utils/runtime/Cargo.toml @@ -59,7 +59,7 @@ substrate-test-runtime-client = { version = "2.0.0", path = "./client" } futures = "0.3.21" [build-dependencies] -substrate-wasm-builder = { version = "5.0.0-dev", path = "../../utils/wasm-builder" } +substrate-wasm-builder = { version = "5.0.0-dev", path = "../../utils/wasm-builder", optional = true } [features] default = [ @@ -101,6 +101,7 @@ std = [ "sp-trie/std", "sp-transaction-pool/std", "trie-db/std", + "substrate-wasm-builder", ] # Special feature to disable logging disable-logging = [ "sp-api/disable-logging" ] diff --git a/test-utils/runtime/build.rs b/test-utils/runtime/build.rs index 5a7b518d0b..870d8ac438 100644 --- a/test-utils/runtime/build.rs +++ b/test-utils/runtime/build.rs @@ -15,24 +15,28 @@ // See the License for the specific language governing permissions and // limitations under the License. -use substrate_wasm_builder::WasmBuilder; - fn main() { - WasmBuilder::new() - .with_current_project() - .export_heap_base() - // Note that we set the stack-size to 1MB explicitly even though it is set - // to this value by default. This is because some of our tests (`restoration_of_globals`) - // depend on the stack-size. - .append_to_rust_flags("-Clink-arg=-zstack-size=1048576") - .import_memory() - .build(); + #[cfg(feature = "std")] + { + substrate_wasm_builder::WasmBuilder::new() + .with_current_project() + .export_heap_base() + // Note that we set the stack-size to 1MB explicitly even though it is set + // to this value by default. This is because some of our tests + // (`restoration_of_globals`) depend on the stack-size. + .append_to_rust_flags("-Clink-arg=-zstack-size=1048576") + .import_memory() + .build(); + } - WasmBuilder::new() - .with_current_project() - .export_heap_base() - .import_memory() - .set_file_name("wasm_binary_logging_disabled.rs") - .enable_feature("disable-logging") - .build(); + #[cfg(feature = "std")] + { + substrate_wasm_builder::WasmBuilder::new() + .with_current_project() + .export_heap_base() + .import_memory() + .set_file_name("wasm_binary_logging_disabled.rs") + .enable_feature("disable-logging") + .build(); + } } diff --git a/utils/wasm-builder/README.md b/utils/wasm-builder/README.md index 547468c7ca..a10611cc26 100644 --- a/utils/wasm-builder/README.md +++ b/utils/wasm-builder/README.md @@ -8,23 +8,24 @@ The Wasm builder is a tool that integrates the process of building the WASM bina A project that should be compiled as a Wasm binary needs to: 1. Add a `build.rs` file. -2. Add `wasm-builder` as dependency into `build-dependencies`. +2. Add `wasm-builder` as dependency into `build-dependencies` (can be made optional and only enabled when `std` feature is used). The `build.rs` file needs to contain the following code: ```rust -use substrate_wasm_builder::WasmBuilder; - fn main() { - WasmBuilder::new() - // Tell the builder to build the project (crate) this `build.rs` is part of. - .with_current_project() - // Make sure to export the `heap_base` global, this is required by Substrate - .export_heap_base() - // Build the Wasm file so that it imports the memory (need to be provided by at instantiation) - .import_memory() - // Build it. - .build() + #[cfg(feature = "std")] + { + substrate_wasm_builder::WasmBuilder::new() + // Tell the builder to build the project (crate) this `build.rs` is part of. + .with_current_project() + // Make sure to export the `heap_base` global, this is required by Substrate + .export_heap_base() + // Build the Wasm file so that it imports the memory (need to be provided by at instantiation) + .import_memory() + // Build it. + .build(); + } } ``` From 4692572ecf8de0f9420972f8bf2f072243cf5a94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= <123550+andresilva@users.noreply.github.com> Date: Fri, 6 Jan 2023 11:52:38 +0000 Subject: [PATCH 111/127] grandpa: bump version to v0.16.1 (#13080) --- Cargo.lock | 4 ++-- client/finality-grandpa/Cargo.toml | 4 ++-- client/finality-grandpa/rpc/Cargo.toml | 2 +- frame/grandpa/Cargo.toml | 2 +- primitives/finality-grandpa/Cargo.toml | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a3dc04a397..eb644202ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2028,9 +2028,9 @@ dependencies = [ [[package]] name = "finality-grandpa" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b22349c6a11563a202d95772a68e0fcf56119e74ea8a2a19cf2301460fcd0df5" +checksum = "e24e6c429951433ccb7c87fd528c60084834dcd14763182c1f83291bcde24c34" dependencies = [ "either", "futures", diff --git a/client/finality-grandpa/Cargo.toml b/client/finality-grandpa/Cargo.toml index b47c803248..95195bfc2d 100644 --- a/client/finality-grandpa/Cargo.toml +++ b/client/finality-grandpa/Cargo.toml @@ -18,7 +18,7 @@ ahash = "0.7.6" array-bytes = "4.1" async-trait = "0.1.57" dyn-clone = "1.0" -finality-grandpa = { version = "0.16.0", features = ["derive-codec"] } +finality-grandpa = { version = "0.16.1", features = ["derive-codec"] } futures = "0.3.21" futures-timer = "3.0.1" log = "0.4.17" @@ -50,7 +50,7 @@ sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } [dev-dependencies] assert_matches = "1.3.0" -finality-grandpa = { version = "0.16.0", features = ["derive-codec", "test-helpers"] } +finality-grandpa = { version = "0.16.1", features = ["derive-codec", "test-helpers"] } serde = "1.0.136" tokio = "1.22.0" sc-network = { version = "0.10.0-dev", path = "../network" } diff --git a/client/finality-grandpa/rpc/Cargo.toml b/client/finality-grandpa/rpc/Cargo.toml index 15b9d3887b..429331e96a 100644 --- a/client/finality-grandpa/rpc/Cargo.toml +++ b/client/finality-grandpa/rpc/Cargo.toml @@ -10,7 +10,7 @@ readme = "README.md" homepage = "https://substrate.io" [dependencies] -finality-grandpa = { version = "0.16.0", features = ["derive-codec"] } +finality-grandpa = { version = "0.16.1", features = ["derive-codec"] } futures = "0.3.16" jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } log = "0.4.8" diff --git a/frame/grandpa/Cargo.toml b/frame/grandpa/Cargo.toml index 5a85a3682d..93dd4143af 100644 --- a/frame/grandpa/Cargo.toml +++ b/frame/grandpa/Cargo.toml @@ -31,7 +31,7 @@ sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../pr sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -grandpa = { package = "finality-grandpa", version = "0.16.0", features = ["derive-codec"] } +grandpa = { package = "finality-grandpa", version = "0.16.1", features = ["derive-codec"] } frame-benchmarking = { version = "4.0.0-dev", path = "../benchmarking" } frame-election-provider-support = { version = "4.0.0-dev", path = "../election-provider-support" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } diff --git a/primitives/finality-grandpa/Cargo.toml b/primitives/finality-grandpa/Cargo.toml index de8de99eed..c33646e3cd 100644 --- a/primitives/finality-grandpa/Cargo.toml +++ b/primitives/finality-grandpa/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -grandpa = { package = "finality-grandpa", version = "0.16.0", default-features = false, features = ["derive-codec"] } +grandpa = { package = "finality-grandpa", version = "0.16.1", default-features = false, features = ["derive-codec"] } log = { version = "0.4.17", default-features = false } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", features = ["derive"], optional = true } From 19da1116ee20a062a15d64cd0e05197eb417e358 Mon Sep 17 00:00:00 2001 From: Sebastian Kunert Date: Fri, 6 Jan 2023 21:09:57 +0100 Subject: [PATCH 112/127] Remove unused finalization_displaced variable in db (#13090) --- client/db/src/lib.rs | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/client/db/src/lib.rs b/client/db/src/lib.rs index c8495c4a02..99fa786e04 100644 --- a/client/db/src/lib.rs +++ b/client/db/src/lib.rs @@ -1291,14 +1291,13 @@ impl Backend { header: &Block::Header, last_finalized: Option, justification: Option, - finalization_displaced: &mut Option>>, ) -> ClientResult> { // TODO: ensure best chain contains this block. let number = *header.number(); self.ensure_sequential_finalization(header, last_finalized)?; let with_state = sc_client_api::Backend::have_state_at(self, hash, number); - self.note_finalized(transaction, header, hash, finalization_displaced, with_state)?; + self.note_finalized(transaction, header, hash, with_state)?; if let Some(justification) = justification { transaction.set_from_vec( @@ -1362,7 +1361,6 @@ impl Backend { fn try_commit_operation(&self, mut operation: BlockImportOperation) -> ClientResult<()> { let mut transaction = Transaction::new(); - let mut finalization_displaced_leaves = None; operation.apply_aux(&mut transaction); operation.apply_offchain(&mut transaction); @@ -1381,7 +1379,6 @@ impl Backend { &block_header, Some(last_finalized_hash), justification, - &mut finalization_displaced_leaves, )?); last_finalized_hash = block_hash; last_finalized_num = *block_header.number(); @@ -1554,13 +1551,7 @@ impl Backend { if finalized { // TODO: ensure best chain contains this block. self.ensure_sequential_finalization(header, Some(last_finalized_hash))?; - self.note_finalized( - &mut transaction, - header, - hash, - &mut finalization_displaced_leaves, - operation.commit_state, - )?; + self.note_finalized(&mut transaction, header, hash, operation.commit_state)?; } else { // canonicalize blocks which are old enough, regardless of finality. self.force_delayed_canonicalize(&mut transaction)? @@ -1692,7 +1683,6 @@ impl Backend { transaction: &mut Transaction, f_header: &Block::Header, f_hash: Block::Hash, - displaced: &mut Option>>, with_state: bool, ) -> ClientResult<()> { let f_num = *f_header.number(); @@ -1720,10 +1710,6 @@ impl Backend { let new_displaced = self.blockchain.leaves.write().finalize_height(f_num); self.prune_blocks(transaction, f_num, &new_displaced)?; - match displaced { - x @ &mut None => *x = Some(new_displaced), - &mut Some(ref mut displaced) => displaced.merge(new_displaced), - } Ok(()) } @@ -2013,7 +1999,6 @@ impl sc_client_api::backend::Backend for Backend { ) -> ClientResult<()> { let mut transaction = Transaction::new(); let header = self.blockchain.expect_header(hash)?; - let mut displaced = None; let m = self.finalize_block_with_transaction( &mut transaction, @@ -2021,8 +2006,8 @@ impl sc_client_api::backend::Backend for Backend { &header, None, justification, - &mut displaced, )?; + self.storage.db.commit(transaction)?; self.blockchain.update_meta(m); Ok(()) From e7814a35cbe506ba153fda24ebd068f4b5617854 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Sat, 7 Jan 2023 14:42:44 +0100 Subject: [PATCH 113/127] Remove unused types (#13091) Signed-off-by: Oliver Tale-Yazdi Signed-off-by: Oliver Tale-Yazdi --- frame/atomic-swap/src/tests.rs | 9 +-------- frame/aura/src/mock.rs | 5 ----- frame/authority-discovery/src/lib.rs | 2 -- frame/authorship/src/lib.rs | 6 ------ frame/babe/src/mock.rs | 5 ----- frame/collective/src/tests.rs | 2 -- frame/conviction-voting/src/tests.rs | 5 ----- frame/examples/basic/src/tests.rs | 5 ----- frame/examples/offchain-worker/src/tests.rs | 4 ---- frame/grandpa/src/mock.rs | 5 ----- frame/identity/src/tests.rs | 4 ---- frame/im-online/src/mock.rs | 5 ----- frame/indices/src/mock.rs | 10 +--------- frame/membership/src/lib.rs | 2 -- frame/message-queue/src/integration_test.rs | 4 ---- frame/message-queue/src/mock.rs | 4 ---- frame/multisig/src/tests.rs | 6 +----- frame/nicks/src/lib.rs | 6 +----- frame/offences/benchmarking/src/mock.rs | 8 -------- frame/offences/src/mock.rs | 11 +---------- frame/preimage/src/mock.rs | 14 ++------------ frame/proxy/src/tests.rs | 5 ----- frame/randomness-collective-flip/src/lib.rs | 2 -- frame/ranked-collective/src/tests.rs | 5 ----- frame/recovery/src/mock.rs | 5 ----- frame/referenda/src/mock.rs | 2 -- frame/root-offences/src/mock.rs | 5 ----- frame/scored-pool/src/mock.rs | 2 -- frame/session/src/mock.rs | 5 ----- frame/society/src/mock.rs | 2 -- frame/staking/src/mock.rs | 4 ---- frame/sudo/src/mock.rs | 11 +---------- frame/system/benches/bench.rs | 4 ---- frame/timestamp/src/mock.rs | 4 ---- frame/treasury/src/tests.rs | 4 ---- frame/vesting/src/mock.rs | 4 ---- frame/whitelist/src/mock.rs | 6 +----- 37 files changed, 9 insertions(+), 183 deletions(-) diff --git a/frame/atomic-swap/src/tests.rs b/frame/atomic-swap/src/tests.rs index 5ad63dfce5..081a449ec9 100644 --- a/frame/atomic-swap/src/tests.rs +++ b/frame/atomic-swap/src/tests.rs @@ -3,10 +3,7 @@ use super::*; use crate as pallet_atomic_swap; -use frame_support::{ - parameter_types, - traits::{ConstU32, ConstU64}, -}; +use frame_support::traits::{ConstU32, ConstU64}; use sp_core::H256; use sp_runtime::{ testing::Header, @@ -28,10 +25,6 @@ frame_support::construct_runtime!( } ); -parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); -} impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); diff --git a/frame/aura/src/mock.rs b/frame/aura/src/mock.rs index 07fa9aa680..7a07fe99ae 100644 --- a/frame/aura/src/mock.rs +++ b/frame/aura/src/mock.rs @@ -46,11 +46,6 @@ frame_support::construct_runtime!( } ); -parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); -} - impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); diff --git a/frame/authority-discovery/src/lib.rs b/frame/authority-discovery/src/lib.rs index b642a9ac28..39e01dba43 100644 --- a/frame/authority-discovery/src/lib.rs +++ b/frame/authority-discovery/src/lib.rs @@ -226,8 +226,6 @@ mod tests { parameter_types! { pub const Period: BlockNumber = 1; pub const Offset: BlockNumber = 0; - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); } impl frame_system::Config for Test { diff --git a/frame/authorship/src/lib.rs b/frame/authorship/src/lib.rs index a40f32d36c..4065472d92 100644 --- a/frame/authorship/src/lib.rs +++ b/frame/authorship/src/lib.rs @@ -434,7 +434,6 @@ mod tests { use super::*; use crate as pallet_authorship; use frame_support::{ - parameter_types, traits::{ConstU32, ConstU64, OnFinalize, OnInitialize}, ConsensusEngineId, }; @@ -459,11 +458,6 @@ mod tests { } ); - parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); - } - impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); diff --git a/frame/babe/src/mock.rs b/frame/babe/src/mock.rs index dbeb588c18..abae1b7e33 100644 --- a/frame/babe/src/mock.rs +++ b/frame/babe/src/mock.rs @@ -64,11 +64,6 @@ frame_support::construct_runtime!( } ); -parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); -} - impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); diff --git a/frame/collective/src/tests.rs b/frame/collective/src/tests.rs index 648b6f88ec..5c90de9f91 100644 --- a/frame/collective/src/tests.rs +++ b/frame/collective/src/tests.rs @@ -91,8 +91,6 @@ pub type MaxMembers = ConstU32<100>; parameter_types! { pub const MotionDuration: u64 = 3; pub const MaxProposals: u32 = 257; - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); } impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; diff --git a/frame/conviction-voting/src/tests.rs b/frame/conviction-voting/src/tests.rs index d20dcb66d6..9caa451e0d 100644 --- a/frame/conviction-voting/src/tests.rs +++ b/frame/conviction-voting/src/tests.rs @@ -22,7 +22,6 @@ use std::collections::BTreeMap; use frame_support::{ assert_noop, assert_ok, parameter_types, traits::{ConstU32, ConstU64, Contains, Polling, VoteTally}, - weights::Weight, }; use sp_core::H256; use sp_runtime::{ @@ -56,10 +55,6 @@ impl Contains for BaseFilter { } } -parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(Weight::from_ref_time(1_000_000)); -} impl frame_system::Config for Test { type BaseCallFilter = BaseFilter; type BlockWeights = (); diff --git a/frame/examples/basic/src/tests.rs b/frame/examples/basic/src/tests.rs index 97fbddfbc4..891c7c7862 100644 --- a/frame/examples/basic/src/tests.rs +++ b/frame/examples/basic/src/tests.rs @@ -21,7 +21,6 @@ use crate::*; use frame_support::{ assert_ok, dispatch::{DispatchInfo, GetDispatchInfo}, - parameter_types, traits::{ConstU64, OnInitialize}, }; use sp_core::H256; @@ -51,10 +50,6 @@ frame_support::construct_runtime!( } ); -parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); -} impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); diff --git a/frame/examples/offchain-worker/src/tests.rs b/frame/examples/offchain-worker/src/tests.rs index 72c001fd6e..2050d76a24 100644 --- a/frame/examples/offchain-worker/src/tests.rs +++ b/frame/examples/offchain-worker/src/tests.rs @@ -51,10 +51,6 @@ frame_support::construct_runtime!( } ); -parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); -} impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); diff --git a/frame/grandpa/src/mock.rs b/frame/grandpa/src/mock.rs index 131f6cafcd..c618705af0 100644 --- a/frame/grandpa/src/mock.rs +++ b/frame/grandpa/src/mock.rs @@ -69,11 +69,6 @@ impl_opaque_keys! { } } -parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); -} - impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); diff --git a/frame/identity/src/tests.rs b/frame/identity/src/tests.rs index 6b0006971f..baca70e279 100644 --- a/frame/identity/src/tests.rs +++ b/frame/identity/src/tests.rs @@ -48,10 +48,6 @@ frame_support::construct_runtime!( } ); -parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); -} impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); diff --git a/frame/im-online/src/mock.rs b/frame/im-online/src/mock.rs index 5782e1a615..bcb13fa1be 100644 --- a/frame/im-online/src/mock.rs +++ b/frame/im-online/src/mock.rs @@ -117,11 +117,6 @@ pub fn new_test_ext() -> sp_io::TestExternalities { result } -parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); -} - impl frame_system::Config for Runtime { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); diff --git a/frame/indices/src/mock.rs b/frame/indices/src/mock.rs index fd2e9fff16..6b5373bb43 100644 --- a/frame/indices/src/mock.rs +++ b/frame/indices/src/mock.rs @@ -20,10 +20,7 @@ #![cfg(test)] use crate::{self as pallet_indices, Config}; -use frame_support::{ - parameter_types, - traits::{ConstU32, ConstU64}, -}; +use frame_support::traits::{ConstU32, ConstU64}; use sp_core::H256; use sp_runtime::testing::Header; @@ -42,11 +39,6 @@ frame_support::construct_runtime!( } ); -parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); -} - impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); diff --git a/frame/membership/src/lib.rs b/frame/membership/src/lib.rs index 77b53aa72d..8531af120e 100644 --- a/frame/membership/src/lib.rs +++ b/frame/membership/src/lib.rs @@ -539,8 +539,6 @@ mod tests { ); parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); pub static Members: Vec = vec![]; pub static Prime: Option = None; } diff --git a/frame/message-queue/src/integration_test.rs b/frame/message-queue/src/integration_test.rs index f4b1b7a125..c9e3b2e870 100644 --- a/frame/message-queue/src/integration_test.rs +++ b/frame/message-queue/src/integration_test.rs @@ -54,10 +54,6 @@ frame_support::construct_runtime!( } ); -parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); -} impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); diff --git a/frame/message-queue/src/mock.rs b/frame/message-queue/src/mock.rs index 7159840d1c..37e504ac50 100644 --- a/frame/message-queue/src/mock.rs +++ b/frame/message-queue/src/mock.rs @@ -47,10 +47,6 @@ frame_support::construct_runtime!( MessageQueue: pallet_message_queue::{Pallet, Call, Storage, Event}, } ); -parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); -} impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); diff --git a/frame/multisig/src/tests.rs b/frame/multisig/src/tests.rs index 206e566cf4..13493587ca 100644 --- a/frame/multisig/src/tests.rs +++ b/frame/multisig/src/tests.rs @@ -23,7 +23,7 @@ use super::*; use crate as pallet_multisig; use frame_support::{ - assert_noop, assert_ok, parameter_types, + assert_noop, assert_ok, traits::{ConstU32, ConstU64, Contains}, }; use sp_core::H256; @@ -47,10 +47,6 @@ frame_support::construct_runtime!( } ); -parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); -} impl frame_system::Config for Test { type BaseCallFilter = TestBaseCallFilter; type BlockWeights = (); diff --git a/frame/nicks/src/lib.rs b/frame/nicks/src/lib.rs index 79daeb9bdb..3d8307c434 100644 --- a/frame/nicks/src/lib.rs +++ b/frame/nicks/src/lib.rs @@ -250,7 +250,7 @@ mod tests { use crate as pallet_nicks; use frame_support::{ - assert_noop, assert_ok, ord_parameter_types, parameter_types, + assert_noop, assert_ok, ord_parameter_types, traits::{ConstU32, ConstU64}, }; use frame_system::EnsureSignedBy; @@ -275,10 +275,6 @@ mod tests { } ); - parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); - } impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); diff --git a/frame/offences/benchmarking/src/mock.rs b/frame/offences/benchmarking/src/mock.rs index 223f551a6e..592e821a81 100644 --- a/frame/offences/benchmarking/src/mock.rs +++ b/frame/offences/benchmarking/src/mock.rs @@ -24,7 +24,6 @@ use frame_election_provider_support::{onchain, SequentialPhragmen}; use frame_support::{ parameter_types, traits::{ConstU32, ConstU64}, - weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}, }; use frame_system as system; use pallet_session::historical as pallet_session_historical; @@ -38,13 +37,6 @@ type AccountIndex = u32; type BlockNumber = u64; type Balance = u64; -parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max( - Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX) - ); -} - impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); diff --git a/frame/offences/src/mock.rs b/frame/offences/src/mock.rs index 8e4256ec3d..63d2b0de34 100644 --- a/frame/offences/src/mock.rs +++ b/frame/offences/src/mock.rs @@ -25,10 +25,7 @@ use codec::Encode; use frame_support::{ parameter_types, traits::{ConstU32, ConstU64}, - weights::{ - constants::{RocksDbWeight, WEIGHT_REF_TIME_PER_SECOND}, - Weight, - }, + weights::{constants::RocksDbWeight, Weight}, }; use sp_core::H256; use sp_runtime::{ @@ -83,12 +80,6 @@ frame_support::construct_runtime!( } ); -parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max( - Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX), - ); -} impl frame_system::Config for Runtime { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); diff --git a/frame/preimage/src/mock.rs b/frame/preimage/src/mock.rs index ce74ea65bd..d81f5a9c32 100644 --- a/frame/preimage/src/mock.rs +++ b/frame/preimage/src/mock.rs @@ -15,13 +15,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! # Scheduler test environment. +//! # Preimage test environment. use super::*; use crate as pallet_preimage; use frame_support::{ - ord_parameter_types, parameter_types, + ord_parameter_types, traits::{ConstU32, ConstU64, Everything}, weights::constants::RocksDbWeight, }; @@ -30,7 +30,6 @@ use sp_core::H256; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup}, - Perbill, }; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; @@ -48,10 +47,6 @@ frame_support::construct_runtime!( } ); -parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(Weight::from_ref_time(2_000_000_000_000)); -} impl frame_system::Config for Test { type BaseCallFilter = Everything; type BlockWeights = (); @@ -91,11 +86,6 @@ impl pallet_balances::Config for Test { type ReserveIdentifier = [u8; 8]; } -parameter_types! { - pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * BlockWeights::get().max_block; - pub const MaxScheduledPerBlock: u32 = 10; -} - ord_parameter_types! { pub const One: u64 = 1; } diff --git a/frame/proxy/src/tests.rs b/frame/proxy/src/tests.rs index 17bc7bcb92..3eb3ab3705 100644 --- a/frame/proxy/src/tests.rs +++ b/frame/proxy/src/tests.rs @@ -26,7 +26,6 @@ use codec::{Decode, Encode}; use frame_support::{ assert_noop, assert_ok, dispatch::DispatchError, - parameter_types, traits::{ConstU32, ConstU64, Contains}, RuntimeDebug, }; @@ -52,10 +51,6 @@ frame_support::construct_runtime!( } ); -parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); -} impl frame_system::Config for Test { type BaseCallFilter = BaseFilter; type BlockWeights = (); diff --git a/frame/randomness-collective-flip/src/lib.rs b/frame/randomness-collective-flip/src/lib.rs index be970ba2a8..d92b053655 100644 --- a/frame/randomness-collective-flip/src/lib.rs +++ b/frame/randomness-collective-flip/src/lib.rs @@ -186,8 +186,6 @@ mod tests { ); parameter_types! { - pub BlockWeights: limits::BlockWeights = limits::BlockWeights - ::simple_max(Weight::from_ref_time(1024)); pub BlockLength: limits::BlockLength = limits::BlockLength ::max(2 * 1024); } diff --git a/frame/ranked-collective/src/tests.rs b/frame/ranked-collective/src/tests.rs index 68bb79f3d0..c11d4877d3 100644 --- a/frame/ranked-collective/src/tests.rs +++ b/frame/ranked-collective/src/tests.rs @@ -22,7 +22,6 @@ use std::collections::BTreeMap; use frame_support::{ assert_noop, assert_ok, error::BadOrigin, - pallet_prelude::Weight, parameter_types, traits::{ConstU16, ConstU32, ConstU64, EitherOf, Everything, MapSuccess, Polling}, }; @@ -49,10 +48,6 @@ frame_support::construct_runtime!( } ); -parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(Weight::from_ref_time(1_000_000)); -} impl frame_system::Config for Test { type BaseCallFilter = Everything; type BlockWeights = (); diff --git a/frame/recovery/src/mock.rs b/frame/recovery/src/mock.rs index 2a29390fdd..05ca8e6a30 100644 --- a/frame/recovery/src/mock.rs +++ b/frame/recovery/src/mock.rs @@ -45,11 +45,6 @@ frame_support::construct_runtime!( } ); -parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); -} - impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); diff --git a/frame/referenda/src/mock.rs b/frame/referenda/src/mock.rs index c98fbf9a67..e957027e56 100644 --- a/frame/referenda/src/mock.rs +++ b/frame/referenda/src/mock.rs @@ -63,8 +63,6 @@ impl Contains for BaseFilter { parameter_types! { pub MaxWeight: Weight = Weight::from_ref_time(2_000_000_000_000); - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(MaxWeight::get()); } impl frame_system::Config for Test { type BaseCallFilter = BaseFilter; diff --git a/frame/root-offences/src/mock.rs b/frame/root-offences/src/mock.rs index e1d9ee14ee..273fbf6141 100644 --- a/frame/root-offences/src/mock.rs +++ b/frame/root-offences/src/mock.rs @@ -84,11 +84,6 @@ impl sp_runtime::BoundToRuntimeAppPublic for OtherSessionHandler { type Public = UintAuthorityId; } -parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); -} - impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); diff --git a/frame/scored-pool/src/mock.rs b/frame/scored-pool/src/mock.rs index d6f653b32a..951871dd83 100644 --- a/frame/scored-pool/src/mock.rs +++ b/frame/scored-pool/src/mock.rs @@ -48,8 +48,6 @@ construct_runtime!( parameter_types! { pub const CandidateDeposit: u64 = 25; - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); } ord_parameter_types! { pub const KickOrigin: u64 = 2; diff --git a/frame/session/src/mock.rs b/frame/session/src/mock.rs index aa13eacba9..1138bd9543 100644 --- a/frame/session/src/mock.rs +++ b/frame/session/src/mock.rs @@ -238,11 +238,6 @@ pub fn new_test_ext() -> sp_io::TestExternalities { sp_io::TestExternalities::new(t) } -parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); -} - impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); diff --git a/frame/society/src/mock.rs b/frame/society/src/mock.rs index 0b1b93aeae..24544bf9e8 100644 --- a/frame/society/src/mock.rs +++ b/frame/society/src/mock.rs @@ -49,8 +49,6 @@ frame_support::construct_runtime!( parameter_types! { pub const SocietyPalletId: PalletId = PalletId(*b"py/socie"); - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); } ord_parameter_types! { diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index 843e452125..ad27a82910 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -114,10 +114,6 @@ impl FindAuthor for Author11 { } parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max( - Weight::from_parts(frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND * 2, u64::MAX), - ); pub static SessionsPerEra: SessionIndex = 3; pub static ExistentialDeposit: Balance = 1; pub static SlashDeferDuration: EraIndex = 0; diff --git a/frame/sudo/src/mock.rs b/frame/sudo/src/mock.rs index 639e81ceaa..f8a03836f8 100644 --- a/frame/sudo/src/mock.rs +++ b/frame/sudo/src/mock.rs @@ -19,12 +19,7 @@ use super::*; use crate as sudo; -use frame_support::{ - parameter_types, - traits::{ConstU32, ConstU64, Contains, GenesisBuild}, - weights::Weight, -}; -use frame_system::limits; +use frame_support::traits::{ConstU32, ConstU64, Contains, GenesisBuild}; use sp_core::H256; use sp_io; use sp_runtime::{ @@ -111,10 +106,6 @@ frame_support::construct_runtime!( } ); -parameter_types! { - pub BlockWeights: limits::BlockWeights = limits::BlockWeights::simple_max(Weight::from_ref_time(1024)); -} - pub struct BlockEverything; impl Contains for BlockEverything { fn contains(_: &RuntimeCall) -> bool { diff --git a/frame/system/benches/bench.rs b/frame/system/benches/bench.rs index 0881b81eac..a623a17dc4 100644 --- a/frame/system/benches/bench.rs +++ b/frame/system/benches/bench.rs @@ -62,10 +62,6 @@ frame_support::construct_runtime!( ); frame_support::parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::with_sensible_defaults( - Weight::from_ref_time(4 * 1024 * 1024), Perbill::from_percent(75), - ); pub BlockLength: frame_system::limits::BlockLength = frame_system::limits::BlockLength::max_with_normal_ratio( 4 * 1024 * 1024, Perbill::from_percent(75), diff --git a/frame/timestamp/src/mock.rs b/frame/timestamp/src/mock.rs index 2208510f24..e1ba5cd11e 100644 --- a/frame/timestamp/src/mock.rs +++ b/frame/timestamp/src/mock.rs @@ -46,10 +46,6 @@ frame_support::construct_runtime!( } ); -parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); -} impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); diff --git a/frame/treasury/src/tests.rs b/frame/treasury/src/tests.rs index 9cfe147ec4..e6a1ea0f54 100644 --- a/frame/treasury/src/tests.rs +++ b/frame/treasury/src/tests.rs @@ -51,10 +51,6 @@ frame_support::construct_runtime!( } ); -parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); -} impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); diff --git a/frame/vesting/src/mock.rs b/frame/vesting/src/mock.rs index 0bd371a035..da9490bea6 100644 --- a/frame/vesting/src/mock.rs +++ b/frame/vesting/src/mock.rs @@ -43,10 +43,6 @@ frame_support::construct_runtime!( } ); -parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); -} impl frame_system::Config for Test { type AccountData = pallet_balances::AccountData; type AccountId = u64; diff --git a/frame/whitelist/src/mock.rs b/frame/whitelist/src/mock.rs index ef5d3d4fb4..e08c2875ae 100644 --- a/frame/whitelist/src/mock.rs +++ b/frame/whitelist/src/mock.rs @@ -22,7 +22,7 @@ use crate as pallet_whitelist; use frame_support::{ - construct_runtime, parameter_types, + construct_runtime, traits::{ConstU32, ConstU64, Nothing}, }; use frame_system::EnsureRoot; @@ -49,10 +49,6 @@ construct_runtime!( } ); -parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); -} impl frame_system::Config for Test { type BaseCallFilter = Nothing; type BlockWeights = (); From 5cbd12605e2ef0882e9cd4de5149619f55954bd4 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Sun, 8 Jan 2023 18:03:51 +0100 Subject: [PATCH 114/127] Run `frame_system` integrity tests in Externalities (#13092) * Run frame_system integrity tests in Externalities Signed-off-by: Oliver Tale-Yazdi * Use feature = 'std' Signed-off-by: Oliver Tale-Yazdi Signed-off-by: Oliver Tale-Yazdi --- frame/system/src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frame/system/src/lib.rs b/frame/system/src/lib.rs index a77d1ca31b..c790d33add 100644 --- a/frame/system/src/lib.rs +++ b/frame/system/src/lib.rs @@ -360,8 +360,11 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { + #[cfg(feature = "std")] fn integrity_test() { - T::BlockWeights::get().validate().expect("The weights are invalid."); + sp_io::TestExternalities::default().execute_with(|| { + T::BlockWeights::get().validate().expect("The weights are invalid."); + }); } } From 911f65be7949342e12601cc3015d5c47e9339de4 Mon Sep 17 00:00:00 2001 From: Ankan <10196091+Ank4n@users.noreply.github.com> Date: Mon, 9 Jan 2023 08:33:29 +0100 Subject: [PATCH 115/127] add note on usage of twox hash (#13089) --- frame/fast-unstake/src/lib.rs | 2 ++ frame/nomination-pools/src/lib.rs | 2 ++ frame/staking/src/pallet/mod.rs | 8 ++++++++ 3 files changed, 12 insertions(+) diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index f2faeebc13..c0bffc4427 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -147,6 +147,8 @@ pub mod pallet { /// The map of all accounts wishing to be unstaked. /// /// Keeps track of `AccountId` wishing to unstake and it's corresponding deposit. + /// + /// TWOX-NOTE: SAFE since `AccountId` is a secure hash. #[pallet::storage] pub type Queue = CountedStorageMap<_, Twox64Concat, T::AccountId, BalanceOf>; diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 99bfc8b8c3..3cb8abedda 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1277,6 +1277,8 @@ pub mod pallet { pub type MaxPoolMembersPerPool = StorageValue<_, u32, OptionQuery>; /// Active members. + /// + /// TWOX-NOTE: SAFE since `AccountId` is a secure hash. #[pallet::storage] pub type PoolMembers = CountedStorageMap<_, Twox64Concat, T::AccountId, PoolMember>; diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 92502949ef..ce4f249462 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -292,6 +292,8 @@ pub mod pallet { pub type Invulnerables = StorageValue<_, Vec, ValueQuery>; /// Map from all locked "stash" accounts to the controller account. + /// + /// TWOX-NOTE: SAFE since `AccountId` is a secure hash. #[pallet::storage] #[pallet::getter(fn bonded)] pub type Bonded = StorageMap<_, Twox64Concat, T::AccountId, T::AccountId>; @@ -320,12 +322,16 @@ pub mod pallet { pub type Ledger = StorageMap<_, Blake2_128Concat, T::AccountId, StakingLedger>; /// Where the reward payment should be made. Keyed by stash. + /// + /// TWOX-NOTE: SAFE since `AccountId` is a secure hash. #[pallet::storage] #[pallet::getter(fn payee)] pub type Payee = StorageMap<_, Twox64Concat, T::AccountId, RewardDestination, ValueQuery>; /// The map from (wannabe) validator stash key to the preferences of that validator. + /// + /// TWOX-NOTE: SAFE since `AccountId` is a secure hash. #[pallet::storage] #[pallet::getter(fn validators)] pub type Validators = @@ -353,6 +359,8 @@ pub mod pallet { /// /// Lastly, if any of the nominators become non-decodable, they can be chilled immediately via /// [`Call::chill_other`] dispatchable by anyone. + /// + /// TWOX-NOTE: SAFE since `AccountId` is a secure hash. #[pallet::storage] #[pallet::getter(fn nominators)] pub type Nominators = From f31588b6e5e3f0dac4b0142002dbe18deb5bda59 Mon Sep 17 00:00:00 2001 From: Sebastian Kunert Date: Mon, 9 Jan 2023 14:46:09 +0100 Subject: [PATCH 116/127] Remove unused code in state-db (#13103) --- client/state-db/src/noncanonical.rs | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/client/state-db/src/noncanonical.rs b/client/state-db/src/noncanonical.rs index b7eec8ab60..e950d245dc 100644 --- a/client/state-db/src/noncanonical.rs +++ b/client/state-db/src/noncanonical.rs @@ -323,7 +323,6 @@ impl NonCanonicalOverlay { &self, level_index: usize, discarded_journals: &mut Vec>, - discarded_blocks: &mut Vec, hash: &BlockHash, ) { if let Some(level) = self.levels.get(level_index) { @@ -335,13 +334,7 @@ impl NonCanonicalOverlay { .clone(); if parent == *hash { discarded_journals.push(overlay.journal_key.clone()); - discarded_blocks.push(overlay.hash.clone()); - self.discard_journals( - level_index + 1, - discarded_journals, - discarded_blocks, - &overlay.hash, - ); + self.discard_journals(level_index + 1, discarded_journals, &overlay.hash); } }); } @@ -393,7 +386,6 @@ impl NonCanonicalOverlay { self.pinned_canonincalized.push(hash.clone()); let mut discarded_journals = Vec::new(); - let mut discarded_blocks = Vec::new(); for (i, overlay) in level.blocks.into_iter().enumerate() { let mut pinned_children = 0; // That's the one we need to canonicalize @@ -411,12 +403,7 @@ impl NonCanonicalOverlay { commit.data.deleted.extend(overlay.deleted.clone()); } else { // Discard this overlay - self.discard_journals( - 0, - &mut discarded_journals, - &mut discarded_blocks, - &overlay.hash, - ); + self.discard_journals(0, &mut discarded_journals, &overlay.hash); pinned_children = discard_descendants( &mut self.levels.as_mut_slices(), &mut self.values, @@ -437,7 +424,6 @@ impl NonCanonicalOverlay { discard_values(&mut self.values, overlay.inserted); } discarded_journals.push(overlay.journal_key.clone()); - discarded_blocks.push(overlay.hash.clone()); } commit.meta.deleted.append(&mut discarded_journals); @@ -548,9 +534,6 @@ impl NonCanonicalOverlay { trace!(target: "state-db-pin", "Discarding unpinned non-canon block: {:?}", hash); discard_values(&mut self.values, inserted); self.parents.remove(&hash); - true - } else { - false } }, Entry::Vacant(_) => break, From 6c7e94127c5f5f8f44b81536a6105d0e1a0d9276 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Mon, 9 Jan 2023 16:06:47 +0000 Subject: [PATCH 117/127] EPM and staking events improvement (#13035) * EPM and staking events improvement * Uses RawOrigin in ElectionCompute event * Refactors new phase events to PhaseTransition event * PhaseTransitioned and remove RawOrigin from event * Adds helpers for epm phase transition and staking force new * addresses review comments * nit: removes unecessary clone * fixes benchmarks Co-authored-by: parity-processbot <> --- .../src/benchmarking.rs | 7 +- .../election-provider-multi-phase/src/lib.rs | 281 +++++++++++++----- .../src/signed.rs | 128 ++++++-- .../src/unsigned.rs | 30 +- frame/staking/src/pallet/impls.rs | 11 +- frame/staking/src/pallet/mod.rs | 8 +- frame/staking/src/tests.rs | 28 +- 7 files changed, 369 insertions(+), 124 deletions(-) diff --git a/frame/election-provider-multi-phase/src/benchmarking.rs b/frame/election-provider-multi-phase/src/benchmarking.rs index 10041f6aec..16263d97da 100644 --- a/frame/election-provider-multi-phase/src/benchmarking.rs +++ b/frame/election-provider-multi-phase/src/benchmarking.rs @@ -200,7 +200,7 @@ frame_benchmarking::benchmarks! { assert!(>::snapshot().is_none()); assert!(>::current_phase().is_off()); }: { - >::on_initialize_open_signed(); + >::phase_transition(Phase::Signed); } verify { assert!(>::snapshot().is_none()); assert!(>::current_phase().is_signed()); @@ -210,7 +210,8 @@ frame_benchmarking::benchmarks! { assert!(>::snapshot().is_none()); assert!(>::current_phase().is_off()); }: { - >::on_initialize_open_unsigned(true, 1u32.into()) + let now = frame_system::Pallet::::block_number(); + >::phase_transition(Phase::Unsigned((true, now))); } verify { assert!(>::snapshot().is_none()); assert!(>::current_phase().is_unsigned()); @@ -318,7 +319,7 @@ frame_benchmarking::benchmarks! { submit { // the queue is full and the solution is only better than the worse. >::create_snapshot().map_err(<&str>::from)?; - MultiPhase::::on_initialize_open_signed(); + >::phase_transition(Phase::Signed); >::put(1); let mut signed_submissions = SignedSubmissions::::get(); diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 6c4a55800f..6c1b1d163d 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -758,7 +758,7 @@ pub mod pallet { // NOTE: if signed-phase length is zero, second part of the if-condition fails. match Self::create_snapshot() { Ok(_) => { - Self::on_initialize_open_signed(); + Self::phase_transition(Phase::Signed); T::WeightInfo::on_initialize_open_signed() }, Err(why) => { @@ -797,7 +797,7 @@ pub mod pallet { if need_snapshot { match Self::create_snapshot() { Ok(_) => { - Self::on_initialize_open_unsigned(enabled, now); + Self::phase_transition(Phase::Unsigned((enabled, now))); T::WeightInfo::on_initialize_open_unsigned() }, Err(why) => { @@ -806,7 +806,7 @@ pub mod pallet { }, } } else { - Self::on_initialize_open_unsigned(enabled, now); + Self::phase_transition(Phase::Unsigned((enabled, now))); T::WeightInfo::on_initialize_open_unsigned() } }, @@ -931,6 +931,7 @@ pub mod pallet { >::put(ready); Self::deposit_event(Event::SolutionStored { compute: ElectionCompute::Unsigned, + origin: None, prev_ejected: ejected_a_solution, }); @@ -983,6 +984,7 @@ pub mod pallet { Self::deposit_event(Event::SolutionStored { compute: ElectionCompute::Emergency, + origin: None, prev_ejected: QueuedSolution::::exists(), }); @@ -1060,6 +1062,7 @@ pub mod pallet { signed_submissions.put(); Self::deposit_event(Event::SolutionStored { compute: ElectionCompute::Signed, + origin: Some(who), prev_ejected: ejected_a_solution, }); Ok(()) @@ -1102,6 +1105,7 @@ pub mod pallet { Self::deposit_event(Event::SolutionStored { compute: ElectionCompute::Fallback, + origin: None, prev_ejected: QueuedSolution::::exists(), }); @@ -1115,11 +1119,16 @@ pub mod pallet { pub enum Event { /// A solution was stored with the given compute. /// - /// If the solution is signed, this means that it hasn't yet been processed. If the - /// solution is unsigned, this means that it has also been processed. - /// - /// The `bool` is `true` when a previous solution was ejected to make room for this one. - SolutionStored { compute: ElectionCompute, prev_ejected: bool }, + /// The `origin` indicates the origin of the solution. If `origin` is `Some(AccountId)`, + /// the stored solution was submited in the signed phase by a miner with the `AccountId`. + /// Otherwise, the solution was stored either during the unsigned phase or by + /// `T::ForceOrigin`. The `bool` is `true` when a previous solution was ejected to make + /// room for this one. + SolutionStored { + compute: ElectionCompute, + origin: Option, + prev_ejected: bool, + }, /// The election has been finalized, with the given computation and score. ElectionFinalized { compute: ElectionCompute, score: ElectionScore }, /// An election failed. @@ -1130,10 +1139,8 @@ pub mod pallet { Rewarded { account: ::AccountId, value: BalanceOf }, /// An account has been slashed for submitting an invalid signed submission. Slashed { account: ::AccountId, value: BalanceOf }, - /// The signed phase of the given round has started. - SignedPhaseStarted { round: u32 }, - /// The unsigned phase of the given round has started. - UnsignedPhaseStarted { round: u32 }, + /// There was a phase transition in a given round. + PhaseTransitioned { from: Phase, to: Phase, round: u32 }, } /// Error of the pallet that can be returned in response to dispatches. @@ -1349,19 +1356,15 @@ impl Pallet { } } - /// Logic for `::on_initialize` when signed phase is being opened. - pub fn on_initialize_open_signed() { - log!(info, "Starting signed phase round {}.", Self::round()); - >::put(Phase::Signed); - Self::deposit_event(Event::SignedPhaseStarted { round: Self::round() }); - } - - /// Logic for `>::on_initialize` when unsigned phase is being opened. - pub fn on_initialize_open_unsigned(enabled: bool, now: T::BlockNumber) { - let round = Self::round(); - log!(info, "Starting unsigned phase round {} enabled {}.", round, enabled); - >::put(Phase::Unsigned((enabled, now))); - Self::deposit_event(Event::UnsignedPhaseStarted { round }); + /// Phase transition helper. + pub(crate) fn phase_transition(to: Phase) { + log!(info, "Starting phase {:?}, round {}.", to, Self::round()); + Self::deposit_event(Event::PhaseTransitioned { + from: >::get(), + to, + round: Self::round(), + }); + >::put(to); } /// Parts of [`create_snapshot`] that happen inside of this pallet. @@ -1571,7 +1574,7 @@ impl Pallet { >::mutate(|r| *r += 1); // Phase is off now. - >::put(Phase::Off); + Self::phase_transition(Phase::Off); // Kill snapshots. Self::kill_snapshot(); @@ -1652,7 +1655,7 @@ impl ElectionProvider for Pallet { }, Err(why) => { log!(error, "Entering emergency mode: {:?}", why); - >::put(Phase::Emergency); + Self::phase_transition(Phase::Emergency); Err(why) }, } @@ -1898,7 +1901,10 @@ mod tests { roll_to_signed(); assert_eq!(MultiPhase::current_phase(), Phase::Signed); - assert_eq!(multi_phase_events(), vec![Event::SignedPhaseStarted { round: 1 }]); + assert_eq!( + multi_phase_events(), + vec![Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 }] + ); assert!(MultiPhase::snapshot().is_some()); assert_eq!(MultiPhase::round(), 1); @@ -1912,8 +1918,12 @@ mod tests { assert_eq!( multi_phase_events(), vec![ - Event::SignedPhaseStarted { round: 1 }, - Event::UnsignedPhaseStarted { round: 1 } + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 }, + Event::PhaseTransitioned { + from: Phase::Signed, + to: Phase::Unsigned((true, 25)), + round: 1 + }, ], ); assert!(MultiPhase::snapshot().is_some()); @@ -1949,8 +1959,12 @@ mod tests { assert_eq!( multi_phase_events(), vec![ - Event::SignedPhaseStarted { round: 1 }, - Event::UnsignedPhaseStarted { round: 1 }, + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 }, + Event::PhaseTransitioned { + from: Phase::Signed, + to: Phase::Unsigned((true, 25)), + round: 1 + }, Event::ElectionFinalized { compute: ElectionCompute::Fallback, score: ElectionScore { @@ -1959,8 +1973,17 @@ mod tests { sum_stake_squared: 0 } }, - Event::SignedPhaseStarted { round: 2 }, - Event::UnsignedPhaseStarted { round: 2 } + Event::PhaseTransitioned { + from: Phase::Unsigned((true, 25)), + to: Phase::Off, + round: 2 + }, + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 2 }, + Event::PhaseTransitioned { + from: Phase::Signed, + to: Phase::Unsigned((true, 55)), + round: 2 + }, ] ); }) @@ -1990,7 +2013,11 @@ mod tests { assert_eq!( multi_phase_events(), vec![ - Event::UnsignedPhaseStarted { round: 1 }, + Event::PhaseTransitioned { + from: Phase::Off, + to: Phase::Unsigned((true, 20)), + round: 1 + }, Event::ElectionFinalized { compute: ElectionCompute::Fallback, score: ElectionScore { @@ -1998,7 +2025,12 @@ mod tests { sum_stake: 0, sum_stake_squared: 0 } - } + }, + Event::PhaseTransitioned { + from: Phase::Unsigned((true, 20)), + to: Phase::Off, + round: 2 + }, ] ); }); @@ -2028,7 +2060,7 @@ mod tests { assert_eq!( multi_phase_events(), vec![ - Event::SignedPhaseStarted { round: 1 }, + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 }, Event::ElectionFinalized { compute: ElectionCompute::Fallback, score: ElectionScore { @@ -2036,7 +2068,8 @@ mod tests { sum_stake: 0, sum_stake_squared: 0 } - } + }, + Event::PhaseTransitioned { from: Phase::Signed, to: Phase::Off, round: 2 }, ] ) }); @@ -2064,10 +2097,17 @@ mod tests { assert_eq!( multi_phase_events(), - vec![Event::ElectionFinalized { - compute: ElectionCompute::Fallback, - score: ElectionScore { minimal_stake: 0, sum_stake: 0, sum_stake_squared: 0 } - }] + vec![ + Event::ElectionFinalized { + compute: ElectionCompute::Fallback, + score: ElectionScore { + minimal_stake: 0, + sum_stake: 0, + sum_stake_squared: 0 + } + }, + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Off, round: 2 }, + ] ); }); } @@ -2079,7 +2119,10 @@ mod tests { // Signed phase started at block 15 and will end at 25. roll_to_signed(); - assert_eq!(multi_phase_events(), vec![Event::SignedPhaseStarted { round: 1 }]); + assert_eq!( + multi_phase_events(), + vec![Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 }] + ); assert_eq!(MultiPhase::current_phase(), Phase::Signed); assert_eq!(MultiPhase::round(), 1); @@ -2090,11 +2133,12 @@ mod tests { assert_eq!( multi_phase_events(), vec![ - Event::SignedPhaseStarted { round: 1 }, + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 }, Event::ElectionFinalized { compute: ElectionCompute::Fallback, score: Default::default() - } + }, + Event::PhaseTransitioned { from: Phase::Signed, to: Phase::Off, round: 2 }, ], ); // All storage items must be cleared. @@ -2114,7 +2158,10 @@ mod tests { // signed phase started at block 15 and will end at 25. roll_to_signed(); - assert_eq!(multi_phase_events(), vec![Event::SignedPhaseStarted { round: 1 }]); + assert_eq!( + multi_phase_events(), + vec![Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 }] + ); assert_eq!(MultiPhase::current_phase(), Phase::Signed); assert_eq!(MultiPhase::round(), 1); @@ -2144,12 +2191,32 @@ mod tests { assert_eq!( multi_phase_events(), vec![ - Event::SignedPhaseStarted { round: 1 }, - Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, - Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, - Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, - Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, - Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 }, + Event::SolutionStored { + compute: ElectionCompute::Signed, + origin: Some(99), + prev_ejected: false + }, + Event::SolutionStored { + compute: ElectionCompute::Signed, + origin: Some(99), + prev_ejected: false + }, + Event::SolutionStored { + compute: ElectionCompute::Signed, + origin: Some(99), + prev_ejected: false + }, + Event::SolutionStored { + compute: ElectionCompute::Signed, + origin: Some(99), + prev_ejected: false + }, + Event::SolutionStored { + compute: ElectionCompute::Signed, + origin: Some(99), + prev_ejected: false + }, Event::Slashed { account: 99, value: 5 }, Event::Slashed { account: 99, value: 5 }, Event::Slashed { account: 99, value: 5 }, @@ -2162,7 +2229,8 @@ mod tests { sum_stake: 0, sum_stake_squared: 0 } - } + }, + Event::PhaseTransitioned { from: Phase::Signed, to: Phase::Off, round: 2 }, ] ); }) @@ -2186,10 +2254,18 @@ mod tests { assert_eq!( multi_phase_events(), vec![ - Event::SignedPhaseStarted { round: 1 }, - Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 }, + Event::SolutionStored { + compute: ElectionCompute::Signed, + origin: Some(99), + prev_ejected: false + }, Event::Rewarded { account: 99, value: 7 }, - Event::UnsignedPhaseStarted { round: 1 }, + Event::PhaseTransitioned { + from: Phase::Signed, + to: Phase::Unsigned((true, 25)), + round: 1 + }, Event::ElectionFinalized { compute: ElectionCompute::Signed, score: ElectionScore { @@ -2197,7 +2273,12 @@ mod tests { sum_stake: 100, sum_stake_squared: 5200 } - } + }, + Event::PhaseTransitioned { + from: Phase::Unsigned((true, 25)), + to: Phase::Off, + round: 2 + }, ], ); }) @@ -2230,10 +2311,15 @@ mod tests { assert_eq!( multi_phase_events(), vec![ - Event::SignedPhaseStarted { round: 1 }, - Event::UnsignedPhaseStarted { round: 1 }, + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 }, + Event::PhaseTransitioned { + from: Phase::Signed, + to: Phase::Unsigned((true, 25)), + round: 1 + }, Event::SolutionStored { compute: ElectionCompute::Unsigned, + origin: None, prev_ejected: false }, Event::ElectionFinalized { @@ -2243,7 +2329,12 @@ mod tests { sum_stake: 100, sum_stake_squared: 5200 } - } + }, + Event::PhaseTransitioned { + from: Phase::Unsigned((true, 25)), + to: Phase::Off, + round: 2 + }, ], ); }) @@ -2270,8 +2361,12 @@ mod tests { assert_eq!( multi_phase_events(), vec![ - Event::SignedPhaseStarted { round: 1 }, - Event::UnsignedPhaseStarted { round: 1 }, + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 }, + Event::PhaseTransitioned { + from: Phase::Signed, + to: Phase::Unsigned((true, 25)), + round: 1 + }, Event::ElectionFinalized { compute: ElectionCompute::Fallback, score: ElectionScore { @@ -2279,7 +2374,12 @@ mod tests { sum_stake: 0, sum_stake_squared: 0 } - } + }, + Event::PhaseTransitioned { + from: Phase::Unsigned((true, 25)), + to: Phase::Off, + round: 2 + }, ] ); }); @@ -2299,9 +2399,18 @@ mod tests { assert_eq!( multi_phase_events(), vec![ - Event::SignedPhaseStarted { round: 1 }, - Event::UnsignedPhaseStarted { round: 1 }, - Event::ElectionFailed + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 }, + Event::PhaseTransitioned { + from: Phase::Signed, + to: Phase::Unsigned((true, 25)), + round: 1 + }, + Event::ElectionFailed, + Event::PhaseTransitioned { + from: Phase::Unsigned((true, 25)), + to: Phase::Emergency, + round: 1 + }, ] ); }) @@ -2339,17 +2448,28 @@ mod tests { assert_eq!( multi_phase_events(), vec![ - Event::SignedPhaseStarted { round: 1 }, - Event::UnsignedPhaseStarted { round: 1 }, + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 }, + Event::PhaseTransitioned { + from: Phase::Signed, + to: Phase::Unsigned((true, 25)), + round: 1 + }, Event::ElectionFailed, + Event::PhaseTransitioned { + from: Phase::Unsigned((true, 25)), + to: Phase::Emergency, + round: 1 + }, Event::SolutionStored { compute: ElectionCompute::Fallback, + origin: None, prev_ejected: false }, Event::ElectionFinalized { compute: ElectionCompute::Fallback, score: Default::default() - } + }, + Event::PhaseTransitioned { from: Phase::Emergency, to: Phase::Off, round: 2 }, ] ); }) @@ -2375,10 +2495,17 @@ mod tests { assert_eq!( multi_phase_events(), - vec![Event::ElectionFinalized { - compute: ElectionCompute::Fallback, - score: ElectionScore { minimal_stake: 0, sum_stake: 0, sum_stake_squared: 0 } - }] + vec![ + Event::ElectionFinalized { + compute: ElectionCompute::Fallback, + score: ElectionScore { + minimal_stake: 0, + sum_stake: 0, + sum_stake_squared: 0 + } + }, + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Off, round: 2 }, + ] ); }); } @@ -2404,7 +2531,13 @@ mod tests { assert_eq!(err, ElectionError::Fallback("NoFallback.")); assert_eq!(MultiPhase::current_phase(), Phase::Emergency); - assert_eq!(multi_phase_events(), vec![Event::ElectionFailed]); + assert_eq!( + multi_phase_events(), + vec![ + Event::ElectionFailed, + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Emergency, round: 1 } + ] + ); }); } diff --git a/frame/election-provider-multi-phase/src/signed.rs b/frame/election-provider-multi-phase/src/signed.rs index 12d39e83b6..895f3670a7 100644 --- a/frame/election-provider-multi-phase/src/signed.rs +++ b/frame/election-provider-multi-phase/src/signed.rs @@ -620,8 +620,12 @@ mod tests { assert_eq!( multi_phase_events(), vec![ - Event::SignedPhaseStarted { round: 1 }, - Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false } + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 }, + Event::SolutionStored { + compute: ElectionCompute::Signed, + origin: Some(99), + prev_ejected: false + } ] ); }) @@ -645,8 +649,12 @@ mod tests { assert_eq!( multi_phase_events(), vec![ - Event::SignedPhaseStarted { round: 1 }, - Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 }, + Event::SolutionStored { + compute: ElectionCompute::Signed, + origin: Some(99), + prev_ejected: false + }, Event::Rewarded { account: 99, value: 7 } ] ); @@ -676,8 +684,12 @@ mod tests { assert_eq!( multi_phase_events(), vec![ - Event::SignedPhaseStarted { round: 1 }, - Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 }, + Event::SolutionStored { + compute: ElectionCompute::Signed, + origin: Some(99), + prev_ejected: false + }, Event::Slashed { account: 99, value: 5 } ] ); @@ -713,9 +725,17 @@ mod tests { assert_eq!( multi_phase_events(), vec![ - Event::SignedPhaseStarted { round: 1 }, - Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, - Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 }, + Event::SolutionStored { + compute: ElectionCompute::Signed, + origin: Some(99), + prev_ejected: false + }, + Event::SolutionStored { + compute: ElectionCompute::Signed, + origin: Some(999), + prev_ejected: false + }, Event::Rewarded { account: 99, value: 7 } ] ); @@ -788,12 +808,32 @@ mod tests { assert_eq!( multi_phase_events(), vec![ - Event::SignedPhaseStarted { round: 1 }, - Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, - Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, - Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, - Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, - Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 }, + Event::SolutionStored { + compute: ElectionCompute::Signed, + origin: Some(99), + prev_ejected: false + }, + Event::SolutionStored { + compute: ElectionCompute::Signed, + origin: Some(100), + prev_ejected: false + }, + Event::SolutionStored { + compute: ElectionCompute::Signed, + origin: Some(101), + prev_ejected: false + }, + Event::SolutionStored { + compute: ElectionCompute::Signed, + origin: Some(102), + prev_ejected: false + }, + Event::SolutionStored { + compute: ElectionCompute::Signed, + origin: Some(103), + prev_ejected: false + }, Event::Rewarded { account: 99, value: 7 }, Event::ElectionFinalized { compute: ElectionCompute::Signed, @@ -856,13 +896,15 @@ mod tests { assert_eq!( multi_phase_events(), vec![ - Event::SignedPhaseStarted { round: 1 }, + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 }, Event::SolutionStored { compute: ElectionCompute::Signed, + origin: Some(99), prev_ejected: false }, Event::SolutionStored { compute: ElectionCompute::Signed, + origin: Some(99), prev_ejected: true } ] @@ -1112,12 +1154,28 @@ mod tests { assert_eq!( multi_phase_events(), vec![ - Event::SignedPhaseStarted { round: 1 }, - Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, - Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, - Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 }, + Event::SolutionStored { + compute: ElectionCompute::Signed, + origin: Some(100), + prev_ejected: false + }, + Event::SolutionStored { + compute: ElectionCompute::Signed, + origin: Some(101), + prev_ejected: false + }, + Event::SolutionStored { + compute: ElectionCompute::Signed, + origin: Some(102), + prev_ejected: false + }, Event::Rewarded { account: 100, value: 7 }, - Event::UnsignedPhaseStarted { round: 1 } + Event::PhaseTransitioned { + from: Phase::Signed, + to: Phase::Unsigned((true, 25)), + round: 1 + }, ] ); }) @@ -1170,10 +1228,22 @@ mod tests { assert_eq!( multi_phase_events(), vec![ - Event::SignedPhaseStarted { round: 1 }, - Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, - Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, - Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 }, + Event::SolutionStored { + compute: ElectionCompute::Signed, + origin: Some(99), + prev_ejected: false + }, + Event::SolutionStored { + compute: ElectionCompute::Signed, + origin: Some(999), + prev_ejected: false + }, + Event::SolutionStored { + compute: ElectionCompute::Signed, + origin: Some(9999), + prev_ejected: false + }, Event::Slashed { account: 999, value: 5 }, Event::Rewarded { account: 99, value: 7 } ] @@ -1304,8 +1374,12 @@ mod tests { assert_eq!( multi_phase_events(), vec![ - Event::SignedPhaseStarted { round: 1 }, - Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 }, + Event::SolutionStored { + compute: ElectionCompute::Signed, + origin: Some(99), + prev_ejected: false + }, Event::Rewarded { account: 99, value: 7 } ] ); diff --git a/frame/election-provider-multi-phase/src/unsigned.rs b/frame/election-provider-multi-phase/src/unsigned.rs index 7340605dfe..cdc71f7bf5 100644 --- a/frame/election-provider-multi-phase/src/unsigned.rs +++ b/frame/election-provider-multi-phase/src/unsigned.rs @@ -1055,7 +1055,7 @@ mod tests { Runtime, RuntimeCall, RuntimeOrigin, System, TestNposSolution, TrimHelpers, UnsignedPhase, }, - CurrentPhase, Event, InvalidTransaction, Phase, QueuedSolution, TransactionSource, + Event, InvalidTransaction, Phase, QueuedSolution, TransactionSource, TransactionValidityError, }; use codec::Decode; @@ -1128,7 +1128,7 @@ mod tests { assert!(::pre_dispatch(&call).is_ok()); // unsigned -- but not enabled. - >::put(Phase::Unsigned((false, 25))); + MultiPhase::phase_transition(Phase::Unsigned((false, 25))); assert!(MultiPhase::current_phase().is_unsigned()); assert!(matches!( ::validate_unsigned( @@ -1321,10 +1321,15 @@ mod tests { assert_eq!( multi_phase_events(), vec![ - Event::SignedPhaseStarted { round: 1 }, - Event::UnsignedPhaseStarted { round: 1 }, + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 }, + Event::PhaseTransitioned { + from: Phase::Signed, + to: Phase::Unsigned((true, 25)), + round: 1 + }, Event::SolutionStored { compute: ElectionCompute::Unsigned, + origin: None, prev_ejected: false } ] @@ -1661,7 +1666,7 @@ mod tests { let current_block = block_plus(offchain_repeat * 2 + 2); // force the unsigned phase to start on the current block. - CurrentPhase::::set(Phase::Unsigned((true, current_block))); + MultiPhase::phase_transition(Phase::Unsigned((true, current_block))); // clear the cache and create a solution since we are on the first block of the unsigned // phase. @@ -1673,8 +1678,12 @@ mod tests { assert_eq!( multi_phase_events(), vec![ - Event::SignedPhaseStarted { round: 1 }, - Event::UnsignedPhaseStarted { round: 1 }, + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Signed, round: 1 }, + Event::PhaseTransitioned { + from: Phase::Signed, + to: Phase::Unsigned((true, 25)), + round: 1 + }, Event::ElectionFinalized { compute: ElectionCompute::Fallback, score: ElectionScore { @@ -1682,7 +1691,12 @@ mod tests { sum_stake: 0, sum_stake_squared: 0 } - } + }, + Event::PhaseTransitioned { + from: Phase::Unsigned((true, 25)), + to: Phase::Unsigned((true, 37)), + round: 1 + }, ] ); }) diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index db9aeba6fb..7af9b0aaaa 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -339,7 +339,7 @@ impl Pallet { if maybe_new_era_validators.is_some() && matches!(ForceEra::::get(), Forcing::ForceNew) { - ForceEra::::put(Forcing::NotForcing); + Self::set_force_era(Forcing::NotForcing); } maybe_new_era_validators @@ -717,11 +717,18 @@ impl Pallet { } } + /// Helper to set a new `ForceEra` mode. + pub(crate) fn set_force_era(mode: Forcing) { + log!(info, "Setting force era mode {:?}.", mode); + ForceEra::::put(mode); + Self::deposit_event(Event::::ForceEra { mode }); + } + /// Ensures that at the end of the current session there will be a new era. pub(crate) fn ensure_new_era() { match ForceEra::::get() { Forcing::ForceAlways | Forcing::ForceNew => (), - _ => ForceEra::::put(Forcing::ForceNew), + _ => Self::set_force_era(Forcing::ForceNew), } } diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index ce4f249462..c0c18b40cf 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -714,6 +714,8 @@ pub mod pallet { PayoutStarted { era_index: EraIndex, validator_stash: T::AccountId }, /// A validator has set their preferences. ValidatorPrefsSet { stash: T::AccountId, prefs: ValidatorPrefs }, + /// A new force era mode was set. + ForceEra { mode: Forcing }, } #[pallet::error] @@ -1377,7 +1379,7 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::force_no_eras())] pub fn force_no_eras(origin: OriginFor) -> DispatchResult { ensure_root(origin)?; - ForceEra::::put(Forcing::ForceNone); + Self::set_force_era(Forcing::ForceNone); Ok(()) } @@ -1401,7 +1403,7 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::force_new_era())] pub fn force_new_era(origin: OriginFor) -> DispatchResult { ensure_root(origin)?; - ForceEra::::put(Forcing::ForceNew); + Self::set_force_era(Forcing::ForceNew); Ok(()) } @@ -1452,7 +1454,7 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::force_new_era_always())] pub fn force_new_era_always(origin: OriginFor) -> DispatchResult { ensure_root(origin)?; - ForceEra::::put(Forcing::ForceAlways); + Self::set_force_era(Forcing::ForceAlways); Ok(()) } diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 46c3c97441..acd4189528 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -899,7 +899,7 @@ fn forcing_new_era_works() { assert_eq!(active_era(), 1); // no era change. - ForceEra::::put(Forcing::ForceNone); + Staking::set_force_era(Forcing::ForceNone); start_session(4); assert_eq!(active_era(), 1); @@ -915,7 +915,7 @@ fn forcing_new_era_works() { // back to normal. // this immediately starts a new session. - ForceEra::::put(Forcing::NotForcing); + Staking::set_force_era(Forcing::NotForcing); start_session(8); assert_eq!(active_era(), 1); @@ -923,7 +923,7 @@ fn forcing_new_era_works() { start_session(9); assert_eq!(active_era(), 2); // forceful change - ForceEra::::put(Forcing::ForceAlways); + Staking::set_force_era(Forcing::ForceAlways); start_session(10); assert_eq!(active_era(), 2); @@ -935,7 +935,7 @@ fn forcing_new_era_works() { assert_eq!(active_era(), 4); // just one forceful change - ForceEra::::put(Forcing::ForceNew); + Staking::set_force_era(Forcing::ForceNew); start_session(13); assert_eq!(active_era(), 5); assert_eq!(ForceEra::::get(), Forcing::NotForcing); @@ -2303,7 +2303,7 @@ fn era_is_always_same_length() { ); let session = Session::current_index(); - ForceEra::::put(Forcing::ForceNew); + Staking::set_force_era(Forcing::ForceNew); advance_session(); advance_session(); assert_eq!(current_era(), 3); @@ -2914,7 +2914,10 @@ fn deferred_slashes_are_deferred() { staking_events_since_last_call().as_slice(), &[ Event::Chilled { stash: 11 }, + Event::ForceEra { mode: Forcing::ForceNew }, Event::SlashReported { validator: 11, slash_era: 1, .. }, + Event::StakersElected, + Event::ForceEra { mode: Forcing::NotForcing }, .., Event::Slashed { staker: 11, amount: 100 }, Event::Slashed { staker: 101, amount: 12 } @@ -2949,6 +2952,7 @@ fn retroactive_deferred_slashes_two_eras_before() { staking_events_since_last_call().as_slice(), &[ Event::Chilled { stash: 11 }, + Event::ForceEra { mode: Forcing::ForceNew }, Event::SlashReported { validator: 11, slash_era: 1, .. }, .., Event::Slashed { staker: 11, amount: 100 }, @@ -3251,6 +3255,7 @@ fn slash_kicks_validators_not_nominators_and_disables_nominator_for_kicked_valid Event::StakersElected, Event::EraPaid { era_index: 0, validator_payout: 11075, remainder: 33225 }, Event::Chilled { stash: 11 }, + Event::ForceEra { mode: Forcing::ForceNew }, Event::SlashReported { validator: 11, fraction: Perbill::from_percent(10), @@ -3318,6 +3323,7 @@ fn non_slashable_offence_doesnt_disable_validator() { Event::StakersElected, Event::EraPaid { era_index: 0, validator_payout: 11075, remainder: 33225 }, Event::Chilled { stash: 11 }, + Event::ForceEra { mode: Forcing::ForceNew }, Event::SlashReported { validator: 11, fraction: Perbill::from_percent(0), @@ -3380,6 +3386,7 @@ fn slashing_independent_of_disabling_validator() { Event::StakersElected, Event::EraPaid { era_index: 0, validator_payout: 11075, remainder: 33225 }, Event::Chilled { stash: 11 }, + Event::ForceEra { mode: Forcing::ForceNew }, Event::SlashReported { validator: 11, fraction: Perbill::from_percent(0), @@ -4662,8 +4669,15 @@ mod election_data_provider { MinimumValidatorCount::::put(2); run_to_block(55); assert_eq!(Staking::next_election_prediction(System::block_number()), 55 + 25); - assert_eq!(staking_events().len(), 6); - assert_eq!(*staking_events().last().unwrap(), Event::StakersElected); + assert_eq!(staking_events().len(), 10); + assert_eq!( + *staking_events().last().unwrap(), + Event::ForceEra { mode: Forcing::NotForcing } + ); + assert_eq!( + *staking_events().get(staking_events().len() - 2).unwrap(), + Event::StakersElected + ); // The new era has been planned, forcing is changed from `ForceNew` to `NotForcing`. assert_eq!(ForceEra::::get(), Forcing::NotForcing); }) From dc18d1380e6cbb0c140cf907f66e9aa143cfff4c Mon Sep 17 00:00:00 2001 From: Vlad Date: Mon, 9 Jan 2023 21:36:27 +0400 Subject: [PATCH 118/127] CI: configure `cargo-nextest`'s reporter options explicitly (#13104) --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a0b1464bae..f6347e51e6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -53,6 +53,8 @@ variables: RUSTY_CACHIER_SINGLE_BRANCH: master RUSTY_CACHIER_DONT_OPERATE_ON_MAIN_BRANCH: "true" RUSTY_CACHIER_COMPRESSION_METHOD: zstd + NEXTEST_FAILURE_OUTPUT: immediate-final + NEXTEST_SUCCESS_OUTPUT: final ZOMBIENET_IMAGE: "docker.io/paritytech/zombienet:v1.3.22" default: From b85246bf1156f9f58825f0be48e6086128cc0bbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Enrique=20Mu=C3=B1oz=20Mart=C3=ADn?= Date: Mon, 9 Jan 2023 20:04:24 +0100 Subject: [PATCH 119/127] Add ensure_pow method (#13042) * add ensure_pow method * reexport checked_pow and ensure_pow --- primitives/arithmetic/src/traits.rs | 52 +++++++++++++++++++++++------ primitives/runtime/src/traits.rs | 10 +++--- 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/primitives/arithmetic/src/traits.rs b/primitives/arithmetic/src/traits.rs index 9e9dff8e6f..dfba046754 100644 --- a/primitives/arithmetic/src/traits.rs +++ b/primitives/arithmetic/src/traits.rs @@ -19,9 +19,9 @@ use codec::HasCompact; pub use ensure::{ - Ensure, EnsureAdd, EnsureAddAssign, EnsureDiv, EnsureDivAssign, EnsureFixedPointNumber, - EnsureFrom, EnsureInto, EnsureMul, EnsureMulAssign, EnsureOp, EnsureOpAssign, EnsureSub, - EnsureSubAssign, + ensure_pow, Ensure, EnsureAdd, EnsureAddAssign, EnsureDiv, EnsureDivAssign, + EnsureFixedPointNumber, EnsureFrom, EnsureInto, EnsureMul, EnsureMulAssign, EnsureOp, + EnsureOpAssign, EnsureSub, EnsureSubAssign, }; pub use integer_sqrt::IntegerSquareRoot; pub use num_traits::{ @@ -342,7 +342,7 @@ impl SaturatedConversion for T {} /// The *EnsureOps* family functions follows the same behavior as *CheckedOps* but /// returning an [`ArithmeticError`](crate::ArithmeticError) instead of `None`. mod ensure { - use super::{CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, Zero}; + use super::{checked_pow, CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, One, Zero}; use crate::{ArithmeticError, FixedPointNumber, FixedPointOperand}; /// Performs addition that returns [`ArithmeticError`] instead of wrapping around on overflow. @@ -511,6 +511,27 @@ mod ensure { } } + /// Raises a value to the power of exp, returning `ArithmeticError` if an overflow occurred. + /// + /// Check [`checked_pow`] for more info about border cases. + /// + /// ``` + /// use sp_arithmetic::{traits::ensure_pow, ArithmeticError}; + /// + /// fn overflow() -> Result<(), ArithmeticError> { + /// ensure_pow(2u64, 64)?; + /// Ok(()) + /// } + /// + /// assert_eq!(overflow(), Err(ArithmeticError::Overflow)); + /// ``` + pub fn ensure_pow( + base: T, + exp: usize, + ) -> Result { + checked_pow(base, exp).ok_or(ArithmeticError::Overflow) + } + impl EnsureAdd for T {} impl EnsureSub for T {} impl EnsureMul for T {} @@ -953,6 +974,15 @@ mod tests { test_ensure(values(), &EnsureDiv::ensure_div, &CheckedDiv::checked_div); } + #[test] + fn ensure_pow_works() { + test_ensure( + values().into_iter().map(|(base, exp)| (base, exp as usize)).collect(), + ensure_pow, + |&a, &b| checked_pow(a, b), + ); + } + #[test] fn ensure_add_assign_works() { test_ensure_assign(values(), &EnsureAddAssign::ensure_add_assign, &EnsureAdd::ensure_add); @@ -974,11 +1004,12 @@ mod tests { } /// Test that the ensured function returns the expected un-ensured value. - fn test_ensure(pairs: Vec<(V, V)>, ensured: E, unensured: P) + fn test_ensure(pairs: Vec<(V, W)>, ensured: E, unensured: P) where V: Ensure + core::fmt::Debug + Copy, - E: Fn(V, V) -> Result, - P: Fn(&V, &V) -> Option, + W: Ensure + core::fmt::Debug + Copy, + E: Fn(V, W) -> Result, + P: Fn(&V, &W) -> Option, { for (a, b) in pairs.into_iter() { match ensured(a, b) { @@ -993,11 +1024,12 @@ mod tests { } /// Test that the ensured function modifies `self` to the expected un-ensured value. - fn test_ensure_assign(pairs: Vec<(V, V)>, ensured: E, unensured: P) + fn test_ensure_assign(pairs: Vec<(V, W)>, ensured: E, unensured: P) where V: Ensure + std::panic::RefUnwindSafe + std::panic::UnwindSafe + core::fmt::Debug + Copy, - E: Fn(&mut V, V) -> Result<(), ArithmeticError>, - P: Fn(V, V) -> Result + std::panic::RefUnwindSafe, + W: Ensure + std::panic::RefUnwindSafe + std::panic::UnwindSafe + core::fmt::Debug + Copy, + E: Fn(&mut V, W) -> Result<(), ArithmeticError>, + P: Fn(V, W) -> Result + std::panic::RefUnwindSafe, { for (mut a, b) in pairs.into_iter() { let old_a = a; diff --git a/primitives/runtime/src/traits.rs b/primitives/runtime/src/traits.rs index 8978cdb11c..6eb19683b4 100644 --- a/primitives/runtime/src/traits.rs +++ b/primitives/runtime/src/traits.rs @@ -32,11 +32,11 @@ use impl_trait_for_tuples::impl_for_tuples; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use sp_application_crypto::AppKey; pub use sp_arithmetic::traits::{ - AtLeast32Bit, AtLeast32BitUnsigned, Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedShl, - CheckedShr, CheckedSub, Ensure, EnsureAdd, EnsureAddAssign, EnsureDiv, EnsureDivAssign, - EnsureFixedPointNumber, EnsureFrom, EnsureInto, EnsureMul, EnsureMulAssign, EnsureOp, - EnsureOpAssign, EnsureSub, EnsureSubAssign, IntegerSquareRoot, One, SaturatedConversion, - Saturating, UniqueSaturatedFrom, UniqueSaturatedInto, Zero, + checked_pow, ensure_pow, AtLeast32Bit, AtLeast32BitUnsigned, Bounded, CheckedAdd, CheckedDiv, + CheckedMul, CheckedShl, CheckedShr, CheckedSub, Ensure, EnsureAdd, EnsureAddAssign, EnsureDiv, + EnsureDivAssign, EnsureFixedPointNumber, EnsureFrom, EnsureInto, EnsureMul, EnsureMulAssign, + EnsureOp, EnsureOpAssign, EnsureSub, EnsureSubAssign, IntegerSquareRoot, One, + SaturatedConversion, Saturating, UniqueSaturatedFrom, UniqueSaturatedInto, Zero, }; use sp_core::{self, storage::StateVersion, Hasher, RuntimeDebug, TypeId}; #[doc(hidden)] From 75d10c73daa03c98f3acbeb65203d39586f6cf8f Mon Sep 17 00:00:00 2001 From: alexgparity <115470171+alexgparity@users.noreply.github.com> Date: Tue, 10 Jan 2023 12:32:19 +0100 Subject: [PATCH 120/127] Add solution hint to error message (#13111) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add solution hint to error message * make it compile * Update frame/support/procedural/src/pallet/expand/hooks.rs Co-authored-by: Bastian Köcher * Update frame/support/procedural/src/pallet/expand/storage.rs Co-authored-by: Bastian Köcher * Update frame/support/procedural/src/pallet/expand/storage.rs Co-authored-by: Bastian Köcher * Update frame/support/procedural/src/pallet/expand/storage.rs Co-authored-by: Bastian Köcher Co-authored-by: Bastian Köcher --- frame/support/procedural/src/pallet/expand/hooks.rs | 2 +- frame/support/procedural/src/pallet/expand/storage.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frame/support/procedural/src/pallet/expand/hooks.rs b/frame/support/procedural/src/pallet/expand/hooks.rs index 0aa7c1e7aa..c2b70c5632 100644 --- a/frame/support/procedural/src/pallet/expand/hooks.rs +++ b/frame/support/procedural/src/pallet/expand/hooks.rs @@ -63,7 +63,7 @@ pub fn expand_hooks(def: &mut Def) -> proc_macro2::TokenStream { ::PalletInfo as #frame_support::traits::PalletInfo - >::name::().expect("Every active pallet has a name in the runtime; qed"); + >::name::().expect("No name found for the pallet! This usually means that the pallet wasn't added to `construct_runtime!`."); #frame_support::log::debug!( target: #frame_support::LOG_TARGET, "🩺 try-state pallet {:?}", diff --git a/frame/support/procedural/src/pallet/expand/storage.rs b/frame/support/procedural/src/pallet/expand/storage.rs index 181f35b545..195a62431f 100644 --- a/frame/support/procedural/src/pallet/expand/storage.rs +++ b/frame/support/procedural/src/pallet/expand/storage.rs @@ -535,7 +535,7 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream { ::PalletInfo as #frame_support::traits::PalletInfo >::name::>() - .expect("Every active pallet has a name in the runtime; qed") + .expect("No name found for the pallet in the runtime! This usually means that the pallet wasn't added to `construct_runtime!`.") } const STORAGE_PREFIX: &'static str = #counter_prefix_struct_const; } @@ -569,7 +569,7 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream { ::PalletInfo as #frame_support::traits::PalletInfo >::name::>() - .expect("Every active pallet has a name in the runtime; qed") + .expect("No name found for the pallet in the runtime! This usually means that the pallet wasn't added to `construct_runtime!`.") } const STORAGE_PREFIX: &'static str = #prefix_struct_const; } @@ -648,7 +648,7 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream { ::PalletInfo as #frame_support::traits::PalletInfo >::name::<#pallet_ident<#type_use_gen>>() - .expect("Every active pallet has a name in the runtime; qed"), + .expect("No name found for the pallet in the runtime! This usually means that the pallet wasn't added to `construct_runtime!`."), entries: { #[allow(unused_mut)] let mut entries = #frame_support::sp_std::vec![]; From 4e383428ffc6d4eba173ca257c1aaf53d1ec339c Mon Sep 17 00:00:00 2001 From: JP <77391175+joao-paulo-parity@users.noreply.github.com> Date: Tue, 10 Jan 2023 09:01:44 -0300 Subject: [PATCH 121/127] Avoid unintentionally canceling the scheduled crate publishing job (#13088) * avoid unintentionally canceling the scheduled crate publishing job because publish-crates and publish-crates-manual share the resource group "crates-publishing", any instance of publish-crates-manual cancels a running instance of publish-crates, as demonstrated by https://gitlab.parity.io/parity/mirrors/substrate/-/jobs/2212179 a workaround for that unintended interaction is to avoid creating instances of publish-crates-manual and instead require pipelines to be triggered manually by checking $CI_JOB_MANUAL == "true" * check manual pipelines by $CI_PIPELINE_SOURCE instead of $CI_JOB_MANUAL * make crate-publishing pipelines uninterruptible * use conditional includes to work around interruptible limitations * organize comments * remove interruptible from common pipeline * wip: check include * wip: check include * fix include * fix include * fix include * fix yaml * fix yaml * remove shared common-pipeline * wip: retry common-pipeline * move .default-template to .gitlab-ci.yml * fix the pipeline add comments * fix default-pipeline.yml * revert publish-crates-manual to when: manual * move "needs:" back to publish-crates * avoid manual repetition * improve previous commit * try to avoid manual repetition * fix indentation * minor adjustments * move defaults to top of .gitlab-ci.yml * fix positioning on default in the diff * comments * indentation * Apply suggestions from code review Co-authored-by: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Co-authored-by: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> --- .gitlab-ci.yml | 42 +++++++++++++++++-- .../ci/gitlab/crate-publishing-pipeline.yml | 1 + scripts/ci/gitlab/default-pipeline.yml | 1 + scripts/ci/gitlab/pipeline/publish.yml | 3 +- 4 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 scripts/ci/gitlab/crate-publishing-pipeline.yml create mode 100644 scripts/ci/gitlab/default-pipeline.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f6347e51e6..ecc27d016a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -57,16 +57,30 @@ variables: NEXTEST_SUCCESS_OUTPUT: final ZOMBIENET_IMAGE: "docker.io/paritytech/zombienet:v1.3.22" -default: +.shared-default: &shared-default retry: max: 2 when: - runner_system_failure - unknown_failure - api_failure - interruptible: true cache: {} +.default-pipeline-definitions: + default: + <<: *shared-default + interruptible: true + +.crate-publishing-pipeline-definitions: + default: + <<: *shared-default + # The crate-publishing pipeline defaults to `interruptible: false` so that we'll be able to + # reach and run the publishing jobs despite the "Auto-cancel redundant pipelines" CI setting. + # The setting is relevant because the crate-publishing pipeline runs on `master`, thus future + # pipelines on `master` (e.g. created for new commits or other schedules) might unintendedly + # cancel the publishing jobs or its dependencies before we get to actually publish the crates. + interruptible: false + .collect-artifacts: artifacts: name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}" @@ -215,9 +229,15 @@ default: # this job runs only on nightly pipeline with the mentioned variable, against `master` branch - if: $CI_COMMIT_REF_NAME == "master" && $CI_PIPELINE_SOURCE == "schedule" && $PIPELINE == "nightly" +.crate-publishing-pipeline: + rules: + - if: $CI_COMMIT_REF_NAME != "master" + when: never + .scheduled-crate-publishing-pipeline: rules: - - if: $CI_COMMIT_REF_NAME == "master" && $CI_PIPELINE_SOURCE == "schedule" && $PIPELINE == "automatic-crate-publishing" + - !reference [.crate-publishing-pipeline, rules] + - if: $CI_PIPELINE_SOURCE == "schedule" && $PIPELINE == "automatic-crate-publishing" .crates-publishing-template: stage: test @@ -265,6 +285,22 @@ include: - scripts/ci/gitlab/pipeline/publish.yml # zombienet jobs - scripts/ci/gitlab/pipeline/zombienet.yml + # The crate-publishing pipeline requires a customized `interruptible` configuration. Unfortunately + # `interruptible` can't currently be dynamically set based on variables as per: + # - https://gitlab.com/gitlab-org/gitlab/-/issues/38349 + # - https://gitlab.com/gitlab-org/gitlab/-/issues/194023 + # Thus we work around that limitation by using conditional includes. + # For crate-publishing pipelines: run it with defaults + `interruptible: false`. The WHOLE + # pipeline is made uninterruptible to ensure that test jobs also get a chance to run to + # completion, because the publishing jobs depends on them AS INTENDED: crates should not be + # published before their source code is checked. + - local: scripts/ci/gitlab/crate-publishing-pipeline.yml + rules: + - if: $PIPELINE == "automatic-crate-publishing" + # For normal pipelines: run it with defaults + `interruptible: true` + - local: scripts/ci/gitlab/default-pipeline.yml + rules: + - if: $PIPELINE != "automatic-crate-publishing" #### stage: deploy diff --git a/scripts/ci/gitlab/crate-publishing-pipeline.yml b/scripts/ci/gitlab/crate-publishing-pipeline.yml new file mode 100644 index 0000000000..9d5303952e --- /dev/null +++ b/scripts/ci/gitlab/crate-publishing-pipeline.yml @@ -0,0 +1 @@ +default: !reference [.crate-publishing-pipeline-definitions, default] diff --git a/scripts/ci/gitlab/default-pipeline.yml b/scripts/ci/gitlab/default-pipeline.yml new file mode 100644 index 0000000000..19f6c320c3 --- /dev/null +++ b/scripts/ci/gitlab/default-pipeline.yml @@ -0,0 +1 @@ +default: !reference [.default-pipeline-definitions, default] diff --git a/scripts/ci/gitlab/pipeline/publish.yml b/scripts/ci/gitlab/pipeline/publish.yml index cc7451a9fb..dff1b50862 100644 --- a/scripts/ci/gitlab/pipeline/publish.yml +++ b/scripts/ci/gitlab/pipeline/publish.yml @@ -214,7 +214,7 @@ update-node-template: timeout: 9h # A custom publishing environment is used for us to be able to set up protected secrets # specifically for it - environment: publish-crates + environment: publish-crates script: - rusty-cachier snapshot create - git clone @@ -234,5 +234,6 @@ publish-crates: publish-crates-manual: extends: .publish-crates-template + rules: !reference [.crate-publishing-pipeline, rules] when: manual allow_failure: true From ad844bef26bac6c7d885d8259064b08189572d8f Mon Sep 17 00:00:00 2001 From: Anthony Alaribe Date: Tue, 10 Jan 2023 20:18:12 +0100 Subject: [PATCH 122/127] introduce log-target constant to more frame pallets (#13116) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * introduce log-target constant to more frame pallets * cargo fmt * make LOG_TARGET in session public * Update frame/elections-phragmen/src/lib.rs Co-authored-by: Bastian Köcher * Update frame/elections-phragmen/src/migrations/v3.rs Co-authored-by: Bastian Köcher * Update frame/elections-phragmen/src/migrations/v3.rs Co-authored-by: Bastian Köcher * Update frame/elections-phragmen/src/migrations/v3.rs Co-authored-by: Bastian Köcher * move LOG_TARGET=runtime::session_historical to migrations module, where it's actually used Co-authored-by: Bastian Köcher --- frame/assets/src/lib.rs | 1 + frame/assets/src/migration.rs | 12 +++++++-- frame/balances/src/lib.rs | 6 +++-- frame/balances/src/migration.rs | 14 ++++++++--- frame/collective/src/lib.rs | 8 +++--- frame/collective/src/migrations/v4.rs | 9 ++++--- frame/elections-phragmen/src/lib.rs | 15 ++++------- frame/elections-phragmen/src/migrations/v3.rs | 25 ++++++------------- frame/elections-phragmen/src/migrations/v4.rs | 7 +++--- frame/membership/src/lib.rs | 2 ++ frame/membership/src/migrations/v4.rs | 9 ++++--- frame/session/src/migrations/v1.rs | 10 +++++--- frame/system/src/lib.rs | 16 ++++++------ frame/system/src/migrations/mod.rs | 7 +++--- frame/tips/src/lib.rs | 2 ++ frame/tips/src/migrations/v4.rs | 9 ++++--- frame/uniques/src/lib.rs | 2 ++ frame/uniques/src/migration.rs | 6 ++--- 18 files changed, 89 insertions(+), 71 deletions(-) diff --git a/frame/assets/src/lib.rs b/frame/assets/src/lib.rs index 1853ef75f5..93e416c3be 100644 --- a/frame/assets/src/lib.rs +++ b/frame/assets/src/lib.rs @@ -175,6 +175,7 @@ pub use pallet::*; pub use weights::WeightInfo; type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; +const LOG_TARGET: &str = "runtime::assets"; /// Trait with callbacks that are executed after successfull asset creation or destruction. pub trait AssetsCallback { diff --git a/frame/assets/src/migration.rs b/frame/assets/src/migration.rs index 89f8d39a90..9f8905ceff 100644 --- a/frame/assets/src/migration.rs +++ b/frame/assets/src/migration.rs @@ -75,10 +75,18 @@ pub mod v1 { Some(old_value.migrate_to_v1()) }); current_version.put::>(); - log::info!(target: "runtime::assets", "Upgraded {} pools, storage to version {:?}", translated, current_version); + log::info!( + target: LOG_TARGET, + "Upgraded {} pools, storage to version {:?}", + translated, + current_version + ); T::DbWeight::get().reads_writes(translated + 1, translated + 1) } else { - log::info!(target: "runtime::assets", "Migration did not execute. This probably should be removed"); + log::info!( + target: LOG_TARGET, + "Migration did not execute. This probably should be removed" + ); T::DbWeight::get().reads(1) } } diff --git a/frame/balances/src/lib.rs b/frame/balances/src/lib.rs index 57f76b1ff6..99d77a3e73 100644 --- a/frame/balances/src/lib.rs +++ b/frame/balances/src/lib.rs @@ -193,6 +193,8 @@ pub use weights::WeightInfo; pub use pallet::*; +const LOG_TARGET: &str = "runtime::balances"; + type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; #[frame_support::pallet] @@ -950,7 +952,7 @@ impl, I: 'static> Pallet { if locks.len() as u32 > T::MaxLocks::get() { log::warn!( - target: "runtime::balances", + target: LOG_TARGET, "Warning: A user has more currency locks than expected. \ A runtime configuration adjustment may be needed." ); @@ -985,7 +987,7 @@ impl, I: 'static> Pallet { // since the funds that are under the lock will themselves be stored in the // account and therefore will need a reference. log::warn!( - target: "runtime::balances", + target: LOG_TARGET, "Warning: Attempt to introduce lock consumer reference, yet no providers. \ This is unexpected but should be safe." ); diff --git a/frame/balances/src/migration.rs b/frame/balances/src/migration.rs index b660ec9fd3..80d1bcd35b 100644 --- a/frame/balances/src/migration.rs +++ b/frame/balances/src/migration.rs @@ -40,10 +40,13 @@ fn migrate_v0_to_v1, I: 'static>(accounts: &[T::AccountId]) -> Weig // Set storage version to `1`. StorageVersion::new(1).put::>(); - log::info!(target: "runtime::balances", "Storage to version 1"); + log::info!(target: LOG_TARGET, "Storage to version 1"); T::DbWeight::get().reads_writes(2 + accounts.len() as u64, 3) } else { - log::info!(target: "runtime::balances", "Migration did not execute. This probably should be removed"); + log::info!( + target: LOG_TARGET, + "Migration did not execute. This probably should be removed" + ); T::DbWeight::get().reads(1) } } @@ -87,10 +90,13 @@ impl, I: 'static> OnRuntimeUpgrade for ResetInactive { // Set storage version to `0`. StorageVersion::new(0).put::>(); - log::info!(target: "runtime::balances", "Storage to version 0"); + log::info!(target: LOG_TARGET, "Storage to version 0"); T::DbWeight::get().reads_writes(1, 2) } else { - log::info!(target: "runtime::balances", "Migration did not execute. This probably should be removed"); + log::info!( + target: LOG_TARGET, + "Migration did not execute. This probably should be removed" + ); T::DbWeight::get().reads(1) } } diff --git a/frame/collective/src/lib.rs b/frame/collective/src/lib.rs index c522b71891..0fe05dbb01 100644 --- a/frame/collective/src/lib.rs +++ b/frame/collective/src/lib.rs @@ -71,6 +71,8 @@ pub mod weights; pub use pallet::*; pub use weights::WeightInfo; +const LOG_TARGET: &str = "runtime::collective"; + /// Simple index type for proposal counting. pub type ProposalIndex = u32; @@ -390,7 +392,7 @@ pub mod pallet { ensure_root(origin)?; if new_members.len() > T::MaxMembers::get() as usize { log::error!( - target: "runtime::collective", + target: LOG_TARGET, "New members count ({}) exceeds maximum amount of members expected ({}).", new_members.len(), T::MaxMembers::get(), @@ -400,7 +402,7 @@ pub mod pallet { let old = Members::::get(); if old.len() > old_count as usize { log::warn!( - target: "runtime::collective", + target: LOG_TARGET, "Wrong count used to estimate set_members weight. expected ({}) vs actual ({})", old_count, old.len(), @@ -1040,7 +1042,7 @@ impl, I: 'static> ChangeMembers for Pallet { ) { if new.len() > T::MaxMembers::get() as usize { log::error!( - target: "runtime::collective", + target: LOG_TARGET, "New members count ({}) exceeds maximum amount of members expected ({}).", new.len(), T::MaxMembers::get(), diff --git a/frame/collective/src/migrations/v4.rs b/frame/collective/src/migrations/v4.rs index 483c3f9fa9..2756b3fd15 100644 --- a/frame/collective/src/migrations/v4.rs +++ b/frame/collective/src/migrations/v4.rs @@ -17,6 +17,7 @@ use sp_io::hashing::twox_128; +use super::super::LOG_TARGET; use frame_support::{ traits::{ Get, GetStorageVersion, PalletInfoAccess, StorageVersion, @@ -42,7 +43,7 @@ pub fn migrate::on_chain_storage_version(); log::info!( - target: "runtime::collective", + target: LOG_TARGET, "Running migration to v4 for collective with storage version {:?}", on_chain_storage_version, ); @@ -66,7 +67,7 @@ pub fn migrate::BlockWeights::get().max_block } else { log::warn!( - target: "runtime::collective", + target: LOG_TARGET, "Attempted to apply migration to v4 but failed because storage version is {:?}", on_chain_storage_version, ); @@ -138,7 +139,7 @@ pub fn post_migrate>(old_ fn log_migration(stage: &str, old_pallet_name: &str, new_pallet_name: &str) { log::info!( - target: "runtime::collective", + target: LOG_TARGET, "{}, prefix: '{}' ==> '{}'", stage, old_pallet_name, diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 1cfdc25fd9..1a020adb28 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -122,6 +122,8 @@ pub use weights::WeightInfo; /// All migrations. pub mod migrations; +const LOG_TARGET: &str = "runtime::elections-phragmen"; + /// The maximum votes allowed per voter. pub const MAXIMUM_VOTE: usize = 16; @@ -789,10 +791,7 @@ impl Pallet { } else { // overlap. This can never happen. If so, it seems like our intended replacement // is already a member, so not much more to do. - log::error!( - target: "runtime::elections-phragmen", - "A member seems to also be a runner-up.", - ); + log::error!(target: LOG_TARGET, "A member seems to also be a runner-up."); } next_best }); @@ -939,7 +938,7 @@ impl Pallet { Ok(_) => (), Err(_) => { log::error!( - target: "runtime::elections-phragmen", + target: LOG_TARGET, "Failed to run election. Number of voters exceeded", ); Self::deposit_event(Event::ElectionError); @@ -1103,11 +1102,7 @@ impl Pallet { >::mutate(|v| *v += 1); }) .map_err(|e| { - log::error!( - target: "runtime::elections-phragmen", - "Failed to run election [{:?}].", - e, - ); + log::error!(target: LOG_TARGET, "Failed to run election [{:?}].", e,); Self::deposit_event(Event::ElectionError); }); diff --git a/frame/elections-phragmen/src/migrations/v3.rs b/frame/elections-phragmen/src/migrations/v3.rs index 9ec9c6e7ee..e48cd6c1a8 100644 --- a/frame/elections-phragmen/src/migrations/v3.rs +++ b/frame/elections-phragmen/src/migrations/v3.rs @@ -17,6 +17,7 @@ //! Migrations to version [`3.0.0`], as denoted by the changelog. +use super::super::LOG_TARGET; use crate::{Config, Pallet}; use codec::{Decode, Encode, FullCodec}; use frame_support::{ @@ -88,7 +89,7 @@ pub fn apply( ) -> Weight { let storage_version = StorageVersion::get::>(); log::info!( - target: "runtime::elections-phragmen", + target: LOG_TARGET, "Running migration for elections-phragmen with storage version {:?}", storage_version, ); @@ -104,7 +105,7 @@ pub fn apply( Weight::MAX } else { log::warn!( - target: "runtime::elections-phragmen", + target: LOG_TARGET, "Attempted to apply migration to V3 but failed because storage version is {:?}", storage_version, ); @@ -118,22 +119,14 @@ pub fn migrate_voters_to_recorded_deposit(old_deposit: V:: Some(Voter { votes, stake, deposit: old_deposit }) }); - log::info!( - target: "runtime::elections-phragmen", - "migrated {} voter accounts.", - >::iter().count(), - ); + log::info!(target: LOG_TARGET, "migrated {} voter accounts.", >::iter().count()); } /// Migrate all candidates to recorded deposit. pub fn migrate_candidates_to_recorded_deposit(old_deposit: V::Balance) { let _ = >::translate::, _>(|maybe_old_candidates| { maybe_old_candidates.map(|old_candidates| { - log::info!( - target: "runtime::elections-phragmen", - "migrated {} candidate accounts.", - old_candidates.len(), - ); + log::info!(target: LOG_TARGET, "migrated {} candidate accounts.", old_candidates.len()); old_candidates.into_iter().map(|c| (c, old_deposit)).collect::>() }) }); @@ -143,11 +136,7 @@ pub fn migrate_candidates_to_recorded_deposit(old_deposit: pub fn migrate_members_to_recorded_deposit(old_deposit: V::Balance) { let _ = >::translate::, _>(|maybe_old_members| { maybe_old_members.map(|old_members| { - log::info!( - target: "runtime::elections-phragmen", - "migrated {} member accounts.", - old_members.len(), - ); + log::info!(target: LOG_TARGET, "migrated {} member accounts.", old_members.len()); old_members .into_iter() .map(|(who, stake)| SeatHolder { who, stake, deposit: old_deposit }) @@ -162,7 +151,7 @@ pub fn migrate_runners_up_to_recorded_deposit(old_deposit: |maybe_old_runners_up| { maybe_old_runners_up.map(|old_runners_up| { log::info!( - target: "runtime::elections-phragmen", + target: LOG_TARGET, "migrated {} runner-up accounts.", old_runners_up.len(), ); diff --git a/frame/elections-phragmen/src/migrations/v4.rs b/frame/elections-phragmen/src/migrations/v4.rs index 76ef630706..535a9fb820 100644 --- a/frame/elections-phragmen/src/migrations/v4.rs +++ b/frame/elections-phragmen/src/migrations/v4.rs @@ -17,6 +17,7 @@ //! Migrations to version [`4.0.0`], as denoted by the changelog. +use super::super::LOG_TARGET; use frame_support::{ traits::{Get, StorageVersion}, weights::Weight, @@ -35,14 +36,14 @@ pub const OLD_PREFIX: &[u8] = b"PhragmenElection"; pub fn migrate>(new_pallet_name: N) -> Weight { if new_pallet_name.as_ref().as_bytes() == OLD_PREFIX { log::info!( - target: "runtime::elections-phragmen", + target: LOG_TARGET, "New pallet name is equal to the old prefix. No migration needs to be done.", ); return Weight::zero() } let storage_version = StorageVersion::get::>(); log::info!( - target: "runtime::elections-phragmen", + target: LOG_TARGET, "Running migration to v4 for elections-phragmen with storage version {:?}", storage_version, ); @@ -59,7 +60,7 @@ pub fn migrate>(new_pallet_name: N) -> Weight { ::BlockWeights::get().max_block } else { log::warn!( - target: "runtime::elections-phragmen", + target: LOG_TARGET, "Attempted to apply migration to v4 but failed because storage version is {:?}", storage_version, ); diff --git a/frame/membership/src/lib.rs b/frame/membership/src/lib.rs index 8531af120e..934abb996d 100644 --- a/frame/membership/src/lib.rs +++ b/frame/membership/src/lib.rs @@ -36,6 +36,8 @@ pub mod weights; pub use pallet::*; pub use weights::WeightInfo; +const LOG_TARGET: &str = "runtime::membership"; + type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; #[frame_support::pallet] diff --git a/frame/membership/src/migrations/v4.rs b/frame/membership/src/migrations/v4.rs index 5b8735aa2b..cbc58cd6ae 100644 --- a/frame/membership/src/migrations/v4.rs +++ b/frame/membership/src/migrations/v4.rs @@ -15,6 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use super::super::LOG_TARGET; use sp_io::hashing::twox_128; use frame_support::{ @@ -43,7 +44,7 @@ pub fn migrate::on_chain_storage_version(); log::info!( - target: "runtime::membership", + target: LOG_TARGET, "Running migration to v4 for membership with storage version {:?}", on_chain_storage_version, ); @@ -67,7 +68,7 @@ pub fn migrate::BlockWeights::get().max_block } else { log::warn!( - target: "runtime::membership", + target: LOG_TARGET, "Attempted to apply migration to v4 but failed because storage version is {:?}", on_chain_storage_version, ); @@ -139,7 +140,7 @@ pub fn post_migrate>(old_pallet_name: N, new fn log_migration(stage: &str, old_pallet_name: &str, new_pallet_name: &str) { log::info!( - target: "runtime::membership", + target: LOG_TARGET, "{}, prefix: '{}' ==> '{}'", stage, old_pallet_name, diff --git a/frame/session/src/migrations/v1.rs b/frame/session/src/migrations/v1.rs index c0dce422fe..6689ca7299 100644 --- a/frame/session/src/migrations/v1.rs +++ b/frame/session/src/migrations/v1.rs @@ -29,6 +29,8 @@ use frame_support::{ use crate::historical as pallet_session_historical; +const LOG_TARGET: &str = "runtime::session_historical"; + const OLD_PREFIX: &str = "Session"; /// Migrate the entire storage of this pallet to a new prefix. @@ -44,7 +46,7 @@ pub fn migrate::on_chain_storage_version(); log::info!( - target: "runtime::session_historical", + target: LOG_TARGET, "Running migration to v1 for session_historical with storage version {:?}", on_chain_storage_version, ); @@ -78,7 +80,7 @@ pub fn migrate::BlockWeights::get().max_block } else { log::warn!( - target: "runtime::session_historical", + target: LOG_TARGET, "Attempted to apply migration to v1 but failed because storage version is {:?}", on_chain_storage_version, ); @@ -184,7 +186,7 @@ pub fn post_migrate< fn log_migration(stage: &str, storage_prefix: &[u8], old_pallet_name: &str, new_pallet_name: &str) { log::info!( - target: "runtime::session_historical", + target: LOG_TARGET, "{} prefix of storage '{}': '{}' ==> '{}'", stage, str::from_utf8(storage_prefix).unwrap_or(""), diff --git a/frame/system/src/lib.rs b/frame/system/src/lib.rs index c790d33add..3909b1e9c5 100644 --- a/frame/system/src/lib.rs +++ b/frame/system/src/lib.rs @@ -129,6 +129,8 @@ pub use extensions::check_mortality::CheckMortality as CheckEra; pub use frame_support::dispatch::RawOrigin; pub use weights::WeightInfo; +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 @@ -1075,7 +1077,7 @@ impl Pallet { if account.providers == 0 { // Logic error - cannot decrement beyond zero. log::error!( - target: "runtime::system", + target: LOG_TARGET, "Logic error: Unexpected underflow in reducing provider", ); account.providers = 1; @@ -1101,7 +1103,7 @@ impl Pallet { } } else { log::error!( - target: "runtime::system", + target: LOG_TARGET, "Logic error: Account already dead when reducing provider", ); Ok(DecRefStatus::Reaped) @@ -1133,7 +1135,7 @@ impl Pallet { if account.sufficients == 0 { // Logic error - cannot decrement beyond zero. log::error!( - target: "runtime::system", + target: LOG_TARGET, "Logic error: Unexpected underflow in reducing sufficients", ); } @@ -1150,7 +1152,7 @@ impl Pallet { } } else { log::error!( - target: "runtime::system", + target: LOG_TARGET, "Logic error: Account already dead when reducing provider", ); DecRefStatus::Reaped @@ -1215,7 +1217,7 @@ impl Pallet { a.consumers -= 1; } else { log::error!( - target: "runtime::system", + target: LOG_TARGET, "Logic error: Unexpected underflow in reducing consumer", ); } @@ -1337,7 +1339,7 @@ impl Pallet { /// resulting header for this block. pub fn finalize() -> T::Header { log::debug!( - target: "runtime::system", + target: LOG_TARGET, "[{:?}] {} extrinsics, length: {} (normal {}%, op: {}%, mandatory {}%) / normal weight:\ {} ({}%) op weight {} ({}%) / mandatory weight {} ({}%)", Self::block_number(), @@ -1547,7 +1549,7 @@ impl Pallet { Ok(_) => Event::ExtrinsicSuccess { dispatch_info: info }, Err(err) => { log::trace!( - target: "runtime::system", + target: LOG_TARGET, "Extrinsic failed at block({:?}): {:?}", Self::block_number(), err, diff --git a/frame/system/src/migrations/mod.rs b/frame/system/src/migrations/mod.rs index 15746d7376..90b88de1ab 100644 --- a/frame/system/src/migrations/mod.rs +++ b/frame/system/src/migrations/mod.rs @@ -17,6 +17,7 @@ //! Migrate the reference counting state. +use super::LOG_TARGET; use crate::{Config, Pallet}; use codec::{Decode, Encode, FullCodec}; use frame_support::{ @@ -75,7 +76,7 @@ pub fn migrate_from_single_u8_to_triple_ref_count() -> Wei Some(AccountInfo { nonce, consumers: rc as RefCount, providers: 1, sufficients: 0, data }) }); log::info!( - target: "runtime::system", + target: LOG_TARGET, "Applied migration from single u8 to triple reference counting to {:?} elements.", translated ); @@ -94,7 +95,7 @@ pub fn migrate_from_single_to_triple_ref_count() -> Weight }, ); log::info!( - target: "runtime::system", + target: LOG_TARGET, "Applied migration from single to triple reference counting to {:?} elements.", translated ); @@ -112,7 +113,7 @@ pub fn migrate_from_dual_to_triple_ref_count() -> Weight { }, ); log::info!( - target: "runtime::system", + target: LOG_TARGET, "Applied migration from dual to triple reference counting to {:?} elements.", translated ); diff --git a/frame/tips/src/lib.rs b/frame/tips/src/lib.rs index dd9ebc9813..43002c7b5f 100644 --- a/frame/tips/src/lib.rs +++ b/frame/tips/src/lib.rs @@ -78,6 +78,8 @@ use frame_support::{ pub use pallet::*; pub use weights::WeightInfo; +const LOG_TARGET: &str = "runtime::tips"; + pub type BalanceOf = pallet_treasury::BalanceOf; pub type NegativeImbalanceOf = pallet_treasury::NegativeImbalanceOf; type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; diff --git a/frame/tips/src/migrations/v4.rs b/frame/tips/src/migrations/v4.rs index 5e10fa7dd2..0107e96a41 100644 --- a/frame/tips/src/migrations/v4.rs +++ b/frame/tips/src/migrations/v4.rs @@ -18,6 +18,7 @@ use sp_io::hashing::twox_128; use sp_std::str; +use super::super::LOG_TARGET; use frame_support::{ storage::StoragePrefixedMap, traits::{ @@ -46,7 +47,7 @@ pub fn migrate::on_chain_storage_version(); log::info!( - target: "runtime::tips", + target: LOG_TARGET, "Running migration to v4 for tips with storage version {:?}", on_chain_storage_version, ); @@ -80,7 +81,7 @@ pub fn migrate::BlockWeights::get().max_block } else { log::warn!( - target: "runtime::tips", + target: LOG_TARGET, "Attempted to apply migration to v4 but failed because storage version is {:?}", on_chain_storage_version, ); @@ -185,7 +186,7 @@ pub fn post_migrate< fn log_migration(stage: &str, storage_prefix: &[u8], old_pallet_name: &str, new_pallet_name: &str) { log::info!( - target: "runtime::tips", + target: LOG_TARGET, "{} prefix of storage '{}': '{}' ==> '{}'", stage, str::from_utf8(storage_prefix).unwrap_or(""), diff --git a/frame/uniques/src/lib.rs b/frame/uniques/src/lib.rs index 6b21477be4..da0f0e0560 100644 --- a/frame/uniques/src/lib.rs +++ b/frame/uniques/src/lib.rs @@ -60,6 +60,8 @@ pub use pallet::*; pub use types::*; pub use weights::WeightInfo; +const LOG_TARGET: &str = "runtime::uniques"; + type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; #[frame_support::pallet] diff --git a/frame/uniques/src/migration.rs b/frame/uniques/src/migration.rs index 8a2a0ef808..fd98c030d7 100644 --- a/frame/uniques/src/migration.rs +++ b/frame/uniques/src/migration.rs @@ -24,7 +24,7 @@ pub fn migrate_to_v1, I: 'static, P: GetStorageVersion + PalletInfo ) -> frame_support::weights::Weight { let on_chain_storage_version =

::on_chain_storage_version(); log::info!( - target: "runtime::uniques", + target: LOG_TARGET, "Running migration storage v1 for uniques with storage version {:?}", on_chain_storage_version, ); @@ -37,7 +37,7 @@ pub fn migrate_to_v1, I: 'static, P: GetStorageVersion + PalletInfo } StorageVersion::new(1).put::

(); log::info!( - target: "runtime::uniques", + target: LOG_TARGET, "Running migration storage v1 for uniques with storage version {:?} was complete", on_chain_storage_version, ); @@ -45,7 +45,7 @@ pub fn migrate_to_v1, I: 'static, P: GetStorageVersion + PalletInfo T::DbWeight::get().reads_writes(count as u64 + 1, count as u64 + 1) } else { log::warn!( - target: "runtime::uniques", + target: LOG_TARGET, "Attempted to apply migration to v1 but failed because storage version is {:?}", on_chain_storage_version, ); From cb63d3dafc986faf5f2181288737ba8513253a02 Mon Sep 17 00:00:00 2001 From: Vladimir Istyufeev Date: Wed, 11 Jan 2023 01:08:28 +0400 Subject: [PATCH 123/127] Update tests for 1.66 and switch CI to stable Rust 1.66 (#13118) * Update UI tests for 1.66 * Fix `test_enum` assertion for Rust 1.66 * Fix another `test_enum` assertion for Rust 1.66 * Fix another `test_enum` assertion for Rust 1.66 * Fix another `test_enum` assertion for Rust 1.66 --- .gitlab-ci.yml | 2 +- .../construct_runtime_ui/invalid_module_details.stderr | 4 ++-- frame/support/test/tests/derive_no_bound.rs | 8 ++++---- .../test/tests/pallet_ui/call_invalid_return.stderr | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ecc27d016a..90298297a8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -48,7 +48,7 @@ variables: CARGO_INCREMENTAL: 0 DOCKER_OS: "debian:stretch" ARCH: "x86_64" - CI_IMAGE: "paritytech/ci-linux:production" + CI_IMAGE: "paritytech/ci-linux@sha256:9140bc3c843a8b12a3bcf6f5886346536092795bbadfd7f1836362cb28dfcc71" BUILDAH_IMAGE: "quay.io/buildah/stable:v1.27" RUSTY_CACHIER_SINGLE_BRANCH: master RUSTY_CACHIER_DONT_OPERATE_ON_MAIN_BRANCH: "true" diff --git a/frame/support/test/tests/construct_runtime_ui/invalid_module_details.stderr b/frame/support/test/tests/construct_runtime_ui/invalid_module_details.stderr index db96b8749c..0a20cf4e39 100644 --- a/frame/support/test/tests/construct_runtime_ui/invalid_module_details.stderr +++ b/frame/support/test/tests/construct_runtime_ui/invalid_module_details.stderr @@ -1,5 +1,5 @@ error: Unexpected tokens, expected one of `::$ident` `::{`, `exclude_parts`, `use_parts`, `=`, `,` - --> $DIR/invalid_module_details.rs:9:17 + --> tests/construct_runtime_ui/invalid_module_details.rs:9:17 | 9 | system: System::(), - | ^^ + | ^ diff --git a/frame/support/test/tests/derive_no_bound.rs b/frame/support/test/tests/derive_no_bound.rs index f891b3a2d2..9162b5013b 100644 --- a/frame/support/test/tests/derive_no_bound.rs +++ b/frame/support/test/tests/derive_no_bound.rs @@ -72,7 +72,7 @@ fn test_struct_named() { assert_eq!(a_2, a_1); assert_eq!( format!("{:?}", a_1), - String::from("StructNamed { a: 1, b: 2, c: 3, phantom: PhantomData }") + String::from("StructNamed { a: 1, b: 2, c: 3, phantom: PhantomData<(derive_no_bound::ImplNone, derive_no_bound::ImplNone)> }") ); let b = StructNamed:: { @@ -103,7 +103,7 @@ fn test_struct_unnamed() { assert_eq!(a_2.1, 2); assert_eq!(a_2.2, 3); assert_eq!(a_2, a_1); - assert_eq!(format!("{:?}", a_1), String::from("StructUnnamed(1, 2, 3, PhantomData)")); + assert_eq!(format!("{:?}", a_1), String::from("StructUnnamed(1, 2, 3, PhantomData<(derive_no_bound::ImplNone, derive_no_bound::ImplNone)>)")); let b = StructUnnamed::(1, 2, 4, Default::default()); @@ -211,11 +211,11 @@ fn test_enum() { assert_eq!( format!("{:?}", variant_0), - String::from("Enum::VariantUnnamed(1, 2, 3, PhantomData)"), + String::from("Enum::VariantUnnamed(1, 2, 3, PhantomData<(derive_no_bound::ImplNone, derive_no_bound::ImplNone)>)"), ); assert_eq!( format!("{:?}", variant_1), - String::from("Enum::VariantNamed { a: 1, b: 2, c: 3, phantom: PhantomData }"), + String::from("Enum::VariantNamed { a: 1, b: 2, c: 3, phantom: PhantomData<(derive_no_bound::ImplNone, derive_no_bound::ImplNone)> }"), ); assert_eq!(format!("{:?}", variant_2), String::from("Enum::VariantUnit")); assert_eq!(format!("{:?}", variant_3), String::from("Enum::VariantUnit2")); diff --git a/frame/support/test/tests/pallet_ui/call_invalid_return.stderr b/frame/support/test/tests/pallet_ui/call_invalid_return.stderr index 6a851ed3fc..8803bbba01 100644 --- a/frame/support/test/tests/pallet_ui/call_invalid_return.stderr +++ b/frame/support/test/tests/pallet_ui/call_invalid_return.stderr @@ -1,5 +1,5 @@ error: expected `DispatchResultWithPostInfo` or `DispatchResult` - --> $DIR/call_invalid_return.rs:17:39 + --> tests/pallet_ui/call_invalid_return.rs:17:39 | 17 | pub fn foo(origin: OriginFor) -> ::DispatchResult { todo!() } - | ^^ + | ^ From 946507ba9ef13e263534176b7b74e26fc56efbd4 Mon Sep 17 00:00:00 2001 From: JP <77391175+joao-paulo-parity@users.noreply.github.com> Date: Wed, 11 Jan 2023 06:03:24 -0300 Subject: [PATCH 124/127] remove update-node-template (#13100) will be moved to polkadot's pipeline ref https://github.com/paritytech/release-engineering/issues/142 --- scripts/ci/gitlab/pipeline/publish.yml | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/scripts/ci/gitlab/pipeline/publish.yml b/scripts/ci/gitlab/pipeline/publish.yml index dff1b50862..d1a7514d17 100644 --- a/scripts/ci/gitlab/pipeline/publish.yml +++ b/scripts/ci/gitlab/pipeline/publish.yml @@ -182,20 +182,6 @@ publish-draft-release: - ./scripts/ci/gitlab/publish_draft_release.sh allow_failure: true -# Ref: https://github.com/paritytech/opstooling/issues/111 -update-node-template: - stage: publish - extends: .kubernetes-env - rules: - - if: $CI_COMMIT_REF_NAME =~ /^polkadot-v[0-9]+\.[0-9]+.*$/ # i.e. polkadot-v1.0.99, polkadot-v2.1rc1 - script: - - git clone --depth=1 --branch="$PIPELINE_SCRIPTS_TAG" https://github.com/paritytech/pipeline-scripts - - ./pipeline-scripts/update_substrate_template.sh - --repo-name "substrate-node-template" - --template-path "bin/node-template" - --github-api-token "$GITHUB_TOKEN" - --polkadot-branch "$CI_COMMIT_REF_NAME" - .publish-crates-template: stage: publish extends: .crates-publishing-template From f38bd6671d460293c93062cc1e4fe9e9e490cb29 Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Tue, 7 Feb 2023 19:15:01 +0100 Subject: [PATCH 125/127] Fix block pruning (#13323) --- client/db/src/lib.rs | 86 +++++++++++++++++++++++++++++++++----------- 1 file changed, 65 insertions(+), 21 deletions(-) diff --git a/client/db/src/lib.rs b/client/db/src/lib.rs index 99fa786e04..23e1103389 100644 --- a/client/db/src/lib.rs +++ b/client/db/src/lib.rs @@ -1709,7 +1709,12 @@ impl Backend { } let new_displaced = self.blockchain.leaves.write().finalize_height(f_num); - self.prune_blocks(transaction, f_num, &new_displaced)?; + self.prune_blocks( + transaction, + f_num, + f_hash, + &new_displaced, + )?; Ok(()) } @@ -1717,7 +1722,8 @@ impl Backend { fn prune_blocks( &self, transaction: &mut Transaction, - finalized: NumberFor, + finalized_number: NumberFor, + finalized_hash: Block::Hash, displaced: &FinalizationOutcome>, ) -> ClientResult<()> { match self.blocks_pruning { @@ -1725,14 +1731,14 @@ impl Backend { BlocksPruning::Some(blocks_pruning) => { // Always keep the last finalized block let keep = std::cmp::max(blocks_pruning, 1); - if finalized >= keep.into() { - let number = finalized.saturating_sub(keep.into()); + if finalized_number >= keep.into() { + let number = finalized_number.saturating_sub(keep.into()); self.prune_block(transaction, BlockId::::number(number))?; } - self.prune_displaced_branches(transaction, finalized, displaced)?; + self.prune_displaced_branches(transaction, finalized_hash, displaced)?; }, BlocksPruning::KeepFinalized => { - self.prune_displaced_branches(transaction, finalized, displaced)?; + self.prune_displaced_branches(transaction, finalized_hash, displaced)?; }, } Ok(()) @@ -1741,26 +1747,20 @@ impl Backend { fn prune_displaced_branches( &self, transaction: &mut Transaction, - finalized: NumberFor, + finalized: Block::Hash, displaced: &FinalizationOutcome>, ) -> ClientResult<()> { // Discard all blocks from displaced branches for h in displaced.leaves() { - let mut number = finalized; - let mut hash = *h; - // Follow displaced chains back until we reach a finalized block. - // Since leaves are discarded due to finality, they can't have parents - // that are canonical, but not yet finalized. So we stop deleting as soon as - // we reach canonical chain. - while self.blockchain.hash(number)? != Some(hash) { - match self.blockchain.header(hash)? { - Some(header) => { - self.prune_block(transaction, BlockId::::hash(hash))?; - number = header.number().saturating_sub(One::one()); - hash = *header.parent_hash(); + match sp_blockchain::tree_route(&self.blockchain, *h, finalized) { + Ok(tree_route) => + for r in tree_route.retracted() { + self.prune_block(transaction, BlockId::::hash(r.hash))?; }, - None => break, - } + Err(sp_blockchain::Error::UnknownBlock(_)) => { + // Sometimes routes can't be calculated. E.g. after warp sync. + }, + Err(e) => Err(e)?, } } Ok(()) @@ -3407,6 +3407,50 @@ pub(crate) mod tests { assert_eq!(Some(vec![4.into()]), bc.body(blocks[4]).unwrap()); } + #[test] + fn prune_blocks_on_finalize_and_reorg() { + // 0 - 1b + // \ - 1a - 2a - 3a + // \ - 2b + + let backend = Backend::::new_test_with_tx_storage(BlocksPruning::Some(10), 10); + + let make_block = |index, parent, val: u64| { + insert_block(&backend, index, parent, None, H256::random(), vec![val.into()], None) + .unwrap() + }; + + let block_0 = make_block(0, Default::default(), 0x00); + let block_1a = make_block(1, block_0, 0x1a); + let block_1b = make_block(1, block_0, 0x1b); + let block_2a = make_block(2, block_1a, 0x2a); + let block_2b = make_block(2, block_1a, 0x2b); + let block_3a = make_block(3, block_2a, 0x3a); + + // Make sure 1b is head + let mut op = backend.begin_operation().unwrap(); + backend.begin_state_operation(&mut op, block_0).unwrap(); + op.mark_head(block_1b).unwrap(); + backend.commit_operation(op).unwrap(); + + // Finalize 3a + let mut op = backend.begin_operation().unwrap(); + backend.begin_state_operation(&mut op, block_0).unwrap(); + op.mark_head(block_3a).unwrap(); + op.mark_finalized(block_1a, None).unwrap(); + op.mark_finalized(block_2a, None).unwrap(); + op.mark_finalized(block_3a, None).unwrap(); + backend.commit_operation(op).unwrap(); + + let bc = backend.blockchain(); + assert_eq!(None, bc.body(block_1b).unwrap()); + assert_eq!(None, bc.body(block_2b).unwrap()); + assert_eq!(Some(vec![0x00.into()]), bc.body(block_0).unwrap()); + assert_eq!(Some(vec![0x1a.into()]), bc.body(block_1a).unwrap()); + assert_eq!(Some(vec![0x2a.into()]), bc.body(block_2a).unwrap()); + assert_eq!(Some(vec![0x3a.into()]), bc.body(block_3a).unwrap()); + } + #[test] fn indexed_data_block_body() { let backend = Backend::::new_test_with_tx_storage(BlocksPruning::Some(1), 10); From 7f2f75df51035fa345abc37634d6850c2a636d33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BBuk?= Date: Tue, 14 Feb 2023 12:13:14 +0100 Subject: [PATCH 126/127] Fix call_index warnings --- frame/liberland-legislation/src/lib.rs | 5 ++ frame/llm/src/lib.rs | 76 ++++++++++++++------------ 2 files changed, 47 insertions(+), 34 deletions(-) diff --git a/frame/liberland-legislation/src/lib.rs b/frame/liberland-legislation/src/lib.rs index 0d25b9750e..eb679de6de 100644 --- a/frame/liberland-legislation/src/lib.rs +++ b/frame/liberland-legislation/src/lib.rs @@ -184,6 +184,7 @@ pub mod pallet { /// `index` already exists. /// /// Emits `LawAdded`. + #[pallet::call_index(0)] #[pallet::weight(10_000)] pub fn add_law( origin: OriginFor, @@ -218,6 +219,7 @@ pub mod pallet { /// - `index`: Index of the legislation. /// /// Emits `LawRepealed`. + #[pallet::call_index(1)] #[pallet::weight(10_000)] pub fn repeal_law(origin: OriginFor, tier: u32, index: u32) -> DispatchResult { ensure!(tier < InvalidTier as u32, Error::::InvalidTier); @@ -250,6 +252,7 @@ pub mod pallet { /// Will fail with `NonCitizen` if caller isn't a valid citizen. /// /// Emits `VetoSubmitted`. + #[pallet::call_index(2)] #[pallet::weight(10_000)] pub fn submit_veto(origin: OriginFor, tier: u32, index: u32) -> DispatchResult { let account = ensure_signed(origin)?; @@ -273,6 +276,7 @@ pub mod pallet { /// - `index`: Index of the legislation. /// /// Emits `VetoReverted`. + #[pallet::call_index(3)] #[pallet::weight(10_000)] pub fn revert_veto(origin: OriginFor, tier: u32, index: u32) -> DispatchResult { let account = ensure_signed(origin)?; @@ -298,6 +302,7 @@ pub mod pallet { /// doesn't meet requirements for given Tier. /// /// Emits `LawRepealedByHeadcountVeto`. + #[pallet::call_index(4)] #[pallet::weight(10_000)] pub fn trigger_headcount_veto( origin: OriginFor, diff --git a/frame/llm/src/lib.rs b/frame/llm/src/lib.rs index 0e29b6e7ea..4b62fd86e4 100644 --- a/frame/llm/src/lib.rs +++ b/frame/llm/src/lib.rs @@ -332,40 +332,6 @@ pub mod pallet { #[pallet::call] impl Pallet { - /// Release LLM to account by transferring LLM from **Vault**. - /// Development only, to be removed/restricted. DO NOT USE IN PROD. - /// - /// - `to_account`: Account to release LLM to - /// - `amount`: Amount of LLM to release - /// - /// Emits: `Transferred` from `pallet-assets` - #[pallet::weight(10_000)] - pub fn fake_send( - origin: OriginFor, - to_account: T::AccountId, - amount: T::Balance, - ) -> DispatchResult { - ensure_signed(origin)?; - Self::transfer_from_vault(to_account, amount) - } - - /// Wrapper over `pallet-assets` `transfer`. Transfers LLM from sender - /// to specified account. - /// - /// - `to_account`: Account to transfer LLM to - /// - `amount`: Amount of LLM to transfer - /// - /// Emits: `Transferred` from `pallet-assets` - #[pallet::weight(10_000)] - pub fn send_llm( - origin: OriginFor, - to_account: T::AccountId, - amount: T::Balance, - ) -> DispatchResult { - let sender = ensure_signed(origin.clone())?; - Self::transfer(sender, to_account, amount) - } - /// Lock LLM into politics pool, a.k.a. politipool. Internally it /// transfers LLM to **Politipool** account. /// @@ -374,6 +340,7 @@ pub mod pallet { /// Emits: /// * `LLMPoliticsLocked` /// * `Transferred` from `pallet-assets` + #[pallet::call_index(0)] #[pallet::weight(10_000)] pub fn politics_lock(origin: OriginFor, amount: T::Balance) -> DispatchResult { let sender = ensure_signed(origin.clone())?; @@ -390,6 +357,7 @@ pub mod pallet { /// Emits: /// * `LLMPoliticsLocked` /// * `Transferred` from `pallet-assets` + #[pallet::call_index(1)] #[pallet::weight(10_000)] pub fn politics_unlock(origin: OriginFor) -> DispatchResult { let sender: T::AccountId = ensure_signed(origin.clone())?; @@ -425,6 +393,7 @@ pub mod pallet { /// - `amount`: Amount to transfer. /// /// Emits: `Transferred` from `pallet-assets` + #[pallet::call_index(2)] #[pallet::weight(10_000)] pub fn treasury_llm_transfer( origin: OriginFor, @@ -454,6 +423,7 @@ pub mod pallet { /// - `amount`: Amount to transfer. /// /// Emits: `Transferred` from `pallet-assets` + #[pallet::call_index(3)] #[pallet::weight(10_000)] pub fn treasury_llm_transfer_to_politipool( origin: OriginFor, @@ -482,6 +452,7 @@ pub mod pallet { /// - `amount`: Amount to transfer. /// /// Emits: `Transferred` from `pallet-assets` + #[pallet::call_index(4)] #[pallet::weight(10_000)] pub fn send_llm_to_politipool( origin: OriginFor, @@ -494,7 +465,44 @@ pub mod pallet { Self::do_politics_lock(to_account, amount) } + /// Wrapper over `pallet-assets` `transfer`. Transfers LLM from sender + /// to specified account. + /// + /// - `to_account`: Account to transfer LLM to + /// - `amount`: Amount of LLM to transfer + /// + /// Emits: `Transferred` from `pallet-assets` + #[pallet::call_index(5)] + #[pallet::weight(10_000)] + pub fn send_llm( + origin: OriginFor, + to_account: T::AccountId, + amount: T::Balance, + ) -> DispatchResult { + let sender = ensure_signed(origin.clone())?; + Self::transfer(sender, to_account, amount) + } + + /// Release LLM to account by transferring LLM from **Vault**. + /// Development only, to be removed/restricted. DO NOT USE IN PROD. + /// + /// - `to_account`: Account to release LLM to + /// - `amount`: Amount of LLM to release + /// + /// Emits: `Transferred` from `pallet-assets` + #[pallet::call_index(6)] + #[pallet::weight(10_000)] + pub fn fake_send( + origin: OriginFor, + to_account: T::AccountId, + amount: T::Balance, + ) -> DispatchResult { + ensure_signed(origin)?; + Self::transfer_from_vault(to_account, amount) + } + /// Allow the senate to approve transfers + #[pallet::call_index(7)] #[pallet::weight(10_000)] pub fn approve_transfer( _origin: OriginFor, From cb3b214661c8b225e15ff7f220a21f518297c195 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BBuk?= Date: Tue, 14 Feb 2023 13:42:15 +0100 Subject: [PATCH 127/127] Migrations Bump spec version to 7 --- bin/node/cli/tests/telemetry.rs | 2 +- bin/node/runtime/src/lib.rs | 67 ++++++++++++++++++++++++++++++++- frame/democracy/src/lib.rs | 16 +------- frame/llm/src/lib.rs | 16 +------- 4 files changed, 69 insertions(+), 32 deletions(-) diff --git a/bin/node/cli/tests/telemetry.rs b/bin/node/cli/tests/telemetry.rs index 1f52d8d60b..bc4e1e405a 100644 --- a/bin/node/cli/tests/telemetry.rs +++ b/bin/node/cli/tests/telemetry.rs @@ -84,7 +84,7 @@ async fn telemetry_works() { // Stop the process kill(Pid::from_raw(substrate.id().try_into().unwrap()), SIGINT).unwrap(); - assert!(common::wait_for(&mut substrate, 60).map(|x| x.success()).unwrap_or_default()); + assert!(common::wait_for(&mut substrate, 120).map(|x| x.success()).unwrap_or_default()); let output = substrate.wait_with_output().unwrap(); diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 109ab86d67..b6ec260a8d 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -132,7 +132,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to 0. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 6, + spec_version: 7, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -1677,10 +1677,75 @@ pub type Executive = frame_executive::Executive< Migrations, >; + +// staking is only expected to be used by polkadot/kusama/et al., so they didn't +// bother to bump the default storage version. as such, we have V7_0_0 version +// set, but it's actually the layout of V12. Fix it before running V13 migration. +mod staking_v12 { + use super::*; + use frame_support::{storage_alias, traits::OnRuntimeUpgrade, pallet_prelude::*}; + + #[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] + enum ObsoleteReleases { + V1_0_0Ancient, + V2_0_0, + V3_0_0, + V4_0_0, + V5_0_0, + V6_0_0, + V7_0_0, + V8_0_0, + V9_0_0, + V10_0_0, + V11_0_0, + V12_0_0, + } + + impl Default for ObsoleteReleases { + fn default() -> Self { + Self::V12_0_0 + } + } + + #[storage_alias] + type StorageVersion = StorageValue, ObsoleteReleases, ValueQuery>; + + pub struct Migration(sp_std::marker::PhantomData); + impl OnRuntimeUpgrade for Migration { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, &'static str> { + frame_support::ensure!( + StorageVersion::::get() == ObsoleteReleases::V7_0_0, + "Expected v7 before upgrading to v12" + ); + + Ok(Default::default()) + } + + fn on_runtime_upgrade() -> Weight { + StorageVersion::::put(ObsoleteReleases::V12_0_0); + log::info!("Migrated pallet-staking StorageVersion to V12"); + T::DbWeight::get().reads_writes(1, 1) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_: Vec) -> Result<(), &'static str> { + frame_support::ensure!( + StorageVersion::::get() == ObsoleteReleases::V12_0_0, + "Failed to upgrade to v12" + ); + Ok(()) + } + } +} + // All migrations executed on runtime upgrade as a nested tuple of types implementing // `OnRuntimeUpgrade`. type Migrations = ( pallet_contracts::Migration, + pallet_referenda::migration::v1::MigrateV0ToV1, + staking_v12::Migration, + pallet_staking::migrations::v13::MigrateToV13, ); /// MMR helper types. diff --git a/frame/democracy/src/lib.rs b/frame/democracy/src/lib.rs index 5ac6738488..3762802a5e 100644 --- a/frame/democracy/src/lib.rs +++ b/frame/democracy/src/lib.rs @@ -161,7 +161,7 @@ use frame_support::{ ensure, traits::{ defensive_prelude::*, - EnsureOrigin, OnRuntimeUpgrade, + EnsureOrigin, schedule::{v3::Named as ScheduleNamed, DispatchTime}, Bounded, Currency, Get, LockIdentifier, LockableCurrency, QueryPreimage, ReservableCurrency, StorePreimage, @@ -559,20 +559,6 @@ pub mod pallet { fn on_initialize(n: T::BlockNumber) -> Weight { Self::begin_block(n) } - - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, &'static str> { - migrations::v3::Migration::::pre_upgrade() - } - - fn on_runtime_upgrade() -> Weight { - migrations::v3::Migration::::on_runtime_upgrade() - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade(state: Vec) -> Result<(), &'static str> { - migrations::v3::Migration::::post_upgrade(state) - } } #[pallet::call] diff --git a/frame/llm/src/lib.rs b/frame/llm/src/lib.rs index 4b62fd86e4..a6f8b72ca7 100644 --- a/frame/llm/src/lib.rs +++ b/frame/llm/src/lib.rs @@ -160,7 +160,7 @@ pub mod pallet { use super::*; use frame_support::{ pallet_prelude::{DispatchResult, *}, - traits::{OnRuntimeUpgrade, fungibles::Mutate}, + traits::fungibles::Mutate, PalletId, }; use frame_system::{ensure_signed, pallet_prelude::*}; @@ -314,20 +314,6 @@ pub mod pallet { Self::maybe_release(blocknumber); Weight::zero() } - - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, &'static str> { - migrations::v1::Migration::::pre_upgrade() - } - - fn on_runtime_upgrade() -> Weight { - migrations::v1::Migration::::on_runtime_upgrade() - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade(state: Vec) -> Result<(), &'static str> { - migrations::v1::Migration::::post_upgrade(state) - } } #[pallet::call]